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.
- iflow_mcp_bethington_cheat_engine_server_python-0.1.0.dist-info/METADATA +16 -0
- iflow_mcp_bethington_cheat_engine_server_python-0.1.0.dist-info/RECORD +40 -0
- iflow_mcp_bethington_cheat_engine_server_python-0.1.0.dist-info/WHEEL +5 -0
- iflow_mcp_bethington_cheat_engine_server_python-0.1.0.dist-info/entry_points.txt +2 -0
- iflow_mcp_bethington_cheat_engine_server_python-0.1.0.dist-info/licenses/LICENSE +21 -0
- iflow_mcp_bethington_cheat_engine_server_python-0.1.0.dist-info/top_level.txt +1 -0
- server/cheatengine/__init__.py +19 -0
- server/cheatengine/ce_bridge.py +1670 -0
- server/cheatengine/lua_interface.py +460 -0
- server/cheatengine/table_parser.py +1221 -0
- server/config/__init__.py +20 -0
- server/config/settings.py +347 -0
- server/config/whitelist.py +378 -0
- server/gui_automation/__init__.py +43 -0
- server/gui_automation/core/__init__.py +8 -0
- server/gui_automation/core/integration.py +951 -0
- server/gui_automation/demos/__init__.py +8 -0
- server/gui_automation/demos/basic_demo.py +754 -0
- server/gui_automation/demos/notepad_demo.py +460 -0
- server/gui_automation/demos/simple_demo.py +319 -0
- server/gui_automation/tools/__init__.py +8 -0
- server/gui_automation/tools/mcp_tools.py +974 -0
- server/main.py +519 -0
- server/memory/__init__.py +0 -0
- server/memory/analyzer.py +0 -0
- server/memory/reader.py +0 -0
- server/memory/scanner.py +0 -0
- server/memory/symbols.py +0 -0
- server/process/__init__.py +16 -0
- server/process/launcher.py +608 -0
- server/process/manager.py +185 -0
- server/process/monitors.py +202 -0
- server/process/permissions.py +131 -0
- server/process_whitelist.json +119 -0
- server/pyautogui/__init__.py +0 -0
- server/utils/__init__.py +37 -0
- server/utils/data_types.py +368 -0
- server/utils/formatters.py +430 -0
- server/utils/validators.py +340 -0
- server/window_automation/__init__.py +59 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Validation Utilities
|
|
3
|
+
Input validation and sanitization functions
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Optional, Any
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
def validate_address(address_str: str) -> Optional[int]:
|
|
13
|
+
"""Validate and parse a memory address string
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
address_str: Address string in hex format (e.g., "0x12345678", "12345678")
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
Parsed address as integer, or None if invalid
|
|
20
|
+
"""
|
|
21
|
+
try:
|
|
22
|
+
if not isinstance(address_str, str):
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
# Remove whitespace
|
|
26
|
+
address_str = address_str.strip()
|
|
27
|
+
|
|
28
|
+
if not address_str:
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
# Handle different hex formats
|
|
32
|
+
if address_str.startswith('0x') or address_str.startswith('0X'):
|
|
33
|
+
address_str = address_str[2:]
|
|
34
|
+
|
|
35
|
+
# Validate hex characters
|
|
36
|
+
if not re.match(r'^[0-9A-Fa-f]+$', address_str):
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
# Convert to integer
|
|
40
|
+
address = int(address_str, 16)
|
|
41
|
+
|
|
42
|
+
# Basic sanity checks
|
|
43
|
+
if address < 0:
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
# Check reasonable address bounds
|
|
47
|
+
if address > 0x7FFFFFFFFFFFFFFFFFFF: # 64-bit max
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
# Warn about suspicious addresses
|
|
51
|
+
if address < 0x1000:
|
|
52
|
+
logger.warning(f"Address {address:X} is in null page range")
|
|
53
|
+
|
|
54
|
+
return address
|
|
55
|
+
|
|
56
|
+
except (ValueError, OverflowError) as e:
|
|
57
|
+
logger.debug(f"Invalid address format '{address_str}': {e}")
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
def validate_size(size: Any) -> bool:
|
|
61
|
+
"""Validate a size parameter
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
size: Size value to validate
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
True if valid, False otherwise
|
|
68
|
+
"""
|
|
69
|
+
try:
|
|
70
|
+
if not isinstance(size, int):
|
|
71
|
+
if isinstance(size, str):
|
|
72
|
+
size = int(size)
|
|
73
|
+
else:
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
# Must be positive
|
|
77
|
+
if size <= 0:
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
# Reasonable upper limit (100MB)
|
|
81
|
+
if size > 100 * 1024 * 1024:
|
|
82
|
+
logger.warning(f"Large size requested: {size} bytes")
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
return True
|
|
86
|
+
|
|
87
|
+
except (ValueError, TypeError):
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
def validate_pattern(pattern: str) -> bool:
|
|
91
|
+
"""Validate a byte pattern string
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
pattern: Pattern string to validate (e.g., "48 8B 05 ?? ?? ?? ??")
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
True if valid, False otherwise
|
|
98
|
+
"""
|
|
99
|
+
try:
|
|
100
|
+
if not isinstance(pattern, str):
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
# Remove extra whitespace
|
|
104
|
+
pattern = re.sub(r'\s+', ' ', pattern.strip())
|
|
105
|
+
|
|
106
|
+
if not pattern:
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
# Split into byte components
|
|
110
|
+
bytes_parts = pattern.split(' ')
|
|
111
|
+
|
|
112
|
+
for byte_part in bytes_parts:
|
|
113
|
+
if byte_part in ['??', '?']:
|
|
114
|
+
continue # Wildcard is valid
|
|
115
|
+
|
|
116
|
+
# Must be valid hex byte
|
|
117
|
+
if not re.match(r'^[0-9A-Fa-f]{1,2}$', byte_part):
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
# Convert to ensure it's a valid byte value
|
|
121
|
+
byte_val = int(byte_part, 16)
|
|
122
|
+
if byte_val > 255:
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
return True
|
|
126
|
+
|
|
127
|
+
except (ValueError, TypeError):
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
def validate_process_identifier(identifier: str) -> bool:
|
|
131
|
+
"""Validate a process identifier (PID or name)
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
identifier: Process identifier to validate
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
True if valid, False otherwise
|
|
138
|
+
"""
|
|
139
|
+
try:
|
|
140
|
+
if not isinstance(identifier, str):
|
|
141
|
+
return False
|
|
142
|
+
|
|
143
|
+
identifier = identifier.strip()
|
|
144
|
+
|
|
145
|
+
if not identifier:
|
|
146
|
+
return False
|
|
147
|
+
|
|
148
|
+
# Check if it's a PID (numeric)
|
|
149
|
+
if identifier.isdigit():
|
|
150
|
+
pid = int(identifier)
|
|
151
|
+
return 1 <= pid <= 65535 # Reasonable PID range
|
|
152
|
+
|
|
153
|
+
# Check if it's a valid process name
|
|
154
|
+
# Allow alphanumeric, underscore, dash, dot
|
|
155
|
+
if re.match(r'^[a-zA-Z0-9_\-\.]+$', identifier):
|
|
156
|
+
return len(identifier) <= 255 # Reasonable name length
|
|
157
|
+
|
|
158
|
+
return False
|
|
159
|
+
|
|
160
|
+
except (ValueError, TypeError):
|
|
161
|
+
return False
|
|
162
|
+
|
|
163
|
+
def validate_data_type(data_type: str) -> bool:
|
|
164
|
+
"""Validate a data type string
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
data_type: Data type to validate
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
True if valid, False otherwise
|
|
171
|
+
"""
|
|
172
|
+
valid_types = {
|
|
173
|
+
'raw', 'bytes',
|
|
174
|
+
'int8', 'uint8', 'byte',
|
|
175
|
+
'int16', 'uint16', 'short', 'ushort',
|
|
176
|
+
'int32', 'uint32', 'int', 'uint', 'dword',
|
|
177
|
+
'int64', 'uint64', 'long', 'ulong', 'qword',
|
|
178
|
+
'float', 'single',
|
|
179
|
+
'double',
|
|
180
|
+
'string', 'str', 'ascii', 'utf8', 'utf16',
|
|
181
|
+
'pointer', 'ptr',
|
|
182
|
+
'auto', 'structure', 'struct'
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if not isinstance(data_type, str):
|
|
186
|
+
return False
|
|
187
|
+
|
|
188
|
+
return data_type.lower() in valid_types
|
|
189
|
+
|
|
190
|
+
def sanitize_filename(filename: str) -> str:
|
|
191
|
+
"""Sanitize a filename for safe file operations
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
filename: Filename to sanitize
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
Sanitized filename
|
|
198
|
+
"""
|
|
199
|
+
if not isinstance(filename, str):
|
|
200
|
+
return "invalid_filename"
|
|
201
|
+
|
|
202
|
+
# Remove or replace invalid characters
|
|
203
|
+
sanitized = re.sub(r'[<>:"/\\|?*]', '_', filename)
|
|
204
|
+
|
|
205
|
+
# Remove control characters
|
|
206
|
+
sanitized = ''.join(char for char in sanitized if ord(char) >= 32)
|
|
207
|
+
|
|
208
|
+
# Limit length
|
|
209
|
+
sanitized = sanitized[:255]
|
|
210
|
+
|
|
211
|
+
# Ensure it's not empty or just dots
|
|
212
|
+
if not sanitized or sanitized in ['.', '..']:
|
|
213
|
+
sanitized = "unnamed_file"
|
|
214
|
+
|
|
215
|
+
return sanitized
|
|
216
|
+
|
|
217
|
+
def validate_region_list(regions: Any) -> bool:
|
|
218
|
+
"""Validate a list of memory regions
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
regions: Region list to validate
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
True if valid, False otherwise
|
|
225
|
+
"""
|
|
226
|
+
try:
|
|
227
|
+
if not isinstance(regions, list):
|
|
228
|
+
return False
|
|
229
|
+
|
|
230
|
+
if len(regions) > 100: # Reasonable limit
|
|
231
|
+
return False
|
|
232
|
+
|
|
233
|
+
for region in regions:
|
|
234
|
+
if isinstance(region, str):
|
|
235
|
+
# Should be a hex address
|
|
236
|
+
if not validate_address(region):
|
|
237
|
+
return False
|
|
238
|
+
elif isinstance(region, dict):
|
|
239
|
+
# Should have 'base' and 'size' keys
|
|
240
|
+
if 'base' not in region or 'size' not in region:
|
|
241
|
+
return False
|
|
242
|
+
if not validate_address(str(region['base'])):
|
|
243
|
+
return False
|
|
244
|
+
if not validate_size(region['size']):
|
|
245
|
+
return False
|
|
246
|
+
else:
|
|
247
|
+
return False
|
|
248
|
+
|
|
249
|
+
return True
|
|
250
|
+
|
|
251
|
+
except (TypeError, KeyError):
|
|
252
|
+
return False
|
|
253
|
+
|
|
254
|
+
def validate_config_value(key: str, value: Any) -> bool:
|
|
255
|
+
"""Validate a configuration value
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
key: Configuration key
|
|
259
|
+
value: Value to validate
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
True if valid, False otherwise
|
|
263
|
+
"""
|
|
264
|
+
config_validators = {
|
|
265
|
+
'max_memory_read': lambda v: isinstance(v, int) and 0 < v <= 1024 * 1024 * 1024,
|
|
266
|
+
'enable_disassembly': lambda v: isinstance(v, bool),
|
|
267
|
+
'log_level': lambda v: isinstance(v, str) and v.upper() in ['DEBUG', 'INFO', 'WARNING', 'ERROR'],
|
|
268
|
+
'read_only_mode': lambda v: isinstance(v, bool),
|
|
269
|
+
'process_whitelist': lambda v: isinstance(v, list) and all(isinstance(p, str) for p in v),
|
|
270
|
+
'audit_log_path': lambda v: isinstance(v, str) and len(v) > 0,
|
|
271
|
+
'symbol_cache_size': lambda v: isinstance(v, int) and v >= 0,
|
|
272
|
+
'scan_chunk_size': lambda v: isinstance(v, int) and 1024 <= v <= 10 * 1024 * 1024,
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
validator = config_validators.get(key)
|
|
276
|
+
if validator:
|
|
277
|
+
try:
|
|
278
|
+
return validator(value)
|
|
279
|
+
except Exception:
|
|
280
|
+
return False
|
|
281
|
+
|
|
282
|
+
# Unknown config key - allow but warn
|
|
283
|
+
logger.warning(f"Unknown configuration key: {key}")
|
|
284
|
+
return True
|
|
285
|
+
|
|
286
|
+
def validate_permissions(access_level: str) -> bool:
|
|
287
|
+
"""Validate an access level string
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
access_level: Access level to validate
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
True if valid, False otherwise
|
|
294
|
+
"""
|
|
295
|
+
valid_levels = {'read', 'debug', 'full'}
|
|
296
|
+
|
|
297
|
+
if not isinstance(access_level, str):
|
|
298
|
+
return False
|
|
299
|
+
|
|
300
|
+
return access_level.lower() in valid_levels
|
|
301
|
+
|
|
302
|
+
def validate_encoding(encoding: str) -> bool:
|
|
303
|
+
"""Validate a text encoding string
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
encoding: Encoding to validate
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
True if valid, False otherwise
|
|
310
|
+
"""
|
|
311
|
+
valid_encodings = {
|
|
312
|
+
'ascii', 'utf-8', 'utf8', 'utf-16', 'utf16', 'utf-16le', 'utf-16be',
|
|
313
|
+
'latin1', 'cp1252', 'cp437'
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if not isinstance(encoding, str):
|
|
317
|
+
return False
|
|
318
|
+
|
|
319
|
+
return encoding.lower() in valid_encodings
|
|
320
|
+
|
|
321
|
+
def sanitize_log_message(message: str) -> str:
|
|
322
|
+
"""Sanitize a log message to prevent log injection
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
message: Message to sanitize
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
Sanitized message
|
|
329
|
+
"""
|
|
330
|
+
if not isinstance(message, str):
|
|
331
|
+
return str(message)
|
|
332
|
+
|
|
333
|
+
# Remove newlines and carriage returns to prevent log injection
|
|
334
|
+
sanitized = message.replace('\n', '\\n').replace('\r', '\\r')
|
|
335
|
+
|
|
336
|
+
# Limit length
|
|
337
|
+
if len(sanitized) > 1000:
|
|
338
|
+
sanitized = sanitized[:997] + "..."
|
|
339
|
+
|
|
340
|
+
return sanitized
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PyWinAuto Integration Module for MCP Cheat Engine Server
|
|
3
|
+
=======================================================
|
|
4
|
+
|
|
5
|
+
This module provides comprehensive PyWinAuto integration for Windows application
|
|
6
|
+
automation through the MCP Cheat Engine Server.
|
|
7
|
+
|
|
8
|
+
Key Features:
|
|
9
|
+
- Application lifecycle management (launch, attach, close)
|
|
10
|
+
- Window and UI element discovery and manipulation
|
|
11
|
+
- Framework-agnostic automation (Win32, WPF, Qt, etc.)
|
|
12
|
+
- Element-based interaction (more reliable than coordinate-based)
|
|
13
|
+
- Advanced UI navigation and property inspection
|
|
14
|
+
- Integration with MCP server architecture
|
|
15
|
+
|
|
16
|
+
Components:
|
|
17
|
+
- core.integration: Main PyWinAuto controller and automation engine
|
|
18
|
+
- tools.mcp_tools: MCP tool definitions for Claude Desktop integration
|
|
19
|
+
- demos: Example scripts and usage demonstrations
|
|
20
|
+
- tests: Comprehensive testing suite
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
from server.window_automation import get_pywinauto_controller
|
|
24
|
+
|
|
25
|
+
controller = get_pywinauto_controller()
|
|
26
|
+
if controller.available:
|
|
27
|
+
app = controller.connect_application("notepad.exe")
|
|
28
|
+
window = controller.find_window(app, title="Untitled - Notepad")
|
|
29
|
+
controller.type_text(window, "Hello PyWinAuto!")
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from .core.integration import (
|
|
33
|
+
PyWinAutoController,
|
|
34
|
+
get_pywinauto_controller,
|
|
35
|
+
PYWINAUTO_AVAILABLE
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
from .tools.mcp_tools import (
|
|
39
|
+
ALL_PYWINAUTO_TOOLS,
|
|
40
|
+
PyWinAutoToolHandler
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
__version__ = "1.0.0"
|
|
44
|
+
__author__ = "MCP Cheat Engine Server"
|
|
45
|
+
|
|
46
|
+
__all__ = [
|
|
47
|
+
# Core integration
|
|
48
|
+
"PyWinAutoController",
|
|
49
|
+
"get_pywinauto_controller",
|
|
50
|
+
"PYWINAUTO_AVAILABLE",
|
|
51
|
+
|
|
52
|
+
# MCP tools
|
|
53
|
+
"ALL_PYWINAUTO_TOOLS",
|
|
54
|
+
"PyWinAutoToolHandler",
|
|
55
|
+
|
|
56
|
+
# Version info
|
|
57
|
+
"__version__",
|
|
58
|
+
"__author__"
|
|
59
|
+
]
|