trainerbase 3.11.4__tar.gz → 3.12.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 (34) hide show
  1. trainerbase-3.12.0/PKG-INFO +36 -0
  2. trainerbase-3.12.0/README.md +14 -0
  3. {trainerbase-3.11.4 → trainerbase-3.12.0}/pyproject.toml +31 -15
  4. {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/codeinjection.py +20 -18
  5. {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/common/helpers.py +4 -4
  6. {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/common/teleport.py +3 -0
  7. {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/config.py +1 -0
  8. {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/gameobject.py +47 -32
  9. trainerbase-3.12.0/trainerbase/gui/__init__.py +0 -0
  10. {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/gui/injections.py +2 -4
  11. {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/gui/objects.py +11 -14
  12. {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/gui/scripts.py +1 -0
  13. {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/gui/teleport.py +22 -2
  14. {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/main.py +8 -4
  15. {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/memory.py +21 -19
  16. trainerbase-3.12.0/trainerbase/process.py +49 -0
  17. {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/scriptengine.py +2 -1
  18. {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/speedhack.py +6 -6
  19. {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/tts.py +4 -7
  20. trainerbase-3.11.4/PKG-INFO +0 -44
  21. trainerbase-3.11.4/README.rst +0 -23
  22. trainerbase-3.11.4/trainerbase/gui/__init__.py +0 -8
  23. {trainerbase-3.11.4 → trainerbase-3.12.0}/LICENSE +0 -0
  24. {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/__init__.py +0 -0
  25. {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/common/__init__.py +0 -0
  26. {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/common/abc.py +0 -0
  27. {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/common/keyboard.py +0 -0
  28. {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/gui/helpers.py +0 -0
  29. {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/gui/misc.py +0 -0
  30. {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/gui/speedhack.py +0 -0
  31. {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/gui/types.py +0 -0
  32. {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase_vendor/FASM.EXE +0 -0
  33. {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase_vendor/speedhack32.dll +0 -0
  34. {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase_vendor/speedhack64.dll +0 -0
@@ -0,0 +1,36 @@
1
+ Metadata-Version: 2.1
2
+ Name: trainerbase
3
+ Version: 3.12.0
4
+ Summary: Framework for creating game trainers/mods
5
+ Home-page: https://gitlab.com/python-trainers/trainer-base
6
+ License: MIT
7
+ Author: v01d
8
+ Author-email: v01d@v01d.ru
9
+ Requires-Python: >=3.12,<4.0
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Requires-Dist: dearpygui (>=1.11.1,<2.0.0)
14
+ Requires-Dist: keyboard (>=0.13.5,<0.14.0)
15
+ Requires-Dist: psutil (>=5.9.8,<6.0.0)
16
+ Requires-Dist: pyinjector (>=1.3.0,<2.0.0)
17
+ Requires-Dist: pymem (>=1.13.1,<2.0.0)
18
+ Requires-Dist: pyttsx3 (>=2.90,<3.0)
19
+ Project-URL: Repository, https://gitlab.com/python-trainers/trainer-base
20
+ Description-Content-Type: text/markdown
21
+
22
+ # Trainer Base
23
+
24
+ [![PyPI version](https://badge.fury.io/py/TrainerBase.svg)](https://badge.fury.io/py/TrainerBase)
25
+ [![Downloads](https://static.pepy.tech/badge/trainerbase)](https://pepy.tech/project/trainerbase)
26
+
27
+ ## To create project from template using cookiecutter
28
+
29
+ ```bash
30
+ cookiecutter https://gitlab.com/v01d-gl/basic-project-template.git --checkout trainerbase
31
+ ```
32
+
33
+ ## Examples
34
+
35
+ [Python Trainers](https://gitlab.com/python-trainers)
36
+
@@ -0,0 +1,14 @@
1
+ # Trainer Base
2
+
3
+ [![PyPI version](https://badge.fury.io/py/TrainerBase.svg)](https://badge.fury.io/py/TrainerBase)
4
+ [![Downloads](https://static.pepy.tech/badge/trainerbase)](https://pepy.tech/project/trainerbase)
5
+
6
+ ## To create project from template using cookiecutter
7
+
8
+ ```bash
9
+ cookiecutter https://gitlab.com/v01d-gl/basic-project-template.git --checkout trainerbase
10
+ ```
11
+
12
+ ## Examples
13
+
14
+ [Python Trainers](https://gitlab.com/python-trainers)
@@ -1,10 +1,10 @@
1
1
  [tool.poetry]
2
2
  name = "trainerbase"
3
- version = "3.11.4"
3
+ version = "3.12.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"
7
- readme = "README.rst"
7
+ readme = "README.md"
8
8
  homepage = "https://gitlab.com/python-trainers/trainer-base"
9
9
  repository = "https://gitlab.com/python-trainers/trainer-base"
10
10
 
@@ -16,28 +16,44 @@ include = [
16
16
  { path = "trainerbase_vendor/", format = ["sdist", "wheel"] },
17
17
  ]
18
18
 
19
- [tool.black]
20
- target-version = ["py312"]
19
+ [tool.poetry.group.dev.dependencies]
20
+ ruff = "^0.4.4"
21
+
22
+ [tool.ruff]
23
+ src = ["trainerbase"]
21
24
  line-length = 120
22
25
 
23
- [tool.isort]
24
- lines_after_imports = 2
25
- skip = "trainerbase/template/"
26
+ [tool.ruff.lint]
27
+ select = [
28
+ "E", # pycodestyle
29
+ "F", # Pyflakes
30
+ "UP", # pyupgrade
31
+ "B", # flake8-bugbear
32
+ "SIM", # flake8-simplify
33
+ "I", # isort
34
+ "N", # pep8-naming
35
+ "FIX",
36
+ "FBT",
37
+ "ARG",
38
+ "PTH",
39
+ "ERA",
40
+ "RUF",
41
+ ]
42
+
43
+ [tool.ruff.lint.isort]
44
+ lines-after-imports = 2
45
+
46
+ [tool.ruff.format]
47
+ line-ending = "lf"
26
48
 
27
49
  [tool.poetry.dependencies]
28
50
  python = "^3.12"
29
51
  pymem = "^1.13.1"
30
- dearpygui = "^1.10.1"
52
+ dearpygui = "^1.11.1"
31
53
  keyboard = "^0.13.5"
32
54
  pyttsx3 = "^2.90"
33
55
  pyinjector = "^1.3.0"
34
-
35
- [tool.poetry.group.dev.dependencies]
36
- isort = "^5.13.2"
37
- black = "^23.12.1"
38
- pylint = "^3.0.3"
39
- docutils = "^0.20.1"
40
- pygments = "^2.16.1"
56
+ psutil = "^5.9.8"
41
57
 
42
58
  [build-system]
43
59
  requires = ["poetry-core"]
@@ -1,9 +1,10 @@
1
1
  from abc import ABC, abstractmethod
2
+ from contextlib import suppress
2
3
  from pathlib import Path
3
4
  from subprocess import DEVNULL
4
5
  from subprocess import run as run_executable
5
6
  from tempfile import TemporaryDirectory
6
- from typing import Final, Never, Self, override
7
+ from typing import ClassVar, Final, Never, Self, override
7
8
 
8
9
  from pymem.exception import MemoryWriteError
9
10
 
@@ -17,7 +18,7 @@ type Code = bytes | str
17
18
 
18
19
 
19
20
  class AbstractCodeInjection(ABC):
20
- created_code_injections: list[Self] = []
21
+ created_code_injections: ClassVar[list[Self]] = []
21
22
 
22
23
  def __new__(cls, *_args, **_kwargs):
23
24
  instance = super().__new__(cls)
@@ -43,20 +44,21 @@ class CodeInjection(AbstractCodeInjection):
43
44
  self.address = ensure_address(address)
44
45
  self.code_to_inject = compile_asm(code_to_inject)
45
46
  self.original_code: bytes = b""
47
+ self.injection_point: int = 0
46
48
  self._injected = False
47
49
 
48
50
  @override
49
51
  def inject(self):
50
- injection_point = self.address.resolve()
52
+ self.injection_point = self.address.resolve()
51
53
 
52
- self.original_code = pm.read_bytes(injection_point, len(self.code_to_inject))
53
- pm.write_bytes(injection_point, self.code_to_inject, len(self.code_to_inject))
54
+ self.original_code = pm.read_bytes(self.injection_point, len(self.code_to_inject))
55
+ pm.write_bytes(self.injection_point, self.code_to_inject, len(self.code_to_inject))
54
56
  self._injected = True
55
57
 
56
58
  @override
57
59
  def eject(self):
58
60
  if self._injected:
59
- pm.write_bytes(self.address.resolve(), self.original_code, len(self.original_code))
61
+ pm.write_bytes(self.injection_point, self.original_code, len(self.original_code))
60
62
  self._injected = False
61
63
 
62
64
 
@@ -89,24 +91,25 @@ class AllocatingCodeInjection(AbstractCodeInjection): # pylint: disable=too-man
89
91
  self.original_code_length = original_code_length
90
92
  self.new_memory_size = new_memory_size
91
93
  self.new_memory_address: int = 0
94
+ self.injection_point: int = 0
92
95
 
93
96
  @override
94
97
  def inject(self):
95
- injection_point = self.address.resolve()
98
+ self.injection_point = self.address.resolve()
96
99
 
97
100
  self.new_memory_address: int = pm.allocate(self.new_memory_size)
98
101
 
99
- self.original_code = pm.read_bytes(injection_point, self.original_code_length)
102
+ self.original_code = pm.read_bytes(self.injection_point, self.original_code_length)
100
103
 
101
104
  if pm.is_64_bit:
102
105
  jump_to_new_mem = compile_asm(get_long_x64_jump_to_new_mem_code(self.new_memory_address))
103
106
  jumper_length = len(jump_to_new_mem)
104
- jump_back_address = injection_point + jumper_length - 1
107
+ jump_back_address = self.injection_point + jumper_length - 1
105
108
  jump_back = compile_asm(get_long_x64_jump_back_code(jump_back_address))
106
109
  else:
107
- jump_to_new_mem = compile_asm(get_simple_jump_code(self.new_memory_address, injection_point))
110
+ jump_to_new_mem = compile_asm(get_simple_jump_code(self.new_memory_address, self.injection_point))
108
111
  jumper_length = len(jump_to_new_mem)
109
- jump_back_address = injection_point + jumper_length
112
+ jump_back_address = self.injection_point + jumper_length
110
113
  jump_back = compile_asm(
111
114
  get_simple_jump_code(jump_back_address, self.new_memory_address + len(self.code_to_inject)),
112
115
  )
@@ -119,14 +122,15 @@ class AllocatingCodeInjection(AbstractCodeInjection): # pylint: disable=too-man
119
122
  code_to_inject = self.code_to_inject + jump_back
120
123
 
121
124
  pm.write_bytes(self.new_memory_address, code_to_inject, len(code_to_inject))
122
- pm.write_bytes(injection_point, jump_to_new_mem, len(jump_to_new_mem))
125
+ pm.write_bytes(self.injection_point, jump_to_new_mem, len(jump_to_new_mem))
123
126
 
124
127
  @override
125
128
  def eject(self):
126
- if self.new_memory_address:
127
- pm.write_bytes(self.address.resolve(), self.original_code, self.original_code_length)
129
+ if self.new_memory_address and self.injection_point:
130
+ pm.write_bytes(self.injection_point, self.original_code, self.original_code_length)
128
131
  pm.free(self.new_memory_address)
129
132
  self.new_memory_address = 0
133
+ self.injection_point = 0
130
134
 
131
135
 
132
136
  class MultipleCodeInjection(AbstractCodeInjection):
@@ -186,8 +190,6 @@ def get_long_x64_jump_back_code(address: int) -> str:
186
190
 
187
191
 
188
192
  def safely_eject_all_code_injections() -> None:
189
- try:
190
- for injection in AbstractCodeInjection.created_code_injections:
193
+ for injection in AbstractCodeInjection.created_code_injections:
194
+ with suppress(MemoryWriteError):
191
195
  injection.eject()
192
- except MemoryWriteError: # Game process exited
193
- return
@@ -2,7 +2,7 @@ from collections.abc import Iterator, Sequence
2
2
  from uuid import uuid4
3
3
 
4
4
  from trainerbase.gameobject import AbstractReadableObject, GameObject
5
- from trainerbase.memory import POINTER_SIZE, Address, allocate_pointer, pm
5
+ from trainerbase.memory import POINTER_SIZE, Address, allocate, pm
6
6
 
7
7
 
8
8
  type Number = int | float
@@ -24,13 +24,13 @@ class ASMArrayManager(Sequence):
24
24
  self.offsets = [] if offsets is None else offsets
25
25
  self.add = add
26
26
 
27
- self.array_address = pm.allocate(array_length * element_size)
28
- self.array_index_pointer = allocate_pointer()
27
+ self.array_address = allocate(element_size, array_length)
28
+ self.array_index_pointer = allocate()
29
29
 
30
30
  def __len__(self) -> int:
31
31
  return self.array_length
32
32
 
33
- def __getitem__(self, index: int) -> Address:
33
+ def __getitem__(self, index: int) -> Address: # type: ignore TODO: Support slices
34
34
  element_address = self.get_pointer_to_element(index)
35
35
  target_address = Address(element_address, self.offsets, self.add)
36
36
 
@@ -84,6 +84,9 @@ class Teleport:
84
84
  return self.player_x.value, self.player_y.value, self.player_z.value
85
85
 
86
86
  def goto(self, label: str) -> None:
87
+ if label not in self.labels:
88
+ return
89
+
87
90
  self.set_coords(*self.labels[label])
88
91
 
89
92
  def save_position(self) -> None:
@@ -1,4 +1,5 @@
1
1
  from pathlib import Path
2
+
2
3
  from tomllib import load as load_toml
3
4
 
4
5
 
@@ -1,5 +1,8 @@
1
1
  from abc import ABC, abstractmethod
2
- from typing import Self, override
2
+ from collections.abc import Callable
3
+ from contextlib import suppress
4
+ from ctypes import c_bool, c_byte, c_double, c_float, c_int, c_longlong, c_short, sizeof
5
+ from typing import ClassVar, Self, cast, override
3
6
 
4
7
  from pymem.exception import MemoryWriteError
5
8
 
@@ -15,22 +18,24 @@ class AbstractReadableObject[T](ABC):
15
18
 
16
19
 
17
20
  class GameObject[PymemType, TrainerBaseType](AbstractReadableObject[TrainerBaseType]):
18
- tracked_objects: list[Self] = []
19
- value_range: None | tuple[TrainerBaseType, TrainerBaseType] = None
21
+ tracked_objects: ClassVar[list[Self]] = []
22
+ value_range: tuple[TrainerBaseType, TrainerBaseType]
23
+ ctype_size: int
20
24
 
21
25
  @staticmethod
22
26
  @abstractmethod
23
- def pm_read(address: int) -> PymemType:
27
+ def pm_read(address: int, /) -> PymemType:
24
28
  pass
25
29
 
26
30
  @staticmethod
27
31
  @abstractmethod
28
- def pm_write(address: int, value: PymemType) -> None:
32
+ def pm_write(address: int, value: PymemType, /) -> None:
29
33
  pass
30
34
 
31
35
  def __init__(
32
36
  self,
33
37
  address: ConvertibleToAddress,
38
+ *,
34
39
  frozen: TrainerBaseType | None = None,
35
40
  is_tracked: bool = True,
36
41
  value_range: None | tuple[TrainerBaseType, TrainerBaseType] = None,
@@ -55,10 +60,10 @@ class GameObject[PymemType, TrainerBaseType](AbstractReadableObject[TrainerBaseT
55
60
  )
56
61
 
57
62
  def after_read(self, value: PymemType) -> TrainerBaseType:
58
- return value # type: ignore
63
+ return cast(TrainerBaseType, value)
59
64
 
60
65
  def before_write(self, value: TrainerBaseType) -> PymemType:
61
- return value # type: ignore
66
+ return cast(PymemType, value)
62
67
 
63
68
  def validate_range(self, value: TrainerBaseType) -> bool:
64
69
  if self.value_range is None:
@@ -69,7 +74,7 @@ class GameObject[PymemType, TrainerBaseType](AbstractReadableObject[TrainerBaseT
69
74
 
70
75
  min_value, max_value = self.value_range
71
76
 
72
- return min_value <= value <= max_value # type: ignore
77
+ return cast(int | float, min_value) <= value <= cast(int | float, max_value)
73
78
 
74
79
  @property
75
80
  @override
@@ -85,8 +90,9 @@ class GameObject[PymemType, TrainerBaseType](AbstractReadableObject[TrainerBaseT
85
90
 
86
91
 
87
92
  class GameFloat(GameObject[float, float]):
88
- value_range: tuple[float, float] = (-3.4e38, 3.4e38)
89
- pm_read = pm.read_float # type: ignore
93
+ value_range = (-3.4e38, 3.4e38)
94
+ ctype_size = sizeof(c_float)
95
+ pm_read = cast(Callable[[int], float], pm.read_float)
90
96
  pm_write = pm.write_float
91
97
 
92
98
  @override
@@ -95,8 +101,9 @@ class GameFloat(GameObject[float, float]):
95
101
 
96
102
 
97
103
  class GameDouble(GameObject[float, float]):
98
- value_range: tuple[float, float] = (-1.7e308, 1.7e308)
99
- pm_read = pm.read_double # type: ignore
104
+ value_range = (-1.7e308, 1.7e308)
105
+ ctype_size = sizeof(c_double)
106
+ pm_read = cast(Callable[[int], float], pm.read_double)
100
107
  pm_write = pm.write_double
101
108
 
102
109
  @override
@@ -105,7 +112,8 @@ class GameDouble(GameObject[float, float]):
105
112
 
106
113
 
107
114
  class GameByte(GameObject[bytes, int]):
108
- value_range: tuple[int, int] = (0, 255)
115
+ value_range = (0, 255)
116
+ ctype_size = sizeof(c_byte)
109
117
 
110
118
  @staticmethod
111
119
  @override
@@ -127,51 +135,58 @@ class GameByte(GameObject[bytes, int]):
127
135
 
128
136
 
129
137
  class GameInt(GameObject[int, int]):
130
- value_range: tuple[int, int] = (-2_147_483_648, 2_147_483_647)
138
+ value_range = (-2_147_483_648, 2_147_483_647)
139
+ ctype_size = sizeof(c_int)
131
140
 
132
- pm_read = pm.read_int # type: ignore
141
+ pm_read = cast(Callable[[int], int], pm.read_int)
133
142
  pm_write = pm.write_int
134
143
 
135
144
 
136
145
  class GameShort(GameObject[int, int]):
137
- value_range: tuple[int, int] = (-32_768, 32_767)
146
+ value_range = (-32_768, 32_767)
147
+ ctype_size = sizeof(c_short)
138
148
 
139
- pm_read = pm.read_short # type: ignore
149
+ pm_read = cast(Callable[[int], int], pm.read_short)
140
150
  pm_write = pm.write_short
141
151
 
142
152
 
143
153
  class GameLongLong(GameObject[int, int]):
144
- value_range: tuple[int, int] = (-(2**63), (2**63) - 1)
154
+ value_range = (-(2**63), (2**63) - 1)
155
+ ctype_size = sizeof(c_longlong)
145
156
 
146
- pm_read = pm.read_longlong # type: ignore
157
+ pm_read = cast(Callable[[int], int], pm.read_longlong)
147
158
  pm_write = pm.write_longlong
148
159
 
149
160
 
150
161
  class GameUnsignedInt(GameObject[int, int]):
151
- value_range: tuple[int, int] = (0, 4_294_967_295)
162
+ value_range = (0, 4_294_967_295)
163
+ ctype_size = sizeof(c_int)
152
164
 
153
- pm_read = pm.read_uint # type: ignore
165
+ pm_read = cast(Callable[[int], int], pm.read_uint)
154
166
  pm_write = pm.write_uint
155
167
 
156
168
 
157
169
  class GameUnsignedShort(GameObject[int, int]):
158
- value_range: tuple[int, int] = (0, 65_535)
170
+ value_range = (0, 65_535)
171
+ ctype_size = sizeof(c_short)
159
172
 
160
- pm_read = pm.read_ushort # type: ignore
173
+ pm_read = cast(Callable[[int], int], pm.read_ushort)
161
174
  pm_write = pm.write_ushort
162
175
 
163
176
 
164
177
  class GameUnsignedLongLong(GameObject[int, int]):
165
- value_range: tuple[int, int] = (0, 18_446_744_073_709_551_615)
178
+ value_range = (0, 18_446_744_073_709_551_615)
179
+ ctype_size = sizeof(c_longlong)
166
180
 
167
- pm_read = pm.read_ulonglong # type: ignore
181
+ pm_read = cast(Callable[[int], int], pm.read_ulonglong)
168
182
  pm_write = pm.write_ulonglong
169
183
 
170
184
 
171
185
  class GameBool(GameObject[bool, bool], Switchable):
172
- value_range: tuple[bool, bool] = (False, True)
186
+ value_range = (False, True)
187
+ ctype_size = sizeof(c_bool)
173
188
 
174
- pm_read = pm.read_bool # type: ignore
189
+ pm_read = cast(Callable[[int], bool], pm.read_bool)
175
190
  pm_write = pm.write_bool
176
191
 
177
192
  @override
@@ -194,8 +209,8 @@ class ReadonlyGameObjectSumGetter(AbstractReadableObject[int | float]):
194
209
 
195
210
  def update_frozen_objects():
196
211
  for game_object in GameObject.tracked_objects:
197
- if game_object.frozen is not None:
198
- try:
199
- game_object.value = game_object.frozen
200
- except (MemoryWriteError, ValueError):
201
- continue
212
+ if game_object.frozen is None:
213
+ continue
214
+
215
+ with suppress(MemoryWriteError, ValueError):
216
+ game_object.value = game_object.frozen
File without changes
@@ -18,6 +18,7 @@ class CodeInjectionUI(AbstractUIComponent):
18
18
  injection: AbstractCodeInjection,
19
19
  label: str,
20
20
  hotkey: str | None = None,
21
+ *,
21
22
  tts_on_hotkey: bool = True,
22
23
  ):
23
24
  self.injection = injection
@@ -36,10 +37,7 @@ class CodeInjectionUI(AbstractUIComponent):
36
37
  dpg.add_checkbox(label=self.label_with_hotkey, tag=self.dpg_tag, callback=self.on_codeinjection_state_change)
37
38
 
38
39
  def on_codeinjection_state_change(self):
39
- if dpg.get_value(self.dpg_tag):
40
- change_codeinjection_state = self.injection.inject
41
- else:
42
- change_codeinjection_state = self.injection.eject
40
+ change_codeinjection_state = self.injection.inject if dpg.get_value(self.dpg_tag) else self.injection.eject
43
41
 
44
42
  try:
45
43
  change_codeinjection_state()
@@ -1,5 +1,6 @@
1
1
  from collections.abc import Callable
2
- from typing import Any, Self, override
2
+ from contextlib import suppress
3
+ from typing import Any, ClassVar, Self, override
3
4
  from uuid import uuid4
4
5
 
5
6
  from dearpygui import dearpygui as dpg
@@ -29,7 +30,7 @@ class GameObjectUI(AbstractUIComponent):
29
30
  DPG_TAG_POSTFIX_GETTER = "__getter"
30
31
  DPG_TAG_POSTFIX_SETTER = "__setter"
31
32
 
32
- displayed_objects: list[Self] = []
33
+ displayed_objects: ClassVar[list[Self]] = []
33
34
 
34
35
  def __init__(
35
36
  self,
@@ -37,6 +38,7 @@ class GameObjectUI(AbstractUIComponent):
37
38
  label: str,
38
39
  freeze_hotkey: str | None = None,
39
40
  set_hotkey: str | None = None,
41
+ *,
40
42
  default_setter_input_value: Any = 0,
41
43
  before_set: Callable | None = None,
42
44
  tts_on_hotkey: bool = True,
@@ -77,10 +79,7 @@ class GameObjectUI(AbstractUIComponent):
77
79
 
78
80
  self.add_setter_input()
79
81
 
80
- if self.set_hotkey is None:
81
- setter_button_text = "Set"
82
- else:
83
- setter_button_text = f"[{self.set_hotkey}] Set"
82
+ setter_button_text = "Set" if self.set_hotkey is None else f"[{self.set_hotkey}] Set"
84
83
 
85
84
  dpg.add_button(label=setter_button_text, callback=self.on_value_set)
86
85
 
@@ -117,12 +116,12 @@ class GameObjectUI(AbstractUIComponent):
117
116
  # There is no input for integers that are not simple `signed long int`.
118
117
  # TODO: Use better input component if it's already added to dpg. Remove this crutch.
119
118
 
120
- min_value = default_kwargs.pop("min_value", GameInt.value_range[0])
121
- max_value = default_kwargs.pop("max_value", GameInt.value_range[1])
119
+ min_value = default_kwargs.pop("min_value", GameInt.value_range[0]) # type: ignore
120
+ max_value = default_kwargs.pop("max_value", GameInt.value_range[1]) # type: ignore
122
121
 
123
122
  if self.gameobject.value_range is not None:
124
- min_value = max(min_value, GameInt.value_range[0])
125
- max_value = min(max_value, GameInt.value_range[1])
123
+ min_value = max(min_value, GameInt.value_range[0]) # type: ignore
124
+ max_value = min(max_value, GameInt.value_range[1]) # type: ignore
126
125
 
127
126
  dpg.add_input_int(min_value=min_value, max_value=max_value, **default_kwargs)
128
127
  case GameBool():
@@ -137,17 +136,15 @@ class GameObjectUI(AbstractUIComponent):
137
136
  try:
138
137
  self.gameobject.frozen = self.gameobject.value if dpg.get_value(self.dpg_tag_frozen) else None
139
138
  except MemoryReadError:
140
- dpg.set_value(self.dpg_tag_frozen, False)
139
+ dpg.set_value(self.dpg_tag_frozen, value=False)
141
140
 
142
141
  def on_value_set(self):
143
142
  raw_new_value = dpg.get_value(self.dpg_tag_setter)
144
143
  new_value = raw_new_value if self.before_set is None else self.before_set(raw_new_value)
145
144
 
146
145
  if self.gameobject.frozen is None:
147
- try:
146
+ with suppress(MemoryWriteError, ValueError):
148
147
  self.gameobject.value = new_value
149
- except (MemoryWriteError, ValueError):
150
- pass
151
148
  else:
152
149
  self.gameobject.frozen = new_value
153
150
 
@@ -17,6 +17,7 @@ class ScriptUI(AbstractUIComponent):
17
17
  script: Script,
18
18
  label: str,
19
19
  hotkey: str | None = None,
20
+ *,
20
21
  tts_on_hotkey: bool = True,
21
22
  ):
22
23
  self.script = script
@@ -12,6 +12,7 @@ from trainerbase.tts import say
12
12
 
13
13
  class TeleportUI(AbstractUIComponent):
14
14
  DPG_TAG_TELEPORT_LABELS = "__teleport_labels"
15
+ DPG_TAG_TELEPORT_LABEL_FILTER_INPUT = "__dpg_tag_teleport_label_filter_input"
15
16
 
16
17
  def __init__(
17
18
  self,
@@ -19,6 +20,7 @@ class TeleportUI(AbstractUIComponent):
19
20
  hotkey_save_position: str | None = "Insert",
20
21
  hotkey_set_saved_position: str | None = "Home",
21
22
  hotkey_dash: str | None = "End",
23
+ *,
22
24
  tts_on_hotkey: bool = True,
23
25
  ):
24
26
  self.tp = tp
@@ -87,8 +89,26 @@ class TeleportUI(AbstractUIComponent):
87
89
  if not self.tp.labels:
88
90
  return
89
91
 
90
- labels = sorted(self.tp.labels.keys())
92
+ dpg.add_input_text(
93
+ label="Search",
94
+ tag=self.DPG_TAG_TELEPORT_LABEL_FILTER_INPUT,
95
+ callback=self._on_apply_filter,
96
+ )
91
97
 
92
98
  with dpg.group(horizontal=True):
93
99
  dpg.add_button(label="Go To", callback=self.on_goto_label)
94
- dpg.add_combo(label="Labels", tag=self.DPG_TAG_TELEPORT_LABELS, items=labels, default_value=labels[0])
100
+ dpg.add_combo(label="Labels", tag=self.DPG_TAG_TELEPORT_LABELS)
101
+
102
+ self._on_apply_filter()
103
+
104
+ def _on_apply_filter(self):
105
+ label_name_part = dpg.get_value(self.DPG_TAG_TELEPORT_LABEL_FILTER_INPUT)
106
+ new_labels = {
107
+ label: coords for label, coords in self.tp.labels.items() if label_name_part.lower() in label.lower()
108
+ }
109
+
110
+ new_label_names = sorted(new_labels.keys())
111
+
112
+ default_value = new_label_names[0] if new_label_names else "<No results!>"
113
+
114
+ dpg.configure_item(self.DPG_TAG_TELEPORT_LABELS, items=new_label_names, default_value=default_value)
@@ -6,8 +6,9 @@ from typing import NoReturn
6
6
 
7
7
  from trainerbase.codeinjection import safely_eject_all_code_injections
8
8
  from trainerbase.gameobject import update_frozen_objects
9
- from trainerbase.gui import update_displayed_objects
10
- from trainerbase.scriptengine import Script, system_script_engine
9
+ from trainerbase.gui.objects import update_displayed_objects
10
+ from trainerbase.process import show_message_and_shutdown_if_process_exited
11
+ from trainerbase.scriptengine import Script, process_healthcheck_script_engine, system_script_engine
11
12
  from trainerbase.speedhack import SpeedHack
12
13
 
13
14
 
@@ -18,6 +19,7 @@ def run( # pylint: disable=too-complex
18
19
  ) -> NoReturn:
19
20
  def on_shutdown() -> NoReturn:
20
21
  system_script_engine.stop()
22
+ process_healthcheck_script_engine.stop()
21
23
  safely_eject_all_code_injections()
22
24
  SpeedHack.disable()
23
25
 
@@ -28,20 +30,22 @@ def run( # pylint: disable=too-complex
28
30
 
29
31
  def on_gui_initialized() -> None:
30
32
  system_script_engine.start()
33
+ process_healthcheck_script_engine.start()
31
34
 
32
35
  if on_gui_initialized_hook is not None:
33
36
  try:
34
37
  on_gui_initialized_hook()
35
- except: # pylint: disable=bare-except
38
+ except Exception:
36
39
  print(format_exc(), file=stderr)
37
40
  on_shutdown()
38
41
 
39
42
  system_script_engine.register_script(Script(update_frozen_objects, enabled=True))
40
43
  system_script_engine.register_script(Script(update_displayed_objects, enabled=True))
44
+ process_healthcheck_script_engine.register_script(Script(show_message_and_shutdown_if_process_exited, enabled=True))
41
45
 
42
46
  try:
43
47
  run_gui(on_gui_initialized)
44
- except: # pylint: disable=bare-except
48
+ except Exception:
45
49
  print(format_exc(), file=stderr)
46
50
 
47
51
  on_shutdown()
@@ -1,24 +1,13 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from collections.abc import Callable
3
3
  from functools import partial
4
- from typing import Final, Self, assert_never, override
4
+ from types import MappingProxyType
5
+ from typing import Final, Self, assert_never, cast, override
5
6
 
6
- from pymem import Pymem
7
- from pymem.exception import ProcessNotFound
8
7
  from pymem.pattern import pattern_scan_all, pattern_scan_module
9
8
  from pymem.process import module_from_name
10
9
 
11
- from trainerbase.config import pymem_config
12
-
13
-
14
- for process_name in pymem_config["process_names"]:
15
- try:
16
- pm = Pymem(process_name, exact_match=pymem_config["exact_match"], ignore_case=pymem_config["ignore_case"])
17
- except ProcessNotFound:
18
- continue
19
- break
20
- else:
21
- raise ProcessNotFound(f"not any: {pymem_config['process_names']}")
10
+ from trainerbase.process import _pm as pm
22
11
 
23
12
 
24
13
  ARCH: Final[int] = 64 if pm.is_64_bit else 32
@@ -28,7 +17,7 @@ POINTER_SIZE: Final[int] = 8 if pm.is_64_bit else 4
28
17
  type ConvertibleToAddress = AbstractAddress | int | bytes | str
29
18
 
30
19
 
31
- read_pointer: Callable[[int], int] = pm.read_ulonglong if pm.is_64_bit else pm.read_uint # type: ignore
20
+ read_pointer = cast(Callable[[int], int], pm.read_ulonglong if pm.is_64_bit else pm.read_uint)
32
21
 
33
22
 
34
23
  class AbstractAddress(ABC):
@@ -39,7 +28,7 @@ class AbstractAddress(ABC):
39
28
 
40
29
  class AOBScan(AbstractAddress):
41
30
  # Constant from re. Like re._special_chars_map, but without dot (`.`).
42
- _SPECIAL_CHARS_MAP = {i: "\\" + chr(i) for i in b"()[]{}?*+-|^$\\&~# \t\n\r\v\f"}
31
+ _SPECIAL_CHARS_MAP = MappingProxyType({i: "\\" + chr(i) for i in b"()[]{}?*+-|^$\\&~# \t\n\r\v\f"})
43
32
 
44
33
  def __init__(
45
34
  self,
@@ -93,7 +82,7 @@ class AOBScan(AbstractAddress):
93
82
  raise ValueError(f"AOB not found: {self.aob}")
94
83
 
95
84
  if isinstance(scan_result, list):
96
- result_address = scan_result[self.multiple_result_index] # type: ignore
85
+ result_address = scan_result[cast(int, self.multiple_result_index)]
97
86
  else:
98
87
  result_address = scan_result
99
88
 
@@ -177,5 +166,18 @@ def ensure_address(obj: ConvertibleToAddress) -> AbstractAddress:
177
166
  raise TypeError(f"Cannot create AbstractAddress from {type(obj)}")
178
167
 
179
168
 
180
- def allocate_pointer(size: int = 1) -> int:
181
- return pm.allocate(size * POINTER_SIZE)
169
+ def allocate(size: int = POINTER_SIZE, count: int = 1) -> int:
170
+ return pm.allocate(size * count)
171
+
172
+
173
+ def get_module_address(module_name: str) -> int:
174
+ """
175
+ Returns 0 if module is not found else module address
176
+ """
177
+
178
+ module_info = module_from_name(pm.process_handle, module_name)
179
+
180
+ if module_info is None:
181
+ return 0
182
+
183
+ return module_info.lpBaseOfDll
@@ -0,0 +1,49 @@
1
+ from itertools import cycle
2
+ from os import _exit as force_exit
3
+ from typing import cast
4
+
5
+ from psutil import pid_exists
6
+ from pymem import Pymem
7
+ from pymem.exception import ProcessNotFound
8
+ from win32api import MessageBox
9
+ from win32con import MB_ICONERROR, MB_OK
10
+
11
+ from trainerbase.config import pymem_config
12
+
13
+
14
+ def attach_to_process() -> Pymem:
15
+ process_names = pymem_config["process_names"]
16
+
17
+ if not process_names:
18
+ raise ValueError("Empty process name list")
19
+
20
+ print("Waiting for process in:", process_names)
21
+
22
+ for process_name in cycle(process_names):
23
+ try:
24
+ pm = Pymem(process_name, exact_match=pymem_config["exact_match"], ignore_case=pymem_config["ignore_case"])
25
+ except ProcessNotFound:
26
+ continue
27
+
28
+ print(f"Found {process_name}, pid: {pm.process_id}")
29
+
30
+ return pm
31
+
32
+ raise RuntimeError("Code should be unreachable")
33
+
34
+
35
+ def show_message_and_shutdown_if_process_exited():
36
+ if pid_exists(cast(int, _pm.process_id)):
37
+ return
38
+
39
+ MessageBox(
40
+ None,
41
+ "Shut down trainer?",
42
+ "Process Exited!",
43
+ MB_ICONERROR | MB_OK,
44
+ )
45
+
46
+ force_exit(0)
47
+
48
+
49
+ _pm = attach_to_process()
@@ -7,7 +7,7 @@ from pymem.exception import MemoryReadError, MemoryWriteError
7
7
 
8
8
 
9
9
  class Script:
10
- def __init__(self, callback: Callable, enabled: bool = False):
10
+ def __init__(self, callback: Callable, *, enabled: bool = False):
11
11
  self.callback = callback
12
12
  self.enabled = enabled
13
13
 
@@ -68,3 +68,4 @@ def enabled_by_default(script: Script) -> Script:
68
68
 
69
69
 
70
70
  system_script_engine = ScriptEngine()
71
+ process_healthcheck_script_engine = ScriptEngine(delay=3)
@@ -1,9 +1,11 @@
1
+ from contextlib import suppress
1
2
  from pathlib import Path
2
- from typing import Final, Self
3
+ from typing import Final, Self, cast
3
4
 
4
5
  from pyinjector import inject
5
6
  from pymem.exception import MemoryWriteError
6
7
  from pymem.process import module_from_name
8
+ from pymem.ressources.structure import MODULEINFO
7
9
 
8
10
  from trainerbase.gameobject import GameDouble
9
11
  from trainerbase.memory import ARCH, pm
@@ -32,9 +34,9 @@ class SpeedHack:
32
34
  self.factor = 1.0
33
35
 
34
36
  def _inject(self) -> int:
35
- inject(pm.process_id, str(SPEEDHACK_DLL_PATH)) # type: ignore
37
+ inject(cast(int, pm.process_id), str(SPEEDHACK_DLL_PATH))
36
38
 
37
- speedhack_address = module_from_name(pm.process_handle, SPEEDHACK_DLL_MODULE_NAME).lpBaseOfDll # type: ignore
39
+ speedhack_address = cast(MODULEINFO, module_from_name(pm.process_handle, SPEEDHACK_DLL_MODULE_NAME)).lpBaseOfDll
38
40
 
39
41
  return speedhack_address
40
42
 
@@ -51,7 +53,5 @@ class SpeedHack:
51
53
  if cls._instance is None:
52
54
  return
53
55
 
54
- try:
56
+ with suppress(MemoryWriteError):
55
57
  cls._instance.factor = 1.0
56
- except MemoryWriteError:
57
- pass
@@ -1,3 +1,4 @@
1
+ from contextlib import suppress
1
2
  from threading import Thread
2
3
 
3
4
  from pyttsx3 import init as init_tts_engine
@@ -6,7 +7,7 @@ from pyttsx3 import init as init_tts_engine
6
7
  engine = init_tts_engine()
7
8
 
8
9
 
9
- def say(text: str, rate: int = 210, volume: float = 1.0, sync=False):
10
+ def say(text: str, rate: int = 210, volume: float = 1.0, *, sync=False):
10
11
  args = (text, rate, volume)
11
12
 
12
13
  if sync:
@@ -18,16 +19,12 @@ def say(text: str, rate: int = 210, volume: float = 1.0, sync=False):
18
19
  def say_sync(text: str, rate: int, volume: float):
19
20
  engine.stop()
20
21
 
21
- try:
22
+ with suppress(RuntimeError):
22
23
  engine.endLoop()
23
- except RuntimeError:
24
- pass
25
24
 
26
25
  engine.setProperty("rate", rate)
27
26
  engine.setProperty("volume", volume)
28
27
  engine.say(text)
29
28
 
30
- try:
29
+ with suppress(RuntimeError):
31
30
  engine.runAndWait()
32
- except RuntimeError:
33
- pass
@@ -1,44 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: trainerbase
3
- Version: 3.11.4
4
- Summary: Framework for creating game trainers/mods
5
- Home-page: https://gitlab.com/python-trainers/trainer-base
6
- License: MIT
7
- Author: v01d
8
- Author-email: v01d@v01d.ru
9
- Requires-Python: >=3.12,<4.0
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.12
13
- Requires-Dist: dearpygui (>=1.10.1,<2.0.0)
14
- Requires-Dist: keyboard (>=0.13.5,<0.14.0)
15
- Requires-Dist: pyinjector (>=1.3.0,<2.0.0)
16
- Requires-Dist: pymem (>=1.13.1,<2.0.0)
17
- Requires-Dist: pyttsx3 (>=2.90,<3.0)
18
- Project-URL: Repository, https://gitlab.com/python-trainers/trainer-base
19
- Description-Content-Type: text/x-rst
20
-
21
- ============
22
- Trainer Base
23
- ============
24
-
25
- .. image:: https://badge.fury.io/py/TrainerBase.svg
26
- :target: https://badge.fury.io/py/TrainerBase
27
- :alt: PyPI version
28
-
29
- .. image:: https://static.pepy.tech/badge/trainerbase
30
- :target: https://pepy.tech/project/trainerbase
31
- :alt: Downloads
32
-
33
- To create project from template using cookiecutter
34
- ==================================================
35
-
36
- .. code-block:: bash
37
-
38
- cookiecutter https://gitlab.com/v01d-gl/basic-project-template.git --checkout trainerbase
39
-
40
- Examples
41
- ========
42
-
43
- `Python Trainers <https://gitlab.com/python-trainers>`__
44
-
@@ -1,23 +0,0 @@
1
- ============
2
- Trainer Base
3
- ============
4
-
5
- .. image:: https://badge.fury.io/py/TrainerBase.svg
6
- :target: https://badge.fury.io/py/TrainerBase
7
- :alt: PyPI version
8
-
9
- .. image:: https://static.pepy.tech/badge/trainerbase
10
- :target: https://pepy.tech/project/trainerbase
11
- :alt: Downloads
12
-
13
- To create project from template using cookiecutter
14
- ==================================================
15
-
16
- .. code-block:: bash
17
-
18
- cookiecutter https://gitlab.com/v01d-gl/basic-project-template.git --checkout trainerbase
19
-
20
- Examples
21
- ========
22
-
23
- `Python Trainers <https://gitlab.com/python-trainers>`__
@@ -1,8 +0,0 @@
1
- from .helpers import *
2
- from .injections import *
3
- from .misc import *
4
- from .objects import *
5
- from .scripts import *
6
- from .speedhack import *
7
- from .teleport import *
8
- from .types import *
File without changes