1use std::collections::HashMap;
18use std::io::Read;
19use std::path::Path;
20
21use monsoon_core::emulation::nes::Nes;
22
23#[derive(Debug, Clone)]
25pub struct MemoryInit {
26 pub address: u16,
28 pub values: Vec<u8>,
30}
31
32impl MemoryInit {
33 pub fn parse(s: &str) -> Result<Self, String> {
49 let (addr_str, values_str) = s.split_once('=').ok_or_else(|| {
50 format!(
51 "Invalid memory init format '{}'. Expected ADDR=VALUE or ADDR=V1,V2,...",
52 s
53 )
54 })?;
55
56 let address = parse_hex_u16(addr_str.trim())?;
57 let values = parse_values(values_str.trim())?;
58
59 if values.is_empty() {
60 return Err(format!("Memory init '{}' has no values", s));
61 }
62
63 Ok(Self {
64 address,
65 values,
66 })
67 }
68}
69
70fn parse_hex_u16(s: &str) -> Result<u16, String> {
72 let s = s
73 .strip_prefix("0x")
74 .or_else(|| s.strip_prefix("0X"))
75 .unwrap_or(s);
76 u16::from_str_radix(s, 16).map_err(|e| format!("Invalid hex address '{}': {}", s, e))
77}
78
79fn parse_values(s: &str) -> Result<Vec<u8>, String> {
81 s.split(',')
82 .map(|v| {
83 let v = v.trim();
84 let v = v
85 .strip_prefix("0x")
86 .or_else(|| v.strip_prefix("0X"))
87 .unwrap_or(v);
88 u8::from_str_radix(v, 16).map_err(|e| format!("Invalid hex value '{}': {}", v, e))
89 })
90 .collect()
91}
92
93#[derive(Debug, Clone, Default)]
95pub struct MemoryInitConfig {
96 pub cpu: HashMap<u16, Vec<u8>>,
98 pub ppu: HashMap<u16, Vec<u8>>,
100 pub oam: HashMap<u16, Vec<u8>>,
102}
103
104impl MemoryInitConfig {
105 pub fn load_from_file(path: &Path) -> Result<Self, String> {
109 let extension = path
110 .extension()
111 .and_then(|e| e.to_str())
112 .unwrap_or("")
113 .to_lowercase();
114
115 match extension.as_str() {
116 "json" => Self::load_json(path),
117 "toml" => Self::load_toml(path),
118 "bin" | "binary" => Self::load_binary(path),
119 _ => Err(format!(
120 "Unsupported init file format '{}'. Use .json, .toml, or .bin",
121 extension
122 )),
123 }
124 }
125
126 fn load_json(path: &Path) -> Result<Self, String> {
128 let content = std::fs::read_to_string(path)
129 .map_err(|e| format!("Failed to read init file '{}': {}", path.display(), e))?;
130
131 let json: serde_json::Value = serde_json::from_str(&content)
132 .map_err(|e| format!("Failed to parse JSON init file: {}", e))?;
133
134 Self::from_json_value(&json)
135 }
136
137 fn load_toml(path: &Path) -> Result<Self, String> {
139 let content = std::fs::read_to_string(path)
140 .map_err(|e| format!("Failed to read init file '{}': {}", path.display(), e))?;
141
142 let toml: toml::Value = content
143 .parse()
144 .map_err(|e| format!("Failed to parse TOML init file: {}", e))?;
145
146 Self::from_toml_value(&toml)
147 }
148
149 fn load_binary(path: &Path) -> Result<Self, String> {
151 let mut file = std::fs::File::open(path).map_err(|e| {
152 format!(
153 "Failed to open binary init file '{}': {}",
154 path.display(),
155 e
156 )
157 })?;
158
159 let mut data = Vec::new();
160 file.read_to_end(&mut data)
161 .map_err(|e| format!("Failed to read binary init file: {}", e))?;
162
163 let mut config = Self::default();
164 config.cpu.insert(0x0000, data);
165 Ok(config)
166 }
167
168 fn from_json_value(json: &serde_json::Value) -> Result<Self, String> {
170 let mut config = Self::default();
171
172 if let Some(cpu) = json.get("cpu").and_then(|v| v.as_object()) {
173 for (addr_str, values) in cpu {
174 let addr = parse_hex_u16(addr_str)?;
175 let vals = parse_json_array(values)?;
176 config.cpu.insert(addr, vals);
177 }
178 }
179
180 if let Some(ppu) = json.get("ppu").and_then(|v| v.as_object()) {
181 for (addr_str, values) in ppu {
182 let addr = parse_hex_u16(addr_str)?;
183 let vals = parse_json_array(values)?;
184 config.ppu.insert(addr, vals);
185 }
186 }
187
188 if let Some(oam) = json.get("oam").and_then(|v| v.as_object()) {
189 for (addr_str, values) in oam {
190 let addr = parse_hex_u16(addr_str)?;
191 let vals = parse_json_array(values)?;
192 config.oam.insert(addr, vals);
193 }
194 }
195
196 Ok(config)
197 }
198
199 fn from_toml_value(toml: &toml::Value) -> Result<Self, String> {
201 let mut config = Self::default();
202
203 if let Some(cpu) = toml.get("cpu").and_then(|v| v.as_table()) {
204 for (addr_str, values) in cpu {
205 let addr = parse_hex_u16(addr_str)?;
206 let vals = parse_toml_array(values)?;
207 config.cpu.insert(addr, vals);
208 }
209 }
210
211 if let Some(ppu) = toml.get("ppu").and_then(|v| v.as_table()) {
212 for (addr_str, values) in ppu {
213 let addr = parse_hex_u16(addr_str)?;
214 let vals = parse_toml_array(values)?;
215 config.ppu.insert(addr, vals);
216 }
217 }
218
219 if let Some(oam) = toml.get("oam").and_then(|v| v.as_table()) {
220 for (addr_str, values) in oam {
221 let addr = parse_hex_u16(addr_str)?;
222 let vals = parse_toml_array(values)?;
223 config.oam.insert(addr, vals);
224 }
225 }
226
227 Ok(config)
228 }
229}
230
231fn parse_json_array(value: &serde_json::Value) -> Result<Vec<u8>, String> {
233 match value {
234 serde_json::Value::Array(arr) => arr
235 .iter()
236 .map(|v| match v {
237 serde_json::Value::Number(n) => n
238 .as_u64()
239 .filter(|&n| n <= 255)
240 .map(|n| n as u8)
241 .ok_or_else(|| format!("Value {} out of range for u8", n)),
242 serde_json::Value::String(s) => {
243 let s = s
244 .strip_prefix("0x")
245 .or_else(|| s.strip_prefix("0X"))
246 .unwrap_or(s);
247 u8::from_str_radix(s, 16)
248 .map_err(|e| format!("Invalid hex value '{}': {}", s, e))
249 }
250 _ => Err("Expected number or hex string".to_string()),
251 })
252 .collect(),
253 serde_json::Value::Number(n) => {
254 let val = n
255 .as_u64()
256 .filter(|&n| n <= 255)
257 .map(|n| n as u8)
258 .ok_or_else(|| format!("Value {} out of range for u8", n))?;
259 Ok(vec![val])
260 }
261 _ => Err("Expected array or number".to_string()),
262 }
263}
264
265fn parse_toml_array(value: &toml::Value) -> Result<Vec<u8>, String> {
267 match value {
268 toml::Value::Array(arr) => arr
269 .iter()
270 .map(|v| match v {
271 toml::Value::Integer(n) => {
272 if *n >= 0 && *n <= 255 {
273 Ok(*n as u8)
274 } else {
275 Err(format!("Value {} out of range for u8", n))
276 }
277 }
278 toml::Value::String(s) => {
279 let s = s
280 .strip_prefix("0x")
281 .or_else(|| s.strip_prefix("0X"))
282 .unwrap_or(s);
283 u8::from_str_radix(s, 16)
284 .map_err(|e| format!("Invalid hex value '{}': {}", s, e))
285 }
286 _ => Err("Expected integer or hex string".to_string()),
287 })
288 .collect(),
289 toml::Value::Integer(n) => {
290 if *n >= 0 && *n <= 255 {
291 Ok(vec![*n as u8])
292 } else {
293 Err(format!("Value {} out of range for u8", n))
294 }
295 }
296 _ => Err("Expected array or integer".to_string()),
297 }
298}
299
300pub fn apply_memory_init(
302 emu: &mut Nes,
303 cpu_inits: &[MemoryInit],
304 ppu_inits: &[MemoryInit],
305 oam_inits: &[MemoryInit],
306) {
307 for init in cpu_inits {
309 for (offset, &value) in init.values.iter().enumerate() {
310 let addr = init.address.wrapping_add(offset as u16);
311 emu.cpu_mem_write(addr, value);
312 }
313 }
314
315 for init in ppu_inits {
317 for (offset, &value) in init.values.iter().enumerate() {
318 let addr = init.address.wrapping_add(offset as u16);
319 emu.ppu_mem_write(addr, value);
320 }
321 }
322
323 for init in oam_inits {
325 for (offset, &value) in init.values.iter().enumerate() {
326 let addr = (init.address.wrapping_add(offset as u16)) & 0xFF;
328 emu.oam_write(addr, value);
329 }
330 }
331}
332
333pub fn apply_memory_init_config(emu: &mut Nes, config: &MemoryInitConfig) {
335 for (&addr, values) in &config.cpu {
337 for (offset, &value) in values.iter().enumerate() {
338 let target_addr = addr.wrapping_add(offset as u16);
339 emu.cpu_mem_write(target_addr, value);
340 }
341 }
342
343 for (&addr, values) in &config.ppu {
345 for (offset, &value) in values.iter().enumerate() {
346 let target_addr = addr.wrapping_add(offset as u16);
347 emu.ppu_mem_write(target_addr, value);
348 }
349 }
350
351 for (&addr, values) in &config.oam {
353 for (offset, &value) in values.iter().enumerate() {
354 let target_addr = (addr.wrapping_add(offset as u16)) & 0xFF;
355 emu.oam_write(target_addr, value);
356 }
357 }
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363
364 #[test]
365 fn test_parse_single_value() {
366 let init = MemoryInit::parse("0x0050=0xFF").unwrap();
367 assert_eq!(init.address, 0x0050);
368 assert_eq!(init.values, vec![0xFF]);
369 }
370
371 #[test]
372 fn test_parse_multiple_values() {
373 let init = MemoryInit::parse("0x6000=0x01,0x02,0x03,0x04").unwrap();
374 assert_eq!(init.address, 0x6000);
375 assert_eq!(init.values, vec![0x01, 0x02, 0x03, 0x04]);
376 }
377
378 #[test]
379 fn test_parse_without_0x_prefix() {
380 let init = MemoryInit::parse("6000=FF").unwrap();
381 assert_eq!(init.address, 0x6000);
382 assert_eq!(init.values, vec![0xFF]);
383 }
384
385 #[test]
386 fn test_parse_with_spaces() {
387 let init = MemoryInit::parse("0x0050 = 0x01, 0x02, 0x03").unwrap();
388 assert_eq!(init.address, 0x0050);
389 assert_eq!(init.values, vec![0x01, 0x02, 0x03]);
390 }
391
392 #[test]
393 fn test_parse_invalid_format() {
394 assert!(MemoryInit::parse("0x0050").is_err());
395 assert!(MemoryInit::parse("invalid").is_err());
396 }
397
398 #[test]
399 fn test_parse_empty_values() {
400 assert!(MemoryInit::parse("0x0050=").is_err());
401 }
402}