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,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)
|