This is certainly a long-term project, but I'm now interested in the structure of FRX files. Here's what I know about them (or what I think I know).
Any property that's non-ANSI will be placed in the FRX file.
Any property that might actually be ANSI but is quite large, will be placed in the FRX file. I don't know the size limit.
Pictures, icons, and the like will be placed in these FRX files.
We can put our own information into them via a UserControl by defining the property as a Variant.
Even arrays placed into Variants will work, and be placed into these FRX files.
The FRM file gets the information out with a "SomeForm.frx":0000 where the 0000 is an offset into the FRX file.
There doesn't seem to be any header within these FRX files.
Now, here's what I don't know:
How is all the information placed into these FRX files?
Is it the same as it's placed into a RES file?
Is it the same as if we were to write the information into a file opened Binary?
Is each piece of information followed by some terminator?
Sometimes, the FRX references in the FRM file look like: $"SomeForm.frx":0000. Does the dollar-sign indicate we're looking for a string?
If we're looking for a string, are they Unicode?
If we're looking for a string, they must be terminated. Are they null terminated?
Specifically, I'm considering writing an FRX string editor. This editor would allow one to edit the strings in an FRX file and then save them, possibly saving a longer string than the original string. I'd need to reliably find the strings, know how to re-save them, and also know how to reliably patch up the offsets in the FRM file.
Any insights would be greatly appreciated.
Elroy
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
Well, I think of FRX files as an extension of the property bags. Property bags are a memory thing, whereas FRM and FRX files are on disk. I'd say that the non-code part of a FRM file, combined with the FRX file, makes up the property bag.
Best Regards,
Elroy
Also, any FRM/FRX pair will typically have many property bags of information.
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
There are a number of threads on this topic where people have tried to decipher what the format of these private propertybag structures is. You might get a few answers there, or better yet (since I doubt it has been fully cracked) the benefit of some of the guesses. They are propertybags of propertybags.
PropertyBags are all about serialization, for either transmission or persistence to disk. They are not a "memory thing."
I have a Form with two picture boxes, Picture1 and Picture2. Picture1 was loaded with a .bmp and Picture2 was loaded with a .gif.
Now, I looked at the Form.frm file and I found this:
Code:
Begin VB.PictureBox Picture2
Height = 1335
Left = 3165
Picture = "Form1.frx":0000
ScaleHeight = 1275
ScaleWidth = 1395
TabIndex = 2
Top = 1755
Width = 1455
End
Begin VB.PictureBox Picture1
Height = 915
Left = 1650
Picture = "Form1.frx":08CA
ScaleHeight = 855
ScaleWidth = 1215
TabIndex = 1
Top = 1335
Width = 1275
End
Those numbers in red are the offsets in the .FRX file for those two images.
So, I open Form1.frx and I find this:
At offset 0000:
C6 08 00 00 6C 74 00 00 BE 08 00 00 GIF8
At offset 08CA:
26 27 04 00 6C 74 00 00 1E 27 04 00 BM
The header appears to be 12-bytes followed by the actual image data
Ok, so what does this header info mean?
Well, knowing a little about computer numbers I knew something about number being in
some sort of reverse order (I think it's called Little Indian or Big Indian, IDK which is which)
and I also knew that somewhere in this string of mumble jumbo hex there had to be the size of the
image data. So I right mouse clicked on the two image files on the disk at looked at their
properties to get the data size.
The .gif file has a sise of 2,738 and the .bmp has a size of 272,158
So I converted these sizes to hex and I get this:
2,738 = &H08BE and 272,158 = &H04271E
If you reverse the digits in the header on every 4-bytes you get this:
I think you're right about a 12 byte header to each entry (but no overall header).
Just to clear a bit up too. It's Endian, not Indian. Also, Endian typically refers to bits, but it's also sometimes used to refer to bytes. And basically, Little-Endian = smaller parts of a number are lower in memory (and vice-versa). Big-Endian = larger parts of the number are lower in memory (and vice-versa), i.e., reversed. VB6 handles pretty much everything as Little-Endian. (One exception is the Decimal type, which is a Mixed-Endian.)
Hmmm, upon further inspection, it appears that you sometimes only get a four-byte header, that's only the size. Also, it appears that the offsets are HEX.
Here's a screenshot of an FRX with two RTF strings in it. The two offsets from the FRM are 0000 and 0120.
EDIT1: Also, clearly, strings are not null terminated. If all I want to edit is strings, this might be all I need to know, but not sure.
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
@Elroy. In the code bank, I have a project to extract images from frx, ctx, etc. There are some notes/comments in the code that may help regarding extraction of other stuff. Do note that lots of stuff can be in those files: list/combo items, byte arrays, large string values and more.
Insomnia is just a byproduct of, "It can't be done"
Do we know that, if I make a string longer and just shift everything out, and then patch up the offsets in the FRX, are we good to go? Do we have to do any kind of alignment (four byte, etc.)?
I'll definitely look at your project. It looks like a fast-running start on what I'm trying to do.
Thanks,
Elroy
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
It's still a good start. I've cut out the RES stuff so I can focus exclusively on FRX files.
You basically scan every byte for the double-word of &H0000746C. I believe I've got to take a different approach of bouncing from item to item to also find the strings.
I'm hoping to just enhance your tree structure to also include a Strings section.
Best Regards,
Elroy
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
Thanks Dex. However, it turns out, that code is only focusing on pics as well. I sort of suspected that though.
I think I'm getting these FRX files sorted though. Each frx-item just has a four-byte header, and it's the size. If it's a picture, there's an additional header, and LaVolpe's code handles all of that.
But I need to back up and focus on that initial header (i.e., size), and that's how I'll find the strings. For these FRX files, it seems that, if it's not a pic, it's a string, no other alternative. I say this because there is no additional header on the strings.
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
For these FRX files, it seems that, if it's not a pic, it's a string, no other alternative. I say this because there is no additional header on the strings.
Wouldn't bet on it. Fonts (stdFont objects) and byte arrays can exist in there along with List & ItemData properties of combo/listboxes. I'm sure there are other things that can be saved there too.
Insomnia is just a byproduct of, "It can't be done"
Elroy, gotta ask. Where do you see this project going?
Typically, someone wants to edit a string, they'd open their project and edit the property and save. I'd think building a project that attempts to read another project's frm/frx files for outside editing wouldn't be in high demand. Maybe there's a niche somewhere? Even in that project I created that extracts image content, the only niche I foresaw were scenarios where a project could not be loaded, i.e., corrupted frm file for example, with assumption the frx isn't corrupted.
Simply extracting a string, for example, editing it and updating the frx correctly will likely not be enough. What if the string is for a label, button or some other control's caption property? Couldn't that negatively affect the appearance of that control (clipping, wordwrap, etc)? If so, the user would likely need to load the project in VB anyway to adjust appearance.
No reply necessary. Just pointing out that I think real-world usage of such a project is likely low. However, I enjoy puzzles and if the main reason for this was self-education, then I completely understand.
To the best of my knowledge, this is the most sophisticated one of the whole bunch...
(have looked at quite a few other frx-Parsers - found them all lacking, and finally wrote one)
I think it is currently the only parser, which does not need any "hints" from the
accompanying *.frm or *.ctl Files (about the Control- or Property-Type, or at which Offset an entry starts).
One can feed it a standalone *.frx, or a *.ctx, let it do its work -
and after it is through with the Parsing, one can enumerate and query the Parser-Class
about the content and content-types it has found.
The Content-Retrieval-Function will always hand out a ByteArray (no matter whether the
entry-type was an Image, or a Text, or RTFText or a List or Binary.
The different entry-types can also be queried (identify themselves over an Enum).
Public Enum eEntryType
frxBinary
frxImage
frxTextA
frxTextW
frxTextRTF
frxList
End Enum
In case an entry is of type frxImage, one can query additional info about the ImageType over:
Property Get ImageExtensionByIndex(ByVal IndexZeroBased As Long) As String
Below I'm posting the code of the cFRX-Class again (it currently depends on the cStream-Class
from vbRichClient, but this can be replaced by ones own Stream-Implementation, or by
"ByteArrayBased-StreamLike-Functions" in a *.bas module or something.
Code:
Option Explicit 'Parser for *.frx- and *.ctx-Files (Olaf Schmidt, 2017)
Public Enum eEntryType
frxBinary
frxImage
frxTextA
frxTextW
frxTextRTF
frxList
End Enum
Private Type tFrxEntry
EntryType As eEntryType
Offset As Long
Length As Long
Content() As Byte
ImageExtension As String
End Type
Private mFileName As String, mShortFileName As String, EntriesCount As Long, Entries() As tFrxEntry, Strm As cStream
Public Sub Parse(FileName As String)
mFileName = FileName
mShortFileName = New_c.FSO.GetFileNameFromFullPath(FileName)
EntriesCount = 0: Erase Entries
Set Strm = New_c.FSO.OpenFileStream(FileName, STRM_READ Or STRM_SHARE_DENY_NONE)
Do While ReadNextEntry: Loop
Set Strm = Nothing
End Sub
Public Property Get FileName() As String
FileName = mFileName
End Property
Public Property Get ShortFileName() As String
ShortFileName = mShortFileName
End Property
Public Property Get Count() As Long
Count = EntriesCount
End Property
Public Property Get EntryTypeByIndex(ByVal IndexZeroBased As Long) As eEntryType
EntryTypeByIndex = Entries(IndexZeroBased).EntryType
End Property
Public Property Get EntryTypeStringByIndex(ByVal IndexZeroBased As Long) As String
Select Case Entries(IndexZeroBased).EntryType
Case frxBinary: EntryTypeStringByIndex = "Binary"
Case frxImage: EntryTypeStringByIndex = "Image"
Case frxTextA: EntryTypeStringByIndex = "TextA"
Case frxTextW: EntryTypeStringByIndex = "TextW"
Case frxTextRTF: EntryTypeStringByIndex = "TextRTF"
Case frxList: EntryTypeStringByIndex = "List"
End Select
End Property
Public Property Get OffsetByIndex(ByVal IndexZeroBased As Long) As Long
OffsetByIndex = Entries(IndexZeroBased).Offset
End Property
Public Property Get LengthByIndex(ByVal IndexZeroBased As Long) As Long
LengthByIndex = Entries(IndexZeroBased).Length
End Property
Public Property Get ContentByIndex(ByVal IndexZeroBased As Long) As Byte()
ContentByIndex = Entries(IndexZeroBased).Content
End Property
Public Property Get ImageExtensionByIndex(ByVal IndexZeroBased As Long) As String
ImageExtensionByIndex = Entries(IndexZeroBased).ImageExtension
End Property
Public Property Get EntryTypeByOffset(ByVal Offset As Long) As eEntryType
EntryTypeByOffset = Entries(FindIndexFor(Offset)).EntryType
End Property
Public Property Get EntryTypeStringByOffset(ByVal Offset As Long) As String
EntryTypeStringByOffset = EntryTypeStringByIndex(FindIndexFor(Offset))
End Property
Public Property Get LengthByOffset(ByVal Offset As Long) As Long
LengthByOffset = Entries(FindIndexFor(Offset)).Length
End Property
Public Property Get ContentByOffset(ByVal Offset As Long) As Byte()
ContentByOffset = Entries(FindIndexFor(Offset)).Content
End Property
Public Property Get ImageExtensionByOffset(ByVal Offset As Long) As String
ImageExtensionByOffset = Entries(FindIndexFor(Offset)).ImageExtension
End Property
Private Function FindIndexFor(ByVal Offset As Long) As Long
Dim i As Long
If Offset < 0 Then Offset = 65536 + Offset
For i = 0 To EntriesCount - 1
If Entries(i).Offset = Offset Then FindIndexFor = i: Exit Function
Next
Err.Raise vbObjectError, , "No such Offset found"
End Function
Private Function ReadNextEntry() As Boolean
Dim P As Long, L As Long, B() As Byte, i As Long, J As Long, K As Long
P = Strm.GetPosition
ReDim Preserve Entries(EntriesCount)
Entries(EntriesCount).Offset = P
Entries(EntriesCount).Content = ""
Entries(EntriesCount).EntryType = frxBinary
Strm.ReadToPtr VarPtr(L), 1
Strm.ReadToByteArr B, L
If L <> UBound(B) + 1 Then Exit Function
If L Then
For i = 0 To UBound(B)
If B(i) = 0 Then Exit For
Next
If i > UBound(B) Then
Entries(EntriesCount).EntryType = frxTextA
Strm.SetPosition P + 1
If L = 255 Then Strm.ReadToPtr VarPtr(L), 2
Else
Strm.SetPosition P
Strm.ReadToByteArr B, 4
If B(1) = 0 And B(2) = 3 And B(3) = 0 Then
Entries(EntriesCount).EntryType = frxList
Else
Strm.SetPosition P
Strm.ReadToLong L
End If
End If
Else
Strm.SetPosition P
Strm.ReadToLong L
End If
If Strm.GetPosition = Strm.GetSize Then Exit Function
P = Strm.GetPosition 'the stream is now placed after the Length-Info
If Entries(EntriesCount).EntryType = frxTextA Then
Strm.ReadToByteArr B, L
Entries(EntriesCount).Content = StrConv(B, vbUnicode)
ElseIf Entries(EntriesCount).EntryType = frxList Then
With New_c.StringBuilder
For i = 1 To L
Strm.ReadToPtr VarPtr(K), 2
Strm.ReadToByteArr B, K
.AppendNL StrConv(B, vbUnicode), IIf(i = L, "", vbNullChar)
Next
Entries(EntriesCount).Content = .ToString
.Clear
EntriesCount = EntriesCount + 1: ReDim Preserve Entries(0 To EntriesCount)
Entries(EntriesCount).Offset = Strm.GetPosition
Entries(EntriesCount).EntryType = frxList
Strm.ReadToByteArr B, 4
If UBound(B) <> 3 Then Exit Function
If B(1) <> 0 Or B(2) <> 3 Or B(3) <> 0 Then Exit Function
For i = 1 To B(0)
Strm.ReadToPtr VarPtr(K), 2
Strm.ReadToByteArr B, K
.AppendNL StrConv(B, vbUnicode), IIf(i = L, "", vbNullChar)
Next
Entries(EntriesCount).Content = .ToString
End With
Else 'it's still at frxBinary, let's see if we can determine more
Strm.ReadToByteArr B, 1
Strm.SetPosition P + 4: Strm.ReadToLong K
Strm.SetPosition P + 20: Strm.ReadToLong J
If B(0) = 123 Then ' { detected
Strm.SetPosition P
Entries(EntriesCount).EntryType = frxTextRTF
Strm.ReadToByteArr B, L
Entries(EntriesCount).Content = StrConv(B, vbUnicode)
ElseIf L = J + 24 Then 'it's apparently an image-header
Entries(EntriesCount).EntryType = frxImage
Strm.SetPosition P + 24
Strm.ReadToByteArr Entries(EntriesCount).Content, J
ElseIf L = K + 8 Then 'it's apparently an image-header
Entries(EntriesCount).EntryType = frxImage
Strm.SetPosition P + 8
Strm.ReadToByteArr Entries(EntriesCount).Content, K
Else 'what now remains is, to check whether we have some text here
Strm.SetPosition P
Strm.ReadToByteArr B, L
For i = 0 To UBound(B) 'ANSI-Text-check first
If B(i) = 0 Then Exit For
Next
If i > UBound(B) Then 'we have ANSI-Text
Entries(EntriesCount).EntryType = frxTextA
Entries(EntriesCount).Content = StrConv(B, vbUnicode)
Else 'we either remain at T=frxBinary or we detect UniCode-Text
Strm.SetPosition P
Do
Strm.ReadToLong K
Loop Until K = 0 Or Strm.GetPosition = Strm.GetSize
If Strm.GetPosition = Strm.GetSize Then 'indicator not found (so we remain at non-specific binary-mode)
Strm.SetPosition P + L
Entries(EntriesCount).Content = B
Else 'the K = 0 case
Strm.SetPosition -8, STRM_SeekFromCurPos
Strm.ReadToLong L
Strm.ReadToLong K
If K = 0 And UBound(B) + 1 - L = Strm.GetPosition - P Then
Entries(EntriesCount).EntryType = frxTextW
Strm.ReadToByteArr Entries(EntriesCount).Content, L
If L > 0 Then If Entries(EntriesCount).Content(0) = 123 Then Entries(EntriesCount).EntryType = frxTextRTF
Else
L = UBound(B) + 1
Strm.SetPosition P + L
Entries(EntriesCount).Content = B
End If
End If
End If
End If
End If
On Error Resume Next
Entries(EntriesCount).Length = 0
Entries(EntriesCount).Length = UBound(Entries(EntriesCount).Content) + 1
If Entries(EntriesCount).EntryType = frxImage Then
Select Case Entries(EntriesCount).Content(0)
Case 1: Entries(EntriesCount).ImageExtension = "emf"
Case 215: Entries(EntriesCount).ImageExtension = "wmf"
Case 66: Entries(EntriesCount).ImageExtension = "bmp"
Case 71: Entries(EntriesCount).ImageExtension = "gif"
Case 255: Entries(EntriesCount).ImageExtension = "jpg"
Case Else: Entries(EntriesCount).ImageExtension = "ico"
End Select
End If
If Err Then Err.Clear
EntriesCount = EntriesCount + 1
ReadNextEntry = True
End Function
lol, this old fart (me) had no idea what google-fu was. Had to look it up. The only thing I recognize was "google" and "fu". Of course, I didn't think "fu" meant the same to you as it did to me ("f" & "u" two 'words'). Glad I looked it up, was starting to wonder how I offended you
Insomnia is just a byproduct of, "It can't be done"
Well, it all has to do with the MS Sans Serif substitution, and it'll very likely only be for my own use, but that's okay. As mentioned in another thread, I've got two UserControls that I make extensive use of: RtfLabel and RtfButton. When these controls are used on a form, the RTF text winds up in the FRX file.
Now, the actual RTF text will have the "MS Sans Serif" sub-string within it. From another thread, I'm "fixing" this with the following (just a code stub)...
Code:
If TypeName(ctl) = "RtfButton" Or TypeName(ctl) = "RtfLabel" Then
If InStr(ctl.RTFCode, "MS Sans Serif") Then
ctl.RTFCode = Replace$(ctl.RTFCode, "MS Sans Serif", "Microsoft Sans Serif")
End If
End If
Everything is working fine as far as I can tell. However, I'm still taking you to heart, thinking that a source-code fix is better than a run-time fix. Therefore, with 100s of forms, I'm looking at an "automated" source code fix. And that's where this thread comes in.
And, I've got your little codebank project coming to life. However, I'm not sure how to filter for other things (beyond strings and pictures). If I hit one of those, it'll probably make my work upchuck. I'll continue to work on that.
Here's where I am so far with it. Just run it and point at some existing FRX (or CTX, etc) file, and it'll read it. At the present time, it's read-only, so no fear of messing up an existing project.
I'm about done for the day, but I'll continue working on it, posting my progress.
Also, as always, I'm open to suggestions.
EDIT1: Here's a screenshot of what it looks like so far:
EDIT2: Here's a better shot that shows some RTF text with the "MS Sans Serif" in it. And, as you will see if you squint, I've got Spanish on some of my forms.
Last edited by Elroy; Aug 22nd, 2017 at 04:12 PM.
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
I haven't taken a detailed look at your project because I don't have the RichClient. However, upon looking at your eEntryType constant enumeration, it doesn't appear that you have all the possible basis covered.
For instance, I know that you can put a Byte array into a Variant and then stuff it into the property bag. And, as such, it's going to save into the FRX file. I haven't studied this situation with a hex editor, but I know that I'll eventually need to. I'm not sure, but I believe you can stuff many other things into a Variant that becomes part of the property bag of some UserControl.
But anyway, I'll continue working out how these appear in these FRX files, probably ignoring many of them (but appropriately jumping over them).
Best Regards,
Elroy
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
@Elroy. FYI: You don't have to stuff a byte array into a variant.
Propbag.WriteProperty "BinaryStuff", bData() works just fine if bData() declared as Byte
I use the above quite often to store binary information: image formats not supported by VB, unicode, custom formats, etc.
Insomnia is just a byproduct of, "It can't be done"
Elroy. Just played for about 30 mins or so. The FRX structure isn't easy to decipher because of all the undocumented entries that exist which define the type of 'resource' within the file. However, there does appear some consistencies...
1. DWord/Word alignment is not in play, no buffering between resources
2. The 1st 4 bytes describe the size of the resource, size of resource (including flags)
3. The next 4 bytes appear to be some flag as to type of resource and if not in some internal list, then text most likely
4. The rest of the block contains undocumented flags, if applicable, and eventually content
For example, byte arrays are written to frx from UC: 4 byte block size, then 4 unknown longs (GUID maybe?), value of vbByte (Integer), then dimension count (Integer), then one long for each L/UBound value, then the byte data.
I think if you pursue this, you will need to cross-reference offsets from the frm to the frx files. The frm file allows you to search for specific property names and the offset identifies where in the frx, the resource block starts which identifies size of block. Standard text begins after the 4 byte block size. Knowing all this, you could rewrite the frx on demand if standard text is the only thing you are editing. The pain would be keeping track of all the frm offsets so you can adjust them as needed, after updating the frx.
Trying to parse the frx without the frm file to reference does you little good since your goal is not to extract only, but to extract then modify then rewrite. And that means rewriting the frm file also with updated offsets. Have fun buddy.
Edited: Important. The entry for the block size is NOT included in the total block size. So for a frx text entry of 45 chars: the block size would be entered in the frx as 45 (text starts after that entry). But total block size would actually be 49 bytes: 4 bytes for the size entry and 45 bytes for the text. Understand? Theoretically there could be a resource of zero bytes where the block size entry would be zero. Don't know if VB would ever write a zero byte resource?
Last edited by LaVolpe; Aug 22nd, 2017 at 09:16 PM.
Insomnia is just a byproduct of, "It can't be done"
@LaVolpe: Good morning. Yeah, I had most of what you mentioned in post #23 already sorted. However, let me go ahead and do a run-down.
Originally Posted by LaVolpe
1. DWord/Word alignment is not in play, no buffering between resources
Correct. That's what I'm also seeing.
Originally Posted by LaVolpe
2. The 1st 4 bytes describe the size of the resource, size of resource (including flags)
Correct. And yeah, this size does not include these 4 bytes, so they must be added when bouncing from item to item.
Originally Posted by LaVolpe
3. The next 4 bytes appear to be some flag as to type of resource and if not in some internal list, then text most likely
Not so sure about this one. If it's an image, there does seem to be a secondary header that's immediately seen. However, if it's a string, it seems that the string immediately follows. Here's a screenshot of an FRX (in a hex editor) with nothing but two strings in it. And, in the FRM, the offsets point directly at that initial 4 byte size value. The two offsets in the FRM are 0000 and 0120.
Only the 4 byte header/size, nothing else.
Originally Posted by LaVolpe
4. The rest of the block contains undocumented flags, if applicable, and eventually content
Unless it's a string. And, in that case, there is no "rest of the block".
Now, what confuses me is, "how does it reliably differentiate a string from other item type (i.e., images, etc)?" This question leads me to believe that we must go back to the FRM and use the offsets to reliably read these things. Because, here's my thinking. In a byte of an ansi string, we can have any value we like. Therefore, a string could quite easily "spoof" itself to be the header of an image (for purposes of these FRX files). Therefore, to be bullet-proof, we need something that unquestionably tells us that we're dealing with a string.
And, as I mentioned in my OP, that's in the FRM file:
Originally Posted by Elroy
Sometimes, the FRX references in the FRM file look like: $"SomeForm.frx":0000. Does the dollar-sign indicate we're looking for a string?
So ... back to the drawing board, with the thinking that the FRM files truly are needed to reliably read these FRX files (as you also stated).
Best Regards,
Elroy
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
This question leads me to believe that we must go back to the FRM and use the offsets to reliably read these things. Because, here's my thinking. In a byte of an ansi string, we can have any value we like. Therefore, a string could quite easily "spoof" itself to be the header of an image (for purposes of these FRX files). Therefore, to be bullet-proof, we need something that unquestionably tells us that we're dealing with a string.
For 100% confidence I agree. I doubt VB reads the frx file without the frm file. Unless we know every possible header that can exist in the frx, then not sure how'd you process/read the frx with full confidence. For example, images have a 4-byte flag that appears 8 bytes after block size entry. But I had an image saved from a UC that resulted in that flag 20-ish bytes (don't recall right now) after the size block, not 8.
Sometimes, the FRX references in the FRM file look like: $"SomeForm.frx":0000. Does the dollar-sign indicate we're looking for a string?
Don't know about that $ symbol equating to text/unicode. I know I've seen strings referenced without the $ symbol. Don't immediately see any examples in my frx file with such a format. Without testing this (not on a VB6 machine right now), could special formatting have anything to do with renaming a form, i.e., Save Form1 As?
Last edited by LaVolpe; Aug 23rd, 2017 at 01:33 PM.
Insomnia is just a byproduct of, "It can't be done"
Since, at least for this endeavor, I'm only interested in strings, I've cut the project down even further.
I'd attach the project, but I'm still working out the Write/Save aspects of it (after a string is changed).
Here are two procedures that are working perfectly though ...
Code:
Private Sub ReadAndEnumFrxFile()
Dim hFileFrx As Long, hFileFrm As Long
Dim sLineFrm As String
Dim sNeedle As String
Dim lOffset As Long
Dim lDataSize As Long
Dim tNode As Node
'
hFileFrm = FreeFile: Open msFileFrm For Input As hFileFrm
hFileFrx = FreeFile: Open msFileFrx For Binary As hFileFrx
'
' Example: $"Form1.frx"
sNeedle = LCase$("$""" & Mid$(msFileFrx, InStrRev(msFileFrx, "\") + 1) & """") ' Allows us to find the FRX strings.
'
Do While Not EOF(hFileFrm) ' Loop through entire FRM file.
Line Input #hFileFrm, sLineFrm
If InStr(LCase$(sLineFrm), sNeedle) <> 0 Then ' Does line contain FRX string?
lOffset = Val("&H" & Mid$(sLineFrm, InStrRev(sLineFrm, ":") + 1)) ' Parse out the FRX offset.
Get hFileFrx, lOffset + 1, lDataSize ' Get length out of FRX file.
If lDataSize < 1& Then Error 1234
Set tNode = tvRez.Nodes("frxStr") ' List in our TreeView.
With tvRez.Nodes.Add(tNode, tvwChild, , "String " & tNode.Children + 1&)
.Tag = lOffset + 4& & "," & lDataSize
End With
Set tNode = Nothing
End If
Loop
'
Close hFileFrm
Close hFileFrx
End Sub
... and ...
Code:
Private Sub ShowString(tNode As Node)
Dim b() As Byte, hFileFrx As Long
'
mlOffset = Val(Split(tNode.Tag, ",")(0))
mlSize = Val(Split(tNode.Tag, ",")(1))
'
hFileFrx = FreeFile
Open msFileFrx For Binary As hFileFrx
'
txtString.Text = vbNullString
'
ReDim b(0 To mlSize - 1&)
Get hFileFrx, mlOffset + 1&, b
Close hFileFrx
'
txtString.Text = StrConv(b, vbUnicode)
cmdReplace.Enabled = True
cmdSave.Enabled = False
End Sub
At least in my case, the $"Form1.frx" (including the $) seem to be reliably there when it's a string. And then, the FRX has a four-byte item length, and no further header.
I'm not sure when I'll get it done, but I'll post the write/modify version when I get it done. Here's a screen-shot of where I'm going:
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
Alrighty, I think I'm gonna call this thread done.
I've attached a program that can extract strings out of an FRX file, edit them, and then re-write them, patching up all offsets (in the FRM file). This is what I needed.
I'm not sure this will be of any use to anyone but me, but my (relatively) completed project is attached. I do have a high degree of confidence that it's correctly managing the FRM & FRX files.
I will be doing more work on it to further automate it. I plan on making it automated if a Command$ is supplied to it. That way, I can create a batch file, and then go through and do a search-and-replace in all of my FRX files (of which I've got many).
Maybe someone else will make some use of these ideas. Who knows.
Enjoy,
Elroy
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
Just small research. A FRX file contains numerous streams with very simple format: DWORD - size of stream followed by data. You can extract only binary data, if you want to extract data in the certain format you should use a FRM/CTL/etc file too.
You basically scan every byte for the double-word of &H0000746C.
746C is added by OLE not by VB, it's the OLE stream signature. VB6 passes the stream that points to that byte into OleLoadPictureEx function, ie that is external data in relation to VB.
You can check my statements by changing an FRX file. Just add random data between streams and fix FRM file, project will work, ie VB6 uses a FRM file as the pointer to a FRX stream.
Last edited by The trick; Aug 23rd, 2017 at 04:37 PM.
However, from everything I see, if the FRM reference is something like $"SomeForm.frx":0123 rather than just "SomeForm.frx":0123 then there seems to be no 746C constant. (Notice the $ sign.) In those cases, there's only a length header in the FRX, immediately followed by the string. No null termination either. If there's nothing but strings in the FRX, they're basically length, string, length, string, etc. And no padding and no 746C.
Best Regards,
Elroy
Last edited by Elroy; Aug 23rd, 2017 at 04:48 PM.
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
And here's a screenshot of a piece of the FRX in a hex editor.
Now this time, I don't even get my four-byte item length. It appears to be only a three-byte length. The first word in the textbox is "This".
Also, there's no $ notation along with the FRX offset in the FRM file.
So, your guess is as good as mine. That's it for me today.
Y'all Take Care,
Elroy
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
Yes, the structure always: DWORD_Length, Data; as i mentioned.
I meant 746C doesn't refer to FRX format. It's stream data that produced by OLE. You can check it make inverse operation:
Hmmm, the more I stare at these things, the more confused I get.
Now this time, I don't even get my four-byte item length. It appears to be only a three-byte length. The first word in the textbox is "This".
Also, there's no $ notation along with the FRX offset in the FRM file.
So, your guess is as good as mine. That's it for me today.
Elroy, I don't know what version of vb began to use frx files. Maybe what you are seeing are two versions of storing text? Don't know.
Anyway, that format is easy enough to decipher. Though I am surprised there is no 4-byte block size. The 1st 3 bytes are:
byte 1: 255 = some flag, some other meaning?
bytes 2 & 3: = 1697 which is size of the string, entire block is 1697 + 3
Kinda throws the idea out that every block starts with a 4-byte block size, huh?
Insomnia is just a byproduct of, "It can't be done"
@LaVolpe: Just as an FYI, everything I've posted in this thread was created with VB6-SP6. The project I posted in post #31 was created from scratch yesterday.
Also, I think I've got what I want, so I'll leave the rest of the sorting out of FRX files to others. To fully understand these things, I think I'd need to develop a much better understanding of a comment made by Trick...
Originally Posted by The trick
It's stream data that produced by OLE.
I strongly suspect all the answers are in a deep understanding of that statement.
Best Regards,
Elroy
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
Well, I'm actively using my little FrxStringEditor, and continuing to find improvements (and fix a couple of bugs).
I found a border condition that wasn't being correctly handled, and fixed it.
Also, it wasn't correctly handling offsets larger than FFFF, and that's fixed as well.
It also has a better search-and-replace function in it.
Who knows if any of this will be of help to anyone else, but here it is (attached).
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
Probably written by the IPersistStream.Save or IPersistStreamInit.Save implementation of each object. The actual contents can be anything in theory, even XML. Only the object itself needs to know the format because nothing else needs to read it.
dilettante, I suspect you're correct. And that's almost certainly why there's no apparent rhyme-or-reason as to what we see in these FRX files. The property of the control knows what kind of data it is, and OLE will know how to deal with streaming/serializing that kind of data, and that's all that needs to be known. And that's true because the only thing re-reading that data will be the same property that wrote it out.
Therefore, to make a reliable parser/reader of these FRX files, we'd need a complete list of every type of item that could be placed into them (and also be able to determine that type by reading the FRM file). And that becomes particularly problematic when we realize that, through UserControls, we can put virtually anything into them.
Again, I'm successfully finding the strings that I'm after. I'm not sure it's worth pursuing this much further, given those facts.
Best Regards,
Elroy
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
In the custom control, you can fill in the code to read the properties and modify the properties. If you debug the assembly in this place, see what function it performs?