i took a look at the .CB file and managed to crack it. 1. ENC0 / ENC2 are not encryption ENC is short for *encoding*, and the digit is the method number. *ENC0 — stored, no compression.** The CLSS blob follows the 8-byte header verbatim. That is exactly why CLSS / CTRFImageBuffer / IBUF were readable in a hex editor in the first place. The LZSS chunks live *inside* that blob. Every image resource in SSYF.CB is ENC0. *ENC2 — the whole record is LZSS-compressed.** Same bitstream as below, but with no 4-byte size prefix; the decompressed size is the resource size already in the TOC. Only the 20 .SET files use it. 2. The compression Chunk layout "LZSS" | u32 chunk_size | u32 decompressed_size | bitstream - Bits are read LSB-first within each byte — bit 0, then bit 1, … bit 7. - Multi-bit fields are assembled MSB-first — the first bit read is the field's most significant bit. Grammar. - flag bit 1 → literal: the next 8 bits are emitted directly. - flag bit 0 → match: 12-bit ring position, then 4-bit length, length = field + 2 (so 2..17 bytes). Window. - A 4096-byte ring buffer, initialised to 0x00, write pointer starting at 0. - pos is a 1-based ring index, so the copy source is ring[(pos - 1 + i) & 0xFFF]. pos == 0 never appears in a real token. - Every copied byte is written back into the ring at the write pointer, as usual. - The stream ends as soon as the declared byte count is produced. There is no end marker, and up to 2 bytes of zero padding follow. int bitpos = 0;
int getbit (const u8 *s) { int b = (s[bitpos>>3] >> (bitpos&7)) & 1; bitpos++; return b; }
u32 getbits(const u8 *s, int n) { u32 v = 0; while (n--) v = (v<<1) | getbit(s); return v; }
u8 ring[4096] = { 0 };
u32 r = 0, o = 0;
while (o < decompressed_size) {
if (getbit(src)) { /* 1 = literal */
u8 c = getbits(src, 8);
out[o++] = c; ring[r] = c; r = (r + 1) & 0xFFF;
} else { /* 0 = match */
u32 pos = getbits(src, 12); /* 1-based ring index */
u32 len = getbits(src, 4) + 2; /* 2..17 */
u32 s = (pos - 1) & 0xFFF;
for (u32 i = 0; i < len; i++) {
u8 c = ring[(s + i) & 0xFFF];
out[o++] = c; ring[r] = c; r = (r + 1) & 0xFFF;
}
}
} One chunk decompresses to at most 0x20000 bytes. Bigger images are split across consecutive LZSS chunks that concatenate to exactly width * height * 2. 3. The .dds files — CTRFImageBuffer "CLSS" | u32 len | class name, NUL-terminated ("CTRFImageBuffer")
"IBUF" | u32 size | u32 (0)
| u16 width | u16 height | u16 pitch (= width*2) | u16 planes (1)
| u32 flags (0x00100000)
| u32 mask A | u32 mask R | u32 mask G | u32 mask B
| u8 bits A, R, G, B (e.g. 0, 5, 6, 5)
| u8 top bit index A, R, G, B (e.g. 0, 15, 10, 4)
then 1..n LZSS chunksThe pixel data is a plain linear raster 4. The .SET files, and exact frame reassembly Once decompressed, each .SET is a serialised CTRFDataSet that names the textures for a scene and gives the blit rectangles — so reassembly is read from the data. "CLSS" | u32 len | "CTRFDataSet\0" | u32 nrec
per record: FF FF FF FF | u32 id | u16 namelen | name | payload
CTRFTexture u32 | u32 count | count x NUL-terminated .dds names
CTRFPictures u32 | u32 npics
npics x ( u16 (0x10) | u16 npieces | u16 W | u16 H | u16 ox | u16 oy
npieces x ( u32 texture index
u16 sx0 sy0 sx1 sy1 <- inclusive source rect
u16 dx0 dy0 dx1 dy1 )) <- inclusive dest rect
npics x NUL-terminated picture name <- names come after ALL pictures
u32Note that the picture names come after all the pictures, The typical scene is 640x480 built from three pieces: 512x480 from the _00 texture, then the _01 texture folded in half to supply the right-hand 128x480 strip. tex0 src (0,0)-(511,479) -> dst (0,0)-(511,479)
tex1 src (0,0)-(127,255) -> dst (512,0)-(639,255)
tex1 src (128,0)-(255,223) -> dst (512,256)-(639,479)A few sets sSYf_11) store frames at half vertical resolution and give a destination rect twice as tall — i.e. the game line-doubles them. Other record types are present CTRFContainer, CTRFClipperBuilder, CTRFPictureDraw, and TEXT / PICT / ANIM / LIPS / CLIP sections), but none are needed for images. Script attached: kitae_cb.py python kitae_cb.py .CB FILE -o extracted --assemble python kitae_cb.py .DDS FILE -o extracted # loose CLSS/IBUF file kitae_cb.py