aboutsummaryrefslogtreecommitdiffstats
path: root/idatui/domain.py
diff options
context:
space:
mode:
authorblasty <blasty@local>2026-07-09 12:25:49 +0200
committerblasty <blasty@local>2026-07-09 12:25:49 +0200
commit51b7cc5a1e4fbb861909946b2feb842294d412e8 (patch)
treef1f8b6de3dc9fb039d619f656b5e35e3694c2b75 /idatui/domain.py
parenttui: --open PATH to create/attach a session for an arbitrary binary (diff)
downloadida-tui-51b7cc5a1e4fbb861909946b2feb842294d412e8.tar.gz
ida-tui-51b7cc5a1e4fbb861909946b2feb842294d412e8.tar.xz
ida-tui-51b7cc5a1e4fbb861909946b2feb842294d412e8.zip
decompiler view: Tab/Shift+Tab toggle disasm<->pseudocode, full bodies
- domain.decompile now fetches the FULL body: server caps responses at 50KB and clips strings to 1KB, but caches the full output and exposes it at _meta.ida_mcp.download_url -> we GET that so pseudocode is complete (main: 43257 chars, not a 1KB stub). include_addresses gives per-line /*0xEA*/. - DecompView (read-only TextArea, cpp highlighting best-effort, line numbers) - Tab and Shift+Tab toggle the code pane; lazy decompile in a worker, cached; hard-failures ('decompilation failed') shown gracefully with disasm fallback - pilot suite 14/14 (adds tab->pseudocode, full-body, shift+tab->disasm)
Diffstat (limited to 'idatui/domain.py')
-rw-r--r--idatui/domain.py37
1 files changed, 32 insertions, 5 deletions
diff --git a/idatui/domain.py b/idatui/domain.py
index 859c883..9417150 100644
--- a/idatui/domain.py
+++ b/idatui/domain.py
@@ -21,11 +21,13 @@ Textual worker threads; the internal prefetch pool is separate and small.
from __future__ import annotations
+import json
import re
import threading
+import urllib.request
from concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass, field
-from typing import Callable, Iterable
+from typing import Callable
from .client import IDAClient, IDAToolError
@@ -352,16 +354,41 @@ class Program:
# -- decompilation ----------------------------------------------------- #
def decompile(self, ea: int, refresh: bool = False) -> Decompilation:
+ """Full pseudocode for a function.
+
+ The server truncates responses over 50KB (strings clipped to 1000
+ chars) but caches the full output and exposes it at
+ ``_meta.ida_mcp.download_url``. We transparently fetch that so the view
+ always gets the complete body, not a 1KB stub.
+ """
if not refresh:
with self._lock:
hit = self._decomp.get(ea)
if hit is not None:
return hit
- payload = self.client.call("decompile", addr=hex(ea))
- result = _parse_decompilation(ea, payload)
+ envelope = self.client.call_envelope("decompile", addr=hex(ea))
+ result = envelope.get("result", {})
+ payload = result.get("structuredContent")
+ if payload is None: # fall back to text content
+ payload = self.client._extract_payload("decompile", result)
+ meta = (result.get("_meta") or {}).get("ida_mcp")
+ if isinstance(meta, dict) and meta.get("download_url"):
+ full = self._fetch_output(meta["download_url"])
+ if isinstance(full, dict) and full.get("code"):
+ payload = full
+ dec = _parse_decompilation(ea, payload)
with self._lock:
- self._decomp[ea] = result
- return result
+ self._decomp[ea] = dec
+ return dec
+
+ @staticmethod
+ def _fetch_output(url: str, timeout: float = 15.0):
+ """GET the server's cached full-output blob (plain HTTP, not MCP)."""
+ try:
+ with urllib.request.urlopen(url, timeout=timeout) as r:
+ return json.loads(r.read().decode("utf-8", "replace"))
+ except Exception: # noqa: BLE001 -- fall back to the truncated preview
+ return None
# -- address resolution ------------------------------------------------ #
def resolve(self, target: int | str) -> int: