In a dictionary, resources need a different structure than regular blocks, so modules are used instead. Modules have more metadata than a block and can also use multiple blocks for each module-specific purpose.
Each module has three blocks: a name block, a data block, and a directory block.
Name blocks store the names of each resource inside that module. Modules themselves can have names as well. The module's own name is stored in block 2 at a specific offset that's specified in the module table.
Data blocks store the actual resource data, like script source code, script documentation, constants, globals, etc. Each resource type gets its own data block.
Directory blocks contain information about the actual resources that are stored inside of a module. They contain two tables and a header. The header contains the module's ID, and how many type slots and resource slots are allocated.
The first table in a directory block is the type table, which contains a list of resource types and how many resources have that type inside the module. Since each type has its own set of resource IDs, you first have to find the resource type that you want to access. From there, you can see how many resources are allocated for that specific type, as well as where the slots are on the resource table. One record in the type table can have multiple resources associated with it, but a resource can only have one type.
The second table is the resource table. It contains information that's unique to a specific resource like the resource's name block, data block, and resource ID.
The header comes first in a directory block, then the type table, then the resource table. The type table should always be at offset 14 (0xE) in the block. To calculate the resource table offset, add the used type slot count with the unused type slot count, multiply by 16, then add 14.
Pseudocode example:
uint ResourceTableOffset = ((UsedTypes + UnusedTypes) * 16) + 14;
The module table header is 9 bytes in size. Offset is from the beginning of the module table block. The unknown field in this instance might be the block number for the module names block but I haven't found any evidence to support that theory.
| Offset (hex) | Purpose | Data type |
|---|---|---|
| 00 | Used Module Count | UInt16 |
| 01 | ||
| 02 | Unused Module Count | UInt16 |
| 03 | ||
| 04 | Unknown | |
| 05 | ||
| 06 | ||
| 07 | ||
Module table records are 16 (0x10) bytes in size. Offset is from the beginning of the record. The name offset and name length refer to block 2, which is for the name of the module itself.
| Offset (hex) | Purpose | Data type |
|---|---|---|
| 00 | Module Type | UInt16 |
| 01 | ||
| 02 | Module ID | UInt32 |
| 03 | ||
| 04 | ||
| 05 | ||
| 06 | Directory Block Number | UInt32 |
| 07 | ||
| 08 | ||
| 09 | ||
| 0A | Name Offset | UInt32 |
| 0B | ||
| 0C | ||
| 0D | ||
| 0E | Name Length | UInt16 |
| 0F |
The module directory block header is 14 (0xE) bytes in size. Offset is from the beginning of the directory block.
| Offset (hex) | Purpose | Data type |
|---|---|---|
| 00 | Module Type | UInt16 |
| 01 | ||
| 02 | Module ID | UInt32 |
| 03 | ||
| 04 | ||
| 05 | ||
| 06 | Used Type Count | UInt16 |
| 07 | ||
| 08 | Unused Type Count | UInt16 |
| 09 | ||
| 0A | Allocated Resource Count | UInt16 |
| 0B | ||
| 0C | Unallocated Resource Count | UInt16 |
| 0D |
Module directory type table records are 16 (0x10) bytes in size. Offset is from the beginning of the record. "First Resource Slot" is the index of the first record in the resource table that's associated with this type.
| Offset (hex) | Purpose | Data type |
|---|---|---|
| 00 | Resource Type | UInt16 |
| 01 | ||
| 02 | Name Block Number | UInt32 |
| 03 | ||
| 04 | ||
| 05 | ||
| 06 | Data Block Number | UInt32 |
| 07 | ||
| 08 | ||
| 09 | ||
| 0A | First Resource Slot | UInt16 |
| 0B | ||
| 0C | Used Resource Count | UInt16 |
| 0D | ||
| 0E | Unused Resource Count | UInt16 |
| 0F |
Module directory resource table records are 16 (0x10) bytes in length. Offset is from the beginning of the record. The version field's purpose is unknown, but that's what it's called by Dex Utils. Name/data length and name/data offset refer to the name/data block that's defined in the types table (see above section).
| Offset (hex) | Purpose | Data type |
|---|---|---|
| 00 | Resource ID | UInt16 |
| 01 | ||
| 02 | Version | Byte |
| 03 | Name Length | Byte |
| 04 | Name Offset | UInt32 |
| 05 | ||
| 06 | ||
| 07 | ||
| 08 | Data Offset | UInt32 |
| 09 | ||
| 0A | ||
| 0B | ||
| 0C | Data Length | UInt32 |
| 0D | ||
| 0E | ||
| 0F |