def test_compute_player_motion_mount_and_active_floor(): elevator = Movable( type=EntityDBEntry(id=EntityType.ACTIVEFLOOR_ELEVATOR), position_x=0.5, position_y=0.7, velocity_x=-0.1, velocity_y=-0.3, ) poly_elevator = PolyPointer(101, elevator, MemContext()) # Turkey on an elevator mount = Mount( type=EntityDBEntry(id=EntityType.MOUNT_TURKEY), position_x=18, position_y=13, velocity_x=-2, velocity_y=-7, overlay=poly_elevator, ) poly_mount = PolyPointer(102, mount, MemContext()) player = Player(position_x=5, position_y=7, velocity_x=-1, velocity_y=-3, overlay=poly_mount) expected_motion = PlayerMotion(position_x=18.5, position_y=13.7, velocity_x=-2.1, velocity_y=-7.3) run_state = RunState() assert run_state.compute_player_motion(player) == expected_motion
def test_could_tp_mount(mount_type, expected_could_tp): mount = Mount(type=EntityDBEntry(id=mount_type)) poly_mount = PolyPointer(101, mount, MemContext()) player = Player(overlay=poly_mount) run_state = RunState() assert run_state.could_tp(player, set(), set()) == expected_could_tp
def test_dataclass_struct_nested(): meta_0 = {} StructFieldMeta(0x0, deferred_uint8).put_into(meta_0) meta_2 = {} StructFieldMeta(0x2, deferred_uint8).put_into(meta_2) @dataclass class Inner: field_a: int = field(metadata=meta_0) field_b: int = field(metadata=meta_2) def deferred_inner(path, field_type): return DataclassStruct(path, field_type) meta_3_inner = {} StructFieldMeta(0x3, deferred_inner).put_into(meta_3_inner) @dataclass class Outer: field_0: int = field(metadata=meta_0) field_2: int = field(metadata=meta_2) inner: Inner = field(metadata=meta_3_inner) dc_outer = DataclassStruct(FieldPath(), Outer) assert dc_outer.from_bytes(b"\x00\x01\x02\x03\x04\x05", MemContext()) == Outer( 0, 2, Inner(3, 5) )
def test_vesctor_bad_buf(): vec_buf = b"" arr_buf = b"\xff\x03\x00\x09\x00" mem_type = Vector(FieldPath(), Optional[Tuple[int, ...]], sc_uint8) mem_ctx = MemContext(BytesReader(arr_buf)) with pytest.raises(ValueError): mem_type.from_bytes(vec_buf, mem_ctx)
def test_poly_pointer_castless(cls, expected_val): pp_type = PolyPointerType(FieldPath(), PolyPointer[cls], DataclassStruct) pp_supreme = pp_type.from_bytes( SUPREME_POINTER_BYTES, MemContext(LOWEST_BYTES_READER) ) assert pp_supreme.addr == SUPREME_POINTER_ADDR assert pp_supreme.value == expected_val
def test_has_mounted_tame(chain_status, theme, mount_type, mount_tamed, expected_low): mount = Mount(type=EntityDBEntry(id=mount_type), is_tamed=mount_tamed) poly_mount = PolyPointer(101, mount, MemContext()) run_state = RunState() run_state.sunken_chain_status = chain_status run_state.update_has_mounted_tame(theme, poly_mount) is_low = Label.LOW in run_state.run_label._set assert is_low == expected_low
def test_dataclass_struct_scalar_c_error_passthrough(): meta_0 = {} StructFieldMeta(0x0, deferred_uint8).put_into(meta_0) @dataclass class MyStruct: field_a: FourEnum = field(metadata=meta_0) dc_struct = DataclassStruct(FieldPath(), MyStruct) with pytest.raises(ScalarCValueConstructionError): dc_struct.from_bytes(b"\x00", MemContext())
def test_vector_dsl(): vec_wrap_buf = ( b"\xdc" * 9 # offset 1 and unused pointer + b"\x01\x00\x00\x00\x00\x00\x00\x00" + b"\xde" * 4 + b"\x02\x00\x00\x00" ) arr_buf = b"\xff\x03\x00\x09\x00" mem_type = DataclassStruct(FieldPath(), VecWrap) mem_ctx = MemContext(BytesReader(arr_buf)) assert mem_type.from_bytes(vec_wrap_buf, mem_ctx) == VecWrap((3, 9))
def test_dataclass_struct_general_error(): meta_0 = {} StructFieldMeta(0x0, deferred_uint8).put_into(meta_0) @dataclass class MyStruct: field_a: FourEnum = field(metadata=meta_0) dc_struct = DataclassStruct(FieldPath(), MyStruct) with pytest.raises(ValueError): # Too few bytes in buffer dc_struct.from_bytes(b"", MemContext())
def test_state(): state_mt = DataclassStruct(FieldPath(), State) state_bytes = b"\x05\xff\x00\x01\x01\x63\x2a\xff\x08\x01\xff\x02\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00" # pylint: disable=line-too-long mem_ctx = MemContext(BytesReader(b"\x00\x00\x03\x01\x0f\x08")) expected = State( 5, (False, True, True), frozenset([Player(99, 42), Player(8, 1)]), 259, PolyPointer(4, Player(15, 8), mem_ctx), ) assert state_mt.from_bytes(state_bytes, mem_ctx) == expected
def test_dataclass_struct_simple(): meta_0 = {} StructFieldMeta(0x0, deferred_uint8).put_into(meta_0) meta_3 = {} StructFieldMeta(0x3, deferred_uint8).put_into(meta_3) @dataclass class MyStruct: field_a: int = field(metadata=meta_0) field_b: int = field(metadata=meta_3) dc_struct = DataclassStruct(FieldPath(), MyStruct) assert dc_struct.from_bytes(b"\x00\x01\x02\x03", MemContext()) == MyStruct(0, 3)
def test_poly_pointer_cast_up(cls): pp_type = PolyPointerType(FieldPath(), PolyPointer[Lowest], DataclassStruct) pp_lowest = pp_type.from_bytes( SUPREME_POINTER_BYTES, MemContext(LOWEST_BYTES_READER) ) expected_val = Lowest(1, 2, 3) cast_val = pp_lowest.as_type(cls) assert cast_val == expected_val pp_poly = pp_lowest.as_poly_type(cls) assert pp_poly.addr == SUPREME_POINTER_ADDR assert pp_poly.value == expected_val
def test_compute_player_motion_mount(): mount = Mount( type=EntityDBEntry(id=EntityType.MOUNT_TURKEY), position_x=18, position_y=13, velocity_x=-2, velocity_y=-7, ) poly_mount = PolyPointer(101, mount, MemContext()) player = Player(position_x=5, position_y=7, velocity_x=-1, velocity_y=-3, overlay=poly_mount) expected_motion = PlayerMotion(position_x=18, position_y=13, velocity_x=-2, velocity_y=-7) run_state = RunState() assert run_state.compute_player_motion(player) == expected_motion
def test_compute_player_motion_active_floor(): elevator = Movable( type=EntityDBEntry(id=EntityType.ACTIVEFLOOR_ELEVATOR), position_x=0.5, position_y=0.7, velocity_x=-0.1, velocity_y=-0.3, ) poly_elevator = PolyPointer(101, elevator, MemContext()) player = Player(position_x=5, position_y=7, velocity_x=-1, velocity_y=-3, overlay=poly_elevator) expected_motion = PlayerMotion(position_x=5.5, position_y=7.7, velocity_x=-1.1, velocity_y=-3.3) run_state = RunState() assert run_state.compute_player_motion(player) == expected_motion
def test_unordered_map_type(): def uint32_deferred(field, py_type): assert field == FieldPath() assert py_type is int return ScalarCType(FieldPath(), int, ctypes.c_uint32) def uint16_deferred(field, py_type): assert field == FieldPath() assert py_type is int return ScalarCType(FieldPath(), int, ctypes.c_uint16) mem_type = UnorderedMapType( FieldPath(), UnorderedMap[int, int], uint32_deferred, uint16_deferred ) mem_ctx = MemContext(BytesReader(UNORDERED_MAP_MEM)) uo_map: UnorderedMap = mem_type.from_bytes(UNORDERED_MAP_BYTES, mem_ctx) # Sanity check key fields before we try to call get() assert uo_map.meta.buckets_ptr == 1 assert uo_map.meta.mask == 1 value = uo_map.get(2) assert value == 3
def read(): sc_type = ScalarCType(FieldPath(), FourEnum, ctypes.c_uint8) sc_type.from_bytes(b"\x00", MemContext())
def read(): sc_type = ScalarCType(FieldPath(), int, ctypes.c_uint8) sc_type.from_bytes(b"", MemContext())
def test_scalar_c_type_pointer(addr_bytes, expected): sc_type = ScalarCType(FieldPath(), int, ctypes.c_void_p) assert sc_type.from_bytes(addr_bytes, MemContext()) == expected
def test_scalar_c_type_byte(py_type, expected): sc_type = ScalarCType(FieldPath(), py_type, ctypes.c_uint8) assert sc_type.from_bytes(b"\x04", MemContext()) == expected
class Spel2Process: def __init__(self, proc_handle): self.proc_handle = proc_handle self._feedcode = None self.mem_ctx = MemContext(Spel2Reader(self)) @classmethod def from_pid(cls, pid): handle = win32api.OpenProcess( win32con.PROCESS_QUERY_INFORMATION | win32con.PROCESS_VM_READ, False, pid) if not handle: return None return cls(handle) def running(self): return win32con.STILL_ACTIVE == win32process.GetExitCodeProcess( self.proc_handle) def read_memory(self, offset, size): try: return win32process.ReadProcessMemory(self.proc_handle, offset, size) except pywintypes.error: return None def find(self, offset, needle, bsize=4096): if bsize < len(needle): raise ValueError( "The buffer size must be larger than the string being searched for." ) cursor = offset overlap = len(needle) - 1 while True: buffer = self.read_memory(cursor, bsize) if not buffer: return None cursor += len(buffer) pos = buffer.find(needle) if pos >= 0: return cursor - len(buffer) + pos if len(buffer) <= overlap: return None cursor -= overlap def find_one(self, start, needle, size): buffer = self.read_memory(start, size) if buffer is None: return None pos = buffer.find(needle) if pos >= 0: return start + pos return None def find_in_page(self, mbi: MemoryBasicInformation, needle): return self.find(mbi.base_address, needle, mbi.region_size) def memory_pages(self, min_addr=0x10000, max_addr=0x00007FFFFFFEFFFF): addr = min_addr while True: mbi = MemoryBasicInformation.from_virtual_query( self.proc_handle, addr) if not mbi: break addr += mbi.region_size if mbi.state != win32con.MEM_COMMIT: continue if mbi.type != win32con.MEM_PRIVATE: continue if mbi.protect & win32con.PAGE_NOACCESS: continue yield mbi if addr >= max_addr: break def get_spel2_module(self): module_handles = win32process.EnumProcessModules(self.proc_handle) for module_handle in module_handles: module_filename = Path( win32process.GetModuleFileNameEx(self.proc_handle, module_handle)) if module_filename.name == "Spel2.exe": return module_handle return None def get_offset_past_bundle(self): exe = self.get_spel2_module() offset = 0x1000 while True: header = win32process.ReadProcessMemory(self.proc_handle, exe + offset, 8) data_len, filepath_len = unpack(b"<II", header) if (data_len, filepath_len) == (0, 0): break offset += 8 + data_len + filepath_len return exe + offset def try_get_feedcode(self) -> Optional[int]: if self._feedcode is not None: return self._feedcode for page in self.memory_pages(min_addr=0x40000000000): result = self.find_in_page(page, b"\x00\xde\xc0\xed\xfe") if result: self._feedcode = result return result return None def get_feedcode(self) -> int: feedcode = self.try_get_feedcode() if feedcode is None: raise FeedcodeNotFound() return feedcode def get_state(self) -> Optional[State]: addr = self.get_feedcode() - 0x5F return self.mem_ctx.type_at_addr(State, addr)
def test_pointer_uint8(addr_bytes, expected): mem_ctx = MemContext(BytesReader(b"\x0c\x03\x10")) arr = Pointer(FieldPath, Optional[int], deferred_uint8) assert arr.from_bytes(addr_bytes, mem_ctx) == expected
def __init__(self, proc_handle): self.proc_handle = proc_handle self._feedcode = None self.mem_ctx = MemContext(Spel2Reader(self))
def poly_pointer_no_mem(value): return PolyPointer(addr=0xBAD, mem_ctx=MemContext(), value=value)
def test_unordered_map_node_deserialize(key_type, val_type, node_bytes, expected): node_type = _UnorderedMapNodeType(key_type, val_type) node = node_type.from_bytes(node_bytes, MemContext()) assert node == expected
def test_array_uint8(py_type, expected): arr = Array(FieldPath, py_type, deferred_uint8, count=2) assert arr.from_bytes(b"\x0a\x02", MemContext()) == expected
def test_vector_model(vec_buf, expected): mem_type = Vector(FieldPath(), Optional[Tuple[int, ...]], sc_uint8) arr_buf = b"\x0a\x0b\x0c\x0d" mem_ctx = MemContext(BytesReader(arr_buf)) assert mem_type.from_bytes(vec_buf, mem_ctx) == expected
def read(): sc_type = Array(FieldPath, Tuple[int, ...], deferred_uint8, count=2) sc_type.from_bytes(b"", MemContext())
def read(): sc_type = Array(FieldPath, Tuple[FourEnum, ...], deferred_uint8, count=2) sc_type.from_bytes(b"\x04\x00", MemContext())
def test_player(): player_mt = DataclassStruct(FieldPath(), Player) state_bytes = b"\x10\x20" assert player_mt.from_bytes(state_bytes, MemContext()) == Player(16, 32)