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,368 @@
1
+ """
2
+ Data Types Module
3
+ Common data type definitions and utilities
4
+ """
5
+
6
+ from dataclasses import dataclass
7
+ from typing import Optional, Dict, Any, List, Union
8
+ from enum import Enum
9
+ import struct
10
+
11
+ class DataType(Enum):
12
+ """Enumeration of supported data types"""
13
+ UINT8 = "uint8"
14
+ INT8 = "int8"
15
+ UINT16 = "uint16"
16
+ INT16 = "int16"
17
+ UINT32 = "uint32"
18
+ INT32 = "int32"
19
+ UINT64 = "uint64"
20
+ INT64 = "int64"
21
+ FLOAT = "float"
22
+ DOUBLE = "double"
23
+ STRING = "string"
24
+ POINTER = "pointer"
25
+ BYTES = "bytes"
26
+
27
+ class Architecture(Enum):
28
+ """Target architecture enumeration"""
29
+ X86 = "x86"
30
+ X64 = "x64"
31
+ UNKNOWN = "unknown"
32
+
33
+ class MemoryProtection(Enum):
34
+ """Memory protection flags"""
35
+ NOACCESS = 0x01
36
+ READONLY = 0x02
37
+ READWRITE = 0x04
38
+ WRITECOPY = 0x08
39
+ EXECUTE = 0x10
40
+ EXECUTE_READ = 0x20
41
+ EXECUTE_READWRITE = 0x40
42
+ EXECUTE_WRITECOPY = 0x80
43
+
44
+ @dataclass
45
+ class ProcessSnapshot:
46
+ """Snapshot of process state"""
47
+ pid: int
48
+ name: str
49
+ timestamp: float
50
+ memory_usage: int
51
+ thread_count: int
52
+ handle_count: Optional[int] = None
53
+ cpu_usage: Optional[float] = None
54
+
55
+ @dataclass
56
+ class MemoryBlock:
57
+ """Represents a block of memory"""
58
+ address: int
59
+ size: int
60
+ data: bytes
61
+ protection: int
62
+ timestamp: float
63
+
64
+ def contains_address(self, addr: int) -> bool:
65
+ """Check if this block contains the given address"""
66
+ return self.address <= addr < self.address + self.size
67
+
68
+ def get_offset(self, addr: int) -> Optional[int]:
69
+ """Get offset of address within this block"""
70
+ if self.contains_address(addr):
71
+ return addr - self.address
72
+ return None
73
+
74
+ @dataclass
75
+ class ScanResult:
76
+ """Result of a memory scan operation"""
77
+ address: int
78
+ value: Any
79
+ data_type: DataType
80
+ context: Optional[bytes] = None
81
+ confidence: float = 1.0
82
+
83
+ @dataclass
84
+ class PointerChain:
85
+ """Represents a chain of pointers"""
86
+ base_address: int
87
+ offsets: List[int]
88
+ final_address: int
89
+ valid: bool = True
90
+
91
+ def __str__(self) -> str:
92
+ """String representation of pointer chain"""
93
+ if not self.offsets:
94
+ return f"0x{self.base_address:08X}"
95
+
96
+ offset_str = " + ".join(f"0x{offset:X}" for offset in self.offsets)
97
+ return f"[0x{self.base_address:08X} + {offset_str}] -> 0x{self.final_address:08X}"
98
+
99
+ class DataTypeConverter:
100
+ """Utility class for data type conversions"""
101
+
102
+ @staticmethod
103
+ def bytes_to_value(data: bytes, data_type: DataType, offset: int = 0) -> Any:
104
+ """Convert bytes to a specific data type"""
105
+ if offset + DataTypeConverter.get_size(data_type) > len(data):
106
+ raise ValueError("Insufficient data for conversion")
107
+
108
+ chunk = data[offset:offset + DataTypeConverter.get_size(data_type)]
109
+
110
+ if data_type == DataType.UINT8:
111
+ return chunk[0]
112
+ elif data_type == DataType.INT8:
113
+ return struct.unpack('<b', chunk)[0]
114
+ elif data_type == DataType.UINT16:
115
+ return struct.unpack('<H', chunk)[0]
116
+ elif data_type == DataType.INT16:
117
+ return struct.unpack('<h', chunk)[0]
118
+ elif data_type == DataType.UINT32:
119
+ return struct.unpack('<I', chunk)[0]
120
+ elif data_type == DataType.INT32:
121
+ return struct.unpack('<i', chunk)[0]
122
+ elif data_type == DataType.UINT64:
123
+ return struct.unpack('<Q', chunk)[0]
124
+ elif data_type == DataType.INT64:
125
+ return struct.unpack('<q', chunk)[0]
126
+ elif data_type == DataType.FLOAT:
127
+ return struct.unpack('<f', chunk)[0]
128
+ elif data_type == DataType.DOUBLE:
129
+ return struct.unpack('<d', chunk)[0]
130
+ elif data_type == DataType.STRING:
131
+ # Find null terminator
132
+ null_pos = chunk.find(b'\x00')
133
+ if null_pos != -1:
134
+ chunk = chunk[:null_pos]
135
+ return chunk.decode('utf-8', errors='ignore')
136
+ elif data_type == DataType.BYTES:
137
+ return chunk
138
+ else:
139
+ raise ValueError(f"Unsupported data type: {data_type}")
140
+
141
+ @staticmethod
142
+ def value_to_bytes(value: Any, data_type: DataType) -> bytes:
143
+ """Convert a value to bytes"""
144
+ if data_type == DataType.UINT8:
145
+ return struct.pack('<B', int(value))
146
+ elif data_type == DataType.INT8:
147
+ return struct.pack('<b', int(value))
148
+ elif data_type == DataType.UINT16:
149
+ return struct.pack('<H', int(value))
150
+ elif data_type == DataType.INT16:
151
+ return struct.pack('<h', int(value))
152
+ elif data_type == DataType.UINT32:
153
+ return struct.pack('<I', int(value))
154
+ elif data_type == DataType.INT32:
155
+ return struct.pack('<i', int(value))
156
+ elif data_type == DataType.UINT64:
157
+ return struct.pack('<Q', int(value))
158
+ elif data_type == DataType.INT64:
159
+ return struct.pack('<q', int(value))
160
+ elif data_type == DataType.FLOAT:
161
+ return struct.pack('<f', float(value))
162
+ elif data_type == DataType.DOUBLE:
163
+ return struct.pack('<d', float(value))
164
+ elif data_type == DataType.STRING:
165
+ return str(value).encode('utf-8') + b'\x00'
166
+ elif data_type == DataType.BYTES:
167
+ return bytes(value)
168
+ else:
169
+ raise ValueError(f"Unsupported data type: {data_type}")
170
+
171
+ @staticmethod
172
+ def get_size(data_type: DataType) -> int:
173
+ """Get the size in bytes of a data type"""
174
+ sizes = {
175
+ DataType.UINT8: 1,
176
+ DataType.INT8: 1,
177
+ DataType.UINT16: 2,
178
+ DataType.INT16: 2,
179
+ DataType.UINT32: 4,
180
+ DataType.INT32: 4,
181
+ DataType.UINT64: 8,
182
+ DataType.INT64: 8,
183
+ DataType.FLOAT: 4,
184
+ DataType.DOUBLE: 8,
185
+ DataType.POINTER: 8, # Default to 64-bit
186
+ DataType.STRING: -1, # Variable length
187
+ DataType.BYTES: -1, # Variable length
188
+ }
189
+ return sizes.get(data_type, -1)
190
+
191
+ @staticmethod
192
+ def get_pointer_size(architecture: Architecture) -> int:
193
+ """Get pointer size for architecture"""
194
+ if architecture == Architecture.X64:
195
+ return 8
196
+ elif architecture == Architecture.X86:
197
+ return 4
198
+ else:
199
+ return 8 # Default to 64-bit
200
+
201
+ class PatternMatcher:
202
+ """Utility for pattern matching operations"""
203
+
204
+ @staticmethod
205
+ def parse_pattern(pattern: str) -> tuple[bytes, bytes]:
206
+ """Parse a pattern string into pattern and mask bytes
207
+
208
+ Args:
209
+ pattern: Pattern string like "48 8B 05 ?? ?? ?? ??"
210
+
211
+ Returns:
212
+ Tuple of (pattern_bytes, mask_bytes)
213
+ """
214
+ import re
215
+
216
+ # Clean up the pattern
217
+ pattern = re.sub(r'\s+', ' ', pattern.strip().upper())
218
+ parts = pattern.split(' ')
219
+
220
+ pattern_bytes = bytearray()
221
+ mask_bytes = bytearray()
222
+
223
+ for part in parts:
224
+ if part in ['??', '?']:
225
+ pattern_bytes.append(0x00)
226
+ mask_bytes.append(0x00)
227
+ else:
228
+ try:
229
+ byte_val = int(part, 16)
230
+ pattern_bytes.append(byte_val)
231
+ mask_bytes.append(0xFF)
232
+ except ValueError:
233
+ raise ValueError(f"Invalid byte in pattern: {part}")
234
+
235
+ return bytes(pattern_bytes), bytes(mask_bytes)
236
+
237
+ @staticmethod
238
+ def match_pattern(data: bytes, pattern: bytes, mask: bytes, offset: int = 0) -> bool:
239
+ """Check if pattern matches at given offset"""
240
+ if offset + len(pattern) > len(data):
241
+ return False
242
+
243
+ for i in range(len(pattern)):
244
+ if mask[i] == 0xFF: # Must match exactly
245
+ if data[offset + i] != pattern[i]:
246
+ return False
247
+
248
+ return True
249
+
250
+ class AddressCalculator:
251
+ """Utility for address calculations"""
252
+
253
+ @staticmethod
254
+ def align_address(address: int, alignment: int) -> int:
255
+ """Align address to specified boundary"""
256
+ return (address + alignment - 1) & ~(alignment - 1)
257
+
258
+ @staticmethod
259
+ def is_aligned(address: int, alignment: int) -> bool:
260
+ """Check if address is aligned to boundary"""
261
+ return address % alignment == 0
262
+
263
+ @staticmethod
264
+ def calculate_rva(address: int, base_address: int) -> int:
265
+ """Calculate relative virtual address"""
266
+ return address - base_address
267
+
268
+ @staticmethod
269
+ def is_valid_user_address(address: int, architecture: Architecture) -> bool:
270
+ """Check if address is in valid user space range"""
271
+ if architecture == Architecture.X86:
272
+ return 0x00010000 <= address <= 0x7FFFFFFF
273
+ elif architecture == Architecture.X64:
274
+ return 0x0000000000010000 <= address <= 0x00007FFFFFFFFFFF
275
+ else:
276
+ return False
277
+
278
+ @staticmethod
279
+ def is_valid_kernel_address(address: int, architecture: Architecture) -> bool:
280
+ """Check if address is in kernel space range"""
281
+ if architecture == Architecture.X86:
282
+ return 0x80000000 <= address <= 0xFFFFFFFF
283
+ elif architecture == Architecture.X64:
284
+ return 0xFFFF800000000000 <= address <= 0xFFFFFFFFFFFFFFFF
285
+ else:
286
+ return False
287
+
288
+ @dataclass
289
+ class AnalysisContext:
290
+ """Context information for analysis operations"""
291
+ target_process: Optional[Dict[str, Any]] = None
292
+ architecture: Architecture = Architecture.UNKNOWN
293
+ base_address: int = 0
294
+ analysis_depth: int = 1
295
+ include_symbols: bool = True
296
+ include_strings: bool = True
297
+ max_results: int = 1000
298
+
299
+ def get_pointer_size(self) -> int:
300
+ """Get pointer size for current architecture"""
301
+ return DataTypeConverter.get_pointer_size(self.architecture)
302
+
303
+ class CacheEntry:
304
+ """Cache entry for analysis results"""
305
+
306
+ def __init__(self, key: str, value: Any, ttl: float = 300.0):
307
+ import time
308
+ self.key = key
309
+ self.value = value
310
+ self.created_time = time.time()
311
+ self.ttl = ttl
312
+
313
+ def is_expired(self) -> bool:
314
+ """Check if cache entry has expired"""
315
+ import time
316
+ return time.time() - self.created_time > self.ttl
317
+
318
+ def refresh(self):
319
+ """Refresh the cache entry timestamp"""
320
+ import time
321
+ self.created_time = time.time()
322
+
323
+ class SimpleCache:
324
+ """Simple in-memory cache for analysis results"""
325
+
326
+ def __init__(self, max_size: int = 1000):
327
+ self.cache: Dict[str, CacheEntry] = {}
328
+ self.max_size = max_size
329
+
330
+ def get(self, key: str) -> Optional[Any]:
331
+ """Get value from cache"""
332
+ if key in self.cache:
333
+ entry = self.cache[key]
334
+ if not entry.is_expired():
335
+ entry.refresh()
336
+ return entry.value
337
+ else:
338
+ del self.cache[key]
339
+ return None
340
+
341
+ def put(self, key: str, value: Any, ttl: float = 300.0):
342
+ """Put value in cache"""
343
+ # Clean up expired entries
344
+ self._cleanup_expired()
345
+
346
+ # Remove oldest entries if at capacity
347
+ if len(self.cache) >= self.max_size:
348
+ # Remove 10% of oldest entries
349
+ oldest_keys = sorted(self.cache.keys(),
350
+ key=lambda k: self.cache[k].created_time)
351
+ for old_key in oldest_keys[:self.max_size // 10]:
352
+ del self.cache[old_key]
353
+
354
+ self.cache[key] = CacheEntry(key, value, ttl)
355
+
356
+ def _cleanup_expired(self):
357
+ """Remove expired cache entries"""
358
+ expired_keys = [k for k, v in self.cache.items() if v.is_expired()]
359
+ for key in expired_keys:
360
+ del self.cache[key]
361
+
362
+ def clear(self):
363
+ """Clear all cache entries"""
364
+ self.cache.clear()
365
+
366
+ def size(self) -> int:
367
+ """Get current cache size"""
368
+ return len(self.cache)