Overview
VB6 Form Resource files (.frx) are binary files that store property data for forms
and controls that cannot be represented as plain text in the .frm file. These files
contain a sequence of variable-length records without an overall file header. Each record is
referenced from the .frm file using a byte offset notation like
$"FormName.frx":0000.
Encoding: FRX files use Windows-1252 encoding for text data and store values in little-endian byte order.
File Structure
Important: There is no file header - the file immediately begins with the first resource entry. Each entry's location is calculated by summing the sizes of all previous entries.
Entry Types and Headers
FRX files contain five distinct entry types, each with a unique header format:
1. Record12ByteHeader (Binary Blobs)
Magic Signature: "lt\0\0" at bytes 4-7
| Offset | Size | Description |
|---|---|---|
| 0x00 | 4 | Size from end of signature to end of data (u32 LE) |
| 0x04 | 4 | Magic signature: 0x6C 0x74 0x00 0x00 ("lt\0\0") |
| 0x08 | 4 | Size of data section only (u32 LE) = [offset 0x00] - 8 |
| 0x0C | N | Binary data payload |
Used For: Icons (.ico), Cursor files (.cur), Bitmap images (.bmp, .dib), PNG images, OLE objects, Picture properties (Icon, Picture, MouseIcon)
Example (Icon at offset 0x0000)
| Offset | Hex Data | Description |
|---|---|---|
0x0000 |
3E 04 00 00 |
Size: 0x043E (1086 bytes from end of sig) |
0x0004 |
6C 74 00 00 |
Magic: "lt\0\0" |
0x0008 |
36 04 00 00 |
Data size: 0x0436 (1078 bytes) |
0x000C |
00 00 01 00 02 00 10 10... |
Icon data (ICONDIR structure) |
2. Record4ByteHeader (Large Text/Binary Data)
Identifier: First 4 bytes contain at least one 0x00 byte
| Offset | Size | Description |
|---|---|---|
| 0x00 | 4 | Size of data section (u32 LE) |
| 0x04 | N | Raw data payload |
Used For: Long text strings (Caption, ToolTipText with >255 characters), Multiline text (Text property of TextBox), Form descriptions, Binary data embedded in properties
3. Record3ByteHeader (Medium Text Data)
Magic Marker: 0xFF at byte 0
| Offset | Size | Description |
|---|---|---|
| 0x00 | 1 | Magic marker: 0xFF |
| 0x01 | 2 | Size of data section (u16 LE) |
| 0x03 | N | Data payload (typically text) |
VB6 IDE Off-by-One Bug: The VB6 IDE sometimes writes N in the size field when the actual data is N-1 bytes. Parsers must check if reading N bytes would exceed the file length and adjust by subtracting 1.
4. ListItems (ComboBox/ListBox Contents)
Magic Signature: 0x03 0x00 or 0x07 0x00 at bytes 2-3
| Offset | Size | Description |
|---|---|---|
| 0x00 | 2 | Number of items (u16 LE) |
| 0x02 | 2 | Magic signature: 0x03 0x00 or 0x07 0x00 |
| 0x04 | N | Item entries (see below) |
Item Entry Format: Each item: 2 bytes length (u16 LE) + N bytes string data (no null terminator)
Used For: List property of ComboBox controls, List property of ListBox controls
5. Record1ByteHeader (Small Data)
Identifier: Default/fallback type (no specific magic)
| Offset | Size | Description |
|---|---|---|
| 0x00 | 1 | Size of data section (u8) |
| 0x01 | N | Data payload (max 255 bytes) |
Used For: Very short strings (< 256 bytes), Small binary chunks, Fallback for unrecognized patterns
Entry Detection Algorithm
The parser uses a waterfall detection approach at each offset:
- Check for Record12ByteHeader: Offset + 4-7 == "lt\0\0" ?
- Check for Record3ByteHeader: buffer[offset] == 0xFF ?
- Check for ListItems: buffer[offset+2..offset+4] == [0x03, 0x00] or [0x07, 0x00] ?
- Check for Record4ByteHeader: buffer[offset..offset+4] contains any 0x00 byte ?
- Default: Record1ByteHeader
Note: This order is critical because later checks can produce false positives on earlier formats.
Cross-Referencing with .frm Files
FRM files reference FRX entries using the syntax:
PropertyName = $"FormName.frx":OFFSET
Where OFFSET is a hexadecimal byte offset (without 0x
prefix) indicating where the resource entry begins in the FRX file.
Examples from .frm files:
' Icon property - references binary blob at offset 0x0000
Icon = "DebugMain.frx":0000
' Long caption - references large text at offset 0x00A6
Caption = $"Form4.frx":00A6
' ListBox items - references list structure at offset 0x0054
ItemData = "SQLGenerator.frx":0054
List = "SQLGenerator.frx":007C
Properties That Use FRX References
| Property | Control Types | FRX Entry Type |
|---|---|---|
Icon |
Form, MDIForm | Record12ByteHeader |
Picture |
Image, PictureBox, Form | Record12ByteHeader |
MouseIcon |
All controls | Record12ByteHeader |
List |
ListBox, ComboBox | ListItems |
ItemData |
ListBox, ComboBox | ListItems |
Caption |
Label, Button, etc. | Record4ByteHeader |
Text |
TextBox | Record4ByteHeader |
ToolTipText |
All controls | Record4ByteHeader |
Parsing Considerations
1. Windows-1252 Encoding
All text data in FRX files uses Windows-1252 encoding, not UTF-8. Parsers must:
- Decode text entries using Windows-1252 codec
- Handle extended characters (0x80-0xFF range)
- Use replacement characters for invalid sequences
2. VB6 IDE Off-by-One Bug
The VB6 IDE has a known bug where it declares size N but writes N-1 bytes for:
- Record3ByteHeader entries
- Record1ByteHeader entries
Detection: If offset + header_size + declared_size > file_length,
subtract 1 from declared_size.
3. Little-Endian Byte Order
All multi-byte integers are stored in little-endian format:
0x0010 0x0000 → 0x0010 (16 decimal)
0x3E 0x04 0x00 0x00 → 0x043E (1086 decimal)
4. No File-Level Metadata
FRX files contain:
- ❌ No file signature/header
- ❌ No version information
- ❌ No entry count or index
- ❌ No checksums or validation
The only way to parse an FRX file is to sequentially scan from offset 0, identifying each entry's type and size, then advancing to the next entry.
Example: Complete Entry Parse
Given this FRX file hex dump:
| Offset | Hex Data | ASCII |
|---|---|---|
00000000 |
A2 00 00 00 41 6C 73 6F 20 74 68 65 72 65 20 61 |
....Also there a |
00000010 |
72 65 20 6F 74 68 65 72 20 77 61 79 73 2F 63 6F |
re other ways/co |
| ... (lines omitted) ... | ||
000000A0 |
34 2C 20 35 29 2E |
4, 5). |
000000A6 |
F4 00 00 00 46 6F 72 6D 34 20 69 73 20 61 20 6E |
....Form4 is a n |
Entry 1 at offset 0x0000:
- Header:
A2 00 00 00(4 bytes) - Type: Record4ByteHeader (has 0x00 bytes)
- Size: 0xA2 = 162 bytes
- Data: 162 bytes of text starting at 0x0004
- Next entry offset: 0x0004 + 162 = 0x00A6
Entry 2 at offset 0x00A6:
- Header:
F4 00 00 00(4 bytes) - Type: Record4ByteHeader
- Size: 0xF4 = 244 bytes
- Data: 244 bytes of text starting at 0x00AA
- Next entry offset: 0x00AA + 244 = 0x019E
Implementation Notes
Robust Parsing Strategy
- Start at offset 0
- Identify entry type using detection algorithm
- Read header to determine data size
- Extract data payload
- Store entry with its offset as key
- Advance offset by header_size + data_size
- Repeat until EOF
Error Handling
Common errors to handle gracefully:
- Offset out of bounds: Entry extends past file end
- Size mismatch: Record12ByteHeader size fields don't match
- Corrupted list: ListItems structure truncated
- Buffer conversion: Not enough bytes for header
- Invalid signature: Record12ByteHeader lacks "lt\0\0"
Best practice: Continue parsing remaining entries even if one fails, accumulating non-fatal errors for reporting.
Memory Efficiency
For large FRX files (>1MB):
- Use
HashMap<usize, ResourceEntry>for O(1) offset lookups - Store entries indexed by their starting offset
- Keep original buffer for reference slicing
- Avoid duplicating large binary blobs
Historical Context
The FRX format was designed for Visual Basic 6 (released 1998) and reflects limitations of that era:
- No compression: Binary data stored raw
- No Unicode: Windows-1252 encoding only
- IDE bugs: Off-by-one size errors
- Brittle format: No version or magic signature
- Sequential access: Must parse from start to find entries
Modern parsers should handle all these quirks while providing robust error recovery and efficient random access to entries by offset.