trainerbase 4.5.1__tar.gz → 4.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {trainerbase-4.5.1 → trainerbase-4.6.0}/PKG-INFO +1 -1
- {trainerbase-4.5.1 → trainerbase-4.6.0}/pyproject.toml +1 -1
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/codeinjection.py +59 -10
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/config.py +3 -3
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/memory.py +38 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/.gitignore +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/LICENSE +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/README.md +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/__init__.py +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/abc.py +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/common/__init__.py +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/common/abc.py +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/common/helpers.py +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/common/keyboard.py +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/common/rainbow.py +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/common/tapper.py +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/common/teleport.py +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/gameobject.py +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/gui/__init__.py +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/gui/helpers.py +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/gui/injections.py +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/gui/misc.py +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/gui/objects.py +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/gui/rainbow.py +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/gui/scripts.py +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/gui/speedhack.py +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/gui/tapper.py +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/gui/teleport.py +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/gui/types.py +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/logger.py +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/main.py +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/process.py +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/py.typed +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/scriptengine.py +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/speedhack.py +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase/tts.py +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase_vendor/FASM.EXE +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase_vendor/speedhack32.dll +0 -0
- {trainerbase-4.5.1 → trainerbase-4.6.0}/trainerbase_vendor/speedhack64.dll +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
from contextlib import suppress
|
|
3
|
+
from functools import lru_cache
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from subprocess import DEVNULL
|
|
5
6
|
from subprocess import run as run_executable
|
|
@@ -15,6 +16,9 @@ from trainerbase.process import pm
|
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
FASM_EXECUTABLE_PATH: Final[Path] = config.vendor.folder / "FASM.EXE"
|
|
19
|
+
ORIGINAL_CODE_PLACEHOLDER: Final[str] = "__original_code__"
|
|
20
|
+
BASE_ADDRESS_PLACEHOLDER: Final[str] = "__base_address__"
|
|
21
|
+
MOVE_X64_MACROS: Final[str] = "__move_x64__"
|
|
18
22
|
|
|
19
23
|
|
|
20
24
|
type Code = bytes | str
|
|
@@ -71,8 +75,8 @@ class CodeInjection(AbstractCodeInjection):
|
|
|
71
75
|
logger.debug("Nothing to eject")
|
|
72
76
|
|
|
73
77
|
|
|
74
|
-
class AllocatingCodeInjection(AbstractCodeInjection):
|
|
75
|
-
def __init__(
|
|
78
|
+
class AllocatingCodeInjection(AbstractCodeInjection):
|
|
79
|
+
def __init__(
|
|
76
80
|
self,
|
|
77
81
|
address: ConvertibleToAddress,
|
|
78
82
|
code_to_inject: Code,
|
|
@@ -86,10 +90,7 @@ class AllocatingCodeInjection(AbstractCodeInjection): # pylint: disable=too-man
|
|
|
86
90
|
|
|
87
91
|
self.is_long_x64_jump_needed = is_long_x64_jump_needed
|
|
88
92
|
self.address = ensure_address(address)
|
|
89
|
-
self.code_to_inject =
|
|
90
|
-
|
|
91
|
-
if self.is_long_x64_jump_needed:
|
|
92
|
-
self.code_to_inject = compile_asm("pop rax") + self.code_to_inject + compile_asm("push rax")
|
|
93
|
+
self.code_to_inject = code_to_inject
|
|
93
94
|
|
|
94
95
|
self.original_code: bytes = b""
|
|
95
96
|
|
|
@@ -101,12 +102,25 @@ class AllocatingCodeInjection(AbstractCodeInjection): # pylint: disable=too-man
|
|
|
101
102
|
@override
|
|
102
103
|
def inject(self):
|
|
103
104
|
self.injection_point = self.address.resolve()
|
|
104
|
-
|
|
105
105
|
self.new_memory_address: int = allocate(self.new_memory_size)
|
|
106
|
-
|
|
107
106
|
self.original_code = pm.read_bytes(self.injection_point, self.original_code_length)
|
|
108
107
|
|
|
108
|
+
code_to_inject = self.code_to_inject
|
|
109
|
+
|
|
110
|
+
if isinstance(code_to_inject, str):
|
|
111
|
+
code_to_inject = preprocess_asm(
|
|
112
|
+
code_to_inject,
|
|
113
|
+
original_code=self.original_code,
|
|
114
|
+
base_address=pm.base_address,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
if isinstance(code_to_inject, bytearray):
|
|
118
|
+
raise TypeError("Code to inject must not be bytearray")
|
|
119
|
+
|
|
120
|
+
code_to_inject = compile_asm(code_to_inject)
|
|
121
|
+
|
|
109
122
|
if self.is_long_x64_jump_needed:
|
|
123
|
+
code_to_inject = compile_asm("pop rax") + code_to_inject + compile_asm("push rax")
|
|
110
124
|
jump_to_new_mem = compile_asm(get_long_x64_jump_to_new_mem_code(self.new_memory_address))
|
|
111
125
|
jumper_length = len(jump_to_new_mem)
|
|
112
126
|
jump_back_address = self.injection_point + jumper_length - 1
|
|
@@ -116,7 +130,7 @@ class AllocatingCodeInjection(AbstractCodeInjection): # pylint: disable=too-man
|
|
|
116
130
|
jumper_length = len(jump_to_new_mem)
|
|
117
131
|
jump_back_address = self.injection_point + jumper_length
|
|
118
132
|
jump_back = compile_asm(
|
|
119
|
-
get_simple_jump_code(jump_back_address, self.new_memory_address + len(
|
|
133
|
+
get_simple_jump_code(jump_back_address, self.new_memory_address + len(code_to_inject)),
|
|
120
134
|
)
|
|
121
135
|
|
|
122
136
|
if jumper_length < self.original_code_length:
|
|
@@ -124,7 +138,7 @@ class AllocatingCodeInjection(AbstractCodeInjection): # pylint: disable=too-man
|
|
|
124
138
|
elif jumper_length > self.original_code_length:
|
|
125
139
|
raise ValueError(f"Jumper length > original code length: {jumper_length} > {self.original_code_length}")
|
|
126
140
|
|
|
127
|
-
code_to_inject =
|
|
141
|
+
code_to_inject = code_to_inject + jump_back
|
|
128
142
|
|
|
129
143
|
logger.debug(f"Injecting {len(code_to_inject)} byte(s) (INJECTION) at 0x{self.new_memory_address:X}")
|
|
130
144
|
pm.write_bytes(self.new_memory_address, code_to_inject, len(code_to_inject))
|
|
@@ -161,6 +175,29 @@ class MultipleCodeInjection(AbstractCodeInjection):
|
|
|
161
175
|
injection.eject()
|
|
162
176
|
|
|
163
177
|
|
|
178
|
+
def preprocess_asm(code: str, *, original_code: bytes | None, base_address: int | None) -> str:
|
|
179
|
+
new_lines = []
|
|
180
|
+
|
|
181
|
+
for line in code.splitlines():
|
|
182
|
+
if original_code is not None:
|
|
183
|
+
formatted_bytes = ", ".join(map(hex, original_code))
|
|
184
|
+
formatted_original_code = f"db {formatted_bytes}"
|
|
185
|
+
|
|
186
|
+
line = line.replace(ORIGINAL_CODE_PLACEHOLDER, formatted_original_code)
|
|
187
|
+
|
|
188
|
+
if base_address is not None:
|
|
189
|
+
line = line.replace(BASE_ADDRESS_PLACEHOLDER, hex(base_address))
|
|
190
|
+
|
|
191
|
+
if MOVE_X64_MACROS in line:
|
|
192
|
+
_, address, register = line.strip().split()
|
|
193
|
+
line = get_move_x64_register_to_memory_code(address, register)
|
|
194
|
+
|
|
195
|
+
new_lines.append(line)
|
|
196
|
+
|
|
197
|
+
return "\n".join(new_lines)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@lru_cache
|
|
164
201
|
def compile_asm(code: Code) -> bytes:
|
|
165
202
|
if isinstance(code, str):
|
|
166
203
|
logger.debug(f"Assembling for x{ARCH}:\n{code}")
|
|
@@ -204,6 +241,18 @@ def get_long_x64_jump_back_code(address: int) -> str:
|
|
|
204
241
|
"""
|
|
205
242
|
|
|
206
243
|
|
|
244
|
+
def get_move_x64_register_to_memory_code(address: int | str, register: str) -> str:
|
|
245
|
+
temporary_register = "rbx" if register == "rax" else "rax"
|
|
246
|
+
code = f"""
|
|
247
|
+
push {temporary_register}
|
|
248
|
+
mov {temporary_register}, {address}
|
|
249
|
+
mov [{temporary_register}], {register}
|
|
250
|
+
pop {temporary_register}
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
return code
|
|
254
|
+
|
|
255
|
+
|
|
207
256
|
def safely_eject_all_code_injections() -> None:
|
|
208
257
|
for injection in AbstractCodeInjection.created_code_injections:
|
|
209
258
|
with suppress(MemoryWriteError):
|
|
@@ -14,7 +14,7 @@ DEFAULT_VENDOR_FOLDER_PATHS: Final[list[Path]] = [
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class Process(BaseModel):
|
|
17
|
-
names: list[str] = Field(
|
|
17
|
+
names: list[str] = Field(validation_alias=AliasChoices("names", "process_names"))
|
|
18
18
|
exact_match: bool = True
|
|
19
19
|
ignore_case: bool = False
|
|
20
20
|
|
|
@@ -44,8 +44,8 @@ class Vendor(BaseModel):
|
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
class Config(BaseModel):
|
|
47
|
-
process: Process = Field(
|
|
48
|
-
vendor: Vendor
|
|
47
|
+
process: Process = Field(validation_alias=AliasChoices("process", "pymem"))
|
|
48
|
+
vendor: Vendor = Vendor()
|
|
49
49
|
logging: dict[str, Any] | None = None # Must be managed by logging.dictConfig
|
|
50
50
|
|
|
51
51
|
|
|
@@ -207,3 +207,41 @@ def get_module_address(module_name: str, *, delay: float = 2, retry_limit: int |
|
|
|
207
207
|
sleep(delay)
|
|
208
208
|
|
|
209
209
|
raise RuntimeError("This code must be unreachable!")
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def ensure_resolved(
|
|
213
|
+
address: AbstractAddress,
|
|
214
|
+
verbose_name: str,
|
|
215
|
+
*,
|
|
216
|
+
delay: float = 2,
|
|
217
|
+
retry_limit: int | None = 4,
|
|
218
|
+
) -> int:
|
|
219
|
+
"""
|
|
220
|
+
Returns resolved address.
|
|
221
|
+
Raises RuntimeError if the address was not found and retry_limit is reached.
|
|
222
|
+
|
|
223
|
+
Set retry_limit to None or negative value if you need infinite retry cycle.
|
|
224
|
+
Set retry_limit to 0 if you want to disable retries.
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
logger.info(f"Looking for {verbose_name}...")
|
|
228
|
+
|
|
229
|
+
for try_counter in count():
|
|
230
|
+
try:
|
|
231
|
+
resolved = address.resolve()
|
|
232
|
+
except ValueError as e:
|
|
233
|
+
if try_counter == retry_limit:
|
|
234
|
+
raise RuntimeError(f"Failed to resolve {verbose_name}, tried {try_counter + 1} time(s).") from e
|
|
235
|
+
|
|
236
|
+
logger.info(f"Failed to resolve {verbose_name}. I will retry in {delay} s. Retry limit is {retry_limit}")
|
|
237
|
+
|
|
238
|
+
sleep(delay)
|
|
239
|
+
continue
|
|
240
|
+
|
|
241
|
+
logger.info(f"Successfully found {verbose_name} at 0x{resolved:X}")
|
|
242
|
+
return resolved
|
|
243
|
+
|
|
244
|
+
raise RuntimeError("This code must be unreachable!")
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
mainaob = partial(AOBScan, module_name=pm.process_base.name)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|