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}