neo-decompiler 0.7.0

Neo N3 NEF decompiler: parse, disassemble, lift bytecode to high-level pseudocode and C# skeletons, with a CLI, JSON reports, and optional WebAssembly bindings.
Documentation
import {
  decompileReport,
  disasmReport,
  infoReport,
  init,
  initPanicHook,
} from "./dist/index.js";

const controls = document.querySelector("#controls");
const nefFileInput = document.querySelector("#nef-file");
const manifestFileInput = document.querySelector("#manifest-file");
const outputFormatInput = document.querySelector("#output-format");
const strictManifestInput = document.querySelector("#strict-manifest");
const failUnknownInput = document.querySelector("#fail-unknown");
const inlineTempsInput = document.querySelector("#inline-temps");
const runButton = document.querySelector("#run");
const status = document.querySelector("#status");
const infoSummary = document.querySelector("#info-summary");
const disasmSummary = document.querySelector("#disasm-summary");
const decompileSummary = document.querySelector("#decompile-summary");
const infoOutput = document.querySelector("#info-output");
const disasmOutput = document.querySelector("#disasm-output");
const decompileOutput = document.querySelector("#decompile-output");

let wasmReady = false;

boot();

async function boot() {
  try {
    await init();
    initPanicHook();
    wasmReady = true;
    status.textContent = "Wasm package loaded. Choose a .nef file to begin.";
  } catch (error) {
    wasmReady = false;
    status.innerHTML =
      "Could not load <code>./pkg/neo_decompiler.js</code>. Run <code>npm run build:wasm</code> first.";
    console.error(error);
  }
}

controls.addEventListener("submit", async (event) => {
  event.preventDefault();
  if (!wasmReady) {
    status.innerHTML =
      "The wasm package is not ready yet. Build it with <code>npm run build:wasm</code>.";
    return;
  }

  const nefFile = nefFileInput.files?.[0];
  if (!nefFile) {
    status.textContent = "Choose a .nef file first.";
    return;
  }

  runButton.disabled = true;
  status.textContent = "Loading files and running the Rust decompiler...";

  try {
    const nefBytes = new Uint8Array(await nefFile.arrayBuffer());
    const manifestJson = await readOptionalText(manifestFileInput.files?.[0]);

    const info = infoReport(nefBytes, {
      manifestJson,
      strictManifest: strictManifestInput.checked,
    });
    const disasm = disasmReport(nefBytes, {
      failOnUnknownOpcodes: failUnknownInput.checked,
    });
    const decompile = decompileReport(nefBytes, {
      manifestJson,
      strictManifest: strictManifestInput.checked,
      failOnUnknownOpcodes: failUnknownInput.checked,
      inlineSingleUseTemps: inlineTempsInput.checked,
      outputFormat: outputFormatInput.value,
    });

    infoSummary.textContent = `${info.script_hash_be}  ${info.script_length} bytes`;
    disasmSummary.textContent = `${disasm.instructions.length} instructions  ${disasm.warnings.length} warnings`;
    decompileSummary.textContent =
      `${decompile.analysis.call_graph.methods.length} methods  ${decompile.warnings.length} warnings`;

    infoOutput.textContent = JSON.stringify(info, null, 2);
    disasmOutput.textContent = JSON.stringify(disasm, null, 2);

    const rendered = [
      decompile.high_level && ["// High-level", decompile.high_level],
      decompile.pseudocode && ["// Pseudocode", decompile.pseudocode],
      decompile.csharp && ["// C#", decompile.csharp],
      decompile.warnings.length
        ? ["// Warnings", decompile.warnings.map((warning) => `- ${warning}`).join("\n")]
        : null,
    ]
      .filter(Boolean)
      .map(([title, body]) => `${title}\n${body}`)
      .join("\n\n");

    decompileOutput.textContent = rendered || JSON.stringify(decompile, null, 2);
    status.textContent = "Analysis complete.";
  } catch (error) {
    console.error(error);
    status.textContent = `Analysis failed: ${String(error)}`;
    decompileOutput.textContent = String(error);
  } finally {
    runButton.disabled = false;
  }
});

async function readOptionalText(file) {
  if (!file) {
    return undefined;
  }
  return await file.text();
}