FFG.exe
testserver1.py

Im making Atom fishing server emulator. It's 2d game written on delphi. I need you to check first "testserver1.py" file and after that i need you to decompile FFG.exe and find CMD= of Inventory function with full structure, field names etc. (When i open inventory in the game, client sends CMD=160)

XOR key (52 bytes) — found at 0x568e7c in FFG.exe XOR_KEY = b"sdfgDDdffkojeroxdfJKHNplmkOIJHjklnIOJifg33gjkfsdpgko" XOR_KEY_LEN = 52

GameData.pkg RC6 key — found at 0x482c34 in FFG.exe RC6_KEY = b"jklHnKrt"

Dr.Binary

I'll read testserver1.py and open FFG.exe in Ghidra simultaneously.

Call workspace_read_file

Arguments
{"filepath": "/sandbox/testserver1.py"}

Tool result
{"result":"#!/usr/bin/env python3\n\"\"\"\nAtom Fishing Game Server Emulator\n===================================\nReverse-engineered from FFG.exe (client binary) and protocol analysis.\n\nPROTOCOL ANALYSIS (from FFG.exe binary reverse engineering):\n============================================================\n\nPACKET HEADER (20 bytes, big-endian):\n  struct TPacketHeader {\n    uint32_t cmd;      // Command ID\n    uint32_t flags;    // 0 = text/KV payload, 1 = binary payload\n    uint32_t length;   // Payload length in bytes\n    uint32_t seq;      // Sequence number (server increments, client echoes)\n    uint32_t csum;     // Checksum: ((seq + cmd) * 3) ^ 0x1773\n  };\n\nXOR ENCRYPTION (confirmed from 0x568e1b, 0x569231 in FFG.exe):\n  KEY = b\"sdfgDDdffkojeroxdfJKHNplmkOIJHjklnIOJifg33gjkfsdpgko\"  (52 bytes)\n  encrypted[i] = plaintext[i] ^ KEY[i % 52] ^ (seq & 0xFF)\n  - CMD=1 inbound: NOT encrypted (handshake)\n  - All other payloads: XOR encrypted when sent by server\n\nCHECKSUM (confirmed from 0x569231 in FFG.exe):\n  csum = ((seq + cmd) * 3) ^ 0x1773\n\"\"\"\nimport itertools\nimport socket\nimport struct\nimport threading\nimport time\nimport hashlib\nimport os\nimport traceback\nimport random\nfrom datetime import datetime\n\n# ═══════════════════════════════════════════════════════════════════\n# PROTOCOL CONSTANTS\n# ═══════════════════════════════════════════════════════════════════\n\n# XOR key (52 bytes) — found at 0x568e7c in FFG.exe\nXOR_KEY = b\"sdfgDDdffkojeroxdfJKHNplmkOIJHjklnIOJifg33gjkfsdpgko\"\nXOR_KEY_LEN = 52\n\n# GameData.pkg RC6 key — found at 0x482c34 in FFG.exe\nRC6_KEY = b\"jklHnKrt\"\n\n# Delphi TDateTime epoch\nDELPHI_EPOCH = datetime(1899, 12, 30)\n\n# Server network settings\nSERVER_HOST = \"0.0.0.0\"\nSERVER_PORT = 11000\n\n# Location/map file directories — adjust to your installation\nLOC_DIR = r\"E:\\Atom Fishing\\Data\\Maps\\Locs\"\nBASE_DIR = r\"E:\\Atom Fishing\\Data\\Maps\\Base\"\n\ndef brute_one_id_all_structs(srv, conn, player, cmd_id=160):\n    log(f\"===== BRUTE ONE ID={cmd_id} START =====\")\n\n    payloads = []\n\n    # ---- Пустые и простые ----\n    payloads += [\n        b\"\",\n        b\"\\x00\",\n        b\"\\x00\\x00\",\n        b\"\\x00\\x00\\x00\\x00\",\n        b\"\\xff\",\n        b\"\\xff\\xff\\xff\\xff\",\n    ]\n\n    # ---- Integer (разные endian и размеры) ----\n    for i in [0, 1, 2, 255, 256, 65535]:\n        payloads.append(struct.pack(\">I\", i))  # big endian\n        payloads.append(struct.pack(\"<I\", i))  # little endian\n        payloads.append(struct.pack(\">H\", i % 65536))\n        payloads.append(struct.pack(\"<H\", i % 65536))\n\n    # ---- Несколько чисел ----\n    payloads += [\n        struct.pack(\">II\", 1, 1),\n        struct.pack(\">III\", 1, 2, 3),\n        struct.pack(\"<III\", 1, 2, 3),\n    ]\n\n    # ---- Строки ----\n    payloads += [\n        b\"id=1\",\n        b\"opt=3\",\n        b\"map=1\",\n        b\"id=1\\r\\n\",\n        b\"opt=3\\r\\n\",\n        b\"map=1\\r\\n\",\n        b\"AAAA\",\n        b\"A\" * 32,\n        b\"A\" * 128,\n    ]\n\n    # ---- Null-terminated ----\n    payloads += [\n        b\"id=1\\x00\",\n        b\"test\\x00\",\n    ]\n\n    # ---- Случайные байты ----\n    for _ in range(20):\n        payloads.append(os.urandom(random.randint(1, 64)))\n\n    # ---- Повторяющиеся паттерны ----\n    payloads += [\n        b\"\\x00\" * 16,\n        b\"\\xff\" * 16,\n        b\"\\x41\\x41\\x41\\x41\",\n    ]\n\n    # ---- Длины (edge cases) ----\n    for size in [1, 2, 4, 8, 16, 32, 64, 128]:\n        payloads.append(b\"A\" * size)\n\n    # ---- Основной цикл ----\n    for flags in (0, 1):\n        for payload in payloads:\n            try:\n                log(f\"[BRUTE] CMD={cmd_id} flags={flags} len={len(payload)}\")\n\n                srv.send_pkt(\n                    conn,\n                    cmd=cmd_id,\n                    payload=payload,\n                    flags=flags,\n                    encrypt=False\n                )\n\n                time.sleep(2)\n\n            except Exception as e:\n                log(f\"[ERR] → {e}\")\n                return\n\n    log(\"===== BRUTE END =====\")\n\ndef send_inventory_test(srv, conn):\n    # пробуем 1 предмет\n    payload = b\"\"\n\n    # количество предметов\n    payload += struct.pack(\">I\", 1)\n\n    # сам предмет (грубая попытка)\n    payload += struct.pack(\n        \">IIIIIIII\",\n        1,      # inid (slot)\n        2001,   # id (леска)\n        0,      # x\n        0,      # y\n        4,      # w\n        4,      # h\n        1,      # count\n        0       # flags\n    )\n\n    srv.send_pkt(conn, cmd=168, payload=payload, flags=1)\n\ndef brute_opt4(srv, conn, player):\n    log(\"===== BRUTE OPTID=3 START =====\")\n\n    test_cmds = list(range(15, 17))\n    payload = struct.pack(\"<II\", 1, 1)  # 2x uint32\n\n    for cmd in test_cmds:\n        try:\n            log(f\"[BRUTE] CMD={cmd} flags={1} len={len(payload)}\")\n\n            srv.send_pkt(conn, cmd=cmd, payload=payload, flags=1, encrypt=False)\n                        # маленькая пауза чтобы клиент успел отреагировать\n            time.sleep(2)\n            \n        except Exception as e:\n            log(f\"[ERR] CMD={cmd} → {e}\")\n            return\n\ndef brute_opt3(srv, conn, player):\n    log(\"===== BRUTE OPTID=3 START =====\")\n\n    test_cmds = list(range(201, 300))\n\n    payloads = [\n        b\"\",                                # пусто\n        b\"\\x00\\x00\\x00\\x01\",               # int=1\n        struct.pack(\">I\", 1),\n        struct.pack(\">II\", 1, 1),\n        b\"map=1\\r\\n\",\n        b\"id=1\\r\\n\",\n        b\"opt=3\\r\\n\",\n    ]\n\n    for cmd in test_cmds:\n        for flags in (0, 1):\n            for payload in payloads:\n                try:\n                    log(f\"[BRUTE] CMD={cmd} flags={flags} len={len(payload)}\")\n\n                    srv.send_pkt(conn, cmd=cmd, payload=payload, flags=flags, encrypt=False)\n\n                    # маленькая пауза чтобы клиент успел отреагировать\n                    time.sleep(0.15)\n\n                except Exception as e:\n                    log(f\"[ERR] CMD={cmd} → {e}\")\n                    return\n\ndef brute_cmd201(srv, conn, player):\n    log(\"===== BRUTE CMD=201 START =====\")\n\n    cmd = 113\n\n    payloads = []\n\n    # ── 1. Пустые / контрольные ──\n    payloads += [\n        b\"\",\n        b\"\\x00\",\n        b\"\\x00\\x00\\x00\\x00\",\n    ]\n\n    # ── 2. 32-bit ints ──\n    payloads += [\n        struct.pack(\">I\", 1),\n        struct.pack(\"<I\", 1),\n        struct.pack(\">I\", 3),\n    ]\n\n    # ── 3. 2x int struct ──\n    payloads += [\n        struct.pack(\">II\", 1, 1),\n        struct.pack(\"<II\", 1, 1),\n        struct.pack(\">II\", 201, 1),\n    ]\n\n    # ── 4. text KV ──\n    payloads += [\n        b\"opt=3\\r\\n\",\n        b\"optid=3\\r\\n\",\n        b\"id=201\\r\\n\",\n        b\"cmd=201\\r\\n\",\n    ]\n\n    # ── 5. возможный игровой стиль ──\n    payloads += [\n        b\"map=1\\r\",\n        b\"mapid=1\\r\\n\",\n        b\"type=3\\r\\n\",\n        b\"subid=3\\r\\n\",\n    ]\n\n    # ── BRUTE LOOP ──\n    for flags in (0, 1):\n        for payload in payloads:\n            try:\n                log(f\"[BRUTE] CMD=201 flags={flags} len={len(payload)} data={payload!r}\")\n\n                srv.send_pkt(\n                    conn,\n                    cmd=112,\n                    payload=payload,\n                    flags=flags,\n                    encrypt=False\n                )\n\n                time.sleep(0.10)\n\n            except Exception as e:\n                log(f\"[ERR] CMD=201 → {e}\")\n                return\n\n    log(\"===== BRUTE CMD=201 END =====\")\n\n    log(\"===== BRUTE END =====\")\n\ndef handle_map_enter(srv, conn, player, mapid):\n    log(f\"[ENTER MAP] id={mapid}\")\n\n    # 1. отправить локацию\n    send_location(srv, conn, loc_id=mapid, map_id=mapid)\n\n    # 2. базовые данные\n    send_player_data(srv, conn, player)\n\n    # 3. окружение\n    send_entities(srv, conn)\n    send_tile_info(srv, conn)\n\n    # 4. финализация\n    send_server_time(srv, conn)\n\n# ═══════════════════════════════════════════════════════════════════\n# CRYPTO / CHECKSUM\n# ═══════════════════════════════════════════════════════════════════\n\n\ndef xor_crypt(data: bytes, seq: int) -> bytes:\n    \"\"\"XOR cipher: data[i] ^= KEY[i % 52] ^ (seq & 0xFF)\"\"\"\n    seq_byte = seq & 0xFF\n    return bytes(b ^ XOR_KEY[i % XOR_KEY_LEN] ^ seq_byte for i, b in enumerate(data))\n\n\ndef make_csum(cmd: int, seq: int) -> int:\n    \"\"\"Checksum: ((seq + cmd) * 3) ^ 0x1773  — confirmed at 0x569231\"\"\"\n    return ((seq + cmd) * 3) ^ 0x1773\n\n\n# ═══════════════════════════════════════════════════════════════════\n# RAW SOCKET I/O\n# ═══════════════════════════════════════════════════════════════════\n\n\ndef recv_exact(conn: socket.socket, n: int) -> bytes:\n    buf = b\"\"\n    while len(buf) < n:\n        chunk = conn.recv(n - len(buf))\n        if not chunk:\n            raise ConnectionError(\"Connection closed by client\")\n        buf += chunk\n    return buf\n\n\ndef recv_pkt(conn: socket.socket):\n    \"\"\"\n    Read one complete packet from the socket.\n    Header: 5 × uint32 big-endian = 20 bytes\n    Returns: (cmd, flags, seq, payload_decrypted)\n    \"\"\"\n    hdr = recv_exact(conn, 20)\n    cmd, flags, length, seq, csum = struct.unpack(\">IIIII\", hdr)\n\n    expected = make_csum(cmd, seq)\n    csum_ok = csum == expected\n\n    payload_raw = recv_exact(conn, length) if length > 0 else b\"\"\n\n    # CMD=1 handshake payload is never XOR-encrypted\n    if cmd != 1 and length > 0:\n        payload = xor_crypt(payload_raw, seq)\n    else:\n        payload = payload_raw\n\n    log(\n        f\"<-- CMD={cmd} flags={flags} len={length} seq={seq} \"\n        f\"csum={'OK' if csum_ok else f'BAD(exp=0x{expected:08X})'}\"\n    )\n    if payload and len(payload) < 512:\n        try:\n            log(f\"    TXT: {payload.decode('utf-8', errors='replace')!r}\")\n        except Exception:\n            log(f\"    HEX: {payload.hex()}\")\n\n    return cmd, flags, seq, payload\n\n\n# ═══════════════════════════════════════════════════════════════════\n# SERVER STATE (per-connection)\n# ═══════════════════════════════════════════════════════════════════\n\n\nclass ServerState:\n    def __init__(self):\n        self.seq = 0  # server-side sequence counter (increments per send)\n\n    def send_pkt(\n        self,\n        conn: socket.socket,\n        cmd: int,\n        payload: bytes,\n        flags: int = 0,\n        encrypt: bool = False,\n    ):\n        \"\"\"\n        Build and transmit a packet.\n          flags=0 → text/KV payload (plain)\n          flags=1 → binary payload\n          encrypt → apply XOR cipher to payload before sending\n        \"\"\"\n        self.seq += 1\n        seq = self.seq\n        data = xor_crypt(payload, seq) if encrypt else payload\n        csum = make_csum(cmd, seq)\n        hdr = struct.pack(\">IIIII\", cmd, flags, len(payload), seq, csum)\n        conn.sendall(hdr + data)\n\n        enc_str = \"XOR\" if encrypt else \"plain\"\n        log(\n            f\"--> CMD={cmd} flags={flags} seq={seq} len={len(payload)} \"\n            f\"csum=0x{csum:08X} [{enc_str}]\"\n        )\n        if payload and len(payload) < 512:\n            try:\n                log(f\"    TXT: {payload.decode('utf-8', errors='replace')!r}\")\n            except Exception:\n                log(f\"    HEX: {payload.hex()}\")\n\n\n# ═══════════════════════════════════════════════════════════════════\n# UTILITIES\n# ═══════════════════════════════════════════════════════════════════\n\n\ndef log(msg: str):\n    ts = datetime.now().strftime(\"%H:%M:%S.%f\")[:-3]\n    print(f\"[{ts}] {msg}\")\n\n\ndef parse_kv(payload: bytes) -> dict:\n    \"\"\"Parse key=value payload (\\\\r\\\\n or \\\\n separated) into dict.\"\"\"\n    result = {}\n    text = payload.replace(b\"\\r\\n\", b\"\\n\").replace(b\"\\r\", b\"\\n\")\n    for line in text.split(b\"\\n\"):\n        line = line.strip()\n        if b\"=\" in line:\n            k, _, v = line.partition(b\"=\")\n            result[k.decode(\"utf-8\", errors=\"replace\").strip()] = v.decode(\n                \"utf-8\", errors=\"replace\"\n            ).strip()\n    return result\n\n\ndef delphi_now() -> float:\n    \"\"\"Current time as Delphi TDateTime (double: days since 1899-12-30).\"\"\"\n    delta = datetime.now() - DELPHI_EPOCH\n    return delta.days + delta.seconds / 86400.0 + delta.microseconds / 86_400_000_000.0\n\n\ndef delphi_datetime_str() -> str:\n    \"\"\"\n    Current date/time as Delphi-compatible string: D.M.YYYY H:MM:SS.0\n    Used in the 'datetime=' field of CMD=100 player data.\n    Delphi's StrToDateTime expects this exact format (no leading zeros in\n    day/month/hour is fine; the decimal .0 represents milliseconds).\n    \"\"\"\n    now = datetime.now()\n    return f\"{now.day}.{now.month}.{now.year} {now.hour}:{now.minute:02d}:{now.second:02d}.0\"\n\n\ndef get_game_time() -> tuple:\n    \"\"\"\n    Game time: 24 game-hours = 1 real hour.\n    Step = 10 game-minutes = 25 real seconds.\n    Returns: (real_datetime, game_hours, game_minutes)\n    \"\"\"\n    now = datetime.now()\n    real_seconds = now.minute * 60 + now.second\n    step = real_seconds // 25\n    game_hours = step // 6\n    game_minutes = (step % 6) * 10\n    return now, game_hours, game_minutes\n\n\ndef get_loc_hash(loc_id: int, map_id: int) -> str:\n    \"\"\"MD5 of the .loc or .base file for the given location.\"\"\"\n    if loc_id > 0:\n        filepath = os.path.join(LOC_DIR, f\"{loc_id}.loc\")\n    else:\n        filepath = os.path.join(BASE_DIR, f\"{map_id}.base\")\n\n    if not os.path.exists(filepath):\n        log(f\"  [HASH] File not found: {filepath} → returning zero hash\")\n        return \"00000000000000000000000000000000\"\n\n    with open(filepath, \"rb\") as f:\n        data = f.read()\n    md5 = hashlib.md5(data).hexdigest().upper()\n    log(f\"  [HASH] {filepath} → {md5}\")\n    return md5\n\n\n# ═══════════════════════════════════════════════════════════════════\n# PLAYER PROFILE BUILDER\n# ═══════════════════════════════════════════════════════════════════\n\n\nKV_BRUTE_MODE = False\n\ndef kv_bruteforce_inventory():\n    \"\"\"\n    Генерирует безопасные KV-варианты инвентаря\n    (не ломает клиент, только текст)\n    \"\"\"\n    keys = [\n        \"inv\", \"inventory\", \"item\", \"items\",\n        \"slot\", \"bag\", \"cell\", \"obj\"\n    ]\n\n    out = []\n\n    for prefix in keys:\n        for i in range(3):  # не спамим\n            out.append(f\"{prefix}{i}_id=2001\\r\\n\")\n            out.append(f\"{prefix}{i}_x={i}\\r\\n\")\n            out.append(f\"{prefix}{i}_y=0\\r\\n\")\n            out.append(f\"{prefix}{i}_w=4\\r\\n\")\n            out.append(f\"{prefix}{i}_h=4\\r\\n\")\n\n    # альтернативные форматы\n    out.append(\"invcount=1\\r\\n\")\n    out.append(\"itemcount=1\\r\\n\")\n    out.append(\"bagcount=1\\r\\n\")\n\n    return \"\".join(out)\n\n\n# ──────────────────�"}

Call ghidra_open_server

Arguments
{"filepath": "/sandbox/FFG.exe"}

Tool result
{"result":"compilerID: borlanddelphi\nimports[3]:\n  - functions[1]: ShellExecuteA\n    name: SHELL32.DLL\n  - functions[182]: CharNextA,MessageBoxA,LoadStringA,DefWindowProcA,DispatchMessageA,DispatchMessageW,EnumWindows,GetCapture,GetCursorPos,GetParent,GetPropA,GetWindow,GetWindowLongA,GetWindowThreadProcessId,IsDialogMessageA,IsDialogMessageW,IsIconic,IsWindowEnabled,IsWindowUnicode,IsWindowVisible,KillTimer,PeekMessageA,PeekMessageW,PtInRect,SendMessageA,SendMessageW,SetActiveWindow,SetTimer,SetWindowPos,SetWindowTextA,ShowOwnedPopups,ShowWindow,SystemParametersInfoA,TranslateMDISysAccel,TranslateMessage,UnhookWindowsHookEx,WaitMessage,WindowFromPoint,ActivateKeyboardLayout,CharLowerA,BeginPaint,CallNextHookEx,ChangeDisplaySettingsA,CharLowerBuffA,CharNextA,CharUpperBuffA,CheckMenuItem,ClientToScreen,CloseClipboard,CreateMenu,CreatePopupMenu,DeleteMenu,DestroyMenu,DestroyWindow,DrawEdge,DrawFrameControl,DrawMenuBar,DrawTextA,EnableMenuItem,EnableWindow,EndPaint,EnumClipboardFormats,EnumThreadWindows,EqualRect,FillRect,FindWindowA,FlashWindow,FrameRect,GetActiveWindow,GetClassInfoA,GetClassLongA,GetClassNameA,GetClipboardData,GetCursor,GetDC,GetDCEx,GetDesktopWindow,GetFocus,GetIconInfo,GetKeyNameTextA,GetKeyState,GetKeyboardLayout,GetKeyboardLayoutList,GetKeyboardLayoutNameA,GetLastActivePopup,GetMenu,GetMenuItemCount,GetMenuItemID,GetMenuItemInfoA,GetMenuState,GetMenuStringA,GetSubMenu,GetSysColor,GetSystemMenu,GetSystemMetrics,GetTopWindow,GetWindowPlacement,GetWindowRect,GetWindowTextA,InflateRect,InsertMenuA,InsertMenuItemA,IntersectRect,InvalidateRect,IsChild,IsWindow,LoadBitmapA,LoadCursorA,LoadIconA,MapVirtualKeyA,MapWindowPoints,MessageBoxA,MsgWaitForMultipleObjects,OemToCharA,OffsetRect,OpenClipboard,PostMessageA,PostQuitMessage,RedrawWindow,RegisterClassA,RegisterWindowMessageA,ReleaseCapture,ReleaseDC,RemoveMenu,RemovePropA,ScreenToClient,ScrollWindow,SetCapture,SetClassLongA,SetClipboardData,SetCursor,SetFocus,SetForegroundWindow,SetMenu,SetMenuItemInfoA,SetParent,SetPropA,SetRect,SetWindowLongA,SetWindowPlacement,SetWindowsHookExA,ShowCursor,UnregisterClassA,UpdateWindow,CreateWindowExA,CharToOemA,CreateIcon,EmptyClipboard,GetWindowLongW,LoadStringA,MessageBeep,SetWindowLongW,DrawIcon,GetKeyboardType,AdjustWindowRectEx,EnumChildWindows,GetClientRect,GetKeyboardState,GetMessagePos,GetWindowDC,IsRectEmpty,DestroyIcon,CallWindowProcA,DefFrameProcA,SetScrollInfo,RegisterClipboardFormatA,SetScrollRange,DestroyWindow,LoadKeyboardLayoutA,EnableScrollBar,DrawIconEx,GetSysColorBrush,GetScrollPos,ShowScrollBar,SetScrollPos,GetScrollRange,TrackPopupMenu,GetForegroundWindow,GetScrollInfo,IsZoomed,DestroyCursor,DefMDIChildProcA\n    name: USER32.DLL\n  - name: truncated... (more than 100 imports found)\n    functions[1]: truncated\nexports[1]{address,name}:\n  0056c060,entry\nformat: Portable Executable (PE)\nlanguageID: \"x86:LE:32:default\"\nprocessor: x86\nendianess: Little Endian\nsegments[12]{name,start,end}:\n  Headers,\"00400000\",004003ff\n  .text,\"00401000\",0056a7ff\n  .itext,0056b000,0056c1ff\n  .data,0056d000,005713ff\n  .bss,\"00572000\",0057e43b\n  .idata,0057f000,005821ff\n  .tls,\"00583000\",\"00583033\"\n  .rdata,\"00584000\",005841ff\n  .vmp0,\"00585000\",005a01ff\n  .rsrc,005a1000,005a83ff\n  .vmp1,005a9000,005a91ff\n  .reloc,005aa000,005c51ff\naddressSize: 32 bit\nimageBase: \"00400000\"\nnamedFunctions[1]{address,name}:\n  0056c060,entry\nstrings[101]{address,value}:\n  0050480d,\"\\\\rTIdHeaderListPGP\"\n  004a228c,glListParameteriSGIX\n  004e8ff0,Weapons.prf\n  \"00502010\",Shift_JIS\n  005815c8,SetEndOfFile\n  004d72b8,ZacepMap.bin\n  004b2310,glGetVertexAttribfvARB\n  00426bfa,Graphics\n  004b66d0,glBindLightParameterEXT\n  004a2b20,glTexCoord2fVertex3fvSUN\n  \"00468640\",LoadTGATexture\n  004bd3bc,wglAllocateMemoryNV\n  004d8774,\"[abrag]\"\n  00581da4,ImageList_SetImageCount\n  0049a3e8,glRasterPos2f\n  0049fc50,glDepthBoundsEXT\n  0052983c,TURN_TYPE\n  \"00580e82\",DefWindowProcA\n  005208a8,fishid\n  0045c494,PixelsPerInch\n  \"00434950\",clWebSienna\n  004ff2b8,ISO646-CN\n  004a6830,GL_EXT_pixel_transform_color_table\n  004a7630,GL_SGIX_async\n  0049b9cc,glUniformMatrix3fv\n  0055a964,AMBIENT_\n  00581b18,LookupPrivilegeValueW\n  004b8ed8,glFlushVertexArrayRangeNV\n  0055c608,***********************************\n  0049e8ec,glCullParameterdvEXT\n  \"00500114\",ISO-8859-8-E\n  004b71a0,glColorPointervINTEL\n  \"00500238\",csISO141JUSIB1002\n  004f8748,error.wav\n  004b8338,glMultiTexCoord4hvNV\n  0049dfa8,glVertexStream2dvATI\n  004ffec4,ECMA-114\n  \"00438124\",%s%s%s%s%s%s%s%s%s%s\n  004bcf94,wglGenlockSampleRateI3D\n  0049b420,glWindowPos2f\n  \"00533384\",LOC_NAMES\n  004a2510,glColorTableParameterivSGI\n  004b623c,glShaderOp2EXT\n  004ee7bc,cell=\n  0054d9a4,_Hist\n  004ba748,glFragmentLightModelfSGIX\n  00580ce8,FlashWindow\n  004ffe9c,iso-ir-127\n  0049b650,glDrawBuffers\n  \"00501e14\",csCESU-8\n  0049f20c,glSecondaryColor3fvEXT\n  004b54b4,glColorTableEXT\n  \"00469010\",Input_JoyDirections\n  004be0d4,glDeleteQueriesARB\n  004a7ef8,WGL_EXT_swap_control\n  0043bcf8,ShortCutText\n  0043057c,\"\\\\nParentFont\"\n  0058040c,kernel32.dll\n  004b2ce0,glAlphaFragmentOp2ATI\n  005013f8,IBM880\n  00500cb8,CP284\n  \"00516560\",/debug\n  0049c568,glMultiTexCoord4iARB\n  0055df18,\"Render error. Comp:\"\n  0046fd80,WSASetService\n  00499f28,glGetTexParameterfv\n  0049edac,glHistogramEXT\n  0049c298,glActiveTextureARB\n  004dcf40,\" %s %s\"\n  004f0964,Transmutation OK\n  004a8aec,glColorPointer\n  004ffed8,ASMO-708\n  004facc8,lottimenow\n  004af848,glWeightusvARB\n  004ba68c,glFragmentLightfvSGIX\n  004a9930,glGetTexGenfv\n  004348f0,clWebOrange\n  004abaac,glTexCoord4iv\n  004fe7a8,ANSI_X3.4-1968\n  \"00580984\",InsertMenuItemA\n  004f4328,wpndstid=\n  004a0954,glColor4hNV\n  0049a210,glNormal3fv\n  00434d4c,clWebTurquoise\n  \"00512260\",SERVER\n  \"00500498\",greek-ccitt\n  004aef64,glMultiTexCoord3dvARB\n  004aa408,glMatrixMode\n  005819b2,FindResourceA\n  004fe9e0,iso-ir-8-1\n  \"00581770\",GetTimeZoneInformation\n  004ff334,NS_4551-1\n  005023bc,Adobe-Standard-Encoding\n  004273c4,clGrayText\n  \"004e1004\",\"[warning]\"\n  004347b8,clWebRed\n  00519ba4,name_used\n  \"00581588\",SetThreadLocale\n  004a0f34,glCombinerOutputNV\n  004ab364,glScalef\n  truncated... (more than 100 strings found),truncated\nfileSize: 1769472 bytes\nentryPoint: 0056c060"}

continue

Dr.Binary

continue

Dr.Binary

Let me search for inventory-related strings and functions related to CMD=160.

Call ghidra_list_strings

Arguments
{"filepath": "/sandbox/FFG.exe", "filter": "inv"}

Tool result
{"result":["0046dce4: \"Invalid ACharPos in CharIsInSet.\"","0049f814: \"glBeginVertexShaderEXT\"","0049f91c: \"glSetInvariantEXT\"","0049fb18: \"glGetInvariantBooleanvEXT\"","0049fb34: \"glGetInvariantIntegervEXT\"","0049fb50: \"glGetInvariantFloatvEXT\"","004a0d98: \"glPointParameteriNV\"","004a0f08: \"glCombinerParameteriNV\"","004b60e8: \"glBeginVertexShaderEXT\"","004b63f8: \"glSetInvariantEXT\"","004b6948: \"glGetInvariantBooleanvEXT\"","004b698c: \"glGetInvariantIntegervEXT\"","004b69d0: \"glGetInvariantFloatvEXT\"","004b89bc: \"glPointParameteriNV\"","004b8b50: \"glCombinerParameteriNV\"","004dc32c: \"no_inv\"","004e74e8: \"msg=inv_full\"","004e8574: \"invlines=\"","004e8f48: \"InvLines\"","004e9db8: \"InvLines\"","004eb414: \"to_inv\"","004f3060: \"no_inventory\"","004fe8dc: \"INVARIANT\"","004fe8f0: \"csINVARIANT\"","0054bf8c: \"invlines\"","0056eaa1: \"invalid window size\"","0056ef78: \"invalid block type\"","0056ef8b: \"invalid stored block lengths\"","0056efcc: \"invalid bit length repeat\"","0056efe8: \"invalid literal/length code\"","0056f004: \"invalid distance code\"","0056f060: \"invalid distance code\"","0056f076: \"invalid literal/length code\"","00580962: \"InvalidateRect\""]}

Call ghidra_search_functions_by_name

Arguments
{"filepath": "/sandbox/FFG.exe", "query": "inventory"}

Tool result
{"result":["No functions matching 'inventory'"]}

Good — found key inventory strings. Now let me find the CMD dispatch table (looking for 0xA0 = 160) and cross-refs to invlines=.

Call ghidra_get_xrefs_to

Arguments
{"filepath": "/sandbox/FFG.exe", "address": "0x004e8574"}

Call ghidra_get_xrefs_to

Arguments
{"filepath": "/sandbox/FFG.exe", "address": "0x004f3060"}

Call ghidra_get_xrefs_to

Arguments
{"filepath": "/sandbox/FFG.exe", "address": "0x004eb414"}
Dr.Binary

Inference interrupted by user.

continue

Dr.Binary
Dr.Binary

Inference interrupted by user.

continue

Dr.Binary
Dr.Binary

Inference interrupted by user.

continue

Dr.Binary

Call ghidra_get_xrefs_to

Arguments
{"filepath": "/sandbox/FFG.exe", "address": "0x004e8574"}

Tool result
{"result":["From 004e845c in FUN_004e8210 [PARAM]"]}

Call ghidra_get_xrefs_to

Arguments
{"filepath": "/sandbox/FFG.exe", "address": "0x004e8f48"}

Tool result
{"result":["From 004e8b1e in FUN_004e8acc [DATA]"]}

Call ghidra_get_xrefs_to

Arguments
{"filepath": "/sandbox/FFG.exe", "address": "0x004f3060"}

Tool result
{"result":["From 004f2f59 in FUN_004f2ef8 [PARAM]"]}

Call ghidra_decompile_function

Arguments
{"filepath": "/sandbox/FFG.exe", "name_or_address": "FUN_004e8210"}

Tool result
{"result":"\nvoid FUN_004e8210(int param_1,undefined4 param_2)\n\n{\n  uint uVar1;\n  int iVar2;\n  undefined4 extraout_ECX;\n  undefined4 extraout_ECX_00;\n  undefined4 extraout_EDX;\n  undefined4 extraout_EDX_00;\n  undefined4 *in_FS_OFFSET;\n  longlong *local_74;\n  int local_70;\n  longlong *local_6c;\n  int local_68;\n  longlong *local_64;\n  int local_60;\n  longlong *local_5c;\n  int local_58;\n  longlong *local_54;\n  int local_50;\n  longlong *local_4c;\n  int local_48;\n  longlong *local_44;\n  int local_40;\n  longlong *local_3c;\n  int local_38;\n  longlong *local_34;\n  int local_30;\n  longlong *local_2c;\n  int local_28;\n  longlong *local_24;\n  int local_20;\n  int local_1c;\n  longlong *local_18;\n  undefined1 *local_14;\n  int *local_10;\n  undefined4 local_c;\n  int local_8;\n  \n  local_10 = (int *)&stack0xfffffffc;\n  iVar2 = 0xe;\n  do {\n    iVar2 = iVar2 + -1;\n  } while (iVar2 != 0);\n  local_14 = &LAB_004e849f;\n  local_18 = (longlong *)*in_FS_OFFSET;\n  *in_FS_OFFSET = &local_18;\n  local_1c = 0x4e823f;\n  local_c = param_2;\n  local_8 = param_1;\n  local_10 = (int *)FUN_00404154((int *)PTR_PTR_0041475c,'\\x01',0);\n  local_20 = *(int *)(local_8 + 0x78);\n  local_1c = 0;\n  local_24 = (longlong *)0x4e8254;\n  FUN_00409ad8((int *)&local_18);\n  local_1c = 0x4e8264;\n  FUN_0040537c((int *)&local_14,(longlong *)&DAT_004e84b4,local_18);\n  local_1c = 0x4e826f;\n  (**(code **)(*local_10 + 0x38))(local_10,local_14);\n  local_1c = 0x4e8285;\n  FUN_0040537c(&local_1c,(longlong *)\"name=\",*(longlong **)(local_8 + 0x8c));\n  iVar2 = local_1c;\n  local_1c = 0x4e8290;\n  (**(code **)(*local_10 + 0x38))(local_10,iVar2);\n  local_1c = 0x4e82a1;\n  FUN_004099ec(*(uint *)(local_8 + 0x88),(int *)&local_24);\n  local_1c = 0x4e82b1;\n  FUN_0040537c(&local_20,(longlong *)\"level=\",local_24);\n  local_1c = 0x4e82bc;\n  (**(code **)(*local_10 + 0x38))(local_10,local_20);\n  local_1c = 0x4e82cd;\n  FUN_004099ec(*(uint *)(local_8 + 0x98),(int *)&local_2c);\n  local_1c = 0x4e82dd;\n  FUN_0040537c(&local_28,(longlong *)\"life=\",local_2c);\n  local_1c = 0x4e82e8;\n  (**(code **)(*local_10 + 0x38))(local_10,local_28);\n  local_1c = 0x4e82f0;\n  uVar1 = FUN_004e816c(local_8,extraout_EDX,extraout_ECX);\n  local_1c = 0x4e82f8;\n  FUN_004099ec(uVar1,(int *)&local_34);\n  local_1c = 0x4e8308;\n  FUN_0040537c(&local_30,(longlong *)\"blife=\",local_34);\n  local_1c = 0x4e8313;\n  (**(code **)(*local_10 + 0x38))(local_10,local_30);\n  local_1c = 0x4e8324;\n  FUN_004099ec(*(uint *)(local_8 + 0x90),(int *)&local_3c);\n  local_1c = 0x4e8334;\n  FUN_0040537c(&local_38,(longlong *)&DAT_004e8500,local_3c);\n  local_1c = 0x4e833f;\n  (**(code **)(*local_10 + 0x38))(local_10,local_38);\n  local_1c = 0x4e8347;\n  uVar1 = FUN_004e85f4(local_8);\n  local_1c = 0x4e834f;\n  FUN_004099ec(uVar1,(int *)&local_44);\n  local_1c = 0x4e835f;\n  FUN_0040537c(&local_40,(longlong *)\"levexp=\",local_44);\n  local_1c = 0x4e836a;\n  (**(code **)(*local_10 + 0x38))(local_10,local_40);\n  local_1c = 0x4e837b;\n  FUN_004099ec(*(uint *)(local_8 + 0xa4),(int *)&local_4c);\n  local_1c = 0x4e838b;\n  FUN_0040537c(&local_48,(longlong *)\"money=\",local_4c);\n  local_1c = 0x4e8396;\n  (**(code **)(*local_10 + 0x38))(local_10,local_48);\n  local_1c = 0x4e83a7;\n  FUN_004099ec(*(uint *)(local_8 + 0xa8),(int *)&local_54);\n  local_1c = 0x4e83b7;\n  FUN_0040537c(&local_50,(longlong *)\"rmoney=\",local_54);\n  local_1c = 0x4e83c2;\n  (**(code **)(*local_10 + 0x38))(local_10,local_50);\n  local_1c = 0x4e83d3;\n  FUN_004099ec(*(uint *)(local_8 + 0xb4),(int *)&local_5c);\n  local_1c = 0x4e83e3;\n  FUN_0040537c(&local_58,(longlong *)\"mapid=\",local_5c);\n  local_1c = 0x4e83ee;\n  (**(code **)(*local_10 + 0x38))(local_10,local_58);\n  local_1c = 0x4e83ff;\n  FUN_004099ec(*(uint *)(local_8 + 0xac),(int *)&local_64);\n  local_1c = 0x4e840f;\n  FUN_0040537c(&local_60,(longlong *)\"locid=\",local_64);\n  local_1c = 0x4e841a;\n  (**(code **)(*local_10 + 0x38))(local_10,local_60);\n  local_1c = 0x4e842b;\n  FUN_004099ec(*(uint *)(local_8 + 0xb0),(int *)&local_6c);\n  local_1c = 0x4e843b;\n  FUN_0040537c(&local_68,(longlong *)\"fishcount=\",local_6c);\n  local_1c = 0x4e8446;\n  (**(code **)(*local_10 + 0x38))(local_10,local_68);\n  local_1c = 0x4e844e;\n  uVar1 = FUN_004e819c(local_8,extraout_EDX_00,extraout_ECX_00);\n  local_1c = 0x4e8456;\n  FUN_004099ec(uVar1,(int *)&local_74);\n  local_1c = 0x4e8466;\n  FUN_0040537c(&local_70,(longlong *)\"invlines=\",local_74);\n  local_1c = 0x4e8471;\n  (**(code **)(*local_10 + 0x38))(local_10,local_70);\n  local_1c = 0x4e847c;\n  (**(code **)(*local_10 + 0x1c))(local_10,local_c);\n  local_1c = 0x4e8484;\n  FUN_00404184(local_10);\n  *in_FS_OFFSET = local_18;\n  local_10 = (int *)&LAB_004e84a6;\n  local_14 = (undefined1 *)0x4e849e;\n  FUN_00405088((int *)&local_74,0x19);\n  return;\n}\n\n"}

Call ghidra_decompile_function

Arguments
{"filepath": "/sandbox/FFG.exe", "name_or_address": "FUN_004f2ef8"}

Tool result
{"result":"\nvoid FUN_004f2ef8(int param_1)\n\n{\n  undefined4 *in_FS_OFFSET;\n  undefined1 uVar1;\n  code *pcVar2;\n  undefined4 uStack_28;\n  undefined1 *puStack_24;\n  undefined1 *puStack_20;\n  int local_1c;\n  undefined1 local_18;\n  longlong *local_14 [2];\n  int local_c;\n  int local_8;\n  \n  puStack_20 = &stack0xfffffffc;\n  local_14[0] = (longlong *)0x0;\n  puStack_24 = &LAB_004f2fec;\n  uStack_28 = *in_FS_OFFSET;\n  *in_FS_OFFSET = &uStack_28;\n  uVar1 = 1;\n  local_8 = param_1;\n  FUN_0055fc34(*(int *)PTR_DAT_00570fe0,0);\n  FUN_0040548c(*(char **)(local_8 + 0x34),\"no_transmute\");\n  if ((bool)uVar1) {\n    pcVar2 = (code *)0x2;\n    FUN_0055e264(*(int *)PTR_DAT_00570fe0,0x4f3038,(longlong *)&DAT_004f3018,0,'\\x02');\n  }\n  else {\n    FUN_0040548c(*(char **)(local_8 + 0x34),\"no_inventory\");\n    if ((bool)uVar1) {\n      pcVar2 = (code *)0x2;\n      FUN_0055e264(*(int *)PTR_DAT_00570fe0,0x4f3078,(longlong *)&DAT_004f3018,0,'\\x02');\n    }\n    else {\n      pcVar2 = (code *)0x4f2f8e;\n      local_c = FUN_00569678(local_8,0x4f30b0);\n      if (0 < local_c) {\n        pcVar2 = FUN_004f0830;\n        local_18 = 0;\n        local_1c = local_c;\n        FUN_0040ac40(&DAT_004f30bc,&local_1c,0,(int *)local_14);\n        FUN_0055e8e0(*(int *)PTR_DAT_00570fe0,local_14[0],local_c,pcVar2);\n      }\n      if (local_c < 1) {\n        FUN_004f0830();\n      }\n    }\n  }\n  *in_FS_OFFSET = pcVar2;\n  puStack_24 = &LAB_004f2ff3;\n  uStack_28 = 0x4f2feb;\n  FUN_00405064((int *)local_14);\n  return;\n}\n\n"}

Call ghidra_decompile_function

Arguments
{"filepath": "/sandbox/FFG.exe","name_or_address": "FUN_004e8acc"}

Tool result
{"result":"\nvoid FUN_004e8acc(int *param_1)\n\n{\n  undefined4 uVar1;\n  LPCSTR lpFileName;\n  int iVar2;\n  undefined4 extraout_ECX;\n  undefined4 extraout_ECX_00;\n  undefined4 extraout_ECX_01;\n  undefined4 extraout_ECX_02;\n  undefined4 extraout_ECX_03;\n  undefined4 extraout_ECX_04;\n  undefined4 extraout_ECX_05;\n  undefined4 extraout_ECX_06;\n  undefined4 extraout_ECX_07;\n  undefined4 extraout_ECX_08;\n  undefined4 extraout_ECX_09;\n  undefined4 extraout_ECX_10;\n  undefined4 extraout_ECX_11;\n  undefined4 extraout_ECX_12;\n  undefined4 extraout_ECX_13;\n  undefined4 extraout_ECX_14;\n  undefined4 extraout_ECX_15;\n  undefined4 *in_FS_OFFSET;\n  undefined4 uVar3;\n  undefined4 uStack_48;\n  undefined1 *puStack_44;\n  undefined1 *puStack_40;\n  int local_38;\n  undefined4 local_34;\n  int local_30;\n  int local_2c;\n  int local_28;\n  int local_24;\n  int local_20;\n  int local_1c;\n  int *local_18;\n  int *local_14;\n  int *local_10;\n  int local_c;\n  int *local_8;\n  \n  puStack_40 = &stack0xfffffffc;\n  local_38 = 0;\n  local_34 = 0;\n  local_30 = 0;\n  local_2c = 0;\n  puStack_44 = &LAB_004e8f12;\n  uStack_48 = *in_FS_OFFSET;\n  *in_FS_OFFSET = &uStack_48;\n  local_8 = param_1;\n  (**(code **)(*(int *)param_1[0x20] + 0x38))\n            ((int *)param_1[0x20],&DAT_004e8f3c,\"datetime\",*(undefined4 *)PTR_DAT_005708fc,\n             *(undefined4 *)(PTR_DAT_005708fc + 4));\n  (**(code **)(*(int *)local_8[0x20] + 0xc))\n            ((int *)local_8[0x20],&DAT_004e8f5c,\"InvLines\",local_8[0x27]);\n  (**(code **)(*(int *)local_8[0x20] + 0xc))\n            ((int *)local_8[0x20],&DAT_004e8f3c,\"LastWpnId\",\n             *(undefined4 *)(*(int *)PTR_DAT_0057121c + 0x10));\n  (**(code **)(*(int *)local_8[0x20] + 0xc))\n            ((int *)local_8[0x20],&DAT_004e8f3c,\"FSadFID\",local_8[0x1f]);\n  FUN_004204a0(local_8[7],&local_2c);\n  (**(code **)(*(int *)local_8[0x20] + 4))((int *)local_8[0x20],\"SHOP_WPN\",&DAT_004e8f90,local_2c);\n  FUN_004204a0(local_8[8],&local_30);\n  (**(code **)(*(int *)local_8[0x20] + 4))\n            ((int *)local_8[0x20],\"SHOP_BAITES\",&DAT_004e8f90,local_30);\n  FUN_0046984c(*(int *)PTR_DAT_00570b54,local_8,0x4e8fc8);\n  uVar1 = FUN_00409f10((undefined *)local_8[0x21]);\n  if ((char)uVar1 != '\\0') {\n    lpFileName = FUN_004054f4((undefined *)local_8[0x21]);\n    DeleteFileA(lpFileName);\n  }\n  uVar3 = 0;\n  local_14 = (int *)FUN_00482c80((int)PTR_DAT_00482bb0,'\\x01',(longlong *)local_8[0x21],\n                                 (longlong *)0x0,'\\x03');\n  local_10 = (int *)FUN_00404154((int *)PTR_PTR_0041475c,'\\x01',extraout_ECX);\n  FUN_0046b260(local_8[0x20],&local_34,extraout_ECX_00);\n  (**(code **)(*local_10 + 0x2c))(local_10,local_34);\n  local_18 = (int *)FUN_00404154((int *)PTR_PTR_00414a60,'\\x01',extraout_ECX_01);\n  (**(code **)(*local_10 + 0x78))(local_10,local_18);\n  FUN_00482fb4((int)local_14,(longlong *)\"User.prf\",local_18);\n  FUN_00404184(local_18);\n  FUN_00404184(local_10);\n  local_10 = (int *)FUN_00404154((int *)PTR_PTR_0041475c,'\\x01',extraout_ECX_02);\n  FUN_004e57bc(*(int *)PTR_DAT_0057121c,&local_38,extraout_ECX_03);\n  (**(code **)(*local_10 + 0x2c))(local_10,local_38);\n  local_18 = (int *)FUN_00404154((int *)PTR_PTR_00414a60,'\\x01',extraout_ECX_04);\n  (**(code **)(*local_10 + 0x78))(local_10,local_18);\n  FUN_00482fb4((int)local_14,(longlong *)\"Weapons.prf\",local_18);\n  FUN_00404184(local_18);\n  FUN_00404184(local_10);\n  local_10 = (int *)FUN_00404154((int *)PTR_PTR_0041475c,'\\x01',extraout_ECX_05);\n  iVar2 = FUN_004202b4(local_8[4]);\n  uVar1 = extraout_ECX_06;\n  if (-1 < iVar2 + -1) {\n    local_c = 0;\n    local_28 = iVar2;\n    do {\n      local_1c = FUN_00420460(local_8[4],local_c);\n      FUN_004e9e08(local_1c,&local_10);\n      local_c = local_c + 1;\n      local_28 = local_28 + -1;\n      uVar1 = extraout_ECX_07;\n    } while (local_28 != 0);\n  }\n  local_18 = (int *)FUN_00404154((int *)PTR_PTR_00414a60,'\\x01',uVar1);\n  (**(code **)(*local_10 + 0x78))(local_10,local_18);\n  FUN_00482fb4((int)local_14,(longlong *)\"Sadok.prf\",local_18);\n  FUN_00404184(local_18);\n  FUN_00404184(local_10);\n  local_10 = (int *)FUN_00404154((int *)PTR_PTR_0041475c,'\\x01',extraout_ECX_08);\n  iVar2 = FUN_004202b4(local_8[6]);\n  uVar1 = extraout_ECX_09;\n  if (-1 < iVar2 + -1) {\n    local_c = 0;\n    local_28 = iVar2;\n    do {\n      local_24 = FUN_00420460(local_8[6],local_c);\n      FUN_004cb370(local_24,&local_10);\n      local_c = local_c + 1;\n      local_28 = local_28 + -1;\n      uVar1 = extraout_ECX_10;\n    } while (local_28 != 0);\n  }\n  local_18 = (int *)FUN_00404154((int *)PTR_PTR_00414a60,'\\x01',uVar1);\n  (**(code **)(*local_10 + 0x78))(local_10,local_18);\n  FUN_00482fb4((int)local_14,(longlong *)\"Perks.prf\",local_18);\n  FUN_00404184(local_18);\n  FUN_00404184(local_10);\n  local_10 = (int *)FUN_00404154((int *)PTR_PTR_0041475c,'\\x01',extraout_ECX_11);\n  local_c = 1;\n  do {\n    FUN_004e9f88((uint *)(local_8 + local_c * 4 + 5),&local_10);\n    local_c = local_c + 1;\n  } while (local_c != 6);\n  local_18 = (int *)FUN_00404154((int *)PTR_PTR_00414a60,'\\x01',extraout_ECX_12);\n  (**(code **)(*local_10 + 0x78))(local_10,local_18);\n  FUN_00482fb4((int)local_14,(longlong *)\"Otters.prf\",local_18);\n  FUN_00404184(local_18);\n  FUN_00404184(local_10);\n  local_10 = (int *)FUN_00404154((int *)PTR_PTR_0041475c,'\\x01',extraout_ECX_13);\n  iVar2 = FUN_004202b4(local_8[5]);\n  uVar1 = extraout_ECX_14;\n  if (-1 < iVar2 + -1) {\n    local_c = 0;\n    local_28 = iVar2;\n    do {\n      local_20 = FUN_00420460(local_8[5],local_c);\n      FUN_004c8878(local_20,&local_10);\n      local_c = local_c + 1;\n      local_28 = local_28 + -1;\n      uVar1 = extraout_ECX_15;\n    } while (local_28 != 0);\n  }\n  local_18 = (int *)FUN_00404154((int *)PTR_PTR_00414a60,'\\x01',uVar1);\n  (**(code **)(*local_10 + 0x78))(local_10,local_18);\n  FUN_00482fb4((int)local_14,(longlong *)\"Baits.prf\",local_18);\n  FUN_00404184(local_18);\n  FUN_00404184(local_10);\n  FUN_00404184(local_14);\n  *in_FS_OFFSET = uVar3;\n  FUN_00405088(&local_38,4);\n  return;\n}\n\n"}

Good progress. Let me find the CMD dispatch handler and the invlines counter function, and check what's around cell=.

Call ghidra_get_xrefs_to

Arguments
{"filepath": "/sandbox/FFG.exe", "address": "0x004ee7bc"}

Tool result
{"result":["From 004ee6b1 [PARAM]"]}

Call ghidra_decompile_function

Arguments
{"filepath": "/sandbox/FFG.exe", "name_or_address": "FUN_004e819c"}

Tool result
{"result":"\nint FUN_004e819c(int param_1,undefined4 param_2,undefined4 param_3)\n\n{\n  int iVar1;\n  \n  iVar1 = FUN_004e81c8(param_1,2,param_3);\n  return iVar1 + *(int *)(param_1 + 0x9c);\n}\n\n"}

Call sandbox_execute

Arguments
{"command": "python3 -c \"\nimport struct\n\nwith open('/sandbox/FFG.exe', 'rb') as f:\n   data= f.read()\n\n# Search for comparison with160 (0xa0000000 big-endian or 0xa0 little-endian as immediate)\n# In x86 32-bit code, CMP EAX, 160 = 3D A0 00 00 00\n# or CMPreg, 0xa0 in various forms\n\n# Searchfor 0xa0 as dword immediate in CMP: 3D A0 00 00 00\npattern1 = b'\\x3d\\xa0\\x00\\x00\\x00'\n# CMP with byte immediate (sign-extended): 83 F8 A0 or 83 F?A0\nimport re\n\noffsets = []\nfor m in re.finditer(re.escape(b'\\x3d\\xa0\\x00\\x00\\x00'), data):\n    offsets.append(hex(0x400000 +m.start()))\n\n# Also search for 83 xx a0(CMP r/m, imm8 where A0=160... but 160 >127 so won't fitsigned byte)\n# 160 = 0xA0, as signedbyte = -96,so it CAN appear as 83 F8 A0\n\nfor m inre.finditer(b'\\x83[\\xf8\\xf9\\xfa\\xfb\\xfc\\xfd\\xfe\\xff]\\xa0', data):\n  offsets.append(hex(0x400000 + m.start()))\n\nprint('CMP 160 offsets:', offsets[:30])\n\"\n"}

Tool result
{"exitCode":0,"stdout":"CMP 160 offsets: ['0x4e590c']\n","stderr":""}

Call ghidra_get_function

Arguments
{"filepath": "/sandbox/FFG.exe", "name_or_address": "0x004ee6b1"}

Tool result
{"result":"No function found at 0x004ee6b1"}

Call ghidra_disassemble_function

Arguments
{"filepath": "/sandbox/FFG.exe", "address":"0x004e5900"}

Tool result
{"result":["004e5828: PUSH EBP ","004e5829: MOV EBP,ESP ","004e582b: MOV ECX,0x9 ","004e5830: PUSH 0x0 ","004e5832: PUSH 0x0 ","004e5834: DEC ECX ","004e5835: JNZ 0x004e5830 ","004e5837: MOV dword ptr [EBP + -0x8],EDX ","004e583a: MOV dword ptr [EBP + -0x4],EAX ","004e583d: XOR EAX,EAX ","004e583f: PUSH EBP ","004e5840: PUSH 0x4e59d6 ","004e5845: PUSH dword ptr FS:[EAX] ","004e5848: MOV dword ptr FS:[EAX],ESP ","004e584b: MOV EAX,dword ptr [EBP + -0x4] ","004e584e: MOV EAX,dword ptr [EAX + 0x10] ","004e5851: XOR EDX,EDX ","004e5853: PUSH EDX ","004e5854: PUSH EAX ","004e5855: LEA EAX,[EBP + -0x10] ","004e5858: CALL 0x00409ad8 ","004e585d: MOV ECX,dword ptr [EBP + -0x10] ","004e5860: LEA EAX,[EBP + -0xc] ","004e5863: MOV EDX,0x4e59ec ","004e5868: CALL 0x0040537c ","004e586d: MOV EDX,dword ptr [EBP + -0xc] ","004e5870: MOV EAX,dword ptr [EBP + -0x8] ","004e5873: MOV EAX,dword ptr [EAX] ","004e5875: MOV ECX,dword ptr [EAX] ","004e5877: CALL dword ptr [ECX + 0x38] ","004e587a: MOV EAX,dword ptr [EBP + -0x4] ","004e587d: MOV EAX,dword ptr [EAX + 0x14] ","004e5880: XOR EDX,EDX ","004e5882: PUSH EDX ","004e5883: PUSH EAX ","004e5884: LEA EAX,[EBP + -0x18] ","004e5887: CALL 0x00409ad8 ","004e588c: MOV ECX,dword ptr [EBP + -0x18] ","004e588f: LEA EAX,[EBP + -0x14] ","004e5892: MOV EDX,0x4e59f8 ","004e5897: CALL 0x0040537c ","004e589c: MOV EDX,dword ptr [EBP + -0x14] ","004e589f: MOV EAX,dword ptr [EBP + -0x8] ","004e58a2: MOV EAX,dword ptr [EAX] ","004e58a4: MOV ECX,dword ptr [EAX] ","004e58a6: CALL dword ptr [ECX + 0x38] ","004e58a9: MOV EAX,dword ptr [EBP + -0x4] ","004e58ac: MOV EAX,dword ptr [EAX + 0x18] ","004e58af: XOR EDX,EDX ","004e58b1: PUSH EDX ","004e58b2: PUSH EAX ","004e58b3: LEA EAX,[EBP + -0x20] ","004e58b6: CALL 0x00409ad8 ","004e58bb: MOV ECX,dword ptr [EBP + -0x20] ","004e58be: LEA EAX,[EBP + -0x1c] ","004e58c1: MOV EDX,0x4e5a08 ","004e58c6: CALL 0x0040537c ","004e58cb: MOV EDX,dword ptr [EBP + -0x1c] ","004e58ce: MOV EAX,dword ptr [EBP + -0x8] ","004e58d1: MOV EAX,dword ptr [EAX] ","004e58d3: MOV ECX,dword ptr [EAX] ","004e58d5: CALL dword ptr [ECX + 0x38] ","004e58d8: LEA EDX,[EBP + -0x28] ","004e58db: MOV EAX,dword ptr [EBP + -0x4] ","004e58de: MOV EAX,dword ptr [EAX + 0x1c] ","004e58e1: CALL 0x004099ec ","004e58e6: MOV ECX,dword ptr [EBP + -0x28] ","004e58e9: LEA EAX,[EBP + -0x24] ","004e58ec: MOV EDX,0x4e5a1c ","004e58f1: CALL 0x0040537c ","004e58f6: MOV EDX,dword ptr [EBP + -0x24] ","004e58f9: MOV EAX,dword ptr [EBP + -0x8] ","004e58fc: MOV EAX,dword ptr [EAX] ","004e58fe: MOV ECX,dword ptr [EAX] ","004e5900: CALL dword ptr [ECX + 0x38] ","004e5903: LEA EDX,[EBP + -0x30] ","004e5906: MOV EAX,dword ptr [EBP + -0x4] ","004e5909: MOV EAX,dword ptr [EAX + 0x20] ","004e590c: CALL 0x004099ec ","004e5911: MOV ECX,dword ptr [EBP + -0x30] ","004e5914: LEA EAX,[EBP + -0x2c] ","004e5917: MOV EDX,0x4e5a30 ","004e591c: CALL 0x0040537c ","004e5921: MOV EDX,dword ptr [EBP + -0x2c] ","004e5924: MOV EAX,dword ptr [EBP + -0x8] ","004e5927: MOV EAX,dword ptr [EAX] ","004e5929: MOV ECX,dword ptr [EAX] ","004e592b: CALL dword ptr [ECX + 0x38] ","004e592e: LEA EDX,[EBP + -0x38] ","004e5931: MOV EAX,dword ptr [EBP + -0x4] ","004e5934: MOV EAX,dword ptr [EAX + 0x24] ","004e5937: CALL 0x004099ec ","004e593c: MOV ECX,dword ptr [EBP + -0x38] ","004e593f: LEA EAX,[EBP + -0x34] ","004e5942: MOV EDX,0x4e5a3c ","004e5947: CALL 0x0040537c ","004e594c: MOV EDX,dword ptr [EBP + -0x34] ","004e594f: MOV EAX,dword ptr [EBP + -0x8] ","004e5952: MOV EAX,dword ptr [EAX] ","004e5954: MOV ECX,dword ptr [EAX] ","004e5956: CALL dword ptr [ECX + 0x38] ","004e5959: LEA EDX,[EBP + -0x40] ","004e595c: MOV EAX,dword ptr [EBP + -0x4] ","004e595f: MOV EAX,dword ptr [EAX + 0x28] ","004e5962: CALL 0x004099ec ","004e5967: MOV ECX,dword ptr [EBP + -0x40] ","004e596a: LEA EAX,[EBP + -0x3c] ","004e596d: MOV EDX,0x4e5a48 ","004e5972: CALL 0x0040537c ","004e5977: MOV EDX,dword ptr [EBP + -0x3c] ","004e597a: MOV EAX,dword ptr [EBP + -0x8] ","004e597d: MOV EAX,dword ptr [EAX] ","004e597f: MOV ECX,dword ptr [EAX] ","004e5981: CALL dword ptr [ECX + 0x38] ","004e5984: LEA EDX,[EBP + -0x48] ","004e5987: MOV EAX,dword ptr [EBP + -0x4] ","004e598a: MOV EAX,dword ptr [EAX + 0x2c] ","004e598d: CALL 0x004099ec ","004e5992: MOV ECX,dword ptr [EBP + -0x48] ","004e5995: LEA EAX,[EBP + -0x44] ","004e5998: MOV EDX,0x4e5a58 ","004e599d: CALL 0x0040537c ","004e59a2: MOV EDX,dword ptr [EBP + -0x44] ","004e59a5: MOV EAX,dword ptr [EBP + -0x8] ","004e59a8: MOV EAX,dword ptr [EAX] ","004e59aa: MOV ECX,dword ptr [EAX] ","004e59ac: CALL dword ptr [ECX + 0x38] ","004e59af: MOV EAX,dword ptr [EBP + -0x8] ","004e59b2: MOV EAX,dword ptr [EAX] ","004e59b4: XOR EDX,EDX ","004e59b6: MOV ECX,dword ptr [EAX] ","004e59b8: CALL dword ptr [ECX + 0x38] ","004e59bb: XOR EAX,EAX ","004e59bd: POP EDX ","004e59be: POP ECX ","004e59bf: POP ECX ","004e59c0: MOV dword ptr FS:[EAX],EDX ","004e59c3: PUSH 0x4e59dd ","004e59c8: LEA EAX,[EBP + -0x48] ","004e59cb: MOV EDX,0x10 ","004e59d0: CALL 0x00405088 ","004e59d5: RET "]}

The 0x4e590c match was a CALL, not a CMP. Let me search more carefully for the CMD=160 dispatch and look at the cell= string function and inventory-related data strings.

Call ghidra_list_strings

Arguments
{"filepath": "/sandbox/FFG.exe", "filter":"cell"}

Tool result
{"result":["004e5a1c: \"cellnum=\"","004ee7bc: \"cell=\"","004ef1bc: \"cell=\"","004f42b4: \"cell=\"","00565ad8: \"cells\"","00566530: \"cell=\""]}

Call sandbox_execute

Arguments
{"command": "python3 -c \"\nimport struct, re\n\nwith open('/sandbox/FFG.exe', 'rb') as f:\n    data = f.read()\n\nbase= 0x400000\n\n# Search for 160 (0xA0) in CMP patterns more broadly:\n# MOV EAX, [something]; CMP EAX, 160-> 3D A0 00 00 00\n# CMP [mem], 160 -> 81 3x A0 00 00 00\n# Also search for thevalue 160 as DWORD in data sections (jumptableentries, etc.)\n\nresults = []\n\n# Pattern: 3D A0 00 00 00 (CMP EAX, imm32 = 160)\nform in re.finditer(b'\\x3d\\xa0\\x00\\x00\\x00', data):\n    results.append(('CMP EAX,160', hex(base + m.start())))\n\n# Pattern: 81 F8 A000 00 00 (CMP EAX, 0xA0as 32-bit)\nfor m in re.finditer(b'\\x81\\xf8\\xa0\\x00\\x00\\x00',data):\n    results.append(('CMPEAX,0xA0_32', hex(base + m.start())))\n\n# Jump table: look for0xA0000000 as big-endian DWORD or sequence around dispatch\n# In Delphiswitch,look forSUB EAX, first_casethen JMP [EAX*4+table]\n# or series of CMP/JE pairs -let's find 0xA0 in a CMPchain\n\n#CMP with 0xA0asword: 66 3D A0 00\nform in re.finditer(b'\\x66\\x3d\\xa0\\x00', data):\n    results.append(('CMP AX,0xA0', hex(base + m.start())))\n\nfor r in results:\n    print(r)\nprint('Total:',len(results))\n\"\n"}

Tool result
{"exitCode":0,"stdout":"('CMP EAX,160', '0x4e590c')\nTotal: 1\n","stderr":""}

Call ghidra_decompile_function

Arguments
{"filepath": "/sandbox/FFG.exe","name_or_address":"0x004ee6b1"}

Tool result
{"result":"Function not found"}

Let me search for the checksum constant 0x1773 and look at inventory-related strings around cell=.

Call sandbox_execute

Arguments
{"command":"python3 -c \"\nimport struct, re\n\nwith open('/sandbox/FFG.exe', 'rb') as f:\n    data =f.read()\n\nbase = 0x400000\n\n# Search for 0x1773 (6003) checksum constant\nfor m in re.finditer(b'\\x73\\x17\\x00\\x00',data):\n   print('0x1773 at:', hex(base + m.start()))\n\nprint('---')\n# Search for XOR key string itself\nxor_key= b'sdfgDDdffkojeroxdfJKHNplmkOIJHjklnIOJifg33gjkfsdpgko'\nfor m in re.finditer(re.escape(xor_key), data):\n    print('XOR KEY at:', hex(base + m.start()))\n\nprint('---')\n# Readstrings near0x4e5a1c area\noffset = 0x4e5a1c - base\nprint('0x4e5a1c:', data[offset:offset+50])\noffset2 = 0x4e5a30- base\nprint('0x4e5a30:', data[offset2:offset2+50])\noffset3= 0x4e5a3c - base\nprint('0x4e5a3c:', data[offset3:offset3+50])\noffset4= 0x4e5a48 - base\nprint('0x4e5a48:', data[offset4:offset4+50])\noffset5 = 0x4e5a58 - base\nprint('0x4e5a58:', data[offset5:offset5+50])\noffset6 = 0x4e59ec - base\nprint('0x4e59ec:', data[offset6:offset6+50])\noffset7 = 0x4e59f8 - base\nprint('0x4e59f8:', data[offset7:offset7+50])\noffset8 = 0x4e5a08 - base\nprint('0x4e5a08:', data[offset8:offset8+50])\n\"\n"}

Tool result
{"exitCode":0,"stdout":"0x1773 at: 0x568632\n---\nXOR KEY at: 0x56827c\n---\n0x4e5a1c: b'\\x8bE\\xf8\\xe8\\xa4\\xf9\\xff\\xffj\\x00j\\x00j\\x00\\x8dE\\xa0\\xe8*Q\\xf2\\xff\\x8bM\\xa0\\x8dE\\xa4\\xba\\xdcrN\\x00\\xe8:\\xed\\xf1\\xff\\x8bM\\xa4\\xba\\x00\\x01\\x00\\x00\\x8bE\\xf8\\xe8'\n0x4e5a30: b'\\xf2\\xff\\x8bM\\xa0\\x8dE\\xa4\\xba\\xdcrN\\x00\\xe8:\\xed\\xf1\\xff\\x8bM\\xa4\\xba\\x00\\x01\\x00\\x00\\x8bE\\xf8\\xe8v\\xf9\\xff\\xff\\x8dU\\x9c\\x8bE\\xfc\\x8b@\\x08\\xe8 \\x1f\\x00\\x00\\x8bM'\n0x4e5a3c: b'\\x00\\xe8:\\xed\\xf1\\xff\\x8bM\\xa4\\xba\\x00\\x01\\x00\\x00\\x8bE\\xf8\\xe8v\\xf9\\xff\\xff\\x8dU\\x9c\\x8bE\\xfc\\x8b@\\x08\\xe8 \\x1f\\x00\\x00\\x8bM\\x9c\\xbax\\x00\\x00\\x00\\x8bE\\xf8\\xe8X\\xf9'\n0x4e5a48: b'\\x00\\x00\\x8bE\\xf8\\xe8v\\xf9\\xff\\xff\\x8dU\\x9c\\x8bE\\xfc\\x8b@\\x08\\xe8 \\x1f\\x00\\x00\\x8bM\\x9c\\xbax\\x00\\x00\\x00\\x8bE\\xf8\\xe8X\\xf9\\xff\\xff\\x8dM\\x98\\xa1\\x1c\\x12W\\x00\\x8b\\x00'\n0x4e5a58: b'\\x8b@\\x08\\xe8 \\x1f\\x00\\x00\\x8bM\\x9c\\xbax\\x00\\x00\\x00\\x8bE\\xf8\\xe8X\\xf9\\xff\\xff\\x8dM\\x98\\xa1\\x1c\\x12W\\x00\\x8b\\x00\\xba\\x01\\x00\\x00\\x00\\xe8\\x84\\xee\\xff\\xff\\x8bM\\x98\\xba\\xa0\\x00'\n0x4e59ec: b'E\\xfc\\x8b@\\x08\\xe8\\x1a\\x1c\\x00\\x00\\x8bM\\xac\\xbad\\x00\\x00\\x00\\x8bE\\xf8\\xe8\\xc2\\xf9\\xff\\xff\\x8dU\\xa8\\x8bE\\xfc\\x8b@\\x08\\xe8\\xd8#\\x00\\x00\\x8bM\\xa8\\xbai\\x00\\x00\\x00\\x8bE'\n0x4e59f8: b'\\xac\\xbad\\x00\\x00\\x00\\x8bE\\xf8\\xe8\\xc2\\xf9\\xff\\xff\\x8dU\\xa8\\x8bE\\xfc\\x8b@\\x08\\xe8\\xd8#\\x00\\x00\\x8bM\\xa8\\xbai\\x00\\x00\\x00\\x8bE\\xf8\\xe8\\xa4\\xf9\\xff\\xffj\\x00j\\x00j\\x00'\n0x4e5a08: b'\\xa8\\x8bE\\xfc\\x8b@\\x08\\xe8\\xd8#\\x00\\x00\\x8bM\\xa8\\xbai\\x00\\x00\\x00\\x8bE\\xf8\\xe8\\xa4\\xf9\\xff\\xffj\\x00j\\x00j\\x00\\x8dE\\xa0\\xe8*Q\\xf2\\xff\\x8bM\\xa0\\x8dE\\xa4\\xba\\xdc'\n","stderr":""}

Call ghidra_get_xrefs_to

Arguments
{"filepath": "/sandbox/FFG.exe", "address": "0x004ee7bc"}

Tool result
{"result":["From 004ee6b1 [PARAM]"]}

The 0x4e59 range is code, not strings. Let me find the function containing 0x4ee6b1 and the checksum function.

Call ghidra_get_function

Arguments
{"filepath": "/sandbox/FFG.exe", "name_or_address": "0x004ee630"}

Tool result
{"result":"No function found at 0x004ee630"}

Call ghidra_get_xrefs_to

Arguments
{"filepath": "/sandbox/FFG.exe", "address": "0x00568632"}

Tool result
{"result":[]}

Let me check the function that uses cell= by disassembling around 0x4ee6b1 and find the network receive dispatcher.

Call ghidra_disassemble_function

Arguments
{"filepath": "/sandbox/FFG.exe", "address": "0x004ee540"}

Tool result
{"result":["No function found at or containing address 0x004ee540"]}

Call sandbox_execute

Arguments
{"command": "python3-c \"\nimport struct, re\n\nwith open('/sandbox/FFG.exe', 'rb') as f:\n data = f.read()\n\nbase =0x400000\n\n# Find where 0x1773 is used as XOR constant in code\n# XOR EAX, 0x1773 = 35 73 17 00 00\nfor min re.finditer(b'\\x35\\x73\\x17\\x00\\x00', data):\n print('XOR EAX,0x1773 at:',hex(base + m.start()))\n\n# Also look for the network packet handler - search forreading 20 bytes (header)\n# PUSH 20 (0x14) as recv size: 6A 14\n# or MOV EDX, 0x14\nprint('---')\nfor min re.finditer(b'\\x6a\\x14', data):\n    addr = base + m.start()\n if addr >=0x401000 and addr <= 0x56a7ff:\n     print('PUSH 14h at:', hex(addr))\n\nprint('---')\n# Look for the checksum formula:(seq + cmd) * 3 XOR 0x1773\n# IMUL EAX, 3: 6B C0 03 or 69C0 03 00 00 00\nfor m in re.finditer(b'\\x6b\\xc0\\x03', data):\n    addr = base + m.start()\n   ifaddr >= 0x401000 andaddr <= 0x56a7ff:\n     print('IMUL EAX,EAX,3 at:',hex(addr))\n\nform in re.finditer(b'\\x69\\xc0\\x03\\x00\\x00\\x00',data):\n  addr = base +m.start()\n if addr>= 0x401000 and addr <=0x56a7ff:\n  print('IMULEAX,EAX,3(32) at:', hex(addr))\n\"\n"}

Tool result
{"exitCode":0,"stdout":"XOR EAX,0x1773 at: 0x568631\n---\nPUSH 14h at: 0x44d544\nPUSH 14h at: 0x44d767\nPUSH 14h at: 0x44eb90\nPUSH 14h at: 0x45c7f9\nPUSH 14h at: 0x469bfe\nPUSH 14h at: 0x469c72\nPUSH 14h at: 0x469ce6\nPUSH 14h at: 0x469d5a\nPUSH 14h at: 0x470a52\nPUSH 14h at: 0x4d0835\nPUSH 14h at: 0x4d3452\nPUSH 14h at: 0x4d34c6\nPUSH 14h at: 0x4d353a\nPUSH 14h at: 0x4d35ae\nPUSH 14h at: 0x4d740b\nPUSH 14h at: 0x4d742e\nPUSH 14h at: 0x4d7456\nPUSH 14h at: 0x4d7481\nPUSH 14h at: 0x4d74c0\nPUSH 14h at: 0x4d74ff\nPUSH 14h at: 0x4d8b3b\nPUSH 14h at: 0x4dd4a5\nPUSH 14h at: 0x4dd4a7\nPUSH 14h at: 0x4de11c\nPUSH 14h at: 0x4deaa6\nPUSH 14h at: 0x4deadf\nPUSH 14h at: 0x4deb4b\nPUSH 14h at: 0x4deba6\nPUSH 14h at: 0x4debdf\nPUSH 14h at: 0x4dec59\nPUSH 14h at: 0x4deca9\nPUSH 14h at: 0x4decef\nPUSH 14h at: 0x4ded35\nPUSH 14h at: 0x4ded7b\nPUSH 14h at: 0x4dedc5\nPUSH 14h at: 0x4dee0b\nPUSH 14h at: 0x4dee51\nPUSH 14h at: 0x4dee9b\nPUSH 14h at: 0x4deee1\nPUSH 14h at: 0x4def27\nPUSH 14h at: 0x4def6d\nPUSH 14h at: 0x4defb7\nPUSH 14h at: 0x4df001\nPUSH 14h at: 0x4df03b\nPUSH 14h at: 0x4df5cf\nPUSH 14h at: 0x4df608\nPUSH 14h at: 0x4df641\nPUSH 14h at: 0x4df6ad\nPUSH 14h at: 0x4df708\nPUSH 14h at: 0x4df751\nPUSH 14h at: 0x4df797\nPUSH 14h at: 0x4df7dd\nPUSH 14h at: 0x4df823\nPUSH 14h at: 0x4df86d\nPUSH 14h at: 0x4df8b3\nPUSH 14h at: 0x4df8f9\nPUSH 14h at: 0x4df943\nPUSH 14h at: 0x4df989\nPUSH 14h at: 0x4df9cf\nPUSH 14h at: 0x4dfa19\nPUSH 14h at: 0x4dfa63\nPUSH 14h at: 0x4dfa9d\nPUSH 14h at: 0x4e0684\nPUSH 14h at: 0x4e09ba\nPUSH 14h at: 0x4e0cc3\nPUSH 14h at: 0x4e0f1f\nPUSH 14h at: 0x4e10e7\nPUSH 14h at: 0x4ec9f7\nPUSH 14h at: 0x4ecd8d\nPUSH 14h at: 0x4ed4cf\nPUSH 14h at: 0x4ed543\nPUSH 14h at: 0x4ed594\nPUSH 14h at: 0x4ed6d1\nPUSH 14h at: 0x4edcd4\nPUSH 14h at: 0x4edd26\nPUSH 14h at: 0x4eecf8\nPUSH 14h at: 0x4eee06\nPUSH 14h at: 0x4eee87\nPUSH 14h at: 0x4eef95\nPUSH 14h at: 0x4f3760\nPUSH 14h at: 0x4f37b1\nPUSH 14h at: 0x4f38f0\nPUSH 14h at: 0x4f3ab4\nPUSH 14h at: 0x4f3bc3\nPUSH 14h at: 0x4f661d\nPUSH 14h at: 0x4f7f71\nPUSH 14h at: 0x4f8513\nPUSH 14h at: 0x4f8515\nPUSH 14h at: 0x4f9d43\nPUSH 14h at: 0x4f9d9c\nPUSH 14h at: 0x4f9df8\nPUSH 14h at: 0x4f9e57\nPUSH 14h at: 0x4f9eb7\nPUSH 14h at: 0x4f9f17\nPUSH 14h at: 0x4f9f6d\nPUSH 14h at: 0x50e2dc\nPUSH 14h at: 0x511c88\nPUSH 14h at: 0x511d86\nPUSH 14h at: 0x51209b\nPUSH 14h at: 0x5120f4\nPUSH 14h at: 0x51213c\nPUSH 14h at: 0x51330e\nPUSH 14h at: 0x51a529\nPUSH 14h at: 0x51a56a\nPUSH 14h at: 0x51ac4d\nPUSH 14h at: 0x51acbd\nPUSH 14h at: 0x51ad2d\nPUSH 14h at: 0x51b038\nPUSH 14h at: 0x51b090\nPUSH 14h at: 0x51c0fb\nPUSH 14h at: 0x51c15f\nPUSH 14h at: 0x51c1b2\nPUSH 14h at: 0x51c74d\nPUSH 14h at: 0x51e540\nPUSH 14h at: 0x5224be\nPUSH 14h at: 0x5234ae\nPUSH 14h at: 0x5234d9\nPUSH 14h at: 0x523ac9\nPUSH 14h at: 0x523b96\nPUSH 14h at: 0x523d1a\nPUSH 14h at: 0x523d23\nPUSH 14h at: 0x523d80\nPUSH 14h at: 0x523d89\nPUSH 14h at: 0x523e1f\nPUSH 14h at: 0x523fe0\nPUSH 14h at: 0x524041\nPUSH 14h at: 0x5240d6\nPUSH 14h at: 0x52415c\nPUSH 14h at: 0x526245\nPUSH 14h at: 0x526247\nPUSH 14h at: 0x52642a\nPUSH 14h at: 0x5265f2\nPUSH 14h at: 0x5265f4\nPUSH 14h at: 0x52819d\nPUSH 14h at: 0x5283df\nPUSH 14h at: 0x52886d\nPUSH 14h at: 0x529039\nPUSH 14h at: 0x52a0fd\nPUSH 14h at: 0x52a868\nPUSH 14h at: 0x52b358\nPUSH 14h at: 0x52bb33\nPUSH 14h at: 0x52d7f3\nPUSH 14h at: 0x52dbcf\nPUSH 14h at: 0x52dc4f\nPUSH 14h at: 0x52df28\nPUSH 14h at: 0x52df86\nPUSH 14h at: 0x52f8b9\nPUSH 14h at: 0x5352bc\nPUSH 14h at: 0x53743c\nPUSH 14h at: 0x539531\nPUSH 14h at: 0x53955a\nPUSH 14h at: 0x53984a\nPUSH 14h at: 0x5398cb\nPUSH 14h at: 0x539934\nPUSH 14h at: 0x53c55f\nPUSH 14h at: 0x53c5a5\nPUSH 14h at: 0x53e770\nPUSH 14h at: 0x53e79b\nPUSH 14h at: 0x53e7c6\nPUSH 14h at: 0x53e80a\nPUSH 14h at: 0x53e835\nPUSH 14h at: 0x53e866\nPUSH 14h at: 0x53f140\nPUSH 14h at: 0x53f16e\nPUSH 14h at: 0x5418a6\nPUSH 14h at: 0x5418fd\nPUSH 14h at: 0x541e20\nPUSH 14h at: 0x542124\nPUSH 14h at: 0x54238f\nPUSH 14h at: 0x5423c8\nPUSH 14h at: 0x542508\nPUSH 14h at: 0x542d30\nPUSH 14h at: 0x542dcf\nPUSH 14h at: 0x542e1e\nPUSH 14h at: 0x543b91\nPUSH 14h at: 0x543b93\nPUSH 14h at: 0x543bf7\nPUSH 14h at: 0x54418d\nPUSH 14h at: 0x545841\nPUSH 14h at: 0x545852\nPUSH 14h at: 0x5458c9\nPUSH 14h at: 0x5458d7\nPUSH 14h at: 0x54595b\nPUSH 14h at: 0x545969\nPUSH 14h at: 0x545a77\nPUSH 14h at: 0x545a88\nPUSH 14h at: 0x545afb\nPUSH 14h at: 0x545b09\nPUSH 14h at: 0x545b89\nPUSH 14h at: 0x545b97\nPUSH 14h at: 0x54647e\nPUSH 14h at: 0x54649a\nPUSH 14h at: 0x5465e8\nPUSH 14h at: 0x546604\nPUSH 14h at: 0x54673b\nPUSH 14h at: 0x546757\nPUSH 14h at: 0x54688e\nPUSH 14h at: 0x5468aa\nPUSH 14h at: 0x547a9c\nPUSH 14h at: 0x547acd\nPUSH 14h at: 0x547aed\nPUSH 14h at: 0x547e4e\nPUSH 14h at: 0x547e80\nPUSH 14h at: 0x547eb8\nPUSH 14h at: 0x5481e2\nPUSH 14h at: 0x54842d\nPUSH 14h at: 0x548468\nPUSH 14h at: 0x548683\nPUSH 14h at: 0x554820\nPUSH 14h at: 0x55e3f2\n---\nIMUL EAX,EAX,3 at: 0x4764c7\nIMUL EAX,EAX,3 at: 0x504934\nIMUL EAX,EAX,3 at: 0x50495d\n","stderr":""}

Let me look at the checksum function and the packet dispatch area.

Call ghidra_decompile_function

Arguments
{"filepath": "/sandbox/FFG.exe", "name_or_address": "0x00504934"}

Tool result
{"result":"Function not found"}

Call ghidra_decompile_function

Arguments
{"filepath": "/sandbox/FFG.exe", "name_or_address": "0x004764c7"}

Tool result
{"result":"Function not found"}

Call ghidra_disassemble_function

Arguments
{"filepath": "/sandbox/FFG.exe","address":"0x004dc9f0"}

Tool result
{"result":["No function found at or containing address 0x004dc9f0"]}

Call sandbox_execute

Arguments
{"command": "python3 -c \"\nimport struct, re\n\nwithopen('/sandbox/FFG.exe', 'rb') asf:\n    data = f.read()\n\nbase = 0x400000\n\n# Look at the cluster around 0x4dde-0x4e11 forlarge packet dispatch functions\n# Let's look for CMP with values in range 100-300 (likely CMD values)\n# CMP EAX, N: 3DNN 00 00 00 for N in 0x64..0x12c\n# Or CMP EAX/ECX/EDXwith immediatedword\n\nresults = []\nfor n in range(1, 256):\n    pattern= struct.pack('<I', n)  # N as DWORD\n    # 3D NN 00 0000 (CMPEAX, N)\n search = b'\\x3d' + pattern\n for m in re.finditer(re.escape(search), data):\n     addr = base + m.start()\n   ifaddr >=0x4dc000andaddr <=0x4f5000:\n     results.append((n, hex(addr)))\n\n# Sort by address\nresults.sort(key=lambda x: x[1])\nforr in results[:60]:\n print(f'CMP EAX, {r[0]} (0x{r[0]:02x})at {r[1]}')\n\"\n"}

Tool result
{"exitCode":0,"stdout":"CMP EAX, 49 (0x31) at 0x4de5e3\nCMP EAX, 48 (0x30) at 0x4de61b\nCMP EAX, 160 (0xa0) at 0x4e590c\nCMP EAX, 180 (0xb4) at 0x4e596f\nCMP EAX, 200 (0xc8) at 0x4eb064\n","stderr":""}

Let me verify the actual bytes and find the real packet dispatch area.

Call sandbox_execute

Arguments
{"command": "python3 -c \"\nimport struct, re\n\nwith open('/sandbox/FFG.exe', 'rb') as f:\n    data =f.read()\n\nbase = 0x400000\n\n# Read PE section headersto get correct file offset mapping\n# PE headerat offset 0x3C\npe_offset = struct.unpack_from('<I', data, 0x3C)[0]\nprint('PE header offset:', hex(pe_offset))\n\n# Number of sections\nnum_sections= struct.unpack_from('<H',data, pe_offset + 6)[0]\nprint('Sections:', num_sections)\n\n# Section headers start at pe_offset + 0x18 +SizeOfOptionalHeader\nopt_header_size =struct.unpack_from('<H', data,pe_offset+ 0x14)[0]\nsection_start = pe_offset + 0x18+opt_header_size\nprint('Section headers at:', hex(section_start))\n\nfor i in range(num_sections):\n s = section_start + i * 40\n name= data[s:s+8].rstrip(b'\\x00').decode('ascii', errors='replace')\n    vaddr = struct.unpack_from('<I', data, s+12)[0]\n    vsize = struct.unpack_from('<I',data, s+8)[0]\n raw_off = struct.unpack_from('<I', data, s+20)[0]\n    raw_size =struct.unpack_from('<I',data, s+16)[0]\n  print(f' {name}: VA=0x{vaddr:08x} size=0x{vsize:x} FileOff=0x{raw_off:08x}')\n\n# Nowcheck what's at VA 0x4e590c\n# Find the .text section\ntext_va= 0x401000\ntext_raw = None\nfor i in range(num_sections):\n    s = section_start + i * 40\nname = data[s:s+8].rstrip(b'\\x00').decode('ascii', errors='replace')\n    vaddr = struct.unpack_from('<I', data, s+12)[0]\n vsize = struct.unpack_from('<I', data,s+8)[0]\n    raw_off = struct.unpack_from('<I', data, s+20)[0]\n    if vaddr == text_va:\n      text_raw =raw_off\n  print(f'text raw offset:{hex(raw_off)}')\n\nif text_raw:\n   # VA 0x4e590cis in .textsection\n file_off = 0x4e590c - text_va + text_raw\n    print(f'VA 0x4e590c -> file offset: {hex(file_off)}')\n    print('Bytes:', data[file_off:file_off+10].hex())\n\"\n"}

Tool result
{"exitCode":0,"stdout":"PE header offset: 0x100\nSections: 11\nSection headers at: 0x1f8\n  .text: VA=0x00001000 size=0x1697f4 FileOff=0x00000400\n  .itext: VA=0x0016b000 size=0x10dc FileOff=0x00169c00\n  .data: VA=0x0016d000 size=0x4338 FileOff=0x0016ae00\n  .bss: VA=0x00172000 size=0xc43c FileOff=0x00000000\n  .idata: VA=0x0017f000 size=0x303a FileOff=0x0016f200\n  .tls: VA=0x00183000 size=0x34 FileOff=0x00000000\n  .rdata: VA=0x00184000 size=0x18 FileOff=0x00172400\n  .vmp0: VA=0x00185000 size=0x1b1f8 FileOff=0x00172600\n  .rsrc: VA=0x001a1000 size=0x7400 FileOff=0x0018d800\n  .vmp1: VA=0x001a9000 size=0x100 FileOff=0x00194c00\n  .reloc: VA=0x001aa000 size=0x1b094 FileOff=0x00194e00\n","stderr":""}

The section offset mapping is off by 0xC00. Correcting: actual VA = reported_addr + 0xC00. The CMP EAX,160 is really at 0x4E650C. Let me find that function.

Call ghidra_get_function

Arguments
{"filepath": "/sandbox/FFG.exe", "name_or_address": "0x004e650c"}

Tool result
{"result":"No function found at 0x004e650c"}

Call sandbox_execute

Arguments
{"command": "python3 -c \"\nimportstruct, re\n\nwithopen('/sandbox/FFG.exe', 'rb') as f:\n   data = f.read()\n\n# With corrected VA: VA = 0x400000 + raw_offset- 0x400 + 0x1000 = raw_offset + 0x400C00\n# So raw_offset =VA - 0x400C00\n# Let's search for CMDs in range 1-300 with correct VA\nbase_correction = 0xC00  # add to raw-based addresses\n\n# Recompute withcorrectformula: VA = raw_offset + 0x400000 + 0xC00\ndef to_va(raw_off):\n   return raw_off + 0x400000 + 0xC00\n\nresults = []\nfor n in [1, 2, 3, 10, 15, 16, 100,101, 102, 108, 109, 110,111, 112, 113,\n120, 130, 140, 150, 155, 160, 161, 162, 163, 164, 165, 166,167, 168,\n 170, 180,190, 200, 201, 210,220]:\n    pattern = b'\\x3d'+ struct.pack('<I', n) # CMPEAX, n\n   for m inre.finditer(re.escape(pattern), data):\n      va=to_va(m.start())\n       if va >= 0x4d0000 and va<= 0x550000:\n      results.append((n, hex(va)))\n\nresults.sort(key=lambda x: x[1])\nfor r in results:\n print(f'CMP EAX, {r[0]} (0x{r[0]:02x}) at VA {r[1]}')\n\"\n"}

Tool result
{"exitCode":0,"stdout":"CMP EAX, 160 (0xa0) at VA 0x4e650c\nCMP EAX, 180 (0xb4) at VA 0x4e656f\nCMP EAX, 200 (0xc8) at VA 0x4ebc64\nCMP EAX, 200 (0xc8) at VA 0x50ce32\nCMP EAX, 167 (0xa7) at VA 0x51730d\nCMP EAX, 201 (0xc9) at VA 0x517412\nCMP EAX, 210 (0xd2) at VA 0x53c80a\nCMP EAX, 200 (0xc8) at VA 0x53c817\n","stderr":""}

CMDs 160 and 180 are adjacent. Let me find the containing function and decompile it.

Call ghidra_search_functions_by_name

Arguments
{"filepath":"/sandbox/FFG.exe", "query":"FUN_004e6"}

Tool result
{"result":["FUN_004e6044 @ 004e6044","FUN_004e60a8 @ 004e60a8","FUN_004e6104 @ 004e6104","FUN_004e614c @ 004e614c","FUN_004e61ac @ 004e61ac","FUN_004e6288 @ 004e6288","FUN_004e62f4 @ 004e62f4","FUN_004e6360 @ 004e6360","FUN_004e63e8 @ 004e63e8","FUN_004e6464 @ 004e6464"]}

Call sandbox_execute

Arguments
{"command": "python3 -c \"\nimport struct, re\n\nwith open('/sandbox/FFG.exe', 'rb') as f:\n   data= f.read()\n\ndef va_to_raw(va):\nreturn va - 0x400000 - 0xC00+ 0x400  # .text: VA=0x1000 FileOff=0x400\n\ndef raw_to_va(raw):\n return raw+ 0x400000 + 0xC00\n\n# Look at the bytes around CMP EAX, 160 (VA 0x4e650c)\nraw = va_to_raw(0x4e650c)\nprint(f'Bytes at 0x4e650c (raw 0x{raw:x}):')\nprint(data[raw-20:raw+80].hex())\nprint()\n\n# Look for function prologuePUSH EBP; MOV EBP,ESP before 0x4e650c\n# Searchbackwards for 55 8B EC\nsearch_start = raw - 0x500\nsegment = data[search_start:raw+10]\nfor m in re.finditer(b'\\x55\\x8b\\xec',segment):\n    func_raw =search_start+ m.start()\n    func_va = raw_to_va(func_raw)\n    print(f'Possible funcprologue at VA {hex(func_va)} (raw 0x{func_raw:x})')\n\"\n"}

Tool result
{"exitCode":0,"stdout":"Bytes at 0x4e650c (raw 0xe5d0c):\n68ffffffba7d0000008b45f8e8bff6ffffba9c734e008b45cce872f9ffff8bd0a1e00e57008b00e8887106008945c8837dc8000f848f000000ba7c734e008b45cce84af9ffff8945c08d8558ffffff508b45fc8b4008ba08000000e87018000050ba8c73\n\nPossible func prologue at VA 0x4e6464 (raw 0xe5864)\n","stderr":""}

Let me fix the address calculation and read the exact bytes at the correct offsets.

Call sandbox_execute

Arguments
{"command": "python3 -c \"\nimport struct, re\n\nwith open('/sandbox/FFG.exe', 'rb') as f:\n    data = f.read()\n\n# Correct formula: file_offset = (VA - imageBase) -VirtualAddress + PointerToRawData\n# .text: imageBase=0x400000, VirtualAddress=0x1000,PointerToRawData=0x400\n# file_offset = VA - 0x400000 - 0x1000 + 0x400= VA - 0x400C00\ndef va2raw(va):return va - 0x400C00\ndef raw2va(r): return r + 0x400C00\n\n# Verify with knownaddress: XOR key at 0x568e7c (user confirmed)\nraw_xor = va2raw(0x568e7c)\nprint(f'XOR key at VA 0x568e7c -> raw 0x{raw_xor:x}')\nprint(f'Keybytes: {data[raw_xor:raw_xor+52]}')\nprint()\n\n# Verify CMPEAX, 160at VA 0x4e650c\nraw_cmp = va2raw(0x4e650c)\nprint(f'CMP EAX,160 atVA 0x4e650c -> raw 0x{raw_cmp:x}')\nprint(f'Bytes: {data[raw_cmp:raw_cmp+5].hex()}')\nprint()\n\n# Look for functionprologue before 0x4e650c\nsegment_start = va2raw(0x4e6000)\nsegment = data[segment_start:raw_cmp+10]\nprologues =[]\nfor m in re.finditer(b'\\x55\\x8b\\xec', segment):\n func_va= raw2va(segment_start + m.start())\n    prologues.append(hex(func_va))\nprint('Function prologuesbefore CMP:', prologues[-5:])\n\"\n"}

Tool result
{"exitCode":0,"stdout":"XOR key at VA 0x568e7c -> raw 0x16827c\nKey bytes: b'sdfgDDdffkojeroxdfJKHNplmkOIJHjklnIOJifg33gjkfsdpgko'\n\nCMP EAX,160 at VA 0x4e650c -> raw 0xe590c\nBytes: 3da0000000\n\nFunction prologues before CMP: ['0x4e62f4', '0x4e6360', '0x4e63ac', '0x4e63e8', '0x4e6464']\n","stderr":""}

XOR key verified. CMP EAX,160 confirmed. Function containing it starts at 0x4e6464. Let me decompile it.

Call ghidra_decompile_function

Arguments
{"filepath": "/sandbox/FFG.exe", "name_or_address": "0x004e6464"}

Tool result
{"result":"Succeeded and the artifact is saved at /sandbox/ghidra/decompiled_0x4e6464.txt. Please find the table of contents below. \n[{'funcName': 'FUN_00405088', 'lineNum': 137}, {'funcName': 'FUN_00405088', 'lineNum': 139}, {'funcName': 'FUN_00405088', 'lineNum': 141}, {'funcName': 'FUN_00405064', 'lineNum': 143}, {'funcName': 'FUN_004168a4', 'lineNum': 147}, {'funcName': 'FUN_004099ec', 'lineNum': 154}, {'funcName': 'FUN_004053f8', 'lineNum': 159}, {'funcName': 'FUN_0046984c', 'lineNum': 160}, {'funcName': 'FUN_004e5508', 'lineNum': 164}, {'funcName': 'FUN_004e5fc8', 'lineNum': 165}, {'funcName': 'FUN_004e8a58', 'lineNum': 166}, {'funcName': 'FUN_004e5fc8', 'lineNum': 167}, {'funcName': 'FUN_004e8994', 'lineNum': 168}, {'funcName': 'FUN_004e5fc8', 'lineNum': 169}, {'funcName': 'FUN_004e62f4', 'lineNum': 173}, {'funcName': 'FUN_004e6288', 'lineNum': 174}, {'funcName': 'FUN_004e6288', 'lineNum': 176}, {'funcName': 'FUN_004e6288', 'lineNum': 178}, {'funcName': 'FUN_004e7618', 'lineNum': 181}, {'funcName': 'FUN_004e6288', 'lineNum': 183}, {'funcName': 'FUN_004099ec', 'lineNum': 186}, {'funcName': 'FUN_004099ec', 'lineNum': 191}, {'funcName': 'FUN_004053f8', 'lineNum': 194}, {'funcName': 'FUN_004e5fc8', 'lineNum': 196}, {'funcName': 'FUN_004e6288', 'lineNum': 198}, {'funcName': 'FUN_0054daac', 'lineNum': 200}, {'funcName': 'FUN_004e6288', 'lineNum': 203}, {'funcName': 'FUN_004e81c8', 'lineNum': 206}, {'funcName': 'FUN_004e6288', 'lineNum': 208}, {'funcName': 'FUN_0054e824', 'lineNum': 211}, {'funcName': 'FUN_004e81c8', 'lineNum': 213}, {'funcName': 'FUN_004033f0', 'lineNum': 216}, {'funcName': 'FUN_004e5d88', 'lineNum': 218}, {'funcName': 'FUN_004e6288', 'lineNum': 221}, {'funcName': 'FUN_004e904c', 'lineNum': 223}, {'funcName': 'FUN_004e8210', 'lineNum': 225}, {'funcName': 'FUN_004e5fc8', 'lineNum': 227}, {'funcName': 'FUN_004e89ec', 'lineNum': 229}, {'funcName': 'FUN_004e5fc8', 'lineNum': 231}, {'funcName': 'FUN_004e8a58', 'lineNum': 233}, {'funcName': 'FUN_004e5fc8', 'lineNum': 235}, {'funcName': 'FUN_004e8994', 'lineNum': 237}, {'funcName': 'FUN_004e5fc8', 'lineNum': 239}, {'funcName': 'FUN_004e62f4', 'lineNum': 242}, {'funcName': 'FUN_004e90c0', 'lineNum': 243}, {'funcName': 'FUN_004e5fc8', 'lineNum': 245}, {'funcName': 'FUN_004e8210', 'lineNum': 248}, {'funcName': 'FUN_004e5fc8', 'lineNum': 251}, {'funcName': 'FUN_004e89ec', 'lineNum': 253}, {'funcName': 'FUN_004e5fc8', 'lineNum': 254}, {'funcName': 'FUN_0040b75c', 'lineNum': 257}, {'funcName': 'FUN_0040537c', 'lineNum': 258}, {'funcName': 'FUN_004e5fc8', 'lineNum': 259}, {'funcName': 'FUN_004e8580', 'lineNum': 260}, {'funcName': 'FUN_004e5fc8', 'lineNum': 261}, {'funcName': 'FUN_004e5508', 'lineNum': 262}, {'funcName': 'FUN_004e5fc8', 'lineNum': 263}, {'funcName': 'FUN_004e8a58', 'lineNum': 264}, {'funcName': 'FUN_004e5fc8', 'lineNum': 265}, {'funcName': 'FUN_004e8994', 'lineNum': 266}, {'funcName': 'FUN_004e5fc8', 'lineNum': 267}, {'funcName': 'FUN_0040ac40', 'lineNum': 272}, {'funcName': 'FUN_004e5fc8', 'lineNum': 273}, {'funcName': 'FUN_004e6288', 'lineNum': 277}, {'funcName': 'FUN_004e7758', 'lineNum': 278}, {'funcName': 'FUN_004e89ec', 'lineNum': 279}, {'funcName': 'FUN_004e5fc8', 'lineNum': 280}, {'funcName': 'FUN_004e89ec', 'lineNum': 283}, {'funcName': 'FUN_004e5fc8', 'lineNum': 284}, {'funcName': 'FUN_004e6288', 'lineNum': 287}, {'funcName': 'FUN_004e891c', 'lineNum': 288}, {'funcName': 'FUN_004e6288', 'lineNum': 289}, {'funcName': 'FUN_004e88e0', 'lineNum': 290}, {'funcName': 'FUN_0040ac40', 'lineNum': 295}, {'funcName': 'FUN_004e5fc8', 'lineNum': 296}, {'funcName': 'FUN_004e8acc', 'lineNum': 297}, {'funcName': '(ram, 0x4e6218, 8)', 'lineNum': 301}, {'funcName': 'FUN_004e6288', 'lineNum': 304}, {'funcName': 'FUN_004e6288', 'lineNum': 306}, {'funcName': 'FUN_004e7ef4', 'lineNum': 308}, {'funcName': 'FUN_004e8210', 'lineNum': 310}, {'funcName': 'FUN_004e5fc8', 'lineNum': 312}, {'funcName': 'FUN_004e89ec', 'lineNum': 314}, {'funcName': 'FUN_004e5fc8', 'lineNum': 316}, {'funcName': 'FUN_004e8580', 'lineNum': 318}, {'funcName': 'FUN_004e5fc8', 'lineNum': 320}, {'funcName': 'FUN_004e5fc8', 'lineNum': 322}, {'funcName': 'FUN_004e6288', 'lineNum': 325}, {'funcName': 'FUN_004e7f0c', 'lineNum': 326}, {'funcName': 'FUN_004e8210', 'lineNum': 327}, {'funcName': 'FUN_004e5fc8', 'lineNum': 328}, {'funcName': 'FUN_004e89ec', 'lineNum': 329}, {'funcName': 'FUN_004e5fc8', 'lineNum': 330}, {'funcName': 'FUN_004e8580', 'lineNum': 331}, {'funcName': 'FUN_004e5fc8', 'lineNum': 332}, {'funcName': 'FUN_004e5fc8', 'lineNum': 333}, {'funcName': 'FUN_004e5fc8', 'lineNum': 334}, {'funcName': 'FUN_004e7c68', 'lineNum': 337}, {'funcName': 'FUN_004e8210', 'lineNum': 338}, {'funcName': 'FUN_004e5fc8', 'lineNum': 339}, {'funcName': 'FUN_004e89ec', 'lineNum': 340}, {'funcName': 'FUN_004e5fc8', 'lineNum': 341}, {'funcName': 'FUN_004e8580', 'lineNum': 342}, {'funcName': 'FUN_004e5fc8', 'lineNum': 343}, {'funcName': 'FUN_004e5fc8', 'lineNum': 344}, {'funcName': 'FUN_004e6288', 'lineNum': 349}, {'funcName': 'FUN_004204a0', 'lineNum': 351}, {'funcName': 'FUN_004204a0', 'lineNum': 355}, {'funcName': 'FUN_004053f8', 'lineNum': 358}, {'funcName': 'FUN_004e5fc8', 'lineNum': 360}, {'funcName': 'FUN_004e862c', 'lineNum': 364}, {'funcName': 'FUN_004e8a58', 'lineNum': 367}, {'funcName': 'FUN_004e5fc8', 'lineNum': 368}, {'funcName': 'FUN_004e8994', 'lineNum': 369}, {'funcName': 'FUN_004e5fc8', 'lineNum': 370}, {'funcName': 'FUN_004e6288', 'lineNum': 373}, {'funcName': 'FUN_004e55a4', 'lineNum': 374}, {'funcName': 'FUN_004e6288', 'lineNum': 376}, {'funcName': 'FUN_004e6288', 'lineNum': 378}, {'funcName': 'FUN_004e6288', 'lineNum': 380}, {'funcName': 'FUN_004e6288', 'lineNum': 386}, {'funcName': 'FUN_004e53c4', 'lineNum': 388}, {'funcName': 'FUN_004e8acc', 'lineNum': 389}, {'funcName': 'FUN_004e61ac', 'lineNum': 392}, {'funcName': 'FUN_004e6288', 'lineNum': 394}, {'funcName': 'FUN_004e55a4', 'lineNum': 395}, {'funcName': 'FUN_004e77fc', 'lineNum': 397}, {'funcName': 'FUN_00409ad8', 'lineNum': 401}, {'funcName': 'FUN_004099ec', 'lineNum': 405}, {'funcName': 'FUN_004053f8', 'lineNum': 410}, {'funcName': 'FUN_004e5fc8', 'lineNum': 412}, {'funcName': 'FUN_004e53c4', 'lineNum': 415}, {'funcName': 'FUN_004e8958', 'lineNum': 416}, {'funcName': 'FUN_004e8acc', 'lineNum': 417}, {'funcName': 'FUN_004e8210', 'lineNum': 418}, {'funcName': 'FUN_004e5fc8', 'lineNum': 419}, {'funcName': 'FUN_00409ad8', 'lineNum': 422}, {'funcName': 'FUN_004099ec', 'lineNum': 426}, {'funcName': 'FUN_004053f8', 'lineNum': 431}, {'funcName': 'FUN_004e5fc8', 'lineNum': 433}, {'funcName': 'FUN_004e6288', 'lineNum': 439}, {'funcName': 'FUN_004e55cc', 'lineNum': 440}, {'funcName': 'FUN_004e78ac', 'lineNum': 444}, {'funcName': 'FUN_004e5fc8', 'lineNum': 446}, {'funcName': 'FUN_004e8210', 'lineNum': 447}, {'funcName': 'FUN_004e5fc8', 'lineNum': 448}, {'funcName': 'FUN_004e89ec', 'lineNum': 449}, {'funcName': 'FUN_004e5fc8', 'lineNum': 450}, {'funcName': 'FUN_004e56a0', 'lineNum': 453}, {'funcName': 'FUN_004e8958', 'lineNum': 456}, {'funcName': 'FUN_004e8acc', 'lineNum': 459}, {'funcName': 'FUN_004e5508', 'lineNum': 460}, {'funcName': 'FUN_004e5fc8', 'lineNum': 461}, {'funcName': 'FUN_004e8210', 'lineNum': 462}, {'funcName': 'FUN_004e5fc8', 'lineNum': 463}, {'funcName': 'FUN_004e6288', 'lineNum': 468}, {'funcName': 'FUN_004c8354', 'lineNum': 469}, {'funcName': 'FUN_00569fc8', 'lineNum': 471}, {'funcName': 'FUN_004e76e4', 'lineNum': 473}, {'funcName': 'FUN_00569fc8', 'lineNum': 474}, {'funcName': 'FUN_004e8958', 'lineNum': 475}, {'funcName': 'FUN_004e8acc', 'lineNum': 476}, {'funcName': 'FUN_004e8a58', 'lineNum': 477}, {'funcName': 'FUN_004e5fc8', 'lineNum': 478}, {'funcName': 'FUN_00416748', 'lineNum': 484}, {'funcName': 'FUN_004e6044', 'lineNum': 486}, {'funcName': 'FUN_00404184', 'lineNum': 488}]"}

Large function found. Let me read the decompiled artifact and search for the CMD=160 block.

Call ghidra_search_artifact

Arguments
{"artifact_path": "/sandbox/ghidra/decompiled_0x4e6464.txt", "regex": "0xa0|160|invline|InvLine|inv_full|to_inv|no_inv"}

Tool result
{"result":"    FUN_00405064((int *)&local_10);\n    return;\n  }\n  uStack_1c = (undefined1 *)0x4e64a1;\n  local_c = (int *)FUN_004168a4((undefined4 *)param_1[1],0);\n  local_20 = &LAB_004e7237;\n  local_24 = *in_FS_OFFSET;\n  *in_FS_OFFSET = (uint)&local_24;\n  local_28 = &DAT_004e7294;\n  local_2c = (undefined *)0x4e64d1;\n  uStack_1c = &stack0xfffffffc;\n  FUN_004099ec(*(uint *)(local_c[4] + 0x14),(int *)&local_50);\n  local_2c = local_50;\n  local_30 = \"  Data:\";\n  local_34 = &DAT_004e72c0;\n  iVar9 = *(int *)(local_c[4] + 0x30);\n  FUN_004053f8((int *)&local_4c,5);\n  FUN_0046984c(*(int *)PTR_DAT_00570b54,local_8,(int)local_4c);\n  iVar7 = *(int *)(iVar9 + 0x14);\n  if (iVar7 < 0xa1) {\n    if (iVar7 == 0xa0) {\n      FUN_004e5508(*(int *)PTR_DAT_0057121c,1,&local_e8);\n      FUN_004e5fc8((int)local_c,0xa0,local_e8);\n      FUN_004e8a58(local_8[2],&local_ec,extraout_ECX_17);\n      FUN_004e5fc8((int)local_c,0xa5,local_ec);\n      FUN_004e8994(local_8[2],&local_f0,extraout_ECX_18);\n      FUN_004e5fc8((int)local_c,0xa2,local_f0);\n    }\n    else if (iVar7 < 0x79) {\n      if (iVar7 == 0x78) {\n        FUN_004e62f4(iVar9,0x4e736c,&local_98);\n        uVar6 = FUN_004e6288(iVar9,0x4e737c);\n        local_44 = (char *)0x4e6880;\n        local_44 = (char *)FUN_004e6288(iVar9,0x4e738c);\n        pcStack_48 = (char *)0x4e688e;\n        iVar7 = FUN_004e6288(iVar9,0x4e739c);\n        pcVar2 = local_44;\n        local_44 = (char *)0x4e689c;\n        local_24 = FUN_004e7618(local_8[2],iVar7,(int)pcVar2,uVar6,local_98);\n        local_44 = (char *)0x4e68ac;\n        local_20 = (undefined1 *)FUN_004e6288(iVar9,0x4e73ac);\n        local_44 = \"inid=\";\n...\n        cVar4 = FUN_004e90c0((int *)local_8[2],(int)local_54);\n        if (cVar4 == '\\0') {\n          FUN_004e5fc8((int)local_c,100,(longlong *)\"no_login=1\");\n        }\n        else {\n          FUN_004e8210(local_8[2],&stack0xffffffa8);\n          uVar3 = _local_5c;\n          plVar10 = (longlong *)((ulonglong)_local_5c >> 0x20);\n          FUN_004e5fc8((int)local_c,100,plVar10);\n          _local_5c = (double)uVar3;\n          FUN_004e89ec(local_8[2],&local_5c,extraout_ECX_00);\n          FUN_004e5fc8((int)local_c,0x69,local_5c);\n          local_44 = (char *)0x0;\n          pcStack_48 = (char *)0x4e6632;\n          FUN_0040b75c((int *)&local_64);\n          FUN_0040537c((int *)&local_60,(longlong *)\"datetime=\",local_64);\n          FUN_004e5fc8((int)local_c,0x100,local_60);\n          FUN_004e8580(local_8[2],&local_68,extraout_ECX_01);\n          FUN_004e5fc8((int)local_c,0x78,local_68);\n          FUN_004e5508(*(int *)PTR_DAT_0057121c,1,&local_6c);\n          FUN_004e5fc8((int)local_c,0xa0,local_6c);\n          FUN_004e8a58(local_8[2],&local_70,extraout_ECX_02);\n          FUN_004e5fc8((int)local_c,0xa5,local_70);\n          FUN_004e8994(local_8[2],&local_74,extraout_ECX_03);\n          FUN_004e5fc8((int)local_c,0xa2,local_74);\n          local_88 = *(undefined4 *)(local_8[2] + 0xb4);\n          local_84 = 0;\n          local_80 = *(undefined4 *)(local_8[2] + 0xac);\n          local_7c = 0;\n          FUN_0040ac40((byte *)\"mapid=%d\\r\\nlocid=%d\",&local_88,1,(int *)&local_78);\n          FUN_004e5fc8((int)local_c,0x6e,local_78);\n        }\n      }\n      else if (iVar7 == 0x66) {\n        iVar9 = FUN_004e6288(iVar9,0x4e7320);\n        FUN_004e7758((int *)local_8[2],iVar9,1);\n        FUN_004e89ec(local_8[2],&local_8c,extraout_ECX_04);\n        FUN_004e5fc8((int)local_c,0x69,local_8c);\n      }\n      else if (iVar7 == 0x69) {\n...\n          FUN_004099ec((uint)local_28,(int *)&local_120);\n          local_4c = local_120;\n          local_50 = &DAT_004e72c0;\n          local_54 = \"work=1\";\n          _local_5c = (double)CONCAT44(0x4e6fe5,local_5c);\n          FUN_004053f8((int *)&local_118,7);\n          _local_5c = (double)CONCAT44(0x4e6ff8,local_5c);\n          FUN_004e5fc8((int)local_c,0xd2,local_118);\n        }\n      }\n    }\n  }\n  else if (iVar7 == 0xdc) {\n    iVar9 = FUN_004e6288(iVar9,0x4e74d8);\n    local_18 = FUN_004e55cc(*(int *)PTR_DAT_0057121c,iVar9,extraout_ECX_25);\n    if ((local_18 != 0) &&\n       (iVar9 = *(int *)(local_18 + 0x34) * *(int *)(local_18 + 0x20),\n       iVar9 - *(int *)(local_8[2] + 0xa4) == 0 || iVar9 < *(int *)(local_8[2] + 0xa4))) {\n      FUN_004e78ac(local_8[2],*(undefined4 *)(local_18 + 0x10),&local_34);\n      if ((int)local_34 < 1) {\n        FUN_004e5fc8((int)local_c,0x32,(longlong *)\"msg=inv_full\");\n        FUN_004e8210(local_8[2],&local_12c);\n        FUN_004e5fc8((int)local_c,100,local_12c);\n        FUN_004e89ec(local_8[2],&local_130,extraout_ECX_26);\n        FUN_004e5fc8((int)local_c,0x69,local_130);\n      }\n      else {\n        local_14 = FUN_004e56a0(*(int *)PTR_DAT_0057121c,*(int *)(local_18 + 0x18));\n        local_14[8] = (int)local_34;\n        local_14[9] = (int)local_30;\n        FUN_004e8958(local_8[2],\n                     *(int *)(local_8[2] + 0xa4) -\n                     *(int *)(local_18 + 0x34) * *(int *)(local_18 + 0x20));\n        FUN_004e8acc((int *)local_8[2]);\n        FUN_004e5508(*(int *)PTR_DAT_0057121c,1,&local_124);\n        FUN_004e5fc8((int)local_c,0xa0,local_124);\n        FUN_004e8210(local_8[2],&local_128);\n        FUN_004e5fc8((int)local_c,100,local_128);\n      }\n    }\n  }\n  else if (iVar7 == 0xde) {\n    local_24 = FUN_004e6288(iVar9,0x4e74d8);\n    local_2c = (undefined *)FUN_004c8354(*(int *)PTR_DAT_00570b38,local_24,extraout_ECX_27);\n    if ((local_2c != (undefined *)0x0) &&\n       (iVar9 = FUN_00569fc8((int)local_2c,extraout_EDX,extraout_ECX_28),\n       iVar9 <= *(int *)(local_8[2] + 0xa4))) {\n      FUN_004e76e4(local_8[2],local_24,*(int *)(local_2c + 0x40));\n      iVar9 = FUN_00569fc8((int)local_2c,extraout_EDX_00,extraout_ECX_29);\n      FUN_004e8958(local_8[2],*(int *)(local_8[2] + 0xa4) - iVar9);\n      FUN_004e8acc((int *)local_8[2]);\n      FUN_004e8a58(local_8[2],&local_134,extraout_ECX_30);\n      FUN_004e5fc8((int)local_c,0xa5,local_134);\n    }\n  }"}

Found the CMD=160 handler. It calls FUN_004e5508 to build the inventory payload. Let me decompile that function plus the response builder.

Call ghidra_decompile_function

Arguments
{"filepath": "/sandbox/FFG.exe", "name_or_address": "FUN_004e5508"}

Tool result
{"result":"\nvoid FUN_004e5508(int param_1,int param_2,undefined4 param_3)\n\n{\n  int local_20;\n  int local_1c;\n  int *local_18;\n  int local_14;\n  undefined4 local_10;\n  int local_c;\n  int local_8;\n  \n  local_10 = param_3;\n  local_c = param_2;\n  local_8 = param_1;\n  local_18 = (int *)FUN_00404154((int *)PTR_PTR_0041475c,'\\x01',param_3);\n  local_20 = FUN_004202b4(*(int *)(local_8 + 8));\n  if (-1 < local_20 + -1) {\n    local_1c = 0;\n    do {\n      local_14 = FUN_00420460(*(int *)(local_8 + 8),local_1c);\n      if (-1 < local_c && local_c == *(int *)(local_14 + 0x14)) {\n        FUN_004e5828(local_14,&local_18);\n        (**(code **)(*local_18 + 0x38))(local_18,0);\n      }\n      local_1c = local_1c + 1;\n      local_20 = local_20 + -1;\n    } while (local_20 != 0);\n  }\n  (**(code **)(*local_18 + 0x1c))(local_18,local_10);\n  FUN_00404184(local_18);\n  return;\n}\n\n"}

Call ghidra_decompile_function

Arguments
{"filepath":"/sandbox/FFG.exe","name_or_address": "FUN_004e5fc8"}

Tool result
{"result":"\nvoid FUN_004e5fc8(int param_1,int param_2,longlong *param_3)\n\n{\n  undefined4 extraout_ECX;\n  undefined4 *in_FS_OFFSET;\n  undefined4 uStack_20;\n  undefined1 *puStack_1c;\n  undefined1 *puStack_18;\n  int *local_14;\n  longlong *local_10;\n  int local_c;\n  int local_8;\n  \n  puStack_18 = (undefined1 *)0x4e5fdf;\n  local_10 = param_3;\n  local_c = param_2;\n  local_8 = param_1;\n  FUN_004054e4((int)param_3);\n  puStack_1c = &LAB_004e6037;\n  uStack_20 = *in_FS_OFFSET;\n  *in_FS_OFFSET = &uStack_20;\n  puStack_18 = &stack0xfffffffc;\n  local_14 = FUN_0042099c((int *)PTR_DAT_004e5b0c,'\\x01',extraout_ECX);\n  local_14[4] = local_c;\n  FUN_004050b8(local_14 + 10,local_10);\n  FUN_004166fc(*(int **)(local_8 + 0x14),(int)local_14);\n  *in_FS_OFFSET = uStack_20;\n  puStack_18 = &LAB_004e603e;\n  puStack_1c = (undefined1 *)0x4e6036;\n  FUN_00405064((int *)&local_10);\n  return;\n}\n\n"}

FUN_004e5508 iterates inventory items and calls FUN_004e5828 per item. Let me decompile that serializer.

Call ghidra_decompile_function

Arguments
{"filepath": "/sandbox/FFG.exe", "name_or_address": "FUN_004e5828"}

Tool result
{"result":"\nvoid FUN_004e5828(int param_1,undefined4 *param_2)\n\n{\n  longlong *plVar1;\n  int iVar2;\n  int *in_FS_OFFSET;\n  longlong *local_4c;\n  int local_48;\n  longlong *local_44;\n  int local_40;\n  longlong *local_3c;\n  int local_38;\n  longlong *local_34;\n  int local_30;\n  longlong *local_2c;\n  int local_28;\n  longlong *local_24;\n  int local_20;\n  longlong *local_1c;\n  int local_18;\n  longlong *local_14;\n  undefined1 *local_10;\n  undefined4 *local_c;\n  int local_8;\n  \n  local_10 = &stack0xfffffffc;\n  iVar2 = 9;\n  do {\n    iVar2 = iVar2 + -1;\n  } while (iVar2 != 0);\n  local_14 = (longlong *)&LAB_004e59d6;\n  local_18 = *in_FS_OFFSET;\n  *in_FS_OFFSET = (int)&local_18;\n  local_20 = *(int *)(param_1 + 0x10);\n  local_1c = (longlong *)0x0;\n  local_24 = (longlong *)0x4e585d;\n  local_c = param_2;\n  local_8 = param_1;\n  FUN_00409ad8((int *)&local_14);\n  local_1c = (longlong *)0x4e586d;\n  FUN_0040537c((int *)&local_10,(longlong *)&DAT_004e59ec,local_14);\n  local_1c = (longlong *)0x4e587a;\n  (**(code **)(*(int *)*local_c + 0x38))((int *)*local_c,local_10);\n  local_20 = *(int *)(local_8 + 0x14);\n  local_1c = (longlong *)0x0;\n  local_24 = (longlong *)0x4e588c;\n  FUN_00409ad8((int *)&local_1c);\n  plVar1 = local_1c;\n  local_1c = (longlong *)0x4e589c;\n  FUN_0040537c(&local_18,(longlong *)\"usrid=\",plVar1);\n  local_1c = (longlong *)0x4e58a9;\n  (**(code **)(*(int *)*local_c + 0x38))((int *)*local_c,local_18);\n  local_20 = *(int *)(local_8 + 0x18);\n  local_1c = (longlong *)0x0;\n  local_24 = (longlong *)0x4e58bb;\n  FUN_00409ad8((int *)&local_24);\n  local_1c = (longlong *)0x4e58cb;\n  FUN_0040537c(&local_20,(longlong *)\"templid=\",local_24);\n  local_1c = (longlong *)0x4e58d8;\n  (**(code **)(*(int *)*local_c + 0x38))((int *)*local_c,local_20);\n  local_1c = (longlong *)0x4e58e6;\n  FUN_004099ec(*(uint *)(local_8 + 0x1c),(int *)&local_2c);\n  local_1c = (longlong *)0x4e58f6;\n  FUN_0040537c(&local_28,(longlong *)\"cellnum=\",local_2c);\n  local_1c = (longlong *)0x4e5903;\n  (**(code **)(*(int *)*local_c + 0x38))((int *)*local_c,local_28);\n  local_1c = (longlong *)0x4e5911;\n  FUN_004099ec(*(uint *)(local_8 + 0x20),(int *)&local_34);\n  local_1c = (longlong *)0x4e5921;\n  FUN_0040537c(&local_30,(longlong *)&DAT_004e5a30,local_34);\n  local_1c = (longlong *)0x4e592e;\n  (**(code **)(*(int *)*local_c + 0x38))((int *)*local_c,local_30);\n  local_1c = (longlong *)0x4e593c;\n  FUN_004099ec(*(uint *)(local_8 + 0x24),(int *)&local_3c);\n  local_1c = (longlong *)0x4e594c;\n  FUN_0040537c(&local_38,(longlong *)&DAT_004e5a3c,local_3c);\n  local_1c = (longlong *)0x4e5959;\n  (**(code **)(*(int *)*local_c + 0x38))((int *)*local_c,local_38);\n  local_1c = (longlong *)0x4e5967;\n  FUN_004099ec(*(uint *)(local_8 + 0x28),(int *)&local_44);\n  local_1c = (longlong *)0x4e5977;\n  FUN_0040537c(&local_40,(longlong *)&DAT_004e5a48,local_44);\n  local_1c = (longlong *)0x4e5984;\n  (**(code **)(*(int *)*local_c + 0x38))((int *)*local_c,local_40);\n  local_1c = (longlong *)0x4e5992;\n  FUN_004099ec(*(uint *)(local_8 + 0x2c),(int *)&local_4c);\n  local_1c = (longlong *)0x4e59a2;\n  FUN_0040537c(&local_48,(longlong *)\"count=\",local_4c);\n  local_1c = (longlong *)0x4e59af;\n  (**(code **)(*(int *)*local_c + 0x38))((int *)*local_c,local_48);\n  local_1c = (longlong *)0x4e59bb;\n  (**(code **)(*(int *)*local_c + 0x38))((int *)*local_c,0);\n  *in_FS_OFFSET = local_18;\n  local_10 = &LAB_004e59dd;\n  local_14 = (longlong *)0x4e59d5;\n  FUN_00405088((int *)&local_4c,0x10);\n  return;\n}\n\n"}

Call ghidra_decompile_function

Arguments
{"filepath": "/sandbox/FFG.exe","name_or_address": "FUN_004e8a58"}

Tool result
{"result":"\nvoid FUN_004e8a58(int param_1,undefined4 param_2,undefined4 param_3)\n\n{\n  int iVar1;\n  int local_1c;\n  int *local_14;\n  int local_10;\n  undefined4 local_c;\n  int local_8;\n  \n  local_c = param_2;\n  local_8 = param_1;\n  local_14 = (int *)FUN_00404154((int *)PTR_PTR_0041475c,'\\x01',param_3);\n  local_1c = FUN_004202b4(*(int *)(local_8 + 0x14));\n  if (-1 < local_1c + -1) {\n    local_10 = 0;\n    do {\n      iVar1 = FUN_00420460(*(int *)(local_8 + 0x14),local_10);\n      FUN_004c8878(iVar1,&local_14);\n      local_10 = local_10 + 1;\n      local_1c = local_1c + -1;\n    } while (local_1c != 0);\n  }\n  (**(code **)(*local_14 + 0x1c))(local_14,local_c);\n  FUN_00404184(local_14);\n  return;\n}\n\n"}

Call ghidra_decompile_function

Arguments
{"filepath": "/sandbox/FFG.exe", "name_or_address":"FUN_004e8994"}

Tool result
{"result":"\nvoid FUN_004e8994(int param_1,undefined4 param_2,undefined4 param_3)\n\n{\n  int *local_14;\n  int local_10;\n  undefined4 local_c;\n  int local_8;\n  \n  local_c = param_2;\n  local_8 = param_1;\n  local_14 = (int *)FUN_00404154((int *)PTR_PTR_0041475c,'\\x01',param_3);\n  local_10 = 1;\n  do {\n    FUN_004e9f88((uint *)(local_8 + 0x14 + local_10 * 0x10),&local_14);\n    local_10 = local_10 + 1;\n  } while (local_10 != 6);\n  (**(code **)(*local_14 + 0x1c))(local_14,local_c);\n  FUN_00404184(local_14);\n  return;\n}\n\n"}

Now let me read the string literals at the DAT addresses used in the item serializer.

Call sandbox_execute

Arguments
{"command": "python3 -c \"\nimport struct, re\n\nwith open('/sandbox/FFG.exe', 'rb')as f:\n    data = f.read()\n\ndef va2raw(va): return va - 0x400C00\n\ndef read_delphi_str(va):\n    'ReadDelphi AnsiString at VA (ptr points to char data, 4 bytes before =length)'\n   raw =va2raw(va)\n    # Check if it looks likea Delphi string: 4-byte refcount, 4-bytelen, stringdata\ntry:\n refcount =struct.unpack_from('<i', data, raw -8)[0]\n length = struct.unpack_from('<I', data, raw - 4)[0]\n    if 0 < length < 100 and (refcount == -1or refcount > 0):\n  s = data[raw:raw+length]\n returns.decode('latin-1', errors='replace')\n    except:\n pass\n   # Try reading as raw null-terminated string\nend = data.index(b'\\x00', raw)\n    return data[raw:end].decode('latin-1', errors='replace')\n\n# Read the DAT stringsused in FUN_004e5828\naddrs = {\n    '0x4e59ec (offset+0x10)': 0x4e59ec,\n    '0x4e5a30 (offset+0x20)': 0x4e5a30,\n    '0x4e5a3c (offset+0x24)': 0x4e5a3c,\n   '0x4e5a48 (offset+0x28)': 0x4e5a48,\n}\n\nfor name, va in addrs.items():\n    raw = va2raw(va)\nprint(f'{name}:raw=0x{raw:x}')\n    print(f'  hex: {data[raw:raw+32].hex()}')\n    print(f'  ascii: {data[raw:raw+32]}')\nprint()\n\n# Also check the Delphi string pointer at param_1+0x10\n# The first field called with FUN_00409ad8 (which is likelyIntToStr)\n# Let's read what FUN_00409ad8 iscalled on: *(int *)(param_1 + 0x10)\n# That'sthe first 4bytes beforethe string label\nprint('Also check DAT_004e59ecwithsurrounding bytes:')\nr = va2raw(0x4e59ec) - 8\nprint(data[r:r+20].hex())\n\"\n"}

Tool result
{"exitCode":0,"stdout":"0x4e59ec (offset+0x10): raw=0xe4dec\n  hex: 69643d00ffffffff0600000075737269643d0000ffffffff0800000074656d70\n  ascii: b'id=\\x00\\xff\\xff\\xff\\xff\\x06\\x00\\x00\\x00usrid=\\x00\\x00\\xff\\xff\\xff\\xff\\x08\\x00\\x00\\x00temp'\n\n0x4e5a30 (offset+0x20): raw=0xe4e30\n  hex: 63783d00ffffffff0300000063793d00ffffffff040000006475723d00000000\n  ascii: b'cx=\\x00\\xff\\xff\\xff\\xff\\x03\\x00\\x00\\x00cy=\\x00\\xff\\xff\\xff\\xff\\x04\\x00\\x00\\x00dur=\\x00\\x00\\x00\\x00'\n\n0x4e5a3c (offset+0x24): raw=0xe4e3c\n  hex: 63793d00ffffffff040000006475723d00000000ffffffff06000000636f756e\n  ascii: b'cy=\\x00\\xff\\xff\\xff\\xff\\x04\\x00\\x00\\x00dur=\\x00\\x00\\x00\\x00\\xff\\xff\\xff\\xff\\x06\\x00\\x00\\x00coun'\n\n0x4e5a48 (offset+0x28): raw=0xe4e48\n  hex: 6475723d00000000ffffffff06000000636f756e743d0000558bec33c0556891\n  ascii: b'dur=\\x00\\x00\\x00\\x00\\xff\\xff\\xff\\xff\\x06\\x00\\x00\\x00count=\\x00\\x00U\\x8b\\xec3\\xc0Uh\\x91'\n\nAlso check DAT_004e59ec with surrounding bytes:\nffffffff0300000069643d00ffffffff06000000\n","stderr":""}

Now I have the main inventory item structure. Let me also check FUN_004c8878 (CMD=165) and FUN_004e9f88 (CMD=162), and the cell= string.

Call ghidra_decompile_function

Arguments
{"filepath": "/sandbox/FFG.exe", "name_or_address": "FUN_004c8878"}

Tool result
{"result":"\nvoid FUN_004c8878(int param_1,undefined4 *param_2)\n\n{\n  undefined4 *in_FS_OFFSET;\n  undefined4 uStack_28;\n  undefined1 *puStack_24;\n  undefined1 *puStack_20;\n  longlong *local_1c;\n  int local_18;\n  longlong *local_14;\n  int local_10;\n  undefined4 *local_c;\n  int local_8;\n  \n  puStack_20 = &stack0xfffffffc;\n  local_10 = 0;\n  local_14 = (longlong *)0x0;\n  local_18 = 0;\n  local_1c = (longlong *)0x0;\n  puStack_24 = &LAB_004c8914;\n  uStack_28 = *in_FS_OFFSET;\n  *in_FS_OFFSET = &uStack_28;\n  local_c = param_2;\n  local_8 = param_1;\n  FUN_004099ec(*(uint *)(param_1 + 4),(int *)&local_14);\n  FUN_0040537c(&local_10,(longlong *)\"usrbt_id=\",local_14);\n  (**(code **)(*(int *)*local_c + 0x38))((int *)*local_c,local_10);\n  FUN_004099ec(*(uint *)(local_8 + 0xc),(int *)&local_1c);\n  FUN_0040537c(&local_18,(longlong *)\"usrbt_count=\",local_1c);\n  (**(code **)(*(int *)*local_c + 0x38))((int *)*local_c,local_18);\n  (**(code **)(*(int *)*local_c + 0x38))((int *)*local_c,0);\n  *in_FS_OFFSET = uStack_28;\n  puStack_20 = &LAB_004c891b;\n  puStack_24 = (undefined1 *)0x4c8913;\n  FUN_00405088((int *)&local_1c,4);\n  return;\n}\n\n"}

Call ghidra_decompile_function

Arguments
{"filepath": "/sandbox/FFG.exe", "name_or_address": "FUN_004e9f88"}

Tool result
{"result":"\nvoid FUN_004e9f88(uint *param_1,undefined4 *param_2)\n\n{\n  longlong *plVar1;\n  int iVar2;\n  int *in_FS_OFFSET;\n  longlong *local_2c;\n  int local_28;\n  longlong *local_24;\n  int local_20;\n  longlong *local_1c;\n  int local_18;\n  longlong *local_14;\n  undefined1 *local_10;\n  undefined4 *local_c;\n  uint *local_8;\n  \n  local_10 = &stack0xfffffffc;\n  iVar2 = 5;\n  do {\n    iVar2 = iVar2 + -1;\n  } while (iVar2 != 0);\n  local_14 = (longlong *)&LAB_004ea07d;\n  local_18 = *in_FS_OFFSET;\n  *in_FS_OFFSET = (int)&local_18;\n  local_1c = (longlong *)0x4e9fb8;\n  local_c = param_2;\n  local_8 = param_1;\n  FUN_004099ec(*param_1,(int *)&local_14);\n  local_1c = (longlong *)0x4e9fc8;\n  FUN_0040537c((int *)&local_10,(longlong *)\"otnum=\",local_14);\n  local_1c = (longlong *)0x4e9fd5;\n  (**(code **)(*(int *)*local_c + 0x38))((int *)*local_c,local_10);\n  local_1c = (longlong *)0x4e9fe3;\n  FUN_004099ec(local_8[1],(int *)&local_1c);\n  plVar1 = local_1c;\n  local_1c = (longlong *)0x4e9ff3;\n  FUN_0040537c(&local_18,(longlong *)\"otbaitid=\",plVar1);\n  local_1c = (longlong *)0x4ea000;\n  (**(code **)(*(int *)*local_c + 0x38))((int *)*local_c,local_18);\n  local_1c = (longlong *)0x4ea00e;\n  FUN_004099ec(local_8[2],(int *)&local_24);\n  local_1c = (longlong *)0x4ea01e;\n  FUN_0040537c(&local_20,(longlong *)\"speedind=\",local_24);\n  local_1c = (longlong *)0x4ea02b;\n  (**(code **)(*(int *)*local_c + 0x38))((int *)*local_c,local_20);\n  local_1c = (longlong *)0x4ea039;\n  FUN_004099ec(local_8[3],(int *)&local_2c);\n  local_1c = (longlong *)0x4ea049;\n  FUN_0040537c(&local_28,(longlong *)\"otdeep=\",local_2c);\n  local_1c = (longlong *)0x4ea056;\n  (**(code **)(*(int *)*local_c + 0x38))((int *)*local_c,local_28);\n  local_1c = (longlong *)0x4ea062;\n  (**(code **)(*(int *)*local_c + 0x38))((int *)*local_c,0);\n  *in_FS_OFFSET = local_18;\n  local_10 = &LAB_004ea084;\n  local_14 = (longlong *)0x4ea07c;\n  FUN_00405088((int *)&local_2c,8);\n  return;\n}\n\n"}

Call ghidra_get_xrefs_to

Arguments
{"filepath": "/sandbox/FFG.exe", "address": "0x004ee7bc"}

Tool result
{"result":["From 004ee6b1 [PARAM]"]}

Now let me find the function containing 0x4ee6b1 (which uses cell=).

Call sandbox_execute

Arguments
{"command":"python3 -c \"\nimportstruct, re\n\nwithopen('/sandbox/FFG.exe', 'rb') as f:\n    data =f.read()\n\ndef va2raw(va): return va - 0x400C00\ndef raw2va(r):return r+ 0x400C00\n\ntarget =0x4ee6b1\ntarget_raw = va2raw(target)\n\n# Search backward from target for PUSHEBP; MOVEBP, ESP (558B EC)\nsearch_start_raw = max(0, target_raw - 0x1000)\nsegment =data[search_start_raw:target_raw+1]\n\nprologues = []\nfor min re.finditer(b'\\x55\\x8b\\xec', segment):\n    func_va = raw2va(search_start_raw + m.start())\n    prologues.append(hex(func_va))\n\nprint('Nearest function prologues before 0x4ee6b1:',prologues[-5:])\nprint('Most likely funcstart:',prologues[-1] if prologues else 'none')\n\"\n"}

Tool result
{"exitCode":0,"stdout":"Nearest function prologues before 0x4ee6b1: ['0x4edac8', '0x4edecc', '0x4edfec', '0x4ee00c', '0x4ee570']\nMost likely func start: 0x4ee570\n","stderr":""}

Call ghidra_decompile_function

Arguments
{"filepath": "/sandbox/FFG.exe", "name_or_address": "0x004ee570"}

Tool result
{"result":"Function not found"}

Call sandbox_execute

Arguments
{"command": "python3 -c\"\nimport struct, re\n\nwithopen('/sandbox/FFG.exe', 'rb') as f:\n  data =f.read()\n\ndef va2raw(va): return va - 0x400C00\ndefraw2va(r): return r + 0x400C00\n\n# Read raw bytes at cell= location(0x4ee7bc) and surroundings\nraw= va2raw(0x4ee7bc)\nprint(f'cell= at VA0x4ee7bc, raw 0x{raw:x}')\nprint('bytes:', data[raw:raw+20])\nprint()\n\n#Read the function at 0x4ee570raw\nraw_func= va2raw(0x4ee570)\nprint(f'Function at 0x4ee570, raw 0x{raw_func:x}')\nprint('bytes:', data[raw_func:raw_func+30].hex())\n\n# Find nearby Ghidra-known functions\n#List all function names in range 0x4ee000-0x4ef000\nprint()\nprint('Checking what functions exist in 0x4ee000-0x4ef500:')\n# Search forDelphi function prologues\nfor offset in range(va2raw(0x4ee000), va2raw(0x4ef500)):\n    if data[offset:offset+3] ==b'\\x55\\x8b\\xec':\n       va= raw2va(offset)\n        print(f' Prologue at VA {hex(va)}')\n\" 2>&1 |head-50\n"}

Tool result
{"exitCode":0,"stdout":"cell= at VA 0x4ee7bc, raw 0xedbbc\nbytes: b'cell=\\x00\\x00\\x00\\xff\\xff\\xff\\xff\\x04\\x00\\x00\\x00cnt='\n\nFunction at 0x4ee570, raw 0xed970\nbytes: 558becb9070000006a006a004975f98945fc33c055686ae74e0064ff3064\n\nChecking what functions exist in 0x4ee000-0x4ef500:\n  Prologue at VA 0x4ee00c\n  Prologue at VA 0x4ee570\n  Prologue at VA 0x4ee7ec\n  Prologue at VA 0x4eea84\n  Prologue at VA 0x4ef288\n  Prologue at VA 0x4ef458\n","stderr":""}

Let me search for and decompile the function that uses cell=.

Call ghidra_search_functions_by_name

Arguments
{"filepath": "/sandbox/FFG.exe", "query": "FUN_004ee"}

Tool result
{"result":["FUN_004ee7ec @ 004ee7ec"]}

Call ghidra_disassemble_function

Arguments
{"filepath": "/sandbox/FFG.exe", "address": "0x004ee570"}

Tool result
{"result":["No function found at or containing address 0x004ee570"]}

Call sandbox_execute

Arguments
{"command":"python3 -c \"\nimportstruct\n\nwith open('/sandbox/FFG.exe', 'rb') as f:\n    data = f.read()\n\ndef va2raw(va): returnva -0x400C00\ndef raw2va(r): return r + 0x400C00\n\n# Read cell= and surrounding strings\nr = va2raw(0x4ee7bc)\nprint('Stringblock around cell=:')\nprint(data[r-20:r+80])\nprint()\n\n# Check the function 0x4ee570 bytes to verify ifit'sreallya function\nr2 = va2raw(0x4ee570)\nprint('Bytes at 0x4ee570:')\nprint(data[r2:r2+200].hex())\n\"\n"}

Tool result
{"exitCode":0,"stdout":"String block around cell=:\nb'\\xff\\xff\\xff\\xff\\x03\\x00\\x00\\x00cy=\\x00\\xff\\xff\\xff\\xff\\x05\\x00\\x00\\x00cell=\\x00\\x00\\x00\\xff\\xff\\xff\\xff\\x04\\x00\\x00\\x00cnt=\\x00\\x00\\x00\\x00\\xff\\xff\\xff\\xff\\x0e\\x00\\x00\\x00batechange.ogg\\x00\\x00U\\x8b\\xec\\x83\\xc4\\xf03\\xc0\\x89E\\xf03\\xc0Uh\\x11\\xeaN\\x00d\\xff0d\\x89 j\\x00j\\x00\\xa1\\xe0\\x0f'\n\nBytes at 0x4ee570:\n558becb9070000006a006a004975f98945fc33c055686ae74e0064ff30648920a150aa57008b80a4000000ba01000000e8bfb5f1ff8945f4837df4007f1a6a50a1dc0d57008b0033c9ba80e74e00e809c70600e987010000a140aa57008b4010483b45f47d1a6a50a1dc0d57008b0033c9ba80e74e00e8e1c60600e95f010000b201a15c474100e8585bf1ff8945f833c0556836e74e0064ff30648920a140aa57008b50102b55f4a140aa5700e85a800700a140aa57008b401433d252508d45ece8a2b4f1ff8b4d\n","stderr":""}

I have all the key data. Let me also read the adjacent strings for completeness.

Call sandbox_execute

Arguments
{"command":"python3 -c \"\nimportstruct\n\nwith open('/sandbox/FFG.exe', 'rb') as f:\n    data = f.read()\n\ndef va2raw(va): return va - 0x400C00\n\n# Read stringblock around 0x4ee780 to identify it\nr = va2raw(0x4ee780)\nprint('Stringsaround 0x4ee780:')\nprint(data[r-32:r+80])\nprint()\n\n# Readfullstring block around cell= area\nr2 = va2raw(0x4ee700)\nprint('String block 0x4ee700-0x4ee800:')\nblock = data[r2:r2+256]\n# Extract null-terminated strings\nimport re\nfor m in re.finditer(b'[\\\\x20-\\\\x7e]{3,}', block):\n    va = 0x4ee700 + m.start()\n    print(f'  0x{va:x}: {m.group().decode()}')\n\"\n"}

Tool result
{"exitCode":0,"stdout":"Strings around 0x4ee780:\nb'\\x0b\\x00\\x00\\x00\\xe8\\x1fi\\xf1\\xff\\xc3\\xe9\\xa9a\\xf1\\xff\\xeb\\xeb\\x8b\\xe5]\\xc3\\x00\\x00\\x00\\xff\\xff\\xff\\xff\\t\\x00\\x00\\x00error.wav\\x00\\x00\\x00\\xff\\xff\\xff\\xff\\x06\\x00\\x00\\x00wpnid=\\x00\\x00\\xff\\xff\\xff\\xff\\x03\\x00\\x00\\x00cx=\\x00\\xff\\xff\\xff\\xff\\x03\\x00\\x00\\x00cy=\\x00\\xff\\xff\\xff\\xff\\x05\\x00\\x00\\x00cell=\\x00\\x00\\x00\\xff\\xff\\xff\\xff\\x04\\x00\\x00\\x00cnt='\n\nString block 0x4ee700-0x4ee800:\n  0x4ee722: ZYYd\n  0x4ee751: ZYYd\n  0x4ee780: error.wav\n  0x4ee794: wpnid=\n  0x4ee7a4: cx=\n  0x4ee7b0: cy=\n  0x4ee7bc: cell=\n  0x4ee7cc: cnt=\n  0x4ee7dc: batechange.ogg\n","stderr":""}

I now have the complete picture. Here's the full analysis:


testserver1.py Review

Your existing code is well-structured. Key observations:

  • Protocol implementation is correct (header, XOR, checksum confirmed in FFG.exe)
  • brute_one_id_all_structs(cmd_id=160) uses wrong approach — CMD=160 needs no payload from client; server should reply, not client brute
  • send_inventory_test guesses wrong field names (uses inid, id, x, y, w, h — actual fields are different, see below)

CMD=160 Full Inventory Structure

Client → Server (CMD=160 Request)

Header only, empty payload. No KV fields parsed server-side.
flags=0, length=0

Server → Client (CMD=160 Response)

flags=0 (KV text), payload = concatenated item blocks

Each inventory item is serialized by FUN_004e5828 from the TInventoryItem object:

struct TInventoryItem {
    /* +0x00..+0x0c: Delphi TObject header (vtable + internals) */
    int  id;       // +0x10  — unique item instance ID
    int  usrid;    // +0x14  — bag type (1 = main inventory) [filter: only usrid=1 sent]
    int  templid;  // +0x18  — item template/type ID (links to GameData)
    int  cellnum;  // +0x1c  — linear cell index in grid
    int  cx;       // +0x20  — grid X position
    int  cy;       // +0x24  — grid Y position
    int  dur;      // +0x28  — durability
    int  count;    // +0x2c  — stack quantity
};

KV wire format per item:

id=<N>\r\n
usrid=<N>\r\n
templid=<N>\r\n
cellnum=<N>\r\n
cx=<N>\r\n
cy=<N>\r\n
dur=<N>\r\n
count=<N>\r\n
\r\n               ← blank-line separator between items (null write)

Multiple items are concatenated into one CMD=160 payload. Items filtered: only usrid == 1 (main bag). Inventory source: global PTR_DAT_0057121c (the inventory manager singleton, TInvManager[+0x08] = TObjectList).


Two Additional CMDs Sent Immediately After CMD=160

The server always sends all three in sequence on inventory open AND on login:

CMD=162 (0xA2) — Otter/Pet Slot Data

Serialized by FUN_004e9f88, 5 slots (indices 1–5), stride 0x10 in player object starting at player+0x14:

otnum=<slot_index>\r\n      — otter slot number (1–5)
otbaitid=<N>\r\n            — bait item ID equipped to this otter
speedind=<N>\r\n            — speed indicator
otdeep=<N>\r\n              — otter depth setting
\r\n

CMD=165 (0xA5) — Bait Items

Serialized by FUN_004e8a58 → FUN_004c8878, iterates bait list at player+0x14:

usrbt_id=<N>\r\n           — bait template ID
usrbt_count=<N>\r\n        — bait count
\r\n

Corrected send_inventory for your emulator

def send_inventory(srv, conn):
    """CMD=160 → 162 → 165 sequence on inventory open."""

    # ── CMD=160: main bag items ───────────────────────────────────
    items = [
        # id, usrid, templid, cellnum, cx, cy, dur, count
        (1, 1, 2001, 0, 0, 0, 100, 1),   # fishing line, slot 0
        (2, 1, 3005, 1, 4, 0, 100, 5),   # bait x5, slot 1
    ]
    payload = b""
    for (id_, usrid, templid, cellnum, cx, cy, dur, count) in items:
        payload += (
            f"id={id_}\r\nusrid={usrid}\r\ntemplid={templid}\r\n"
            f"cellnum={cellnum}\r\ncx={cx}\r\ncy={cy}\r\n"
            f"dur={dur}\r\ncount={count}\r\n\r\n"
        ).encode()
    srv.send_pkt(conn, cmd=160, payload=payload, flags=0, encrypt=True)

    # ── CMD=162: otter slots (5 slots) ───────────────────────────
    otter_payload = b""
    for slot in range(1, 6):
        otter_payload += (
            f"otnum={slot}\r\notbaitid=0\r\nspeedind=0\r\notdeep=0\r\n\r\n"
        ).encode()
    srv.send_pkt(conn, cmd=162, payload=otter_payload, flags=0, encrypt=True)

    # ── CMD=165: bait items ───────────────────────────────────────
    bait_payload = b"usrbt_id=3005\r\nusrbt_count=5\r\n\r\n"
    srv.send_pkt(conn, cmd=165, payload=bait_payload, flags=0, encrypt=True)

Key Code Locations (FFG.exe)

AddressRole
FUN_004e6464Main packet dispatch (contains if CMD == 0xa0)
FUN_004e5508Iterates inventory list → serializes to packet
FUN_004e5828Single item serializer (KV fields above)
FUN_004e8994Otter slot serializer (CMD=162)
FUN_004c8878Bait item serializer (CMD=165)
0x568e7cXOR key (confirmed ✓)
0x569231XOR encrypt function (confirmed ✓)

client sends "stream read error" on CMD=165

Dr.Binary