You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
229 lines
5.7 KiB
Markdown
229 lines
5.7 KiB
Markdown
STCM
|
|
====
|
|
|
|
STCM files store bytecode for a VM used by the Re;Births.
|
|
|
|
The general structure of an STCM file is:
|
|
|
|
* Header
|
|
* Padding until 0x50, `GLOBAL_DATA` string, padding until 0x70.
|
|
* Data structures
|
|
* Padding until dividable by 16, `CODE_START_\0`
|
|
* Instructions
|
|
* `EXPORT_DATA\0`
|
|
* Export entries
|
|
* `COLLECTION_LINK\0`
|
|
* Collection link header, entries until EOF
|
|
|
|
But generally the engine do not care about it, it only follows the offsets. The
|
|
strings and extra padding can be removed without breaking anything. There are
|
|
usually a lot of dead code and unused data in the STCM files.
|
|
|
|
Header
|
|
------
|
|
|
|
```c++
|
|
struct Header
|
|
{
|
|
char magic[0x20];
|
|
uint32_t export_offset;
|
|
uint32_t export_count;
|
|
uint32_t field_28; // ??
|
|
uint32_t collection_link_offset;
|
|
};
|
|
sizeof(Header) == 0x30
|
|
```
|
|
|
|
`magic` starts with `STCM2L` (where `L` likely means little-endian), after a
|
|
null terminated describing the build date (?) of the generator follows (ignored
|
|
by the game). (Like`STCM2L Apr 22 2013 19:39:01` in R;B3).
|
|
|
|
There are `export_count` count `ExportEntry` at `export_offset` and a
|
|
`CollectionLinkHeader` at `collection_link_offset`.
|
|
|
|
|
|
Export entry
|
|
------------
|
|
|
|
```c++
|
|
struct ExportEntry
|
|
{
|
|
uint32_t type; // 0 = CODE, 1 = DATA
|
|
char name[0x20];
|
|
uint32_t offset;
|
|
};
|
|
sizeof(ExportEntry) == 0x28
|
|
```
|
|
|
|
If `type == 0`, `offset` is an offset to an `InstructionHeader`, if `type == 1`,
|
|
it should be an offset to `Data` (not really used by the game). `name` is a null
|
|
terminated string.
|
|
|
|
Data
|
|
----
|
|
|
|
```c++
|
|
struct Data
|
|
{
|
|
uint32_t type; // 0 or 1 ??
|
|
uint32_t offset_unit;
|
|
uint32_t field_8;
|
|
uint32_t length;
|
|
};
|
|
sizeof(Data) = 0x10
|
|
```
|
|
|
|
`length` bytes of data follow the structure. `offset_unit` is `length/4` for
|
|
string data, 1 otherwise?
|
|
|
|
Strings are stored as null terminated strings, the length is padded to a
|
|
multiple of 4 (so a string of length 4 will have a terminating zero byte and 3
|
|
padding zero bytes at the end).
|
|
|
|
|
|
Instruction
|
|
-----------
|
|
|
|
```c++
|
|
struct InstructionHeader
|
|
{
|
|
uint32_t is_call; // 0 or 1
|
|
uint32_t opcode_offset;
|
|
uint32_t param_count; // < 16
|
|
uint32_t size;
|
|
};
|
|
sizeof(InstructionHeader) == 0x10;
|
|
```
|
|
|
|
If `is_call` is true, `opcode_offset` contains an offset to the called
|
|
instruction. If `is_call` is false, `opcode_offset` is simply an opcode number
|
|
in the VM, not an offset.
|
|
|
|
`param_count` of `Parameter` structure follows the header, followed by `size`
|
|
bytes of arbitrary payload (usually `Data` structures described above). After
|
|
the payload usually comes the next instruction. The following opcodes are known
|
|
to jump unconditionally, thus in this case the engine won't try to parse and
|
|
execute the next instruction: 0, 6.
|
|
|
|
```c++
|
|
struct Parameter
|
|
{
|
|
uint32_t param_0;
|
|
uint32_t param_4;
|
|
uint32_t param_8;
|
|
};
|
|
sizeof(Parameter) == 0x0c
|
|
|
|
uint32_t TypeTag(uint32_t x) { return x >> 30; }
|
|
uint32_t Value(uint32_t x) { return x & 0x3fffffff; }
|
|
```
|
|
|
|
The meaning of the members depend on the value of `TypeTag(param_0)` (i.e. the
|
|
upper two bits of `param_0`.
|
|
|
|
### `param_0`
|
|
|
|
```c++
|
|
enum Type
|
|
{
|
|
MEM_OFFSET = 0,
|
|
IMMEDIATE = 1,
|
|
INDIRECT = 2,
|
|
SPECIAL = 3,
|
|
};
|
|
```
|
|
|
|
If `TypeTag(param_0) == MEM_OFFSET`, `Value(param_0)` (the lower 30 bits of
|
|
`param_0`) contains an offset to a `Data` structure, `param_4` and `param_8`
|
|
parsed normally.
|
|
|
|
If `TypeTag(param_0) == INDIRECT`, `Value() < 256` (and not a file offset).
|
|
`param_4` must be `0x40000000`. `param_8` parsed normally.
|
|
|
|
If `TypeTag(param_0) == SPECIAL`, then there are other subcases:
|
|
|
|
```c++
|
|
enum TypeSpecial
|
|
{
|
|
READ_STACK_MIN = 0xffffff00, // range MIN..MAX
|
|
READ_STACK_MAX = 0xffffff0f,
|
|
READ_4AC_MIN = 0xffffff20, // range MIN..MAX
|
|
READ_4AC_MAX = 0xffffff27,
|
|
INSTR_PTR0 = 0xffffff40,
|
|
INSTR_PTR1 = 0xffffff41,
|
|
COLL_LINK = 0xffffff42,
|
|
};
|
|
```
|
|
|
|
If `(param_0 >= READ_STACK_MIN && param_0 <= READ_STACK_MAX) || (param_0 >=
|
|
READ_4AC_MIN && param_0 <= READ_4AC_MAX)`, then `param_4` and `param_8` must be
|
|
`0x40000000`.
|
|
|
|
If `param_0 == INSTR_PTR0 || param_0 == INSTR_PTR1`, then `param_4` contains an
|
|
offset to an another instruction, `param_8` must be `0x40000000`.
|
|
|
|
If `patam_0 == COLL_LINK`, `param_4` contains an offset to a `CollectionLink`
|
|
structure, `param_8` must be 8.
|
|
|
|
### `param_4` and `param_8`
|
|
|
|
The following only applies when these parameters are "parsed normally".
|
|
`param_n` refers to either `param_4` or `param_8`.
|
|
|
|
If `TypeTag(param_n) == MEM_OFFSET`, `Value(param_n)` contains an offset to ???.
|
|
|
|
If `TypeTag(param_n) == IMMEDIATE || TypeTag(param_n) == INDIRECT`,
|
|
`Value(param_n)` contains a value.
|
|
|
|
If `TypeTag(param_n) == SPECIAL`, then `(param_n >= READ_STACK_MIN && param_n <=
|
|
READ_STACK_MAX) || (param_n >= READ_4AC_MIN && param_n <= READ_4AC_MAX)`.
|
|
|
|
Collection link
|
|
---------------
|
|
|
|
```c++
|
|
struct CollectionLinkHeader
|
|
{
|
|
uint32_t field_00;
|
|
uint32_t offset;
|
|
uint32_t count;
|
|
uint32_t field_0c;
|
|
uint32_t field_10;
|
|
uint32_t field_14;
|
|
uint32_t field_18;
|
|
uint32_t field_1c;
|
|
uint32_t field_20;
|
|
uint32_t field_24;
|
|
uint32_t field_28;
|
|
uint32_t field_2c;
|
|
uint32_t field_30;
|
|
uint32_t field_34;
|
|
uint32_t field_38;
|
|
uint32_t field_3c;
|
|
};
|
|
sizeof(CollectionLinkHeader) == 0x40);
|
|
```
|
|
|
|
The `field_*` members are always zero in the files I encountered... There are
|
|
`count` `CollectionLinkEntry` structures at `offset`.
|
|
|
|
|
|
```c++
|
|
struct CollectionLinkEntry
|
|
{
|
|
uint32_t name_0;
|
|
uint32_t name_1;
|
|
uint32_t field_08;
|
|
uint32_t field_0c;
|
|
uint32_t field_10;
|
|
uint32_t field_14;
|
|
uint32_t field_18;
|
|
uint32_t field_1c;
|
|
};
|
|
sizeof(CollectionLinkEntry) == 0x20;
|
|
```
|
|
|
|
The `field_*` members are always zero in the files I encountered... `name_0` and
|
|
`name_1` are offsets to null terminated strings (they're generally after the
|
|
last entry).
|