Created
January 26, 2021 15:32
-
-
Save mooskagh/697d7f0a3f7af42cf3ba564752eb04b3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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