diff options
| author | Miquel Sabaté Solà <mikisabate@gmail.com> | 2024-12-22 22:40:11 +0100 |
|---|---|---|
| committer | Miquel Sabaté Solà <mikisabate@gmail.com> | 2024-12-22 23:05:18 +0100 |
| commit | 4628873cfc53d3bdc7a0834b3edbb02b0b9cf4a7 (patch) | |
| tree | 59d4fefeac7ce0d095f6e86712d5c70816a2e428 /lib/xixanta/src/mapping.rs | |
| parent | 0bc97c99c644955096b3e13a2a7df6b281314e25 (diff) | |
| download | tools.nes-4628873cfc53d3bdc7a0834b3edbb02b0b9cf4a7.tar.gz tools.nes-4628873cfc53d3bdc7a0834b3edbb02b0b9cf4a7.zip | |
Transform mapping configurations into toml files
This will allow the creation of configuration files that can live
outside of this tree, so developers can fine tune configuration files of
their own without having to pick up whatever is currently available.
Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
Diffstat (limited to 'lib/xixanta/src/mapping.rs')
| -rw-r--r-- | lib/xixanta/src/mapping.rs | 530 |
1 files changed, 183 insertions, 347 deletions
diff --git a/lib/xixanta/src/mapping.rs b/lib/xixanta/src/mapping.rs index 8cf752d..a196c8e 100644 --- a/lib/xixanta/src/mapping.rs +++ b/lib/xixanta/src/mapping.rs @@ -1,327 +1,11 @@ use crate::errors::EvalError; use crate::object::Bundle; +use toml::{Table, Value}; -lazy_static! { - /// An empty mapper used for testing purposes. - pub static ref EMPTY: Vec<Mapping> = vec![ - Mapping { - name: String::from("HEADER"), - start: 0x0000, - size: 0x0010, - offset: 0, - fill: Some(0x00), - section_type: SectionType::Header, - segments: vec![Segment { - name: String::from("HEADER"), - len: 0, - offset: 0, - bundles: vec![], - }] - }, - Mapping { - name: String::from("ROM0"), - start: 0x8000, - size: 0x8000, - offset: 0, - fill: None, - section_type: SectionType::PrgRom, - segments: vec![Segment { - name: String::from("CODE"), - len: 0, - offset: 0, - bundles: vec![], - },] - }, - ]; - - // Mapper for a simple NROM setup (e.g. Super Mario Bros). - pub static ref NROM: Vec<Mapping> = vec![ - Mapping { - name: String::from("HEADER"), - start: 0x0000, - size: 0x0010, - offset: 0, - fill: Some(0x00), - section_type: SectionType::Header, - segments: vec![Segment { - name: String::from("HEADER"), - len: 0, - offset: 0, - bundles: vec![], - }] - }, - Mapping { - name: String::from("ROM0"), - start: 0x8000, - size: 0x7FFA, - offset: 0, - fill: Some(0x00), - section_type: SectionType::PrgRom, - segments: vec![Segment { - name: String::from("CODE"), - len: 0, - offset: 0, - bundles: vec![], - },] - }, - Mapping { - name: String::from("ROMV"), - start: 0xFFFA, - size: 0x0006, - offset: 0, - fill: Some(0x00), - section_type: SectionType::PrgRom, - segments: vec![Segment { - name: String::from("VECTORS"), - len: 0, - offset: 0, - bundles: vec![], - },] - }, - Mapping { - name: String::from("ROM2"), - start: 0x0000, - size: 0x2000, - offset: 0, - fill: Some(0x00), - section_type: SectionType::ChrRom, - segments: vec![Segment { - name: String::from("CHARS"), - len: 0, - offset: 0, - bundles: vec![], - },] - }, - ]; - - // Mapper for UxROM boards (UxROM (NES-UNROM, NES-UOROM, HVC-UN1ROM their - // HVC counterparts, and clone boards). - pub static ref UXROM: Vec<Mapping> = vec![ - Mapping { - name: String::from("HEADER"), - start: 0x0000, - size: 0x0010, - offset: 0, - fill: Some(0x00), - section_type: SectionType::Header, - segments: vec![Segment { - name: String::from("HEADER"), - len: 0, - offset: 0, - bundles: vec![], - }] - }, - Mapping { - name: String::from("PRG0"), - start: 0x8000, - size: 0x4000, - offset: 0, - fill: Some(0xF8), - section_type: SectionType::PrgRom, - segments: vec![ - Segment { - name: String::from("BANK0"), - len: 0, - offset: 0, - bundles: vec![], - }, - ] - }, - Mapping { - name: String::from("PRG1"), - start: 0x8000, - size: 0x4000, - offset: 0, - fill: Some(0xF9), - section_type: SectionType::PrgRom, - segments: vec![ - Segment { - name: String::from("BANK1"), - len: 0, - offset: 0, - bundles: vec![], - }, - ] - }, - Mapping { - name: String::from("PRG2"), - start: 0x8000, - size: 0x4000, - offset: 0, - fill: Some(0xFA), - section_type: SectionType::PrgRom, - segments: vec![ - Segment { - name: String::from("BANK2"), - len: 0, - offset: 0, - bundles: vec![], - }, - ] - }, - Mapping { - name: String::from("PRG3"), - start: 0x8000, - size: 0x4000, - offset: 0, - fill: Some(0xFB), - section_type: SectionType::PrgRom, - segments: vec![ - Segment { - name: String::from("BANK3"), - len: 0, - offset: 0, - bundles: vec![], - }, - ] - }, - Mapping { - name: String::from("PRG4"), - start: 0x8000, - size: 0x4000, - offset: 0, - fill: Some(0xFC), - section_type: SectionType::PrgRom, - segments: vec![ - Segment { - name: String::from("BANK4"), - len: 0, - offset: 0, - bundles: vec![], - }, - ] - }, - Mapping { - name: String::from("PRG5"), - start: 0x8000, - size: 0x4000, - offset: 0, - fill: Some(0xFD), - section_type: SectionType::PrgRom, - segments: vec![ - Segment { - name: String::from("BANK5"), - len: 0, - offset: 0, - bundles: vec![], - }, - ] - }, - Mapping { - name: String::from("PRG6"), - start: 0x8000, - size: 0x4000, - offset: 0, - fill: Some(0xFE), - section_type: SectionType::PrgRom, - segments: vec![ - Segment { - name: String::from("BANK6"), - len: 0, - offset: 0, - bundles: vec![], - }, - ] - }, - Mapping { - name: String::from("PRG"), - start: 0xC000, - size: 0x3FFA, - offset: 0, - fill: Some(0xFF), - section_type: SectionType::PrgRom, - segments: vec![ - Segment { - name: String::from("FIXED"), - len: 0, - offset: 0, - bundles: vec![], - }, - ] - }, - Mapping { - name: String::from("ROMV"), - start: 0xFFFA, - size: 0x0006, - offset: 0, - fill: Some(0x00), - section_type: SectionType::PrgRom, - segments: vec![Segment { - name: String::from("VECTORS"), - len: 0, - offset: 0, - bundles: vec![], - },] - }, - ]; - - pub static ref NROM65: Vec<Mapping> = vec![ - Mapping { - name: String::from("HEADER"), - start: 0x0000, - size: 0x0010, - offset: 0, - fill: Some(0x00), - section_type: SectionType::Header, - segments: vec![Segment { - name: String::from("HEADER"), - len: 0, - offset: 0, - bundles: vec![], - }] - }, - Mapping { - name: String::from("ROM0"), - start: 0x8000, - size: 0x7FFA, - offset: 0, - fill: Some(0x00), - section_type: SectionType::PrgRom, - segments: vec![ - Segment { - name: String::from("STARTUP"), - len: 0, - offset: 0, - bundles: vec![], - }, - Segment { - name: String::from("CODE"), - len: 0, - offset: 0, - bundles: vec![], - }, - ] - }, - Mapping { - name: String::from("ROMV"), - start: 0xFFFA, - size: 0x0006, - offset: 0, - fill: Some(0x00), - section_type: SectionType::PrgRom, - segments: vec![Segment { - name: String::from("VECTORS"), - len: 0, - offset: 0, - bundles: vec![], - },] - }, - Mapping { - name: String::from("ROM2"), - start: 0x0000, - size: 0x2000, - offset: 0, - fill: Some(0x00), - section_type: SectionType::ChrRom, - segments: vec![Segment { - name: String::from("CHARS"), - len: 0, - offset: 0, - bundles: vec![], - },] - }, - ]; -} +const EMPTY_CONFIG: &str = include_str!("mappings/empty.toml"); +const NROM_CONFIG: &str = include_str!("mappings/nrom.toml"); +const NROM65_CONFIG: &str = include_str!("mappings/nrom65.toml"); +const UXROM_CONFIG: &str = include_str!("mappings/unrom.toml"); /// The type of section that a Mapping represents. #[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)] @@ -353,6 +37,17 @@ pub struct Segment { pub bundles: Vec<Bundle>, } +impl From<&str> for Segment { + fn from(name: &str) -> Self { + Segment { + name: name.to_string(), + offset: 0, + len: 0, + bundles: vec![], + } + } +} + impl Segment { /// Returns the length of the segment by counting the bundles that have been // pushed so far into the segment. @@ -401,39 +96,180 @@ pub struct Mapping { pub section_type: SectionType, } -/// Assert that the given mappings conform to a minimum standard. -pub fn assert(mappings: &[Mapping]) { - assert!( - !mappings.is_empty(), - "We need at least one segment defined, the header" - ); - assert!( - !mappings.first().unwrap().segments.is_empty(), - "We need at least one segment defined, the header" - ); - assert_eq!( - mappings.first().unwrap().section_type, - SectionType::Header, - "First mapping section must be the header" - ); - assert_eq!( - mappings.first().unwrap().size, - 0x10, - "The header must be exactly 16 bytes long" - ); +/// Returns a vector corresponding to the configuration of mappings that is +/// expected for the given `name`. Returns an error if the configuration cannot +/// be parsed or there's something wrong about it. +pub fn get_mapping_configuration(name: &str) -> Result<Vec<Mapping>, String> { + let text = match name.to_lowercase().as_str() { + "empty" => EMPTY_CONFIG, + "nrom" => NROM_CONFIG, + "nrom65" => NROM65_CONFIG, + "uxrom" | "unrom" => UXROM_CONFIG, + _ => return Err("mapper configuration is not known".to_string()), + }; + + let configuration = load_configuration_for(text)?; + validate_configuration(&configuration)?; + + Ok(configuration) +} + +// Returns the integer value for the mandatory integer contained in `value` that +// is named `prop_name` under the `section_name` section. This integer has to be +// lesser or equal to the `max` value. +fn get_integer( + section_name: &String, + prop_name: &str, + value: Option<&Value>, + max: usize, +) -> Result<usize, String> { + if value.is_none() { + return Err(format!( + "you have to define a value for '{}' in '{}'", + section_name, prop_name + )); + } + if !value.unwrap().is_integer() { + return Err(format!( + "value for '{}' in '{}' has to be an integer value", + section_name, prop_name + )); + } + + let val = value.unwrap().as_integer().unwrap() as usize; + if val > max { + return Err(format!( + "value for '{}' in '{}' is too big", + prop_name, section_name + )); + } + Ok(val) +} + +// Get a `SectionType` out of the given mandatory `value` which is under the +// `section_name`. +fn parse_section_type(section_name: &String, value: Option<&Value>) -> Result<SectionType, String> { + match value { + Some(v) => { + if !v.is_str() { + return Err(format!( + "'section_type' in '{}' has to be a string", + section_name + )); + } + match v.as_str().unwrap().to_lowercase().as_str() { + "header" => Ok(SectionType::Header), + "prgrom" => Ok(SectionType::PrgRom), + "chrrom" => Ok(SectionType::ChrRom), + _ => Err(format!( + "bad value for 'section_type' in '{}'", + section_name + )), + } + } + None => Err(format!( + "you have to define 'section_type' in '{}'", + section_name + )), + } +} + +// Returns a vector of segments which are contained inside of the mandatory +// `value`. +fn get_segments(section_name: &String, value: Option<&Value>) -> Result<Vec<Segment>, String> { + if value.is_none() { + return Err(format!( + "you have to define a value for 'segments' in '{}'", + section_name + )); + } + if !value.unwrap().is_array() { + return Err(format!( + "value for 'segments' in '{}' has to be an array value", + section_name + )); + } + + let mut res = vec![]; + for item in value.unwrap().as_array().unwrap() { + if !item.is_str() { + return Err(format!( + "every item in 'segments' has to be a string ({})", + section_name + )); + } + res.push(Segment::from(item.as_str().unwrap())); + } + + Ok(res) +} + +// Returns a vector of mappings that is retrieved by parsing the given text. +fn load_configuration_for(text: &str) -> Result<Vec<Mapping>, String> { + // Obtain the raw data by parsing the given text as a toml::Table. + let table = match text.parse::<Table>() { + Ok(t) => t, + Err(e) => return Err(format!("could not parse configuration file: {}", e)), + }; + + // Each section of the configuration file is a mapping, where the title is + // simply the name of it. + let mut mappings = vec![]; + for (name, value) in table { + let start = get_integer(&name, "start", value.get("start"), u16::MAX as usize)? as u16; + let size = get_integer(&name, "size", value.get("size"), u16::MAX as usize)?; + let fill = match value.get("fill") { + Some(_) => Some(get_integer(&name, "fill", value.get("fill"), u8::MAX as usize)? as u8), + None => None, + }; + let section_type = parse_section_type(&name, value.get("section_type"))?; + let segments = get_segments(&name, value.get("segments"))?; + + mappings.push(Mapping { + name, + start, + size, + offset: 0, + fill, + section_type, + segments, + }); + } + + Ok(mappings) +} + +// Ensure that the given mappings conform to a minimum standard. +fn validate_configuration(mappings: &[Mapping]) -> Result<(), String> { + if mappings.is_empty() { + return Err("We need at least one segment defined, the header".to_string()); + } + if mappings.first().unwrap().segments.is_empty() { + return Err("We need at least one segment defined, the header".to_string()); + } + if mappings.first().unwrap().section_type != SectionType::Header { + return Err("First mapping section must be the header".to_string()); + } + if mappings.first().unwrap().size != 0x10 { + return Err("The header must be exactly 16 bytes long".to_string()); + } let prg_rom_len = mappings .iter() .filter(|m| m.section_type == SectionType::PrgRom) .fold(0, |acc, x| acc + x.size); - assert!(prg_rom_len >= 0x4000, "PRG ROM must be at least 8KB long"); - assert!( - prg_rom_len % 0x4000 == 0, - "PRG ROM must be formed by banks of exactly 8KB" - ); + + if prg_rom_len < 0x4000 { + return Err("PRG ROM must be at least 8KB long".to_string()); + } + if prg_rom_len % 0x4000 != 0 { + return Err("PRG ROM must be formed by banks of exactly 8KB".to_string()); + } + + Ok(()) } -/// Validate some sanity checks on the given `mappings`. Only call this function +/// Perform some sanity checks on the given `mappings`. Only call this function /// after all bundles have been produced. pub fn validate(mappings: &[Mapping]) -> Result<(), EvalError> { // Guaranteed by `crate::mapping::assert` to be the header. |
