-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathDolphinClient.py
117 lines (93 loc) · 3.95 KB
/
DolphinClient.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
from logging import Logger
from typing import Any
import dolphin_memory_engine # type: ignore
import subprocess
import Utils
GC_GAME_ID_ADDRESS = 0x80000000
class DolphinException(Exception):
pass
class DolphinClient:
dolphin: dolphin_memory_engine # type: ignore
logger: Logger
def __init__(self, logger: Logger):
self.dolphin = dolphin_memory_engine
self.logger = logger
def is_connected(self):
try:
self.__assert_connected()
return True
except Exception:
return False
def connect(self):
if not self.dolphin.is_hooked():
self.dolphin.hook()
if not self.dolphin.is_hooked():
raise DolphinException(
"Could not connect to Dolphin, verify that you have a game running in the emulator"
)
def disconnect(self):
if self.dolphin.is_hooked():
self.dolphin.un_hook()
def __assert_connected(self):
"""Custom assert function that returns a DolphinException instead of a generic RuntimeError if the connection is lost"""
try:
self.dolphin.assert_hooked()
# For some reason the dolphin_memory_engine.is_hooked() function doesn't recognize when the game is closed, checking if memory is available will assert the connection is alive
self.dolphin.read_bytes(GC_GAME_ID_ADDRESS, 1)
except RuntimeError as e:
self.disconnect()
raise DolphinException(e)
def verify_target_address(self, target_address: int, read_size: int):
"""Ensures that the target address is within the valid range for GC memory"""
if target_address < 0x80000000 or target_address + read_size > 0x81800000:
raise DolphinException(
f"{target_address:x} -> {target_address + read_size:x} is not a valid for GC memory"
)
def read_pointer(self, pointer: int, offset: int, byte_count: int) -> Any:
self.__assert_connected()
address = None
try:
address = self.dolphin.follow_pointers(pointer, [0])
except RuntimeError:
return None
if not self.dolphin.is_hooked():
raise DolphinException("Dolphin no longer connected")
address += offset
return self.read_address(address, byte_count)
def read_address(self, address: int, bytes_to_read: int) -> Any:
self.__assert_connected()
self.verify_target_address(address, bytes_to_read)
result = self.dolphin.read_bytes(address, bytes_to_read)
return result
def write_pointer(self, pointer: int, offset: int, data: Any):
self.__assert_connected()
address = None
try:
address = self.dolphin.follow_pointers(pointer, [0])
except RuntimeError:
return None
if not self.dolphin.is_hooked():
raise DolphinException("Dolphin no longer connected")
address += offset
return self.write_address(address, data)
def write_address(self, address: int, data: Any):
self.__assert_connected()
result = self.dolphin.write_bytes(address, data)
return result
def assert_no_running_dolphin() -> bool:
"""Only checks on windows for now, verifies no existing instances of dolphin are running."""
if Utils.is_windows:
if get_num_dolphin_instances() > 0:
return False
return True
def get_num_dolphin_instances() -> int:
"""Only checks on windows for now, kind of brittle so if it causes problems then just ignore it"""
try:
if Utils.is_windows:
output = subprocess.check_output("tasklist", shell=True).decode()
lines = output.strip().split("\n")
count = sum("Dolphin.exe" in line for line in lines)
return count
return 0
except:
return 0