') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); GitHub - maci0/recoverage: Coverage dashboard for binary-matching decompilation projects · GitHub
Skip to content

Repository files navigation

🔍 recoverage

recoverage mascot — a raccoon detective investigating code coverage
Coverage dashboard for binary-matching decompilation projects.
See every byte. Track every match. Ship the decomp.

Install · Quick Start · Screenshots · Potato Mode


What is recoverage?

recoverage serves a local web dashboard that visualises per-byte match status across .text, .data, .bss, and other PE sections of a decompilation project. Think of it as a defrag map for your decomp — every byte of the original binary is a cell in a grid, colored by how closely your C code matches the original compiled output.

🚀 Features

  • Byte-Perfect Confidence: Stop guessing if your C code produced the correct assembly. See exact byte comparisons visually.
  • Fast Iteration: Quickly identify which parts of a function are matching and which parts have diverged (e.g. register allocation differences, instruction reordering).
  • Interactive Triage: Click any block in the grid to immediately view the corresponding C source, disassembled binary, and hex diff.

✨ Highlights

🧱 Defrag-style gridOne cell per chunk — Exact (green), Reloc (cyan), Matching (yellow), Stub (red), None (gray)
🔎 Function detail panelClick any cell to see metadata, C source, disassembly, and hex dump side-by-side
🌗 Light & dark themesRetro CRT dark mode by default, clean light mode one click away
🔗 Clickable cross-referencesHex addresses in disassembly are live links — click to jump to that chunk
📊 Interactive progress barSegmented by status; click a segment to filter the grid
🗜️ First draw in first TCP packetHTML + CSS + JS inlined & compressed (Brotli/Zstd) to ~14.5 KB
🥔 Potato ModeZero-JS server-rendered fallback for constrained environments
🔄 Live regenOne-click re-catalog + rebuild without restarting the server

Screenshots

Main Dashboard

Main dashboard — coverage grid with section tabs and filter buttons

Function Detail

Function detail panel showing metadata, C source, and disassembly

Dark Mode

Dark mode with function detail panel

🥔 Potato Mode

Potato Mode — retro pure-HTML table view

Potato Mode is a zero-JavaScript, server-side rendered HTML fallback. Every view is a plain HTML table — no CSS, no JS — so it works on low-spec machines, restricted browsers, or anywhere you just want a quick glance without loading the full SPA.


Installation

pip install recoverage

For development:

uv pip install -e .

Optional runtime extras

Install an extra to enable its feature: pip install 'recoverage[<extra>]' (or uv sync --extra <extra> in a workspace).

ExtraPackageWhat it does
capstonecapstoneEnables on-demand disassembly in the detail panel
pygmentspygmentsSyntax highlighting in Potato Mode
playwrightplaywright, pytest-playwrightBrowser integration tests (tests/test_playwright.py)

Quick Start

# 1. Generate the coverage database (from your project directory)
uv run rebrew catalog --json
# Analyzes the target binary, parses your annotations, and dumps raw match data to db/data_*.json
uv run rebrew build-db
# Consumes the JSON files and builds a fast SQLite database (db/coverage.db) for the dashboard# 2. Start the dashboard
uv run recoverage serve
# Starts a lightweight Bottle web server serving the frontend SPA and providing the API backend

Note

The server resolves coverage.db from the current working directory: [project] db_dir in rebrew-project.toml when set, falling back to db/coverage.db — so run it from your project root.


CLI Commands

recoverage serve

Start the dashboard web server.

FlagDefaultDescription
--port8001HTTP port to serve on
--bind127.0.0.1Interface to bind to (use 0.0.0.0 for LAN access)
--allow-remoteoffRequired with a non-loopback --bind: acknowledge the API is reachable on the network
--tokenoffRequire this token for every request (Authorization: Bearer, ?token=, or open /?token=<token> to set the SPA cookie)
--no-openoffDon't auto-open the browser
--regenoffRun rebrew catalog + rebrew build-db before starting
--corsoffEnable CORS processing (allowlisted origins only; the wildcard is never emitted)
--cors-originnoneOrigin URL allowed to read the API cross-origin (repeatable; without it --cors allows no cross-origin reads)

recoverage stats

Print per-section coverage stats as a Rich table, or as JSON with --json.

recoverage stats # all targets
recoverage stats --target SERVER # single target
recoverage stats --json # machine-readable

recoverage export

Export coverage data to stdout.

recoverage export --format json # JSON (default)
recoverage export --format csv # CSV
recoverage export --format md # Markdown table

recoverage check

CI gate — exits non-zero if coverage is below a threshold. Sections the grid never records matches for (e.g. .bss/.data when only .text matches are tracked) are skipped, not failed.

recoverage check --min-coverage 60 # all targets, all sections
recoverage check --min-coverage 60 --target SERVER --section .text # specific
recoverage check --min-coverage 60 --json # machine-readable verdict

Exit codes: 0 = gate passed, 1 = coverage below threshold (or bad input), 2 = infrastructure error (database missing/unreadable).

recoverage regen

Re-run rebrew catalog + rebrew build-db to regenerate coverage.db.

recoverage regen

recoverage open

Open the dashboard in a browser (useful when --no-open was used).

recoverage open --port 8001

API Endpoints

PathMethodDescription
/GETMain SPA dashboard
/potatoGETPotato Mode (pure-HTML fallback)
/api/healthGETServer version, DB info, installed extras
/api/targetsGETList available targets
/api/targets/<target>/statsGETPer-section coverage stats with percentages
/api/targets/<target>/dataGETSection + cell data (?section=.text for partial)
/api/targets/<target>/functionsGETPaginated list (?status=&search=&sort=&limit=&offset=)
/api/targets/<target>/functionsPOSTBatch lookup: {"vas": [...]} → function/global details in input order
/api/targets/<target>/functions/<va>GETSingle function/global detail
/api/targets/<target>/asmGETDisassembly (?format=json for structured output)
/api/targets/<target>/sections/<section>/bytesGETRaw byte slice (?offset=&size=)
/api/eventsGETServer-Sent Events: db-updated when coverage.db changes (SPA auto-refresh)
/api/regenPOSTRe-run catalog + build-db (localhost only, rate-limited)

Architecture & How it works

recoverage is designed as a standalone consumer of the data that rebrew produces — the two packages are intentionally decoupled.

rebrew catalog --json rebrew build-db recoverage (Bottle + SQLite)
│ │ │
db/data_*.json ──────────▶ db/coverage.db ──────────▶ VanJS Dashboard
  1. rebrew catalog --json: Scans your project's source annotations and writes intermediate db/data_*.json files containing coverage metrics. Jump table / switch data bytes are absorbed into their parent function's size. Use --export-ghidra-labels to generate ghidra_data_labels.json for round-trip Ghidra sync.
  2. rebrew build-db: Consumes those JSON files and builds a structured db/coverage.db (SQLite v4 schema) database, storing per-function metadata (detected_by, size_by_tool, textOffset), per-global metadata (module, size), per-cell metadata (label, parent_function), and stamping db_version for schema detection. See DB_FORMAT.md for the full schema.
  3. recoverage: Starts a Bottle web server. The backend serves API endpoints querying the SQLite database, while the frontend is a zero-build Single Page Application (SPA) powered by VanJS, rendering the interactive defrag grid.

You can run recoverage independently on any machine (or even host it remotely) as long as it has access to a compiled coverage.db — no rebrew dependency or compiler toolchain is required.


Project layout

recoverage/
├── pyproject.toml
├── README.md
├── docs/ # Screenshots, mascot & design doc
│ ├── DESIGN.md # Detailed architecture & design doc
│ ├── DESIGN_PRINCIPLES.md # Core operational philosophies
│ ├── USER_STORIES.md # User stories with acceptance criteria
│ └── ideas.md # Future improvement ideas
├── tests/
│ ├── conftest.py # Shared fixtures (synthetic coverage.db)
│ ├── test_api.py # API validation & security tests
│ ├── test_cli.py # CSV export, formatting tests
│ ├── test_lifecycle.py # Process lifecycle (regen timeouts, opener reaping)
│ ├── test_paths.py # DB path resolution tests
│ ├── test_server.py # Compression, encoding tests
│ ├── test_potato.py # Potato Mode rendering tests
│ └── test_playwright.py # Browser integration tests
└── src/recoverage/
├── __init__.py
├── __main__.py # python -m recoverage
├── _paths.py # DB path resolution (rebrew-project.toml db_dir)
├── cli.py # Typer CLI entry point
├── server.py # Bottle app, shared helpers & compression
├── regen.py # rebrew regen subprocess lifecycle (group kill + reap)
├── api.py # REST API routes (/api/*)
├── ui.py # UI routes (/, /potato, static files)
├── potato.py # Potato Mode renderer
├── webapp.py # Composition root: imports api+ui so app has every route
└── assets/
├── index.html # SPA shell
├── style.css # All styles
├── print.css # Print stylesheet
├── app.js # VanJS frontend
├── detail.js # Deferred panel logic (hex dump, modal, live reload)
├── van.min.js # VanJS library (~2 KB)
├── favicon.svg # Retro "R" logo favicon
├── hljs.min.js # Highlight.js core
├── hljs-c.min.js # Highlight.js C grammar
├── hljs-x86asm.min.js # Highlight.js x86 asm grammar (hex lang is in detail.js)
└── hljs.css # Highlight.js theme

Vendored third-party assets

The browser libraries under src/recoverage/assets/ are vendored so the dashboard works air-gapped (see docs/DESIGN.md); nothing is fetched from a CDN at runtime. Licenses and versions are recorded here because the minified blobs themselves carry little provenance:

FileUpstreamVersionLicense
van.min.jsVanJS core, classic-script build (window.van)not embedded in the blobMIT (upstream license)
hljs.min.jsHighlight.js core11.11.1 (in-file banner)BSD-3-Clause
hljs-c.min.jsHighlight.js c grammarcompiled for 11.11.1BSD-3-Clause
hljs-x86asm.min.jsHighlight.js x86asm grammarcompiled for 11.11.1BSD-3-Clause

hljs.css is a first-party theme (not upstream Highlight.js CSS). When re-vendoring any of these files, keep the upstream license banner in the minified output so this table stays verifiable against the blobs.


License

MIT

About

Coverage dashboard for binary-matching decompilation projects

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages