trainerbase 2.17.0__tar.gz → 3.1.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: trainerbase
3
- Version: 2.17.0
3
+ Version: 3.1.0
4
4
  Summary: Framework for creating game trainers/mods
5
5
  Home-page: https://gitlab.com/python-trainers/trainer-base
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "trainerbase"
3
- version = "2.17.0"
3
+ version = "3.1.0"
4
4
  description = "Framework for creating game trainers/mods"
5
5
  authors = ["v01d <v01d@v01d.ru>", "doomayka <zd@doomayka.ru>"]
6
6
  license = "MIT"
@@ -4,11 +4,10 @@ from subprocess import DEVNULL
4
4
  from subprocess import run as run_executable
5
5
  from tempfile import TemporaryDirectory
6
6
  from typing import Self, override
7
- from uuid import uuid4
8
7
 
9
8
  from pymem.exception import MemoryWriteError
10
9
 
11
- from trainerbase.memory import ARCH, AbstractAddress, ConvertibleToAddress, ensure_address, pm
10
+ from trainerbase.memory import ARCH, ConvertibleToAddress, ensure_address, pm
12
11
 
13
12
 
14
13
  FASM_EXECUTABLE_PATH = Path(__file__).resolve().parent.parent / "vendor" / "FASM.EXE"
@@ -18,11 +17,7 @@ type Code = bytes | str
18
17
 
19
18
 
20
19
  class AbstractCodeInjection(ABC):
21
- DPG_TAG_PREFIX = "injection__"
22
-
23
20
  created_code_injections: list[Self] = []
24
- address: AbstractAddress
25
- dpg_tag: str
26
21
 
27
22
  def __new__(cls, *_args, **_kwargs):
28
23
  instance = super().__new__(cls)
@@ -47,7 +42,6 @@ class CodeInjection(AbstractCodeInjection):
47
42
  ):
48
43
  self.address = ensure_address(address)
49
44
  self.code_to_inject = compile_asm(code_to_inject)
50
- self.dpg_tag = f"{self.DPG_TAG_PREFIX}{uuid4()}"
51
45
  self.original_code: bytes = b""
52
46
  self._injected = False
53
47
 
@@ -75,7 +69,6 @@ class AllocatingCodeInjection(AbstractCodeInjection): # pylint: disable=too-man
75
69
  ):
76
70
  self.address = ensure_address(address)
77
71
  self.code_to_inject = compile_asm(code_to_inject)
78
- self.dpg_tag = f"{self.DPG_TAG_PREFIX}{uuid4()}"
79
72
  self.original_code: bytes = b""
80
73
 
81
74
  self.original_code_length = original_code_length
@@ -121,6 +114,21 @@ class AllocatingCodeInjection(AbstractCodeInjection): # pylint: disable=too-man
121
114
  self.new_memory_address = 0
122
115
 
123
116
 
117
+ class MultipleCodeInjection(AbstractCodeInjection):
118
+ def __init__(self, *injections: AbstractCodeInjection):
119
+ self._injections = injections
120
+
121
+ @override
122
+ def inject(self):
123
+ for injection in self._injections:
124
+ injection.inject()
125
+
126
+ @override
127
+ def eject(self):
128
+ for injection in self._injections:
129
+ injection.eject()
130
+
131
+
124
132
  def compile_asm(code: Code) -> bytes:
125
133
  if isinstance(code, str):
126
134
  fasm_mode = f"use{ARCH}"
@@ -8,8 +8,8 @@ from typing import Self
8
8
  from keyboard import on_press_key, on_release_key
9
9
  from pymem.exception import MemoryReadError
10
10
 
11
- from trainerbase.gameobject import GameObject
12
- from trainerbase.scriptengine import Script
11
+ from trainerbase.gameobject import AbstractReadableObject, GameObject
12
+ from trainerbase.scriptengine import Script, system_script_engine
13
13
 
14
14
 
15
15
  type Coords[T: (int, float)] = tuple[T, T, T]
@@ -75,7 +75,8 @@ class Teleport:
75
75
  self.current_position: Coords | None = None
76
76
  self.movement_vector = Vector3(0, 0, 0)
77
77
  self.minimal_movement_vector_length = minimal_movement_vector_length
78
- self.movement_vector_updater_script: Script | None = None
78
+
79
+ system_script_engine.register_script(Script(self.update_movement_vector, enabled=True))
79
80
 
80
81
  def set_coords(self, x: Number, y: Number, z: Number = 100) -> None:
81
82
  self.player_x.value = x
@@ -126,30 +127,35 @@ class Teleport:
126
127
  new_coords = Vector3(*self.get_coords()) + dash_movement_vector
127
128
  self.set_coords(*new_coords)
128
129
 
129
- def create_movement_vector_updater_script(self) -> Script:
130
- if self.movement_vector_updater_script is None:
131
- self.movement_vector_updater_script = Script(self.update_movement_vector, enabled=True)
132
130
 
133
- return self.movement_vector_updater_script
131
+ class Switchable(ABC):
132
+ @abstractmethod
133
+ def enable(self):
134
+ pass
135
+
136
+ @abstractmethod
137
+ def disable(self):
138
+ pass
134
139
 
135
140
 
136
- class ShortLongSwitch(ABC):
137
- def __init__(self, key: str, short_press_max_delta: float = 0.3, context: dict | None = None):
141
+ class ShortLongPressSwitch:
142
+ def __init__(self, switchable: Switchable, key: str, short_press_max_delta: float = 0.3):
138
143
  self._key = key
139
144
  self._press_timestamp = None
140
145
  self._short_press_max_delta = short_press_max_delta
141
- self._context = context or {}
146
+ self._switchable = switchable
147
+ self._enabled = False
142
148
 
143
149
  def _on_press(self, _):
144
150
  if self._press_timestamp is None:
145
151
  self._press_timestamp = time()
146
- self.on_press(self._context)
152
+ self.on_press()
147
153
 
148
154
  def _on_release(self, _):
149
155
  if self._is_short_delta():
150
- self.on_short_press(self._context)
156
+ self.on_short_press()
151
157
  else:
152
- self.on_long_press(self._context)
158
+ self.on_long_press()
153
159
 
154
160
  self._press_timestamp = None
155
161
 
@@ -166,22 +172,25 @@ class ShortLongSwitch(ABC):
166
172
  on_release_key(self._key, self._on_release)
167
173
  on_press_key(self._key, self._on_press)
168
174
 
169
- @abstractmethod
170
- def on_press(self, context: dict):
171
- pass
175
+ def on_press(self):
176
+ self._switchable.enable()
172
177
 
173
- @abstractmethod
174
- def on_short_press(self, context: dict):
175
- pass
178
+ def on_short_press(self):
179
+ if self._enabled:
180
+ self._enabled = False
181
+ self._switchable.disable()
182
+ else:
183
+ self._enabled = True
184
+ self._switchable.enable()
176
185
 
177
- @abstractmethod
178
- def on_long_press(self, context: dict):
179
- pass
186
+ def on_long_press(self):
187
+ self._enabled = False
188
+ self._switchable.disable()
180
189
 
181
190
 
182
191
  def regenerate(
183
192
  current_value: GameObject,
184
- max_value: GameObject,
193
+ max_value: AbstractReadableObject,
185
194
  percent: Number,
186
195
  min_value: Number = 1,
187
196
  ):
@@ -1,17 +1,20 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from typing import Self, override
3
- from uuid import uuid4
3
+
4
+ from pymem.exception import MemoryWriteError
4
5
 
5
6
  from trainerbase.memory import ConvertibleToAddress, ensure_address, pm
6
7
 
7
8
 
8
- class GameObject[PymemType, TrainerBaseType](ABC):
9
- DPG_TAG_PREFIX = "object__"
10
- DPG_TAG_POSTFIX_IS_FROZEN = "__frozen"
11
- DPG_TAG_POSTFIX_GETTER = "__getter"
12
- DPG_TAG_POSTFIX_SETTER = "__setter"
9
+ class AbstractReadableObject[T](ABC):
10
+ @property
11
+ @abstractmethod
12
+ def value(self) -> T:
13
+ pass
13
14
 
14
- updated_objects: list[Self] = []
15
+
16
+ class GameObject[PymemType, TrainerBaseType](AbstractReadableObject[TrainerBaseType]):
17
+ created_objects: list[Self] = []
15
18
 
16
19
  @staticmethod
17
20
  @abstractmethod
@@ -28,25 +31,17 @@ class GameObject[PymemType, TrainerBaseType](ABC):
28
31
  address: ConvertibleToAddress,
29
32
  frozen: TrainerBaseType | None = None,
30
33
  ):
31
- GameObject.updated_objects.append(self)
34
+ GameObject.created_objects.append(self)
32
35
 
33
36
  self.address = ensure_address(address)
34
37
  self.frozen = frozen
35
38
 
36
- dpg_tag = f"{GameObject.DPG_TAG_PREFIX}{uuid4()}"
37
- self.dpg_tag_frozen = f"{dpg_tag}{GameObject.DPG_TAG_POSTFIX_IS_FROZEN}"
38
- self.dpg_tag_getter = f"{dpg_tag}{GameObject.DPG_TAG_POSTFIX_GETTER}"
39
- self.dpg_tag_setter = f"{dpg_tag}{GameObject.DPG_TAG_POSTFIX_SETTER}"
40
-
41
39
  def __repr__(self):
42
40
  return (
43
41
  f"<{self.__class__.__name__}"
44
42
  f" at {hex(self.address.resolve())}:"
45
43
  f" value={self.value},"
46
- f" frozen={self.frozen},"
47
- f" dpg_tag_frozen={self.dpg_tag_frozen},"
48
- f" dpg_tag_getter={self.dpg_tag_getter}"
49
- f" dpg_tag_setter={self.dpg_tag_setter}"
44
+ f" frozen={self.frozen}"
50
45
  ">"
51
46
  )
52
47
 
@@ -57,6 +52,7 @@ class GameObject[PymemType, TrainerBaseType](ABC):
57
52
  return value # type: ignore
58
53
 
59
54
  @property
55
+ @override
60
56
  def value(self) -> TrainerBaseType:
61
57
  return self.after_read(self.pm_read(self.address.resolve()))
62
58
 
@@ -136,3 +132,21 @@ class GameUnsignedLongLong(GameObject[int, int]):
136
132
  class GameBool(GameObject[bool, bool]):
137
133
  pm_read = pm.read_bool # type: ignore
138
134
  pm_write = pm.write_bool
135
+
136
+
137
+ class ReadonlyGameObjectSumGetter(AbstractReadableObject[int | float]):
138
+ def __init__(self, *game_numbers: GameInt | GameFloat):
139
+ self.game_numbers = game_numbers
140
+
141
+ @property
142
+ def value(self) -> int | float:
143
+ return sum(number.value for number in self.game_numbers)
144
+
145
+
146
+ def update_frozen_objects():
147
+ for game_object in GameObject.created_objects:
148
+ if game_object.frozen is not None:
149
+ try:
150
+ game_object.value = game_object.frozen
151
+ except MemoryWriteError:
152
+ continue
@@ -0,0 +1,370 @@
1
+ from abc import ABC, abstractmethod
2
+ from collections.abc import Callable
3
+ from functools import partial, wraps
4
+ from typing import Any, Self, override
5
+ from uuid import uuid4
6
+
7
+ from dearpygui import dearpygui as dpg
8
+ from keyboard import add_hotkey
9
+ from pymem.exception import MemoryReadError
10
+
11
+ from trainerbase.codeinjection import AbstractCodeInjection
12
+ from trainerbase.common import ShortLongPressSwitch, Switchable, Teleport
13
+ from trainerbase.gameobject import (
14
+ GameBool,
15
+ GameByte,
16
+ GameDouble,
17
+ GameFloat,
18
+ GameInt,
19
+ GameLongLong,
20
+ GameObject,
21
+ GameShort,
22
+ GameUnsignedInt,
23
+ GameUnsignedLongLong,
24
+ GameUnsignedShort,
25
+ )
26
+ from trainerbase.scriptengine import Script
27
+ from trainerbase.speedhack import SpeedHack
28
+ from trainerbase.tts import say
29
+
30
+
31
+ TAG_TELEPORT_LABELS = "__teleport_labels"
32
+ TAG_SPEEDHACK_FACTOR_INPUT = "tag_speedhack_factor_input"
33
+
34
+
35
+ class AbstractUIComponent(ABC):
36
+ @abstractmethod
37
+ def add_to_ui(self) -> None:
38
+ pass
39
+
40
+
41
+ class ScriptUI(AbstractUIComponent):
42
+ DPG_TAG_PREFIX = "script__"
43
+
44
+ def __init__(
45
+ self,
46
+ script: Script,
47
+ label: str,
48
+ hotkey: str | None = None,
49
+ tts_on_hotkey: bool = True,
50
+ ):
51
+ self.script = script
52
+ self.pure_label = label
53
+ self.label_with_hotkey = label if hotkey is None else f"[{hotkey}] {label}"
54
+ self.hotkey = hotkey
55
+ self.tts_on_hotkey = tts_on_hotkey
56
+
57
+ self.dpg_tag = f"{self.DPG_TAG_PREFIX}{uuid4()}"
58
+
59
+ @override
60
+ def add_to_ui(self) -> None:
61
+ if self.hotkey is not None:
62
+ add_hotkey(self.hotkey, self.on_hotkey_press)
63
+
64
+ dpg.add_checkbox(
65
+ label=self.label_with_hotkey,
66
+ tag=self.dpg_tag,
67
+ callback=self.on_script_state_change,
68
+ default_value=self.script.enabled,
69
+ )
70
+
71
+ def on_script_state_change(self):
72
+ self.script.enabled = dpg.get_value(self.dpg_tag)
73
+
74
+ def on_hotkey_press(self):
75
+ dpg.set_value(self.dpg_tag, not dpg.get_value(self.dpg_tag))
76
+
77
+ self.on_script_state_change()
78
+
79
+ if self.tts_on_hotkey:
80
+ status = "enabled" if self.script.enabled else "disabled"
81
+ say(f"Script {self.pure_label} {status}")
82
+
83
+
84
+ class CodeInjectionUI(AbstractUIComponent):
85
+ DPG_TAG_PREFIX = "injection__"
86
+
87
+ def __init__(
88
+ self,
89
+ injection: AbstractCodeInjection,
90
+ label: str,
91
+ hotkey: str | None = None,
92
+ tts_on_hotkey: bool = True,
93
+ ):
94
+ self.injection = injection
95
+ self.pure_label = label
96
+ self.label_with_hotkey = label if hotkey is None else f"[{hotkey}] {label}"
97
+ self.hotkey = hotkey
98
+ self.tts_on_hotkey = tts_on_hotkey
99
+
100
+ self.dpg_tag = f"{self.DPG_TAG_PREFIX}{uuid4()}"
101
+
102
+ @override
103
+ def add_to_ui(self) -> None:
104
+ if self.hotkey is not None:
105
+ add_hotkey(self.hotkey, self.on_hotkey_press)
106
+
107
+ dpg.add_checkbox(label=self.label_with_hotkey, tag=self.dpg_tag, callback=self.on_codeinjection_state_change)
108
+
109
+ def on_codeinjection_state_change(self):
110
+ if dpg.get_value(self.dpg_tag):
111
+ self.injection.inject()
112
+ else:
113
+ self.injection.eject()
114
+
115
+ def on_hotkey_press(self):
116
+ dpg.set_value(self.dpg_tag, not dpg.get_value(self.dpg_tag))
117
+ self.on_codeinjection_state_change()
118
+
119
+ if self.tts_on_hotkey:
120
+ status = "applied" if dpg.get_value(self.dpg_tag) else "removed"
121
+ say(f"CodeInjection {self.pure_label} {status}")
122
+
123
+
124
+ class GameObjectUI(AbstractUIComponent):
125
+ DPG_TAG_PREFIX = "object__"
126
+ DPG_TAG_POSTFIX_IS_FROZEN = "__frozen"
127
+ DPG_TAG_POSTFIX_GETTER = "__getter"
128
+ DPG_TAG_POSTFIX_SETTER = "__setter"
129
+
130
+ displayed_objects: list[Self] = []
131
+
132
+ def __init__(
133
+ self,
134
+ gameobject: GameObject,
135
+ label: str,
136
+ hotkey: str | None = None,
137
+ default_setter_input_value: Any = 0,
138
+ before_set: Callable = int,
139
+ tts_on_hotkey: bool = True,
140
+ ):
141
+ self.gameobject = gameobject
142
+ self.pure_label = label
143
+ self.label_with_hotkey = label if hotkey is None else f"[{hotkey}] {label}"
144
+ self.hotkey = hotkey
145
+ self.default_setter_input_value = default_setter_input_value
146
+ self.before_set = before_set
147
+ self.tts_on_hotkey = tts_on_hotkey
148
+
149
+ dpg_tag = f"{self.DPG_TAG_PREFIX}{uuid4()}"
150
+ self.dpg_tag_frozen = f"{dpg_tag}{self.DPG_TAG_POSTFIX_IS_FROZEN}"
151
+ self.dpg_tag_getter = f"{dpg_tag}{self.DPG_TAG_POSTFIX_GETTER}"
152
+ self.dpg_tag_setter = f"{dpg_tag}{self.DPG_TAG_POSTFIX_SETTER}"
153
+
154
+ @override
155
+ def add_to_ui(self) -> None:
156
+ if self.hotkey is not None:
157
+ add_hotkey(self.hotkey, self.on_hotkey_press)
158
+
159
+ match self.gameobject:
160
+ case GameFloat():
161
+ add_setter_input = dpg.add_input_float
162
+ case GameDouble():
163
+ add_setter_input = dpg.add_input_double
164
+ case GameByte() | GameShort() | GameInt() | GameLongLong() | GameBool():
165
+ add_setter_input = dpg.add_input_int
166
+ case GameUnsignedShort() | GameUnsignedInt() | GameUnsignedLongLong():
167
+ add_setter_input = partial(dpg.add_input_int, min_clamped=True, min_value=0)
168
+ case _:
169
+ add_setter_input = dpg.add_input_text
170
+
171
+ with dpg.group(horizontal=True):
172
+ dpg.add_checkbox(tag=self.dpg_tag_frozen, callback=self.on_frozen_state_change)
173
+ dpg.add_text(self.label_with_hotkey)
174
+ dpg.add_input_text(width=220, tag=self.dpg_tag_getter, readonly=True)
175
+ add_setter_input(width=220, tag=self.dpg_tag_setter, default_value=self.default_setter_input_value)
176
+ dpg.add_button(label="Set", callback=self.on_value_set)
177
+
178
+ GameObjectUI.displayed_objects.append(self)
179
+
180
+ def on_frozen_state_change(self):
181
+ self.gameobject.frozen = self.gameobject.value if dpg.get_value(self.dpg_tag_frozen) else None
182
+
183
+ def on_value_set(self):
184
+ new_value = self.before_set(dpg.get_value(self.dpg_tag_setter))
185
+
186
+ if self.gameobject.frozen is None:
187
+ self.gameobject.value = new_value
188
+ else:
189
+ self.gameobject.frozen = new_value
190
+
191
+ def on_hotkey_press(self):
192
+ dpg.set_value(self.dpg_tag_frozen, not dpg.get_value(self.dpg_tag_frozen))
193
+
194
+ self.on_frozen_state_change()
195
+
196
+ if self.tts_on_hotkey:
197
+ status = "released" if self.gameobject.frozen is None else "frozen"
198
+ say(f"GameObject {self.pure_label} {status}")
199
+
200
+
201
+ class TeleportUI(AbstractUIComponent):
202
+ def __init__(
203
+ self,
204
+ tp: Teleport,
205
+ hotkey_save_position: str | None = None,
206
+ hotkey_set_saved_position: str | None = None,
207
+ hotkey_dash: str | None = None,
208
+ tts_on_hotkey: bool = True,
209
+ ):
210
+ self.tp = tp
211
+ self.hotkey_save_position = hotkey_save_position
212
+ self.hotkey_set_saved_position = hotkey_set_saved_position
213
+ self.hotkey_dash = hotkey_dash
214
+ self.tts_on_hotkey = tts_on_hotkey
215
+
216
+ @override
217
+ def add_to_ui(self) -> None:
218
+ self._tp_add_save_set_position_hotkeys_if_needed()
219
+ self._tp_add_dash_hotkeys_if_needed()
220
+
221
+ add_components(
222
+ GameObjectUI(self.tp.player_x, "X"),
223
+ GameObjectUI(self.tp.player_y, "Y"),
224
+ GameObjectUI(self.tp.player_z, "Z"),
225
+ )
226
+
227
+ self._tp_add_labels_if_needed()
228
+
229
+ dpg.add_button(label="Clip Coords", callback=self.on_clip_coords)
230
+
231
+ def on_clip_coords(self):
232
+ dpg.set_clipboard_text(repr(self.tp.get_coords()))
233
+
234
+ def on_hotkey_save_position_press(self):
235
+ self.tp.save_position()
236
+
237
+ if self.tts_on_hotkey:
238
+ say("Position saved")
239
+
240
+ def on_hotkey_set_saved_position_press(self):
241
+ is_position_restored = self.tp.restore_saved_position()
242
+
243
+ if self.tts_on_hotkey:
244
+ say("Position restored" if is_position_restored else "Save position at first")
245
+
246
+ def on_hotkey_dash_press(self):
247
+ self.tp.dash()
248
+ if self.tts_on_hotkey:
249
+ say("Dash!")
250
+
251
+ def on_goto_label(self):
252
+ self.tp.goto(dpg.get_value(TAG_TELEPORT_LABELS))
253
+
254
+ def _tp_add_save_set_position_hotkeys_if_needed(self):
255
+ if self.hotkey_save_position is None or self.hotkey_set_saved_position is None:
256
+ return
257
+
258
+ add_hotkey(self.hotkey_save_position, self.on_hotkey_save_position_press)
259
+ add_hotkey(self.hotkey_set_saved_position, self.on_hotkey_set_saved_position_press)
260
+
261
+ dpg.add_text(f"[{self.hotkey_save_position}] Save Position")
262
+ dpg.add_text(f"[{self.hotkey_set_saved_position}] Set Saved Position")
263
+
264
+ def _tp_add_dash_hotkeys_if_needed(self):
265
+ if self.hotkey_dash is None:
266
+ return
267
+
268
+ add_hotkey(self.hotkey_dash, self.on_hotkey_dash_press)
269
+
270
+ dpg.add_text(f"[{self.hotkey_dash}] Dash")
271
+
272
+ def _tp_add_labels_if_needed(self):
273
+ if not self.tp.labels:
274
+ return
275
+
276
+ labels = sorted(self.tp.labels.keys())
277
+
278
+ with dpg.group(horizontal=True):
279
+ dpg.add_button(label="Go To", callback=self.on_goto_label)
280
+ dpg.add_combo(label="Labels", tag=TAG_TELEPORT_LABELS, items=labels, default_value=labels[0])
281
+
282
+
283
+ class SpeedHackUI(AbstractUIComponent):
284
+ def __init__(self, speedhack: SpeedHack, key: str):
285
+ self.speedhack = speedhack
286
+ self.key = key
287
+ self.switch = ShortLongPressSwitch(SpeedHackUISwitch(speedhack, TAG_SPEEDHACK_FACTOR_INPUT), key)
288
+
289
+ @override
290
+ def add_to_ui(self) -> None:
291
+ dpg.add_text(f"Hold [{self.key}] Enable SpeedHack")
292
+ dpg.add_text(f"Press [{self.key}] Toggle SpeedHack")
293
+
294
+ dpg.add_input_double(
295
+ tag=TAG_SPEEDHACK_FACTOR_INPUT,
296
+ label="SpeedHack Factor",
297
+ min_value=0.0,
298
+ max_value=100.0,
299
+ default_value=3.0,
300
+ min_clamped=True,
301
+ max_clamped=True,
302
+ )
303
+
304
+ self.switch.handle()
305
+
306
+
307
+ class SpeedHackUISwitch(Switchable):
308
+ def __init__(self, speedhack: SpeedHack, dpg_tag: str):
309
+ self.speedhack = speedhack
310
+ self.dpg_tag = dpg_tag
311
+
312
+ @override
313
+ def enable(self):
314
+ self.speedhack.factor = dpg.get_value(self.dpg_tag)
315
+
316
+ @override
317
+ def disable(self):
318
+ self.speedhack.factor = 1.0
319
+
320
+
321
+ def add_components(*components: AbstractUIComponent):
322
+ for component in components:
323
+ component.add_to_ui()
324
+
325
+
326
+ def simple_trainerbase_menu(window_title: str, width: int, height: int):
327
+ def menu_decorator(initializer: Callable):
328
+ @wraps(initializer)
329
+ def run_menu_wrapper(on_initialized: Callable):
330
+ dpg.create_context()
331
+ dpg.create_viewport(
332
+ title=window_title,
333
+ min_width=width,
334
+ min_height=height,
335
+ width=width,
336
+ height=height,
337
+ )
338
+ dpg.setup_dearpygui()
339
+
340
+ with dpg.window(
341
+ label=window_title,
342
+ tag="menu",
343
+ min_size=[width, height],
344
+ no_close=True,
345
+ no_move=True,
346
+ no_title_bar=True,
347
+ horizontal_scrollbar=True,
348
+ ):
349
+ initializer()
350
+
351
+ dpg.show_viewport()
352
+
353
+ on_initialized()
354
+
355
+ dpg.start_dearpygui()
356
+ dpg.destroy_context()
357
+
358
+ return run_menu_wrapper
359
+
360
+ return menu_decorator
361
+
362
+
363
+ def update_displayed_objects():
364
+ for game_object_ui in GameObjectUI.displayed_objects:
365
+ try:
366
+ new_value = game_object_ui.gameobject.value
367
+ except MemoryReadError:
368
+ new_value = "<Unresolved>"
369
+
370
+ dpg.set_value(game_object_ui.dpg_tag_getter, new_value)
@@ -0,0 +1,37 @@
1
+ from collections.abc import Callable
2
+ from functools import partial
3
+ from os import _exit as force_exit
4
+ from typing import NoReturn
5
+
6
+ from trainerbase.codeinjection import safely_eject_all_code_injections
7
+ from trainerbase.gameobject import update_frozen_objects
8
+ from trainerbase.gui import update_displayed_objects
9
+ from trainerbase.scriptengine import Script, system_script_engine
10
+ from trainerbase.speedhack import SpeedHack
11
+
12
+
13
+ def run(
14
+ run_gui: Callable[[Callable], None],
15
+ on_gui_initialized_hook: Callable | None = None,
16
+ on_shutdown_hook: Callable | None = None,
17
+ ) -> NoReturn:
18
+ system_script_engine.register_script(Script(update_frozen_objects, enabled=True))
19
+ system_script_engine.register_script(Script(update_displayed_objects, enabled=True))
20
+
21
+ run_gui(partial(on_gui_initialized, on_gui_initialized_hook))
22
+
23
+ system_script_engine.stop()
24
+ safely_eject_all_code_injections()
25
+ SpeedHack.disable()
26
+
27
+ if on_shutdown_hook is not None:
28
+ on_shutdown_hook()
29
+
30
+ force_exit(0)
31
+
32
+
33
+ def on_gui_initialized(on_gui_initialized_hook: Callable | None = None):
34
+ system_script_engine.start()
35
+
36
+ if on_gui_initialized_hook is not None:
37
+ on_gui_initialized_hook()
@@ -1,29 +1,17 @@
1
1
  from collections.abc import Callable
2
2
  from threading import Thread
3
3
  from time import sleep
4
- from uuid import uuid4
5
4
 
6
- from dearpygui import dearpygui as dpg
7
5
  from pymem.exception import MemoryReadError, MemoryWriteError
8
6
 
9
- from trainerbase.gameobject import GameObject
10
-
11
7
 
12
8
  class Script:
13
- DPG_TAG_PREFIX = "script__"
14
-
15
9
  def __init__(self, callback: Callable, enabled: bool = False):
16
10
  self.callback = callback
17
11
  self.enabled = enabled
18
- self.dpg_tag = f"{Script.DPG_TAG_PREFIX}{uuid4()}"
19
12
 
20
13
  def __repr__(self):
21
- return (
22
- f"<Script {getattr(self.callback, '__name__', 'Anon')}:"
23
- f" enabled={self.enabled},"
24
- f" dpg_tag={repr(self.dpg_tag)}"
25
- f">"
26
- )
14
+ return f"<Script {getattr(self.callback, '__name__', 'Anon')}:" f" enabled={self.enabled}" f">"
27
15
 
28
16
  def __call__(self):
29
17
  return self.callback()
@@ -72,27 +60,3 @@ class ScriptEngine:
72
60
 
73
61
 
74
62
  system_script_engine = ScriptEngine()
75
-
76
-
77
- @system_script_engine.simple_script
78
- def update_frozen_objects():
79
- for game_object in GameObject.updated_objects:
80
- if game_object.frozen is not None:
81
- try:
82
- game_object.value = game_object.frozen
83
- except MemoryWriteError:
84
- continue
85
-
86
-
87
- @system_script_engine.simple_script
88
- def update_displayed_objects():
89
- for game_object in GameObject.updated_objects:
90
- if dpg.does_alias_exist(game_object.dpg_tag_getter):
91
- try:
92
- dpg.set_value(game_object.dpg_tag_getter, game_object.value)
93
- except MemoryReadError:
94
- dpg.set_value(game_object.dpg_tag_getter, "<Unresolved>")
95
-
96
-
97
- update_frozen_objects.enabled = True
98
- update_displayed_objects.enabled = True
@@ -1,11 +1,10 @@
1
1
  from pathlib import Path
2
- from typing import override
2
+ from typing import Self
3
3
 
4
4
  from pyinjector import inject
5
5
  from pymem.exception import MemoryWriteError
6
6
  from pymem.process import module_from_name
7
7
 
8
- from trainerbase.common import ShortLongSwitch
9
8
  from trainerbase.gameobject import GameDouble
10
9
  from trainerbase.memory import ARCH, pm
11
10
 
@@ -16,6 +15,14 @@ SPEED_MODIFIER_OFFSET = 0x78048 if ARCH == 32 else 0x86040
16
15
 
17
16
 
18
17
  class SpeedHack:
18
+ _instance: Self | None = None
19
+
20
+ def __new__(cls, *_args, **_kwargs):
21
+ if cls._instance is None:
22
+ cls._instance = super().__new__(cls)
23
+
24
+ return cls._instance
25
+
19
26
  def __init__(self):
20
27
  self._dll_injection_address = self._inject()
21
28
  self._speed_modifier = GameDouble(self._dll_injection_address + SPEED_MODIFIER_OFFSET)
@@ -37,35 +44,13 @@ class SpeedHack:
37
44
  def factor(self, value: float):
38
45
  self._speed_modifier.value = value
39
46
 
40
- def disable(self):
47
+ @classmethod
48
+ def disable(cls):
49
+ if cls._instance is None:
50
+ return
51
+
41
52
  # TODO: Should I eject dll?
42
53
  try:
43
- self.factor = 1.0
54
+ cls._instance.factor = 1.0
44
55
  except MemoryWriteError:
45
56
  pass
46
-
47
-
48
- class SpeedHackShortLongSwitch(ShortLongSwitch):
49
- @override
50
- def on_press(self, context: dict):
51
- speedhack = context["speedhack"]
52
- dpg_tag = context["dpg_tag"]
53
- dpg = context["dpg"]
54
- speedhack.factor = dpg.get_value(dpg_tag)
55
-
56
- @override
57
- def on_short_press(self, context: dict):
58
- speedhack = context["speedhack"]
59
-
60
- if context["enabled"]:
61
- context["enabled"] = False
62
- speedhack.factor = 1.0
63
- else:
64
- context["enabled"] = True
65
- speedhack.factor = context["dpg"].get_value(context["dpg_tag"])
66
-
67
- @override
68
- def on_long_press(self, context: dict):
69
- speedhack = context["speedhack"]
70
- context["enabled"] = False
71
- speedhack.factor = 1.0
@@ -1,277 +0,0 @@
1
- from collections.abc import Callable
2
- from functools import partial, wraps
3
- from typing import Any
4
-
5
- from dearpygui import dearpygui as dpg
6
- from keyboard import add_hotkey
7
-
8
- from trainerbase.codeinjection import AbstractCodeInjection
9
- from trainerbase.common import Teleport
10
- from trainerbase.gameobject import (
11
- GameBool,
12
- GameByte,
13
- GameDouble,
14
- GameFloat,
15
- GameInt,
16
- GameLongLong,
17
- GameObject,
18
- GameShort,
19
- GameUnsignedInt,
20
- GameUnsignedLongLong,
21
- GameUnsignedShort,
22
- )
23
- from trainerbase.scriptengine import Script
24
- from trainerbase.speedhack import SpeedHack, SpeedHackShortLongSwitch
25
- from trainerbase.tts import say
26
-
27
-
28
- TAG_TELEPORT_LABELS = "__teleport_labels"
29
- TAG_SPEEDHACK_FACTOR_INPUT = "tag_speedhack_factor_input"
30
-
31
-
32
- def simple_trainerbase_menu(window_title: str, width: int, height: int):
33
- def menu_decorator(initializer: Callable):
34
- @wraps(initializer)
35
- def run_menu_wrapper(on_initialized: Callable):
36
- dpg.create_context()
37
- dpg.create_viewport(
38
- title=window_title,
39
- min_width=width,
40
- min_height=height,
41
- width=width,
42
- height=height,
43
- )
44
- dpg.setup_dearpygui()
45
-
46
- with dpg.window(
47
- label=window_title,
48
- tag="menu",
49
- min_size=[width, height],
50
- no_close=True,
51
- no_move=True,
52
- no_title_bar=True,
53
- horizontal_scrollbar=True,
54
- ):
55
- initializer()
56
-
57
- dpg.show_viewport()
58
-
59
- on_initialized()
60
-
61
- dpg.start_dearpygui()
62
- dpg.destroy_context()
63
-
64
- return run_menu_wrapper
65
-
66
- return menu_decorator
67
-
68
-
69
- def add_script_to_gui(
70
- script: Script,
71
- label: str,
72
- hotkey: str | None = None,
73
- tts_on_hotkey: bool = True,
74
- ):
75
- def on_script_state_change():
76
- script.enabled = dpg.get_value(script.dpg_tag)
77
-
78
- if hotkey is not None:
79
- pure_label = label
80
-
81
- def on_hotkey_press():
82
- dpg.set_value(script.dpg_tag, not dpg.get_value(script.dpg_tag))
83
- on_script_state_change()
84
- if tts_on_hotkey:
85
- status = "enabled" if script.enabled else "disabled"
86
- say(f"Script {pure_label} {status}")
87
-
88
- add_hotkey(hotkey, on_hotkey_press)
89
-
90
- label = f"[{hotkey}] {label}"
91
-
92
- dpg.add_checkbox(label=label, tag=script.dpg_tag, callback=on_script_state_change, default_value=script.enabled)
93
-
94
-
95
- def add_gameobject_to_gui(
96
- gameobject: GameObject,
97
- label: str,
98
- hotkey: str | None = None,
99
- default_setter_input_value: Any = 0,
100
- before_set: Callable = int,
101
- tts_on_hotkey: bool = True,
102
- ):
103
- def on_frozen_state_change():
104
- gameobject.frozen = gameobject.value if dpg.get_value(gameobject.dpg_tag_frozen) else None
105
-
106
- def on_value_set():
107
- new_value = before_set(dpg.get_value(gameobject.dpg_tag_setter))
108
-
109
- if gameobject.frozen is None:
110
- gameobject.value = new_value
111
- else:
112
- gameobject.frozen = new_value
113
-
114
- if hotkey is not None:
115
- pure_label = label
116
-
117
- def on_hotkey_press():
118
- dpg.set_value(gameobject.dpg_tag_frozen, not dpg.get_value(gameobject.dpg_tag_frozen))
119
- on_frozen_state_change()
120
- if tts_on_hotkey:
121
- status = "released" if gameobject.frozen is None else "frozen"
122
- say(f"GameObject {pure_label} {status}")
123
-
124
- add_hotkey(hotkey, on_hotkey_press)
125
-
126
- label = f"[{hotkey}] {label}"
127
-
128
- match gameobject:
129
- case GameFloat():
130
- add_setter_input = dpg.add_input_float
131
- case GameDouble():
132
- add_setter_input = dpg.add_input_double
133
- case GameByte() | GameShort() | GameInt() | GameLongLong() | GameBool():
134
- add_setter_input = dpg.add_input_int
135
- case GameUnsignedShort() | GameUnsignedInt() | GameUnsignedLongLong():
136
- add_setter_input = partial(dpg.add_input_int, min_clamped=True, min_value=0)
137
- case _:
138
- add_setter_input = dpg.add_input_text
139
-
140
- with dpg.group(horizontal=True):
141
- dpg.add_checkbox(tag=gameobject.dpg_tag_frozen, callback=on_frozen_state_change)
142
- dpg.add_text(label)
143
- dpg.add_input_text(width=220, tag=gameobject.dpg_tag_getter, readonly=True)
144
- add_setter_input(width=220, tag=gameobject.dpg_tag_setter, default_value=default_setter_input_value)
145
- dpg.add_button(label="Set", callback=on_value_set)
146
-
147
-
148
- def add_codeinjection_to_gui(
149
- injection: AbstractCodeInjection,
150
- label: str,
151
- hotkey: str | None = None,
152
- tts_on_hotkey: bool = True,
153
- ):
154
- def on_codeinjection_state_change():
155
- if dpg.get_value(injection.dpg_tag):
156
- injection.inject()
157
- else:
158
- injection.eject()
159
-
160
- if hotkey is not None:
161
- pure_label = label
162
-
163
- def on_hotkey_press():
164
- dpg.set_value(injection.dpg_tag, not dpg.get_value(injection.dpg_tag))
165
- on_codeinjection_state_change()
166
- if tts_on_hotkey:
167
- status = "applied" if dpg.get_value(injection.dpg_tag) else "removed"
168
- say(f"CodeInjection {pure_label} {status}")
169
-
170
- add_hotkey(hotkey, on_hotkey_press)
171
-
172
- label = f"[{hotkey}] {label}"
173
-
174
- dpg.add_checkbox(label=label, tag=injection.dpg_tag, callback=on_codeinjection_state_change)
175
-
176
-
177
- def add_teleport_to_gui(
178
- tp: Teleport,
179
- hotkey_save_position: str | None = None,
180
- hotkey_set_saved_position: str | None = None,
181
- hotkey_dash: str | None = None,
182
- tts_on_hotkey: bool = True,
183
- ):
184
- _tp_add_save_set_position_hotkeys_if_needed(tp, hotkey_save_position, hotkey_set_saved_position, tts_on_hotkey)
185
- _tp_add_dash_hotkeys_if_needed(tp, hotkey_dash, tts_on_hotkey)
186
-
187
- add_gameobject_to_gui(tp.player_x, "X")
188
- add_gameobject_to_gui(tp.player_y, "Y")
189
- add_gameobject_to_gui(tp.player_z, "Z")
190
-
191
- _tp_add_labels_if_needed(tp)
192
-
193
- def on_clip_coords():
194
- dpg.set_clipboard_text(repr(tp.get_coords()))
195
-
196
- dpg.add_button(label="Clip Coords", callback=on_clip_coords)
197
-
198
-
199
- def _tp_add_save_set_position_hotkeys_if_needed(
200
- tp: Teleport,
201
- hotkey_save_position: str | None,
202
- hotkey_set_saved_position: str | None,
203
- tts_on_hotkey: bool,
204
- ):
205
- if hotkey_save_position is None or hotkey_set_saved_position is None:
206
- return
207
-
208
- def on_hotkey_save_position_press():
209
- tp.save_position()
210
- if tts_on_hotkey:
211
- say("Position saved")
212
-
213
- def on_hotkey_set_saved_position_press():
214
- is_position_restored = tp.restore_saved_position()
215
-
216
- if tts_on_hotkey:
217
- say("Position restored" if is_position_restored else "Save position at first")
218
-
219
- add_hotkey(hotkey_save_position, on_hotkey_save_position_press)
220
- add_hotkey(hotkey_set_saved_position, on_hotkey_set_saved_position_press)
221
-
222
- dpg.add_text(f"[{hotkey_save_position}] Save Position")
223
- dpg.add_text(f"[{hotkey_set_saved_position}] Set Saved Position")
224
-
225
-
226
- def _tp_add_dash_hotkeys_if_needed(tp, hotkey_dash: str | None, tts_on_hotkey: bool):
227
- if tp.movement_vector_updater_script is None or hotkey_dash is None:
228
- return
229
-
230
- def on_hotkey_dash_press():
231
- tp.dash()
232
- if tts_on_hotkey:
233
- say("Dash!")
234
-
235
- add_hotkey(hotkey_dash, on_hotkey_dash_press)
236
-
237
- dpg.add_text(f"[{hotkey_dash}] Dash")
238
-
239
-
240
- def _tp_add_labels_if_needed(tp: Teleport):
241
- if not tp.labels:
242
- return
243
-
244
- def on_goto_label():
245
- tp.goto(dpg.get_value(TAG_TELEPORT_LABELS))
246
-
247
- labels = sorted(tp.labels.keys())
248
-
249
- with dpg.group(horizontal=True):
250
- dpg.add_button(label="Go To", callback=on_goto_label)
251
- dpg.add_combo(label="Labels", tag=TAG_TELEPORT_LABELS, items=labels, default_value=labels[0])
252
-
253
-
254
- def add_speedhack_to_gui(speedhack: SpeedHack, key: str):
255
- switch = SpeedHackShortLongSwitch(
256
- key,
257
- context={
258
- "speedhack": speedhack,
259
- "dpg_tag": TAG_SPEEDHACK_FACTOR_INPUT,
260
- "enabled": False,
261
- "dpg": dpg,
262
- },
263
- )
264
- switch.handle()
265
-
266
- dpg.add_text(f"Hold [{key}] Enable SpeedHack")
267
- dpg.add_text(f"Press [{key}] Toggle SpeedHack")
268
-
269
- dpg.add_input_double(
270
- tag=TAG_SPEEDHACK_FACTOR_INPUT,
271
- label="SpeedHack Factor",
272
- min_value=0.0,
273
- max_value=100.0,
274
- default_value=3.0,
275
- min_clamped=True,
276
- max_clamped=True,
277
- )
File without changes
File without changes