Files
rex-wizards-save-editor/ricsave/arr.py
T
Patryk GenschandCodex edd8ceda9e Add web-based save editor for "Reksio i Czarodzieje"
- Implemented a local web server using http.server to serve the save editor.
- Created a user interface with HTML, CSS, and JavaScript for managing game slots and editing save files.
- Added API endpoints for retrieving slot information, overview, and raw data for editing.
- Implemented functionality for saving changes back to the game files.
- Added tests for ARR and DTA formats to ensure data integrity during round-trip serialization.

Co-authored-by: Codex <noreply@openai.com>
2026-08-03 01:18:08 +02:00

94 lines
3.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Obiekt ARRAY silnika PIK — pliki .ARR i .SAV.
Format (little-endian, bez nagłówka):
uint32 count
count × { uint32 type; type==1 ? int32 : (uint32 len; bytes[len]) }
type 1 = INTEGER, type 2 = STRING (cp1250, bez terminatora).
"""
from __future__ import annotations
import struct
from pathlib import Path
ENCODING = "cp1250"
TYPE_INT = 1
TYPE_STR = 2
TYPE_BOOL = 3
TYPE_FLOAT = 4
Value = int | str
class ArrError(ValueError):
"""Plik .ARR nie trzyma się formatu."""
def loads(data: bytes) -> list[Value]:
if len(data) < 4:
raise ArrError(f"plik ma {len(data)} B, minimum to 4 B (samo count)")
(count,) = struct.unpack_from("<I", data, 0)
off = 4
out: list[Value] = []
for i in range(count):
try:
(kind,) = struct.unpack_from("<I", data, off)
off += 4
if kind == TYPE_INT:
(value,) = struct.unpack_from("<i", data, off)
off += 4
out.append(value)
elif kind == TYPE_STR:
(length,) = struct.unpack_from("<I", data, off)
off += 4
raw = data[off : off + length]
if len(raw) != length:
raise ArrError(
f"element {i}: deklarowana długość {length} B, dostępne {len(raw)} B"
)
off += length
out.append(raw.decode(ENCODING))
elif kind == TYPE_BOOL:
(value,) = struct.unpack_from("<i", data, off)
off += 4
out.append(value != 0)
elif kind == TYPE_FLOAT:
(value,) = struct.unpack_from("<i", data, off)
off += 4
out.append(value / 1000) # w Reksio i Czarodzieje dzielone jest przez 1000
else:
raise ArrError(f"element {i} @0x{off - 4:x}: nieznany typ {kind}")
except struct.error as exc:
raise ArrError(f"element {i}: plik urwany @0x{off:x}") from exc
if off != len(data):
raise ArrError(f"{len(data) - off} B śmieci na końcu (po {count} elementach)")
return out
def dumps(values: list[Value]) -> bytes:
parts = [struct.pack("<I", len(values))]
for i, value in enumerate(values):
if isinstance(value, bool):
parts.append(struct.pack("<Ii", TYPE_BOOL, 1 if value else 0))
elif isinstance(value, int):
parts.append(struct.pack("<Ii", TYPE_INT, value))
elif isinstance(value, str):
raw = value.encode(ENCODING)
parts.append(struct.pack("<II", TYPE_STR, len(raw)) + raw)
elif isinstance(value, float):
parts.append(struct.pack("<Ii", TYPE_FLOAT, int(value * 1000))) # w Reksio i Czarodzieje mnożone jest przez 1000
else:
raise ArrError(f"element {i}: typ {type(value).__name__} nieobsługiwany")
return b"".join(parts)
def read(path: str | Path) -> list[Value]:
return loads(Path(path).read_bytes())
def write(path: str | Path, values: list[Value]) -> None:
Path(path).write_bytes(dumps(values))