neo-decompiler 0.10.1

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
export function joinType(a, b) {
  if (a === b) return a;
  if (a === "unknown") return b;
  if (b === "unknown") return a;
  return "any";
}

export function directCallTarget(instruction) {
  const operand = instruction.operand;
  if (operand?.kind === "Jump" || operand?.kind === "Jump32") {
    return instruction.offset + operand.value;
  }
  return null;
}

export function pushaTarget(instruction) {
  const operand = instruction.operand;
  if (operand?.kind === "I32") {
    const target = instruction.offset + operand.value;
    return target >= 0 ? target : null;
  }
  return null;
}

export function constrainOrigins(origins, expected, locals, args, statics) {
  for (const origin of origins ?? []) {
    const slots =
      origin.kind === "local"
        ? locals
        : origin.kind === "argument"
          ? args
          : origin.kind === "static"
            ? statics
            : null;
    if (!slots || origin.index < 0 || origin.index >= slots.length) continue;
    slots[origin.index] = joinType(slots[origin.index], expected);
  }
}

export function reverseTop(stack, count) {
  if (count === 0 || stack.length < count) return;
  const start = stack.length - count;
  const slice = stack.slice(start).reverse();
  for (let i = 0; i < count; i += 1) stack[start + i] = slice[i];
}

export function replaceWithUnknowns(stack) {
  for (let index = 0; index < stack.length; index += 1) {
    stack[index] = { ty: "unknown", lit: null, ptr: null, origins: [] };
  }
}

export function intLiteral(operand) {
  if (!operand) return null;
  switch (operand.kind) {
    case "I8":
    case "I16":
    case "I32":
    case "I64":
    case "U8":
    case "U16":
    case "U32": {
      const v = operand.value;
      return typeof v === "bigint" ? v : Number(v);
    }
    default:
      return null;
  }
}

export function asCount(lit) {
  if (lit === null || lit === undefined) return null;
  if (typeof lit === "bigint") {
    if (lit < 0n) return null;
    return lit > BigInt(Number.MAX_SAFE_INTEGER) ? Number.MAX_SAFE_INTEGER : Number(lit);
  }
  if (!Number.isInteger(lit) || lit < 0) return null;
  return lit;
}

export function manifestType(kind) {
  const normalized = String(kind).toLowerCase();
  if (normalized === "any") return "any";
  if (normalized === "boolean") return "bool";
  if (normalized === "integer") return "integer";
  if (
    normalized === "string" ||
    normalized === "bytearray" ||
    normalized === "signature" ||
    normalized === "hash160" ||
    normalized === "hash256"
  ) {
    return "bytestring";
  }
  if (normalized === "array") return "array";
  if (normalized === "map") return "map";
  if (normalized === "interopinterface") return "interopinterface";
  return "unknown";
}

const CONVERT_TARGET_MAP = new Map([
  [0x00, "any"],
  [0x10, "pointer"],
  [0x20, "bool"],
  [0x21, "integer"],
  [0x28, "bytestring"],
  [0x30, "buffer"],
  [0x40, "array"],
  [0x41, "struct"],
  [0x48, "map"],
  [0x60, "interopinterface"],
]);

export function convertTargetType(operand) {
  if (!operand || (operand.kind !== "U8" && operand.kind !== "I8")) {
    return null;
  }
  const byte = operand.kind === "U8" ? operand.value : operand.value & 0xff;
  return CONVERT_TARGET_MAP.get(byte) ?? null;
}