aboutsummaryrefslogtreecommitdiff
path: root/lib/xixanta/src/mapping.rs
diff options
context:
space:
mode:
Diffstat (limited to 'lib/xixanta/src/mapping.rs')
-rw-r--r--lib/xixanta/src/mapping.rs530
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.