neo-decompiler 0.10.2

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
use crate::{Error, OutputFormat};
use serde::de::DeserializeOwned;
use serde::Deserialize;
use wasm_bindgen::prelude::*;

use super::api::{decompile_report, disasm_report, info_report_with_manifest_options};
use super::options::{WebDecompileOptions, WebDisasmOptions};

#[derive(Debug, Default, Deserialize)]
#[serde(default)]
struct JsInfoOptions {
    manifest_json: Option<String>,
    strict_manifest: bool,
}

#[derive(Debug, Default, Deserialize)]
#[serde(default)]
struct JsDisasmOptions {
    fail_on_unknown_opcodes: bool,
}

#[derive(Debug, Deserialize)]
#[serde(default)]
struct JsDecompileOptions {
    manifest_json: Option<String>,
    strict_manifest: bool,
    fail_on_unknown_opcodes: bool,
    inline_single_use_temps: bool,
    emit_trace_comments: bool,
    typed_declarations: bool,
    output_format: Option<String>,
}

impl Default for JsDecompileOptions {
    fn default() -> Self {
        let defaults = WebDecompileOptions::default();
        Self {
            manifest_json: None,
            strict_manifest: false,
            fail_on_unknown_opcodes: false,
            inline_single_use_temps: defaults.inline_single_use_temps,
            emit_trace_comments: defaults.emit_trace_comments,
            typed_declarations: defaults.typed_declarations,
            output_format: None,
        }
    }
}

#[wasm_bindgen(js_name = initPanicHook)]
/// Install a panic hook that forwards Rust panics to the browser console.
pub fn init_panic_hook() {
    console_error_panic_hook::set_once();
}

#[wasm_bindgen(js_name = infoReport)]
/// Build an info report from NEF bytes and a JS options object.
pub fn info_report_wasm(
    nef_bytes: &[u8],
    options: JsValue,
) -> std::result::Result<JsValue, JsValue> {
    let options: JsInfoOptions = parse_js_options(options)?;
    let report = info_report_with_manifest_options(
        nef_bytes,
        options.manifest_json.as_deref(),
        options.strict_manifest,
    )
    .map_err(to_js_error)?;
    report_to_js(&report)
}

#[wasm_bindgen(js_name = disasmReport)]
/// Build a disassembly report from NEF bytes and a JS options object.
pub fn disasm_report_wasm(
    nef_bytes: &[u8],
    options: JsValue,
) -> std::result::Result<JsValue, JsValue> {
    let options: JsDisasmOptions = parse_js_options(options)?;
    let report = disasm_report(
        nef_bytes,
        WebDisasmOptions {
            fail_on_unknown_opcodes: options.fail_on_unknown_opcodes,
        },
    )
    .map_err(to_js_error)?;
    report_to_js(&report)
}

#[wasm_bindgen(js_name = decompileReport)]
/// Build a decompilation report from NEF bytes and a JS options object.
pub fn decompile_report_wasm(
    nef_bytes: &[u8],
    options: JsValue,
) -> std::result::Result<JsValue, JsValue> {
    let options: JsDecompileOptions = parse_js_options(options)?;
    let output_format = parse_output_format(options.output_format.as_deref())
        .map_err(|err| JsValue::from_str(&err))?;
    let report = decompile_report(
        nef_bytes,
        WebDecompileOptions {
            manifest_json: options.manifest_json,
            strict_manifest: options.strict_manifest,
            fail_on_unknown_opcodes: options.fail_on_unknown_opcodes,
            inline_single_use_temps: options.inline_single_use_temps,
            emit_trace_comments: options.emit_trace_comments,
            typed_declarations: options.typed_declarations,
            output_format,
        },
    )
    .map_err(to_js_error)?;
    report_to_js(&report)
}

/// Serialize a report to a `JsValue` with BigInt enabled for 64/128-bit
/// integers. The default serde-wasm-bindgen serializer throws on any i64/u64
/// outside the JS safe-integer range, which aborts the whole report for
/// otherwise-valid input. The CLI serializes via serde_json and already handles
/// the full range; this keeps the web boundary at parity.
fn report_to_js<T: serde::Serialize>(value: &T) -> std::result::Result<JsValue, JsValue> {
    let serializer =
        serde_wasm_bindgen::Serializer::new().serialize_large_number_types_as_bigints(true);
    serde::Serialize::serialize(value, &serializer)
        .map_err(|err| JsValue::from_str(&err.to_string()))
}

fn parse_js_options<T>(value: JsValue) -> std::result::Result<T, JsValue>
where
    T: Default + DeserializeOwned,
{
    if value.is_null() || value.is_undefined() {
        Ok(T::default())
    } else {
        serde_wasm_bindgen::from_value(value)
            .map_err(|err| JsValue::from_str(&format!("invalid options: {err}")))
    }
}

fn parse_output_format(value: Option<&str>) -> std::result::Result<OutputFormat, String> {
    match value.unwrap_or("all") {
        "all" => Ok(OutputFormat::All),
        "pseudocode" => Ok(OutputFormat::Pseudocode),
        "high_level" | "highLevel" => Ok(OutputFormat::HighLevel),
        "csharp" | "c_sharp" => Ok(OutputFormat::CSharp),
        other => Err(format!("invalid output_format: {other}")),
    }
}

fn to_js_error(err: Error) -> JsValue {
    JsValue::from_str(&err.to_string())
}