c64_cartridge/
lib.rs

1#![no_std]
2#![cfg_attr(docsrs, feature(doc_auto_cfg))]
3#![deny(clippy::unwrap_used)]
4#![warn(missing_docs)]
5// enable some pedantic clippy lints
6#![warn(clippy::pedantic)]
7#![allow(clippy::missing_errors_doc)]
8#![allow(clippy::module_name_repetitions)]
9
10//! A parser for the [C64's cartridge format](https://ist.uwaterloo.ca/~schepers/formats/CRT.TXT).
11//!
12//! The parser requires that the whole file must be in ram.
13//!
14//! To check a cartridge step by step read [`RawCartridge`] and [`RawChip`] and use their check functions.
15//!
16//! The library is `no_std`.
17//!
18//! # Example
19//!
20//! ```no_run
21//! # use c64_cartridge::{parse_crt, ParseError, Position};
22//! # fn test() -> Result<(), ParseError> {
23//! # let crt_file_slice = b"";
24//! let (cartridge, data) = parse_crt(crt_file_slice)?;
25//!
26//! // convert to the "usual 8 KiB ROM banking"
27//! let Some(data) = data.into_8k_baking() else { panic!("crt contains RAM and/or not the usual 8 KiB ROM banking") };
28//!
29//! // calculate max bank (the unwrap is safe because data guarantees at lest one chip)
30//! let max_bank = data.iter().map(|(chip, _)| chip.bank_number).max().unwrap() as usize;
31//!
32//! // create a single memory of all ROM banks
33//! let mut all = vec![0xff; (max_bank + 1) * 0x4000];
34//! for (chip, data) in data {
35//!     let mut ofs = chip.bank_number as usize * 0x4000;
36//!     if chip.position == Position::High {
37//!         ofs += 0x2000;
38//!     }
39//!     all[ofs..ofs + 0x2000].copy_from_slice(data);
40//! }
41//! # Ok(())
42//! # }
43//! ```
44//!
45//! # Features
46//!
47//! - `default`: `alloc`
48//! - `alloc`: Enable [`Data::check_overlap`] and [`Data8K::check_overlap`].
49//!
50//! ## Usage
51//! With defaults (`alloc`):
52//! ```toml
53//! [dependencies]
54//! c64-cartridge = "0.5"
55//! ```
56//!
57//! Without defaults (`alloc`):
58//! ```toml
59//! [dependencies]
60//! c64-cartridge = { version = "0.5", default-features = false }
61//! ```
62
63#[cfg(feature = "alloc")]
64extern crate alloc;
65
66mod borrowed_or_owned;
67mod cartridge;
68mod cartridge_type;
69mod chip;
70mod chip_type;
71mod data;
72mod error;
73mod iter;
74mod parser;
75mod position;
76
77pub use crate::cartridge::{Cartridge, RawCartridge};
78pub use crate::cartridge_type::CartridgeType;
79pub use crate::chip::{Chip, Chip8K, RawChip};
80pub use crate::chip_type::ChipType;
81pub use crate::data::{Data, Data8K};
82pub use crate::error::ParseError;
83pub use crate::iter::{Iter, Iter8K};
84pub use crate::parser::{parse_crt, parse_crt_aligned};
85pub use crate::position::Position;
86
87#[cfg(feature = "alloc")]
88#[cfg(test)]
89mod test {
90    use crate::{parse_crt, parse_crt_aligned, Cartridge, CartridgeType, ChipType, Data, Position};
91    use alloc::vec;
92    use alloc::vec::Vec;
93
94    const EXAMPLE_HEAD: [u8; 80] = [
95        0x43, 0x36, 0x34, 0x20, 0x43, 0x41, 0x52, 0x54, 0x52, 0x49, 0x44, 0x47, 0x45, 0x20, 0x20,
96        0x20, 0x00, 0x00, 0x00, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
97        0x00, 0x00, 0x41, 0x54, 0x54, 0x41, 0x43, 0x4B, 0x20, 0x4F, 0x46, 0x20, 0x54, 0x48, 0x45,
98        0x20, 0x4D, 0x55, 0x54, 0x41, 0x4E, 0x54, 0x20, 0x43, 0x41, 0x4D, 0x45, 0x4C, 0x53, 0x00,
99        0x00, 0x00, 0x00, 0x00, 0x43, 0x48, 0x49, 0x50, 0x00, 0x00, 0x20, 0x10, 0x00, 0x00, 0x00,
100        0x00, 0x80, 0x00, 0x20, 0x00,
101    ];
102    const BUFFER_SIZE: usize = EXAMPLE_HEAD.len() + 8192;
103
104    #[test]
105    fn test() {
106        // create a buffer which is aligned to u32 (should be anyways but can't be sure)
107        let mut crt = vec![0u8; BUFFER_SIZE + 2 * align_of::<u32>()];
108        let crt = crt.as_mut_slice();
109        let offset = crt.as_ptr().align_offset(align_of::<u32>());
110        assert!(
111            offset <= align_of::<u32>(),
112            "crt not alignable, this shouldn't happen"
113        );
114        let crt = &mut crt[offset..offset + BUFFER_SIZE];
115
116        // fill with example data
117        crt[0..EXAMPLE_HEAD.len()].copy_from_slice(&EXAMPLE_HEAD);
118
119        // make readonly
120        #[allow(clippy::useless_asref)]
121        let crt = crt.as_ref();
122
123        // check both parse functions
124        check(parse_crt(crt).unwrap_or_else(|_| panic!("unable to parse crt")));
125        check(unsafe { parse_crt_aligned(crt) }.unwrap_or_else(|_| panic!("unable to parse crt")));
126    }
127
128    fn check<const A: bool>(input: (Cartridge, Data<A>)) {
129        let (cartridge, data) = input;
130        assert_eq!(cartridge.cartridge_hardware_type, CartridgeType::Generic);
131        assert!(!cartridge.exrom_line);
132        assert!(cartridge.game_line);
133        assert_eq!(cartridge.name(), b"ATTACK OF THE MUTANT CAMELS");
134
135        let Some(data) = data.into_8k_baking() else {
136            panic!("crt contains RAM and/or not the usual 8 KiB ROM banking")
137        };
138
139        let data = data.iter().collect::<Vec<_>>();
140        assert_eq!(data.len(), 1);
141        let (chip, rom) = &data[0];
142        assert_eq!(chip.chip_type, ChipType::Rom);
143        assert_eq!(chip.bank_number, 0);
144        assert_eq!(chip.position, Position::Low);
145        assert_eq!(rom.len(), 8192);
146    }
147}