flashy64_backend/
carts.rs

1use std::collections::HashMap;
2use std::fmt::{Display, Formatter};
3use std::str::FromStr;
4use crc::{Crc, CRC_32_ISO_HDLC};
5use log::debug;
6
7pub const CRC: Crc<u32> = Crc::<u32>::new(&CRC_32_ISO_HDLC);
8
9pub mod sixtyfourdrive;
10
11lazy_static! {
12    pub static ref ROMDB: HashMap<String, SaveType> = {
13        #[derive(Clone, Debug, PartialEq, Default)]
14        pub struct DbEntry {
15            pub md5: String,
16            pub savetype: SaveType
17        }
18        
19        let mut entries = HashMap::new();
20        
21        let mut lines = include_str!("romdb.ini").lines();
22        let mut entry = None;
23        while let Some(line) = lines.next() {
24            if line.starts_with("[") {
25                entry = Some(DbEntry::default());
26                
27                let mut tmp = entry.unwrap();
28                tmp.md5 = line.trim_matches('[').trim_matches(']').to_string();
29                
30                entry = Some(tmp);
31            } else if line.starts_with("SaveType=") {
32                let mut tmp = entry.unwrap();
33                tmp.savetype = match line.split_once('=').unwrap().1 {
34                    "None" => SaveType::Nothing,
35                    "SRAM" => SaveType::Sram256Kbit,
36                    "Eeprom 4KB" => SaveType::Eeprom4Kbit,
37                    "Eeprom 16KB" => SaveType::Eeprom16Kbit,
38                    "Flash RAM" => SaveType::FlashRam1Mbit,
39                    _ => SaveType::Unknown
40                };
41                
42                entry = Some(tmp);
43            } else if line.starts_with("GoodName=") {
44                let mut tmp = entry.unwrap();
45                let goodname = line.split_once('=').unwrap().1;
46                if goodname.contains("Dezaemon 3D") {
47                    tmp.savetype = SaveType::Sram768Kbit;
48                } else if goodname.contains("Pokemon Stadium 2") {
49                    tmp.savetype = SaveType::FlashRam1MbitStadium;
50                }
51                
52                entry = Some(tmp);
53            } else if line.is_empty() && entry.is_some() {
54                let tmp = entry.unwrap();
55                entries.insert(tmp.md5, tmp.savetype);
56                
57                entry = None;
58            }
59        }
60        
61        entries
62    };
63}
64
65
66
67#[derive(Copy, Clone, Debug, PartialEq)]
68pub enum Cic {
69    Auto,
70    Var6101,
71    Var6102,
72    Var7101,
73    Var7102,
74    VarX103,
75    VarX105,
76    VarX106,
77    Var5101,
78    Unknown,
79}
80impl Display for Cic {
81    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
82        use Cic::*;
83        
84        write!(f, "{}", match self {
85            Auto => "auto",
86            Var6101 => "6101",
87            Var6102 => "6102",
88            Var7101 => "7101",
89            Var7102 => "7102",
90            VarX103 => "x103",
91            VarX105 => "x105",
92            VarX106 => "x106",
93            Var5101 => "5101",
94            Unknown => "unknown",
95        })
96    }
97}
98impl FromStr for Cic {
99    type Err = String;
100    
101    fn from_str(s: &str) -> Result<Self, Self::Err> {
102        use Cic::*;
103        
104        Ok(match s.to_lowercase().as_str() {
105            "auto" => Auto,
106            "6101" => Var6101,
107            "6102" => Var6102,
108            "7101" => Var7101,
109            "7102" => Var7102,
110            "x103" => VarX103,
111            "x105" => VarX105,
112            "x106" => VarX106,
113            "5101" => Var5101,
114            
115            _ => return Err("Accepted values: auto, 6101, 6102, 7101, 7102, x103, x105, x106, or 5101".into())
116        })
117    }
118}
119
120impl Cic {
121    /// Attempts to detect which CIC variant matches the provided ROM.
122    /// 
123    /// If ROM does not include standard 0x40 byte header, or is smaller than 0x1000 bytes, this method
124    /// will fail.
125    pub fn from_rom(data: &[u8]) -> Cic {
126        if data.len() < 0x1000 { return Cic::Unknown }
127        
128        Self::from_ipl3(&data[0x40..0x1000])
129    }
130    
131    /// Attempts to detect which CIC variant matches the provided IPL3.
132    /// 
133    /// Data slice should NOT include the ROM header. Only data from rom offset 0x40 to 0x1000 (exclusive).
134    pub fn from_ipl3(data: &[u8]) -> Cic {
135        use Cic::*;
136        
137        let sum = CRC.checksum(data);
138        debug!("Calculated IPL3 CRC: {:#010X}", sum);
139        match sum {
140            0x6170A4A1 => Var6101,
141            0x90BB6CB5 => Var6102,
142            0x009E9EA3 => Var7102,
143            0x0B050EE0 => VarX103,
144            0x98BC2C86 => VarX105,
145            0xACC8580A => VarX106,
146            _ => Unknown
147        }
148    }
149}
150
151
152#[derive(Copy, Clone, Debug, PartialEq)]
153pub enum SaveType {
154    Auto,
155    Nothing,
156    Eeprom4Kbit,
157    Eeprom16Kbit,
158    Sram256Kbit,
159    FlashRam1Mbit,
160    Sram768Kbit,
161    FlashRam1MbitStadium,
162    Unknown,
163}
164impl Default for SaveType {
165    fn default() -> Self {
166        SaveType::Nothing
167    }
168}
169impl FromStr for SaveType {
170    type Err = String;
171
172    fn from_str(s: &str) -> Result<Self, Self::Err> {
173        use SaveType::*;
174        
175        Ok(match s.to_lowercase().as_str() {
176            "auto" => Auto,
177            "eeprom4kbit" => Eeprom4Kbit,
178            "eeprom16kbit" => Eeprom16Kbit,
179            "sram256kbit" => Sram256Kbit,
180            "flashram1mbit" => FlashRam1Mbit,
181            "sram768kbit" => Sram768Kbit,
182            "pokestadium2" => FlashRam1MbitStadium,
183            "none" | "nothing" => Nothing,
184            
185            _ => return Err("Accepted values: auto, eeprom4kbit, eeprom16kbit, sram256kbit, flashram1mbit, sram768kbit, pokestadium2, or none".into())
186        })
187    }
188}
189impl SaveType {
190    pub fn from_rom(data: &[u8]) -> SaveType {
191        let hash = md5::compute(data).0;
192        let mut hash_str = String::new();
193        for byte in hash {
194            hash_str.push_str(&format!("{:02X}", byte));
195        }
196        debug!("Calculated ROM Hash: {}", hash_str);
197        
198        match ROMDB.get(&hash_str) {
199            Some(savetype) => *savetype,
200            None => SaveType::Unknown,
201        }
202    }
203}