Reference · Formats

The CCSF scene file

CyberConnect2's chunked container for everything drawn: textures, palettes, models, materials, animation, cameras, lights, hit volumes and effects. The engine class that loads it is ccStream (D:\usr\RpgUS\prog\system\libccs2.cpp). Every member of DATA.BIN and of the STREAM/*.BIN archives is one, gzip-compressed.

Layout

All little-endian. The file is a flat sequence of chunks:

chunk
  u16       kind             what the dispatcher switches on
  u16       tag              0xcccc, except the Setup chunk (0x0000); ignored
  u32       size             payload length in 32-bit words - NOT used by the
                             game, and wrong for some kinds (see below)
  u8[]      payload

The game never skips a chunk by size. ccStream::DecodeSetupSection (0x00149ff0) reads the 8-byte chunk header and calls one decoder per kind; each decoder reads exactly what it needs from the stream. A reader that wants to find the next chunk has to know each decoder's length rule. size is right for every kind except textures and mtype & 4 models (see the model chunk); with those two rules every CCSF file on the Infection disc - 1,023 in DATA.BIN, 266 in STREAM/ - walks to its last byte.

The file is three sections: header (0x0001), index (0x0002), setup (everything up to and including the first Frame chunk, 0x0005), then the frame section.

Header, 0x0001

Read by ccStream::DecodeHeaderSection (0x00149d20); size 13 in all 1,023 DATA.BIN files.

char[4]   magic            "CCSF"
char[32]  name             equals the archive member name without extension
u16       version          ccStream+0x184; below 0x90 is a deliberate crash
          padding to 4
u32       frames           ccStream+0x16c; length of the frame seek table at
                           ccStream+0x48 (see the frame section)
u32       count            ccStream+0x44
u32[count] words           ccMalloc'ed array at ccStream+0x3c

version is 0x100 in 999 files and 0x90, 0x92, 0x95 or 0x96 in 24; decoders compare it against 0x90 and 0x92. count is 1 and the word is 0 in all 1,023.

Index, 0x0002

u32       file_count       includes the blank entry 0
u32       object_count     includes the blank entry 0
file[file_count]
  char[32]  path           ' ', or '#' for an object defined in another file
                           loaded alongside; then the artist's source path
object[object_count]
  char[30]  name           PREFIX_name
  u16       file           index into file[]

size is always 2 + 8 * (file_count + object_count). Entry 0 of both tables is all zeros. Chunks name their object by its index in object[], and the engine keeps one 0x40-byte ccChunkIndex per object at ccStream+0x30 (index * 64).

Object name prefixes across all 1,023 files: OBJ_ 244,315, MDL_ 33,661, MAT_ 11,071, CMP_ 7,254, CLT_ 5,538, TEX_ 4,306, ANM_ 4,091, DMY_ 2,529, HIT_ 2,388, LGT_ 421, BLT_ 290, BOX_ 237, MPH_ 233, EFF_ 171, CAM_ 20, FBR_ 2, PAG_ 1.

Chunk kinds

The complete dispatch table of DecodeSetupSection. An unknown kind executes sw $zero, 0($zero).

kinddecoderkinddecoder
0x0003Decode_Setup0x0c00Decode_Bbox
0x0005Decode_Frame - ends the setup section0x0d00Decode_Particle
0x0100Decode_Obj0x0e00Decode_Eff
0x0200Decode_Material0x1000Decode_BltGrp
0x0300Decode_Texture0x1100Decode_FBRect
0x0400Decode_Clut0x1200Decode_FBPage
0x0500Decode_Camera0x1300Decode_DummyPos
0x0600Decode_Light0x1400Decode_DummyPosRot
0x0700Decode_Anime0x1700Decode_Layer
0x0800Decode_Model0x1800Decode_Shadow
0x0900Decode_Clump0x1900Decode_Morpher
0x0a00Decode_ExtObj0x2000Decode_Obj2
0x0b00Decode_Hit0x2200Decode_Pcm

Small chunks

0x0003 Setup      no payload; size 0 in all 1,289 files
0x0005 Frame      u32 frame count (ccStream+0x168 = count - 1); ends setup
0x0100 Obj        u32 object, u32 parent, u32 model;
                  u32 shadow model if version >= 0x96
0x0900 Clump      u32 object, u16 n, padding to 4, u32 node[n]
0x0a00 ExtObj     u32 object, u32 parent, u32 target (what animation drives)
0x0200 Material   u32 object, u32 texture, f32 transparency, u16 cropU, u16 cropV
                  (the first chunk for an object wins; crops: see below)
0x0c00 Bbox       u32 box, u32 target, f32 min[3], f32 max[3]
0x1300 DummyPos   u32 object, f32 pos[3]
0x1400 DummyPosRot u32 object, f32 pos[3], f32 rot[3] (degrees)
0x1900 Morpher    u32 morpher (MPH_), u32 base model (MDL_)
0x2200 Pcm        16 bytes + 4 * u32(+8) * u32(+12)
0x0500 Camera     u32 object (CAM_); the camera's values are all in
                  its Anime chunk's F_Camera records
0x0600 Light      u32 object, s16 type, pad to 4
                  type 1 distant, 2 direct, 3 spot, 4 omni; the values
                  are in the Anime chunk's light controllers
0x2000 Obj2       u32 object, u32 flags, u32 modifier, u32 layer, u32 slayer
                  flags (ccObj2Chunk.succession): bit 0 transparency
                  inherits the parent's, bit 1 hidden while at the origin;
                  modifier: the MPH_ morpher; layer, slayer: the draw and
                  shadow layer objects (0 for the default)
0x1700 Layer      u16 n, pad to 4, n x (u8 kind, pad to 4, u32 object)
                  kind 0 a draw layer, 1 a shadow layer
0x0b00 Hit        CCSTRM_HIT: u32 object, u32 parent, u16 groups, s16 pad,
                  u32 total vertices; then per group:
                    u32 vertices (3 per triangle), u32 attribute,
                    f32 pos[vertices][3], f32 normal[vertices][3]
                  the normals are read and not kept
0x0e00 Eff        CCSTRM_EFF (36 bytes), then patNum x 8-byte UV patterns;
                  see the effects page
0x1100 FBRect     CCSTRM_FB_RECT: u32 object, u8 type, pad[3], u16 fx, fy,
                  w, h (pixels)
0x1200 FBPage     CCSTRM_FB_PAGE: u32 object, u32 n, then n x
                  CCSTRM_FB_PAGE_INFO: u8 px, py, pw, ph (GS pages of
                  64 x 32), u8 type, pad[3]

cropU/cropV are not an offset to apply. ccMaterial::Init (0x001399f0) keeps its own runtime offset at +0x14/+0x16 and starts it at 0. ccModel::SetUV(u, v, material, flags) (0x0013abe0, called from ccAnm::SetUV during UV animation) sets that offset to u - cropU and v - cropV. So the crop is the reference point of a UV animation, and a model at rest draws with no offset. They are nonzero in 218 of the 11,071 materials in DATA.BIN.

DummyPos and DummyPosRot are named points (DMY_) that game code places things at - a town's pieces, its flags, merchants, event markers; see town assembly. A Morpher's targets are named by the F_Morpher records (0x1901) of an Anime chunk: u32 morpher, u32 count, count x (u32 target model, f32 weight). The targets carry positions only (mtype 0x600/0x601: no colour, no ST); 820 of the 835 such models in DATA.BIN are named by an F_Morpher record in their own file.

Every kind above was read in its decoder, and the names are the DWARF's CCSTRM_* structures where the executable has one. The Eff chunk is laid out on the effects page, the Hit mesh's use in piney-world's hit.rs, the layers and Obj2 on the stream page, and the Shadow chunk in the shadow volumes.

Decode_FBRect and Decode_FBPage read their records and keep nothing but the object's kind. Only FRAMEBUF.CCS has them, and no file list names it, so the game never loads it. It is a picture of the VRAM plan: FBR_描画領域 ("draw area", type 0) at (0, 0) 1024 x 512, FBR_Zバッファ ("Z buffer", type 1) at (0, 512) 512 x 512, and PAG_framebuff's six page rectangles: those two (type 0x40), two of type 0x13 at page (8, 16) 1 x 2 and (8, 24) 2 x 4, and two single pages of type 0 at (8, 30) and (8, 31).

In DATA.BIN: 16 Camera, 421 Light, 2,285 Hit, 171 Eff, 1,207 DummyPos, 1,322 DummyPosRot, 233 Morpher, 218,971 Obj2, 2 FBRect and 1 FBPage chunks. Layer is only in the streams.

Anime sub-kinds

An Anime chunk (0x0700) is u32 object, u32 frames, u32 words, then Top records and these sub-chunks, all laid out on the animation page. Over DATA.BIN's 4,091 Anime chunks:

kindrecordcount
0x0101F_Obj, a whole transform3,388
0x0102object controllers229,107
0x0108F_Note2,697
0x0202material U/V controllers216
0x0502F_Camera4,956
0x0601F_Ambient17,764
0x0603distant light controllers162
0x0605direct light controllers7
0x0607spot light controllers1 (town05)
0x0609omni light controllers251
0x1901F_Morpher11,086
0xff01Top190,663

The frame section

After the Frame chunk, DecodeFrameChunk (0x0014e1b0) reads a u16 kind, pads to 4 (skipping the 0xcccc half), ignores size, and dispatches on its own table. Every frame decoder reads exactly its size field.

0xff01 Top          u32 frame number; opens a frame. -1 ends the section,
                    -2 ends it and resets the scene (str7300.tmp only)
0x0101 F_Obj        52 bytes (CCSTRM_FSET_OBJ)
0x0108 F_Note       12 bytes
0x0201 F_Material   8 bytes, +8 unless flag & 2
0x0502 F_Camera     8 bytes + 4 per clear bit of 0x1fe, or 8 if flag & 1
0x0601 F_Ambient    4 bytes
0x0602 F_DistantLight 24 bytes, +4 if flag & 0x20
0x0604 F_DirectLight 56 bytes     0x0606 F_SpotLight 56 bytes
0x0608 F_OmniLight  36 bytes
0x0802 F_ModelVertex, 0x0803 F_ModelNormal   decoded, never present
0x1801 F_Shadow     20 bytes: u32 obj, f32 rx, ry, rz (degrees), f32 alpha
0x1901 F_Morpher    8 + 8 * n
0x2201 F_Pcm        12 + 4 * blocks(u16 +4) * words(u32 +8)

The section is: Frame, then for each frame 0..N-1 a Top and that frame's chunks, then the end Top. DecodeF_Top (0x0014e490) records each Top's address in the seek table while the frame number is below the header's frames. Every DATA.BIN file has an empty section (Frame = 1, then the end Top). The STREAM/ cutscenes come in pairs: strNNNNe.tmp with models and textures, and strNNNN.tmp with object stubs, Pcm audio and the frames.

0x0400, CLUT

ccStream::Decode_Clut (0x0014d660):

u32       clut             object index of the CLT_ entry
u32       blt_group        only if version >= 0x92
u8        flag             only if version >= 0x90, and so on to count
u8        cpsm             the GS TEX0 CPSM; 0 (PSMCT32) in all 5,538
u8        unknown_2        read and discarded
u8        unknown_3        read and discarded
u16       buf_x            VRAM placement; 0/0 or x < 768 becomes 896/992
u16       buf_y
u32       count            16 or 256 in the files
u32[count] colour          R, G, B, A; A is 0x80 for opaque

cpsm goes to ccClutChunkDesc.cpsm and from there into bits 51-54 of the chunk's TEX0 (ccClutChunk::SetBltData, 0x00136980). The palette's psm is not in the file: over 16 colours it is PSMT8 (0x13), else PSMT4 (0x14). unknown_2 and unknown_3 are 0 in every CLUT but one (0 and 112).

Colours are in logical order. For a 256-colour palette the decoder writes colour i to slot clut256Tbl[i] (0x002fb190), which is the GS's CSM1 arrangement; a converter should use the file order as it is.

0x0300, texture

ccStream::Decode_Texture (0x0014d9a0):

u32       texture          object index of the TEX_ entry
u32       clut             object index of its CLT_ entry, always in the
                           same file (all 4,306 textures in DATA.BIN)
u32       blt_group        only if version >= 0x92
u8        flag             low byte of ccTexChunkDesc.flag
u8        psm              0x13 PSMT8 or 0x14 PSMT4 in every file surveyed
u8        mipmap           only if version >= 0x90: levels after level 0
u8        aref             only if version >= 0x90: alpha-test reference
u8        tw               only if version >= 0x90: log2 width
u8        th               only if version >= 0x90: log2 height
          padding to 4
level[mipmap + 1]          at most 4 (ccTexChunkDesc.pos[4])
  u16     buf_x            VRAM placement; 0/0 or x < 768 takes a default
  u16     buf_y            from warnPos (0x0034ac90)
  u32     count            words that follow
  u32[count] pixels

Texture and CLUT flags

The file's flag byte becomes the low byte of the descriptor's flag. ccTexChunk keeps flag & 0x1d, ccClutChunk flag & 5:

bittextureCLUT
0x01the pixels stay in memory and are not sent at loadthe same
0x04kept, never readkept, never read
0x08translucent: its mmats go to the layer's sorted groupdropped
0x10CLAMP both axes (register value 5), else REPEATdropped
  • 0x01. Without it, the decoder uploads every level (ccBltData::LoadData) and frees the pixels (DelBltData, which clears bits 0 and 1). With it, the pixels are kept for the blt groups to send. The decoder also sets it when a level's (or the palette's) VRAM place is 0/0 or has x below 768, after moving it to the default.
  • 0x04. The decoder sets it on a palette with an alpha other than 0 or 0x80, and on a texture without a palette whose alpha bytes are all 0 or 0x80 (no texture in DATA.BIN lacks one). Nothing reads it afterwards.
  • 0x08. ccModel::Draw, DrawBoneType and ccEff::Draw treat a mmat (or sprite) whose texture has it as translucent even at full transparency (the desktop's draw order).
  • 0x40 in the descriptor stops SetBltData making the pixel buffer at all; no file sets it.

Over DATA.BIN's 4,306 textures: 0x01 on 4,229, 0x04 on 290, 0x08 on 173, 0x10 on 30; no other bit. Over its 5,538 palettes: 0x01 on 5,337, 0x04 on 313, 0x08 on 2 (xddesk01).

The real length is the header plus 2 + count words per level; the size field disagrees (xasc00: 0x2039 declared, 0x2007 read). Level 0 is exactly (1 << tw) * (1 << th) pixels at 8 or 4 bits in all 4,306 textures in DATA.BIN.

Pixels are row-major and bottom row first: the art was authored as .bmp and the rows kept BMP order. At 4 bits the low nibble is the left pixel. Mip level n is (w >> n) x (h >> n). Over all 4,306 textures in DATA.BIN: psm 0x13 4,046, 0x14 260; mipmap 0 in 2,146, 3 in 1,807, 2 in 353. (Worklog 8's survey reached only 3,985 of them; worklog 10 fixed the model walk that stopped it.)

Worked example, INF DATA/DATA.BIN::xasc00.cmp at +0x150:

00 03 cc cc  39 20 00 00      kind 0x0300, size 0x2039 (not the real length)
02 00 00 00                   texture = object 2, TEX_xasc00
01 00 00 00                   clut = object 1, CLT_xasc00
00 00 00 00                   blt_group 0 (version 0x100 >= 0x92)
00 14 00 70                   flag 0, psm PSMT4, mipmap 0, aref 0x70
08 08 cd cd                   tw 8, th 8 (256x256), padding
00 03 00 00                   level 0: buf_x 768, buf_y 0
00 20 00 00                   count 0x2000 words = 32,768 bytes of 4-bit pixels

Notes

0xcd padding bytes are the Microsoft C debug heap's fill for uninitialised memory: the exporter that wrote these files was a debug build on Windows.

tools/ccs.py survey ARCHIVE walks every member and counts chunks that disagree with their size field; tools/ccstex.py converts textures to PNG and tools/ccsmodel.py models to OBJ.

Unknown

None.