trainerbase 4.5.0__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.0 → trainerbase-4.6.0}/PKG-INFO +3 -2
- {trainerbase-4.5.0 → trainerbase-4.6.0}/pyproject.toml +4 -3
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/codeinjection.py +61 -11
- trainerbase-4.6.0/trainerbase/config.py +56 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/logger.py +3 -6
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/memory.py +38 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/process.py +7 -7
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/speedhack.py +4 -3
- trainerbase-4.5.0/trainerbase/config.py +0 -29
- {trainerbase-4.5.0 → trainerbase-4.6.0}/.gitignore +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/LICENSE +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/README.md +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/__init__.py +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/abc.py +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/common/__init__.py +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/common/abc.py +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/common/helpers.py +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/common/keyboard.py +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/common/rainbow.py +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/common/tapper.py +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/common/teleport.py +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/gameobject.py +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/gui/__init__.py +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/gui/helpers.py +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/gui/injections.py +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/gui/misc.py +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/gui/objects.py +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/gui/rainbow.py +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/gui/scripts.py +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/gui/speedhack.py +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/gui/tapper.py +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/gui/teleport.py +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/gui/types.py +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/main.py +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/py.typed +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/scriptengine.py +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase/tts.py +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase_vendor/FASM.EXE +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase_vendor/speedhack32.dll +0 -0
- {trainerbase-4.5.0 → trainerbase-4.6.0}/trainerbase_vendor/speedhack64.dll +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: trainerbase
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.6.0
|
|
4
4
|
Summary: Framework for creating game trainers/mods
|
|
5
5
|
Project-URL: Homepage, https://gitlab.com/python-trainers/
|
|
6
6
|
Project-URL: Repository, https://gitlab.com/python-trainers/trainer-base
|
|
@@ -13,7 +13,8 @@ Requires-Python: >=3.13
|
|
|
13
13
|
Requires-Dist: dearpygui>=2.0.0
|
|
14
14
|
Requires-Dist: keyboard>=0.13.5
|
|
15
15
|
Requires-Dist: mouse>=0.7.1
|
|
16
|
-
Requires-Dist: psutil>=
|
|
16
|
+
Requires-Dist: psutil>=7.0.0
|
|
17
|
+
Requires-Dist: pydantic>=2.10.6
|
|
17
18
|
Requires-Dist: pyinjector>=1.3.0
|
|
18
19
|
Requires-Dist: pymem>=1.14.0
|
|
19
20
|
Requires-Dist: pyttsx3>=2.98
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "trainerbase"
|
|
3
|
-
version = "4.
|
|
3
|
+
version = "4.6.0"
|
|
4
4
|
description = "Framework for creating game trainers/mods"
|
|
5
5
|
license = "MIT"
|
|
6
6
|
authors = [
|
|
@@ -13,7 +13,8 @@ dependencies = [
|
|
|
13
13
|
"dearpygui>=2.0.0",
|
|
14
14
|
"keyboard>=0.13.5",
|
|
15
15
|
"mouse>=0.7.1",
|
|
16
|
-
"psutil>=
|
|
16
|
+
"psutil>=7.0.0",
|
|
17
|
+
"pydantic>=2.10.6",
|
|
17
18
|
"pyinjector>=1.3.0",
|
|
18
19
|
"pymem>=1.14.0",
|
|
19
20
|
"pyttsx3>=2.98",
|
|
@@ -73,5 +74,5 @@ build-backend = "hatchling.build"
|
|
|
73
74
|
|
|
74
75
|
[dependency-groups]
|
|
75
76
|
dev = [
|
|
76
|
-
"ruff>=0.
|
|
77
|
+
"ruff>=0.9.7",
|
|
77
78
|
]
|
|
@@ -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
|
|
@@ -8,12 +9,16 @@ from typing import ClassVar, Final, Self, override
|
|
|
8
9
|
|
|
9
10
|
from pymem.exception import MemoryWriteError
|
|
10
11
|
|
|
12
|
+
from trainerbase.config import config
|
|
11
13
|
from trainerbase.logger import logger
|
|
12
14
|
from trainerbase.memory import ARCH, ConvertibleToAddress, allocate, ensure_address
|
|
13
15
|
from trainerbase.process import pm
|
|
14
16
|
|
|
15
17
|
|
|
16
|
-
FASM_EXECUTABLE_PATH: Final[Path] =
|
|
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__"
|
|
17
22
|
|
|
18
23
|
|
|
19
24
|
type Code = bytes | str
|
|
@@ -70,8 +75,8 @@ class CodeInjection(AbstractCodeInjection):
|
|
|
70
75
|
logger.debug("Nothing to eject")
|
|
71
76
|
|
|
72
77
|
|
|
73
|
-
class AllocatingCodeInjection(AbstractCodeInjection):
|
|
74
|
-
def __init__(
|
|
78
|
+
class AllocatingCodeInjection(AbstractCodeInjection):
|
|
79
|
+
def __init__(
|
|
75
80
|
self,
|
|
76
81
|
address: ConvertibleToAddress,
|
|
77
82
|
code_to_inject: Code,
|
|
@@ -85,10 +90,7 @@ class AllocatingCodeInjection(AbstractCodeInjection): # pylint: disable=too-man
|
|
|
85
90
|
|
|
86
91
|
self.is_long_x64_jump_needed = is_long_x64_jump_needed
|
|
87
92
|
self.address = ensure_address(address)
|
|
88
|
-
self.code_to_inject =
|
|
89
|
-
|
|
90
|
-
if self.is_long_x64_jump_needed:
|
|
91
|
-
self.code_to_inject = compile_asm("pop rax") + self.code_to_inject + compile_asm("push rax")
|
|
93
|
+
self.code_to_inject = code_to_inject
|
|
92
94
|
|
|
93
95
|
self.original_code: bytes = b""
|
|
94
96
|
|
|
@@ -100,12 +102,25 @@ class AllocatingCodeInjection(AbstractCodeInjection): # pylint: disable=too-man
|
|
|
100
102
|
@override
|
|
101
103
|
def inject(self):
|
|
102
104
|
self.injection_point = self.address.resolve()
|
|
103
|
-
|
|
104
105
|
self.new_memory_address: int = allocate(self.new_memory_size)
|
|
105
|
-
|
|
106
106
|
self.original_code = pm.read_bytes(self.injection_point, self.original_code_length)
|
|
107
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
|
+
|
|
108
122
|
if self.is_long_x64_jump_needed:
|
|
123
|
+
code_to_inject = compile_asm("pop rax") + code_to_inject + compile_asm("push rax")
|
|
109
124
|
jump_to_new_mem = compile_asm(get_long_x64_jump_to_new_mem_code(self.new_memory_address))
|
|
110
125
|
jumper_length = len(jump_to_new_mem)
|
|
111
126
|
jump_back_address = self.injection_point + jumper_length - 1
|
|
@@ -115,7 +130,7 @@ class AllocatingCodeInjection(AbstractCodeInjection): # pylint: disable=too-man
|
|
|
115
130
|
jumper_length = len(jump_to_new_mem)
|
|
116
131
|
jump_back_address = self.injection_point + jumper_length
|
|
117
132
|
jump_back = compile_asm(
|
|
118
|
-
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)),
|
|
119
134
|
)
|
|
120
135
|
|
|
121
136
|
if jumper_length < self.original_code_length:
|
|
@@ -123,7 +138,7 @@ class AllocatingCodeInjection(AbstractCodeInjection): # pylint: disable=too-man
|
|
|
123
138
|
elif jumper_length > self.original_code_length:
|
|
124
139
|
raise ValueError(f"Jumper length > original code length: {jumper_length} > {self.original_code_length}")
|
|
125
140
|
|
|
126
|
-
code_to_inject =
|
|
141
|
+
code_to_inject = code_to_inject + jump_back
|
|
127
142
|
|
|
128
143
|
logger.debug(f"Injecting {len(code_to_inject)} byte(s) (INJECTION) at 0x{self.new_memory_address:X}")
|
|
129
144
|
pm.write_bytes(self.new_memory_address, code_to_inject, len(code_to_inject))
|
|
@@ -160,6 +175,29 @@ class MultipleCodeInjection(AbstractCodeInjection):
|
|
|
160
175
|
injection.eject()
|
|
161
176
|
|
|
162
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
|
|
163
201
|
def compile_asm(code: Code) -> bytes:
|
|
164
202
|
if isinstance(code, str):
|
|
165
203
|
logger.debug(f"Assembling for x{ARCH}:\n{code}")
|
|
@@ -203,6 +241,18 @@ def get_long_x64_jump_back_code(address: int) -> str:
|
|
|
203
241
|
"""
|
|
204
242
|
|
|
205
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
|
+
|
|
206
256
|
def safely_eject_all_code_injections() -> None:
|
|
207
257
|
for injection in AbstractCodeInjection.created_code_injections:
|
|
208
258
|
with suppress(MemoryWriteError):
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from tomllib import load as load_toml
|
|
3
|
+
from typing import Any, Final
|
|
4
|
+
|
|
5
|
+
from pydantic import AliasChoices, BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
CONFIG_FILE: Final[Path] = Path("./trainerbase.toml")
|
|
9
|
+
|
|
10
|
+
DEFAULT_VENDOR_FOLDER_PATHS: Final[list[Path]] = [
|
|
11
|
+
Path(__file__).resolve().parent.parent / "trainerbase_vendor",
|
|
12
|
+
Path().resolve() / "trainerbase_vendor",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Process(BaseModel):
|
|
17
|
+
names: list[str] = Field(validation_alias=AliasChoices("names", "process_names"))
|
|
18
|
+
exact_match: bool = True
|
|
19
|
+
ignore_case: bool = False
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SpeedhackConfig(BaseModel):
|
|
23
|
+
offset_x64: int = 0x47098
|
|
24
|
+
offset_x32: int = 0x40058
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Vendor(BaseModel):
|
|
28
|
+
extra_search_paths: list[Path] | None = None
|
|
29
|
+
speedhack: SpeedhackConfig = SpeedhackConfig()
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def folder(self) -> Path:
|
|
33
|
+
search_paths = DEFAULT_VENDOR_FOLDER_PATHS.copy()
|
|
34
|
+
|
|
35
|
+
if self.extra_search_paths is not None:
|
|
36
|
+
search_paths.extend(self.extra_search_paths)
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
path = next(filter(Path.is_dir, search_paths))
|
|
40
|
+
except StopIteration as e:
|
|
41
|
+
raise ValueError("Failed to find Trainer Base vendor folder") from e
|
|
42
|
+
|
|
43
|
+
return path.resolve()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Config(BaseModel):
|
|
47
|
+
process: Process = Field(validation_alias=AliasChoices("process", "pymem"))
|
|
48
|
+
vendor: Vendor = Vendor()
|
|
49
|
+
logging: dict[str, Any] | None = None # Must be managed by logging.dictConfig
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
with CONFIG_FILE.resolve().open("rb") as trainerbase_toml:
|
|
53
|
+
trainerbase_config = load_toml(trainerbase_toml)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
config = Config.model_validate(trainerbase_config)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
from logging import ERROR, getLogger
|
|
2
2
|
from logging.config import dictConfig
|
|
3
3
|
|
|
4
|
-
from trainerbase.config import CONFIG_FILE,
|
|
4
|
+
from trainerbase.config import CONFIG_FILE, config
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
if
|
|
7
|
+
if config.logging is None:
|
|
8
8
|
logging_config = {
|
|
9
9
|
"version": 1,
|
|
10
10
|
"disable_existing_loggers": True,
|
|
@@ -28,9 +28,9 @@ if logging_config is None:
|
|
|
28
28
|
}
|
|
29
29
|
using_default_config = True
|
|
30
30
|
else:
|
|
31
|
+
logging_config = config.logging
|
|
31
32
|
using_default_config = False
|
|
32
33
|
|
|
33
|
-
|
|
34
34
|
dictConfig(logging_config)
|
|
35
35
|
getLogger("comtypes").setLevel(ERROR)
|
|
36
36
|
|
|
@@ -38,6 +38,3 @@ logger = getLogger("TrainerBase")
|
|
|
38
38
|
|
|
39
39
|
if using_default_config:
|
|
40
40
|
logger.debug(f"No logging config in {CONFIG_FILE}! Using default one.")
|
|
41
|
-
|
|
42
|
-
for warning_message in config_warnings:
|
|
43
|
-
logger.warning(warning_message)
|
|
@@ -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)
|
|
@@ -9,7 +9,7 @@ from psutil import Process, pid_exists, process_iter
|
|
|
9
9
|
from pymem import Pymem
|
|
10
10
|
from pymem.exception import ProcessError
|
|
11
11
|
|
|
12
|
-
from trainerbase.config import
|
|
12
|
+
from trainerbase.config import config
|
|
13
13
|
from trainerbase.logger import logger
|
|
14
14
|
|
|
15
15
|
|
|
@@ -70,15 +70,15 @@ def ensure_main_module_is_initialized(pm: Pymem) -> int:
|
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
def attach_to_process() -> Pymem:
|
|
73
|
-
|
|
74
|
-
exact_match = process_config["exact_match"]
|
|
75
|
-
ignore_case = process_config["ignore_case"]
|
|
76
|
-
|
|
77
|
-
if not process_names:
|
|
73
|
+
if not config.process.names:
|
|
78
74
|
raise ValueError("Empty process name list")
|
|
79
75
|
|
|
80
76
|
with suppress(KeyboardInterrupt):
|
|
81
|
-
pid = get_target_pid(
|
|
77
|
+
pid = get_target_pid(
|
|
78
|
+
config.process.names,
|
|
79
|
+
exact_match=config.process.exact_match,
|
|
80
|
+
ignore_case=config.process.ignore_case,
|
|
81
|
+
)
|
|
82
82
|
|
|
83
83
|
pm = Pymem(pid)
|
|
84
84
|
ensure_main_module_is_initialized(pm)
|
|
@@ -5,16 +5,17 @@ from typing import Final, Self, cast
|
|
|
5
5
|
from pyinjector import inject
|
|
6
6
|
from pymem.exception import MemoryWriteError
|
|
7
7
|
|
|
8
|
+
from trainerbase.config import config
|
|
8
9
|
from trainerbase.gameobject import GameDouble
|
|
9
10
|
from trainerbase.memory import ARCH, get_module_address
|
|
10
11
|
from trainerbase.process import pm
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
SPEEDHACK_DLL_MODULE_NAME: Final[str] = f"speedhack{ARCH}.dll"
|
|
14
|
-
SPEEDHACK_DLL_PATH: Final[Path] =
|
|
15
|
-
|
|
15
|
+
SPEEDHACK_DLL_PATH: Final[Path] = config.vendor.folder / SPEEDHACK_DLL_MODULE_NAME
|
|
16
|
+
SPEED_MODIFIER_OFFSET: Final[int] = (
|
|
17
|
+
config.vendor.speedhack.offset_x32 if ARCH == 32 else config.vendor.speedhack.offset_x64
|
|
16
18
|
)
|
|
17
|
-
SPEED_MODIFIER_OFFSET: Final[int] = 0x40058 if ARCH == 32 else 0x47098
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
class SpeedHack:
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
from tomllib import load as load_toml
|
|
3
|
-
from typing import Final
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
CONFIG_FILE: Final[Path] = Path("./trainerbase.toml")
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
with CONFIG_FILE.resolve().open("rb") as trainerbase_toml:
|
|
10
|
-
trainerbase_config = load_toml(trainerbase_toml)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
config_warnings = []
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
try:
|
|
17
|
-
process_config = trainerbase_config["process"]
|
|
18
|
-
except KeyError:
|
|
19
|
-
# Legacy section support
|
|
20
|
-
process_config = trainerbase_config["pymem"]
|
|
21
|
-
process_config["names"] = process_config["process_names"]
|
|
22
|
-
del trainerbase_config["pymem"]
|
|
23
|
-
|
|
24
|
-
config_warnings.append(
|
|
25
|
-
"Deprecated config! Use [process] section with field `names` instead of [pymem] with `process_names`"
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
logging_config = trainerbase_config.get("logging")
|
|
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
|