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.
- trainerbase-3.12.0/PKG-INFO +36 -0
- trainerbase-3.12.0/README.md +14 -0
- {trainerbase-3.11.4 → trainerbase-3.12.0}/pyproject.toml +31 -15
- {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/codeinjection.py +20 -18
- {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/common/helpers.py +4 -4
- {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/common/teleport.py +3 -0
- {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/config.py +1 -0
- {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/gameobject.py +47 -32
- trainerbase-3.12.0/trainerbase/gui/__init__.py +0 -0
- {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/gui/injections.py +2 -4
- {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/gui/objects.py +11 -14
- {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/gui/scripts.py +1 -0
- {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/gui/teleport.py +22 -2
- {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/main.py +8 -4
- {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/memory.py +21 -19
- trainerbase-3.12.0/trainerbase/process.py +49 -0
- {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/scriptengine.py +2 -1
- {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/speedhack.py +6 -6
- {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/tts.py +4 -7
- trainerbase-3.11.4/PKG-INFO +0 -44
- trainerbase-3.11.4/README.rst +0 -23
- trainerbase-3.11.4/trainerbase/gui/__init__.py +0 -8
- {trainerbase-3.11.4 → trainerbase-3.12.0}/LICENSE +0 -0
- {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/__init__.py +0 -0
- {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/common/__init__.py +0 -0
- {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/common/abc.py +0 -0
- {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/common/keyboard.py +0 -0
- {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/gui/helpers.py +0 -0
- {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/gui/misc.py +0 -0
- {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/gui/speedhack.py +0 -0
- {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase/gui/types.py +0 -0
- {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase_vendor/FASM.EXE +0 -0
- {trainerbase-3.11.4 → trainerbase-3.12.0}/trainerbase_vendor/speedhack32.dll +0 -0
- {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
|
+
[](https://badge.fury.io/py/TrainerBase)
|
|
25
|
+
[](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
|
+
[](https://badge.fury.io/py/TrainerBase)
|
|
4
|
+
[](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.
|
|
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.
|
|
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.
|
|
20
|
-
|
|
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.
|
|
24
|
-
|
|
25
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
190
|
-
|
|
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,
|
|
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 =
|
|
28
|
-
self.array_index_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
|
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from
|
|
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:
|
|
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
|
|
63
|
+
return cast(TrainerBaseType, value)
|
|
59
64
|
|
|
60
65
|
def before_write(self, value: TrainerBaseType) -> PymemType:
|
|
61
|
-
return value
|
|
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 <=
|
|
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
|
|
89
|
-
|
|
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
|
|
99
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
146
|
+
value_range = (-32_768, 32_767)
|
|
147
|
+
ctype_size = sizeof(c_short)
|
|
138
148
|
|
|
139
|
-
pm_read = pm.read_short
|
|
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
|
|
154
|
+
value_range = (-(2**63), (2**63) - 1)
|
|
155
|
+
ctype_size = sizeof(c_longlong)
|
|
145
156
|
|
|
146
|
-
pm_read = pm.read_longlong
|
|
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
|
|
162
|
+
value_range = (0, 4_294_967_295)
|
|
163
|
+
ctype_size = sizeof(c_int)
|
|
152
164
|
|
|
153
|
-
pm_read = pm.read_uint
|
|
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
|
|
170
|
+
value_range = (0, 65_535)
|
|
171
|
+
ctype_size = sizeof(c_short)
|
|
159
172
|
|
|
160
|
-
pm_read = pm.read_ushort
|
|
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
|
|
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
|
|
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
|
|
186
|
+
value_range = (False, True)
|
|
187
|
+
ctype_size = sizeof(c_bool)
|
|
173
188
|
|
|
174
|
-
pm_read = pm.read_bool
|
|
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
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
|
@@ -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
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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]
|
|
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
|
|
181
|
-
return pm.allocate(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))
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
29
|
+
with suppress(RuntimeError):
|
|
31
30
|
engine.runAndWait()
|
|
32
|
-
except RuntimeError:
|
|
33
|
-
pass
|
trainerbase-3.11.4/PKG-INFO
DELETED
|
@@ -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
|
-
|
trainerbase-3.11.4/README.rst
DELETED
|
@@ -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>`__
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|