trainerbase 3.6.2__tar.gz → 3.7.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: 3.6.2
3
+ Version: 3.7.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 = "3.6.2"
3
+ version = "3.7.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"
@@ -3,14 +3,14 @@ from pathlib import Path
3
3
  from subprocess import DEVNULL
4
4
  from subprocess import run as run_executable
5
5
  from tempfile import TemporaryDirectory
6
- from typing import Never, Self, override
6
+ from typing import Final, Never, Self, override
7
7
 
8
8
  from pymem.exception import MemoryWriteError
9
9
 
10
10
  from trainerbase.memory import ARCH, ConvertibleToAddress, ensure_address, pm
11
11
 
12
12
 
13
- FASM_EXECUTABLE_PATH = Path(__file__).resolve().parent.parent / "trainerbase_vendor" / "FASM.EXE"
13
+ FASM_EXECUTABLE_PATH: Final[Path] = Path(__file__).resolve().parent.parent / "trainerbase_vendor" / "FASM.EXE"
14
14
 
15
15
 
16
16
  type Code = bytes | str
@@ -15,6 +15,7 @@ class AbstractReadableObject[T](ABC):
15
15
 
16
16
  class GameObject[PymemType, TrainerBaseType](AbstractReadableObject[TrainerBaseType]):
17
17
  tracked_objects: list[Self] = []
18
+ value_range: None | tuple[int | float, int | float] = None
18
19
 
19
20
  @staticmethod
20
21
  @abstractmethod
@@ -31,6 +32,7 @@ class GameObject[PymemType, TrainerBaseType](AbstractReadableObject[TrainerBaseT
31
32
  address: ConvertibleToAddress,
32
33
  frozen: TrainerBaseType | None = None,
33
34
  is_tracked: bool = True,
35
+ value_range: None | tuple[int | float, int | float] = None,
34
36
  ):
35
37
  if is_tracked:
36
38
  GameObject.tracked_objects.append(self)
@@ -39,6 +41,9 @@ class GameObject[PymemType, TrainerBaseType](AbstractReadableObject[TrainerBaseT
39
41
  self.frozen = frozen
40
42
  self.is_tracked = is_tracked
41
43
 
44
+ if value_range is not None:
45
+ self.value_range = value_range
46
+
42
47
  def __repr__(self):
43
48
  return (
44
49
  f"<{self.__class__.__name__}"
@@ -54,6 +59,17 @@ class GameObject[PymemType, TrainerBaseType](AbstractReadableObject[TrainerBaseT
54
59
  def before_write(self, value: TrainerBaseType) -> PymemType:
55
60
  return value # type: ignore
56
61
 
62
+ def validate_range(self, value: TrainerBaseType) -> bool:
63
+ if self.value_range is None:
64
+ return True
65
+
66
+ if not isinstance(value, (int, float)):
67
+ raise TypeError(f"Unable to validate range for non-numeric value: {value} (type: {type(value)})")
68
+
69
+ min_value, max_value = self.value_range
70
+
71
+ return min_value <= value <= max_value
72
+
57
73
  @property
58
74
  @override
59
75
  def value(self) -> TrainerBaseType:
@@ -61,10 +77,14 @@ class GameObject[PymemType, TrainerBaseType](AbstractReadableObject[TrainerBaseT
61
77
 
62
78
  @value.setter
63
79
  def value(self, new_value: TrainerBaseType):
80
+ if not self.validate_range(new_value):
81
+ raise ValueError(f"Wrong value for type {type(self)}: {new_value} not in range {self.value_range}")
82
+
64
83
  self.pm_write(self.address.resolve(), self.before_write(new_value))
65
84
 
66
85
 
67
86
  class GameFloat(GameObject[float, float]):
87
+ value_range = (-3.4e38, 3.4e38)
68
88
  pm_read = pm.read_float # type: ignore
69
89
  pm_write = pm.write_float
70
90
 
@@ -74,6 +94,7 @@ class GameFloat(GameObject[float, float]):
74
94
 
75
95
 
76
96
  class GameDouble(GameObject[float, float]):
97
+ value_range = (-1.7e308, 1.7e308)
77
98
  pm_read = pm.read_double # type: ignore
78
99
  pm_write = pm.write_double
79
100
 
@@ -83,6 +104,8 @@ class GameDouble(GameObject[float, float]):
83
104
 
84
105
 
85
106
  class GameByte(GameObject[bytes, int]):
107
+ value_range = (0, 255)
108
+
86
109
  @staticmethod
87
110
  @override
88
111
  def pm_read(address: int) -> bytes:
@@ -103,36 +126,50 @@ class GameByte(GameObject[bytes, int]):
103
126
 
104
127
 
105
128
  class GameInt(GameObject[int, int]):
129
+ value_range = (-2_147_483_648, 2_147_483_647)
130
+
106
131
  pm_read = pm.read_int # type: ignore
107
132
  pm_write = pm.write_int
108
133
 
109
134
 
110
135
  class GameShort(GameObject[int, int]):
136
+ value_range = (-32_768, 32_767)
137
+
111
138
  pm_read = pm.read_short # type: ignore
112
139
  pm_write = pm.write_short
113
140
 
114
141
 
115
142
  class GameLongLong(GameObject[int, int]):
143
+ value_range = (-(2**63), (2**63) - 1)
144
+
116
145
  pm_read = pm.read_longlong # type: ignore
117
146
  pm_write = pm.write_longlong
118
147
 
119
148
 
120
149
  class GameUnsignedInt(GameObject[int, int]):
150
+ value_range = (0, 4_294_967_295)
151
+
121
152
  pm_read = pm.read_uint # type: ignore
122
153
  pm_write = pm.write_uint
123
154
 
124
155
 
125
156
  class GameUnsignedShort(GameObject[int, int]):
157
+ value_range = (0, 65_535)
158
+
126
159
  pm_read = pm.read_ushort # type: ignore
127
160
  pm_write = pm.write_ushort
128
161
 
129
162
 
130
163
  class GameUnsignedLongLong(GameObject[int, int]):
164
+ value_range = (0, 18_446_744_073_709_551_615)
165
+
131
166
  pm_read = pm.read_ulonglong # type: ignore
132
167
  pm_write = pm.write_ulonglong
133
168
 
134
169
 
135
170
  class GameBool(GameObject[bool, bool]):
171
+ value_range = (False, True)
172
+
136
173
  pm_read = pm.read_bool # type: ignore
137
174
  pm_write = pm.write_bool
138
175
 
@@ -1,6 +1,6 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from collections.abc import Callable
3
- from functools import partial, wraps
3
+ from functools import wraps
4
4
  from typing import Any, Self, override
5
5
  from uuid import uuid4
6
6
 
@@ -153,6 +153,7 @@ class GameObjectUI(AbstractUIComponent):
153
153
  default_setter_input_value: Any = 0,
154
154
  before_set: Callable | None = None,
155
155
  tts_on_hotkey: bool = True,
156
+ setter_input_width: int = 220,
156
157
  ):
157
158
  if not gameobject.is_tracked:
158
159
  hotkey = None
@@ -164,6 +165,7 @@ class GameObjectUI(AbstractUIComponent):
164
165
  self.default_setter_input_value = default_setter_input_value
165
166
  self.before_set = before_set
166
167
  self.tts_on_hotkey = tts_on_hotkey
168
+ self.setter_input_width = setter_input_width
167
169
 
168
170
  dpg_tag = f"{self.DPG_TAG_PREFIX}{uuid4()}"
169
171
  self.dpg_tag_frozen = f"{dpg_tag}{self.DPG_TAG_POSTFIX_IS_FROZEN}"
@@ -175,29 +177,56 @@ class GameObjectUI(AbstractUIComponent):
175
177
  if self.hotkey is not None:
176
178
  add_hotkey(self.hotkey, self.on_hotkey_press)
177
179
 
178
- match self.gameobject:
179
- case GameFloat():
180
- add_setter_input = dpg.add_input_float
181
- case GameDouble():
182
- add_setter_input = dpg.add_input_double
183
- case GameByte() | GameShort() | GameInt() | GameLongLong() | GameBool():
184
- add_setter_input = dpg.add_input_int
185
- case GameUnsignedShort() | GameUnsignedInt() | GameUnsignedLongLong():
186
- add_setter_input = partial(dpg.add_input_int, min_clamped=True, min_value=0)
187
- case _:
188
- add_setter_input = dpg.add_input_text
189
-
190
180
  with dpg.group(horizontal=True):
191
181
  if self.gameobject.is_tracked:
192
182
  dpg.add_checkbox(tag=self.dpg_tag_frozen, callback=self.on_frozen_state_change)
193
183
 
194
184
  dpg.add_text(self.label_with_hotkey)
195
185
  dpg.add_input_text(width=220, tag=self.dpg_tag_getter, readonly=True)
196
- add_setter_input(width=220, tag=self.dpg_tag_setter, default_value=self.default_setter_input_value)
186
+
187
+ self.add_setter_input()
188
+
197
189
  dpg.add_button(label="Set", callback=self.on_value_set)
198
190
 
199
191
  GameObjectUI.displayed_objects.append(self)
200
192
 
193
+ def add_setter_input(self):
194
+ default_kwargs = {
195
+ "tag": self.dpg_tag_setter,
196
+ "width": self.setter_input_width,
197
+ "default_value": self.default_setter_input_value,
198
+ }
199
+
200
+ if self.gameobject.value_range is not None:
201
+ min_value, max_value = self.gameobject.value_range
202
+ default_kwargs["min_clamped"] = True
203
+ default_kwargs["max_clamped"] = True
204
+ default_kwargs["min_value"] = min_value
205
+ default_kwargs["max_value"] = max_value
206
+
207
+ match self.gameobject:
208
+ case GameFloat():
209
+ dpg.add_input_float(**default_kwargs)
210
+ case GameDouble():
211
+ dpg.add_input_double(**default_kwargs)
212
+ case (
213
+ GameByte()
214
+ | GameShort()
215
+ | GameInt()
216
+ | GameLongLong()
217
+ | GameUnsignedShort()
218
+ | GameUnsignedInt()
219
+ | GameUnsignedLongLong()
220
+ ):
221
+ dpg.add_input_int(**default_kwargs)
222
+ case GameBool():
223
+ dpg.add_checkbox(
224
+ tag=self.dpg_tag_setter,
225
+ default_value=bool(self.default_setter_input_value),
226
+ )
227
+ case _:
228
+ dpg.add_input_text(**default_kwargs)
229
+
201
230
  def on_frozen_state_change(self):
202
231
  try:
203
232
  self.gameobject.frozen = self.gameobject.value if dpg.get_value(self.dpg_tag_frozen) else None
@@ -316,11 +345,13 @@ class SpeedHackUI(AbstractUIComponent):
316
345
 
317
346
  PRESETS: tuple[float, ...] = (0.1, 0.2, 0.3, 0.5, 1.0, 2.0, 3.0, 5.0, 10.0, 20.0, 50.0)
318
347
 
319
- def __init__(self, speedhack: SpeedHack, key: str = "Delete", default_factor_input_value: float = 3.0):
320
- self.speedhack = speedhack
348
+ def __init__(
349
+ self, speedhack: SpeedHack | None = None, key: str = "Delete", default_factor_input_value: float = 3.0
350
+ ):
351
+ self.speedhack = SpeedHack() if speedhack is None else speedhack
321
352
  self.key = key
322
353
  self.default_factor_input_value = default_factor_input_value
323
- self.switch = ShortLongPressSwitch(SpeedHackUISwitch(speedhack, self.DPG_TAG_SPEEDHACK_FACTOR_INPUT), key)
354
+ self.switch = ShortLongPressSwitch(SpeedHackUISwitch(self.speedhack, self.DPG_TAG_SPEEDHACK_FACTOR_INPUT), key)
324
355
 
325
356
  @override
326
357
  def add_to_ui(self) -> None:
@@ -1,7 +1,7 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from collections.abc import Callable
3
3
  from functools import partial
4
- from typing import Self, assert_never, override
4
+ from typing import Final, Self, assert_never, override
5
5
 
6
6
  from pymem import Pymem
7
7
  from pymem.exception import ProcessNotFound
@@ -21,8 +21,8 @@ else:
21
21
  raise ProcessNotFound(f"not any: {pymem_config['process_names']}")
22
22
 
23
23
 
24
- ARCH = 64 if pm.is_64_bit else 32
25
- POINTER_SIZE = 8 if pm.is_64_bit else 4
24
+ ARCH: Final[int] = 64 if pm.is_64_bit else 32
25
+ POINTER_SIZE: Final[int] = 8 if pm.is_64_bit else 4
26
26
 
27
27
 
28
28
  type ConvertibleToAddress = AbstractAddress | int | bytes
@@ -1,5 +1,5 @@
1
1
  from pathlib import Path
2
- from typing import Self
2
+ from typing import Final, Self
3
3
 
4
4
  from pyinjector import inject
5
5
  from pymem.exception import MemoryWriteError
@@ -9,9 +9,11 @@ from trainerbase.gameobject import GameDouble
9
9
  from trainerbase.memory import ARCH, pm
10
10
 
11
11
 
12
- SPEEDHACK_DLL_MODULE_NAME = f"speedhack{ARCH}.dll"
13
- SPEEDHACK_DLL_PATH = Path(__file__).resolve().parent.parent / "trainerbase_vendor" / SPEEDHACK_DLL_MODULE_NAME
14
- SPEED_MODIFIER_OFFSET = 0x40058 if ARCH == 32 else 0x47098
12
+ SPEEDHACK_DLL_MODULE_NAME: Final[str] = f"speedhack{ARCH}.dll"
13
+ SPEEDHACK_DLL_PATH: Final[Path] = (
14
+ Path(__file__).resolve().parent.parent / "trainerbase_vendor" / SPEEDHACK_DLL_MODULE_NAME
15
+ )
16
+ SPEED_MODIFIER_OFFSET: Final[int] = 0x40058 if ARCH == 32 else 0x47098
15
17
 
16
18
 
17
19
  class SpeedHack:
File without changes
File without changes