trainerbase 4.2.4__tar.gz → 4.3.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.2.4 → trainerbase-4.3.0}/LICENSE +2 -2
- {trainerbase-4.2.4 → trainerbase-4.3.0}/PKG-INFO +5 -4
- {trainerbase-4.2.4 → trainerbase-4.3.0}/pyproject.toml +3 -3
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/codeinjection.py +18 -1
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/gui/helpers.py +5 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/gui/misc.py +1 -2
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/gui/speedhack.py +30 -1
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/main.py +6 -7
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/process.py +16 -1
- {trainerbase-4.2.4 → trainerbase-4.3.0}/.gitignore +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/README.md +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/__init__.py +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/abc.py +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/common/__init__.py +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/common/abc.py +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/common/helpers.py +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/common/keyboard.py +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/common/rainbow.py +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/common/tapper.py +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/common/teleport.py +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/config.py +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/gameobject.py +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/gui/__init__.py +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/gui/injections.py +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/gui/objects.py +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/gui/rainbow.py +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/gui/scripts.py +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/gui/tapper.py +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/gui/teleport.py +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/gui/types.py +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/logger.py +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/memory.py +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/py.typed +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/scriptengine.py +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/speedhack.py +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/tts.py +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase_vendor/FASM.EXE +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase_vendor/speedhack32.dll +0 -0
- {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase_vendor/speedhack64.dll +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2021 -
|
|
4
|
-
Copyright (c) 2022 -
|
|
3
|
+
Copyright (c) 2021 - 2025 v01d
|
|
4
|
+
Copyright (c) 2022 - 2025 doomayka
|
|
5
5
|
|
|
6
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
7
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: trainerbase
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.3.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
|
|
7
7
|
Author-email: v01d <v01d@v01d.ru>, doomayka <zd@doomayka.ru>
|
|
8
|
-
License: MIT
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
9
10
|
Classifier: Programming Language :: Python :: 3.12
|
|
10
11
|
Classifier: Programming Language :: Python :: 3.13
|
|
11
12
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
@@ -13,7 +14,7 @@ Requires-Python: >=3.13
|
|
|
13
14
|
Requires-Dist: dearpygui>=2.0.0
|
|
14
15
|
Requires-Dist: keyboard>=0.13.5
|
|
15
16
|
Requires-Dist: mouse>=0.7.1
|
|
16
|
-
Requires-Dist: psutil>=6.1.
|
|
17
|
+
Requires-Dist: psutil>=6.1.1
|
|
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.3.0"
|
|
4
4
|
description = "Framework for creating game trainers/mods"
|
|
5
5
|
license = "MIT"
|
|
6
6
|
authors = [
|
|
@@ -13,7 +13,7 @@ dependencies = [
|
|
|
13
13
|
"dearpygui>=2.0.0",
|
|
14
14
|
"keyboard>=0.13.5",
|
|
15
15
|
"mouse>=0.7.1",
|
|
16
|
-
"psutil>=6.1.
|
|
16
|
+
"psutil>=6.1.1",
|
|
17
17
|
"pyinjector>=1.3.0",
|
|
18
18
|
"pymem>=1.14.0",
|
|
19
19
|
"pyttsx3>=2.98",
|
|
@@ -74,5 +74,5 @@ build-backend = "hatchling.build"
|
|
|
74
74
|
|
|
75
75
|
[dependency-groups]
|
|
76
76
|
dev = [
|
|
77
|
-
"ruff>=0.8.
|
|
77
|
+
"ruff>=0.8.6",
|
|
78
78
|
]
|
|
@@ -8,6 +8,7 @@ from typing import ClassVar, Final, Self, override
|
|
|
8
8
|
|
|
9
9
|
from pymem.exception import MemoryWriteError
|
|
10
10
|
|
|
11
|
+
from trainerbase.logger import logger
|
|
11
12
|
from trainerbase.memory import ARCH, ConvertibleToAddress, allocate, ensure_address
|
|
12
13
|
from trainerbase.process import pm
|
|
13
14
|
|
|
@@ -51,16 +52,22 @@ class CodeInjection(AbstractCodeInjection):
|
|
|
51
52
|
@override
|
|
52
53
|
def inject(self):
|
|
53
54
|
self.injection_point = self.address.resolve()
|
|
54
|
-
|
|
55
55
|
self.original_code = pm.read_bytes(self.injection_point, len(self.code_to_inject))
|
|
56
|
+
|
|
57
|
+
logger.debug(f"Injecting {len(self.code_to_inject)} byte(s) at 0x{self.injection_point:X}")
|
|
58
|
+
|
|
56
59
|
pm.write_bytes(self.injection_point, self.code_to_inject, len(self.code_to_inject))
|
|
57
60
|
self._injected = True
|
|
58
61
|
|
|
59
62
|
@override
|
|
60
63
|
def eject(self):
|
|
61
64
|
if self._injected:
|
|
65
|
+
logger.debug(f"Ejecting {len(self.code_to_inject)} byte(s) from 0x{self.injection_point:X}")
|
|
66
|
+
|
|
62
67
|
pm.write_bytes(self.injection_point, self.original_code, len(self.original_code))
|
|
63
68
|
self._injected = False
|
|
69
|
+
else:
|
|
70
|
+
logger.debug("Nothing to eject")
|
|
64
71
|
|
|
65
72
|
|
|
66
73
|
class AllocatingCodeInjection(AbstractCodeInjection): # pylint: disable=too-many-instance-attributes
|
|
@@ -118,16 +125,24 @@ class AllocatingCodeInjection(AbstractCodeInjection): # pylint: disable=too-man
|
|
|
118
125
|
|
|
119
126
|
code_to_inject = self.code_to_inject + jump_back
|
|
120
127
|
|
|
128
|
+
logger.debug(f"Injecting {len(code_to_inject)} byte(s) (INJECTION) at 0x{self.new_memory_address:X}")
|
|
121
129
|
pm.write_bytes(self.new_memory_address, code_to_inject, len(code_to_inject))
|
|
130
|
+
|
|
131
|
+
logger.debug(f"Injecting {len(jump_to_new_mem)} byte(s) (JUMPER) at 0x{self.injection_point:X}")
|
|
122
132
|
pm.write_bytes(self.injection_point, jump_to_new_mem, len(jump_to_new_mem))
|
|
123
133
|
|
|
124
134
|
@override
|
|
125
135
|
def eject(self):
|
|
126
136
|
if self.new_memory_address and self.injection_point:
|
|
137
|
+
logger.debug(f"Ejecting {self.original_code_length} byte(s) (JUMPER) from 0x{self.injection_point:X}")
|
|
127
138
|
pm.write_bytes(self.injection_point, self.original_code, self.original_code_length)
|
|
139
|
+
|
|
140
|
+
logger.debug(f"Freeing memory at 0x{self.new_memory_address:X}")
|
|
128
141
|
pm.free(self.new_memory_address)
|
|
129
142
|
self.new_memory_address = 0
|
|
130
143
|
self.injection_point = 0
|
|
144
|
+
else:
|
|
145
|
+
logger.debug("Nothing to eject")
|
|
131
146
|
|
|
132
147
|
|
|
133
148
|
class MultipleCodeInjection(AbstractCodeInjection):
|
|
@@ -147,6 +162,8 @@ class MultipleCodeInjection(AbstractCodeInjection):
|
|
|
147
162
|
|
|
148
163
|
def compile_asm(code: Code) -> bytes:
|
|
149
164
|
if isinstance(code, str):
|
|
165
|
+
logger.debug(f"Assembling for x{ARCH}:\n{code}")
|
|
166
|
+
|
|
150
167
|
fasm_mode = f"use{ARCH}"
|
|
151
168
|
with TemporaryDirectory() as tmp_dir:
|
|
152
169
|
asm_file = Path(tmp_dir) / "injection.asm"
|
|
@@ -6,6 +6,7 @@ from dearpygui import dearpygui as dpg
|
|
|
6
6
|
from trainerbase.common.rainbow import Color, gradient_updater
|
|
7
7
|
from trainerbase.gui.rainbow import RainbowUI
|
|
8
8
|
from trainerbase.gui.types import AbstractUIComponent
|
|
9
|
+
from trainerbase.logger import logger
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
def add_components(*components: AbstractUIComponent):
|
|
@@ -58,8 +59,12 @@ def simple_trainerbase_menu(
|
|
|
58
59
|
|
|
59
60
|
on_initialized()
|
|
60
61
|
|
|
62
|
+
logger.debug("Showing Window")
|
|
63
|
+
|
|
61
64
|
dpg.start_dearpygui()
|
|
62
65
|
|
|
66
|
+
logger.debug("Window has been closed")
|
|
67
|
+
|
|
63
68
|
on_shutdown() # it's better to call it here, because after destroy_context it may not be possible
|
|
64
69
|
|
|
65
70
|
dpg.destroy_context()
|
|
@@ -50,8 +50,7 @@ class HotkeyHandlerUI(AbstractUIComponent):
|
|
|
50
50
|
def add_to_ui(self) -> None:
|
|
51
51
|
match self.handler:
|
|
52
52
|
case ShortLongHotkeyPressSwitch():
|
|
53
|
-
dpg.add_text(f"Hold [{self.handler.hotkey}] Enable {self.label}")
|
|
54
|
-
dpg.add_text(f"Press [{self.handler.hotkey}] Toggle {self.label}")
|
|
53
|
+
dpg.add_text(f"Press/Hold [{self.handler.hotkey}] Toggle/Enable {self.label}")
|
|
55
54
|
case ReleaseHotkeySwitch():
|
|
56
55
|
dpg.add_text(f"[{self.handler.hotkey}] Toggle {self.label}")
|
|
57
56
|
case SimpleHotkeyHandler():
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
from functools import partial
|
|
1
2
|
from typing import override
|
|
2
3
|
|
|
3
4
|
from dearpygui import dearpygui as dpg
|
|
4
5
|
|
|
5
|
-
from trainerbase.common.keyboard import ShortLongHotkeyPressSwitch, Switchable
|
|
6
|
+
from trainerbase.common.keyboard import ShortLongHotkeyPressSwitch, SimpleHotkeyHandler, Switchable
|
|
6
7
|
from trainerbase.gui.helpers import add_components
|
|
7
8
|
from trainerbase.gui.misc import HotkeyHandlerUI
|
|
8
9
|
from trainerbase.gui.types import AbstractUIComponent
|
|
@@ -34,9 +35,15 @@ class SpeedHackUI(AbstractUIComponent):
|
|
|
34
35
|
speedhack: SpeedHack | None = None,
|
|
35
36
|
key: str = "Delete",
|
|
36
37
|
default_factor_input_value: float = 3.0,
|
|
38
|
+
next_preset_hotkey: str | None = "Shift+PageUp",
|
|
39
|
+
previous_preset_hotkey: str | None = "Shift+PageDown",
|
|
37
40
|
):
|
|
38
41
|
self.speedhack = SpeedHack() if speedhack is None else speedhack
|
|
42
|
+
|
|
39
43
|
self.key = key
|
|
44
|
+
self.next_preset_hotkey = next_preset_hotkey
|
|
45
|
+
self.previous_preset_hotkey = previous_preset_hotkey
|
|
46
|
+
|
|
40
47
|
self.default_factor_input_value = default_factor_input_value
|
|
41
48
|
self.handler = ShortLongHotkeyPressSwitch(
|
|
42
49
|
SpeedHackUISwitch(self.speedhack, self.DPG_TAG_SPEEDHACK_FACTOR_INPUT),
|
|
@@ -47,6 +54,19 @@ class SpeedHackUI(AbstractUIComponent):
|
|
|
47
54
|
def add_to_ui(self) -> None:
|
|
48
55
|
add_components(HotkeyHandlerUI(self.handler, "SpeedHack"))
|
|
49
56
|
|
|
57
|
+
if self.next_preset_hotkey is not None and self.previous_preset_hotkey is not None:
|
|
58
|
+
with dpg.group(horizontal=True):
|
|
59
|
+
add_components(
|
|
60
|
+
HotkeyHandlerUI(
|
|
61
|
+
SimpleHotkeyHandler(partial(self.change_preset, -1), self.previous_preset_hotkey),
|
|
62
|
+
"Preset -",
|
|
63
|
+
),
|
|
64
|
+
HotkeyHandlerUI(
|
|
65
|
+
SimpleHotkeyHandler(partial(self.change_preset, 1), self.next_preset_hotkey),
|
|
66
|
+
"Preset +",
|
|
67
|
+
),
|
|
68
|
+
)
|
|
69
|
+
|
|
50
70
|
dpg.add_input_double(
|
|
51
71
|
tag=self.DPG_TAG_SPEEDHACK_FACTOR_INPUT,
|
|
52
72
|
label="SpeedHack Factor",
|
|
@@ -68,6 +88,15 @@ class SpeedHackUI(AbstractUIComponent):
|
|
|
68
88
|
callback=self.on_preset_change,
|
|
69
89
|
)
|
|
70
90
|
|
|
91
|
+
def change_preset(self, diff: int):
|
|
92
|
+
new_preset_index = dpg.get_value(self.DPG_TAG_SPEEDHACK_PRESET_INPUT) + diff
|
|
93
|
+
|
|
94
|
+
if not (0 <= new_preset_index < len(self.PRESETS)):
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
dpg.set_value(self.DPG_TAG_SPEEDHACK_PRESET_INPUT, new_preset_index)
|
|
98
|
+
self.on_preset_change()
|
|
99
|
+
|
|
71
100
|
def on_preset_change(self):
|
|
72
101
|
new_factor = self.PRESETS[dpg.get_value(self.DPG_TAG_SPEEDHACK_PRESET_INPUT)]
|
|
73
102
|
dpg.set_value(self.DPG_TAG_SPEEDHACK_FACTOR_INPUT, new_factor)
|
|
@@ -28,20 +28,19 @@ def run( # pylint: disable=too-complex
|
|
|
28
28
|
on_shutdown_hook: Callback | None = None,
|
|
29
29
|
) -> NoReturn:
|
|
30
30
|
def on_shutdown() -> NoReturn:
|
|
31
|
-
logger.debug("Shutting down
|
|
32
|
-
|
|
33
|
-
rainbow_script_engine.stop()
|
|
34
|
-
system_script_engine.stop()
|
|
35
|
-
process_healthcheck_script_engine.stop()
|
|
31
|
+
logger.debug("Shutting down...")
|
|
36
32
|
|
|
37
33
|
logger.debug("Ejecting Code Injections.")
|
|
38
|
-
|
|
39
34
|
safely_eject_all_code_injections()
|
|
40
35
|
|
|
41
36
|
logger.debug("Disabling SpeedHack")
|
|
42
|
-
|
|
43
37
|
SpeedHack.disable()
|
|
44
38
|
|
|
39
|
+
logger.debug("Stopping Script Engines.")
|
|
40
|
+
rainbow_script_engine.stop()
|
|
41
|
+
system_script_engine.stop()
|
|
42
|
+
process_healthcheck_script_engine.stop()
|
|
43
|
+
|
|
45
44
|
if on_shutdown_hook is not None:
|
|
46
45
|
logger.debug("on_shutdown_hook provided. Calling.")
|
|
47
46
|
on_shutdown_hook()
|
|
@@ -2,10 +2,12 @@ from collections.abc import Iterable
|
|
|
2
2
|
from contextlib import suppress
|
|
3
3
|
from itertools import cycle
|
|
4
4
|
from os import _exit as force_exit
|
|
5
|
+
from time import sleep
|
|
5
6
|
from typing import cast
|
|
6
7
|
|
|
7
8
|
from psutil import Process, pid_exists, process_iter
|
|
8
9
|
from pymem import Pymem
|
|
10
|
+
from pymem.exception import ProcessError
|
|
9
11
|
|
|
10
12
|
from trainerbase.config import process_config
|
|
11
13
|
from trainerbase.logger import logger
|
|
@@ -58,6 +60,15 @@ def get_target_pid(process_names: Iterable[str], *, exact_match: bool, ignore_ca
|
|
|
58
60
|
raise RuntimeError("This place must be unreachable! `process_names` is probably empty.")
|
|
59
61
|
|
|
60
62
|
|
|
63
|
+
def ensure_main_module_is_initialized(pm: Pymem) -> int:
|
|
64
|
+
while True:
|
|
65
|
+
try:
|
|
66
|
+
return pm.base_address
|
|
67
|
+
except ProcessError:
|
|
68
|
+
logger.debug("Main module is not initialized. Waiting...")
|
|
69
|
+
sleep(0.25)
|
|
70
|
+
|
|
71
|
+
|
|
61
72
|
def attach_to_process() -> Pymem:
|
|
62
73
|
process_names = process_config["names"]
|
|
63
74
|
exact_match = process_config["exact_match"]
|
|
@@ -68,7 +79,11 @@ def attach_to_process() -> Pymem:
|
|
|
68
79
|
|
|
69
80
|
with suppress(KeyboardInterrupt):
|
|
70
81
|
pid = get_target_pid(process_names, exact_match=exact_match, ignore_case=ignore_case)
|
|
71
|
-
|
|
82
|
+
|
|
83
|
+
pm = Pymem(pid)
|
|
84
|
+
ensure_main_module_is_initialized(pm)
|
|
85
|
+
|
|
86
|
+
return pm
|
|
72
87
|
|
|
73
88
|
force_exit(0)
|
|
74
89
|
|
|
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
|