diff options
| author | blasty <blasty@local> | 2026-07-09 12:25:49 +0200 |
|---|---|---|
| committer | blasty <blasty@local> | 2026-07-09 12:25:49 +0200 |
| commit | 51b7cc5a1e4fbb861909946b2feb842294d412e8 (patch) | |
| tree | f1f8b6de3dc9fb039d619f656b5e35e3694c2b75 /idatui/domain.py | |
| parent | tui: --open PATH to create/attach a session for an arbitrary binary (diff) | |
| download | ida-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.py | 37 |
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: |
