Initial commit

This commit is contained in:
2025-09-18 13:17:47 +02:00
commit 8f6b7fbc1f
9 changed files with 1063 additions and 0 deletions

234
src/target.rs Normal file
View File

@@ -0,0 +1,234 @@
use std::io::Error;
use std::path::Path;
use std::time::Duration;
use serial2::SerialPort;
use crate::utils::Hex;
pub trait Target {
fn read_flash(&mut self, offset: u16, out: &mut [u8]) -> Result<(), Error>;
fn write_flash(&mut self, page: u16, data: &[u8; 128]) -> Result<(), Error>;
fn erase(&mut self) -> Result<(), Error>;
}
pub struct PhysicalTarget {
port: SerialPort
}
impl PhysicalTarget {
pub fn init(path: impl AsRef<Path>, baud: u32) -> Result<Self, Error> {
let mut port = SerialPort::open(path, baud)?;
port.set_read_timeout(Duration::from_millis(100))?;
port.set_dtr(false)?;
std::thread::sleep(Duration::from_millis(100));
for i in (0..5).rev() {
port.write(b"U")?;
let mut buf = [0u8; 1];
port.read(&mut buf)?;
if buf[0] == b'U' { break }
if i == 0 {
Err(Error::other(format!("Invalid sync response: 0x{:02X}", buf[0])))?
}
}
let mut new = Self { port };
let manufacturer = new.read_info(0x00, 0x00).unwrap();
let family = new.read_info(0x00, 0x01).unwrap();
let name = new.read_info(0x00, 0x02).unwrap();
let rev = new.read_info(0x00, 0x03).unwrap();
println!("Detected chip ID: {manufacturer:02X} {family:02X} {name:02X} {rev:02X} (whatever the fuck that means)");
Ok(new)
}
pub fn read_info(&mut self, category: u8, idx: u8) -> Result<u8, Error> {
self.send_command(0x05, 0, &[category, idx])?;
let mut resp_hex_digits = Vec::new();
loop {
let mut tmp = [0u8; 1];
self.port.read_exact(&mut tmp)?;
match tmp[0] {
x @ (b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F') => resp_hex_digits.push(x),
b'.' | b'\r' => {},
b'\n' => break,
b'P' => Err(Error::other("Security error"))?,
b'X' => Err(Error::other("Checksum error"))?,
x => Err(Error::other(format!("Invalid byte in response: {x:02X}")))?
}
}
let mut rx = [0u8; 1];
let resp = resp_hex_digits.as_chunks::<2>().0.iter()
.map(u8::hex_decode);
if resp.len() != rx.len() {
Err(Error::other(format!("Response size mismatch, got {} bytes, expected {} bytes", resp.len(), rx.len())))?
}
rx.iter_mut()
.zip(resp)
.for_each(|(dest, src)| *dest = src);
Ok(u8::from_le_bytes(rx))
}
fn send_command(&mut self, cmd_type: u8, offset: u16, tx: &[u8]) -> Result<(), Error> {
if tx.len() >= 256 {
Err(Error::other(format!("Payload too large: {} bytes", tx.len())))?
}
// Why, the ACTUAL FUCK, ARE THEY USING ASCII FOR TRANSFERING BINARIES?!
let mut ascii_buf = Vec::with_capacity(1 + 2 + 4 + 2 + tx.len() * 2 + 1); // Mark, Length, Offset, Type, Data, Checksum
ascii_buf.push(b':');
ascii_buf.extend_from_slice(&(tx.len() as u8).hex_encode());
ascii_buf.extend_from_slice(&offset.hex_encode());
ascii_buf.extend_from_slice(&cmd_type.hex_encode());
let mut checksum: u8 = 0;
checksum = checksum.wrapping_add(cmd_type);
checksum = checksum.wrapping_add(tx.len() as u8);
checksum = checksum.wrapping_add(offset as u8);
checksum = checksum.wrapping_add((offset >> 8) as u8);
for val in tx {
ascii_buf.extend_from_slice(&val.hex_encode());
checksum = checksum.wrapping_add(*val);
}
ascii_buf.extend_from_slice(&checksum.wrapping_neg().hex_encode());
self.port.write_all(&ascii_buf)?;
let mut echo = vec![0u8; ascii_buf.len()];
self.port.read_exact(&mut echo)?;
if echo != ascii_buf {
Err(Error::other("Echo response mismatch"))?
}
Ok(())
}
}
impl Target for PhysicalTarget {
fn read_flash(&mut self, offset: u16, out: &mut [u8]) -> Result<(), Error> {
self.send_command(0x04, 0, &[
offset.to_be_bytes().as_slice(),
offset.saturating_add(out.len() as u16 - 1).to_be_bytes().as_slice(),
&[0] // Flash
].concat())?;
let mut resp_hex_digits = Vec::with_capacity(out.len() * 2);
let mut ignore = false;
let mut finish_line = false;
loop {
let mut tmp = [0u8; 1];
self.port.read_exact(&mut tmp)?;
match tmp[0] {
x @ (b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F') => {
if !ignore {
resp_hex_digits.push(x);
if resp_hex_digits.len() >= resp_hex_digits.capacity() {
ignore = true;
finish_line = true;
}
}
},
b'=' => {
ignore = false;
}
b'.' | b'\r' => {},
b'\n' => {
ignore = true;
if finish_line { break }
},
b'P' => Err(Error::other("Security error"))?,
b'X' => Err(Error::other("Checksum error"))?,
x => Err(Error::other(format!("Invalid byte in response: {x:02X}")))?
}
}
let resp = resp_hex_digits.as_chunks::<2>().0.iter()
.map(u8::hex_decode);
out.iter_mut()
.zip(resp)
.for_each(|(dest, src)| *dest = src);
Ok(())
}
fn write_flash(&mut self, page: u16, data: &[u8; 128]) -> Result<(), Error> {
self.send_command(0x00, page * 128, data)?;
loop {
let mut tmp = [0u8; 1];
self.port.read_exact(&mut tmp)?;
match tmp[0] {
b'.' | b'\r' => {},
b'\n' => break,
b'P' => Err(Error::other("Security error"))?,
b'X' => Err(Error::other("Checksum error"))?,
x => Err(Error::other(format!("Invalid byte in response: {x:02X}")))?
}
}
Ok(())
}
fn erase(&mut self) -> Result<(), Error> {
self.send_command(0x03, 0, &[0x07])?;
let old_timeout = self.port.get_read_timeout()?;
self.port.set_read_timeout(Duration::from_secs(15))?;
loop {
let mut tmp = [0u8; 1];
self.port.read_exact(&mut tmp)?;
match tmp[0] {
b'.' | b'\r' => {},
b'\n' => break,
b'P' => Err(Error::other("Security error"))?,
b'X' => Err(Error::other("Checksum error"))?,
x => Err(Error::other(format!("Invalid byte in response: {x:02X}")))?
}
}
self.port.set_read_timeout(old_timeout)?;
Ok(())
}
}
impl Drop for PhysicalTarget {
fn drop(&mut self) {
let _ = self.port.set_dtr(true);
}
}