PyMemoryEditor 1.5.7__tar.gz → 1.5.8__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 (53) hide show
  1. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PKG-INFO +6 -3
  2. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/__init__.py +1 -1
  3. pymemoryeditor-1.5.8/PyMemoryEditor/errors.py +6 -0
  4. pymemoryeditor-1.5.8/PyMemoryEditor/sample/application.py +15 -0
  5. pymemoryeditor-1.5.8/PyMemoryEditor/sample/main_application_window.py +443 -0
  6. pymemoryeditor-1.5.8/PyMemoryEditor/sample/open_process_window.py +56 -0
  7. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/util/scan.py +3 -3
  8. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/win32/functions.py +1 -1
  9. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/win32/process.py +4 -2
  10. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/README.md +4 -1
  11. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/pyproject.toml +5 -2
  12. pymemoryeditor-1.5.7/PyMemoryEditor/errors.py +0 -3
  13. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/.github/workflows/python-package.yml +0 -0
  14. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/.gitignore +0 -0
  15. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/LICENSE +0 -0
  16. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/enums.py +0 -0
  17. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/linux/functions.py +0 -0
  18. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/linux/process.py +0 -0
  19. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/linux/ptrace/__init__.py +0 -0
  20. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/linux/ptrace/enums.py +0 -0
  21. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/linux/types.py +0 -0
  22. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/process/__init__.py +0 -0
  23. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/process/errors.py +0 -0
  24. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/process/info.py +0 -0
  25. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/process/util.py +0 -0
  26. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/util/__init__.py +0 -0
  27. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/util/search/abstract.py +0 -0
  28. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/util/search/kmp.py +0 -0
  29. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/win32/enums/__init__.py +0 -0
  30. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/win32/enums/memory_allocation_states.py +0 -0
  31. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/win32/enums/memory_protections.py +0 -0
  32. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/win32/enums/memory_types.py +0 -0
  33. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/win32/enums/process_operations.py +0 -0
  34. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/win32/enums/standard_access_rights.py +0 -0
  35. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/win32/types.py +0 -0
  36. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/__init__.py +0 -0
  37. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/open_process.py +0 -0
  38. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/process/__init__.py +0 -0
  39. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/process/errors.py +0 -0
  40. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/process/util.py +0 -0
  41. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/win32/enums/__init__.py +0 -0
  42. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/win32/enums/memory_allocation_states.py +0 -0
  43. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/win32/enums/memory_protections.py +0 -0
  44. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/win32/enums/memory_types.py +0 -0
  45. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/win32/enums/process_operations.py +0 -0
  46. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/win32/enums/scan_types.py +0 -0
  47. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/win32/enums/standard_access_rights.py +0 -0
  48. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/win32/functions.py +0 -0
  49. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/win32/types.py +0 -0
  50. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/win32/util.py +0 -0
  51. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/requirements.txt +0 -0
  52. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/tests/package.py +0 -0
  53. {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/tests/test_editor.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyMemoryEditor
3
- Version: 1.5.7
4
- Summary: Multi-platform library developed with ctypes for reading, writing and searching process memory, in a simple and friendly way with Python 3. It supports Windows and Linux (32-bit and 64-bit).
3
+ Version: 1.5.8
4
+ Summary: Multi-platform library developed with ctypes for reading, writing and searching process memory, in a simple and friendly way with Python 3.
5
5
  Project-URL: Homepage, https://github.com/JeanExtreme002/PyMemoryEditor
6
6
  Author-email: Jean Loui Bernard Silva de Jesus <jeanextreme002@gmail.com>
7
7
  License-Expression: MIT
@@ -110,7 +110,7 @@ for address, info process.search_by_value(..., progress_information = True):
110
110
  print(template.format(address, progress))
111
111
  ```
112
112
 
113
- # Getting memory regions:
113
+ ## Getting memory regions:
114
114
  Use the method `get_memory_regions()` to get the base address, size and more information of all memory regions used by the process.
115
115
 
116
116
  ```py
@@ -119,3 +119,6 @@ for memory_region in process.get_memory_regions():
119
119
  size = memory_region["size"]
120
120
  information = memory_region["struct"]
121
121
  ```
122
+
123
+ # Tkinter application sample:
124
+ You can run a simple tkinter application to scan the memory of a process by typing `pymemoryeditor` at the CLI.
@@ -8,7 +8,7 @@ The package supports Windows and Linux (32-bit and 64-bit).
8
8
  """
9
9
 
10
10
  __author__ = "Jean Loui Bernard Silva de Jesus"
11
- __version__ = "1.5.7"
11
+ __version__ = "1.5.8"
12
12
 
13
13
 
14
14
  from .enums import ScanTypesEnum
@@ -0,0 +1,6 @@
1
+ from .process.errors import ProcessIDNotExistsError, ProcessNotFoundError
2
+
3
+
4
+ class ClosedProcess(Exception):
5
+ def __str__(self):
6
+ return "operation on closed process."
@@ -0,0 +1,15 @@
1
+ from .main_application_window import ApplicationWindow
2
+ from .open_process_window import OpenProcessWindow
3
+
4
+
5
+ def main(*args, **kwargs):
6
+ open_process_window = OpenProcessWindow()
7
+ process = open_process_window.get_process()
8
+
9
+ if process:
10
+ ApplicationWindow(process)
11
+ process.close()
12
+
13
+
14
+ if __name__ == "__main__":
15
+ main()
@@ -0,0 +1,443 @@
1
+ from tkinter import DoubleVar, Frame, Label, Menu, Listbox, Scrollbar, Tk
2
+ from tkinter.ttk import Button, Entry, Menubutton, Progressbar
3
+
4
+ from PyMemoryEditor import ScanTypesEnum
5
+ from PyMemoryEditor.process import AbstractProcess
6
+
7
+
8
+ class ApplicationWindow(Tk):
9
+ """
10
+ Main window.
11
+ """
12
+ __comp_methods = {
13
+ ScanTypesEnum.EXACT_VALUE: lambda x, y: x == y,
14
+ ScanTypesEnum.NOT_EXACT_VALUE: lambda x, y: x != y,
15
+ ScanTypesEnum.BIGGER_THAN: lambda x, y: x > y,
16
+ ScanTypesEnum.SMALLER_THAN: lambda x, y: x < y,
17
+ }
18
+
19
+ def __init__(self, process: AbstractProcess):
20
+ super().__init__()
21
+ self.__process = process
22
+
23
+ self.__scan_type = ScanTypesEnum.EXACT_VALUE
24
+ self.__value = 0
25
+ self.__value_type = int
26
+ self.__value_length = 4
27
+ self.__addresses = []
28
+
29
+ self.__updating = False # Indicate the application is updating the values of the found addresses.
30
+ self.__scanning = False # Indicate a scan has started.
31
+
32
+ self["bg"] = "white"
33
+
34
+ self.title(f"PyMemoryEditor (Sample) - Process ID: {process.pid}")
35
+ self.geometry("900x400")
36
+
37
+ self.protocol("WM_DELETE_WINDOW", self.__on_close)
38
+ self.__close = False
39
+
40
+ self.__addresses = []
41
+
42
+ self.__build()
43
+ self.mainloop()
44
+
45
+ def __build(self):
46
+ """
47
+ Build the widgets of the window.
48
+ """
49
+ self.__entry_register_int = self.register(self.__validate_int_entry)
50
+ self.__entry_register_hex = self.register(self.__validate_hex_entry)
51
+
52
+ self.__input_frame_1 = Frame(self)
53
+ self.__input_frame_1["bg"] = "white"
54
+ self.__input_frame_1.pack(padx=5, fill="x", expand=True)
55
+
56
+ self.__scan_input_frame = Frame(self.__input_frame_1)
57
+ self.__scan_input_frame["bg"] = "white"
58
+ self.__scan_input_frame.pack(fill="x", expand=True)
59
+
60
+ # Value input.
61
+ Label(self.__scan_input_frame, text="Value: ", bg="white", font=("Arial", 12)).pack(side="left")
62
+
63
+ self.__value_entry = Entry(self.__scan_input_frame)
64
+ self.__value_entry.pack(side="left", expand=True, fill="x")
65
+
66
+ Label(self.__scan_input_frame, bg="white").pack(side="left")
67
+
68
+ # Value length.
69
+ Label(self.__scan_input_frame, text="Length (Bytes): ", bg="white", font=("Arial", 12)).pack(side="left")
70
+
71
+ self.__length_entry = Entry(self.__scan_input_frame, width=5)
72
+ self.__length_entry.insert(0, "4")
73
+ self.__length_entry.config(validate="key", validatecommand=(self.__entry_register_int, "%P"))
74
+ self.__length_entry.pack(side="left")
75
+
76
+ Label(self.__scan_input_frame, bg="white").pack(side="left", padx=5)
77
+
78
+ # Value type input.
79
+ self.__type_menu_button = Menubutton(self.__scan_input_frame, width=10)
80
+ self.__type_menu_button.pack(side="left")
81
+
82
+ self.__type_menu = Menu(tearoff=0, bg="white")
83
+ self.__type_menu.add_command(label="Boolean", command=lambda: self.__set_value_type(0))
84
+ self.__type_menu.add_command(label="Integer", command=lambda: self.__set_value_type(1))
85
+ self.__type_menu.add_command(label="Float", command=lambda: self.__set_value_type(2))
86
+ self.__type_menu.add_command(label="String", command=lambda: self.__set_value_type(3))
87
+ self.__type_menu_button.config(menu=self.__type_menu, text = "Integer")
88
+
89
+ Label(self.__scan_input_frame, bg="white").pack(side="left", padx=10)
90
+
91
+ # Scan type input.
92
+ Label(self.__scan_input_frame, text="Scan Type: ", bg="white", font=("Arial", 12)).pack(side="left")
93
+
94
+ self.__scan_menu_button = Menubutton(self.__scan_input_frame, width=15)
95
+ self.__scan_menu_button.pack(side="left")
96
+
97
+ self.__scan_menu = Menu(tearoff=0, bg="white")
98
+ self.__scan_menu.add_command(label="Exact Value", command=lambda: self.__set_scan_type(0))
99
+ self.__scan_menu.add_command(label="Not Exact Value", command=lambda: self.__set_scan_type(1))
100
+ self.__scan_menu.add_command(label="Smaller Than", command=lambda: self.__set_scan_type(2))
101
+ self.__scan_menu.add_command(label="Bigger Than", command=lambda: self.__set_scan_type(3))
102
+ self.__scan_menu_button.config(menu=self.__scan_menu, text = "Exact Value")
103
+
104
+ Label(self.__scan_input_frame, bg="white").pack(side="left", padx=5)
105
+
106
+ # Buttons for scanning.
107
+ self.__new_scan_button = Button(self.__scan_input_frame, text="First Scan", command=self.__new_scan)
108
+ self.__new_scan_button.pack(side="left")
109
+
110
+ Label(self.__scan_input_frame, bg="white").pack(side="left")
111
+
112
+ self.__next_scan_button = Button(self.__scan_input_frame, command=self.__next_scan)
113
+ self.__next_scan_button.pack(side="left")
114
+
115
+ # Scanning process bar.
116
+ self.__progress_var = DoubleVar()
117
+
118
+ self.__progress_bar = Progressbar(self.__input_frame_1, variable=self.__progress_var)
119
+ self.__progress_bar.pack(pady=5, fill="x", expand=True)
120
+
121
+ # Label for counting and button for updating values.
122
+ self.__result_frame = Frame(self)
123
+ self.__result_frame["bg"] = "white"
124
+ self.__result_frame.pack(padx=5, fill="both", expand=True)
125
+
126
+ self.__count_frame = Frame(self.__result_frame)
127
+ self.__count_frame["bg"] = "white"
128
+ self.__count_frame.pack(pady=5, fill="x", expand=True)
129
+
130
+ self.__count_label = Label(self.__count_frame, font=("Arial", 8), bg="white")
131
+ self.__count_label.config(text="Start a new scan to find memory addresses.")
132
+ self.__count_label.pack(side="left")
133
+
134
+ Button(self.__count_frame, text="Update Values", command=self.__update_values).pack(side="right")
135
+
136
+ # List with addresses and their values.
137
+ self.__list_frame = Frame(self.__result_frame)
138
+ self.__list_frame["bg"] = "white"
139
+ self.__list_frame.pack(fill="both", expand=True)
140
+
141
+ self.__scrollbar = Scrollbar(self.__list_frame, orient="vertical", command=self.__on_move_list_box)
142
+
143
+ self.__address_list = Listbox(self.__list_frame, width=20)
144
+ self.__address_list.bind("<MouseWheel>", self.__on_mouse_wheel)
145
+ self.__address_list.bind("<<ListboxSelect>>", self.__select_address)
146
+ self.__address_list.config(yscrollcommand = self.__scrollbar.set)
147
+ self.__address_list.pack(side="left", fill="y")
148
+
149
+ self.__value_list = Listbox(self.__list_frame)
150
+ self.__value_list.bind("<MouseWheel>", self.__on_mouse_wheel)
151
+ self.__value_list.bind("<<ListboxSelect>>", self.__select_value)
152
+ self.__value_list.config(yscrollcommand = self.__scrollbar.set)
153
+ self.__value_list.pack(side="left", fill="both", expand=True)
154
+
155
+ self.__scrollbar.pack(side="left", fill="y")
156
+
157
+ # Widgets for change the value of a memory address.
158
+ self.__input_frame_2 = Frame(self)
159
+ self.__input_frame_2["bg"] = "white"
160
+ self.__input_frame_2.pack(padx=5, fill="x", expand=True)
161
+
162
+ Label(self.__input_frame_2, text="Address:", bg="white").pack(side="left")
163
+
164
+ self.__address_entry = Entry(self.__input_frame_2)
165
+ self.__address_entry.config(validate="key", validatecommand=(self.__entry_register_hex, "%P"))
166
+ self.__address_entry.pack(side="left")
167
+
168
+ Label(self.__input_frame_2, bg="white").pack(side="left")
169
+
170
+ Label(self.__input_frame_2, text="New Value:", bg="white").pack(side="left")
171
+
172
+ self.__new_value_entry = Entry(self.__input_frame_2)
173
+ self.__new_value_entry.pack(side="left", fill="x", expand=True)
174
+
175
+ Button(self.__input_frame_2, text="Replace", command=self.__write_value).pack(side="left")
176
+
177
+ def __new_scan(self):
178
+ """
179
+ Start a new seach at the whole memory of the process.
180
+ """
181
+ if self.__new_scan_button["text"].lower() == "scanning" or self.__updating: return
182
+ self.__next_scan_button.config(text="")
183
+
184
+ self.__addresses = []
185
+
186
+ # If a scan is already in progress, clear all results and get everything ready for a new scan.
187
+ if self.__scanning:
188
+ self.__new_scan_button.config(text="First Scan")
189
+ self.__count_label.config(text="Start a new scan to find memory addresses.")
190
+ self.__address_list.delete(0, "end")
191
+ self.__value_list.delete(0, "end")
192
+ self.__progress_var.set(0)
193
+ self.__scanning = False
194
+ return
195
+
196
+ # Get the inputs.
197
+ value = self.__value_entry.get().strip()
198
+ length = int(self.__length_entry.get())
199
+ pytype = self.__value_type
200
+ scan_type = self.__scan_type
201
+
202
+ if not value or length == 0: return
203
+
204
+ # Check if the value is valid for the selected value type.
205
+ try:
206
+ if str(pytype(value)) != value or (pytype is str and length < len(value)):
207
+ raise ValueError()
208
+ except:
209
+ self.__value_entry.delete(0, "end")
210
+ return self.__value_entry.insert(0, "Invalid value")
211
+
212
+ # Start the scan.
213
+ value = pytype(value)
214
+
215
+ self.__value_length = length
216
+ self.__value = value
217
+
218
+ self.after(100, lambda: self.__start_scan(pytype, length, value, scan_type))
219
+
220
+ def __next_scan(self):
221
+ """
222
+ Filter the found addresses.
223
+ """
224
+ self.__update_values(remove=True)
225
+
226
+ def __on_close(self, *args):
227
+ """
228
+ Event to close the program graciously.
229
+ """
230
+ self.__close = True
231
+
232
+ if self.__updating or self.__new_scan_button["text"].lower() == "scanning":
233
+ self.update()
234
+ return self.after(10, self.__on_close)
235
+
236
+ self.destroy()
237
+
238
+ def __on_move_list_box(self, *args):
239
+ """
240
+ Event to sync the listboxes.
241
+ """
242
+ self.__address_list.yview(*args)
243
+ self.__value_list.yview(*args)
244
+
245
+ def __on_mouse_wheel(self, event):
246
+ """
247
+ Event to sync the listboxes.
248
+ """
249
+ self.__address_list.yview("scroll", event.delta, "units")
250
+ self.__value_list.yview("scroll", event.delta, "units")
251
+ return "break"
252
+
253
+ def __select_address(self, event):
254
+ """
255
+ Event to get the selected address and copy it.
256
+ """
257
+ selection = event.widget.curselection()
258
+ if not selection: return
259
+
260
+ address = self.__address_list.get(int(selection[0])).split(" ")[-1]
261
+ if not address: return
262
+
263
+ self.__address_entry.delete(0, "end")
264
+ self.__address_entry.insert(0, address)
265
+
266
+ def __select_value(self, event):
267
+ """
268
+ Event to get the selected value and copy it.
269
+ """
270
+ selection = event.widget.curselection()
271
+ if not selection: return
272
+
273
+ value = self.__value_list.get(int(selection[0]))[len("Value: "):]
274
+ self.__new_value_entry.delete(0, "end")
275
+ self.__new_value_entry.insert(0, value)
276
+
277
+ def __start_scan(self, pytype, length, value, scan_type):
278
+ """
279
+ Search for a value on the whole memory of the process.
280
+ """
281
+ self.__new_scan_button.config(text="Scanning")
282
+ self.update()
283
+
284
+ self.__scanning = True
285
+ self.__addresses = []
286
+
287
+ for address, info in self.__process.search_by_value(pytype, length, value, scan_type, progress_information=True):
288
+ if self.__close: break
289
+
290
+ self.__address_list.insert("end", f"Addr: {hex(address)[2:].upper()}")
291
+ self.__value_list.insert("end", f"Value: {value}")
292
+
293
+ self.__progress_var.set(info["progress"] * 100)
294
+ self.__addresses.append(address)
295
+ self.update()
296
+
297
+ self.__count_label.config(text=f"Found {len(self.__addresses)} addresses.")
298
+
299
+ self.__new_scan_button.config(text="New Scan")
300
+ self.__next_scan_button.config(text="Next Scan")
301
+ self.__progress_var.set(100)
302
+
303
+ def __set_scan_type(self, scan_type: int):
304
+ """
305
+ Method for the Menubutton to select a scan type.
306
+ """
307
+ # Allow select a new scan type only if program is not getting new addresses or updating their values.
308
+ if self.__new_scan_button["text"].lower() == "scanning" or self.__updating: return
309
+
310
+ self.__scan_type = [
311
+ ScanTypesEnum.EXACT_VALUE,
312
+ ScanTypesEnum.NOT_EXACT_VALUE,
313
+ ScanTypesEnum.SMALLER_THAN,
314
+ ScanTypesEnum.BIGGER_THAN,
315
+ ][scan_type]
316
+
317
+ text = " ".join(word.capitalize() for word in self.__scan_type.name.split("_"))
318
+ self.__scan_menu_button.config(text=text)
319
+
320
+ def __set_value_type(self, value_type: int):
321
+ """
322
+ Method for the Menubutton to select a value type.
323
+ """
324
+ if self.__scanning: return
325
+
326
+ self.__value_type = [bool, int, float, str][value_type]
327
+ self.__type_menu_button.config(text=["Boolean", "Integer", "Float", "String"][value_type])
328
+
329
+ def __validate_int_entry(self, string):
330
+ """
331
+ Method to validate if an input is integer.
332
+ """
333
+ if self.__scanning: return False
334
+
335
+ for char in string:
336
+ if char not in "0123456789": return False
337
+ return True
338
+
339
+ def __validate_hex_entry(self, string):
340
+ """
341
+ Method to validate if an input is hexadecimal.
342
+ """
343
+ for char in string.upper():
344
+ if char not in "0123456789ABCDEF": return False
345
+ return True
346
+
347
+ def __update_values(self, index = 0, *, remove = False, first_call = True):
348
+ """
349
+ Update the values of the found addresses. If "remove" is True, it will
350
+ compare the current value in memory and remove the address from the
351
+ results if the comparison is False.
352
+ """
353
+ if self.__updating and first_call: return # Allow call the method once.
354
+
355
+ # Return if user asked for closing the application.
356
+ if self.__close:
357
+ self.__updating = False
358
+ return
359
+
360
+ # Get the value to compare.
361
+ if first_call:
362
+ value = self.__value_entry.get().strip()
363
+
364
+ try:
365
+ if str(self.__value_type(value)) != value:
366
+ raise ValueError()
367
+ except:
368
+ self.__value_entry.delete(0, "end")
369
+ return self.__value_entry.insert(0, "Invalid value")
370
+
371
+ self.__value = self.__value_type(value)
372
+ self.__progress_var.set(0)
373
+
374
+ # Indicate the application is updating the values.
375
+ self.__updating = True
376
+
377
+ if index % 10 == 0: self.update()
378
+
379
+ # Get the address from the list.
380
+ try:
381
+ address = self.__addresses[index]
382
+ except:
383
+ self.__updating = False
384
+
385
+ self.__count_label.config(text=f"Found {len(self.__addresses)} addresses.")
386
+ return self.__progress_var.set(100 if not first_call else 0)
387
+
388
+ # Get the current value of the address.
389
+ corrupted = False
390
+ value = None
391
+
392
+ try: value = self.__process.read_process_memory(address, self.__value_type, self.__value_length)
393
+ except: corrupted = True
394
+
395
+ # If "remove" is True, compare the value.
396
+ if remove or corrupted:
397
+ compare = self.__comp_methods[self.__scan_type]
398
+
399
+ # Remove the address from the results.
400
+ if corrupted or not compare(value, self.__value):
401
+ self.__address_list.delete(index)
402
+ self.__value_list.delete(index)
403
+ self.__addresses.remove(address)
404
+ index -= 1
405
+
406
+ # Update the value at the listbox.
407
+ else:
408
+ self.__value_list.delete(index)
409
+ self.__value_list.insert(index, f"Value: {value}")
410
+
411
+ # Call the method again recursively
412
+ if index % 10 == 0: self.__progress_var.set((index / len(self.__addresses)) * 100)
413
+ self.after(5, lambda: self.__update_values(index + 1, remove=remove, first_call=False))
414
+
415
+ def __write_value(self):
416
+ """
417
+ Change the value in memory of an address of the result list.
418
+ """
419
+ try:
420
+ address = int(self.__address_entry.get().strip(), 16)
421
+ if address not in self.__addresses: raise ValueError()
422
+ except:
423
+ self.__address_entry.delete(0, "end")
424
+ return self.__address_entry.insert(0, "00000000")
425
+
426
+ # Get the inputs.
427
+ value = self.__new_value_entry.get()
428
+ pytype = self.__value_type
429
+ length = self.__value_length
430
+
431
+ if not value or length == 0: return
432
+
433
+ # Check if the value is valid for the selected value type.
434
+ try:
435
+ if str(pytype(value)) != value or (pytype is str and length < len(value)):
436
+ raise ValueError()
437
+ except:
438
+ self.__new_value_entry.delete(0, "end")
439
+ return self.__new_value_entry.insert(0, "Invalid value")
440
+
441
+ # Write the new value.
442
+ self.__process.write_process_memory(address, pytype, length, pytype(value))
443
+
@@ -0,0 +1,56 @@
1
+ from tkinter import Label, Tk
2
+ from tkinter.ttk import Button, Entry
3
+ from typing import Optional
4
+
5
+ from PyMemoryEditor import OpenProcess
6
+ from PyMemoryEditor.errors import ProcessIDNotExistsError, ProcessNotFoundError
7
+ from PyMemoryEditor.process import AbstractProcess
8
+
9
+
10
+ class OpenProcessWindow(Tk):
11
+ """
12
+ Window for opening a process.
13
+ """
14
+ def __init__(self):
15
+ super().__init__()
16
+ self.__process = None
17
+
18
+ self["bg"] = "white"
19
+
20
+ self.geometry("350x100")
21
+ self.title("PyMemoryEditor (Sample)")
22
+
23
+ Label(self, text="Insert the PID or the process name:", bg="white", font=("Arial", 10)).pack(padx=20)
24
+
25
+ self.__entry = Entry(self)
26
+ self.__entry.pack(padx=50, fill="x", expand=True)
27
+
28
+ Button(self, text="Open Process", command=self.__open_process).pack(pady=10)
29
+
30
+ self.mainloop()
31
+
32
+ def __open_process(self):
33
+ """
34
+ Open the process by the user input.
35
+ """
36
+ entry = self.__entry.get().strip()
37
+
38
+ try:
39
+ self.__process = OpenProcess(pid = int(entry))
40
+ return self.destroy()
41
+
42
+ except ValueError:
43
+ try:
44
+ self.__process = OpenProcess(process_name = entry)
45
+ return self.destroy()
46
+ except (ProcessIDNotExistsError, ProcessNotFoundError): pass
47
+ except (ProcessIDNotExistsError, ProcessNotFoundError): pass
48
+
49
+ self.__entry.delete(0, "end")
50
+ self.__entry.insert(0, "Process not found.")
51
+
52
+ def get_process(self) -> Optional[AbstractProcess]:
53
+ """
54
+ Return the opened process.
55
+ """
56
+ return self.__process
@@ -3,7 +3,7 @@
3
3
  from ..enums import ScanTypesEnum
4
4
  from .search.kmp import KMPSearch
5
5
 
6
- from typing import Sequence, Tuple, Union
6
+ from typing import Generator, Sequence, Tuple, Union
7
7
  import ctypes
8
8
  import sys
9
9
 
@@ -15,7 +15,7 @@ def scan_memory_for_exact_value(
15
15
  target_value_size: int,
16
16
  comparison: ScanTypesEnum = ScanTypesEnum.EXACT_VALUE,
17
17
  *args, **kwargs
18
- ):
18
+ ) -> Generator[int, None, None]:
19
19
  """
20
20
  Search for an exact value at the memory region.
21
21
 
@@ -50,7 +50,7 @@ def scan_memory(
50
50
  target_value_size: int,
51
51
  scan_type: ScanTypesEnum,
52
52
  is_string: bool,
53
- ):
53
+ ) -> Generator[int, None, None]:
54
54
  """
55
55
  Search for a value at the memory region.
56
56
  """
@@ -127,7 +127,7 @@ def ReadProcessMemory(
127
127
  data = get_c_type_of(pytype, bufflength)
128
128
  kernel32.ReadProcessMemory(process_handle, ctypes.c_void_p(address), ctypes.byref(data), bufflength, None)
129
129
 
130
- return str(data.value) if pytype is str else data.value
130
+ return data.value.decode() if pytype is str else data.value
131
131
 
132
132
 
133
133
  def SearchAllMemory(
@@ -56,8 +56,10 @@ class WindowsProcess(AbstractProcess):
56
56
  """
57
57
  Close the process handle.
58
58
  """
59
- self.__closed = True
60
- return CloseProcessHandle(self.__process_handle) != 0
59
+ if self.__closed: return True
60
+
61
+ self.__closed = CloseProcessHandle(self.__process_handle) != 0
62
+ return self.__closed
61
63
 
62
64
  def get_memory_regions(self) -> Generator[dict, None, None]:
63
65
  """
@@ -78,7 +78,7 @@ for address, info process.search_by_value(..., progress_information = True):
78
78
  print(template.format(address, progress))
79
79
  ```
80
80
 
81
- # Getting memory regions:
81
+ ## Getting memory regions:
82
82
  Use the method `get_memory_regions()` to get the base address, size and more information of all memory regions used by the process.
83
83
 
84
84
  ```py
@@ -87,3 +87,6 @@ for memory_region in process.get_memory_regions():
87
87
  size = memory_region["size"]
88
88
  information = memory_region["struct"]
89
89
  ```
90
+
91
+ # Tkinter application sample:
92
+ You can run a simple tkinter application to scan the memory of a process by typing `pymemoryeditor` at the CLI.
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "PyMemoryEditor"
3
3
  dynamic = ["version"]
4
- description = "Multi-platform library developed with ctypes for reading, writing and searching process memory, in a simple and friendly way with Python 3. It supports Windows and Linux (32-bit and 64-bit)."
4
+ description = "Multi-platform library developed with ctypes for reading, writing and searching process memory, in a simple and friendly way with Python 3."
5
5
  authors = [
6
6
  { name = "Jean Loui Bernard Silva de Jesus", email = "jeanextreme002@gmail.com" },
7
7
  ]
@@ -58,4 +58,7 @@ path = "PyMemoryEditor/__init__.py"
58
58
 
59
59
  [build-system]
60
60
  requires = ["hatchling"]
61
- build-backend = "hatchling.build"
61
+ build-backend = "hatchling.build"
62
+
63
+ [project.scripts]
64
+ pymemoryeditor = "PyMemoryEditor.sample.application:main"
@@ -1,3 +0,0 @@
1
- class ClosedProcess(Exception):
2
- def __str__(self):
3
- return "operation on closed process."
File without changes