Skip to main content
Glama

scan_unchanged

Identifies memory addresses in processes where values remain unchanged since the last scan, helping isolate static variables during game hacking and reverse engineering.

Instructions

Find addresses where value has NOT changed since last scan. Returns: Number of unchanged addresses.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • The main handler function for the 'scan_unchanged' tool. It filters existing scan results to keep only addresses where the memory value has not changed since the last scan. Uses Frida scripts to read current values in batches, compares hex representations to expected last values, and updates the scan state with matching addresses and their current unpacked values.
    @mcp.tool() def scan_unchanged() -> Dict[str, Any]: """ Find addresses where value has NOT changed since last scan. Returns: Number of unchanged addresses. """ global _session if not _session.is_attached(): return {"error": "Not attached. Use attach() first."} if not _session.scan_state.scan_active: return {"error": "No active scan. Use scan_value() first."} try: value_type = _session.scan_state.value_type value_size = _get_value_size(value_type) addresses = [a for a in _session.scan_state.results if a in _session.scan_state.last_values] if not addresses: return {"success": True, "remaining": 0} expected_map = {} for addr in addresses: last_val = _session.scan_state.last_values[addr] if value_type == "string": expected_map[addr] = str(last_val).encode('utf-8').hex() else: expected_map[addr] = _pack_value(last_val, value_type).hex() batch_size = 1000 new_results = [] new_last_values = {} for batch_start in range(0, len(addresses), batch_size): batch = addresses[batch_start:batch_start + batch_size] addr_expected = ", ".join(f'["{hex(a)}", "{expected_map[a]}"]' for a in batch) script_code = f""" var pairs = [{addr_expected}]; var size = {value_size}; var unchanged = []; for (var i = 0; i < pairs.length; i++) {{ try {{ var data = Memory.readByteArray(ptr(pairs[i][0]), size); var hex = ''; var bytes = new Uint8Array(data); for (var j = 0; j < bytes.length; j++) {{ hex += ('0' + bytes[j].toString(16)).slice(-2); }} if (hex === pairs[i][1]) {{ unchanged.push({{address: pairs[i][0], hex: hex}}); }} }} catch (e) {{ }} }} send(JSON.stringify(unchanged)); """ result_data = [] def on_message(message, data): if message['type'] == 'send': result_data.append(message['payload']) script = _session.session.create_script(script_code) script.on('message', on_message) script.load() script.unload() if result_data: import json unchanged = json.loads(result_data[0]) for c in unchanged: addr = int(c['address'], 16) try: current_value = _unpack_value(bytes.fromhex(c['hex']), value_type) new_results.append(addr) new_last_values[addr] = current_value except: pass _session.scan_state.results = new_results _session.scan_state.last_values = new_last_values return {"success": True, "remaining": len(new_results)} except Exception as e: return {"error": f"Scan unchanged failed: {str(e)}"}
  • The 'scan_unchanged' tool is listed in the memory_operations category within the list_capabilities tool response.
    "read_memory", "write_memory", "scan_value", "scan_next", "scan_changed", "scan_unchanged", "scan_pattern", "get_scan_results", "clear_scan", "list_memory_regions"
  • ScanState dataclass used by scan_unchanged to track results, last_values for comparison, value_type, and scan_active status.
    class ScanState: """Tracks memory scan state for Cheat Engine-style scanning.""" value_type: str = "" results: List[int] = field(default_factory=list) last_values: Dict[int, Any] = field(default_factory=dict) scan_active: bool = False
  • Helper function to determine byte size based on value_type, used for reading memory chunks.
    def _get_value_size(value_type: str) -> int: """Get byte size for value type.""" sizes = { "int8": 1, "uint8": 1, "int16": 2, "uint16": 2, "int32": 4, "uint32": 4, "int64": 8, "uint64": 8, "float": 4, "double": 8 } return sizes.get(value_type, 4)
  • Helper to unpack raw bytes from memory into Python value based on value_type, used to update last_values.
    def _unpack_value(data: bytes, value_type: str) -> Any: """Unpack bytes to value based on type.""" formats = { "int8": "<b", "uint8": "<B", "int16": "<h", "uint16": "<H", "int32": "<i", "uint32": "<I", "int64": "<q", "uint64": "<Q", "float": "<f", "double": "<d" } fmt = formats.get(value_type) if fmt: return struct.unpack(fmt, data)[0] elif value_type == "string": return data.split(b'\x00')[0].decode('utf-8', errors='replace') return struct.unpack("<i", data)[0]

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/0xhackerfren/frida-game-hacking-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server