goldbox.amiga_dax

The Amiga Gold Box .dax container, and the depacker its blocks are in.

goldbox/dos_savegame.py reads the DOS .DAX, which is a different format with the same extension: a little-endian index of id:u8 offset:u32 raw:u16 compressed:u16 and run-length coded blocks. This one is the Amiga’s – a big-endian index of id:u16 offset:u32 compressed:u16 raw:u16 and ByteKiller-packed blocks – and the two must never be read through each other’s reader (#65).

Why a conversion needs it. An Amiga Pool of Radiance saved game carries 7680 bytes of the area’s own ECL script, live on load exactly as the DOS one is, and a character record holds none of it. DOS keeps a script per container in ECL<n>.DAX; the Amiga keeps all of them in one /ecl.dax on disk 2, the POOLDATA volume. So a party cannot be converted to the Amiga without the player’s disk 2, and this is what reads it.

The depacker is transcribed from the game’s own code, at /program hunk 27 + $7346 (file offset 0x4887A), read with tools/amiga68k.py. It is the ByteKiller shape: a bit stream consumed backwards from the end of the block, writing the output backwards from its end, with a trailer of three big-endian longwords – the unpacked length, a checksum, and the first bit buffer. The checksum is a running XOR of every longword the stream reads and the routine ends with tst.l d5 on it, so a block that unpacks to the stated length with a non-zero checksum is a block that was read wrong.

CONFIRMED, and the oracle is the game’s own saved game: block 0 of ecl.dax unpacked, from byte 2 on, is byte for byte the 7468 bytes the shipped save/savgamA.dat carries in its script buffer. All 29 blocks unpack to exactly the length the index states with a zero checksum, and every one opens 88 13u16le 5000, the ECL load address DOS and the C64 use too.

Module Attributes

ENTRY

block id, offset from the end of the index, the stored size, the unpacked size.

TRAILER

unpacked length, checksum and the first bit buffer, one big-endian longword each.

Functions

block(data, block_id[, name])

One block of an Amiga .dax, unpacked.

block_ids(data[, name])

Which blocks a container holds, in the order the index lists them.

blocks(data[, name])

(id, unpacked bytes) for every block of an Amiga .dax.

index(data[, name])

(id, offset, stored size, unpacked size) for every block.

unpack(block[, raw_size, name])

Decompress one packed block, or raise saying what did not add up.

Exceptions

AmigaDaxError

This is not the Amiga .dax this reader knows how to read.

exception goldbox.amiga_dax.AmigaDaxError[source]

Bases: ValueError

This is not the Amiga .dax this reader knows how to read.

goldbox.amiga_dax.ENTRY = Struct('>HIHH')

block id, offset from the end of the index, the stored size, the unpacked size. Ten bytes, big-endian.

Type:

The .dax index entry

goldbox.amiga_dax.TRAILER = 12

unpacked length, checksum and the first bit buffer, one big-endian longword each.

Type:

How many bytes of trailer every packed block ends with

goldbox.amiga_dax.block(data, block_id, name='dax')[source]

One block of an Amiga .dax, unpacked. Raises if it is not there.

Parameters:
Return type:

bytes

goldbox.amiga_dax.block_ids(data, name='dax')[source]

Which blocks a container holds, in the order the index lists them.

Parameters:
Return type:

list[int]

goldbox.amiga_dax.blocks(data, name='dax')[source]

(id, unpacked bytes) for every block of an Amiga .dax.

Parameters:
Return type:

Iterator[tuple[int, bytes]]

goldbox.amiga_dax.index(data, name='dax')[source]

(id, offset, stored size, unpacked size) for every block.

A file too short for the index it declares is named as such rather than raising struct.error out of a comprehension, because the caller is a conversion reading the player’s own disk and “this is not an Amiga .dax” is an answer it has to be able to give.

Parameters:
Return type:

list[tuple[int, int, int, int]]

goldbox.amiga_dax.unpack(block, raw_size=None, name='block')[source]

Decompress one packed block, or raise saying what did not add up.

raw_size is the index’s own figure and is checked against the block’s own trailer when it is given; the two disagreeing means the index was read at the wrong stride, which is the failure this argument exists to catch.

The listing this follows, instruction for instruction:

  • the trailer, backwards – unpacked length, checksum, first bit buffer;

  • lsr.l #1,d0 for each bit, and when the buffer empties, a fresh longword with a sentinel 1 rotated into bit 31 so that each longword yields exactly 32 bits;

  • tag 0 then 0: a 3-bit count, then that many plus one literal bytes;

  • tag 0 then 1: an 8-bit offset, two bytes copied;

  • tag 1 then 00 or 01: a 9- or 10-bit offset, three or four bytes;

  • tag 1 then 10: an 8-bit count and a 12-bit offset;

  • tag 1 then 11: an 8-bit count, then count + 9 literal bytes.

Parameters:
Return type:

bytes