Skip to content

Commit b307266

Browse files
committed
Implement parser for nqc binary output
1 parent 80d72d1 commit b307266

File tree

4 files changed

+269
-3
lines changed

4 files changed

+269
-3
lines changed

Cargo.lock

+8-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+7
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,14 @@ lazy_static = "1"
1919
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
2020
uuid = "1"
2121
rcx = "0.1.0"
22+
nom = "7.1.3"
23+
24+
[dev-dependencies]
25+
hex-literal = "0.4.1"
2226

2327
# [dependencies.lego-powered-up]
2428
# version = "0.3"
2529
# path = "../lego-powered-up/lego-powered-up"
30+
31+
[patch.crates-io]
32+
rcx = { path = "../rcx/rcx" }

src/rcx.rs

+50-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ use std::{
77
process::Command,
88
};
99

10+
mod binfmt;
11+
12+
use binfmt::RcxBin;
13+
1014
const MAX_PROGRAM_SLOT: u8 = 9;
1115
const DEVICE: &str = "/dev/usb/legousbtower0";
1216

@@ -50,9 +54,54 @@ pub fn compile(file: PathBuf) -> Result<()> {
5054
Ok(())
5155
}
5256

53-
pub fn program(slot: u8, _file: PathBuf) -> Result<()> {
57+
pub fn program(slot: u8, file: PathBuf) -> Result<()> {
5458
if slot > MAX_PROGRAM_SLOT {
5559
return Err(eyre!("Program slot must be 0-9"));
5660
}
61+
62+
let rcx = UsbTower::open(DEVICE)?;
63+
let mut rcx = Rcx::new(rcx);
64+
65+
// Read in the target binary
66+
let bin = std::fs::read(&file)?;
67+
let bin = RcxBin::parse(&bin)?;
68+
69+
// Prepare RCX for download
70+
rcx.set_program_number(slot)?;
71+
72+
// Download the program chunks
73+
for (idx, chunk) in bin.chunks.iter().enumerate() {
74+
println!(
75+
"Downloading chunk {} of {} to task {}",
76+
idx + 1,
77+
bin.chunks.len(),
78+
chunk.number
79+
);
80+
rcx.start_task_download(chunk.number, chunk.data.len().try_into()?)?;
81+
82+
for (idx, data_chunk) in chunk.data.chunks(256).enumerate() {
83+
let mut buf = [0; 256];
84+
buf[..data_chunk.len()].copy_from_slice(data_chunk);
85+
let checksum = buf
86+
.iter()
87+
.copied()
88+
.reduce(u8::wrapping_add)
89+
.unwrap_or_default();
90+
let idx = if (idx + 1) * 256 >= chunk.data.len() {
91+
// last block
92+
0
93+
} else {
94+
idx as i16 + 1
95+
};
96+
rcx.transfer_data(
97+
idx,
98+
data_chunk.len().try_into()?,
99+
buf,
100+
checksum,
101+
)?;
102+
}
103+
}
104+
105+
println!("Successfully downloaded {}", file.display());
57106
todo!()
58107
}

src/rcx/binfmt.rs

+204
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
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
17+
//! * for each symbol:
18+
//! - type - 1 byte
19+
//! - index - 1 byte
20+
//! - length - 1 byte
21+
//! - name - <length> bytes cstr
22+
//! ```
23+
24+
use crate::Result;
25+
use color_eyre::eyre::eyre;
26+
use nom::{number::Endianness, IResult};
27+
use std::ffi::CString;
28+
29+
const RCX_TAG: &str = "RCXI";
30+
const MAX_CHUNKS: usize = 10;
31+
32+
#[derive(Clone, Debug, PartialEq, Eq)]
33+
pub struct RcxBin {
34+
pub signature: [u8; 4],
35+
pub version: u16,
36+
pub chunk_count: u16,
37+
pub symbol_count: u16,
38+
pub target_type: u8,
39+
pub reserved: u8,
40+
pub chunks: Vec<Chunk>,
41+
pub symbols: Vec<Symbol>,
42+
}
43+
44+
impl RcxBin {
45+
pub fn parse(bin: &[u8]) -> Result<Self> {
46+
let (_i, bin) = parse(bin).map_err(|e| eyre!("Parse error: {e}"))?;
47+
bin.verify()?;
48+
Ok(bin)
49+
}
50+
51+
pub fn verify(&self) -> Result<()> {
52+
fn repeated_idx(chunks: &[Chunk]) -> bool {
53+
let mut c = chunks.iter().map(|c| c.number).collect::<Vec<_>>();
54+
c.sort_unstable();
55+
c.dedup();
56+
c.len() != chunks.len()
57+
}
58+
59+
// check chunk count
60+
if self.chunk_count as usize != self.chunks.len()
61+
|| self.chunks.len() > MAX_CHUNKS
62+
{
63+
Err(eyre!("Invalid number of chunks"))
64+
} else if repeated_idx(&self.chunks) {
65+
Err(eyre!("Nonunique chunk numbers"))
66+
} else {
67+
Ok(())
68+
}
69+
}
70+
}
71+
72+
#[derive(Clone, Debug, PartialEq, Eq)]
73+
pub struct Chunk {
74+
pub ty: u8,
75+
pub number: u8,
76+
pub length: u16,
77+
pub data: Vec<u8>,
78+
}
79+
80+
fn parse_chunk(i: &[u8]) -> IResult<&[u8], Chunk> {
81+
let read_u16 = nom::number::complete::u16(Endianness::Little);
82+
let read_u8 = nom::number::complete::u8;
83+
84+
let (i, ty) = read_u8(i)?;
85+
let (i, number) = read_u8(i)?;
86+
let (i, length) = read_u16(i)?;
87+
let (i, data) = nom::bytes::complete::take(length)(i)?;
88+
89+
Ok((
90+
i,
91+
Chunk {
92+
ty,
93+
number,
94+
length,
95+
data: data.to_vec(),
96+
},
97+
))
98+
}
99+
100+
#[derive(Clone, Debug, PartialEq, Eq)]
101+
pub struct Symbol {
102+
pub ty: u8,
103+
pub index: u8,
104+
pub length: u16,
105+
pub name: CString,
106+
}
107+
108+
fn parse_symbol(i: &[u8]) -> IResult<&[u8], Symbol> {
109+
let read_u16 = nom::number::complete::u16(Endianness::Little);
110+
let read_u8 = nom::number::complete::u8;
111+
112+
let (i, ty) = read_u8(i)?;
113+
let (i, index) = read_u8(i)?;
114+
let (i, length) = read_u16(i)?;
115+
let (i, name) = nom::bytes::complete::take(length)(i)?;
116+
117+
Ok((
118+
i,
119+
Symbol {
120+
ty,
121+
index,
122+
length,
123+
name: CString::from_vec_with_nul(name.to_vec()).map_err(|_| {
124+
nom::Err::Failure(nom::error::Error {
125+
input: i,
126+
code: nom::error::ErrorKind::Alpha,
127+
})
128+
})?,
129+
},
130+
))
131+
}
132+
133+
fn parse(bin: &[u8]) -> IResult<&[u8], RcxBin> {
134+
let read_u16 = nom::number::complete::u16(Endianness::Little);
135+
let read_u8 = nom::number::complete::u8;
136+
137+
let (i, signature) = nom::bytes::complete::tag(RCX_TAG)(bin)?;
138+
let (i, version) = read_u16(i)?;
139+
let (i, chunk_count) = read_u16(i)?;
140+
let (i, symbol_count) = read_u16(i)?;
141+
let (i, target_type) = read_u8(i)?;
142+
let (i, reserved) = read_u8(i)?;
143+
144+
let (i, chunks) = nom::multi::count(parse_chunk, chunk_count.into())(i)?;
145+
let (i, symbols) = nom::multi::count(parse_symbol, symbol_count.into())(i)?;
146+
147+
IResult::Ok((
148+
i,
149+
RcxBin {
150+
signature: signature.try_into().unwrap_or([0; 4]),
151+
version,
152+
chunk_count,
153+
symbol_count,
154+
target_type,
155+
reserved,
156+
chunks,
157+
symbols,
158+
},
159+
))
160+
}
161+
162+
#[cfg(test)]
163+
mod test {
164+
use super::*;
165+
use hex_literal::hex;
166+
167+
const SAMPLE: &[u8] = &hex!(
168+
"5243584902010100010000000000 \
169+
140013070207e18713010232e1812181 \
170+
430264002141000005006d61696e00"
171+
);
172+
173+
#[test]
174+
fn parse_sample() {
175+
let bin = RcxBin::parse(SAMPLE).unwrap();
176+
assert_eq!(
177+
bin,
178+
RcxBin {
179+
signature: *b"RCXI",
180+
version: 0x0102,
181+
chunk_count: 1,
182+
symbol_count: 1,
183+
target_type: 0,
184+
reserved: 0,
185+
chunks: vec![Chunk {
186+
ty: 0,
187+
number: 0,
188+
length: 20,
189+
data: vec![
190+
0x13, 0x7, 0x2, 0x7, 0xe1, 0x87, 0x13, 0x1, 0x2, 0x32,
191+
0xe1, 0x81, 0x21, 0x81, 0x43, 0x2, 0x64, 0x0, 0x21,
192+
0x41
193+
]
194+
}],
195+
symbols: vec![Symbol {
196+
ty: 0,
197+
index: 0,
198+
length: 5,
199+
name: CString::new("main").unwrap(),
200+
}],
201+
}
202+
);
203+
}
204+
}

0 commit comments

Comments
 (0)