rgbds_obj/
lib.rs

1//! This crate allows working with [RGBDS] object files.
2//! Currently, only version 9 revisions 6–13 are supported, but more should be added in the
3//! future.
4//!
5//! # Object file revision table
6//!
7//! The object file format has changed several times over RGBDS' lifespan.
8//! The following table indicates which object file version each release of RGBDS uses.
9//!
10//! Note that a "revision" field was introduced in version 9, so it's not listed earlier.
11//!
12//! RGBDS release                                          | Object file format
13//! -------------------------------------------------------|-------------------
14//! [v1.0.0](https://rgbds.gbdev.io/docs/v1.0.0/rgbds.5)   | v9 r13
15//! [v0.9.4](https://rgbds.gbdev.io/docs/v0.9.4/rgbds.5)   | v9 r12
16//! [v0.9.3](https://rgbds.gbdev.io/docs/v0.9.3/rgbds.5)   | v9 r12
17//! [v0.9.2](https://rgbds.gbdev.io/docs/v0.9.2/rgbds.5)   | v9 r12
18//! [v0.9.1](https://rgbds.gbdev.io/docs/v0.9.1/rgbds.5)   | v9 r11
19//! [v0.9.0](https://rgbds.gbdev.io/docs/v0.9.0/rgbds.5)   | v9 r11
20//! [v0.8.0](https://rgbds.gbdev.io/docs/v0.8.0/rgbds.5)   | v9 r10
21//! [v0.7.0](https://rgbds.gbdev.io/docs/v0.7.0/rgbds.5)   | v9 r9 (reported), v9 r10 (actual)
22//! [v0.6.1](https://rgbds.gbdev.io/docs/v0.6.1/rgbds.5)   | v9 r9
23//! [v0.6.0](https://rgbds.gbdev.io/docs/v0.6.0/rgbds.5)   | v9 r9
24//! [v0.5.2](https://rgbds.gbdev.io/docs/v0.5.2/rgbds.5)   | v9 r8
25//! [v0.5.1](https://rgbds.gbdev.io/docs/v0.5.1/rgbds.5)   | v9 r8
26//! [v0.5.0](https://rgbds.gbdev.io/docs/v0.5.0/rgbds.5)   | v9 r7
27//! [v0.4.2](https://rgbds.gbdev.io/docs/v0.4.2/rgbds.5)   | v9 r6
28//! [v0.4.1](https://rgbds.gbdev.io/docs/v0.4.1/rgbds.5)   | v9 r5
29//! [v0.4.0](https://rgbds.gbdev.io/docs/v0.4.0/rgbds.5)   | v9 r3
30//! [v0.3.10](https://rgbds.gbdev.io/docs/v0.3.10/rgbds.5) | v6
31//!
32//! RGBDS releases v0.3.4 through v0.3.9 also used object format v6.
33//!
34//! [RGBDS]: https://rgbds.gbdev.io
35
36#![doc(html_root_url = "https://docs.rs/rgbds-obj/0.5.0")]
37
38use std::convert::TryInto;
39use std::error::Error;
40use std::fmt::{self, Display, Formatter};
41use std::io::{self, Read};
42
43mod assertion;
44pub use assertion::*;
45mod fstack;
46pub use fstack::*;
47mod patch;
48pub use patch::*;
49mod rpn;
50pub use rpn::*;
51mod section;
52pub use section::*;
53mod symbol;
54pub use symbol::*;
55mod util;
56use util::*;
57
58/// A RGBDS object file.
59#[derive(Debug)]
60pub struct Object {
61    version: u8,
62    revision: u32,
63    fstack_nodes: Vec<Node>,
64    symbols: Vec<Symbol>,
65    sections: Vec<Section>,
66    assertions: Vec<Assertion>,
67}
68
69impl Object {
70    /// Reads a serialized object.
71    ///
72    /// # Errors
73    ///
74    /// This function returns any errors that occurred while reading data, as well as if the object
75    /// data itself cannot be deserialized.
76    /// Note that not all consistency checks are performed when reading the file; for example, RPN
77    /// expressions may be invalid, the file stack node tree may be malformed, etc.
78    ///
79    /// Note that maximum upwards compatibility is assumed: for example, currently, RPN data is
80    /// parsed using the v9 r8 spec, even if the file reports an earlier revision.
81    /// This should change in the future.
82    pub fn read_from(mut input: impl Read) -> Result<Self, io::Error> {
83        let mut magic = [0; 4];
84        input.read_exact(&mut magic)?;
85
86        if &magic[0..3] != b"RGB" || !magic[3].is_ascii_digit() {
87            return Err(io::Error::new(
88                io::ErrorKind::InvalidData,
89                "This does not appear to be a valid RGBDS object",
90            ));
91        }
92
93        let version = magic[3];
94        if version != b'9' {
95            return Err(io::Error::new(
96                io::ErrorKind::InvalidData,
97                format!(
98                    "Object file version {} is not supported (must be 9)",
99                    version as char
100                ),
101            ));
102        }
103
104        let revision = read_u32le(&mut input)?;
105        if !(6..=13).contains(&revision) {
106            return Err(io::Error::new(
107                io::ErrorKind::InvalidData,
108                format!(
109                    "Object file {} revision {revision} is not supported (must be between 6 and 13)",
110                    version as char
111                ),
112            ));
113        }
114
115        let sections_have_srcs = version > b'9' || (version == b'9' && revision >= 11);
116
117        let nb_symbols = read_u32le(&mut input)?.try_into().unwrap();
118        let nb_sections = read_u32le(&mut input)?.try_into().unwrap();
119        let nb_fstack_nodes = read_u32le(&mut input)?.try_into().unwrap();
120
121        let mut obj = Self {
122            version,
123            revision,
124            fstack_nodes: Vec::with_capacity(nb_fstack_nodes),
125            symbols: Vec::with_capacity(nb_symbols),
126            sections: Vec::with_capacity(nb_sections),
127            assertions: Vec::new(), // We don't have the assertion count yet
128        };
129
130        for _ in 0..nb_fstack_nodes {
131            obj.fstack_nodes.push(Node::read_from(&mut input)?);
132        }
133        for _ in 0..nb_symbols {
134            obj.symbols.push(Symbol::read_from(&mut input)?);
135        }
136        for _ in 0..nb_sections {
137            obj.sections
138                .push(Section::read_from(&mut input, sections_have_srcs)?);
139        }
140
141        let nb_assertions = read_u32le(&mut input)?.try_into().unwrap();
142        obj.assertions.reserve_exact(nb_assertions);
143        for _ in 0..nb_assertions {
144            obj.assertions.push(Assertion::read_from(&mut input)?);
145        }
146
147        Ok(obj)
148    }
149
150    /// The object's version.
151    pub fn version(&self) -> u8 {
152        self.version
153    }
154
155    /// The object's revision.
156    pub fn revision(&self) -> u32 {
157        self.revision
158    }
159
160    /// Retrieves one of the object's [file stack nodes][crate::Node] by ID.
161    /// Returns `None` if the ID is invalid (too large).
162    pub fn node(&self, id: u32) -> Option<&Node> {
163        let id: usize = id.try_into().unwrap();
164        if id < self.fstack_nodes.len() {
165            Some(&self.fstack_nodes[self.fstack_nodes.len() - 1 - id])
166        } else {
167            None
168        }
169    }
170
171    /// Walks the node tree, from its root up to the node with the given ID, running a callback on
172    /// each node encountered.
173    ///
174    /// The function may return an error, which aborts the walk.
175    /// If the function does not fail, you can (and probably will have to) use [`Infallible`][std::convert::Infallible]:
176    ///
177    /// ```no_run
178    /// # use rgbds_obj::Object;
179    /// # use std::convert::Infallible;
180    /// # use std::fs::File;
181    /// #
182    /// # let input = File::open("camera.o").unwrap();
183    /// # let object = Object::read_from(&input).unwrap();
184    /// object.walk_nodes::<Infallible, _>(0, 1, &mut |node| {
185    ///     println!("{node:?}");
186    ///     Ok(())
187    /// });
188    /// ```
189    pub fn walk_nodes<E, F>(
190        &self,
191        id: u32,
192        line_no: u32,
193        callback: &mut F,
194    ) -> Result<(), NodeWalkError<E>>
195    where
196        F: FnMut(&Node, u32) -> Result<(), NodeWalkError<E>>,
197    {
198        let node = self
199            .node(id)
200            .ok_or_else(|| NodeWalkError::bad_id(id, self))?;
201
202        if let Some((parent_id, parent_line_no)) = node.parent() {
203            self.walk_nodes(parent_id, parent_line_no, callback)?;
204        }
205        callback(node, line_no)
206    }
207
208    /// The object's symbols.
209    pub fn symbols(&self) -> &[Symbol] {
210        &self.symbols
211    }
212
213    /// The object's sections.
214    pub fn sections(&self) -> &[Section] {
215        &self.sections
216    }
217
218    /// The object's assertions.
219    pub fn assertions(&self) -> &[Assertion] {
220        &self.assertions
221    }
222}
223
224/// An error that occurs while walking the node tree.
225#[derive(Debug)]
226pub enum NodeWalkError<E> {
227    /// Requested a node number that is out of bounds.
228    BadId(u32, usize),
229    /// Inner error thrown by the callback.
230    Custom(E),
231}
232impl<E> NodeWalkError<E> {
233    /// Constructs an "out of bounds node ID" error that occurred while walking a given object.
234    pub fn bad_id(id: u32, object: &Object) -> Self {
235        Self::BadId(id, object.fstack_nodes.len())
236    }
237}
238impl<E: Display> Display for NodeWalkError<E> {
239    fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> {
240        use NodeWalkError::*;
241
242        match self {
243            BadId(id, len) => write!(fmt, "Requested node #{id} of {len}"),
244            Custom(err) => err.fmt(fmt),
245        }
246    }
247}
248impl<E: Error + 'static> Error for NodeWalkError<E> {
249    fn source(&self) -> Option<&(dyn Error + 'static)> {
250        use NodeWalkError::*;
251
252        if let Custom(err) = self {
253            Some(err)
254        } else {
255            None
256        }
257    }
258}
259impl<E> From<E> for NodeWalkError<E> {
260    fn from(inner: E) -> Self {
261        Self::Custom(inner)
262    }
263}