libeverdrive/lib.rs
1use std::io::Read;
2
3#[cfg(feature = "bitmap")]
4use bmp::{Image, Pixel};
5
6pub const ROM_BASE_ADDR: u32 = 0x10000000;
7pub const ROM_BASE_ADDR_EMU: u32 = 0x10200000;
8pub const KSEG0_BASE_ADDR: u32 = 0x80000000;
9
10pub enum EdCommand {
11 Test,
12 RamRead(u32, u32),
13 RomRead(u32, u32),
14 RomWrite(u32, u32),
15 RomFill(u32, u32, u32),
16 FpgaInit(u32),
17 AppStart(bool),
18}
19
20#[repr(u8)]
21pub enum EdSaveType {
22 Eeprom4k = 0x10,
23 Eeprom16k = 0x20,
24 Sram = 0x30,
25 Sram768k = 0x40,
26 FlashRam = 0x50,
27 Sram128k = 0x60,
28}
29
30#[repr(u8)]
31pub enum EdRtcRegionType {
32 Rtc = 0x01,
33 NoRegion = 0x02,
34 All = 0x03,
35}
36
37impl EdCommand {
38 fn to_bytes(&self) -> std::io::Result<[u8; 16]> {
39 const CMD_PREFIX: &[u8; 3] = b"cmd";
40
41 let (cmd, addr, size, arg) = match self {
42 EdCommand::Test => (b't', 0u32, 0u32, 0u32),
43 EdCommand::RamRead(addr, size) => (b'r', *addr, *size, 0),
44 EdCommand::RomRead(addr, size) => (b'R', *addr, *size, 0),
45 EdCommand::RomWrite(addr, size) => (b'W', *addr, *size, 0),
46 EdCommand::RomFill(addr, size, arg) => (b'c', *addr, *size, *arg),
47 EdCommand::FpgaInit(size) => (b'f', 0, *size, 0),
48 EdCommand::AppStart(save_path) => (b's', 0, 0, *save_path as u32),
49 };
50
51 let size = if size % 512 != 0 {
52 return Err(std::io::Error::new(
53 std::io::ErrorKind::InvalidInput,
54 "Size must be a multiple of 512",
55 ));
56 } else {
57 size / 512
58 };
59
60 let mut buf = [0; 16];
61 buf[0..3].copy_from_slice(CMD_PREFIX);
62
63 buf[3] = cmd;
64
65 buf[4..8].copy_from_slice(&addr.to_be_bytes());
66 buf[8..12].copy_from_slice(&size.to_be_bytes());
67 buf[12..16].copy_from_slice(&arg.to_be_bytes());
68
69 Ok(buf)
70 }
71}
72
73#[derive(Debug)]
74pub struct Everdrive {
75 port: Box<dyn serialport::SerialPort>,
76}
77
78impl Everdrive {
79 /// Creates a new Everdrive instance and returns an error if the device is not found
80 /// or if there is an error opening the USB serial port.
81 ///
82 /// `timeout` is configured for the serial port for future reads and writes
83 ///
84 /// # Examples
85 ///
86 /// ```no_run
87 /// use libeverdrive::Everdrive;
88 ///
89 /// let mut ed = match Everdrive::new(std::time::Duration::from_millis(100)) {
90 /// Ok(ed) => ed,
91 /// Err(err) => {
92 /// eprintln!("Failed to find Everdrive: {:?}", err);
93 /// return;
94 /// }
95 /// };
96 /// ```
97 pub fn new(timeout: std::time::Duration) -> std::io::Result<Self> {
98 let ports = serialport::available_ports().expect("No available USB ports found");
99
100 let usb_port = match ports.iter().find(|p| match &p.port_type {
101 serialport::SerialPortType::UsbPort(info) => info.vid == 0x0403 && info.pid == 0x6001,
102 _ => false,
103 }) {
104 Some(port) => port,
105 None => {
106 return Err(std::io::Error::new(
107 std::io::ErrorKind::NotFound,
108 "Everdrive USB device not found",
109 ));
110 }
111 };
112
113 let mut port = match serialport::new(&usb_port.port_name, 115_200).open() {
114 Ok(port) => port,
115 Err(err) => {
116 return Err(err.into());
117 }
118 };
119
120 match port.set_timeout(timeout) {
121 Ok(_) => (),
122 Err(err) => {
123 return Err(err.into());
124 }
125 };
126
127 let mut ed = Self { port };
128
129 ed.status()?;
130
131 Ok(ed)
132 }
133
134 /// Tests a handshake with the Everdrive device and returns an error if the handshake fails.
135 ///
136 /// # Examples
137 ///
138 /// ```no_run
139 /// use libeverdrive::Everdrive;
140 ///
141 /// let mut ed = Everdrive::new(std::time::Duration::from_millis(100)).unwrap();
142 ///
143 /// match ed.status() {
144 /// Ok(_) => println!("ED status OK"),
145 /// Err(err) => eprintln!("ED status error: {:?}", err),
146 /// }
147 /// ```
148 pub fn status(&mut self) -> std::io::Result<()> {
149 self.tx(EdCommand::Test)?;
150 self.rx(b'r')?;
151 Ok(())
152 }
153
154 /// Takes a screenshot from the N64 device. The return value is the n64 framebuffer
155 /// and the screen with and height.
156 ///
157 /// Optional `screen_size_wh` can be used to specify screen width and height. If
158 /// None, an attempt is made to determine the screen size automatically via
159 /// the console video interface.
160 ///
161 /// # Examples
162 ///
163 /// ```no_run
164 /// use libeverdrive::Everdrive;
165 ///
166 /// let mut ed = Everdrive::new(std::time::Duration::from_millis(100)).unwrap();
167 ///
168 /// let framebuffer = ed.screenshot(None).unwrap();
169 /// println!("{:?}", framebuffer);
170 /// ```
171 pub fn screenshot(
172 &mut self,
173 screen_size_wh: Option<(u32, u32)>,
174 ) -> std::io::Result<(Vec<u8>, u32, u32)> {
175 let mut buf = [0; 512];
176 self.ram_read(0xA4400004, &mut buf)?;
177
178 let fb_ptr = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
179
180 let (screen_width, screen_height) = match screen_size_wh {
181 Some((w, h)) => (w, h),
182 None => {
183 let screen_width = u32::from_be_bytes([buf[4], buf[5], buf[6], buf[7]]);
184 match screen_width {
185 320 => (screen_width, 240),
186 640 => (screen_width, 480),
187 _ /* Default height to 240 */ => (screen_width, 240),
188 }
189 }
190 };
191
192 let mut buf = vec![0; (screen_width * screen_height * 2) as usize];
193 self.ram_read(KSEG0_BASE_ADDR + fb_ptr, &mut buf)?;
194 Ok((buf, screen_width, screen_height))
195 }
196
197 #[cfg(feature = "bitmap")]
198 /// Takes a screenshot and converts it to a BMP image buffer.
199 ///
200 /// Optional `screen_size_wh` can be used to specify screen width and height. If
201 /// None, an attempt is made to determine the screen size automatically via
202 /// the console video interface.
203 ///
204 /// # Examples
205 ///
206 /// ```no_run
207 /// use libeverdrive::Everdrive;
208 ///
209 /// let mut ed = Everdrive::new(std::time::Duration::from_millis(100)).unwrap();
210 ///
211 /// let bmp_buf = ed.screenshot_bmp(None).unwrap();
212 ///
213 /// std::fs::write("screenshot.bmp", bmp_buf).unwrap();
214 /// ```
215 pub fn screenshot_bmp(
216 &mut self,
217 screen_size_wh: Option<(u32, u32)>,
218 ) -> std::io::Result<Vec<u8>> {
219 let (buf, width, height) = self.screenshot(screen_size_wh)?;
220 Self::n64_fb_to_bitmap(&buf, width, height)
221 }
222
223 /// Fills a region of the rom with a value.
224 ///
225 /// # Examples
226 ///
227 /// ```no_run
228 /// use libeverdrive::Everdrive;
229 ///
230 /// let mut ed = Everdrive::new(std::time::Duration::from_millis(100)).unwrap();
231 ///
232 /// ed.rom_fill(0x10000000, 0x1000, 0xFF).unwrap();
233 /// ```
234 pub fn rom_fill(&mut self, addr: u32, size: u32, val: u32) -> std::io::Result<()> {
235 self.tx(EdCommand::RomFill(addr, size, val))
236 }
237
238 /// Reads a region of the rom into a buffer. Buffer size must be divisible by 512.
239 ///
240 /// # Examples
241 ///
242 /// ```no_run
243 /// use libeverdrive::Everdrive;
244 ///
245 /// let mut ed = Everdrive::new(std::time::Duration::from_millis(100)).unwrap();
246 ///
247 /// let mut buf = vec![0; 512];
248 /// ed.rom_read(0x10000000, &mut buf).unwrap();
249 ///
250 /// println!("{:?}", buf);
251 /// ```
252 pub fn rom_read(&mut self, addr: u32, buf: &mut [u8]) -> std::io::Result<()> {
253 self.tx(EdCommand::RomRead(addr, buf.len() as u32))?;
254 self.read(buf)
255 }
256
257 /// Allocates a new buffer of size `S` and reads a region of the rom into it.
258 /// Size `S` must be divisible by 512.
259 ///
260 /// # Examples
261 ///
262 /// ```no_run
263 /// use libeverdrive::Everdrive;
264 ///
265 /// let mut ed = Everdrive::new(std::time::Duration::from_millis(100)).unwrap();
266 ///
267 /// let buf = ed.rom_read_size::<512>(0x10000000).unwrap();
268 /// println!("{:?}", buf);
269 /// ```
270 pub fn rom_read_size<const S: usize>(&mut self, addr: u32) -> std::io::Result<[u8; S]> {
271 let mut buf = [0; S];
272 self.rom_read(addr, &mut buf)?;
273 Ok(buf)
274 }
275
276 /// Reads a region of the ram into a buffer. Buffer size must be divisible by 512.
277 ///
278 /// # Examples
279 ///
280 /// ```no_run
281 /// use libeverdrive::Everdrive;
282 ///
283 /// let mut ed = Everdrive::new(std::time::Duration::from_millis(100)).unwrap();
284 ///
285 /// let mut buf = vec![0; 512];
286 /// ed.ram_read(0x10000000, &mut buf).unwrap();
287 ///
288 /// println!("{:?}", buf);
289 /// ```
290 pub fn ram_read(&mut self, addr: u32, buf: &mut [u8]) -> std::io::Result<()> {
291 self.tx(EdCommand::RamRead(addr, buf.len() as u32))?;
292 self.read(buf)
293 }
294
295 /// Allocates a new buffer of size `S` and reads a region of the ram into it.
296 /// Size `S` must be divisible by 512.
297 ///
298 /// # Examples
299 ///
300 /// ```no_run
301 /// use libeverdrive::Everdrive;
302 ///
303 /// let mut ed = Everdrive::new(std::time::Duration::from_millis(100)).unwrap();
304 ///
305 /// let buf = ed.ram_read_size::<512>(0x10000000).unwrap();
306 /// println!("{:?}", buf);
307 /// ```
308 pub fn ram_read_size<const S: usize>(&mut self, addr: u32) -> std::io::Result<[u8; S]> {
309 let mut buf = [0; S];
310 self.ram_read(addr, &mut buf)?;
311 Ok(buf)
312 }
313
314 /// Writes a region of the rom with data. Data size must be divisible by 512.
315 ///
316 /// # Examples
317 ///
318 /// ```no_run
319 /// use libeverdrive::Everdrive;
320 ///
321 /// let mut ed = Everdrive::new(std::time::Duration::from_millis(100)).unwrap();
322 ///
323 /// let data = vec![0; 512];
324 /// ed.rom_write(0x10000000, &data).unwrap();
325 /// ```
326 pub fn rom_write(&mut self, addr: u32, data: &[u8]) -> std::io::Result<()> {
327 self.tx(EdCommand::RomWrite(addr, data.len() as u32))?;
328 self.write(data)
329 }
330
331 /// Inits fpga with a RBF file. Data size must be divisible by 512.
332 ///
333 /// # Examples
334 ///
335 /// ```no_run
336 /// use libeverdrive::Everdrive;
337 ///
338 /// let mut ed = Everdrive::new(std::time::Duration::from_millis(100)).unwrap();
339 ///
340 /// let fpga_data = vec![0; 0x100000];
341 /// ed.fpga_init(0x100000, &fpga_data).unwrap();
342 /// ```
343 pub fn fpga_init(&mut self, size: u32, data: &[u8]) -> std::io::Result<()> {
344 self.tx(EdCommand::FpgaInit(size))?;
345 self.write(data)?;
346
347 // @todo - Check that the second response byte is 0
348 // non-zero are error codes
349 self.rx(b'r')
350 }
351
352 /// Starts a rom file. The rom file must be loaded first using `load_rom`
353 ///
354 /// Optional `file_name` is used for specifying save file on the SD card
355 ///
356 /// # Examples
357 ///
358 /// ```no_run
359 /// use libeverdrive::Everdrive;
360 /// use std::fs;
361 ///
362 /// let mut ed = Everdrive::new(std::time::Duration::from_millis(100)).unwrap();
363 ///
364 /// let rom_data = fs::read("your_rom.z64").unwrap();
365 ///
366 /// ed.load_rom(rom_data, None, None, None).unwrap();
367 /// ed.app_start(Some("your_rom.z64")).unwrap();
368 /// ```
369 pub fn app_start(&mut self, file_name: Option<&str>) -> std::io::Result<()> {
370 let file_name_buf = if let Some(file_name) = file_name {
371 if file_name.len() >= 256 {
372 return Err(std::io::Error::new(
373 std::io::ErrorKind::InvalidInput,
374 "File name is too long",
375 ));
376 }
377
378 let mut buf = [0; 256];
379 buf[0..file_name.len()].copy_from_slice(file_name.as_bytes());
380
381 Some(buf)
382 } else {
383 None
384 };
385
386 self.tx(EdCommand::AppStart(file_name_buf.is_some()))?;
387
388 if let Some(buf) = file_name_buf {
389 self.write(&buf)?;
390 }
391
392 Ok(())
393 }
394
395 /// Loads a rom file into the specified base address
396 ///
397 /// `rom_file` should contain the rom file as data. The base address is optional and defaults to ROM_BASE_ADDR.
398 /// `save_type` and `rtc_region_type` are optional and are used to specify the save type and RTC region type respectively.
399 /// Additional checks are done to determine the endianness of the rom file and swap bytes accordingly, and
400 /// to set the save type and RTC region type in the rom file header.
401 ///
402 /// # Examples
403 ///
404 /// ```no_run
405 /// use libeverdrive::Everdrive;
406 /// use std::fs;
407 ///
408 /// let mut ed = Everdrive::new(std::time::Duration::from_millis(100)).unwrap();
409 ///
410 /// let rom_data = fs::read("your_rom.z64").unwrap();
411 ///
412 /// ed.load_rom(rom_data, None, None, None).unwrap();
413 /// ed.app_start(Some("your_rom.z64")).unwrap();
414 /// ```
415 pub fn load_rom(
416 &mut self,
417 rom_file: Vec<u8>,
418 base_address: Option<u32>,
419 save_type: Option<EdSaveType>,
420 rtc_region_type: Option<EdRtcRegionType>,
421 ) -> std::io::Result<()> {
422 // reference https://github.com/krikzz/ED64/blob/master/usb64/usb64/CommandProcessor.cs#L125
423 let mut rom_file = rom_file.clone();
424
425 let header_word_be =
426 u32::from_be_bytes([rom_file[0], rom_file[1], rom_file[2], rom_file[3]]);
427
428 let mut base_address = base_address.unwrap_or(ROM_BASE_ADDR);
429
430 match header_word_be {
431 0x80371240 /* Big-endian native */ => { /* No need to do anything */}
432 0x37804012 /* Byte-swapped, swap every 2 bytes */=> {
433 for i in (0..rom_file.len()).step_by(2) {
434 rom_file.swap(i, i + 1);
435 }
436 }
437 0x40123780 /* Little-endian, swap every 4 bytes */ => {
438 for i in (0..rom_file.len()).step_by(4) {
439 rom_file.swap(i, i + 3);
440 rom_file.swap(i + 1, i + 2);
441 }
442 }
443 _ => {
444 // Don't swap and assume emulator rom
445 base_address = ROM_BASE_ADDR_EMU;
446 }
447 };
448
449 if let Some(st) = save_type {
450 let region_type = rtc_region_type.map(|val| val as u8).unwrap_or(0);
451 rom_file[0x3C] = 0x45;
452 rom_file[0x3D] = 0x44;
453 rom_file[0x3F] = ((st as u8) << 4) | region_type;
454 }
455
456 self.load_rom_force(rom_file, base_address)?;
457
458 Ok(())
459 }
460
461 /// Loads a rom file into the specified base address. But does not do checks for
462 /// endianness or base_address.
463 pub fn load_rom_force(&mut self, data: Vec<u8>, base_address: u32) -> std::io::Result<()> {
464 const CRC_AREA_SIZE: usize = 0x101000;
465
466 if data.len() < CRC_AREA_SIZE {
467 let val = if Self::is_ed_bootloader(&data) {
468 0xFFFFFFFF
469 } else {
470 0
471 };
472
473 self.rom_fill(base_address, CRC_AREA_SIZE as u32, val)?;
474 }
475
476 self.tx(EdCommand::RomWrite(base_address, data.len() as u32))?;
477 self.write(&data)
478 }
479
480 /// Transmits an EdCommand to the Everdrive device
481 /// and returns an error if sending the command fails.
482 pub fn tx(&mut self, cmd: EdCommand) -> std::io::Result<()> {
483 self.port.write_all(&cmd.to_bytes()?)
484 }
485
486 /// Receives a response from the Everdrive device
487 /// and returns an error if reading from the device fails
488 /// or if the response is invalid.
489 pub fn rx(&mut self, resp: u8) -> std::io::Result<()> {
490 let mut recv_buf = vec![0; 16];
491
492 match self.read(&mut recv_buf) {
493 Ok(_) => {
494 if recv_buf[0..4] == [b'c', b'm', b'd', resp] {
495 Ok(())
496 } else {
497 Err(std::io::Error::new(
498 std::io::ErrorKind::InvalidData,
499 "Invalid response from Everdrive device",
500 ))
501 }
502 }
503 Err(err) => return Err(err),
504 }
505 }
506
507 /// Directly write a buffer to the serial port
508 pub fn write(&mut self, buf: &[u8]) -> std::io::Result<()> {
509 self.port.write_all(buf)
510 }
511
512 /// Directly read a buffer from the serial port
513 pub fn read(&mut self, buf: &mut [u8]) -> std::io::Result<()> {
514 self.port.read_exact(buf)
515 }
516
517 fn is_ed_bootloader(data: &[u8]) -> bool {
518 const ED_HEADER: &[u8] = b"EverDrive bootloader";
519 ED_HEADER
520 .iter()
521 .zip(data.iter().skip(0x20))
522 .all(|(a, b)| a == b)
523 }
524
525 #[cfg(feature = "bitmap")]
526 fn n64_fb_to_bitmap(fb: &[u8], width: u32, height: u32) -> std::io::Result<Vec<u8>> {
527 let mut img = Image::new(width, height);
528
529 for y in 0..height {
530 for x in 0..width {
531 let b0 = fb[(y * width + x) as usize * 2];
532 let b1 = fb[(y * width + x) as usize * 2 + 1];
533
534 let r = b0 & 0xF8;
535 let g = ((b0 & 0x07) << 5) | ((b1 & 0xC0) >> 3);
536 let b = (b1 & 0x3E) << 2;
537
538 img.set_pixel(x, y, Pixel::new(r, g, b));
539 }
540 }
541
542 let mut img_buf: Vec<u8> = Vec::new();
543 img.to_writer(&mut img_buf)?;
544
545 Ok(img_buf)
546 }
547}