Skip to content

Instantly share code, notes, and snippets.

@mooskagh
Created January 26, 2021 15:32
Show Gist options
  • Save mooskagh/697d7f0a3f7af42cf3ba564752eb04b3 to your computer and use it in GitHub Desktop.
Save mooskagh/697d7f0a3f7af42cf3ba564752eb04b3 to your computer and use it in GitHub Desktop.
import struct
from dataclasses import dataclass, field
def _CheckMagic(f, data, write):
if write:
f.write(b'P1R')
else:
assert b'P1R' == f.read(3)
def _SerializeWithFormat(f, data, field, write, fmt):
if write:
f.write(struct.pack(fmt, getattr(data, field)))
else:
size = struct.calcsize(fmt)
setattr(data, field, struct.unpack(fmt, f.read(size))[0])
def _SerializeU16(f, data, field, write):
_SerializeWithFormat(f, data, field, write, 'H')
def _SerializeI16(f, data, field, write):
_SerializeWithFormat(f, data, field, write, 'h')
def _SerializeU8(f, data, field, write):
_SerializeWithFormat(f, data, field, write, 'B')
def _SerializeI8(f, data, field, write):
_SerializeWithFormat(f, data, field, write, 'b')
def _SerializeU64(f, data, field, write):
_SerializeWithFormat(f, data, field, write, 'Q')
def _SerializeU32(f, data, field, write):
_SerializeWithFormat(f, data, field, write, 'I')
def _SerializeString(f, data, field, write, fmt='B', as_array=False):
if write:
val = getattr(data, field)
if as_array:
assert isinstance(val, list)
val = bytes(val)
else:
assert isinstance(val, bytes)
f.write(struct.pack(fmt, len(val)))
f.write(val)
else:
size_size = struct.calcsize(fmt)
size = struct.unpack(fmt, f.read(size_size))[0]
val = f.read(size) if size else b''
setattr(data, field, list(val) if as_array else val)
def _SerializeArray(f, data, field, write, func):
if write:
val = getattr(data, field)
f.write(struct.pack('I', len(val)))
for x in val:
func(f, x, write)
else:
size = struct.unpack('I', f.read(4))[0]
val = []
for _ in range(size):
x = {}
func(f, x, write)
val.append(x)
setattr(data, field, val)
def _SerializeFixedByteArray(size, f, data, field, write):
if write:
val = getattr(data, field)
assert size == len(val)
f.write(bytes(val))
else:
setattr(data, field, list(f.read(size)))
@dataclass
class Level():
fg: bytes = b''
bg: bytes = b''
doorlinks1: bytes = b''
doorlinks2: bytes = b''
roomlinks: bytes = b''
used_rooms: int = 0
roomxs: bytes = b''
roomys: bytes = b''
fill_1: bytes = b''
start_room: int = 0
start_pos: int = 0
start_dir: int = 0
fill_2: bytes = b''
guards_tile: bytes = b''
guards_dir: bytes = b''
guards_x: bytes = b''
guards_seq_lo: bytes = b''
guards_skill: bytes = b''
guards_seq_hi: bytes = b''
guards_color: bytes = b''
fill_3: bytes = b''
def _Serialize(self, f, write):
_SerializeFixedByteArray(720, f, self, 'fg', write)
_SerializeFixedByteArray(720, f, self, 'bg', write)
_SerializeFixedByteArray(256, f, self, 'doorlinks1', write)
_SerializeFixedByteArray(256, f, self, 'doorlinks2', write)
_SerializeFixedByteArray(96, f, self, 'roomlinks', write)
_SerializeU8(f, self, 'used_rooms', write)
_SerializeFixedByteArray(24, f, self, 'roomxs', write)
_SerializeFixedByteArray(24, f, self, 'roomys', write)
_SerializeFixedByteArray(15, f, self, 'fill_1', write)
_SerializeU8(f, self, 'start_room', write)
_SerializeU8(f, self, 'start_pos', write)
_SerializeI8(f, self, 'start_dir', write)
_SerializeFixedByteArray(4, f, self, 'fill_2', write)
_SerializeFixedByteArray(24, f, self, 'guards_tile', write)
_SerializeFixedByteArray(24, f, self, 'guards_dir', write)
_SerializeFixedByteArray(24, f, self, 'guards_x', write)
_SerializeFixedByteArray(24, f, self, 'guards_seq_lo', write)
_SerializeFixedByteArray(24, f, self, 'guards_skill', write)
_SerializeFixedByteArray(24, f, self, 'guards_seq_hi', write)
_SerializeFixedByteArray(24, f, self, 'guards_color', write)
_SerializeFixedByteArray(18, f, self, 'fill_3', write)
@dataclass
class Char():
frame: int = 0
x: int = 0
y: int = 0
direction: int = 0
curr_col: int = 0
curr_row: int = 0
action: int = 0
fall_x: int = 0
fall_y: int = 0
room: int = 0
repeat: int = 0
charid: int = 0
sword: int = 0
alive: int = 0
curr_seq: int = 0
def _Serialize(self, f, write):
_SerializeU8(f, self, 'frame', write)
_SerializeU8(f, self, 'x', write)
_SerializeU8(f, self, 'y', write)
_SerializeI8(f, self, 'direction', write)
_SerializeU8(f, self, 'curr_col', write)
_SerializeU8(f, self, 'curr_row', write)
_SerializeU8(f, self, 'action', write)
_SerializeI8(f, self, 'fall_x', write)
_SerializeI8(f, self, 'fall_y', write)
_SerializeU8(f, self, 'room', write)
_SerializeU8(f, self, 'repeat', write)
_SerializeU8(f, self, 'charid', write)
_SerializeU8(f, self, 'sword', write)
_SerializeI8(f, self, 'alive', write)
_SerializeU16(f, self, 'curr_seq', write)
@dataclass
class Savegame():
level: Level = field(default_factory=Level)
checkpoint: int = 0
upside_down: int = 0
drawn_room: int = 0
current_level: int = 0
next_level: int = 0
mobs_count: int = 0
mobs: bytes = b''
trobs_count: int = 0
trobs: bytes = b''
leveldoor_open: int = 0
kid: Char = field(default_factory=Char)
hitp_curr: int = 0
hitp_max: int = 0
hitp_beg_lev: int = 0
grab_timer: int = 0
holding_sword: int = 0
united_with_shadow: int = 0
have_sword: int = 0
kid_sword_strike: int = 0
pickup_obj_type: int = 0
offguard: int = 0
guard: Char = field(default_factory=Char)
char: Char = field(default_factory=Char)
opp: Char = field(default_factory=Char)
guardhp_curr: int = 0
guardhp_max: int = 0
demo_index: int = 0
demo_time: int = 0
curr_guard_color: int = 0
guard_notice_timer: int = 0
guard_skill: int = 0
shadow_initialized: int = 0
guard_refrac: int = 0
justblocked: int = 0
droppedout: int = 0
curr_row_coll_room: bytes = b''
curr_row_coll_flags: bytes = b''
below_row_coll_room: bytes = b''
below_row_coll_flags: bytes = b''
above_row_coll_room: bytes = b''
above_row_coll_flags: bytes = b''
prev_collision_row: int = 0
flash_color: int = 0
flash_time: int = 0
need_level1_music: int = 0
is_screaming: int = 0
is_feather_fall: int = 0
last_loose_sound: int = 0
random_seed: int = 0
rem_min: int = 0
rem_tick: int = 0
control_x: int = 0
control_y: int = 0
control_shift: int = 0
control_forward: int = 0
control_backward: int = 0
control_up: int = 0
control_down: int = 0
control_shift2: int = 0
ctrl1_forward: int = 0
ctrl1_backward: int = 0
ctrl1_up: int = 0
ctrl1_down: int = 0
ctrl1_shift2: int = 0
curr_tick: int = 0
def _Serialize(self, f, write):
self.level._Serialize(f, write)
_SerializeU16(f, self, 'checkpoint', write)
_SerializeU16(f, self, 'upside_down', write)
_SerializeU16(f, self, 'drawn_room', write)
_SerializeU16(f, self, 'current_level', write)
_SerializeU16(f, self, 'next_level', write)
_SerializeU16(f, self, 'mobs_count', write)
_SerializeFixedByteArray(14 * 6, f, self, 'mobs', write)
_SerializeU16(f, self, 'trobs_count', write)
_SerializeFixedByteArray(30 * 3, f, self, 'trobs', write)
_SerializeU16(f, self, 'leveldoor_open', write)
self.kid._Serialize(f, write)
_SerializeU16(f, self, 'hitp_curr', write)
_SerializeU16(f, self, 'hitp_max', write)
_SerializeU16(f, self, 'hitp_beg_lev', write)
_SerializeU16(f, self, 'grab_timer', write)
_SerializeU16(f, self, 'holding_sword', write)
_SerializeI16(f, self, 'united_with_shadow', write)
_SerializeU16(f, self, 'have_sword', write)
_SerializeU16(f, self, 'kid_sword_strike', write)
_SerializeI16(f, self, 'pickup_obj_type', write)
_SerializeU16(f, self, 'offguard', write)
self.guard._Serialize(f, write)
self.char._Serialize(f, write)
self.opp._Serialize(f, write)
_SerializeU16(f, self, 'guardhp_curr', write)
_SerializeU16(f, self, 'guardhp_max', write)
_SerializeU16(f, self, 'demo_index', write)
_SerializeI16(f, self, 'demo_time', write)
_SerializeU16(f, self, 'curr_guard_color', write)
_SerializeI16(f, self, 'guard_notice_timer', write)
_SerializeU16(f, self, 'guard_skill', write)
_SerializeU16(f, self, 'shadow_initialized', write)
_SerializeU16(f, self, 'guard_refrac', write)
_SerializeU16(f, self, 'justblocked', write)
_SerializeU16(f, self, 'droppedout', write)
_SerializeFixedByteArray(10, f, self, 'curr_row_coll_room', write)
_SerializeFixedByteArray(10, f, self, 'curr_row_coll_flags', write)
_SerializeFixedByteArray(10, f, self, 'below_row_coll_room', write)
_SerializeFixedByteArray(10, f, self, 'below_row_coll_flags', write)
_SerializeFixedByteArray(10, f, self, 'above_row_coll_room', write)
_SerializeFixedByteArray(10, f, self, 'above_row_coll_flags', write)
_SerializeI8(f, self, 'prev_collision_row', write)
_SerializeU16(f, self, 'flash_color', write)
_SerializeU16(f, self, 'flash_time', write)
_SerializeU16(f, self, 'need_level1_music', write)
_SerializeU16(f, self, 'is_screaming', write)
_SerializeU16(f, self, 'is_feather_fall', write)
_SerializeU16(f, self, 'last_loose_sound', write)
_SerializeU32(f, self, 'random_seed', write)
_SerializeI16(f, self, 'rem_min', write)
_SerializeU16(f, self, 'rem_tick', write)
_SerializeI8(f, self, 'control_x', write)
_SerializeI8(f, self, 'control_y', write)
_SerializeI8(f, self, 'control_shift', write)
_SerializeI8(f, self, 'control_forward', write)
_SerializeI8(f, self, 'control_backward', write)
_SerializeI8(f, self, 'control_up', write)
_SerializeI8(f, self, 'control_down', write)
_SerializeI8(f, self, 'control_shift2', write)
_SerializeI8(f, self, 'ctrl1_forward', write)
_SerializeI8(f, self, 'ctrl1_backward', write)
_SerializeI8(f, self, 'ctrl1_up', write)
_SerializeI8(f, self, 'ctrl1_down', write)
_SerializeI8(f, self, 'ctrl1_shift2', write)
_SerializeU32(f, self, 'curr_tick', write)
def Load(self, filename):
with open(filename, "rb") as f:
assert b'V1.16b4 \0' == f.read(9)
self._Serialize(f, False)
def Save(self, filename):
with open(filename, "wb") as f:
f.write(b'V1.16b4 \0')
self._Serialize(f, True)
class FrameKey:
def __init__(self, val=0):
def CheckBit(x):
nonlocal val
if val & x == x:
val -= x
return True
return False
self.sound_off = CheckBit(0x40)
self.restart = CheckBit(0x20)
self.up = CheckBit(0x0c)
self.down = CheckBit(0x04)
self.left = CheckBit(0x3)
self.right = CheckBit(0x1)
def AsByte(self):
res = 0
if self.sound_off:
res |= 0x40
if self.restart:
res |= 0x20
if self.up:
res |= 0x0c
if self.down:
res |= 0x04
if self.left:
res |= 0x3
if self.right:
res |= 0x1
return bytes([res])
def __str__(self):
res = ''
for val, ch in [
(self.sound_off, 'e'),
(self.restart, 'a'),
(self.up, 'U'),
(self.down, 'D'),
(self.left, 'L'),
(self.right, 'R'),
]:
if val:
res = res + ch
return res or '-'
def __eq__(self, other):
if not isinstance(other, FrameKey):
return False
return (self.sound_off == other.sound_off
and self.restart == other.restart and self.up == other.up
and self.down == other.down and self.left == other.left
and self.right == other.right)
class KeyPresses:
def __init__(self):
self.moves = []
def __str__(self):
return self.AsShortString()
def __repr__(self):
return '[%s]' % self.AsShortString()
def AsShortString(self):
prev = None
count = 0
res = []
def Out():
if count == 0:
return
elif count == 1:
res.append(prev)
else:
res.append('%d×%s' % (count, prev))
for move in self.moves:
if move == prev:
count += 1
else:
Out()
count = 1
prev = move
Out()
return ' '.join([str(x) for x in res])
def _Serialize(self, f, write):
if write:
f.write(struct.pack('I', len(self.moves)))
for x in self.moves:
f.write(x.AsByte())
else:
size = struct.unpack('I', f.read(4))[0]
self.moves = []
for z in range(size):
self.moves.append(FrameKey(f.read(1)[0]))
@dataclass
class Replay():
file_class: int = 0
version_number: int = 0
deprecation_number: int = 0
creation_time: int = 0
custom_levelset: bytes = b''
implementation_name: bytes = b''
savestate_datasize: int = 0
savegame: Savegame = field(default_factory=Savegame)
options_process_features: bytes = b''
options_process_enhancements: bytes = b''
options_process_fixes: bytes = b''
options_process_custom_general: bytes = b''
options_process_custom_per_level: bytes = b''
start_level: int = 0
saved_random_seed: int = 0
keypresses: KeyPresses = field(default_factory=KeyPresses)
def _Serialize(self, f, write):
_SerializeU16(f, self, 'file_class', write)
_SerializeU8(f, self, 'version_number', write)
_SerializeU8(f, self, 'deprecation_number', write)
_SerializeU64(f, self, 'creation_time', write)
_SerializeString(f, self, 'custom_levelset', write)
_SerializeString(f, self, 'implementation_name', write)
_SerializeU32(f, self, 'savestate_selfsize', write)
self.savegame._Serialize(f, write)
_SerializeString(f, self, 'options_process_features', write, 'I')
_SerializeString(f, self, 'options_process_enhancements', write, 'I')
_SerializeString(f, self, 'options_process_fixes', write, 'I')
_SerializeString(f, self, 'options_process_custom_general', write, 'I')
_SerializeString(f, self, 'options_process_custom_per_level', write,
'I')
_SerializeU16(f, self, 'start_level', write)
_SerializeU32(f, self, 'saved_random_seed', write)
self.keypresses._Serialize(f, write)
def Load(self, filename):
with open(filename, "rb") as f:
assert b'P1R' == f.read(3)
self._Serialize(f, False)
def Save(self, filename):
with open(filename, "wb") as f:
f.write(b'P1R')
self._Serialize(f, True)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment