neo-decompiler 0.6.2

Minimal tooling for inspecting Neo N3 NEF bytecode
Documentation
import { makeUniqueIdentifier, sanitizeIdentifier } from "./manifest.js";
import { jumpTarget } from "./high-level-utils.js";

export function buildMethodGroups(instructions, manifest) {
  const entryOffset = instructions[0]?.offset ?? 0;
  const used = new Set();
  const methods = manifest?.abi?.methods ?? [];
  const manifestOffsets = methods
    .map((method, index) => ({ index, offset: method.offset }))
    .filter((entry) => Number.isInteger(entry.offset) && entry.offset >= 0)
    .sort((left, right) => left.offset - right.offset);
  const knownOffsets = new Set(instructions.map((instruction) => instruction.offset));
  const starts = new Set([entryOffset]);

  for (const { offset } of manifestOffsets) {
    starts.add(offset);
  }
  for (const instruction of instructions) {
    if (instruction.opcode.mnemonic === "INITSLOT") {
      starts.add(instruction.offset);
    }
    if (instruction.opcode.mnemonic === "CALL" || instruction.opcode.mnemonic === "CALL_L") {
      const target = jumpTarget(instruction);
      if (target !== null && knownOffsets.has(target)) {
        starts.add(target);
      }
    }
  }

  const groups = [];
  const offsets = [...starts].sort((left, right) => left - right);
  const manifestOffsetMap = new Map(manifestOffsets.map(({ offset, index }) => [offset, index]));
  const allManifestOffsetsMissing = manifestOffsets.length === 0;

  for (const offset of offsets) {
    if (offset === entryOffset && allManifestOffsetsMissing && methods[0]) {
      groups.push({
        start: offset,
        name: makeUniqueIdentifier(sanitizeIdentifier(methods[0].name), used),
        source: methods[0],
      });
      continue;
    }

    const manifestIndex = manifestOffsetMap.get(offset);
    if (manifestIndex !== undefined) {
      groups.push({
        start: offset,
        name: makeUniqueIdentifier(sanitizeIdentifier(methods[manifestIndex].name), used),
        source: methods[manifestIndex],
      });
      continue;
    }

    groups.push({
      start: offset,
      name: makeUniqueIdentifier(
        offset === entryOffset ? "script_entry" : `sub_0x${offset.toString(16).padStart(4, "0")}`,
        used,
      ),
      source: null,
    });
  }

  groups.sort((left, right) => left.start - right.start);

  return groups.map((group, index) => ({
    ...group,
    end: groups[index + 1]?.start ?? (instructions.at(-1)?.offset ?? group.start) + 1,
    instructions: instructions.filter(
      (instruction) =>
        instruction.offset >= group.start &&
        instruction.offset < (groups[index + 1]?.start ?? Number.POSITIVE_INFINITY),
    ),
  }));
}