Engine changes vs the vendored upstream (full ledger in docs/upstream-patches.md): - Assets: replace the encrypted FILE000N.GLB archives with a catalog of pre-decrypted loose files (data/INDEX); GLB_SlotItems() drives mission-pack / registered-version detection; payloads never run the GLB cipher at runtime. - Platform: Linux/SDL2/ALSA only; Android, MSVC/MinGW, macOS packaging, bundled Windows SDL2 SDKs and the ENDOOM exit screen are gone; EXIT_Error now prints before exiting. - Rendering: decouple presentation from simulation -- sim stays at the original 70/3 Hz, frames render interpolated at display rate (player, map scroll, enemies, shots, bonuses, anims, shadows); menus keep 70 Hz pacing; vsync with a delay-guard fallback. - QoL: borderless window mode (Video/borderless), no menu cursor snapping onto default buttons. Fix four regressions from the render split: - Shield bars never redrew: RAP_ThinkStats latches g_oldshield each sim step before RAP_DisplayStats' dirty check could fire. Bars now draw unconditionally per rendered frame; g_oldsuper deleted, g_oldshield kept as sim-side damage-detection state only. - Dying ships despawned without exploding: the render loop never called ANIMS_DisplayHigh(); most air-death explosions register HIGH_AIR. Restored between the player and enemy shots, as upstream orders it. - Laser turret broke: reusing doneflag as a draw marker (it is hit/retire game state, and the turret retire path never frees its shot) hid the beam on hitting ticks and leaked one SHOTS pool slot per fire until no weapon could shoot. S_LINE tracers now carry an age counter: one Think pass, every render of their birth step, purged at the top of the next Think. - Muzzle flash was cleared per rendered frame, making it invisible at display refresh rates; now cleared once per simulation step.
75 lines
2.6 KiB
Markdown
75 lines
2.6 KiB
Markdown
# GLB archive format
|
|
|
|
The original game ships its data as `FILE0000.GLB` ... `FILE0006.GLB`,
|
|
produced by the "GLib" library bundled with the commercial source. The
|
|
format below was reverse-engineered from `raptor/src/glbapi.cpp` (upstream
|
|
loader) and verified against all seven shipped archives (6449 items total).
|
|
|
|
## Container
|
|
|
|
A GLB file is a flat sequence of 28-byte records:
|
|
|
|
```c
|
|
typedef struct {
|
|
uint32_t opt; // 0 = plain payload, 1 = encrypted payload
|
|
uint32_t offset; // payload offset in the file
|
|
uint32_t filesize; // payload size in bytes
|
|
char name[16]; // NUL-padded item name, e.g. "GENMIDI_OP2"
|
|
} KEYFILE; // little-endian fields
|
|
```
|
|
|
|
- **Record 0 is the header.** Its `offset` field holds the *item count*;
|
|
its other fields are unused.
|
|
- Records `1 .. count` are the item descriptor table, starting at byte 28.
|
|
- Item payloads live at each descriptor's `offset`, `filesize` bytes long.
|
|
- Names are unique per archive only in practice; duplicates (e.g. dozens of
|
|
`CHASE_AGX`, thousands of empty `unnamed` labels) are common.
|
|
- Descriptor entries with `filesize == 0` are labels/placeholders.
|
|
|
|
## Cipher
|
|
|
|
Every record *and* every payload flagged `opt == 1` is scrambled with a
|
|
rolling additive cipher keyed on the ASCII string `32768GLB`, seeded at 25
|
|
(`SEED` in `glbapi.h`):
|
|
|
|
```
|
|
kidx = 25 % len(key) # 25 % 8 = 1
|
|
prev = key[kidx]
|
|
|
|
# decrypt (GLB_DeCrypt):
|
|
for each byte b:
|
|
out = (b - key[kidx] - prev) mod 256
|
|
prev = b # chain uses the CIPHERTEXT byte
|
|
kidx = (kidx + 1) mod len(key)
|
|
|
|
# encrypt (GLB_EnCrypt) is the inverse:
|
|
out = (b + key[kidx] + prev) mod 256
|
|
prev = out # chain uses the PLAINTEXT byte
|
|
```
|
|
|
|
Notes:
|
|
|
|
- The cipher state resets **per record** and **per payload**.
|
|
- The original C uses signed `char`; the stored byte equals the
|
|
mathematical result mod 256 either way, so unsigned arithmetic is
|
|
byte-exact.
|
|
- Payload encryption is independent of descriptor encryption: an archive
|
|
typically mixes plain and encrypted items.
|
|
|
|
## Verification
|
|
|
|
`tools/glbextract.py` implements this format. Correctness was checked
|
|
against known payloads:
|
|
|
|
- `GENMIDI_OP2` starts with the DMX magic `#OPL_II#` (11908 bytes)
|
|
- `PALETTE_DAT` is exactly 768 bytes (256 x RGB)
|
|
- `DMXGUS_INI` decodes to readable INI text
|
|
- item counts per archive match the id ranges in `raptor/src/fileids.h`
|
|
(e.g. slot 0: 230 items, last id `0x0e5`)
|
|
- every payload lies within its archive's bounds (extractor warns otherwise)
|
|
|
|
## See also
|
|
|
|
- [asset-pipeline.md](asset-pipeline.md) — how archives become `data/`
|
|
- `tools/glbextract.py` — reference implementation (Python, stdlib only)
|