File size: 2,353 Bytes
767a051 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | """Binary .tmg format for TOMAGI 1.0."""
from __future__ import annotations
import struct
from pathlib import Path
from .core import Cell, Program, State
MAGIC = b"TOMAGI1\0"
VERSION_WORD = 0x00010000
HEADER_SIZE = 128
CELL_SIZE = 48
STATE_SIZE = 64
_HEADER = struct.Struct("<8s14I") # 64 bytes: magic + eight fields + six reserved
_STATE = struct.Struct("<16I")
_CELL = struct.Struct("<12I")
def dumps(program: Program) -> bytes:
header = _HEADER.pack(
MAGIC,
VERSION_WORD,
program.flags & 0xFFFFFFFF,
len(program.cells),
program.entry,
program.seed & 0xFFFFFFFF,
program.default_ticks,
CELL_SIZE,
STATE_SIZE,
0, 0, 0, 0, 0, 0,
)
assert len(header) == 64
state_blob = _STATE.pack(*program.initial_state.words())
cells_blob = b"".join(_CELL.pack(*cell.words()) for cell in program.cells)
return header + state_blob + cells_blob
def loads(data: bytes) -> Program:
if len(data) < HEADER_SIZE:
raise ValueError("file is shorter than the TOMAGI header")
values = _HEADER.unpack_from(data, 0)
magic = values[0]
if magic != MAGIC:
raise ValueError("not a TOMAGI 1.0 program")
version, flags, count, entry, seed, ticks, cell_size, state_size = values[1:9]
reserved = values[9:15]
if any(reserved):
raise ValueError("reserved TOMAGI header words must be zero")
if version != VERSION_WORD:
raise ValueError(f"unsupported TOMAGI version word 0x{version:08x}")
if cell_size != CELL_SIZE or state_size != STATE_SIZE:
raise ValueError("record size does not match TOMAGI 1.0")
expected = HEADER_SIZE + count * CELL_SIZE
if len(data) != expected:
raise ValueError(f"file length {len(data)} does not match expected {expected}")
state = State.from_words(_STATE.unpack_from(data, 64))
cells = []
offset = HEADER_SIZE
for _ in range(count):
cells.append(Cell.from_words(_CELL.unpack_from(data, offset)))
offset += CELL_SIZE
return Program(cells=cells, entry=entry, seed=seed, default_ticks=ticks,
initial_state=state, flags=flags)
def dump(program: Program, path: str | Path) -> None:
Path(path).write_bytes(dumps(program))
def load(path: str | Path) -> Program:
return loads(Path(path).read_bytes())
|