flashy64_backend/carts/
sixtyfourdrive.rs

1use std::cmp::min;
2use std::time::Duration;
3use bytes::{BufMut, BytesMut};
4use libftd2xx::{BitMode, DeviceInfo, Ftdi, FtdiCommon};
5use log::debug;
6use crate::{Error, Flashcart, Result};
7use crate::carts::{Cic, SaveType};
8use crate::Error::CommunicationFailed;
9use crate::unfloader::{DataType, DebugResponse};
10
11#[derive(Clone, PartialEq, Debug)]
12pub enum Command {
13    LoadFromPc {
14        addr: u32,
15        bank_id_len: u32,
16        data: Vec<u8>,
17    },
18    DumpToPc {
19        addr: u32,
20        bank_id_len: u32,
21    },
22    TargetSideFifo(Vec<u8>),
23    SetSaveType(SaveType),
24    SetCicType(Cic),
25    SetCiExtended(u32),
26    VersionRequest,
27}
28impl Command {
29    pub fn id(&self) -> u8 {
30        use Command::*;
31        match self {
32            LoadFromPc { .. } => 0x20,
33            DumpToPc { .. } => 0x30,
34            TargetSideFifo(_) => 0x40,
35            SetSaveType(_) => 0x70,
36            SetCicType(_) => 0x72,
37            SetCiExtended(_) => 0x74,
38            VersionRequest => 0x80,
39        }
40    }
41    
42    pub fn encode_packet(&self) -> Vec<u8> {
43        let mut packet = BytesMut::from([self.id(), 0x43, 0x4D, 0x44].as_ref());
44        
45        use Command::*;
46        match self {
47            LoadFromPc { addr, bank_id_len, data } => {
48                packet.put_u32(*addr);
49                packet.put_u32(*bank_id_len);
50                packet.put_slice(data);
51            },
52            DumpToPc { addr, bank_id_len } => {
53                packet.put_u32(*addr);
54                packet.put_u32(*bank_id_len);
55            },
56            TargetSideFifo(data) => packet.put_slice(data),
57            SetSaveType(savetype) => packet.put_u32((savetype_index(*savetype).unwrap_or(0) as u32) & 0x0000000F),
58            SetCicType(cic) => packet.put_u32((cic_index(*cic).unwrap_or(1) & 0x7) as u32 | 0x80000000),
59            SetCiExtended(enable) => packet.put_u32(*enable),
60            VersionRequest => (),
61        }
62        
63        packet.to_vec()
64    }
65    
66    pub fn recv_length(&self) -> u32 {
67        use Command::*;
68        match self {
69            LoadFromPc { .. } => 0,
70            DumpToPc { bank_id_len, .. } => bank_id_len & 0x00FFFFFF,
71            TargetSideFifo(_) => 0,
72            SetSaveType(_) => 0,
73            SetCicType(_) => 0,
74            SetCiExtended(_) => 0,
75            VersionRequest => 8,
76        }
77    }
78    
79    /// Checks if provided `data` is a valid and complete command "footer" from the 64drive.
80    /// 
81    /// `data` must be exactly 4 bytes long.
82    /// 
83    /// # Example
84    /// If the DUMP_TO_PC command was previously sent, `data` should be the 4 bytes recieved _after_
85    /// the payload data requested from the cartridge: `[0x43, 0x4D, 0x50, 0x30]` (3 constant bytes,
86    /// followed by the command's ID number.)
87    pub fn complete_check<D: AsRef<[u8]>>(&self, data: D) -> Result<()> {
88        let expected = [0x43, 0x4D, 0x50, self.id()];
89        if expected == data.as_ref() {
90            Ok(())
91        } else {
92            Err(CommunicationFailed(format!("64Drive: complete packet mismatch: {:02X?} vs expected {expected:02X?}", data.as_ref())))
93        }
94    }
95}
96
97#[derive(Copy, Clone, Debug, PartialEq)]
98pub enum Segment {
99    Rom,
100    Sram256,
101    Sram768,
102    FlashRam,
103    Eeprom4,
104    Eeprom16,
105}
106impl Segment {
107    pub fn max_length(self, cart: &mut SixtyFourDrive) -> u32 {
108        use Segment::*;
109        match self {
110            Rom if !cart.is_hw1().unwrap_or(false) => 240 * 1024 * 1024,
111            Rom => 64 * 1024 * 1024,
112            Sram256 => 32 * 1024,
113            Sram768 => 96 * 1024,
114            FlashRam => 128 * 1024,
115            Eeprom4 => 512,
116            Eeprom16 => 2 * 1024,
117        }
118    }
119}
120
121#[derive(Copy, Clone, Debug, PartialEq)]
122pub enum Model {
123    HW1, HW2,
124}
125
126#[derive(Debug)]
127pub struct SixtyFourDrive {
128    device: Ftdi,
129}
130impl Flashcart for SixtyFourDrive {
131    fn upload_rom(&mut self, data: &[u8]) -> Result<()> {
132        self.upload(Segment::Rom, 0, data)
133    }
134
135    fn download_rom(&mut self, length: u32) -> Result<Vec<u8>> {
136        self.download(Segment::Rom, 0, length)
137    }
138
139    fn set_cic(&mut self, cic: Cic) -> Result<()> {
140        let cic_index = (cic_index(cic).unwrap_or(1) & 0x7) as u32 | 0x80000000;
141        
142        self.send_packet(Command::SetCicType(cic))?;
143        
144        debug!("CIC is set {:#010X}", cic_index);
145        Ok(())
146    }
147
148    fn set_savetype(&mut self, savetype: SaveType) -> Result<()> {
149        let savetype_index = (savetype_index(savetype).unwrap_or(0) as u32) & 0x0000000F;
150        
151        self.send_packet(Command::SetSaveType(savetype))?;
152        
153        debug!("SaveType is set {:#010X}", savetype_index);
154        Ok(())
155    }
156
157    fn recv_debug(&mut self) -> Result<DebugResponse> {
158        let buf = self.ftdi_read(4)?;
159        if buf != b"DMA@"{
160            debug!("buf mismatch: {buf:02X?}");
161            std::thread::sleep(Duration::from_millis(5));
162            self.device.purge_rx()?;
163            return Err(Error::CommunicationFailed(format!("64drive: debug packet mismatch: {buf:02X?} vs expected {:02X?}", b"DMA@")))
164        }
165        
166        let [kind, length @ ..]: [u8; 4] = self.ftdi_read(4)?.try_into().unwrap();
167        
168        let kind = DataType::from(kind);
169        let length = u32::from_be_bytes([0, length[0], length[1], length[2]]) as usize;
170        
171        let data = self.ftdi_read(length)?;
172        
173        let complete = self.ftdi_read(4)?;
174        if complete != b"CMPH" {
175            return Err(Error::CommunicationFailed(format!("64drive: complete packet mismatch: {complete:02X?} vs expected {:02X?}", b"CMPH")));
176        }
177        
178        debug!("Received {kind:?} data: {data:02X?}");
179        
180        Ok((kind, data))
181    }
182
183    fn send_debug(&mut self) -> Result<()> {
184        todo!()
185    }
186
187    fn info(&mut self) -> Result<DeviceInfo> {
188        self.device.device_info().map_err(|err| err.into())
189    }
190}
191impl SixtyFourDrive {
192    pub fn new(mut device: Ftdi) -> Result<Self> {
193        device.reset().unwrap_or_default();
194        device.set_timeouts(Duration::from_secs(10), Duration::from_secs(10))?;
195        
196        device.set_bit_mode(0xFF, BitMode::Reset)?;
197        device.set_bit_mode(0xFF, BitMode::SyncFifo)?;
198        
199        device.purge_all()?;
200        
201        Ok(Self {
202            device,
203        })
204    }
205    
206    fn is_hw1(&mut self) -> Result<bool> {
207        let info = self.info()?;
208        
209        Ok(match (info.vendor_id, info.product_id, info.description.as_str()) {
210            (0x0403, 0x6010, "64drive USB device A") => true,
211            _ => false
212        })
213    }
214    
215    pub fn upload(&mut self, segment: Segment, offset: u32, data: &[u8]) -> Result<()> {
216        const SIZE: u32 = 0x800000;
217        
218        let chunks = (data.len() as f32 / SIZE as f32).ceil() as u32;
219        let bank = bank_index(&segment, self.is_hw1()?, false); //TODO detect stadium 2
220        
221        let mut data_index = 0;
222        for i in 0..chunks {
223            let length = min(data.len() - data_index, SIZE as usize);
224            
225            let addr = offset + (i as u32 * SIZE);
226            let bank_id_len = bank | (length as u32 & 0x00FFFFFF);
227            
228            let cmd = Command::LoadFromPc {
229                addr,
230                bank_id_len,
231                data: data[data_index..(data_index + length)].to_vec(),
232            };
233            
234            data_index += length;
235            
236            debug!("Uploading data. offset: {addr:#010X}, banklen: {bank_id_len:#010X}");
237            self.send_packet(cmd)?;
238            debug!("Write complete.");
239        }
240        
241        debug!("Upload complete!");
242        Ok(())
243    }
244    
245    pub fn download(&mut self, segment: Segment, offset: u32, mut length: u32) -> Result<Vec<u8>> {
246        const SIZE: u32 = 0x20000;
247        
248        if length == 0 {
249            return Ok(vec![]);
250        }
251        
252        if length & 3 > 0 {
253            length = length + (4 - (length & 3));
254        }
255        length = min(length, segment.max_length(self));
256        
257        let chunks = (length as f32 / SIZE as f32).ceil() as u32;
258        let bank = bank_index(&segment, self.is_hw1()?, false); //TODO detect stadium 2
259        
260        let mut data = vec![];
261        let mut data_index = 0;
262        for i in 0..chunks {
263            let length = min(length - data_index, SIZE);
264            if (length & 0x00FFFFFF) < 4 {
265                break;
266            }
267            data_index += length;
268            
269            let addr = offset + (i * SIZE);
270            let bank_id_len = bank | (length & 0x00FFFFFF);
271            
272            let cmd = Command::DumpToPc {
273                addr,
274                bank_id_len,
275            };
276            
277            debug!("Downloading data. offset: {addr:#010X}, banklen: {bank_id_len:#010X}");
278            let buf = self.send_packet(cmd)?;
279            debug!("Read complete.");
280            
281            data.extend_from_slice(&buf);
282        }
283        
284        debug!("Download complete! {:.4} MiB", data.len() as f32 / (1024.0 * 1024.0));
285        Ok(data)
286    }
287    
288    fn send_packet(&mut self, cmd: Command) -> Result<Vec<u8>> {
289        self.ftdi_write(cmd.encode_packet())?;
290        
291        let response = self.ftdi_read(cmd.recv_length() as usize)?;
292        cmd.complete_check(self.ftdi_read(4)?)?;
293        
294        Ok(response)
295    }
296    
297    fn ftdi_read(&mut self, length: usize) -> Result<Vec<u8>> {
298        let mut buf = vec![0xFFu8; length];
299        if length == 0 {
300            return Ok(buf);
301        }
302        
303        self.device.read_all(&mut buf)?;
304        
305        Ok(buf)
306    }
307    
308    fn ftdi_write<D: AsRef<[u8]>>(&mut self, data: D) -> Result<()> {
309        self.device.write_all(data.as_ref()).map_err(|err| err.into())
310    }
311}
312
313
314
315fn bank_index(segment: &Segment, is_hw1: bool, is_stadium: bool) -> u32 {
316    use Segment::*;
317    
318    (match segment {
319        Rom => 1,
320        Sram256 => 2,
321        Sram768 => 3,
322        FlashRam if is_hw1 && is_stadium => 5,
323        FlashRam => 4,
324        Eeprom4 => 6,
325        Eeprom16 => 6,
326    } << 24)
327}
328
329/// Gets the 64drive index value associated with each CIC variant.
330/// 
331/// `Cic::Auto` and `Cic::Unknown` will return `None`.
332fn cic_index(cic: Cic) -> Option<u8> {
333    use Cic::*;
334    
335    match cic {
336        Var6101 => Some(0),
337        Var6102 => Some(1),
338        Var7101 => Some(2),
339        Var7102 => Some(3),
340        VarX103 => Some(4),
341        VarX105 => Some(5),
342        VarX106 => Some(6),
343        Var5101 => Some(7),
344        Auto | Unknown => None
345    }
346}
347
348fn savetype_index(savetype: SaveType) -> Option<u8> {
349    use SaveType::*;
350    
351    match savetype {
352        Nothing => Some(0),
353        Eeprom4Kbit => Some(1),
354        Eeprom16Kbit => Some(2),
355        Sram256Kbit => Some(3),
356        FlashRam1Mbit => Some(4),
357        Sram768Kbit => Some(5),
358        FlashRam1MbitStadium => Some(6),
359        Auto | Unknown => None,
360    }
361}