December 6, 2025Dec 6 Localization Today I am gonna discuss on how we can reverse engineer the extraction of the game archives, sit back because this is where it starts to get interesting... +==== TUTORIAL SECTION ====+ But how do those files store game assets like 3D Models, Textures, Sounds, Videos and etc... Well, the anwser is simple, they usually bundle them, they pack them close together in their eighter compressed or even encrypted form (Rarely). To understand let's first quickly move into the basics, into how the Computer stores any file at all. DATA TYPES Those are the most frequent Data types: Byte/Character = 1 Byte, so 8 Bits Word/Short = 2 Bytes, so 16 Bits Dword/Int = 4 Bytes, so 32 Bits ULONG32/Long = 4 Bytes, so 32 Bits ULONG64/Long Long = 8 Bytes, so 64 Bits Float = 4 Byte, so 32 bits Double = 8 Bytes, so 64 Bits String = A sequence of 1 Byte Characters terminated with null ("00") Where Bit is literally one of the smallest Data that we can present, it's eighter 0 or 1 but combining those 8 Bits together (Example: 0 1 1 1 0 0 1 1) so we get a whole byte. So, all files literally look like this: Addres: HEX: ASCII: 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 0x00000040 2a 2a 20 2a 2f 0a 09 54 61 62 6c 65 20 77 69 74 ** */..Table wit 0x00000050 68 20 54 41 42 73 20 28 30 39 29 0a 09 31 09 09 h TABs (09)..1.. 0x00000060 32 09 09 33 0a 09 33 2e 31 34 09 36 2e 32 38 09 2..3..3.14.6.28. 0x00000070 39 2e 34 32 0a 9.42. This is called a Hex dump, it's essentially a mkore human readable code of binary file that aside the actual Binary data in HEX shows us the Adresses and the ASCII representation for each 0x..0 to 0x..F line. The packed file usually contains compressed data and a small separator/padding between them, hover it doesn't tell us the name and the path of the file we want to com press, whch is a problem. Heck, we don't even know which compression method was used and which "flavour/version" and how the decompressed file should look like... That's where QuckBMS comes to help. QuickBMS QuickBMS has one very specific function I wanna talk about, it's "comptype unzip_dynamic" it supports millions methods and their "flavours/versions". It has also a very fast perfomance and is good for extracting the multiple files out of the package at once. There are also already lots of QuckBMS scripts out there for extracting specific archives, but I'll talk about that later. Compression types As said previously, the block separators/markers are very usefull to identify but turns out most of the compression methods have their own headers and magic numbers, here are few of them: Magic numbers: ZLIB: 78 01 (NoComp) 78 5E (Fastest) 78 9C (Default) 78 DA (Maximum) LZ4: [No Magic Numbers] LZ4 Frame: 04 22 4D 18 (Default) LZW: [No Magic Numbers] LZO: [No Magic Numbers] BZIP/BZIP2: 42 5A 68 GZIP: 1F 8B 08 Practical steps Below is the example of how average QuickBMS Archive extractor looks like, it's also one of my first favourites and the first one, it's designed for extraction of assets from Wolfenstein: The New Order & Wolfenstein: The Old Blood: wolfenstein.bms: open FDDE index 0 open FDDE resources 1 comtype unzip_dynamic endian big goto 0x24 get files long get unk long math TMP = files math TMP - 1 for i = 0 < files endian little get FNsize1 long getdstring FN1 FNsize1 get FNsize2 long getdstring FN2 FNsize2 get namesize long getdstring name namesize endian big get offset long get size long get zsize long get unksize long math unksize * 0x18 math unksize + 5 getdstring unkdata unksize if size = zsize log name offset size 1 else clog name offset zsize size 1 endif if i != TMP get filenumber long endif next i Script summary: Open index + data files Read file count For each file: Read filename Read offset and sizes Skip metadata Extract raw or compressed data Compression logic ConditionAction | size == zsizeRaw copy | | size != zsizeZlib decompress | In ID Tech 5, games have all assets packed inside the .resources files and all of the metadata like name, path, extension, compressed size, decompressed size are in the .index files that means is that we open both files in this way: 1. Setup open FDDE index 0 open FDDE resources 1 open = tells QuickBMS to open files. FDDE = the format ID (arbitrary, just a label). index -> is opened as file 0 resources -> is opened as file 1 comtype unzip_dynamic 2. Loop This sets compression to Zlib dynamic (handles soo many decompressions). endian big This sets reading data in Big-Endian. goto 0x24 Jumps to 0x24 to skip archive header get files long get unk long files -> total number of files in the archive unk -> unknown value (probably versioning or flags) math TMP = files math TMP - 1 TMP = holds files - 1 (Used later to avoid reading an extra value after the last entry). for i = 0 < files The script now iterates once per file entry. endian little The filename block is little-endian, even though the rest of the archive is big-endian. get FNsize1 long getdstring FN1 FNsize1 Reads length of string Reads string data get FNsize2 long getdstring FN2 FNsize2 Another string component get namesize long getdstring name namesize This is the real filename name is used later by log / clog This determines the extracted file name on disk endian big Switch back to big-endian. get offset long get size long get zsize long offset = Where the file data is located in resources size = Uncompressed size zsize = Compressed size get unksize long math unksize * 0x18 math unksize + 5 getdstring unkdata unksize Reads a count (unksize) Multiplies it by 0x18 (24 bytes per entry) Adds 5 extra bytes Skips a block of unknown metadata This is not used for extraction, only skipped to reach the next file entry. 3. Extract the files if size = zsize log name offset size 1 If compressed size equals uncompressed size: File is stored raw log copies data directly from file 1 (resources) else clog name offset zsize size 1 endif clog means compressed log Reads zsize bytes Decompresses using unzip_dynamic Writes size bytes to disk if i != TMP get filenumber long endif Between entries, there is an extra long Probably an ID or index value Not present after the last entry This prevents reading past the table next i Repeats until all files are extracted Edited December 14, 2025Dec 14 by user3678
Create an account or sign in to comment