378 lines
9.5 KiB
Python
378 lines
9.5 KiB
Python
from typing import Optional, Dict, NamedTuple
|
|
import struct
|
|
from logging import Logger
|
|
from enum import IntEnum
|
|
|
|
from .pcsx2_interface.pine import Pine
|
|
from .data.Constants import ADDRESSES, MENU_RETURN_DATA
|
|
|
|
class Sly3Episode(IntEnum):
|
|
Title_Screen = 0
|
|
An_Opera_of_Fear = 1
|
|
Rumble_Down_Under = 2
|
|
Flight_of_Fancy = 3
|
|
A_Cold_Alliance = 4
|
|
Dead_Men_Tell_No_Tales = 5
|
|
Honor_Among_Thieves = 6
|
|
|
|
class PowerUps(NamedTuple):
|
|
attack = False
|
|
binocucom = False
|
|
bombs = False
|
|
unknown = False
|
|
trigger_Bomb = False
|
|
fishing_pole = False
|
|
|
|
alarm_clock = False
|
|
adrenaline_burst = False
|
|
health_extractor = False
|
|
hover_pack = False
|
|
insanity_strike = False
|
|
grapple_cam = False
|
|
size_destabilizer = False
|
|
rage_bomb = False
|
|
|
|
reduction_bomb = False
|
|
ball_form = False
|
|
berserker_charge = False
|
|
juggernaut_throw = False
|
|
guttural_roar = False
|
|
fists_of_flame = False
|
|
temporal_lock = False
|
|
raging_inferno_flop = False
|
|
|
|
diablo_fire_slam = False
|
|
smoke_bomb = False
|
|
combat_dodge = False
|
|
paraglider = False
|
|
silent_obliteration = False
|
|
feral_pounce = False
|
|
mega_jump = False
|
|
knockout_dive = False
|
|
|
|
shadow_power_1 = False
|
|
thief_reflexes = False
|
|
shadow_power_2 = False
|
|
rocket_boots = False
|
|
treasure_map = False
|
|
shield = False
|
|
venice_disguise = False
|
|
photographer_disguise = False
|
|
|
|
pirate_disguise = False
|
|
spin_1 = False
|
|
spin_2 = False
|
|
spin_3 = False
|
|
jump_1 = False
|
|
jump_2 = False
|
|
jump_3 = False
|
|
push_1 = False
|
|
|
|
push_2 = False
|
|
push_3 = False
|
|
|
|
class GameInterface():
|
|
"""
|
|
Base class for connecting with a pcsx2 game
|
|
"""
|
|
|
|
pcsx2_interface: Pine = Pine()
|
|
logger: Logger
|
|
game_id_error: Optional[str] = None
|
|
current_game: Optional[str] = None
|
|
addresses: Dict = {}
|
|
|
|
def __init__(self, logger) -> None:
|
|
self.logger = logger
|
|
|
|
def _read8(self, address: int):
|
|
return self.pcsx2_interface.read_int8(address)
|
|
|
|
def _read16(self, address: int):
|
|
return self.pcsx2_interface.read_int16(address)
|
|
|
|
def _read32(self, address: int):
|
|
return self.pcsx2_interface.read_int32(address)
|
|
|
|
def _read_bytes(self, address: int, n: int):
|
|
return self.pcsx2_interface.read_bytes(address, n)
|
|
|
|
def _read_float(self, address: int):
|
|
return struct.unpack("f",self.pcsx2_interface.read_bytes(address, 4))[0]
|
|
|
|
def _write8(self, address: int, value: int):
|
|
self.pcsx2_interface.write_int8(address, value)
|
|
|
|
def _write16(self, address: int, value: int):
|
|
self.pcsx2_interface.write_int16(address, value)
|
|
|
|
def _write32(self, address: int, value: int):
|
|
self.pcsx2_interface.write_int32(address, value)
|
|
|
|
def _write_bytes(self, address: int, value: bytes):
|
|
self.pcsx2_interface.write_bytes(address, value)
|
|
|
|
def _write_float(self, address: int, value: float):
|
|
self.pcsx2_interface.write_bytes(address, struct.pack("f", value))
|
|
|
|
def connect_to_game(self):
|
|
"""
|
|
Initializes the connection to PCSX2 and verifies it is connected to the
|
|
right game
|
|
"""
|
|
if not self.pcsx2_interface.is_connected():
|
|
self.pcsx2_interface.connect()
|
|
if not self.pcsx2_interface.is_connected():
|
|
return
|
|
self.logger.info("Connected to PCSX2 Emulator")
|
|
try:
|
|
game_id = self.pcsx2_interface.get_game_id()
|
|
# The first read of the address will be null if the client is faster than the emulator
|
|
self.current_game = None
|
|
if game_id in ADDRESSES.keys():
|
|
self.current_game = game_id
|
|
self.addresses = ADDRESSES[game_id]
|
|
if self.current_game is None and self.game_id_error != game_id and game_id != b'\x00\x00\x00\x00\x00\x00':
|
|
self.logger.warning(
|
|
f"Connected to the wrong game ({game_id})")
|
|
self.game_id_error = game_id
|
|
except RuntimeError:
|
|
pass
|
|
except ConnectionError:
|
|
pass
|
|
|
|
def disconnect_from_game(self):
|
|
self.pcsx2_interface.disconnect()
|
|
self.current_game = None
|
|
self.logger.info("Disconnected from PCSX2 Emulator")
|
|
|
|
def get_connection_state(self) -> bool:
|
|
try:
|
|
connected = self.pcsx2_interface.is_connected()
|
|
return connected and self.current_game is not None
|
|
except RuntimeError:
|
|
return False
|
|
|
|
class Sly3Interface(GameInterface):
|
|
def _reload(self, reload_data: bytes):
|
|
self._write_bytes(
|
|
self.addresses["reload values"],
|
|
reload_data
|
|
)
|
|
self._write32(self.addresses["reload"], 1)
|
|
|
|
def _get_job_address(self, task: int) -> int:
|
|
pointer = self._read32(self.addresses["DAG root"])
|
|
for _ in range(task):
|
|
pointer = self._read32(pointer+0x20)
|
|
|
|
return pointer
|
|
|
|
def get_current_map(self) -> int:
|
|
return self._read32(self.addresses["map id"])
|
|
|
|
def get_current_job(self) -> int:
|
|
return self._read32(self.addresses["job id"])
|
|
|
|
def set_current_job(self, job: int) -> None:
|
|
self._write32(self.addresses["job id"], job)
|
|
|
|
def set_items_received(self, n:int) -> None:
|
|
self._write32(self.addresses["items received"], n)
|
|
|
|
def to_episode_menu(self) -> None:
|
|
self.logger.info("Skipping to episode menu")
|
|
if (
|
|
self.get_current_map() == 35 and
|
|
self.get_current_job() == 1797
|
|
):
|
|
self.set_current_job(0xffffffff)
|
|
# self.set_items_received(0)
|
|
|
|
self._reload(bytes.fromhex(MENU_RETURN_DATA))
|
|
|
|
def unlock_episodes(self) -> None:
|
|
self._write8(self.addresses["episode unlocks"], 8)
|
|
|
|
def in_cutscene(self) -> bool:
|
|
frame_counter = self._read16(self.addresses["frame counter"])
|
|
return frame_counter > 10
|
|
|
|
def skip_cutscene(self) -> None:
|
|
pressing_x = self._read8(self.addresses["x pressed"]) == 255
|
|
|
|
if self.in_cutscene() and pressing_x:
|
|
self._write32(self.addresses["skip cutscene"],0)
|
|
|
|
if __name__ == "__main__":
|
|
interf = Sly3Interface(Logger("test"))
|
|
interf.connect_to_game()
|
|
|
|
byte_list = [
|
|
[
|
|
False, # ???
|
|
False, # ???
|
|
True, # Sly/Bentley square attack
|
|
True, # Binocucom
|
|
True, # Bentley's bombs
|
|
False, # ???
|
|
True, # Trigger Bomb
|
|
True # Fishing Pole
|
|
],
|
|
[
|
|
True, # Alarm Clock
|
|
True, # Adrenaline Burst
|
|
True, # Health Extractor
|
|
True, # Hover Pack (Doesn't activate until you reload)
|
|
True, # Insanity Strike
|
|
True, # Grapple Came
|
|
True, # Size Destabilizer
|
|
True # Rage Bomb
|
|
],
|
|
[
|
|
True, # Reduction Bomb
|
|
True, # Ball Form
|
|
True, # Berskerker Charge
|
|
True, # Juggernaut Throw
|
|
True, # Gutteral Roar
|
|
True, # Fists of Flame
|
|
True, # Temporal Lock
|
|
True # Raging Inferno Flop
|
|
],
|
|
[
|
|
True, # Diablo Fire Slam
|
|
True, # Smoke Bomb
|
|
True, # Combat Dodge
|
|
True, # Paraglider
|
|
True, # Silent Obliteration
|
|
True, # Feral Pounce
|
|
True, # Mega Jump
|
|
True # Knockout Dive
|
|
],
|
|
[
|
|
True, # Shadow Power Lvl 1
|
|
True, # Thief Reflexes
|
|
True, # Shadow Power Lvl 2
|
|
True, # Rocket Boots
|
|
True, # Treasure Map
|
|
False, # ???
|
|
True, # Venice Disguise
|
|
True # Photographer Disguise
|
|
],
|
|
[
|
|
True, # Pirate Disguise
|
|
True, # Spin Attack lvl 1
|
|
True, # Spin Attack lvl 2
|
|
True, # Spin Attack lvl 3
|
|
True, # Jump Attack lvl 1
|
|
True, # Jump Attack lvl 2
|
|
True, # Jump Attack lvl 3
|
|
True # Push Attack lvl 1
|
|
],
|
|
[
|
|
True, # Push Attack lvl 1
|
|
True, # Push Attack lvl 1
|
|
False,
|
|
False,
|
|
False,
|
|
False,
|
|
False,
|
|
False
|
|
],
|
|
[
|
|
False,
|
|
False,
|
|
False,
|
|
False,
|
|
False,
|
|
False,
|
|
False,
|
|
False
|
|
],
|
|
]
|
|
|
|
# byte_list = [[False for _ in range(8)] for _ in range(8)]
|
|
|
|
data = b''.join(
|
|
int(''.join(str(int(i)) for i in byte[::-1]),2).to_bytes(1,"big")
|
|
for byte in byte_list
|
|
)
|
|
|
|
interf._write_bytes(0x468DCC,data)
|
|
|
|
# data = interf._read_bytes(0x468DCC, 8)
|
|
# bits = [
|
|
# bool(int(b))
|
|
# for byte in data
|
|
# for b in f"{byte:08b}"[::-1]
|
|
# ]
|
|
|
|
# print(bits)
|
|
|
|
interf._write32(0x468DDC, 10000)
|
|
|
|
# thiefnet_values = list(range(9)) + list(range(10,))
|
|
|
|
|
|
# def read_text(address: int):
|
|
# text = ""
|
|
|
|
# while True:
|
|
# character = interf._read_bytes(address,2)
|
|
|
|
# if character == b"\x00\x00":
|
|
# break
|
|
|
|
# text += character.decode("utf-16-le")
|
|
|
|
# address += 2
|
|
|
|
# return text
|
|
|
|
# def find_string_id(_id: int):
|
|
# string_table_address = interf._read32(0x47A2D8)
|
|
# i = 0
|
|
# while True:
|
|
# string_id = interf._read32(string_table_address+i*8)
|
|
# if string_id == _id:
|
|
# return interf._read32(string_table_address+i*8+4)
|
|
# i += 1
|
|
|
|
|
|
# print(" {")
|
|
# for i in range(44):
|
|
# address = 0x343208+i*0x3c
|
|
# interf._write32(address,i+1)
|
|
# interf._write32(address+0xC,0)
|
|
|
|
# name_id = interf._read32(address+0x14)
|
|
# name_address = find_string_id(name_id)
|
|
# name_text = read_text(name_address)
|
|
|
|
# description_id = interf._read32(address+0x18)
|
|
# description_address = find_string_id(description_id)
|
|
|
|
# print(
|
|
# " " +
|
|
# f"\"{name_text}\": "+
|
|
# f"({hex(name_address)},{hex(description_address)}),"
|
|
# )
|
|
|
|
# print(" }")
|
|
|
|
# string_table_address = interf._read32(0x47A2D8)
|
|
# for i in range(10):
|
|
# print("----")
|
|
# string_id = interf._read32(string_table_address+i*8)
|
|
# print(string_id)
|
|
# string_address = interf._read32(string_table_address+i*8+4)
|
|
# print(hex(string_address))
|
|
# print(read_text(string_address))
|
|
|
|
# print(interf._read32(0x6b4110+0x44))
|
|
print(interf._read32(0x1365be0+0x44))
|
|
print()
|
|
print(interf._read32(0x1357f80+0x44))
|
|
print(interf._read32(0x1350560+0x44))
|
|
print(interf._read32(0x135aba0+0x44))
|
|
print(interf._read32(0x36DB98)) |