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}