use alloc::collections::{BTreeMap, BTreeSet};
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
use onerom_config::chip::{ChipFunction, ChipType};
use onerom_config::fw::{FirmwareProperties, FirmwareVersion, ServeAlg};
use onerom_config::mcu::Family;
use crate::image::{Chip, ChipSet, ChipSetType, CsConfig, CsLogic, Location, SizeHandling};
use crate::meta::Metadata;
use crate::{Error, FIRMWARE_SIZE, MAX_METADATA_LEN, MIN_FIRMWARE_OVERRIDES_VERSION, Result};
pub const MAX_SUPPORTED_FIRMWARE_VERSION: FirmwareVersion = FirmwareVersion::new(0, 6, 999, 0);
const UNSUPPORTED_FIRMWARE_VERSIONS: [FirmwareVersion; 1] = [FirmwareVersion::new(0, 6, 3, 0)];
pub const SUPPORTED_CHIP_TYPES: &[ChipType; 32] = &[
ChipType::Chip2316,
ChipType::Chip2716,
ChipType::Chip6116,
ChipType::Chip2332,
ChipType::Chip2732,
ChipType::Chip2364,
ChipType::Chip2764,
ChipType::Chip23128,
ChipType::Chip27128,
ChipType::Chip23256,
ChipType::Chip27256,
ChipType::Chip23512,
ChipType::Chip27512,
ChipType::Chip231024,
ChipType::Chip27C400,
ChipType::Chip27C010,
ChipType::Chip27C020,
ChipType::Chip27C040,
ChipType::Chip27C080,
ChipType::Chip27C301,
ChipType::Chip2704,
ChipType::Chip2708,
ChipType::SystemPlugin,
ChipType::UserPlugin,
ChipType::PioPlugin,
ChipType::Chip28C16,
ChipType::Chip28C64,
ChipType::Chip28C256,
ChipType::Chip28C512,
ChipType::Chip23C1010,
ChipType::Chip27C080,
ChipType::Chip23QL512,
];
pub(crate) use crate::firmware::*;
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct Builder {
version: FirmwareVersion,
config: Config,
files: BTreeMap<usize, Vec<u8>>,
licenses: BTreeMap<usize, License>,
file_id_map: BTreeMap<usize, usize>,
}
impl Builder {
pub fn from_json(version: FirmwareVersion, mcu_family: Family, json: &str) -> Result<Self> {
if version > MAX_SUPPORTED_FIRMWARE_VERSION {
return Err(Error::FirmwareTooNew {
version,
maximum: MAX_SUPPORTED_FIRMWARE_VERSION,
});
}
let config: Config = serde_json::from_str(json).map_err(|e| Error::InvalidConfig {
error: e.to_string(),
})?;
Self::validate_config(&version, &mcu_family, &config)?;
let mut builder = Self {
version,
config,
files: BTreeMap::new(),
licenses: BTreeMap::new(),
file_id_map: BTreeMap::new(),
};
builder.build_file_id_map();
Ok(builder)
}
pub fn config(&self) -> &Config {
&self.config
}
fn validate_config(
version: &FirmwareVersion,
mcu_family: &Family,
config: &Config,
) -> Result<()> {
if config.version != 1 {
return Err(Error::UnsupportedConfigVersion {
version: config.version,
});
}
for unsupported_version in UNSUPPORTED_FIRMWARE_VERSIONS.iter() {
if unsupported_version.matches_release(version) {
return Err(Error::FirmwareUnsupported { version: *version });
}
}
const MIN_FW_CHIP_TYPE_231024: FirmwareVersion = FirmwareVersion::new(0, 6, 3, 0);
if *version < MIN_FW_CHIP_TYPE_231024 {
for set in config.chip_sets.iter() {
for chip in set.chips.iter() {
if matches!(chip.chip_type, ChipType::Chip231024) {
return Err(Error::FirmwareTooOld {
feat: "231024 ROMs",
version: *version,
minimum: MIN_FW_CHIP_TYPE_231024,
});
}
}
}
}
let mut chip_num = 0;
for (set_id, set) in config.chip_sets.iter().enumerate() {
if set.chips.is_empty() {
return Err(Error::NoChips { id: set_id });
}
#[allow(clippy::collapsible_if)]
if set.firmware_overrides.is_some() {
if version < &MIN_FIRMWARE_OVERRIDES_VERSION {
return Err(Error::FirmwareTooOld {
feat: "firmware overrides",
version: *version,
minimum: MIN_FIRMWARE_OVERRIDES_VERSION,
});
}
}
if set.chips.len() > 1 {
if set.set_type == ChipSetType::Single {
return Err(Error::TooManyChips {
id: set_id,
expected: 1,
actual: set.chips.len(),
});
}
if set.chips.len() > 3 && set.set_type == ChipSetType::Multi {
return Err(Error::TooManyChips {
id: set_id,
expected: 3,
actual: set.chips.len(),
});
}
if set.chips.len() > 4 && set.set_type == ChipSetType::Banked {
return Err(Error::TooManyChips {
id: set_id,
expected: 4,
actual: set.chips.len(),
});
}
}
for chip in set.chips.iter() {
let chip0 = &set.chips[0];
if !SUPPORTED_CHIP_TYPES.contains(&chip.chip_type) {
return Err(Error::UnsupportedToolChipType {
chip_type: chip.chip_type,
});
}
if let Some(min_version) = chip.chip_type.min_supported_firmware_version() {
if version < &min_version {
return Err(Error::FirmwareTooOld {
feat: chip.chip_type.name(),
version: *version,
minimum: min_version,
});
}
} else {
return Err(Error::UnsupportedToolChipType {
chip_type: chip.chip_type,
});
}
if chip.file.is_empty() && chip.chip_type.chip_function() != ChipFunction::Ram {
return Err(Error::InvalidConfig {
error: format!("Chip {} file name is empty", chip_num),
});
}
if set.set_type == ChipSetType::Banked && chip.chip_type != chip0.chip_type {
return Err(Error::InvalidConfig {
error: format!(
"All Chips in a banked set must be of the same type ({} != {})",
chip.chip_type.name(),
chip0.chip_type.name()
),
});
}
for line in chip.chip_type.control_lines() {
let cs = match line.name {
"cs1" => chip.cs1,
"cs2" => chip.cs2,
"cs3" => chip.cs3,
"ce" | "oe" => Some(CsLogic::Ignore),
"write" | "byte" | "busy" => Some(CsLogic::Ignore),
_ => {
return Err(Error::InvalidConfig {
error: format!("Unknown control line {}", line.name),
});
}
};
if cs.is_none() {
return Err(Error::MissingCsConfig {
chip_type: chip.chip_type,
line: line.name,
});
}
}
let has_cs2 = chip
.chip_type
.control_lines()
.iter()
.any(|line| line.name == "cs2");
let has_cs3 = chip
.chip_type
.control_lines()
.iter()
.any(|line| line.name == "cs3");
if chip.cs2.is_some() && !has_cs2 {
return Err(Error::InvalidConfig {
error: format!(
"CS2 specified for Chip type {} which does not use CS2",
chip.chip_type.name()
),
});
}
if chip.cs3.is_some() && !has_cs3 {
return Err(Error::InvalidConfig {
error: format!(
"CS3 specified for Chip type {} which does not use CS3",
chip.chip_type.name()
),
});
}
let cs1_active = chip.cs1.is_some() && chip.cs1.unwrap() != CsLogic::Ignore;
let cs2_active = chip.cs2.is_some() && chip.cs2.unwrap() != CsLogic::Ignore;
let cs3_active = chip.cs3.is_some() && chip.cs3.unwrap() != CsLogic::Ignore;
if !cs1_active && (cs2_active || cs3_active) {
return Err(Error::InvalidConfig {
error: "CS1 cannot be ignore when CS2 or CS3 are active".to_string(),
});
}
if !cs2_active && cs3_active {
return Err(Error::InvalidConfig {
error: "CS2 cannot be ignore when CS3 is active".to_string(),
});
}
let mut required_cs_lines: BTreeSet<&str> = chip
.chip_type
.control_lines()
.iter()
.filter(|line| matches!(line.name, "cs1" | "cs2" | "cs3"))
.map(|line| line.name)
.collect();
if chip.chip_type == ChipType::Chip27C080 {
required_cs_lines.insert("cs1");
}
let specified_cs_lines: BTreeSet<&str> = {
let mut lines = BTreeSet::new();
if chip.cs1.is_some() {
lines.insert("cs1");
}
if chip.cs2.is_some() {
lines.insert("cs2");
}
if chip.cs3.is_some() {
lines.insert("cs3");
}
lines
};
if required_cs_lines != specified_cs_lines {
return Err(Error::InvalidConfig {
error: format!(
"Chip type {} requires CS lines {:?}, but specified CS lines are {:?}",
chip.chip_type.name(),
required_cs_lines,
specified_cs_lines
),
});
}
for line in &["cs1", "cs2", "cs3"] {
if !required_cs_lines.contains(line) && specified_cs_lines.contains(line) {
return Err(Error::InvalidConfig {
error: format!(
"Chip type {} does not use {}, but it is specified",
chip.chip_type.name(),
line
),
});
}
}
if set.chips.len() == 1 {
for line in &["cs1", "cs2", "cs3"] {
let cs = match *line {
"cs1" => &chip.cs1,
"cs2" => &chip.cs2,
"cs3" => &chip.cs3,
_ => unreachable!(),
};
#[allow(clippy::collapsible_if)]
if let Some(cs_logic) = cs {
if *cs_logic == CsLogic::Ignore {
return Err(Error::InvalidConfig {
error: format!(
"{} cannot be ignore for single-ROM sets (Chip {})",
line.to_uppercase(),
chip_num
),
});
}
}
}
} else {
if !cs1_active {
return Err(Error::InvalidConfig {
error: format!(
"CS1 cannot be ignore for multi-ROM sets (Chip {})",
chip_num
),
});
}
}
if let Some(location) = &chip.location {
if location.length == 0 {
return Err(Error::InvalidConfig {
error: format!("Chip {} location length must be non-zero", chip_num),
});
}
if location.start.checked_add(location.length).is_none() {
return Err(Error::InvalidConfig {
error: format!("Chip {} location start + length overflows", chip_num),
});
}
}
if chip.chip_type.is_plugin() && *mcu_family == Family::Stm32f4 {
return Err(Error::InvalidConfig {
error: format!("Plugins are not supported on Ice (Chip {})", chip_num),
});
}
if chip.chip_type == ChipType::SystemPlugin && set_id != 0 {
return Err(Error::InvalidConfig {
error: "System plugins must be in the first slot".to_string(),
});
}
if chip.chip_type == ChipType::UserPlugin {
if set_id != 1 {
return Err(Error::InvalidConfig {
error: "User plugins must be in the second slot".to_string(),
});
} else {
if config.chip_sets[0].chips[0].chip_type != ChipType::SystemPlugin {
return Err(Error::InvalidConfig {
error: "User plugins must be in the second slot, and the first slot must be a system plugin".to_string()
});
}
}
}
chip_num += 1;
}
#[allow(clippy::collapsible_if)]
if set.set_type == ChipSetType::Multi || set.set_type == ChipSetType::Banked {
if set.chips.len() > 1 {
let first_cs1 = set.chips[0].cs1;
let first_cs2 = set.chips[0].cs2;
let first_cs3 = set.chips[0].cs3;
for (idx, rom) in set.chips.iter().enumerate().skip(1) {
if rom.cs1 != first_cs1 || rom.cs2 != first_cs2 || rom.cs3 != first_cs3 {
if (rom.cs2 != first_cs2)
&& let Some(cs) = rom.cs2
&& (cs == CsLogic::Ignore)
{
continue;
}
return Err(Error::InvalidConfig {
error: format!(
"{:?} set requires all ROMs to have identical CS configuration. ROM 0 has cs1={:?}/cs2={:?}/cs3={:?}, but ROM {} has cs1={:?}/cs2={:?}/cs3={:?}",
set.set_type,
first_cs1,
first_cs2,
first_cs3,
idx,
rom.cs1,
rom.cs2,
rom.cs3
),
});
}
}
}
}
}
Ok(())
}
fn build_file_id_map(&mut self) {
let mut seen_files: BTreeMap<(String, Option<String>), usize> = BTreeMap::new();
let mut file_id = 0;
let mut chip_id = 0;
for chip_set in self.config.chip_sets.iter() {
for chip in &chip_set.chips {
if chip.file.is_empty() {
chip_id += 1;
continue;
}
let key = (chip.file.clone(), chip.extract.clone());
let assigned_file_id = if let Some(&existing_id) = seen_files.get(&key) {
existing_id
} else {
seen_files.insert(key, file_id);
let id = file_id;
file_id += 1;
id
};
self.file_id_map.insert(chip_id, assigned_file_id);
chip_id += 1;
}
}
}
pub fn file_specs(&self) -> Vec<FileSpec> {
let mut specs = Vec::new();
let mut seen_files: BTreeMap<(String, Option<String>), usize> = BTreeMap::new();
let mut rom_id = 0;
for (chip_set_num, chip_set) in self.config.chip_sets.iter().enumerate() {
for rom in &chip_set.chips {
if rom.file.is_empty() {
rom_id += 1;
continue;
}
let key = (rom.file.clone(), rom.extract.clone());
let file_id = *self.file_id_map.get(&rom_id).unwrap();
seen_files.entry(key).or_insert_with(|| {
specs.push(FileSpec {
id: file_id,
description: rom.description.clone(),
source: rom.file.clone(),
extract: rom.extract.clone(),
size_handling: rom.size_handling.clone(),
chip_type: rom.chip_type,
rom_size: rom.chip_type.size_bytes(),
cs1: rom.cs1,
cs2: rom.cs2,
cs3: rom.cs3,
set_id: chip_set_num,
set_type: chip_set.set_type.clone(),
set_description: chip_set.description.clone(),
});
file_id
});
rom_id += 1;
}
}
specs
}
pub fn add_file(&mut self, file: FileData) -> Result<()> {
if self.files.contains_key(&file.id) {
return Err(Error::DuplicateFile { id: file.id });
}
let total_files = self.total_file_count();
if file.id >= total_files {
return Err(Error::InvalidFile {
id: file.id,
total: total_files,
});
}
self.files.insert(file.id, file.data);
Ok(())
}
pub fn licenses(&mut self) -> Vec<License> {
let mut licenses = Vec::new();
let mut license_id = 0;
let mut rom_id = 0;
for chip_set in self.config.chip_sets.iter() {
for rom in &chip_set.chips {
if let Some(ref url) = rom.license {
let license = License::new(license_id, rom_id, url.clone());
licenses.push(license.clone());
self.licenses.insert(license_id, license);
license_id += 1;
}
rom_id += 1;
}
}
licenses
}
pub fn accept_license(&mut self, license: &License) -> Result<()> {
let own_license = self
.licenses
.get_mut(&license.id)
.ok_or(Error::InvalidLicense { id: license.id })?;
own_license.validated = true;
Ok(())
}
fn total_file_count(&self) -> usize {
self.file_id_map.values().collect::<BTreeSet<_>>().len()
}
pub fn build_validation(&self, props: &FirmwareProperties) -> Result<()> {
for ii in 0..self.total_file_count() {
if !self.files.contains_key(&ii) {
return Err(Error::MissingFile { id: ii });
}
}
for (id, license) in self.licenses.iter() {
if !license.validated {
return Err(Error::UnvalidatedLicense { id: *id });
}
}
let board = props.board();
for set in self.config.chip_sets.iter() {
for rom in set.chips.iter() {
if !board.supports_chip_type(rom.chip_type) {
return Err(Error::UnsupportedBoardChipType {
board,
chip_type: rom.chip_type,
});
}
}
}
let mut rom_id = 0;
for set in self.config.chip_sets.iter() {
for rom in set.chips.iter() {
if matches!(
rom.chip_type,
ChipType::SystemPlugin | ChipType::UserPlugin | ChipType::PioPlugin
) {
let file_id = self.file_id_map.get(&rom_id).unwrap();
let data = self.files.get(file_id).unwrap();
if data.len() < 256 {
return Err(Error::InvalidPluginImage { plugin_type: rom.chip_type, image_file: rom.file.clone(), error: "Plugin image is smaller than the required plugin header (256 bytes).".to_string() });
}
if &data[0..4] != b"ORA " {
return Err(Error::InvalidPluginImage {
plugin_type: rom.chip_type,
image_file: rom.file.clone(),
error: "Invalid magic value in plugin header.".to_string(),
});
}
let api_version = u32::from_le_bytes(data[4..8].try_into().unwrap());
if api_version != 1 {
return Err(Error::InvalidPluginImage {
plugin_type: rom.chip_type,
image_file: rom.file.clone(),
error: format!(
"Invalid API version {api_version} in plugin header - must be 1."
),
});
}
let plugin_fw_major = u16::from_le_bytes([data[24], data[25]]);
let plugin_fw_minor = u16::from_le_bytes([data[26], data[27]]);
let plugin_fw_patch = u16::from_le_bytes([data[28], data[29]]);
let plugin_fw_version =
FirmwareVersion::new(plugin_fw_major, plugin_fw_minor, plugin_fw_patch, 0);
if plugin_fw_version > props.version() {
return Err(Error::InvalidPluginImage {
plugin_type: rom.chip_type,
image_file: rom.file.clone(),
error: format!(
"Plugin requires at least firmware version {} which is newer than the firmware version being built for ({})",
plugin_fw_version,
props.version()
),
});
}
}
rom_id += 1;
}
}
Ok(())
}
pub fn build(&self, props: FirmwareProperties) -> Result<(Vec<u8>, Vec<u8>)> {
if props.version() > MAX_SUPPORTED_FIRMWARE_VERSION {
return Err(Error::FirmwareTooNew {
version: props.version(),
maximum: MAX_SUPPORTED_FIRMWARE_VERSION,
});
}
self.build_validation(&props)?;
let mut chip_sets = Vec::new();
let mut chip_id = 0;
for (set_id, chip_set_config) in self.config.chip_sets.iter().enumerate() {
let mut set_roms = Vec::new();
if let Some(overrides) = chip_set_config.firmware_overrides.as_ref()
&& let Some(fire) = overrides.fire.as_ref()
&& fire.serve_mode == Some(FireServeMode::Cpu)
{
if props.board().chip_pins() != 24 {
return Err(Error::InvalidConfig {
error: "Fire CPU serving mode is only supported on One ROM 24".to_string(),
});
} else if chip_set_config.chips[0].chip_type == ChipType::Chip28C16 {
return Err(Error::InvalidConfig {
error: "Fire CPU serving mode is not supported with 28C16".to_string(),
});
}
}
for chip_config in &chip_set_config.chips {
let data = if let Some(&file_id) = self.file_id_map.get(&chip_id) {
Some(self.files.get(&file_id).unwrap())
} else {
None
};
let filename = chip_config.filename();
let rom = Chip::from_raw_rom_image(
chip_id,
filename,
chip_config.label.clone(),
data.map(|v| &**v),
vec![0u8; chip_config.chip_type.size_bytes()],
&chip_config.chip_type,
CsConfig::new(chip_config.cs1, chip_config.cs2, chip_config.cs3),
&chip_config.size_handling,
chip_config.location,
)?;
set_roms.push(rom);
chip_id += 1;
}
let serve_alg = if let Some(alg) = chip_set_config.serve_alg {
alg
} else {
props.serve_alg()
};
let chip_set = ChipSet::new(
set_id,
chip_set_config.set_type.clone(),
serve_alg,
set_roms,
chip_set_config.firmware_overrides.clone(),
)?;
chip_sets.push(chip_set);
}
let metadata = Metadata::new(
props.board(),
chip_sets,
props.boot_logging(),
props.board().mcu_pio(),
props.version(),
);
let metadata_size = metadata.metadata_len();
let rom_data_size: usize = metadata.rom_images_size();
let set_count = metadata.total_set_count();
let mcu_variant = props.mcu_variant();
let flash_size = mcu_variant.flash_storage_bytes();
let rom_space = flash_size - FIRMWARE_SIZE - MAX_METADATA_LEN;
assert!(rom_space > 0);
if rom_data_size > rom_space {
return Err(Error::BufferTooSmall {
location: "Flash",
expected: rom_data_size,
actual: rom_space,
});
}
let mut metadata_buf = vec![0u8; metadata_size];
let mut rom_data_buf = vec![0u8; rom_data_size];
let mut rom_data_ptrs = vec![0u32; set_count];
metadata.write_all(&mut metadata_buf, &mut rom_data_ptrs)?;
metadata.write_roms(&mut rom_data_buf)?;
Ok((metadata_buf, rom_data_buf))
}
fn num_chip_sets(&self) -> usize {
self.config.chip_sets.len()
}
fn num_roms(&self) -> usize {
self.config
.chip_sets
.iter()
.map(|set| set.chips.len())
.sum()
}
pub fn description(&self) -> String {
let mut desc = String::new();
if let Some(name) = self.config.name.as_ref() {
desc.push_str(name);
desc.push('\n');
desc.push_str(&"-".repeat(name.len()));
desc.push_str("\n\n");
}
desc.push_str(&self.config.description);
desc.push_str("\n\n");
if let Some(detail) = &self.config.detail {
desc.push_str(detail);
desc.push_str("\n\n");
}
let multi_chip_sets = if self.num_chip_sets() == self.num_roms() {
desc.push_str("Images:");
false
} else {
desc.push_str("Sets:");
true
};
desc.push('\n');
let mut none = true;
for (ii, set) in self.config.chip_sets.iter().enumerate() {
none = false;
desc.push_str(&format!("{ii}:"));
if multi_chip_sets {
desc.push_str(&format!(" {:?}", set.set_type));
if let Some(ref set_desc) = set.description {
desc.push_str(&format!(", {set_desc}"));
}
desc.push('\n');
} else {
desc.push(' ');
}
for (jj, rom) in set.chips.iter().enumerate() {
if multi_chip_sets {
desc.push_str(&format!(" {jj}: "));
}
if let Some(rom_desc) = &rom.description {
desc.push_str(rom_desc);
} else {
desc.push_str(&rom.file);
}
desc.push('\n');
}
}
if none {
desc.push_str(" None\n");
}
if let Some(notes) = &self.config.notes {
desc.push('\n');
desc.push_str(notes);
} else {
desc.pop();
}
desc
}
pub fn categories(&self) -> Vec<String> {
let mut categories = Vec::new();
if let Some(cats) = &self.config.categories {
for cat in cats {
categories.push(cat.clone());
}
}
categories
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct License {
pub id: usize,
pub file_id: usize,
pub url: String,
validated: bool,
}
impl License {
pub fn new(id: usize, file_id: usize, url: String) -> Self {
Self {
id,
file_id,
url,
validated: false,
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct FileSpec {
pub id: usize,
pub description: Option<String>,
pub source: String,
pub extract: Option<String>,
pub size_handling: SizeHandling,
pub chip_type: ChipType,
pub rom_size: usize,
pub cs1: Option<CsLogic>,
pub cs2: Option<CsLogic>,
pub cs3: Option<CsLogic>,
pub set_id: usize,
pub set_type: ChipSetType,
pub set_description: Option<String>,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct FileData {
pub id: usize,
pub data: Vec<u8>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schemars", schemars(title = "One ROM Configuration"))]
pub struct Config {
#[cfg_attr(feature = "schemars", schemars(schema_with = "version_schema"))]
pub version: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
pub description: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub detail: Option<String>,
#[serde(alias = "rom_sets")]
pub chip_sets: Vec<ChipSetConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub notes: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub categories: Option<Vec<String>>,
}
#[cfg(feature = "schemars")]
fn version_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({
"const": 1
})
}
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ChipSetConfig {
#[serde(rename = "type")]
#[cfg_attr(feature = "schemars", schemars(default))]
pub set_type: ChipSetType,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(alias = "roms")]
pub chips: Vec<ChipConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub serve_alg: Option<ServeAlg>,
#[serde(skip_serializing_if = "Option::is_none")]
pub firmware_overrides: Option<FirmwareConfig>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ChipConfig {
#[serde(default)]
pub file: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub license: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(rename = "type")]
pub chip_type: ChipType,
#[serde(skip_serializing_if = "Option::is_none")]
pub cs1: Option<CsLogic>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cs2: Option<CsLogic>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cs3: Option<CsLogic>,
#[serde(default, skip_serializing_if = "SizeHandling::is_none")]
pub size_handling: SizeHandling,
#[serde(skip_serializing_if = "Option::is_none")]
pub extract: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<Location>,
}
impl ChipConfig {
fn filename(&self) -> String {
if let Some(label) = &self.label {
return label.clone();
}
let filename_base = if let Some(extract) = &self.extract {
format!("{}|{}", self.file, extract)
} else {
self.file.clone()
};
if let Some(location) = &self.location {
format!(
"{}|start={:#X},length={:#X}",
filename_base, location.start, location.length
)
} else {
filename_base
}
}
}