X'BPGH solutions are stored in the game's base save file, found at
Windows: %USERPROFILE%\Documents\My Games\Last Call BBS\<user-id>\save.dat
Linux: $HOME/.local/share/Last Call BBS/<user-id>/save.dat
The relevant lines of save.dat
are of the form
Toronto.Solution.<LevelID>.<SaveSlot> = <SolutionString>
LevelID is the numeric ID of the level (see this post). SaveSlot is 0, 1, 2, or 3 (top-left, top-right, bottom-left, bottom-right). SolutionString is is the binary solution file, zlib compressed and base64 encoded.
Decompressed solutions are variable-length encoded, using mostly little-endian (LE) 32-bit integers, and have the following high level structure
header: 4-byte LE int, always 1003 (0xEB 0x03 0x00 0x00)
num_rules: 4-byte LE int, always 16 (0x10 0x00 0x00 0x00)
rules: <num_rules = 16> rules in priority order, using a variable-length encoding, see below
start_coords: pair of 4-byte LE ints (x, y)
num_metal_coords: 4-byte LE int, always 0 except for level editor
metal_coords: <num_metal_coords> pairs of 4-byte LE ints (x, y)
All coordinates are 0-indexed and in (x, y)
form, with x
from left to right and y
from bottom to top (so 0 <= x < 4
and 0 <= y < 5
, origin at the bottom left). start_coords
gives the coordinates of the starting seed cell, and metal_coords
is a list of coordinates containing metal (only nonempty in the level editor, LevelID 16).
Rules are variable-length encoded, and each has length 13, 17, or 21 bytes. The rule format is:
target_cell_type: 4-byte LE int, see cell types
neighbor_cell_type: 4-byte LE int, see cell types
neighbor_direction: 4-byte LE int, 1 = RIGHT, 2 = UP, 4 = LEFT, 8 = DOWN
reaction_type: *1*-byte int, 0 = IGNORE, 1 = DIVIDE, 2 = SPECIALIZE, 3 = FUSE, 4 = DIE
divide_coords: (for DIVIDE reactions only) pair of 4-byte LE ints (dx, dy) from -1 to +1, direction to divide
fuse_direction: (for FUSE reactions only) 4-byte LE int, 1=R, 2=U, 4=L, 8=D, direction to fuse
specialize_type: (for SPECIALIZE reactions only) 4-byte LE int, see cell types
The cell types are:
0: IGNORE
1: SEED
2: FLESH
3: FLESH_HEART
4: FLESH_MUSCLE
5: FLESH_FAT
6: BONE
7: BONE_SPINE
8: SKIN
9: SKIN_HAIR
10: SKIN_EYE
11: METAL
12: ANY
13: NONE
Note that FLESH_HEART
and FLESH_MUSCLE
are transposed compared to the game UI.
For example (written in left-to-right byte order):
0x01000000 0x0c000000 0x02000000 0x01 0xffffffff 0x00000000 SEED with ANY ABOVE should DIVIDE towards (-1, 0) (LEFT)
0x08000000 0x00000000 0x01000000 0x03 0x08000000 SKIN (IGNORE neighbor) should FUSE DOWN
0x06000000 0x05000000 0x01000000 0x02 0x07000000 BONE with FLESH_FAT to the RIGHT should SPECIALIZE into BONE_SPINE
0x0a000000 0x00000000 0x01000000 0x04 SKIN_EYE (IGNORE neighbor) should DIE
0x04000000 0x00000000 0x01000000 0x00 FLESH_MUSCLE (IGNORE neighbor) should IGNORE (rule is treated as empty)
0x00000000 0x00000000 0x01000000 0x00 empty rule (has no effect)
This has been expanded into a full library+simulator at https://github.com/ecnerwala/xbpgh-sim.