rcx/
binfmt.rs

1//! Parser for the .rcx binary format
2//!
3//! Referenced from: <https://github.com/BrickBot/nqc/blob/master/rcxlib/RCX_Image.cpp>
4//!
5//! ```text
6//! * signature - 4 bytes
7//! * version - 2 bytes
8//! * chunks_count - 2 bytes
9//! * symbol_count - 2 bytes
10//! * target_type - 1 byte
11//! * reserved - 1 byte
12//! * for each chunk:
13//!   - type - 1 byte (type <= 2)
14//!   - number - 1 byte
15//!   - length - 2 bytes
16//!   - data - <length> bytes, padded to u32 alignment
17//!  * for each symbol:
18//!   - type - 1 byte
19//!   - index - 1 byte
20//!   - length - 1 byte
21//!   - name - <length> bytes cstr
22//! ```
23
24use crate::{Error, Result};
25use nom::{number::Endianness, IResult};
26use std::{
27    ffi::CString,
28    fmt::{self, Debug, Display, Formatter, Write},
29};
30use tracing::trace;
31
32const RCX_TAG: &str = "RCXI";
33const MAX_SECTIONS: usize = 10;
34const INDENT: &str = "  ";
35const HEXDUMP_WRAP_BYTES: usize = 16;
36
37fn print_hex_with_marker_at(bin: &[u8], pos: usize) -> String {
38    let mut out = String::new();
39
40    // header
41    write!(&mut out, "     ").unwrap();
42    for n in 0..16 {
43        write!(&mut out, " {n:2x}").unwrap();
44    }
45    writeln!(&mut out).unwrap();
46
47    // hexdump
48    for (idx, chunk) in bin.chunks(HEXDUMP_WRAP_BYTES).enumerate() {
49        write!(&mut out, "0x{:02x}:", idx * HEXDUMP_WRAP_BYTES,).unwrap();
50        for byte in chunk {
51            write!(&mut out, " {byte:02x}").unwrap();
52        }
53        writeln!(&mut out).unwrap();
54        if (idx * HEXDUMP_WRAP_BYTES..(idx + 1) * HEXDUMP_WRAP_BYTES)
55            .contains(&pos)
56        {
57            // indent the marker appropriately
58            out += "     "; // indent past the offset display
59            out.extend(std::iter::repeat("   ").take(pos % HEXDUMP_WRAP_BYTES));
60            writeln!(&mut out, "^<<").unwrap();
61        }
62    }
63    out
64}
65
66#[derive(Clone, Debug, PartialEq, Eq)]
67pub struct RcxBin {
68    pub signature: [u8; 4],
69    pub version: u16,
70    pub section_count: u16,
71    pub symbol_count: u16,
72    pub target_type: TargetType,
73    pub reserved: u8,
74    pub sections: Vec<Section>,
75    pub symbols: Vec<Symbol>,
76}
77
78impl RcxBin {
79    pub fn parse(bin: &[u8]) -> Result<Self> {
80        let (_i, bin) = parse(bin).map_err(|err| {
81            let input = match &err {
82                nom::Err::Error(err) => err.input,
83                nom::Err::Failure(err) => err.input,
84                nom::Err::Incomplete(needed) => {
85                    return Error::Nom(format!(
86                        "Incomplete input, needed {needed:?}",
87                    ));
88                }
89            };
90            let pos = bin.len() - input.len();
91            println!("{}", print_hex_with_marker_at(bin, pos));
92            err.into()
93        })?;
94        bin.verify()?;
95        Ok(bin)
96    }
97
98    pub fn verify(&self) -> Result<()> {
99        fn repeated_idx(sections: &[Section]) -> bool {
100            let mut c = sections
101                .iter()
102                .map(|c| (c.ty as u8, c.number))
103                .collect::<Vec<_>>();
104            c.sort_unstable();
105            c.dedup();
106            c.len() != sections.len()
107        }
108
109        // check chunk count
110        if self.section_count as usize != self.sections.len()
111            || self.sections.len() > MAX_SECTIONS
112        {
113            Err(Error::Parse("Invalid number of chunks"))
114        } else if repeated_idx(&self.sections) {
115            Err(Error::Parse("Nonunique chunk numbers"))
116        } else {
117            Ok(())
118        }
119    }
120}
121
122impl Display for RcxBin {
123    fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
124        writeln!(
125            fmt,
126            "Signature: {}",
127            String::from_utf8_lossy(&self.signature),
128        )?;
129        writeln!(fmt, "Version: {:x}", self.version)?;
130        writeln!(
131            fmt,
132            "{} sections, {} symbols",
133            self.section_count, self.symbol_count,
134        )?;
135        writeln!(fmt, "Target: {}", self.target_type)?;
136        writeln!(fmt, "Sections:")?;
137        for section in &self.sections {
138            writeln!(fmt, "{section}")?;
139        }
140        writeln!(fmt, "Symbols:")?;
141        for symbol in &self.symbols {
142            writeln!(fmt, "{symbol}")?;
143        }
144        Ok(())
145    }
146}
147
148fn parse(bin: &[u8]) -> IResult<&[u8], RcxBin> {
149    trace!("Input len: {}", bin.len());
150    let read_u16 = nom::number::complete::u16(Endianness::Little);
151    let read_u8 = nom::number::complete::u8;
152
153    let (i, signature) = nom::bytes::complete::tag(RCX_TAG)(bin)?;
154    let (i, version) = read_u16(i)?;
155    let (i, section_count) = read_u16(i)?;
156    let (i, symbol_count) = read_u16(i)?;
157    let (i, target_type) = TargetType::parse(i)?;
158    let (i, reserved) = read_u8(i)?;
159
160    trace!("Parse {section_count} sections");
161    let (i, sections) =
162        nom::multi::count(parse_section, section_count.into())(i)?;
163    trace!("Parse {symbol_count} symbols");
164    let (i, symbols) = nom::multi::count(parse_symbol, symbol_count.into())(i)?;
165
166    IResult::Ok((
167        i,
168        RcxBin {
169            signature: signature.try_into().unwrap_or([0; 4]),
170            version,
171            section_count,
172            symbol_count,
173            target_type,
174            reserved,
175            sections,
176            symbols,
177        },
178    ))
179}
180
181#[derive(Copy, Clone, Debug, PartialEq, Eq)]
182#[repr(u8)]
183pub enum TargetType {
184    /// Original RCX (i.e., RCX bricks with v.0309 or earlier firmwares)
185    Rcx = 0,
186    /// CyberMaster
187    CyberMaster,
188    /// Scout
189    Scout,
190    /// RCX 2.0 (i.e., RCX bricks with v.0328 or later firmwares)
191    Rcx2,
192    /// Spybotics
193    Spybotics,
194    /// Dick Swan's alternate firmware
195    Swan,
196}
197
198impl TargetType {
199    pub fn parse(i: &[u8]) -> IResult<&[u8], Self> {
200        let (i, ty) = nom::number::complete::u8(i)?;
201        let ty = match ty {
202            0 => Self::Rcx,
203            1 => Self::CyberMaster,
204            2 => Self::Scout,
205            3 => Self::Rcx2,
206            4 => Self::Spybotics,
207            5 => Self::Swan,
208            _ => {
209                return Err(nom::Err::Failure(nom::error::Error {
210                    input: i,
211                    code: nom::error::ErrorKind::Verify,
212                }));
213            }
214        };
215        Ok((i, ty))
216    }
217}
218
219impl Display for TargetType {
220    fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
221        Debug::fmt(self, fmt)
222    }
223}
224
225#[derive(Clone, Debug, PartialEq, Eq)]
226pub struct Section {
227    pub ty: SectionType,
228    pub number: u8,
229    pub length: u16,
230    pub data: Vec<u8>,
231}
232
233fn parse_section(i: &[u8]) -> IResult<&[u8], Section> {
234    let neg_offset = i.len();
235    let read_u16 = nom::number::complete::u16(Endianness::Little);
236    let read_u8 = nom::number::complete::u8;
237
238    let (i, ty) = SectionType::parse(i)?;
239    trace!("- section type {ty} - offset from back: {neg_offset}");
240    let (i, number) = read_u8(i)?;
241    trace!("  number {number}");
242    trace!("  length raw: {:02x}{:02x}", i[1], i[0]);
243    let (i, length) = read_u16(i)?;
244    trace!("  length {length}");
245    let (i, data) = nom::bytes::complete::take(length)(i)?;
246    trace!("  data len {}", data.len());
247    trace!("  data: {data:02x?}");
248
249    // read padding bytes
250    let (i, _pad) = nom::bytes::complete::take((4 - (length % 4)) & 3)(i)?;
251
252    Ok((
253        i,
254        Section {
255            ty,
256            number,
257            length,
258            data: data.to_vec(),
259        },
260    ))
261}
262
263impl Display for Section {
264    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
265        writeln!(fmt, "{INDENT}{} - {} bytes", self.ty, self.length)?;
266        writeln!(fmt, "{INDENT}{INDENT}{}", hex::encode(&self.data))?;
267        Ok(())
268    }
269}
270
271#[derive(Copy, Clone, Debug, PartialEq, Eq)]
272#[repr(u8)]
273pub enum SectionType {
274    Task = 0,
275    Subroutine,
276    Sound,
277    Animation,
278    Count,
279}
280
281impl SectionType {
282    pub fn parse(i: &[u8]) -> IResult<&[u8], Self> {
283        let (i, ty) = nom::number::complete::u8(i)?;
284        let ty = match ty {
285            0 => Self::Task,
286            1 => Self::Subroutine,
287            2 => Self::Sound,
288            3 => Self::Animation,
289            4 => Self::Count,
290            _ => {
291                return Err(nom::Err::Failure(nom::error::Error {
292                    input: i,
293                    code: nom::error::ErrorKind::Verify,
294                }));
295            }
296        };
297        Ok((i, ty))
298    }
299}
300
301impl Display for SectionType {
302    fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
303        Debug::fmt(self, fmt)
304    }
305}
306
307#[derive(Clone, Debug, PartialEq, Eq)]
308pub struct Symbol {
309    pub ty: SymbolType,
310    pub index: u8,
311    pub length: u16,
312    pub name: CString,
313}
314
315fn parse_symbol(i: &[u8]) -> IResult<&[u8], Symbol> {
316    let read_u16 = nom::number::complete::u16(Endianness::Little);
317    let read_u8 = nom::number::complete::u8;
318
319    let (i, ty) = SymbolType::parse(i)?;
320    trace!("Symbol type {ty}");
321    let (i, index) = read_u8(i)?;
322    let (i, length) = read_u16(i)?;
323    let (i, name) = nom::bytes::complete::take(length)(i)?;
324
325    Ok((
326        i,
327        Symbol {
328            ty,
329            index,
330            length,
331            name: CString::from_vec_with_nul(name.to_vec()).map_err(|_| {
332                nom::Err::Failure(nom::error::Error {
333                    input: i,
334                    code: nom::error::ErrorKind::Alpha,
335                })
336            })?,
337        },
338    ))
339}
340
341impl Display for Symbol {
342    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
343        writeln!(
344            fmt,
345            "{INDENT}{} at {} - {} bytes",
346            self.ty, self.index, self.length
347        )?;
348        writeln!(fmt, "{INDENT}{INDENT}{:?}", self.name)?;
349        Ok(())
350    }
351}
352
353#[derive(Copy, Clone, Debug, PartialEq, Eq)]
354#[repr(u8)]
355pub enum SymbolType {
356    Task = 0,
357    Sub,
358    Var,
359}
360
361impl SymbolType {
362    pub fn parse(i: &[u8]) -> IResult<&[u8], Self> {
363        let (i, ty) = nom::number::complete::u8(i)?;
364        let ty = match ty {
365            0 => Self::Task,
366            1 => Self::Sub,
367            2 => Self::Var,
368            _ => {
369                return Err(nom::Err::Failure(nom::error::Error {
370                    input: i,
371                    code: nom::error::ErrorKind::Verify,
372                }));
373            }
374        };
375        Ok((i, ty))
376    }
377}
378
379impl Display for SymbolType {
380    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
381        Debug::fmt(self, fmt)
382    }
383}
384
385#[cfg(test)]
386mod test {
387    use super::*;
388    use hex_literal::hex;
389
390    const SAMPLE: &[u8] = &hex!(
391        "5243584902010100010000000000 \
392        140013070207e18713010232e1812181 \
393        430264002141000005006d61696e00"
394    );
395
396    const COMPLEX: &[u8] = &hex!(
397        "52435849020103000500000001000400e181218100000e0013070207e187
398    130102321700710100000001330014000232001401020500130100002400
399    00010085420059000008140102feff270d8502000b000006140102020043
400    02640027a800010008007365745f66776400000005006d61696e0000010a
401    006c6f6f705f7461736b0002000600706f776572000201060064656c7461
402    00"
403    );
404
405    #[test]
406    fn err_msg() {
407        let out = print_hex_with_marker_at(COMPLEX, 5);
408        let expected = "       0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
4090x00: 52 43 58 49 02 01 03 00 05 00 00 00 01 00 04 00
410                    ^<<
4110x10: e1 81 21 81 00 00 0e 00 13 07 02 07 e1 87 13 01
4120x20: 02 32 17 00 71 01 00 00 00 01 33 00 14 00 02 32
4130x30: 00 14 01 02 05 00 13 01 00 00 24 00 00 01 00 85
4140x40: 42 00 59 00 00 08 14 01 02 fe ff 27 0d 85 02 00
4150x50: 0b 00 00 06 14 01 02 02 00 43 02 64 00 27 a8 00
4160x60: 01 00 08 00 73 65 74 5f 66 77 64 00 00 00 05 00
4170x70: 6d 61 69 6e 00 00 01 0a 00 6c 6f 6f 70 5f 74 61
4180x80: 73 6b 00 02 00 06 00 70 6f 77 65 72 00 02 01 06
4190x90: 00 64 65 6c 74 61 00
420";
421        assert_eq!(out, expected);
422
423        let out = print_hex_with_marker_at(COMPLEX, 35);
424        let expected = "       0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
4250x00: 52 43 58 49 02 01 03 00 05 00 00 00 01 00 04 00
4260x10: e1 81 21 81 00 00 0e 00 13 07 02 07 e1 87 13 01
4270x20: 02 32 17 00 71 01 00 00 00 01 33 00 14 00 02 32
428              ^<<
4290x30: 00 14 01 02 05 00 13 01 00 00 24 00 00 01 00 85
4300x40: 42 00 59 00 00 08 14 01 02 fe ff 27 0d 85 02 00
4310x50: 0b 00 00 06 14 01 02 02 00 43 02 64 00 27 a8 00
4320x60: 01 00 08 00 73 65 74 5f 66 77 64 00 00 00 05 00
4330x70: 6d 61 69 6e 00 00 01 0a 00 6c 6f 6f 70 5f 74 61
4340x80: 73 6b 00 02 00 06 00 70 6f 77 65 72 00 02 01 06
4350x90: 00 64 65 6c 74 61 00
436";
437        assert_eq!(out, expected);
438    }
439
440    #[test]
441    fn parse_sample() {
442        let bin = RcxBin::parse(SAMPLE).unwrap();
443        assert_eq!(
444            bin,
445            RcxBin {
446                signature: *b"RCXI",
447                version: 0x0102,
448                section_count: 1,
449                symbol_count: 1,
450                target_type: TargetType::Rcx,
451                reserved: 0,
452                sections: vec![Section {
453                    ty: SectionType::Task,
454                    number: 0,
455                    length: 20,
456                    data: vec![
457                        0x13, 0x7, 0x2, 0x7, 0xe1, 0x87, 0x13, 0x1, 0x2, 0x32,
458                        0xe1, 0x81, 0x21, 0x81, 0x43, 0x2, 0x64, 0x0, 0x21,
459                        0x41
460                    ]
461                }],
462                symbols: vec![Symbol {
463                    ty: SymbolType::Task,
464                    index: 0,
465                    length: 5,
466                    name: CString::new("main").unwrap(),
467                }],
468            }
469        );
470    }
471
472    #[test]
473    fn parse_complex() {
474        let bin = RcxBin::parse(COMPLEX).unwrap();
475        assert_eq!(
476            bin,
477            RcxBin {
478                signature: *b"RCXI",
479                version: 0x0102,
480                section_count: 3,
481                symbol_count: 5,
482                target_type: TargetType::Rcx,
483                reserved: 0,
484                sections: vec![
485                    Section {
486                        ty: SectionType::Subroutine,
487                        number: 0,
488                        length: 4,
489                        data: vec![225, 129, 33, 129]
490                    },
491                    Section {
492                        ty: SectionType::Task,
493                        number: 0,
494                        length: 14,
495                        data: vec![
496                            19, 7, 2, 7, 225, 135, 19, 1, 2, 50, 23, 0, 113, 1
497                        ]
498                    },
499                    Section {
500                        ty: SectionType::Task,
501                        number: 1,
502                        length: 51,
503                        data: vec![
504                            20, 0, 2, 50, 0, 20, 1, 2, 5, 0, 19, 1, 0, 0, 36,
505                            0, 0, 1, 0, 133, 66, 0, 89, 0, 0, 8, 20, 1, 2, 254,
506                            255, 39, 13, 133, 2, 0, 11, 0, 0, 6, 20, 1, 2, 2,
507                            0, 67, 2, 100, 0, 39, 168
508                        ]
509                    },
510                ],
511                symbols: vec![
512                    Symbol {
513                        ty: SymbolType::Sub,
514                        index: 0,
515                        length: 8,
516                        name: CString::new("set_fwd").unwrap()
517                    },
518                    Symbol {
519                        ty: SymbolType::Task,
520                        index: 0,
521                        length: 5,
522                        name: CString::new("main").unwrap()
523                    },
524                    Symbol {
525                        ty: SymbolType::Task,
526                        index: 1,
527                        length: 10,
528                        name: CString::new("loop_task").unwrap()
529                    },
530                    Symbol {
531                        ty: SymbolType::Var,
532                        index: 0,
533                        length: 6,
534                        name: CString::new("power").unwrap()
535                    },
536                    Symbol {
537                        ty: SymbolType::Var,
538                        index: 1,
539                        length: 6,
540                        name: CString::new("delta").unwrap()
541                    }
542                ],
543            }
544        );
545    }
546}