-
Notifications
You must be signed in to change notification settings - Fork 0
Liberation Technical
OpenCaptive currently verifies and opens the known CD32 data track, reads its ISO9660 filesystem by content hash, and provides a separate city/interior runtime loop. It does not yet reproduce the original CityGen/PlotGen game logic. The verified payloads are preserved as a reverse-engineering boundary, not silently substituted for original behaviour.
The supported track is a raw MODE1/2352 image containing ISO9660 with CDTV extensions. ISO sectors are 2352 bytes in raw storage; user data starts after the MODE1 framing. The ISO reader validates directory-record extents and file sizes before returning a buffer.
The root-track and all current runtime-analysis resources are selected by their SHA-256 bytes. See Data identity and verification for the manifest.
The verified resource set includes an executable payload, city generator, plot generator, plot text, city text and dialogue text. At present:
-
liberation_data_open()opens the hash-identified raw track. - Each required resource is looked up by a hash scan of the ISO directory.
- Verification fails closed if any required resource is absent or mismatched.
-
liberation_data_read()exposes byte buffers to future parsers without filename coupling.
No original payload is bundled or emitted by tooling.
The hash-identified main executable
db61f7e39fd31ac19b82216ea963711728d25518454fae42fd89c5bab52f2215
is a valid four-hunk Amiga LoadSeg stream. Structural inspection reports one
225,396-byte code hunk at byte offset 44, one data hunk, two BSS hunks and
1,356 RELOC32 entries. Hunk tags may carry Amiga memory-attribute bits, so
the parser masks those tag bits before validating the container. This makes
the original presentation/game executable available for static analysis; it
does not execute it or claim that its gameplay logic is already reproduced.
liberation_inventory is the discovery entry point for every remaining CD32
resource. It recursively walks the verified ISO and emits only SHA-256 digest,
byte length and container class (IFF/ILBM, IFF/ANIM, RNC1,
Amiga-HUNK, AMOS sprite/icon bank or raw). It intentionally neither displays
nor accepts filenames:
./build/liberation_inventory /path/to/mediaAny future graphics decoder must record the selected digest in code and tests before it is wired into the renderer.
The hash-identified resource
07cca53c7efaac9e2880d50524039b0f9cb2a403e0cfbfb0b5f6ce408594d2d1
has the AmSp signature and a 42-entry declaration. Its initial eight entries
are conventional four-colour-bitplane images followed by a fifth, one-bit
transparency mask plane. The observed record size is therefore
10 + words × height × (depth + 1) × 2; this makes every boundary through the
eighth entry exact. amos_sprite_dump decodes this verified, unflagged prefix
to a PPM inspection image using a resource hash and an entry index.
The high bit of the width word is not compression. The original 68000 loader doubles the low 15 bits and subtracts one when that bit is set, so it represents an odd number of bytes per row. The same planar-plus-mask decoder handles both even and odd row widths. Records are word aligned: an odd total payload is followed by one padding byte before the next header. The full declared 42-entry stream, including the final entry, is now structurally decodable. The decoder remains an analysis tool until the runtime maps particular sprite hashes to particular game entities.
The loader also tests the X-hotspot sign bit. A negative X hotspot adds one transparency-mask plane after the colour planes; a non-negative hotspot leaves the image unmasked. Both variants are now accepted by the decoder.
The separately hash-identified resource
d6bb0dd9c578beb8e84ddf9f458f0be43ec158b2b261491d023e972d2812c2d2
contains one unmasked 320×109 AMOS scene. It remains available to the analysis
tools only. The runtime does not place it into an inferred city or building,
because that would falsely assign original pixels to an unverified game state.
RNC method 2 in the verified CD32 data is the old Amiga backwards stream: it has a 12-byte header, reads compressed bytes from the packed end and writes the decoded bytes backwards. OpenCaptive verifies its output byte-for-byte against a separately decoded local reference before using it for container analysis.
The presentation bundle is selected from the verified ISO by SHA-256
1d3a335d254c0eae919a712dd73bd41b24ed897bf145ed118ccf2277baa7a35f.
Its byte offset 386824 is an old-RNC2 stream which expands to a FORM/ANIM
container. The first PACK record declares a 320×167, six-bitplane image
(40 × 167 × 6 = 40,080 bytes) and is paired with the container's 32-colour
PALL palette. Runtime selection is exclusively the bundle digest plus this
offset; the ISO member name is neither queried nor used as an identity.
liberation_anim_decode_first_frame() handles this verified record and
liberation_anim_blit() renders planar pixels without interpolation. The
native capture's 320×167 city region has been directly compared against an
independent decoding of the same source bytes: both RGB buffers have SHA-256
c546bebd107928a5721cd1a33d7e458098b134d4cd86c48b6547f8b615abbdae.
This proves that particular original raster is rendered exactly. It does not
yet prove parity for animated overlays, the in-game HUD, city state, or plot
logic.
The PACK stream is not a sequence of independently decodable full frames.
The verified player first expands one bounded work area: in the city resource
that area is 40,094 bytes (14-byte descriptor plus 40,080 planar bytes). The
following bytes in the PACK chunk are not yet assigned a standalone record
meaning. liberation_pack_decode_workspace() exposes only the bounded area;
the stricter full-stream diagnostic intentionally reports the trailing format
data as undecoded. This keeps the exact first raster available without
misrepresenting unknown animation layers as image data.
The accompanying SCPT IFF chunk is now copied verbatim into
LiberationAnimScript after the same FORM boundary checks. Both the intro and
city presentation report decoded/SCPT through --verify-data liberation.
This makes the original scene bytecode available to the runtime without
pretending that its instruction semantics have already been recovered.
The verified CD32 player traverses a script by reading a big-endian 16-bit
record size at each record boundary, advancing by that full size, and stopping
at a zero size. OpenCaptive validates this first sequence and exposes its
records through liberation_anim_script_record_at(). All 21 decoded
presentation resources terminate cleanly under that rule. This proves the
container-level record boundaries only: the payload opcodes, timing units and
draw operations are still deliberately opaque.
One payload field is also directly evidenced by the player: byte 2 of a record
is multiplied by the current timing base, shifted right by four and added to
the next update countdown. It is exposed as timing_multiplier; the absolute
clock unit and all remaining payload fields are not yet assigned semantics.
The city-generator payload selected by
e54540c3bf8dfaf569380a135ac039f1438e9efb85cf6d5e3e487e25d4c7c13e
is 10,896 bytes and is recognised as an AmigaOS loadseg() executable. Its
embedded release string identifies it as CityGen 1.12, built 1994-01-03.
It uses the Amiga HUNK container layout. Initial disassembly shows an exported entry path that receives a caller-owned parameter block, clears a 12,288-byte work area and records 64×64 dimensions in its output state before invoking its generation routines. These are observations from the verified bytes, not a claim that OpenCaptive already reproduces CityGen output.
amiga_hunk_parse() now validates the HUNK header, allocation table, code,
data, BSS, RELOC32, symbol and end records without loading or executing the
payload. It is deliberately a structural parser: relocations are counted and
bounds-checked, but no original instruction stream is interpreted as gameplay
logic.
Against the verified CityGen digest, the parser reports two hunks, one code
block of 10,824 bytes at byte offset 36, one BSS block and one RELOC32
entry. The structural inspection command is hash-driven:
./build/liberation_hunk_info /path/to/media \
e54540c3bf8dfaf569380a135ac039f1438e9efb85cf6d5e3e487e25d4c7c13eThe verified PlotGen digest
bc9c922801661eb66024d0bcf822c03e38ffea7f3576693e0512692ccf6d6705
has the same two-hunk shape, with one 12,388-byte code block at offset 36,
one BSS block and three RELOC32 entries. This confirms that CityGen and
PlotGen are separate relocatable executable payloads, not data tables that can
be substituted with the current procedural interior generator.
The hash-identified city-text payload
99f7bd75794a7b4f3e94eeef9c61b756da938d862bb83339b140c18d02eb79c5
is 17,809 bytes. The hash-identified dialogue payload
e154d250c1acdbed66835bb356a699efdb6f9f8b5e6d586ca07080414610a94c
is 14,136 bytes. Both contain readable English prose interleaved with compact
control bytes and expression-like tokens, so neither is a flat NUL-terminated
string table.
The city-text payload has a readable location-description section whose records begin with a numeric selector followed by prose and branch comments. The surrounding control stream contains conditional expressions and variable references. The dialogue payload similarly contains response alternatives, quoted text and scripted branches. This is enough to establish that text is data-driven, but not enough to assign opcode semantics or wire original plot decisions into the runtime. A future parser must retain byte offsets and control boundaries, rather than stripping printable strings and treating them as independent records.
liberation_extract is the supported inspection entry point:
./build/liberation_extract /path/to/media \
e54540c3bf8dfaf569380a135ac039f1438e9efb85cf6d5e3e487e25d4c7c13e \
/tmp/citygen.binThe command first verifies the enclosing CD32 track, then performs an ISO lookup by the resource digest. Its output must stay outside version control.
The current LibState is deliberately separate from Captive's dungeon and
mission loop. It contains a 32×32 city grid, building footprints, up to four
interior floors, a city/building mode and player coordinates. Its deterministic
generator uses a seed, produces streets and building blocks, and supports
entering a building, walking interiors and using elevators.
This separation matters: Captive combat ticks and Captive generator-completion rules must not advance or end a Liberation session. The main loop explicitly branches before applying either Captive-only rule. The former inferred city and interior rasterizers have been removed; no procedural drawing is used as a fallback when original presentation data is available.
The next parity work is data-driven rather than visual:
- Decode executable container/relocation records without distributing code.
- Identify CityGen inputs, PRNG, output layout and persistent city state.
- Identify PlotGen’s building/interior format and plot progression state.
- Decode text table offsets, encoding and dialogue references.
- Add small independently testable parsers, golden hashes and structural invariants before wiring them into the live city loop.
Until those steps are complete, do not describe the procedural city as a faithful reproduction of Liberation’s original story, population or plot logic.