iflow-mcp_bethington-cheat-engine-server-python 0.1.0__py3-none-any.whl

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 (40) hide show
  1. iflow_mcp_bethington_cheat_engine_server_python-0.1.0.dist-info/METADATA +16 -0
  2. iflow_mcp_bethington_cheat_engine_server_python-0.1.0.dist-info/RECORD +40 -0
  3. iflow_mcp_bethington_cheat_engine_server_python-0.1.0.dist-info/WHEEL +5 -0
  4. iflow_mcp_bethington_cheat_engine_server_python-0.1.0.dist-info/entry_points.txt +2 -0
  5. iflow_mcp_bethington_cheat_engine_server_python-0.1.0.dist-info/licenses/LICENSE +21 -0
  6. iflow_mcp_bethington_cheat_engine_server_python-0.1.0.dist-info/top_level.txt +1 -0
  7. server/cheatengine/__init__.py +19 -0
  8. server/cheatengine/ce_bridge.py +1670 -0
  9. server/cheatengine/lua_interface.py +460 -0
  10. server/cheatengine/table_parser.py +1221 -0
  11. server/config/__init__.py +20 -0
  12. server/config/settings.py +347 -0
  13. server/config/whitelist.py +378 -0
  14. server/gui_automation/__init__.py +43 -0
  15. server/gui_automation/core/__init__.py +8 -0
  16. server/gui_automation/core/integration.py +951 -0
  17. server/gui_automation/demos/__init__.py +8 -0
  18. server/gui_automation/demos/basic_demo.py +754 -0
  19. server/gui_automation/demos/notepad_demo.py +460 -0
  20. server/gui_automation/demos/simple_demo.py +319 -0
  21. server/gui_automation/tools/__init__.py +8 -0
  22. server/gui_automation/tools/mcp_tools.py +974 -0
  23. server/main.py +519 -0
  24. server/memory/__init__.py +0 -0
  25. server/memory/analyzer.py +0 -0
  26. server/memory/reader.py +0 -0
  27. server/memory/scanner.py +0 -0
  28. server/memory/symbols.py +0 -0
  29. server/process/__init__.py +16 -0
  30. server/process/launcher.py +608 -0
  31. server/process/manager.py +185 -0
  32. server/process/monitors.py +202 -0
  33. server/process/permissions.py +131 -0
  34. server/process_whitelist.json +119 -0
  35. server/pyautogui/__init__.py +0 -0
  36. server/utils/__init__.py +37 -0
  37. server/utils/data_types.py +368 -0
  38. server/utils/formatters.py +430 -0
  39. server/utils/validators.py +340 -0
  40. server/window_automation/__init__.py +59 -0
@@ -0,0 +1,430 @@
1
+ """
2
+ Formatting Utilities
3
+ Output formatting and data presentation functions
4
+ """
5
+
6
+ import struct
7
+ import logging
8
+ from typing import Any, List, Dict, Optional, Union
9
+ from datetime import datetime
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ def format_memory_data(data: bytes, data_type: str, address: int, analysis: Optional[Dict] = None) -> str:
14
+ """Format memory data according to the specified type
15
+
16
+ Args:
17
+ data: Raw memory data
18
+ data_type: Type of formatting to apply
19
+ address: Base address of the data
20
+ analysis: Optional analysis results
21
+
22
+ Returns:
23
+ Formatted string representation
24
+ """
25
+ try:
26
+ if not data:
27
+ return "No data"
28
+
29
+ if data_type.lower() == "raw":
30
+ return format_raw_bytes(data, address)
31
+ elif data_type.lower() in ["int32", "uint32", "dword"]:
32
+ return format_integers(data, address, 4, signed=(data_type.lower() == "int32"))
33
+ elif data_type.lower() in ["int64", "uint64", "qword"]:
34
+ return format_integers(data, address, 8, signed=(data_type.lower() == "int64"))
35
+ elif data_type.lower() in ["int16", "uint16", "word"]:
36
+ return format_integers(data, address, 2, signed=(data_type.lower() == "int16"))
37
+ elif data_type.lower() in ["float", "single"]:
38
+ return format_floats(data, address, 4)
39
+ elif data_type.lower() == "double":
40
+ return format_floats(data, address, 8)
41
+ elif data_type.lower() in ["string", "str", "ascii"]:
42
+ return format_strings(data, address, encoding="ascii")
43
+ elif data_type.lower() == "utf16":
44
+ return format_strings(data, address, encoding="utf-16le")
45
+ elif data_type.lower() == "auto":
46
+ return format_auto_detect(data, address)
47
+ elif data_type.lower() in ["structure", "struct"]:
48
+ return format_structure(data, address, analysis)
49
+ else:
50
+ return format_raw_bytes(data, address)
51
+
52
+ except Exception as e:
53
+ logger.error(f"Error formatting memory data: {e}")
54
+ return f"Error formatting data: {e}"
55
+
56
+ def format_raw_bytes(data: bytes, address: int, bytes_per_line: int = 16) -> str:
57
+ """Format data as raw hex bytes with ASCII representation"""
58
+ output = []
59
+ output.append(f"Raw memory data at 0x{address:08X} ({len(data)} bytes):")
60
+ output.append("")
61
+
62
+ for i in range(0, len(data), bytes_per_line):
63
+ chunk = data[i:i+bytes_per_line]
64
+ addr_str = f"0x{address + i:08X}:"
65
+
66
+ # Hex representation
67
+ hex_str = ' '.join(f"{b:02X}" for b in chunk)
68
+ hex_str = hex_str.ljust(bytes_per_line * 3 - 1) # Pad for alignment
69
+
70
+ # ASCII representation
71
+ ascii_str = ''.join(chr(b) if 32 <= b <= 126 else '.' for b in chunk)
72
+
73
+ output.append(f"{addr_str} {hex_str} |{ascii_str}|")
74
+
75
+ return '\n'.join(output)
76
+
77
+ def format_integers(data: bytes, address: int, size: int, signed: bool = False) -> str:
78
+ """Format data as integers of specified size"""
79
+ if size == 2:
80
+ format_char = '<h' if signed else '<H'
81
+ type_name = "int16" if signed else "uint16"
82
+ elif size == 4:
83
+ format_char = '<i' if signed else '<I'
84
+ type_name = "int32" if signed else "uint32"
85
+ elif size == 8:
86
+ format_char = '<q' if signed else '<Q'
87
+ type_name = "int64" if signed else "uint64"
88
+ else:
89
+ return "Unsupported integer size"
90
+
91
+ output = []
92
+ output.append(f"{type_name} values at 0x{address:08X}:")
93
+ output.append("")
94
+
95
+ for i in range(0, len(data) - size + 1, size):
96
+ try:
97
+ value = struct.unpack(format_char, data[i:i+size])[0]
98
+ addr = address + i
99
+
100
+ if signed:
101
+ output.append(f"0x{addr:08X}: {value:>12} (0x{value & ((1 << (size*8)) - 1):0{size*2}X})")
102
+ else:
103
+ output.append(f"0x{addr:08X}: {value:>12} (0x{value:0{size*2}X})")
104
+
105
+ except struct.error:
106
+ break
107
+
108
+ return '\n'.join(output)
109
+
110
+ def format_floats(data: bytes, address: int, size: int) -> str:
111
+ """Format data as floating-point numbers"""
112
+ if size == 4:
113
+ format_char = '<f'
114
+ type_name = "float"
115
+ elif size == 8:
116
+ format_char = '<d'
117
+ type_name = "double"
118
+ else:
119
+ return "Unsupported float size"
120
+
121
+ output = []
122
+ output.append(f"{type_name} values at 0x{address:08X}:")
123
+ output.append("")
124
+
125
+ for i in range(0, len(data) - size + 1, size):
126
+ try:
127
+ value = struct.unpack(format_char, data[i:i+size])[0]
128
+ addr = address + i
129
+
130
+ # Format with appropriate precision
131
+ if abs(value) < 1e-6 or abs(value) > 1e6:
132
+ formatted_value = f"{value:.6e}"
133
+ else:
134
+ formatted_value = f"{value:.6f}"
135
+
136
+ output.append(f"0x{addr:08X}: {formatted_value}")
137
+
138
+ except struct.error:
139
+ break
140
+
141
+ return '\n'.join(output)
142
+
143
+ def format_strings(data: bytes, address: int, encoding: str = "ascii", max_length: int = 100) -> str:
144
+ """Format data as strings"""
145
+ output = []
146
+ output.append(f"String data at 0x{address:08X} ({encoding}):")
147
+ output.append("")
148
+
149
+ # Find strings in the data
150
+ strings_found = []
151
+ current_string = []
152
+ string_start = 0
153
+
154
+ if encoding == "ascii":
155
+ for i, byte in enumerate(data):
156
+ if 32 <= byte <= 126: # Printable ASCII
157
+ if not current_string:
158
+ string_start = i
159
+ current_string.append(chr(byte))
160
+ elif byte == 0 and current_string: # Null terminator
161
+ if len(current_string) >= 3: # Minimum string length
162
+ strings_found.append((string_start, ''.join(current_string)))
163
+ current_string = []
164
+ else:
165
+ if current_string and len(current_string) >= 3:
166
+ strings_found.append((string_start, ''.join(current_string)))
167
+ current_string = []
168
+
169
+ elif encoding == "utf-16le":
170
+ i = 0
171
+ while i < len(data) - 1:
172
+ char_bytes = data[i:i+2]
173
+ if len(char_bytes) == 2:
174
+ try:
175
+ char = char_bytes.decode('utf-16le')
176
+ if char.isprintable() and char != '\x00':
177
+ if not current_string:
178
+ string_start = i
179
+ current_string.append(char)
180
+ elif char == '\x00' and current_string:
181
+ if len(current_string) >= 3:
182
+ strings_found.append((string_start, ''.join(current_string)))
183
+ current_string = []
184
+ else:
185
+ if current_string and len(current_string) >= 3:
186
+ strings_found.append((string_start, ''.join(current_string)))
187
+ current_string = []
188
+ except UnicodeDecodeError:
189
+ if current_string and len(current_string) >= 3:
190
+ strings_found.append((string_start, ''.join(current_string)))
191
+ current_string = []
192
+ i += 2
193
+
194
+ # Add any remaining string
195
+ if current_string and len(current_string) >= 3:
196
+ strings_found.append((string_start, ''.join(current_string)))
197
+
198
+ if not strings_found:
199
+ output.append("No strings found")
200
+ else:
201
+ for offset, string_value in strings_found:
202
+ addr = address + offset
203
+ # Truncate long strings
204
+ display_string = string_value[:max_length]
205
+ if len(string_value) > max_length:
206
+ display_string += "..."
207
+
208
+ output.append(f"0x{addr:08X}: \"{display_string}\"")
209
+
210
+ return '\n'.join(output)
211
+
212
+ def format_auto_detect(data: bytes, address: int) -> str:
213
+ """Auto-detect and format data types"""
214
+ output = []
215
+ output.append(f"Auto-detected data at 0x{address:08X}:")
216
+ output.append("=" * 50)
217
+
218
+ # Check for strings first
219
+ string_result = format_strings(data, address)
220
+ if "No strings found" not in string_result:
221
+ output.append("Strings found:")
222
+ output.append(string_result)
223
+ output.append("")
224
+
225
+ # Check for potential pointers (4-byte aligned values in reasonable ranges)
226
+ output.append("Potential pointers:")
227
+ pointer_count = 0
228
+ for i in range(0, len(data) - 3, 4):
229
+ try:
230
+ value = struct.unpack('<I', data[i:i+4])[0]
231
+ if 0x00400000 <= value <= 0x7FFFFFFF: # Typical user space
232
+ addr = address + i
233
+ output.append(f"0x{addr:08X}: 0x{value:08X}")
234
+ pointer_count += 1
235
+ if pointer_count >= 10: # Limit output
236
+ output.append("... (more pointers found)")
237
+ break
238
+ except struct.error:
239
+ break
240
+
241
+ if pointer_count == 0:
242
+ output.append("No obvious pointers found")
243
+
244
+ output.append("")
245
+
246
+ # Show first few bytes as hex/ASCII
247
+ output.append("Raw bytes (first 64 bytes):")
248
+ preview_data = data[:64]
249
+ hex_preview = format_raw_bytes(preview_data, address)
250
+ output.append(hex_preview)
251
+
252
+ return '\n'.join(output)
253
+
254
+ def format_structure(data: bytes, address: int, analysis: Optional[Dict] = None) -> str:
255
+ """Format data as a structured analysis"""
256
+ output = []
257
+ output.append(f"Structure analysis at 0x{address:08X}:")
258
+ output.append("=" * 50)
259
+
260
+ if analysis and 'fields' in analysis:
261
+ output.append(f"Detected structure (confidence: {analysis.get('confidence', 0):.1%}):")
262
+ output.append("")
263
+ output.append(f"{'Offset':<8} {'Type':<12} {'Name':<20} {'Value'}")
264
+ output.append("-" * 60)
265
+
266
+ for field in analysis['fields']:
267
+ offset_str = f"+0x{field['offset']:X}"
268
+ confidence_indicator = "*" if field.get('confidence', 0) > 0.7 else " "
269
+ output.append(f"{offset_str:<8} {field['type']:<12} {field['name']:<20} {field['value']}{confidence_indicator}")
270
+
271
+ else:
272
+ # Fallback to simple hex dump with annotations
273
+ output.append("Basic structure view:")
274
+ output.append("")
275
+
276
+ for i in range(0, min(len(data), 128), 4): # Show first 128 bytes in 4-byte chunks
277
+ chunk = data[i:i+4]
278
+ addr = address + i
279
+
280
+ if len(chunk) == 4:
281
+ # Try to interpret as different types
282
+ try:
283
+ uint_val = struct.unpack('<I', chunk)[0]
284
+ int_val = struct.unpack('<i', chunk)[0]
285
+ float_val = struct.unpack('<f', chunk)[0]
286
+
287
+ annotations = []
288
+
289
+ # Check if it looks like a pointer
290
+ if 0x00400000 <= uint_val <= 0x7FFFFFFF:
291
+ annotations.append(f"ptr: 0x{uint_val:08X}")
292
+
293
+ # Check if it's a reasonable integer
294
+ if -1000000 <= int_val <= 1000000:
295
+ annotations.append(f"int: {int_val}")
296
+
297
+ # Check if it's a reasonable float
298
+ if -1000000.0 <= float_val <= 1000000.0 and not (float_val != float_val): # Not NaN
299
+ annotations.append(f"float: {float_val:.3f}")
300
+
301
+ hex_bytes = ' '.join(f"{b:02X}" for b in chunk)
302
+ annotation_str = " | ".join(annotations[:2]) # Limit annotations
303
+
304
+ output.append(f"+0x{i:02X}: {hex_bytes} - {annotation_str}")
305
+
306
+ except struct.error:
307
+ hex_bytes = ' '.join(f"{b:02X}" for b in chunk)
308
+ output.append(f"+0x{i:02X}: {hex_bytes}")
309
+
310
+ return '\n'.join(output)
311
+
312
+ def format_process_info(processes: List[Dict], detailed: bool = False) -> str:
313
+ """Format process information for display"""
314
+ if not processes:
315
+ return "No processes found"
316
+
317
+ output = []
318
+
319
+ if detailed and len(processes) == 1:
320
+ # Detailed view for single process
321
+ proc = processes[0]
322
+ output.append(f"Process Details:")
323
+ output.append("=" * 40)
324
+ output.append(f"PID: {proc['pid']}")
325
+ output.append(f"Name: {proc['name']}")
326
+ output.append(f"Path: {proc.get('exe_path', 'N/A')}")
327
+ output.append(f"Architecture: {proc.get('architecture', 'Unknown')}")
328
+ output.append(f"Status: {proc.get('status', 'Unknown')}")
329
+
330
+ if 'memory_info' in proc:
331
+ mem_info = proc['memory_info']
332
+ output.append(f"Memory (RSS): {format_size(mem_info.get('rss', 0))}")
333
+ output.append(f"Memory (VMS): {format_size(mem_info.get('vms', 0))}")
334
+ output.append(f"Memory %: {mem_info.get('percent', 0):.1f}%")
335
+
336
+ if 'num_threads' in proc:
337
+ output.append(f"Threads: {proc['num_threads']}")
338
+
339
+ if 'modules_count' in proc:
340
+ output.append(f"Modules: {proc['modules_count']}")
341
+
342
+ else:
343
+ # List view
344
+ output.append(f"Found {len(processes)} processes:")
345
+ output.append("")
346
+ output.append(f"{'PID':<8} {'Name':<25} {'Arch':<6} {'Memory':<10}")
347
+ output.append("-" * 55)
348
+
349
+ for proc in processes:
350
+ pid = proc['pid']
351
+ name = proc['name'][:24] # Truncate long names
352
+ arch = proc.get('architecture', 'N/A')[:5]
353
+ memory = format_size(proc.get('memory_usage', 0))
354
+
355
+ output.append(f"{pid:<8} {name:<25} {arch:<6} {memory:<10}")
356
+
357
+ return '\n'.join(output)
358
+
359
+ def format_size(size_bytes: int) -> str:
360
+ """Format a size in bytes to human-readable format"""
361
+ if size_bytes == 0:
362
+ return "0 B"
363
+
364
+ units = ['B', 'KB', 'MB', 'GB', 'TB']
365
+ unit_index = 0
366
+ size = float(size_bytes)
367
+
368
+ while size >= 1024 and unit_index < len(units) - 1:
369
+ size /= 1024
370
+ unit_index += 1
371
+
372
+ if unit_index == 0:
373
+ return f"{int(size)} {units[unit_index]}"
374
+ else:
375
+ return f"{size:.1f} {units[unit_index]}"
376
+
377
+ def format_timestamp(timestamp: Optional[float] = None) -> str:
378
+ """Format a timestamp for display"""
379
+ if timestamp is None:
380
+ timestamp = datetime.now().timestamp()
381
+
382
+ dt = datetime.fromtimestamp(timestamp)
383
+ return dt.strftime("%Y-%m-%d %H:%M:%S")
384
+
385
+ def format_hex_dump(data: bytes, address: int = 0, width: int = 16) -> str:
386
+ """Format data as a hex dump with ASCII representation"""
387
+ output = []
388
+
389
+ for i in range(0, len(data), width):
390
+ chunk = data[i:i+width]
391
+ addr_str = f"{address + i:08X}:"
392
+
393
+ # Hex bytes
394
+ hex_part = ' '.join(f"{b:02X}" for b in chunk)
395
+ hex_part = hex_part.ljust(width * 3 - 1) # Pad for alignment
396
+
397
+ # ASCII representation
398
+ ascii_part = ''.join(chr(b) if 32 <= b <= 126 else '.' for b in chunk)
399
+
400
+ output.append(f"{addr_str} {hex_part} |{ascii_part}|")
401
+
402
+ return '\n'.join(output)
403
+
404
+ def format_error_message(error: Exception, context: str = "") -> str:
405
+ """Format an error message for user display"""
406
+ error_type = type(error).__name__
407
+ error_msg = str(error)
408
+
409
+ if context:
410
+ return f"Error in {context}: {error_type} - {error_msg}"
411
+ else:
412
+ return f"{error_type}: {error_msg}"
413
+
414
+ def format_scan_results(results: List[int], pattern: str, limit: int = 50) -> str:
415
+ """Format memory scan results"""
416
+ if not results:
417
+ return f"No matches found for pattern: {pattern}"
418
+
419
+ output = []
420
+ output.append(f"Found {len(results)} matches for pattern '{pattern}':")
421
+ output.append("")
422
+
423
+ displayed = min(len(results), limit)
424
+ for i in range(displayed):
425
+ output.append(f"{i+1:4d}: 0x{results[i]:08X}")
426
+
427
+ if len(results) > limit:
428
+ output.append(f"... and {len(results) - limit} more matches")
429
+
430
+ return '\n'.join(output)