1pub mod color;
49pub mod font;
50pub mod input;
51pub mod origin;
52pub mod platform;
53pub mod program;
54pub mod quirk;
55pub mod rom;
56pub mod rotation;
57
58use program::Program;
59use rom::Rom;
60use sha1::{Digest, Sha1};
61use std::collections::HashMap;
62
63#[cfg(feature = "extra-data")]
64use platform::PlatformDetails;
65
66#[cfg(feature = "extra-data")]
67use quirk::QuirkDetails;
68
69#[derive(Clone, Debug, Default)]
71pub struct Database {
72 pub programs: Vec<Program>,
74
75 pub hashes: HashMap<String, usize>,
77
78 #[cfg(feature = "extra-data")]
80 pub platforms: Vec<PlatformDetails>,
81
82 #[cfg(feature = "extra-data")]
84 pub quirks: Vec<QuirkDetails>,
85}
86
87impl Database {
88 pub fn new() -> Self {
90 let programs = {
93 let json = include_str!("../chip-8-database/database/programs.json");
94
95 serde_json::from_str(json)
96 .expect("programs.json is hardcoded and should never be in an invalid state")
97 };
98
99 let hashes = {
100 let json = include_str!("../chip-8-database/database/sha1-hashes.json");
101
102 serde_json::from_str(json)
103 .expect("sha1-hashes.json is hardcoded and should never be in an invalid state")
104 };
105
106 #[cfg(feature = "extra-data")]
107 let platforms = {
108 let json = include_str!("../chip-8-database/database/platforms.json");
109
110 serde_json::from_str(json)
111 .expect("platforms.json is hardcoded and should never be in an invalid state")
112 };
113
114 #[cfg(feature = "extra-data")]
115 let quirks = {
116 let json = include_str!("../chip-8-database/database/quirks.json");
117
118 serde_json::from_str(json)
119 .expect("quirks.json is hardcoded and should never be in an invalid state")
120 };
121
122 Database {
123 programs,
124 hashes,
125
126 #[cfg(feature = "extra-data")]
127 platforms,
128
129 #[cfg(feature = "extra-data")]
130 quirks,
131 }
132 }
133
134 pub fn get_metadata(&self, rom: &[u8]) -> Metadata {
136 let mut hasher = Sha1::new();
137 hasher.update(rom);
138 let hash = hasher.finalize();
139 let mut buf = [0u8; 40];
140 let hash = base16ct::lower::encode_str(&hash, &mut buf).unwrap();
141
142 self.get_metadata_from_hash(hash)
143 }
144
145 pub fn get_metadata_from_hash(&self, hash: &str) -> Metadata {
147 let hash = hash.to_owned();
148 let program = self.hashes.get(&hash).map(|i| self.programs[*i].clone());
149 let rom = program
150 .as_ref()
151 .and_then(|prog| prog.roms.get(&hash).cloned());
152
153 Metadata { hash, program, rom }
154 }
155}
156
157#[derive(Clone, Debug, Default)]
159pub struct Metadata {
160 pub hash: String,
162
163 pub program: Option<Program>,
165
166 pub rom: Option<Rom>,
168}
169
170#[cfg(test)]
171mod test {
172 use super::*;
173
174 mod program {
175 use super::*;
176
177 use crate::{
178 font::FontStyle,
179 input::{Keymap, TouchInputMode},
180 origin::OriginType,
181 platform::Platform,
182 quirk::Quirk,
183 rotation::ScreenRotation,
184 };
185 use std::io::Result;
186
187 #[test]
188 fn deserialize_full() -> Result<()> {
189 let input = r##"{
190 "title": "Test Program",
191 "origin": {
192 "type": "manual",
193 "reference": "What's this supposed to be?"
194 },
195 "description": "A description of the program",
196 "release": "2023-06-24",
197 "copyright": "Probably copyrighted or something",
198 "license": "MIT",
199 "authors": ["Someone"],
200 "images": ["https://example.com/chip8/test-program.png"],
201 "urls": ["https://example.com/chip8/test-program.html"],
202 "roms": {
203 "0123456789abcdef0123456789abcdef01234567": {
204 "file": "test-program.ch8",
205 "embeddedTitle": "Test Program Embedded",
206 "description": "The test program to test all programs",
207 "release": "2023-06-24",
208 "platforms": ["originalChip8"],
209 "quirkyPlatforms": {
210 "originalChip8": {
211 "shift": true,
212 "memoryIncrementByX": false,
213 "memoryLeaveIUnchanged": true,
214 "wrap": false,
215 "jump": true,
216 "vblank": false,
217 "logic": true
218 }
219 },
220 "authors": ["Someone Else"],
221 "images": ["https://example.com/chip8/test-program-detail.png"],
222 "urls": ["https://example.com/chip8/test-program.ch8"],
223 "tickrate": 10,
224 "startAddress": 512,
225 "screenRotation": 0,
226 "keys": {
227 "up": 0,
228 "down": 1,
229 "left": 2,
230 "right": 3,
231 "a": 4,
232 "b": 5,
233 "player2Up": 16,
234 "player2Down": 17,
235 "player2Left": 18,
236 "player2Right": 19,
237 "player2A": 20,
238 "player2B": 21
239 },
240 "touchInputMode": "none",
241 "fontStyle": "vip",
242 "colors": {
243 "pixels": ["#000000", "#ff0000", "#00ff00", "#0000ff"],
244 "buzzer": "#cccccc",
245 "silence": "#555555"
246 }
247 }
248 }
249 }"##;
250
251 let program: Program = serde_json::from_str(input)?;
252
253 assert_eq!(program.title, "Test Program");
254 assert_eq!(
255 &OriginType::Manual,
256 program
257 .origin
258 .as_ref()
259 .unwrap()
260 .origin_type
261 .as_ref()
262 .unwrap()
263 );
264 assert_eq!(
265 "What's this supposed to be?",
266 program.origin.unwrap().reference.unwrap()
267 );
268 assert_eq!(
269 "A description of the program",
270 &program.description.unwrap()
271 );
272 assert_eq!("2023-06-24", &program.release.unwrap());
273 assert_eq!(
274 "Probably copyrighted or something",
275 &program.copyright.unwrap()
276 );
277 assert_eq!("MIT", &program.license.unwrap());
278 assert_eq!(vec!["Someone".to_owned()], program.authors.unwrap());
279 assert_eq!(
280 vec!["https://example.com/chip8/test-program.png"],
281 program.images.unwrap()
282 );
283 assert_eq!(
284 vec!["https://example.com/chip8/test-program.html"],
285 program.urls.unwrap()
286 );
287
288 let rom = program.roms["0123456789abcdef0123456789abcdef01234567"].clone();
289
290 assert_eq!("test-program.ch8", &rom.file_name.unwrap());
291 assert_eq!("Test Program Embedded", &rom.embedded_title.unwrap());
292 assert_eq!(
293 "The test program to test all programs",
294 rom.description.unwrap()
295 );
296 assert_eq!("2023-06-24", rom.release.unwrap());
297 assert_eq!(vec![Platform::OriginalChip8], rom.platforms);
298
299 let quirks = rom.quirky_platforms.unwrap()[&Platform::OriginalChip8].clone();
300
301 assert!(quirks[&Quirk::Shift]);
302 assert!(!quirks[&Quirk::MemoryIncrementByX]);
303 assert!(quirks[&Quirk::MemoryLeaveIUnchanged]);
304 assert!(!quirks[&Quirk::Wrap]);
305 assert!(quirks[&Quirk::Jump]);
306 assert!(!quirks[&Quirk::VBlank]);
307 assert!(quirks[&Quirk::Logic]);
308
309 assert_eq!(vec!["Someone Else"], rom.authors.unwrap());
310 assert_eq!(
311 vec!["https://example.com/chip8/test-program-detail.png"],
312 rom.images.unwrap()
313 );
314 assert_eq!(
315 vec!["https://example.com/chip8/test-program.ch8"],
316 rom.urls.unwrap()
317 );
318 assert_eq!(10, rom.tickrate.unwrap());
319 assert_eq!(0x200, rom.start_address.unwrap());
320 assert_eq!(ScreenRotation::Landscape, rom.screen_rotation.unwrap());
321
322 let keys = rom.keys.unwrap();
323
324 assert_eq!(0x00, keys[&Keymap::P1Up]);
325 assert_eq!(0x01, keys[&Keymap::P1Down]);
326 assert_eq!(0x02, keys[&Keymap::P1Left]);
327 assert_eq!(0x03, keys[&Keymap::P1Right]);
328 assert_eq!(0x04, keys[&Keymap::P1A]);
329 assert_eq!(0x05, keys[&Keymap::P1B]);
330
331 assert_eq!(0x10, keys[&Keymap::P2Up]);
332 assert_eq!(0x11, keys[&Keymap::P2Down]);
333 assert_eq!(0x12, keys[&Keymap::P2Left]);
334 assert_eq!(0x13, keys[&Keymap::P2Right]);
335 assert_eq!(0x14, keys[&Keymap::P2A]);
336 assert_eq!(0x15, keys[&Keymap::P2B]);
337
338 assert_eq!(TouchInputMode::None, rom.touch_input_mode.unwrap());
339 assert_eq!(FontStyle::VIP, rom.font_style.unwrap());
340
341 let colors = rom.colors.unwrap();
342
343 assert_eq!(
344 vec!["#000000", "#ff0000", "#00ff00", "#0000ff"],
345 colors.pixels.unwrap()
346 );
347 assert_eq!("#cccccc", colors.buzzer.unwrap());
348 assert_eq!("#555555", colors.silence.unwrap());
349
350 Ok(())
351 }
352
353 #[test]
354 fn deserialize_minimal() -> Result<()> {
355 let input = r##"{
356 "title": "Minimal",
357 "roms": {
358 "0123456789abcdef0123456789abcdef01234567": {
359 "platforms": ["originalChip8"]
360 }
361 }
362 }"##;
363
364 let program: Program = serde_json::from_str(input)?;
365
366 assert_eq!("Minimal", program.title);
367
368 let rom = program.roms["0123456789abcdef0123456789abcdef01234567"].clone();
369
370 assert_eq!(vec![Platform::OriginalChip8], rom.platforms);
371
372 Ok(())
373 }
374 }
375
376 #[cfg(feature = "extra-data")]
377 mod platform {
378 use crate::{platform::Platform, quirk::Quirk};
379
380 use super::*;
381
382 use std::io::Result;
383
384 #[test]
385 fn deserialize_minimal() -> Result<()> {
386 let input = r##"{
387 "id": "originalChip8",
388 "name": "Minimal Platform Example",
389 "displayResolutions": ["64x32"],
390 "defaultTickrate": 15,
391 "quirks": {
392 "shift": false,
393 "memoryIncrementByX": false,
394 "memoryLeaveIUnchanged": false,
395 "wrap": false,
396 "jump": false,
397 "vblank": true,
398 "logic": true
399 }
400 }"##;
401
402 let platform: PlatformDetails = serde_json::from_str(input)?;
403
404 assert_eq!(Platform::OriginalChip8, platform.id);
405 assert_eq!("Minimal Platform Example", platform.name);
406 assert_eq!(vec!["64x32"], platform.display_resolutions);
407 assert_eq!(15, platform.default_tickrate);
408
409 assert!(!platform.quirks[&Quirk::Shift]);
410 assert!(!platform.quirks[&Quirk::MemoryIncrementByX]);
411 assert!(!platform.quirks[&Quirk::MemoryLeaveIUnchanged]);
412 assert!(!platform.quirks[&Quirk::Wrap]);
413 assert!(!platform.quirks[&Quirk::Jump]);
414 assert!(platform.quirks[&Quirk::VBlank]);
415 assert!(platform.quirks[&Quirk::Logic]);
416
417 Ok(())
418 }
419
420 #[test]
421 fn deserialize_full() -> Result<()> {
422 let input = r##"{
423 "id": "hybridVIP",
424 "name": "Platform Example",
425 "description": "A description goes here",
426 "release": "1999-12-31",
427 "authors": ["Who Knows?"],
428 "urls": ["https://example.com"],
429 "copyright": "Probably copyrighted or something",
430 "license": "GPL",
431 "displayResolutions": ["128x64"],
432 "defaultTickrate": 999,
433 "quirks": {
434 "shift": true,
435 "memoryIncrementByX": false,
436 "memoryLeaveIUnchanged": true,
437 "wrap": false,
438 "jump": true,
439 "vblank": false,
440 "logic": true
441 }
442 }"##;
443
444 let platform: PlatformDetails = serde_json::from_str(input)?;
445
446 assert_eq!(Platform::HybridVIP, platform.id);
447 assert_eq!("Platform Example", platform.name);
448
449 assert_eq!("A description goes here", &platform.description.unwrap());
450 assert_eq!("1999-12-31", &platform.release.unwrap());
451 assert_eq!(vec!["Who Knows?".to_owned()], platform.authors.unwrap());
452
453 assert_eq!(
454 vec!["https://example.com".to_owned()],
455 platform.urls.unwrap()
456 );
457
458 assert_eq!(
459 "Probably copyrighted or something",
460 &platform.copyright.unwrap()
461 );
462
463 assert_eq!("GPL", &platform.license.unwrap());
464 assert_eq!(vec!["128x64"], platform.display_resolutions);
465 assert_eq!(999, platform.default_tickrate);
466
467 assert!(platform.quirks[&Quirk::Shift]);
468 assert!(!platform.quirks[&Quirk::MemoryIncrementByX]);
469 assert!(platform.quirks[&Quirk::MemoryLeaveIUnchanged]);
470 assert!(!platform.quirks[&Quirk::Wrap]);
471 assert!(platform.quirks[&Quirk::Jump]);
472 assert!(!platform.quirks[&Quirk::VBlank]);
473 assert!(platform.quirks[&Quirk::Logic]);
474
475 Ok(())
476 }
477 }
478
479 #[cfg(feature = "extra-data")]
480 mod quirks {
481 use crate::quirk::Quirk;
482
483 use super::*;
484
485 use std::io::Result;
486
487 #[test]
488 fn deserialize_minimal() -> Result<()> {
489 let input = r##"{
490 "id": "shift",
491 "name": "Minimal Quirk Example",
492 "default": false,
493 "ifTrue": "Do some thing",
494 "ifFalse": "Do some other thing"
495 }"##;
496
497 let quirk: QuirkDetails = serde_json::from_str(input)?;
498
499 assert_eq!(Quirk::Shift, quirk.id);
500 assert_eq!("Minimal Quirk Example", quirk.name);
501
502 assert!(!quirk.default);
503
504 assert_eq!("Do some thing", quirk.if_true);
505 assert_eq!("Do some other thing", quirk.if_false);
506
507 Ok(())
508 }
509
510 #[test]
511 fn deserialize_full() -> Result<()> {
512 let input = r##"{
513 "id": "jump",
514 "name": "Quirk Example",
515 "description": "An example of a quirk",
516 "default": true,
517 "ifTrue": "Do some more things",
518 "ifFalse": "Do some more other things"
519 }"##;
520
521 let quirk: QuirkDetails = serde_json::from_str(input)?;
522
523 assert_eq!(Quirk::Jump, quirk.id);
524 assert_eq!("Quirk Example", quirk.name);
525 assert_eq!("An example of a quirk", quirk.description.unwrap());
526
527 assert!(quirk.default);
528
529 assert_eq!("Do some more things", quirk.if_true);
530 assert_eq!("Do some more other things", quirk.if_false);
531
532 Ok(())
533 }
534 }
535}