1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
use crate::disassembler::{Disassembler, DisassemblyOutput, UnknownHandling};
use crate::error::Result;
use crate::manifest::ContractManifest;
use crate::nef::NefParser;
use super::decompilation::Decompilation;
use super::output_format::OutputFormat;
mod bytes;
mod io;
/// Main entry point used by the CLI and tests.
#[derive(Debug, Default)]
pub struct Decompiler {
parser: NefParser,
disassembler: Disassembler,
inline_single_use_temps: bool,
emit_trace_comments: bool,
typed_declarations: bool,
}
impl Decompiler {
/// Create a new decompiler that permits unknown opcodes during disassembly.
///
/// This is equivalent to `Decompiler::with_unknown_handling(UnknownHandling::Permit)`.
#[must_use]
pub fn new() -> Self {
Self::with_unknown_handling(UnknownHandling::Permit)
}
/// Create a new decompiler configured with the desired unknown-opcode policy.
///
/// Unknown opcodes can appear when disassembling corrupted inputs or when
/// targeting a newer VM revision. Use [`crate::UnknownHandling::Error`] to
/// fail fast, or [`crate::UnknownHandling::Permit`] to emit `Unknown`
/// instructions and continue.
///
/// # Examples
/// ```
/// use neo_decompiler::{Decompiler, UnknownHandling};
///
/// let decompiler = Decompiler::with_unknown_handling(UnknownHandling::Error);
/// let _ = decompiler;
/// ```
#[must_use]
pub fn with_unknown_handling(handling: UnknownHandling) -> Self {
Self {
parser: NefParser::new(),
disassembler: Disassembler::with_unknown_handling(handling),
inline_single_use_temps: false,
// Emit `// XXXX: OPCODE` trace comments next to each lifted
// statement. Defaults to true to preserve existing rendering
// (golden tests assert their presence). Disable via
// `with_trace_comments(false)` for clean human-readable output.
emit_trace_comments: true,
// Annotate lifted signatures and slot declarations with inferred
// types. Defaults to false to preserve historical output; enable
// for richer, more strongly-typed pseudo/C# output.
typed_declarations: false,
}
}
/// Enable experimental inlining of single-use temporary variables in the high-level view.
///
/// This can reduce noise in lifted code by replacing temps like `t0` with their RHS
/// at the first use site, but it may reduce readability for larger expressions.
#[must_use]
pub fn with_inline_single_use_temps(mut self, enabled: bool) -> Self {
self.inline_single_use_temps = enabled;
self
}
/// Toggle inline `// XXXX: OPCODE` trace comments in the high-level view.
///
/// Defaults to `true` (preserved historical behaviour). Set to `false` for
/// clean output suitable for end users — the lifted statements alone, with
/// no per-instruction trace markers. Untranslated instructions still emit
/// their `// not yet translated` notes regardless of this setting.
#[must_use]
pub fn with_trace_comments(mut self, enabled: bool) -> Self {
self.emit_trace_comments = enabled;
self
}
/// Toggle inferred-type annotations on generated signatures and slot declarations.
///
/// When enabled, renderers emit concrete inferred types for arguments,
/// locals, and static fields (`fn script_entry(arg0: int)` and `int loc0 =
/// ...;` in high-level output, `BigInteger arg0` / `BigInteger loc0 = ...;`
/// in C#). Slots with no inferable type still fall back to the historical
/// declaration form. Defaults to `false`; off has no effect, so existing
/// tests stay byte-identical.
#[must_use]
pub fn with_typed_declarations(mut self, enabled: bool) -> Self {
self.typed_declarations = enabled;
self
}
/// Decompile a NEF blob already loaded in memory.
///
/// # Errors
///
/// Returns an error if the NEF container is malformed or disassembly fails.
pub fn decompile_bytes(&self, bytes: &[u8]) -> Result<Decompilation> {
self.decompile_bytes_with_manifest(bytes, None, OutputFormat::All)
}
/// Disassemble a NEF blob already loaded in memory.
///
/// This fast path parses the NEF container and decodes instructions only;
/// it skips CFG construction, analysis passes, and renderers.
///
/// # Errors
///
/// Returns an error if the NEF container is malformed or disassembly fails.
pub fn disassemble_bytes(&self, bytes: &[u8]) -> Result<DisassemblyOutput> {
self.disassemble_bytes_inner(bytes)
}
/// Decompile a NEF blob using an optional manifest.
///
/// # Errors
///
/// Returns an error if the NEF container is malformed or disassembly fails.
pub fn decompile_bytes_with_manifest(
&self,
bytes: &[u8],
manifest: Option<ContractManifest>,
output_format: OutputFormat,
) -> Result<Decompilation> {
self.decompile_bytes_with_manifest_inner(bytes, manifest, output_format)
}
/// Decompile a NEF file from disk.
///
/// # Errors
///
/// Returns an error if the file cannot be read, the NEF container is
/// malformed, or disassembly fails.
pub fn decompile_file<P: AsRef<std::path::Path>>(&self, path: P) -> Result<Decompilation> {
self.io_decompile_file(path)
}
/// Disassemble a NEF file from disk.
///
/// # Errors
///
/// Returns an error if the file cannot be read, the NEF container is
/// malformed, or disassembly fails.
pub fn disassemble_file<P: AsRef<std::path::Path>>(
&self,
path: P,
) -> Result<DisassemblyOutput> {
self.io_disassemble_file(path)
}
/// Decompile a NEF file alongside an optional manifest file.
///
/// # Errors
///
/// Returns an error if either file cannot be read, the NEF container is
/// malformed, the manifest JSON is invalid, or disassembly fails.
pub fn decompile_file_with_manifest<P, Q>(
&self,
nef_path: P,
manifest_path: Option<Q>,
output_format: OutputFormat,
) -> Result<Decompilation>
where
P: AsRef<std::path::Path>,
Q: AsRef<std::path::Path>,
{
self.io_decompile_file_with_manifest(nef_path, manifest_path, output_format)
}
}