cardinal_uxn/disassembler.rs
1//! Uxn disassembler
2// (
3// https://github.com/Liorst4/uxn-disassembler/tree/main
4// Copyright © 2025 David Horner
5// Copyright © 2022 Lior Stern
6
7// Permission is hereby granted, free of charge, to any person obtaining
8// a copy of this software and associated documentation files (the
9// “Software”), to deal in the Software without restriction, including
10// without limitation the rights to use, copy, modify, merge, publish,
11// distribute, sublicense, and/or sell copies of the Software, and to
12// permit persons to whom the Software is furnished to do so, subject to
13// the following conditions:
14
15// The above copyright notice and this permission notice shall be
16// included in all copies or substantial portions of the Software.
17
18// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
19// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25// )
26
27const SHORT_MODE_MASK: u8 = 0x20;
28const RETURN_MODE_MASK: u8 = 0x40;
29const KEEP_MODE_MASK: u8 = 0x80;
30const OPCODE_MASK: u8 = 0x1F;
31
32const OPCODE_NAMES: [&str; 32] = [
33 "LIT", "INC", "POP", "NIP", "SWP", "ROT", "DUP", "OVR", "EQU", "NEQ", "GTH", "LTH", "JMP",
34 "JCN", "JSR", "STH", "LDZ", "STZ", "LDR", "STR", "LDA", "STA", "DEI", "DEO", "ADD", "SUB",
35 "MUL", "DIV", "AND", "ORA", "EOR", "SFT",
36];
37
38/// A disassembled Uxn instruction
39pub struct DisassembledInstr {
40 /// Address of the instruction
41 pub addr: usize,
42 /// Opcode byte
43 pub opcode: u8,
44 /// Mnemonic string
45 pub mnemonic: &'static str,
46 /// Keep flag
47 pub keep: bool,
48 /// Return flag
49 pub ret: bool,
50 /// Short flag
51 pub short: bool,
52 /// Optional literal value
53 pub literal: Option<u16>,
54 /// Raw bytes of the instruction (max 4)
55 pub raw_bytes: [u8; 4],
56 /// Length of the instruction in bytes
57 pub raw_len: usize,
58}
59
60/// Disassemble a Uxn ROM, calling `callback` for each instruction
61pub fn disassemble<F>(rom: &[u8], _disassemble_to_byte: usize, mut callback: F)
62where
63 F: FnMut(DisassembledInstr),
64{
65 let mut i = 0;
66 while i < rom.len() {
67 let instr = rom[i];
68 let opcode = instr & OPCODE_MASK;
69 let keep = (instr & KEEP_MODE_MASK) != 0;
70 let ret = (instr & RETURN_MODE_MASK) != 0;
71 let short = (instr & SHORT_MODE_MASK) != 0;
72 let addr = i + 0x100;
73
74 if opcode == 0x00 {
75 // LIT instruction
76 if short {
77 if i + 2 >= rom.len() {
78 let mut raw = [0u8; 4];
79 let len = rom.len() - i;
80 raw[..len].copy_from_slice(&rom[i..(len + i)]);
81 callback(DisassembledInstr {
82 addr,
83 opcode,
84 mnemonic: "LIT2",
85 keep,
86 ret,
87 short,
88 literal: None,
89 raw_bytes: raw,
90 raw_len: len,
91 });
92 break;
93 }
94 let value = u16::from_be_bytes([rom[i + 1], rom[i + 2]]);
95 callback(DisassembledInstr {
96 addr,
97 opcode,
98 mnemonic: "LIT2",
99 keep,
100 ret,
101 short,
102 literal: Some(value),
103 raw_bytes: [instr, rom[i + 1], rom[i + 2], 0],
104 raw_len: 3,
105 });
106 i += 3;
107 } else {
108 if i + 1 >= rom.len() {
109 let mut raw = [0u8; 4];
110 let len = rom.len() - i;
111 raw[..len].copy_from_slice(&rom[i..(len + i)]);
112 callback(DisassembledInstr {
113 addr,
114 opcode,
115 mnemonic: "LIT",
116 keep,
117 ret,
118 short,
119 literal: None,
120 raw_bytes: raw,
121 raw_len: len,
122 });
123 break;
124 }
125 callback(DisassembledInstr {
126 addr,
127 opcode,
128 mnemonic: "LIT",
129 keep,
130 ret,
131 short,
132 literal: Some(rom[i + 1] as u16),
133 raw_bytes: [instr, rom[i + 1], 0, 0],
134 raw_len: 2,
135 });
136 i += 2;
137 }
138 } else {
139 callback(DisassembledInstr {
140 addr,
141 opcode,
142 mnemonic: OPCODE_NAMES[opcode as usize],
143 keep,
144 ret,
145 short,
146 literal: None,
147 raw_bytes: [instr, 0, 0, 0],
148 raw_len: 1,
149 });
150 i += 1;
151 }
152 }
153}
154
155#[allow(dead_code)]
156fn write_literal_prefix(buf: &mut [u8], short: bool, _keep: bool, ret: bool) -> usize {
157 let mut idx = 0;
158 if !ret {
159 buf[idx] = b'#';
160 idx += 1;
161 } else {
162 let lit = b"LIT";
163 buf[idx..idx + 3].copy_from_slice(lit);
164 idx += 3;
165 if short {
166 buf[idx] = b'2';
167 idx += 1;
168 }
169 buf[idx] = b'r';
170 idx += 1;
171 buf[idx] = b' ';
172 idx += 1;
173 }
174 idx
175}
176
177#[allow(dead_code)]
178fn write_literal_postfix(buf: &mut [u8], short: bool, ret: bool) -> usize {
179 let mut idx = 0;
180 if short && ret {
181 buf[idx] = b'\t';
182 idx += 1;
183 }
184 idx
185}