gfxd_rs/disassembler.rs
1/* SPDX-FileCopyrightText: © 2025 Decompollaborate */
2/* SPDX-License-Identifier: MIT OR Apache-2.0 */
3
4use alloc::{borrow::ToOwned as _, string::String};
5use gfxd_sys::{ffi, ptr::NonNullConst};
6
7use crate::{
8 lib_data::{LibData, LibDataWrap},
9 Customizer, Microcode,
10};
11
12// TODO: figure out where and how to expose gfxd_arg_callbacks
13
14/// `Gfx` packet macro disassembler.
15///
16/// This struct allows configuring general settings that control the generated
17/// output. Once the disassembler is configured, call [`disassemble`] to
18/// produce a string with the disassembly of the passed Gfx packets.
19///
20/// Refer to the [`Customizer`] struct to further customize how each `Gfx`
21/// macro is outputted or register callbacks for the different kinds of macros.
22///
23/// ## Examples
24///
25/// Disassemble F3DEX packets without any kind of customization
26///
27/// ```rust
28/// use gfxd_rs::{Customizer, Disassembler, Microcode};
29///
30/// pub fn plain_disasm_f3dex(data: &[u8]) -> String {
31/// let mut customizer = Customizer::new();
32///
33/// Disassembler::new()
34/// .disassemble(data, Microcode::F3dex, &mut customizer)
35/// }
36/// ```
37///
38/// Use a dynamic argument and print each macro on a different line.
39///
40/// ```rust
41/// use gfxd_rs::{Customizer, Disassembler, MacroPrinter, Microcode};
42///
43/// pub fn disasm_pretty(data: &[u8], microcode: Microcode, dynamic: &str) -> String {
44/// let mut customizer = Customizer::new();
45///
46/// // This has to be binded to a local variable to avoid dropping it too soon.
47/// let mut macro_fn = |printer: &mut MacroPrinter, _info: &mut _| {
48/// // Write 4 spaces.
49/// printer.write_str(" ");
50/// // Call the original macro handler, to emit the macro as-is.
51/// let ret = printer.macro_dflt();
52/// // Write a newline after the macro.
53/// printer.write_str(",\n");
54/// ret
55/// };
56/// customizer
57/// .macro_fn(&mut macro_fn);
58///
59/// Disassembler::new()
60/// .dynamic(Some(dynamic))
61/// .disassemble(data, microcode, &mut customizer)
62/// }
63/// ```
64///
65/// [`disassemble`]: Disassembler::disassemble
66#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
67pub struct Disassembler<'d> {
68 // TODO: endian and wordsize
69 dynamic: Option<&'d str>,
70 stop_on_invalid: bool,
71 stop_on_end: bool,
72 emit_dec_color: bool,
73 emit_q_macro: bool,
74 emit_ext_macro: bool,
75}
76
77impl<'d> Disassembler<'d> {
78 /// Construct a `Disassembler` instance.
79 ///
80 /// Consult the `Disassembler` struct documentation for more information.
81 #[must_use]
82 pub const fn new() -> Self {
83 Self {
84 dynamic: None,
85 stop_on_invalid: true,
86 stop_on_end: true,
87 emit_dec_color: false,
88 emit_q_macro: false,
89 emit_ext_macro: false,
90 }
91 }
92
93 /// Enable or disable the use of dynamic `g` macros instead of static `gs`
94 /// macros, and select the dynamic display list pointer argument to be used.
95 ///
96 /// If `Some`, this value will be used by [`macro_dflt`] as the first
97 /// argument to dynamic macros.
98 /// If `None`, dynamic macros are disabled and `gs` macros are used.
99 /// Defaults to `None`.
100 ///
101 /// Also affects the result of [`macro_name`], as it will return either the
102 /// dynamic or static version of the macro name as selected by this
103 /// setting.
104 ///
105 /// [`macro_dflt`]: crate::MacroPrinter::macro_dflt
106 /// [`macro_name`]: crate::MacroInfo::macro_name
107 pub fn dynamic(&mut self, dynamic: Option<&'d str>) -> &mut Self {
108 self.dynamic = dynamic;
109 self
110 }
111
112 /// Stop execution when encountering an invalid macro.
113 ///
114 /// Enabled by default.
115 pub fn stop_on_invalid(&mut self, value: bool) -> &mut Self {
116 self.stop_on_invalid = value;
117 self
118 }
119 /// Stop execution when encountering a [`SPBranchList`] or
120 /// [`SPEndDisplayList`].
121 ///
122 /// Enabled by default.
123 ///
124 /// [`SPBranchList`]: crate::MacroId::SPBranchList
125 /// [`SPEndDisplayList`]: crate::MacroId::SPEndDisplayList
126 pub fn stop_on_end(&mut self, value: bool) -> &mut Self {
127 self.stop_on_end = value;
128 self
129 }
130 /// Print color components as decimal instead of hexadecimal.
131 ///
132 /// Disabled by default.
133 pub fn emit_dec_color(&mut self, value: bool) -> &mut Self {
134 self.emit_dec_color = value;
135 self
136 }
137 /// Print fixed-point conversion `q` macros for fixed-point values.
138 ///
139 /// Disabled by default.
140 pub fn emit_q_macro(&mut self, value: bool) -> &mut Self {
141 self.emit_q_macro = value;
142 self
143 }
144 /// Emit non-standard macros.
145 ///
146 /// Some commands are valid (though possibly meaningless), but have no
147 /// macros associated with them, such as a standalone `G_RDPHALF_1`.
148 /// When this feature is enabled, such a command will produce a
149 /// non-standard [`gsDPHalf1`] macro instead of a raw hexadecimal command.
150 ///
151 /// Also enables some non-standard multi-packet texture loading macros.
152 ///
153 /// Disabled by default.
154 ///
155 /// [`gsDPHalf1`]: crate::MacroId::DPHalf1
156 pub fn emit_ext_macro(&mut self, value: bool) -> &mut Self {
157 self.emit_ext_macro = value;
158 self
159 }
160
161 /// Start executing `gfxd` with the current settings.
162 ///
163 /// The `data` argument is a big endian byte array containing the `Gfx`
164 /// packets to be disassembled.
165 ///
166 /// `microcode` corresponds to the target microcode to decode the data.
167 ///
168 /// `customizer` allows registering callbacks to customize the output or to
169 /// extract data from each macro type.
170 ///
171 /// For each macro, the macro handler registered with [`macro_fn`] is
172 /// called.
173 ///
174 /// Execution ends when:
175 /// - the input ends,
176 /// - the macro handler returns [`Stop`],
177 /// - when an invalid macro is encountered and [`stop_on_invalid`] is
178 /// enabled,
179 /// - or when [`SPBranchList`] or [`SPEndDisplayList`] is encountered and
180 /// [`stop_on_end`] is enabled.
181 ///
182 /// [`macro_fn`]: Customizer::macro_fn
183 /// [`Stop`]: crate::MacroFnRet::Stop
184 /// [`stop_on_invalid`]: Disassembler::stop_on_invalid
185 /// [`stop_on_end`]: Disassembler::stop_on_end
186 /// [`SPBranchList`]: crate::MacroId::SPBranchList
187 /// [`SPEndDisplayList`]: crate::MacroId::SPEndDisplayList
188 #[must_use]
189 pub fn disassemble(
190 self,
191 data: &[u8],
192 microcode: Microcode,
193 customizer: &mut Customizer,
194 ) -> String {
195 let mut lib_data = LibData::new(customizer);
196 // Make sure we don't get surprises by the data somehow moving.
197 // tbh I'm not sure if this is needed at all, I need to investigate.
198 // let lib_data = alloc::boxed::Box::pin(lib_data);
199
200 // We need to ensure this String doesn't get dropped before we execute
201 // gfxd. Allocating it outside disassemble_impl and passing the
202 // reference should ensure that, as far as I understand it.
203 let dynamic = self.dynamic.map(|x| {
204 let mut x = x.to_owned();
205 // nul-terminate the string
206 x.push('\0');
207 x
208 });
209 // `as_ref` is needed, otherwise the string gets consumed and this ends
210 // up pointing to itself or something weird like that.
211 let dynamic_ptr = dynamic
212 .as_ref()
213 .and_then(|x| NonNullConst::new(x.as_ptr().cast()));
214
215 {
216 let mut lib_data_wrap = lib_data.gfxd_set();
217
218 self.disassemble_impl(data, microcode, &mut lib_data_wrap, dynamic_ptr);
219 }
220
221 lib_data.consume()
222 }
223
224 // Use a wrapper function to make sure lib_data does not get dropped too
225 // soon.
226 fn disassemble_impl(
227 self,
228 data: &[u8],
229 microcode: Microcode,
230 lib_data_wrap: &mut LibDataWrap,
231 dynamic: Option<NonNullConst<ffi::c_char>>,
232 ) {
233 #[allow(clippy::cast_possible_truncation)]
234 let data_len = data.len() as _;
235 // Setup input and output
236 unsafe {
237 // We only use the input_buffer and the output_callback functions
238 // of libgfxd, completely ignoring the output_buffer, input_callback
239 // or the fd ones.
240 // I don't know if it is worth to expose those in the API.
241
242 gfxd_sys::io::gfxd_input_buffer(NonNullConst::new_void(data.as_ptr()), data_len);
243 }
244
245 // Set the microcode
246 unsafe {
247 gfxd_sys::settings::gfxd_target(Some(microcode.to_microcode_ptr()));
248 }
249
250 // Set the dynamic arg, if any
251 unsafe {
252 gfxd_sys::settings::gfxd_dynamic(dynamic);
253 }
254
255 // Set the options
256 set_feature_option(
257 self.stop_on_invalid,
258 gfxd_sys::settings::FeatureOption::gfxd_stop_on_invalid,
259 );
260 set_feature_option(
261 self.stop_on_end,
262 gfxd_sys::settings::FeatureOption::gfxd_stop_on_end,
263 );
264 set_feature_option(
265 self.emit_dec_color,
266 gfxd_sys::settings::FeatureOption::gfxd_emit_dec_color,
267 );
268 set_feature_option(
269 self.emit_q_macro,
270 gfxd_sys::settings::FeatureOption::gfxd_emit_q_macro,
271 );
272 set_feature_option(
273 self.emit_ext_macro,
274 gfxd_sys::settings::FeatureOption::gfxd_emit_ext_macro,
275 );
276
277 // Run
278 lib_data_wrap.do_before();
279 unsafe {
280 gfxd_sys::execution::gfxd_execute();
281 }
282 lib_data_wrap.do_after();
283 }
284}
285
286fn set_feature_option(on: bool, cap: gfxd_sys::settings::FeatureOption) {
287 #[allow(clippy::multiple_unsafe_ops_per_block)]
288 unsafe {
289 if on {
290 gfxd_sys::settings::gfxd_enable(cap);
291 } else {
292 gfxd_sys::settings::gfxd_disable(cap);
293 }
294 }
295}
296
297impl Default for Disassembler<'_> {
298 fn default() -> Self {
299 Self::new()
300 }
301}