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.
Files changed (39) hide show
  1. {trainerbase-4.2.4 → trainerbase-4.3.0}/LICENSE +2 -2
  2. {trainerbase-4.2.4 → trainerbase-4.3.0}/PKG-INFO +5 -4
  3. {trainerbase-4.2.4 → trainerbase-4.3.0}/pyproject.toml +3 -3
  4. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/codeinjection.py +18 -1
  5. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/gui/helpers.py +5 -0
  6. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/gui/misc.py +1 -2
  7. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/gui/speedhack.py +30 -1
  8. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/main.py +6 -7
  9. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/process.py +16 -1
  10. {trainerbase-4.2.4 → trainerbase-4.3.0}/.gitignore +0 -0
  11. {trainerbase-4.2.4 → trainerbase-4.3.0}/README.md +0 -0
  12. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/__init__.py +0 -0
  13. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/abc.py +0 -0
  14. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/common/__init__.py +0 -0
  15. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/common/abc.py +0 -0
  16. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/common/helpers.py +0 -0
  17. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/common/keyboard.py +0 -0
  18. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/common/rainbow.py +0 -0
  19. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/common/tapper.py +0 -0
  20. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/common/teleport.py +0 -0
  21. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/config.py +0 -0
  22. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/gameobject.py +0 -0
  23. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/gui/__init__.py +0 -0
  24. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/gui/injections.py +0 -0
  25. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/gui/objects.py +0 -0
  26. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/gui/rainbow.py +0 -0
  27. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/gui/scripts.py +0 -0
  28. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/gui/tapper.py +0 -0
  29. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/gui/teleport.py +0 -0
  30. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/gui/types.py +0 -0
  31. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/logger.py +0 -0
  32. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/memory.py +0 -0
  33. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/py.typed +0 -0
  34. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/scriptengine.py +0 -0
  35. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/speedhack.py +0 -0
  36. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase/tts.py +0 -0
  37. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase_vendor/FASM.EXE +0 -0
  38. {trainerbase-4.2.4 → trainerbase-4.3.0}/trainerbase_vendor/speedhack32.dll +0 -0
  39. {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 - 2024 v01d
4
- Copyright (c) 2022 - 2024 doomayka
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.3
1
+ Metadata-Version: 2.4
2
2
  Name: trainerbase
3
- Version: 4.2.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.0
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.2.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.0",
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.2",
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. Stopping Script Engines.")
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
- return Pymem(pid)
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