neptools

Modding tools to Neptunia games
git clone https://git.neptards.moe/neptards/neptools.git
Log | Files | Refs | Submodules | README | LICENSE

cl3.md (3912B)


      1 CL3
      2 ===
      3 
      4 CL3 is an archive format, that contains uncompressed files plus links between
      5 them. All integers are little-endian. Offsets are 0x40 bytes aligned in the
      6 original files (this is not a requirement).
      7 
      8 Header
      9 ------
     10 
     11 ```c++
     12 struct Header
     13 {
     14     char magic[3];     // always "CL3"
     15     char endian;
     16     uint32_t field_04; // always 0
     17     uint32_t field_08; // always 3
     18     uint32_t sections_count;
     19     uint32_t sections_offset;
     20     uint32_t field_14;
     21     // maybe more?
     22 };
     23 sizeof(Header) == 0x18
     24 ```
     25 
     26 `endian` is `L` if the file is little-endian, `B` if big-endian. `field_14` is
     27 either 0, 1 or 2 (at least in RB3). Script `.cl3`s use 1, effect `.cl3`s use 2.
     28 
     29 Sections
     30 --------
     31 
     32 Starting at `sections_offset` (relative to file beginning), there are
     33 `sections_count` sections.
     34 
     35 ```c++
     36 struct Section
     37 {
     38     char name[0x20];     // '\0' terminated
     39     uint32_t count;
     40     uint32_t data_size;
     41     uint32_t data_offset;
     42     uint32_t padding[9]; // always 0
     43 };
     44 sizeof(Section) == 0x50
     45 ```
     46 
     47 There are two known sections: `FILE_COLLECTION` and `FILE_LINK`. Each has a
     48 payload of `data_size` bytes starting at `data_offset`.
     49 
     50 FILE_COLLECTION
     51 ---------------
     52 
     53 This is used to store the actual files. It begins with `count` entries:
     54 ```c++
     55 struct FileEntry
     56 {
     57     char name[0x200];     // '\0' terminated
     58     uint32_t field_200;
     59     uint32_t data_offset;
     60     uint32_t data_size;
     61     uint32_t link_start;
     62     uint32_t link_count;
     63     uint32_t padding[7];  // always 0
     64 };
     65 sizeof(FileEntry) == 0x230
     66 ```
     67 
     68 `Section::data_size` includes both the size of these entries and the actual file
     69 data size (it's `last_entry.offset + last_entry.size + pad`).
     70 
     71 `name` is the name of the file. `field_200` is either 0 for all files, or they
     72 contain the index of the file (?). The file contents start at `data_offset`,
     73 (unlike other offsets) **relative to the beginning of FILE_COLLECTION**, and
     74 it's `data_size` bytes long. `link_count` contains the number of links this file
     75 has (see below), `link_start` is an index inside the `FILE_LINK`.
     76 
     77 FILE_LINK
     78 ---------
     79 
     80 It contains `count` entries:
     81 ```c++
     82 struct LinkEntry
     83 {
     84     uint32_t field_00;   // always 0
     85     uint32_t linked_file_id;
     86     uint32_t link_id;
     87     uint32_t padding[5]; // always 0
     88 };
     89 sizeof(LinkEntry) == 0x20;
     90 ```
     91 
     92 `Section::data_size` is `count*sizeof(LinkEntry)`. `linked_file_id` contains the
     93 index of the link destination (inside `FILE_COLLECTION`). `link_id` counts the
     94 index of the current file's link (it's an incrementing counter reset with every
     95 new file).
     96 
     97 In the original files, files and the corresponding link entries are in the same
     98 order, if a file has 0 links, the `link_start` is still set to where it would
     99 began if it'd actually have links.
    100 
    101 Example
    102 -------
    103 
    104 Probably the way I described this whole link thing is not clear enough, so
    105 here's an example:
    106 
    107 `FILE_COLLECTION` contains this:
    108 ```c
    109   [0] = { .name = "a.bin", .link_start = 0, .link_count = 2 },
    110   [1] = { .name = "b.bin", .link_start = 2, .link_count = 1 },
    111   [2] = { .name = "c.bin", .link_start = 3, .link_count = 0 },
    112   [3] = { .name = "d.bin", .link_start = 3, .link_count = 1 },
    113 ```
    114 
    115 And `FILE_LINK` contains this:
    116 ```c
    117   [0] = { .linked_file_id = 1, link_id = 0 },
    118   [1] = { .linked_file_id = 2, link_id = 1 },
    119   [2] = { .linked_file_id = 2, link_id = 0 },
    120   [3] = { .linked_file_id = 1, link_id = 0 },
    121 ```
    122 
    123 This means that `a.bin` has two links, starting at 0 in `FILE_LINK`. `link[0]`
    124 says it's a link to `file[1]` (i.e. `b.bin`), and `link[1]` says it's a link to
    125 `file[2]` (i.e. `c.bin`).
    126 
    127 `b.bin` only has one link, at `link[2]`. It's a link to `file[2]` (i.e.
    128 `c.bin`).
    129 
    130 `c.bin` has zero links, but `link_start` still contains 3, because the links
    131 would be there if there were one. Finally `d.bin` contains a single link, still
    132 starting at 3, since `3+0=0`.
    133 
    134 The actual link graph looks like this:
    135 ```
    136 a.bin ---> b.bin <--- d.bin
    137   |          |
    138   |          v
    139   +------> c.bin
    140 ```