The game uses the chl
format to represent a script of the game logic such as
God AI, objectives, picking creatures, cinematics, everything.
These files are internally referred to as challenge files, they are compiled from a proprietary scripting language into bytecode, this bytecode is then interpreted using the LHVM.
The file has a header identifying the file format and version: ASCII 4 bytes
LHVM
followed by a 4 byte integer representing the version, Black & White 2
is 12
.
A uint32
at the start of this section represents the number of global variables
to read. Each global variable is just a null terminated string representing the
global variable's name. You can create a dictionary mapping like so:
map <uint32_t, string> global_names;
for (uint32_t i = 0; i < length; i++)
global_names[i] = read_null_terminated_string()
A uint32
at the start of this section represents the number of instructions to
read. Each instruction can be represented as an array:
Type | Description | |
---|---|---|
Type | uint32_t | Type defines the overall function of the instruction, such as add, sub,,etc. Note that there will be several instructions which share the same,main Type value. For example there are a number of “push” instructions,that all have a type of 2, since they all push data onto the stack.,However, their data types and parameters will differ. |
Sub Type | uint32_t | Usually used to differentiate functions who's overall functions are the,same, but still differ slightly. This can be seein in the “pop”,instruction. One is used to simply pop a value from the stack,,effectively deleting it. Another is used to pop a value and store it,into a variable. Since these functions both pop a variable from the,stack, they share a Type, but since they handle the popped value,differently the have different SubTypes. |
Data Type | uint32_t | Usually used to specify what kind of data the instruction is going to be,working with. This is not a reliable way of determining operand data,types however. It can be useful for trying to figure out unknown,instructions. |
Parameter | var32* | Data used by instruction, if applicable. Most instructions use the,stack as their source of data. Though, obviously, the data has to get,onto the stack some how, this is where this field comes into play. |
Line Number | uint32_t | This field holds the line number that was responsible for,generating this instruction. On occasion this field will be null due to,auto generated instructions that do not link directly to any one line in,the code. These, luckily, are very few. |
A complete list of opcodes can be found here. |
The total number of bytes to read here is 20 * instruction count
A uint32
at the start of this section represents the number of procedures that
need to be auto started. Each procedure is represented as a uint32
of the
procedure's id read in the next section.
A uint32
at the start of this section represents the number of procedures that
need to be read.
Type | Description | |
---|---|---|
Name | string | The name of the procedure. |
Source Filename | string | The filename of the source file this was compiled from. |
? | uint32_t | Unknown. |
? | uint32_t | Unknown. |
Local Variables | uint32_t + data | Names of the local variables, read the integer and then that amount of null terminated strings. |
Instruction Address | uint32_t | The location of the instruction to start at in the instruction array. |
Parameter Count | uint32_t | How many local variables are parameters. |
Procedure ID | uint32_t | Unique ID of this procedure, used by instructions to call it. |
A raw byte array represented with it's length prefixed as a uint32
. Contains
data used throughout the LHVM.
4096 bytes of null bytes padding.
A lonesome uint32
always 0 in every test case.
A uint32
at the start of this section represents the number of saved globals
that need to be read.
Type | Description | |
---|---|---|
? | uint32_t | Unknown |
Value | byte[4] | The data to set the global to - unspecified type because it can be different. |
Name | string | The name of the variable. |
Saved globals are simply default values for the globals.