Skip to main content

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}