neptools

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

stcm.md (5792B)


      1 STCM
      2 ====
      3 
      4 STCM files store bytecode for a VM used by the Re;Births.
      5 
      6 The general structure of an STCM file is:
      7 
      8 * Header
      9 * Padding until 0x50, `GLOBAL_DATA` string, padding until 0x70.
     10 * Data structures
     11 * Padding until dividable by 16, `CODE_START_\0`
     12 * Instructions
     13 * `EXPORT_DATA\0`
     14 * Export entries
     15 * `COLLECTION_LINK\0`
     16 * Collection link header, entries until EOF
     17 
     18 But generally the engine do not care about it, it only follows the offsets. The
     19 strings and extra padding can be removed without breaking anything. There are
     20 usually a lot of dead code and unused data in the STCM files.
     21 
     22 Header
     23 ------
     24 
     25 ```c++
     26 struct Header
     27 {
     28     char magic[0x20];
     29     uint32_t export_offset;
     30     uint32_t export_count;
     31     uint32_t field_28; // ??
     32     uint32_t collection_link_offset;
     33 };
     34 sizeof(Header) == 0x30
     35 ```
     36 
     37 `magic` starts with `STCM2L` (where `L` likely means little-endian), after a
     38 null terminated describing the build date (?) of the generator follows (ignored
     39 by the game). (Like`STCM2L Apr 22 2013 19:39:01` in R;B3).
     40 
     41 There are `export_count` count `ExportEntry` at `export_offset` and a
     42 `CollectionLinkHeader` at `collection_link_offset`.
     43 
     44 
     45 Export entry
     46 ------------
     47 
     48 ```c++
     49 struct ExportEntry
     50 {
     51     uint32_t type; // 0 = CODE, 1 = DATA
     52     char name[0x20];
     53     uint32_t offset;
     54 };
     55 sizeof(ExportEntry) == 0x28
     56 ```
     57 
     58 If `type == 0`, `offset` is an offset to an `InstructionHeader`, if `type == 1`,
     59 it should be an offset to `Data` (not really used by the game). `name` is a null
     60 terminated string.
     61 
     62 Data
     63 ----
     64 
     65 ```c++
     66 struct Data
     67 {
     68     uint32_t type; // 0 or 1 ??
     69     uint32_t offset_unit;
     70     uint32_t field_8;
     71     uint32_t length;
     72 };
     73 sizeof(Data) = 0x10
     74 ```
     75 
     76 `length` bytes of data follow the structure. `offset_unit` is `length/4` for
     77 string data, 1 otherwise?
     78 
     79 Strings are stored as null terminated strings, the length is padded to a
     80 multiple of 4 (so a string of length 4 will have a terminating zero byte and 3
     81 padding zero bytes at the end).
     82 
     83 
     84 Instruction
     85 -----------
     86 
     87 ```c++
     88 struct InstructionHeader
     89 {
     90     uint32_t is_call; // 0 or 1
     91     uint32_t opcode_offset;
     92     uint32_t param_count; // < 16
     93     uint32_t size;
     94 };
     95 sizeof(InstructionHeader) == 0x10;
     96 ```
     97 
     98 If `is_call` is true, `opcode_offset` contains an offset to the called
     99 instruction. If `is_call` is false, `opcode_offset` is simply an opcode number
    100 in the VM, not an offset.
    101 
    102 `param_count` of `Parameter` structure follows the header, followed by `size`
    103 bytes of arbitrary payload (usually `Data` structures described above). After
    104 the payload usually comes the next instruction. The following opcodes are known
    105 to jump unconditionally, thus in this case the engine won't try to parse and
    106 execute the next instruction: 0, 6.
    107 
    108 ```c++
    109 struct Parameter
    110 {
    111     uint32_t param_0;
    112     uint32_t param_4;
    113     uint32_t param_8;
    114 };
    115 sizeof(Parameter) == 0x0c
    116 
    117 uint32_t TypeTag(uint32_t x) { return x >> 30; }
    118 uint32_t Value(uint32_t x) { return x & 0x3fffffff; }
    119 ```
    120 
    121 The meaning of the members depend on the value of `TypeTag(param_0)` (i.e. the
    122 upper two bits of `param_0`.
    123 
    124 ### `param_0`
    125 
    126 ```c++
    127 enum Type
    128 {
    129     MEM_OFFSET = 0,
    130     IMMEDIATE = 1,
    131     INDIRECT = 2,
    132     SPECIAL = 3,
    133 };
    134 ```
    135 
    136 If `TypeTag(param_0) == MEM_OFFSET`, `Value(param_0)` (the lower 30 bits of
    137 `param_0`) contains an offset to a `Data` structure, `param_4` and `param_8`
    138 parsed normally.
    139 
    140 If `TypeTag(param_0) == INDIRECT`, `Value() < 256` (and not a file offset).
    141 `param_4` must be `0x40000000`. `param_8` parsed normally.
    142 
    143 If `TypeTag(param_0) == SPECIAL`, then there are other subcases:
    144 
    145 ```c++
    146 enum TypeSpecial
    147 {
    148     READ_STACK_MIN = 0xffffff00, // range MIN..MAX
    149     READ_STACK_MAX = 0xffffff0f,
    150     READ_4AC_MIN   = 0xffffff20, // range MIN..MAX
    151     READ_4AC_MAX   = 0xffffff27,
    152     INSTR_PTR0     = 0xffffff40,
    153     INSTR_PTR1     = 0xffffff41,
    154     COLL_LINK      = 0xffffff42,
    155 };
    156 ```
    157 
    158 If `(param_0 >= READ_STACK_MIN && param_0 <= READ_STACK_MAX) || (param_0 >=
    159 READ_4AC_MIN && param_0 <= READ_4AC_MAX)`, then `param_4` and `param_8` must be
    160 `0x40000000`.
    161 
    162 If `param_0 == INSTR_PTR0 || param_0 == INSTR_PTR1`, then `param_4` contains an
    163 offset to an another instruction, `param_8` must be `0x40000000`.
    164 
    165 If `patam_0 == COLL_LINK`, `param_4` contains an offset to a `CollectionLink`
    166 structure, `param_8` must be 8.
    167 
    168 ### `param_4` and `param_8`
    169 
    170 The following only applies when these parameters are "parsed normally".
    171 `param_n` refers to either `param_4` or `param_8`.
    172 
    173 If `TypeTag(param_n) == MEM_OFFSET`, `Value(param_n)` contains an offset to ???.
    174 
    175 If `TypeTag(param_n) == IMMEDIATE || TypeTag(param_n) == INDIRECT`,
    176 `Value(param_n)` contains a value.
    177 
    178 If `TypeTag(param_n) == SPECIAL`, then `(param_n >= READ_STACK_MIN && param_n <=
    179 READ_STACK_MAX) || (param_n >= READ_4AC_MIN && param_n <= READ_4AC_MAX)`.
    180 
    181 Collection link
    182 ---------------
    183 
    184 ```c++
    185 struct CollectionLinkHeader
    186 {
    187     uint32_t field_00;
    188     uint32_t offset;
    189     uint32_t count;
    190     uint32_t field_0c;
    191     uint32_t field_10;
    192     uint32_t field_14;
    193     uint32_t field_18;
    194     uint32_t field_1c;
    195     uint32_t field_20;
    196     uint32_t field_24;
    197     uint32_t field_28;
    198     uint32_t field_2c;
    199     uint32_t field_30;
    200     uint32_t field_34;
    201     uint32_t field_38;
    202     uint32_t field_3c;
    203 };
    204 sizeof(CollectionLinkHeader) == 0x40);
    205 ```
    206 
    207 The `field_*` members are always zero in the files I encountered... There are
    208 `count` `CollectionLinkEntry` structures at `offset`.
    209 
    210 
    211 ```c++
    212 struct CollectionLinkEntry
    213 {
    214     uint32_t name_0;
    215     uint32_t name_1;
    216     uint32_t field_08;
    217     uint32_t field_0c;
    218     uint32_t field_10;
    219     uint32_t field_14;
    220     uint32_t field_18;
    221     uint32_t field_1c;
    222 };
    223 sizeof(CollectionLinkEntry) == 0x20;
    224 ```
    225 
    226 The `field_*` members are always zero in the files I encountered... `name_0` and
    227 `name_1` are offsets to null terminated strings (they're generally after the
    228 last entry).