Yesterday at 10:20 AM1 day Localization Hello everyone,I’m looking for some help cracking the per-image compression for Buccaneer (1997, SSI).I am trying to extract the textures and UI bitmaps from bitmaps256.cat and combat.cat (which share the exact same format).I've included a summary of what I've accomplished and where I'm stuck, hoping someone can help me. I've also attached the .cat and .exe files for the game.Thanks in advance! buccaner files.zip
14 hours ago14 hr 9 hours ago, UZ.- said:Hello everyone,I’m looking for some help cracking the per-image compression for Buccaneer (1997, SSI).I am trying to extract the textures and UI bitmaps from bitmaps256.cat and combat.cat (which share the exact same format).I've included a summary of what I've accomplished and where I'm stuck, hoping someone can help me.I've also attached the .cat and .exe files for the game.Thanks in advance!buccaner files.zipTry this QuickBMS script to decompress the BMP files. cat.zip
11 hours ago11 hr Localization Solution The .cat files used by Buccaneer use Haruhiko Okumura's LZSS.C from 1989, used essentially verbatim.It is not an adaptive entropy coder.The LZSS parameters are:N = 4096 F = 18 THRESHOLD = 2 Ring buffer fill = 0x00 Initial write position r = N - F = 4078The zero-filled ring buffer is important. The original Okumura implementation commonly uses 0x20, but Buccaneer uses 0x00.1. Container FormatThere is a real central TOC (table of contents) at the end of the file.The actual directory is a single clean list at the end of the file.At file offset 0 there is a u32 pointing to the TOC:u32 toc_offsetAt toc_offset the TOC is structured as:u32 count count x { u32 namelen // includes the trailing NUL char name[namelen] // ASCII, NUL-terminated u32 offset // absolute file offset of the image block }The structure therefore looks like:Offset 0: u32 toc_offset At toc_offset: u32 count Entry 0: u32 namelen char name[namelen] u32 offset Entry 1: u32 namelen char name[namelen] u32 offset ... Entry count-1: u32 namelen char name[namelen] u32 offsetFor bitmaps256.cat:count = 266There are:266 entries266 unique names266 unique offsetsNothing is shared or reused.The TOC consumes exactly to the end of the file with zero trailing bytes.2. Image Block FormatEach image block is:u32 f0 // decompressed size u32 f1 // compressed stream size u8 stream[f1]So the complete block size is:8 + f1There is no additional marker or class byte.The values previously identified as supposed markers such as:0xEF 0xDF 0x5Fare actually just the first byte of the LZSS stream, specifically the first LZSS flag byte.The compressed size f1 therefore includes this flag byte.So the block is:8 + f1 bytesand not:9 + f1 bytesThe first flag byte's values appeared to correlate with image size, but this is only incidental.Specifically, bit 4 of the first flag byte indicates whether bfSize required a third non-zero byte, which effectively corresponds to whether the BMP is over 64 KB.The image blocks are contiguous starting at offset 4.There is a total of 285 bytes of slack before the TOC.3. LZSS CodecThe compression is Okumura's classic LZSS implementation.The compressed data is processed using a flag byte, with the bits consumed LSB first.Each flag byte controls 8 tokens.Flag meaningsbit = 1 literal byte bit = 0 2-byte match tokenA match token consists of two bytes:b0 b1The match position and length are calculated as:pos = b0 | ((b1 & 0xF0) << 4) len = (b1 & 0x0F) + 3The decoder then copies len bytes from the ring buffer:ring[(pos + k) & 0xFFF]Every byte copied is:appended to the outputwritten into the ring buffer at the current write positionThe write position is incremented as:ring[r++ & 0xFFF]Decoding continues until exactly f0 bytes have been produced.4. Recurring Zero-RunsTwo match tokens appear repeatedly:E9 F2 EB F0They appeared 207 times.These are not special compression markers.They are simply zero-runs being read from the still-untouched, zero-filled portion of the ring buffer.For example:E9 F2produces:pos = 0xFE9 len = 5and therefore copies five zero bytes.Likewise:EB F0produces a three-byte zero run.5. Compressed Data Can Be Larger Than the OriginalNot every bitmap compresses smaller than the original.For example:Mouse.BMPhas:compressed = 7577 bytes raw = 7478 bytesSo the compressed data is actually slightly larger than the original.This is normal LZSS behaviour.The file uses ordinary 9/8 worst-case expansion for incompressible data. This can occur with things such as the 256-colour palette and noisy pixel data.6. Usageextract.py bitmaps256.cat extracted extract.py
11 hours ago11 hr Author Localization Hi guys @DKDave @Hazza12555 ! Thank you both so much for your time, expertise, and willingness to help. Also, thank you for being so quick—you were incredibly fast!!! Both the QuickBMS script and the Python script worked flawlessly. Infinite thanks!!!!!!!!!!!!!!!!!!!!!!! You both are absolute legends!
Create an account or sign in to comment