diff --git a/.gitignore b/.gitignore index 9c6ce49..401a0ab 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ /build.ps1 /TODO.md /cli/target +/temp/ diff --git a/Cargo.toml b/Cargo.toml index 861184b..e1c3ee9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,7 @@ resolver = "2" [profile.release] lto = true + +[profile.release-fast] +inherits = "release" +lto = false diff --git a/assets/arm9.lcf.template b/assets/arm9.lcf.template index ec701fe..f4f3818 100644 --- a/assets/arm9.lcf.template +++ b/assets/arm9.lcf.template @@ -2,6 +2,14 @@ MEMORY \{ {{ for module in modules -}} {module.name} : ORIGIN = {module.origin} > {module.output_file} {{ endfor }} + SPACE : ORIGIN = AFTER( + {{- for module in modules -}} + {{- if not module.in_tcm -}} + {{- if not @first -}}, {{ endif -}} + {module.name} + {{- endif -}} + {{- endfor -}} + ) } KEEP_SECTION \{ @@ -14,6 +22,10 @@ SECTIONS \{ {overlay.id_symbol} = {overlay.id}; {{ endfor }} + {{ for variable in variables -}} + {variable.symbol} = {variable.value}; + {{ endfor }} + {{ for module in modules -}} {module.link_section} : \{ ALIGNALL(4); diff --git a/cli/src/analysis/overlay_groups.rs b/cli/src/analysis/overlay_groups.rs index 6e75b80..48029d4 100644 --- a/cli/src/analysis/overlay_groups.rs +++ b/cli/src/analysis/overlay_groups.rs @@ -95,4 +95,8 @@ impl OverlayGroups { pub fn iter(&self) -> impl Iterator { self.groups.iter() } + + pub fn last(&self) -> Option<&OverlayGroup> { + self.groups.last() + } } diff --git a/cli/src/cmd/delink.rs b/cli/src/cmd/delink.rs index d45bce4..86a6b52 100644 --- a/cli/src/cmd/delink.rs +++ b/cli/src/cmd/delink.rs @@ -7,11 +7,11 @@ use std::{ use anyhow::{Context, Result, bail}; use clap::Args; use ds_decomp::config::{ - config::{Config, ConfigModule}, + config::Config, delinks::{DelinkFile, Delinks}, module::{Module, ModuleKind}, relocations::RelocationKind, - section::{DTCM_SECTION, SectionKind}, + section::{MigrateSection, Section, SectionKind}, symbol::{InstructionMode, SymFunction, SymbolKind, SymbolMaps}, }; use ds_rom::rom::{Rom, RomLoadOptions, raw::AutoloadKind}; @@ -20,7 +20,7 @@ use object::{Architecture, BinaryFormat, Endianness, RelocationFlags}; use super::Lcf; use crate::{ config::{ - delinks::DelinksExt, + delinks::{DelinksMap, DelinksMapOptions}, relocation::{RelocationKindExt, RelocationModuleExt}, section::SectionExt, symbol::{SymbolExt, SymbolKindExt}, @@ -53,9 +53,11 @@ struct Delinker<'a> { impl Delink { pub fn run(&self) -> Result<()> { let config = Config::from_file(&self.config_path)?; - let config_path = self.config_path.parent().unwrap().to_path_buf(); + let config_path = self.config_path.parent().unwrap(); - let symbol_maps = SymbolMaps::from_config(&config_path, &config)?; + let delinks_map = DelinksMap::from_config(&config, config_path, DelinksMapOptions { migrate_sections: true })?; + + let symbol_maps = SymbolMaps::from_config(config_path, &config)?; let rom = Rom::load(config_path.join(&config.rom_config), RomLoadOptions { key: None, compress: false, @@ -76,7 +78,7 @@ impl Delink { let mut delinker = Delinker { config: &config, - config_path, + config_path: config_path.to_path_buf(), rom, symbol_maps, elf_path, @@ -84,12 +86,8 @@ impl Delink { dtcm_end, }; - delinker.delink_module(&config.main_module, ModuleKind::Arm9)?; - for autoload in &config.autoloads { - delinker.delink_module(&autoload.module, ModuleKind::Autoload(autoload.kind))?; - } - for overlay in &config.overlays { - delinker.delink_module(&overlay.module, ModuleKind::Overlay(overlay.id))?; + for delinks in delinks_map.iter() { + delinker.delink_module(delinks)?; } Ok(()) @@ -97,23 +95,20 @@ impl Delink { } impl<'a> Delinker<'a> { - fn delink_module(&mut self, module_config: &ConfigModule, kind: ModuleKind) -> Result<()> { - let delinks = if kind == ModuleKind::Autoload(AutoloadKind::Dtcm) { - Delinks::new_dtcm(&self.config_path, self.config, module_config)? - } else { - Delinks::from_file_and_generate_gaps(self.config_path.join(&module_config.delinks), kind)? - }; - - let module = self.config.load_module(&self.config_path, &mut self.symbol_maps, kind, &self.rom)?; - let symbol_map = self.symbol_maps.get(kind).unwrap(); + fn delink_module(&mut self, delinks: &Delinks) -> Result<()> { + let module = self.config.load_module(&self.config_path, &mut self.symbol_maps, delinks.module_kind(), &self.rom)?; + let symbol_map = self.symbol_maps.get(delinks.module_kind()).unwrap(); for file in &delinks.files { for section in file.sections.iter() { - let (symbol_map, section_end) = if section.name() == DTCM_SECTION { - (self.symbol_maps.get(ModuleKind::Autoload(AutoloadKind::Dtcm)).unwrap(), self.dtcm_end) - } else { - let (_, module_section) = module.sections().by_name(section.name()).unwrap(); - (symbol_map, module_section.end_address()) + let (symbol_map, section_end) = match MigrateSection::parse(section.name()) { + MigrateSection::Dtcm => { + (self.symbol_maps.get(ModuleKind::Autoload(AutoloadKind::Dtcm)).unwrap(), self.dtcm_end) + } + MigrateSection::None => { + let (_, module_section) = module.sections().by_name(section.name()).unwrap(); + (symbol_map, module_section.end_address()) + } }; if let Some((symbol, size)) = symbol_map.get_symbol_containing(section.end_address() - 1, section_end)? @@ -157,115 +152,145 @@ impl<'a> Delinker<'a> { fn delink( &self, symbol_maps: &SymbolMaps, - module: &Module, - delink_file: &DelinkFile, - ) -> Result> { - let symbol_map = symbol_maps.get(module.kind()).unwrap(); - let dtcm_symbol_map = symbol_maps.get(ModuleKind::Autoload(AutoloadKind::Dtcm)).unwrap(); + module: &'a Module, + delink_file: &'a DelinkFile, + ) -> Result> { + let mut delink_object = DelinkObject::new(module, delink_file); + + for file_section in delink_file.sections.iter() { + delink_object.define_sections_and_symbols(symbol_maps, file_section, self.all_mapping_symbols)?; + } + for file_section in delink_file.migrated_sections.iter() { + delink_object.define_sections_and_symbols(symbol_maps, file_section, self.all_mapping_symbols)?; + } + delink_object.define_relocations(symbol_maps)?; + + Ok(delink_object.into_object()) + } +} + +struct DelinkObject<'a> { + object: object::write::Object<'a>, + // Maps address to ObjSection + obj_sections: BTreeMap, + // Maps address and module to ObjSymbol + obj_symbols: BTreeMap<(u32, ModuleKind), object::write::SymbolId>, + + module: &'a Module, + delink_file: &'a DelinkFile, +} + +impl<'a> DelinkObject<'a> { + fn new(module: &'a Module, delink_file: &'a DelinkFile) -> Self { let mut object = object::write::Object::new(BinaryFormat::Elf, Architecture::Arm, Endianness::Little); object.elf_is_rela = Some(true); - // Maps address to ObjSection/ObjSymbol - let mut obj_sections = BTreeMap::new(); - let mut obj_symbols = BTreeMap::new(); - - let mut error = false; + // Maps address to ObjSection + let obj_sections = BTreeMap::new(); + // Maps address and module to ObjSymbol + let obj_symbols = BTreeMap::new(); - for file_section in delink_file.sections.iter() { - // Get section data - let code = file_section.relocatable_code(module)?.unwrap_or_else(Vec::new); - let name = file_section.name().as_bytes().to_vec(); - let kind = match file_section.kind() { - SectionKind::Code => object::SectionKind::Text, - SectionKind::Data => object::SectionKind::Data, - SectionKind::Rodata => object::SectionKind::ReadOnlyData, - SectionKind::Bss => object::SectionKind::UninitializedData, - }; - - // Create section - let obj_section_id = object.add_section(vec![], name.clone(), kind); - let section = object.section_mut(obj_section_id); - if !file_section.kind().is_initialized() { - section.append_bss(file_section.size() as u64, 1); - } else { - let alignment = if file_section.kind().is_executable() { 4 } else { 1 }; - section.set_data(code, alignment); - } + Self { object, obj_sections, obj_symbols, module, delink_file } + } - // Add dummy symbol to make linker notice the section - object.add_symbol(object::write::Symbol { - name, // same name as section - value: 0, - size: 0, - kind: object::SymbolKind::Section, - scope: object::SymbolScope::Compilation, + fn define_sections_and_symbols( + &mut self, + symbol_maps: &SymbolMaps, + file_section: &Section, + all_mapping_symbols: bool, + ) -> Result<(), anyhow::Error> { + let code = file_section.relocatable_code(self.module)?.unwrap_or_else(Vec::new); + let name = file_section.name().as_bytes().to_vec(); + let kind = match file_section.kind() { + SectionKind::Code => object::SectionKind::Text, + SectionKind::Data => object::SectionKind::Data, + SectionKind::Rodata => object::SectionKind::ReadOnlyData, + SectionKind::Bss => object::SectionKind::UninitializedData, + }; + let obj_section_id = self.object.add_section(vec![], name.clone(), kind); + let section = self.object.section_mut(obj_section_id); + if !file_section.kind().is_initialized() { + section.append_bss(file_section.size() as u64, 1); + } else { + let alignment = if file_section.kind().is_executable() { 4 } else { 1 }; + section.set_data(code, alignment); + } + self.object.add_symbol(object::write::Symbol { + name, // same name as section + value: 0, + size: 0, + kind: object::SymbolKind::Section, + scope: object::SymbolScope::Compilation, + weak: false, + section: object::write::SymbolSection::Section(obj_section_id), + flags: object::SymbolFlags::None, + }); + let symbol_module = match MigrateSection::parse(file_section.name()) { + MigrateSection::Dtcm => ModuleKind::Autoload(AutoloadKind::Dtcm), + MigrateSection::None => self.module.kind(), + }; + let search_symbol_map = symbol_maps.get(symbol_module).context("Failed to find symbol map")?; + let mut symbols = search_symbol_map.iter_by_address(file_section.address_range()).filter(|s| !s.skip).peekable(); + while let Some(symbol) = symbols.next() { + // Get symbol data + let max_address = symbols.peek().map(|s| s.addr).unwrap_or(file_section.end_address()); + let kind = symbol.kind.as_obj_symbol_kind(); + let scope = symbol.get_obj_symbol_scope(); + let value = (symbol.addr - file_section.start_address()) as u64; + + // Create symbol + let symbol_section = object::write::SymbolSection::Section(obj_section_id); + let symbol_id = self.object.add_symbol(object::write::Symbol { + name: symbol.name.clone().into_bytes(), + value, + size: symbol.size(max_address) as u64, + kind, + scope, weak: false, - section: object::write::SymbolSection::Section(obj_section_id), + section: symbol_section, flags: object::SymbolFlags::None, }); - // Add symbols to section - let (search_symbol_map, symbol_module) = if file_section.name() == DTCM_SECTION { - (dtcm_symbol_map, ModuleKind::Autoload(AutoloadKind::Dtcm)) - } else { - (symbol_map, module.kind()) - }; - let mut symbols = search_symbol_map.iter_by_address(file_section.address_range()).filter(|s| !s.skip).peekable(); - while let Some(symbol) = symbols.next() { - // Get symbol data - let max_address = symbols.peek().map(|s| s.addr).unwrap_or(file_section.end_address()); - let kind = symbol.kind.as_obj_symbol_kind(); - let scope = symbol.get_obj_symbol_scope(); - let value = (symbol.addr - file_section.start_address()) as u64; - - // Create symbol - let symbol_section = object::write::SymbolSection::Section(obj_section_id); - let symbol_id = object.add_symbol(object::write::Symbol { - name: symbol.name.clone().into_bytes(), - value, - size: symbol.size(max_address) as u64, - kind, - scope, - weak: false, - section: symbol_section, - flags: object::SymbolFlags::None, - }); - - let is_thumb = matches!(symbol.kind, SymbolKind::Function(SymFunction { mode: InstructionMode::Thumb, .. })); - let thumb_bit = if is_thumb { 1 } else { 0 }; - obj_symbols.insert((symbol.addr | thumb_bit, symbol_module), symbol_id); - - if self.all_mapping_symbols - || matches!(symbol.kind, SymbolKind::Function(_) | SymbolKind::Label(_) | SymbolKind::PoolConstant) - { - // Create mapping symbol - if let Some(name) = symbol.mapping_symbol_name() { - object.add_symbol(object::write::Symbol { - name: name.to_string().into_bytes(), - value, - size: 0, - kind: object::SymbolKind::Label, - scope: object::SymbolScope::Compilation, - weak: false, - section: symbol_section, - flags: object::SymbolFlags::None, - }); - } + let is_thumb = matches!(symbol.kind, SymbolKind::Function(SymFunction { mode: InstructionMode::Thumb, .. })); + let thumb_bit = if is_thumb { 1 } else { 0 }; + self.obj_symbols.insert((symbol.addr | thumb_bit, symbol_module), symbol_id); + + if all_mapping_symbols + || matches!(symbol.kind, SymbolKind::Function(_) | SymbolKind::Label(_) | SymbolKind::PoolConstant) + { + // Create mapping symbol + if let Some(name) = symbol.mapping_symbol_name() { + self.object.add_symbol(object::write::Symbol { + name: name.to_string().into_bytes(), + value, + size: 0, + kind: object::SymbolKind::Label, + scope: object::SymbolScope::Compilation, + weak: false, + section: symbol_section, + flags: object::SymbolFlags::None, + }); } } - - obj_sections.insert(file_section.start_address(), obj_section_id); } + self.obj_sections.insert(file_section.start_address(), obj_section_id); + Ok(()) + } + + fn define_relocations(&mut self, symbol_maps: &SymbolMaps) -> Result<()> { + let symbol_map = symbol_maps.get(self.module.kind()).unwrap(); + + let mut error = false; // Maps overlay ID to ObjSymbol let mut overlay_id_symbols = BTreeMap::new(); // Must start a new loop here so we can know which section a symbol ID belongs to - for file_section in delink_file.sections.iter() { - let obj_section_id = *obj_sections.get(&file_section.start_address()).unwrap(); + for file_section in self.delink_file.sections.iter() { + let obj_section_id = *self.obj_sections.get(&file_section.start_address()).unwrap(); // Add relocations to section - for (_, relocation) in module.relocations().iter_range(file_section.address_range()) { + for (_, relocation) in self.module.relocations().iter_range(file_section.address_range()) { // Get relocation data let offset = relocation.from_address() - file_section.start_address(); let dest_addr = relocation.to_address(); @@ -277,7 +302,7 @@ impl<'a> Delinker<'a> { *symbol_id } else { // Create overlay ID symbol - let symbol_id = object.add_symbol(object::write::Symbol { + let symbol_id = self.object.add_symbol(object::write::Symbol { name: Lcf::overlay_id_symbol_name(overlay_id as u16).into_bytes(), value: 0, size: 0, @@ -296,7 +321,7 @@ impl<'a> Delinker<'a> { log::warn!( "No module for relocation from {:#010x} in {} to {:#010x}", relocation.from_address(), - module.kind(), + self.module.kind(), dest_addr, ); continue; @@ -304,7 +329,7 @@ impl<'a> Delinker<'a> { // Get destination symbol let symbol_key = (dest_addr, reloc_module); - if let Some(obj_symbol) = obj_symbols.get(&symbol_key) { + if let Some(obj_symbol) = self.obj_symbols.get(&symbol_key) { // Use existing symbol *obj_symbol } else { @@ -318,7 +343,7 @@ impl<'a> Delinker<'a> { log::error!( "No symbol found for relocation from {:#010x} in {} to {:#010x} in {}", relocation.from_address(), - module.kind(), + self.module.kind(), dest_addr, reloc_module ); @@ -337,7 +362,7 @@ impl<'a> Delinker<'a> { dest_addr, reloc_module, relocation.from_address(), - module.kind(), + self.module.kind(), reloc_base, offset, ); @@ -348,7 +373,7 @@ impl<'a> Delinker<'a> { // Add external symbol to section let kind = relocation.kind().as_obj_symbol_kind(); let symbol_section = object::write::SymbolSection::Undefined; - let symbol_id = object.add_symbol(object::write::Symbol { + let symbol_id = self.object.add_symbol(object::write::Symbol { name: symbol.name.clone().into_bytes(), value: 0, size: 0, @@ -358,7 +383,7 @@ impl<'a> Delinker<'a> { section: symbol_section, flags: object::SymbolFlags::None, }); - obj_symbols.insert(symbol_key, symbol_id); + self.obj_symbols.insert(symbol_key, symbol_id); symbol_id } }; @@ -366,7 +391,7 @@ impl<'a> Delinker<'a> { // Create relocation let r_type = relocation.kind().as_elf_relocation_type(); let addend = relocation.addend(); - object.add_relocation(obj_section_id, object::write::Relocation { + self.object.add_relocation(obj_section_id, object::write::Relocation { offset: offset as u64, symbol: symbol_id, addend, @@ -374,11 +399,13 @@ impl<'a> Delinker<'a> { })?; } } - if error { - bail!("Failed to delink '{}', see errors above", delink_file.name); + bail!("Failed to delink '{}', see errors above", self.delink_file.name); } + Ok(()) + } - Ok(object) + fn into_object(self) -> object::write::Object<'a> { + self.object } } diff --git a/cli/src/cmd/dis.rs b/cli/src/cmd/dis.rs index 6fb990c..781a3b1 100644 --- a/cli/src/cmd/dis.rs +++ b/cli/src/cmd/dis.rs @@ -7,9 +7,9 @@ use std::{ use anyhow::{Context, Result}; use clap::Args; use ds_decomp::config::{ - config::{Config, ConfigModule}, + config::Config, delinks::{DelinkFile, Delinks}, - module::{Module, ModuleKind}, + module::Module, section::Section, symbol::{InstructionMode, Symbol, SymbolKind, SymbolMaps}, }; @@ -18,7 +18,7 @@ use ds_rom::rom::{Rom, RomLoadOptions}; use crate::{ analysis::functions::FunctionExt, config::{ - delinks::DelinksExt, + delinks::{DelinksMap, DelinksMapOptions}, symbol::{SymDataExt, SymbolLookup}, }, util::io::create_file, @@ -45,6 +45,13 @@ impl Disassemble { let config = Config::from_file(&self.config_path)?; let config_path = self.config_path.parent().unwrap(); + let delinks_map = DelinksMap::from_config(&config, config_path, DelinksMapOptions { + // Set to false because we want to disassemble migrated sections like .dtcm in the same + // file. Setting this to true actually creates two files, but one gets overwritten and + // deleted by the other. + migrate_sections: false, + })?; + let rom_config_path = config_path.join(&config.rom_config); let rom = Rom::load(&rom_config_path, RomLoadOptions { key: None, @@ -57,31 +64,17 @@ impl Disassemble { })?; let mut symbol_maps = SymbolMaps::from_config(config_path, &config)?; - - self.disassemble_module(&config, &config.main_module, ModuleKind::Arm9, &mut symbol_maps, &rom)?; - for autoload in &config.autoloads { - self.disassemble_module(&config, &autoload.module, ModuleKind::Autoload(autoload.kind), &mut symbol_maps, &rom)?; - } - for overlay in &config.overlays { - self.disassemble_module(&config, &overlay.module, ModuleKind::Overlay(overlay.id), &mut symbol_maps, &rom)?; + for delinks in delinks_map.iter() { + self.disassemble_module(&config, delinks, &mut symbol_maps, &rom)?; } Ok(()) } - fn disassemble_module( - &self, - config: &Config, - module_config: &ConfigModule, - kind: ModuleKind, - symbol_maps: &mut SymbolMaps, - rom: &Rom, - ) -> Result<()> { + fn disassemble_module(&self, config: &Config, delinks: &Delinks, symbol_maps: &mut SymbolMaps, rom: &Rom) -> Result<()> { let config_path = self.config_path.parent().unwrap(); - let delinks = Delinks::from_file_and_generate_gaps(config_path.join(&module_config.delinks), kind)?; - - let module = config.load_module(config_path, symbol_maps, kind, rom)?; + let module = config.load_module(config_path, symbol_maps, delinks.module_kind(), rom)?; for file in &delinks.files { let (file_path, _) = file.split_file_ext(); diff --git a/cli/src/cmd/json/delinks.rs b/cli/src/cmd/json/delinks.rs index cb75bfe..61239ea 100644 --- a/cli/src/cmd/json/delinks.rs +++ b/cli/src/cmd/json/delinks.rs @@ -1,16 +1,15 @@ -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use anyhow::Result; use clap::Args; -use ds_decomp::config::{ - config::Config, - delinks::{DelinkFile, Delinks}, - module::ModuleKind, -}; -use ds_rom::rom::raw::AutoloadKind; +use ds_decomp::config::config::Config; use serde::Serialize; -use crate::{cmd::ARM9_LCF_FILE_NAME, config::delinks::DelinksExt, util::path::PathExt}; +use crate::{ + cmd::ARM9_LCF_FILE_NAME, + config::delinks::{DelinksMap, DelinksMapOptions}, + util::path::PathExt, +}; #[derive(Args)] pub struct JsonDelinks { @@ -49,15 +48,21 @@ impl JsonDelinks { let build_path = config_dir.join(&config.build_path); let delinks_path = config_dir.join(&config.delinks_path); - let files = Self::get_delink_files(config_dir, &config)? - .into_iter() + let delinks_map = DelinksMap::from_config(&config, config_dir, DelinksMapOptions { + // Migrating would create delink files with identical names, which is wasteful when we + // call `DelinksMap::delink_files()` later + migrate_sections: false, + })?; + + let files = delinks_map + .delink_files() .map(|file| { let (file_path, _) = file.split_file_ext(); let base_path = if file.complete { &build_path } else { &delinks_path }; let object_to_link = base_path.join(file_path).clean().with_extension("o"); let delink_file = delinks_path.join(file_path).clean().with_extension("o"); - Ok(DelinkFileJson { name: file.name, delink_file, object_to_link }) + Ok(DelinkFileJson { name: file.name.clone(), delink_file, object_to_link }) }) .collect::>>()?; @@ -66,43 +71,4 @@ impl JsonDelinks { Ok(ModulesJson { arm9_lcf_file, arm9_objects_file, files }) } - - pub fn get_delink_files(config_dir: &Path, config: &Config) -> Result> { - let mut delink_files = vec![]; - - // Main module - delink_files.extend( - Delinks::from_file_and_generate_gaps(config_dir.join(&config.main_module.delinks), ModuleKind::Arm9)? - .without_dtcm_sections() - .files, - ); - - // Autoloads - for autoload in &config.autoloads { - let delinks = if autoload.kind == AutoloadKind::Dtcm { - Delinks::new_dtcm(config_dir, config, &autoload.module)? - } else { - Delinks::from_file_and_generate_gaps( - config_dir.join(&autoload.module.delinks), - ModuleKind::Autoload(autoload.kind), - )? - .without_dtcm_sections() - }; - delink_files.extend(delinks.files); - } - - // Overlays - for overlay in &config.overlays { - delink_files.extend( - Delinks::from_file_and_generate_gaps( - config_dir.join(&overlay.module.delinks), - ModuleKind::Overlay(overlay.id), - )? - .without_dtcm_sections() - .files, - ); - } - - Ok(delink_files) - } } diff --git a/cli/src/cmd/lcf.rs b/cli/src/cmd/lcf.rs index 51e0085..e6caad7 100644 --- a/cli/src/cmd/lcf.rs +++ b/cli/src/cmd/lcf.rs @@ -1,24 +1,23 @@ use std::{ collections::{HashMap, hash_map}, + fmt::Display, io::{BufWriter, Write}, path::{Path, PathBuf}, }; -use anyhow::{Result, bail}; +use anyhow::{Context, Result, bail}; use clap::Args; -use ds_decomp::config::{ - config::{Config, ConfigModule}, - delinks::Delinks, - module::ModuleKind, -}; +use ds_decomp::config::{config::Config, delinks::Delinks, module::ModuleKind}; use ds_rom::rom::{Rom, RomLoadOptions, raw::AutoloadKind}; use serde::Serialize; use tinytemplate::TinyTemplate; use crate::{ analysis::overlay_groups::OverlayGroups, - cmd::JsonDelinks, - config::{delinks::DelinksExt, section::SectionExt}, + config::{ + delinks::{DelinksMap, DelinksMapOptions}, + section::SectionExt, + }, util::{ io::{create_dir_all, create_file_and_dirs}, path::PathExt, @@ -47,6 +46,7 @@ struct LcfModule { link_section: String, object: String, sections: Vec, + in_tcm: bool, } #[derive(Serialize, Clone)] @@ -68,6 +68,7 @@ struct LcfFile { struct Arm9LcfContext { modules: Vec, overlays: Vec, + variables: Vec, } #[derive(Serialize)] @@ -76,12 +77,29 @@ struct Arm9LcfOverlay { id: u16, } +#[derive(Serialize)] +struct LcfVariable { + symbol: String, + value: String, +} + +impl LcfVariable { + fn new(symbol: impl Display, value: impl Display) -> Self { + Self { symbol: symbol.to_string(), value: value.to_string() } + } +} + impl Lcf { pub fn run(&self) -> Result<()> { let config = Config::from_file(&self.config_path)?; - self.validate_all_file_names(&config)?; let config_dir = self.config_path.parent().unwrap(); + let delinks_map = DelinksMap::from_config(&config, config_dir, DelinksMapOptions { + // We want migrated sections to be linked in the module it belongs to + migrate_sections: true, + })?; + self.validate_all_file_names(&delinks_map)?; + let rom = Rom::load(config_dir.join(&config.rom_config), RomLoadOptions { key: None, compress: false, @@ -94,11 +112,12 @@ impl Lcf { let build_path = config_dir.join(&config.build_path).clean(); - let link_modules = LinkModules::new(&rom, &config, config_dir)?; + let link_modules = LinkModules::new(&rom, &config, &delinks_map)?; let mut tt = TinyTemplate::new(); tt.add_template("arm9", ARM9_LCF_TEMPLATE)?; + let variables = self.generate_lcf_variables(&config)?; let arm9_context = Arm9LcfContext { modules: link_modules.modules, overlays: config @@ -106,9 +125,10 @@ impl Lcf { .iter() .map(|overlay| Arm9LcfOverlay { id_symbol: Self::overlay_id_symbol_name(overlay.id), id: overlay.id }) .collect(), + variables, }; self.write_arm9_lcf(&arm9_context, &tt, &build_path)?; - self.write_arm9_objects(&config, &build_path)?; + self.write_arm9_objects(&config, &build_path, &delinks_map)?; // mwldarm doesn't create the build directory for the modules create_dir_all(build_path.join("build"))?; @@ -116,6 +136,19 @@ impl Lcf { Ok(()) } + fn generate_lcf_variables(&self, config: &Config) -> Result> { + let mut variables = Vec::new(); + + let overlay_count = config.overlays.len(); + + variables.push(LcfVariable::new("__DTCM_LO", "ADDR(DTCM)")); + variables.push(LcfVariable::new("__ITCM_HI", "ADDR(ITCM) + SIZEOF(ITCM)")); + variables.push(LcfVariable::new("__CODE_HI", "ADDR(SPACE)")); + variables.push(LcfVariable::new("__OVERLAY_COUNT", overlay_count)); + + Ok(variables) + } + fn write_arm9_lcf(&self, context: &Arm9LcfContext, tt: &TinyTemplate, lcf_path: &Path) -> Result<()> { let lcf_file_path = lcf_path.join("arm9.lcf"); let lcf_string = tt.render("arm9", &context)?; @@ -127,11 +160,11 @@ impl Lcf { Ok(()) } - fn write_arm9_objects(&self, config: &Config, lcf_path: &Path) -> Result<()> { + fn write_arm9_objects(&self, config: &Config, lcf_path: &Path, delinks_map: &DelinksMap) -> Result<()> { let config_dir = self.config_path.parent().unwrap(); let objects_file_path = lcf_path.join(ARM9_OBJECTS_FILE_NAME); let mut writer = BufWriter::new(create_file_and_dirs(objects_file_path)?); - for file in JsonDelinks::get_delink_files(config_dir, config)?.iter() { + for file in delinks_map.delink_files() { let (file_path, _) = file.split_file_ext(); let base_path = if file.complete { &config_dir.join(&config.build_path) @@ -160,15 +193,11 @@ impl Lcf { } } - fn validate_all_file_names(&self, config: &Config) -> Result<()> { + fn validate_all_file_names(&self, delinks_map: &DelinksMap) -> Result<()> { let mut delink_files: HashMap = HashMap::new(); let mut success = true; - success &= self.validate_file_names(&config.main_module, ModuleKind::Arm9, &mut delink_files)?; - for autoload in &config.autoloads { - success &= self.validate_file_names(&autoload.module, ModuleKind::Autoload(autoload.kind), &mut delink_files)?; - } - for overlay in &config.overlays { - success &= self.validate_file_names(&overlay.module, ModuleKind::Overlay(overlay.id), &mut delink_files)?; + for delinks in delinks_map.iter() { + success &= self.validate_file_names(delinks, &mut delink_files)?; } if !success { bail!("Duplicate file names found, see logs above"); @@ -176,24 +205,26 @@ impl Lcf { Ok(()) } - fn validate_file_names( - &self, - config_module: &ConfigModule, - module_kind: ModuleKind, - delink_files: &mut HashMap, - ) -> Result { - let config_dir = self.config_path.parent().unwrap(); - let delinks = Delinks::from_file(config_dir.join(&config_module.delinks), module_kind)?; + fn validate_file_names(&self, delinks: &Delinks, delink_files: &mut HashMap) -> Result { let mut success = true; - for file in delinks.files { + for file in &delinks.files { + if file.migrated() { + // Migrated files will have the same name as the original, that is intentional + continue; + } let filename = file.name.rsplit_once(['/', '\\']).unwrap_or(("", &file.name)).1; match delink_files.entry(filename.to_string()) { hash_map::Entry::Occupied(occupied_entry) => { - log::error!("Delink file name '{}' in {} already used in {}", filename, module_kind, occupied_entry.get()); + log::error!( + "Delink file name '{}' in {} already used in {}", + filename, + delinks.module_kind(), + occupied_entry.get() + ); success = false; } hash_map::Entry::Vacant(vacant_entry) => { - vacant_entry.insert(module_kind); + vacant_entry.insert(delinks.module_kind()); } } } @@ -202,7 +233,7 @@ impl Lcf { } impl LcfModule { - fn new(kind: ModuleKind, origin: String, config: &Config, config_dir: &Path) -> Result { + fn new(kind: ModuleKind, origin: String, config: &Config, delinks_map: &DelinksMap) -> Result { let module_config = match kind { ModuleKind::Arm9 => &config.main_module, ModuleKind::Autoload(autoload) => &config.autoloads.iter().find(|a| a.kind == autoload).unwrap().module, @@ -223,12 +254,7 @@ impl LcfModule { let object = format!("{}.o", module_config.name); - let delinks_path = config_dir.join(&module_config.delinks).clean(); - let delinks = if kind == ModuleKind::Autoload(AutoloadKind::Dtcm) { - Delinks::new_dtcm(config_dir, config, module_config)? - } else { - Delinks::from_file_and_generate_gaps(delinks_path, kind)?.without_dtcm_sections() - }; + let delinks = delinks_map.get(kind).context("Failed to find delinks")?; let sections = delinks .sections @@ -258,7 +284,9 @@ impl LcfModule { let end_address = delinks.sections.end_address().unwrap(); - Ok(Self { name: module_name, origin, end_address, output_file, link_section, object, sections }) + let in_tcm = matches!(kind, ModuleKind::Autoload(AutoloadKind::Itcm | AutoloadKind::Dtcm)); + + Ok(Self { name: module_name, origin, end_address, output_file, link_section, object, sections, in_tcm }) } } @@ -268,8 +296,8 @@ struct LinkModules { } impl LinkModules { - pub fn new(rom: &Rom<'_>, config: &Config, config_dir: &Path) -> Result { - let mut link_modules = Self::find_static(rom, config, config_dir)?; + pub fn new(rom: &Rom<'_>, config: &Config, delinks_map: &DelinksMap) -> Result { + let mut link_modules = Self::find_static(rom, config, delinks_map)?; let static_end_address = link_modules.last_static_module().end_address; log::debug!("Static end address: {:#010x}", static_end_address); let overlay_groups = OverlayGroups::analyze(static_end_address, rom.arm9_overlays())?; @@ -282,16 +310,16 @@ impl LinkModules { }; for &overlay_id in &group.overlays { let kind = ModuleKind::Overlay(overlay_id); - link_modules.modules.push(LcfModule::new(kind, origin.clone(), config, config_dir)?); + link_modules.modules.push(LcfModule::new(kind, origin.clone(), config, delinks_map)?); } } Ok(link_modules) } - fn find_static(rom: &Rom<'_>, config: &Config, config_dir: &Path) -> Result { + fn find_static(rom: &Rom<'_>, config: &Config, delinks_map: &DelinksMap) -> Result { let arm9 = rom.arm9(); let mut modules = vec![]; - modules.push(LcfModule::new(ModuleKind::Arm9, format!("{:#010x}", arm9.base_address()), config, config_dir)?); + modules.push(LcfModule::new(ModuleKind::Arm9, format!("{:#010x}", arm9.base_address()), config, delinks_map)?); let mut prev_static_index = 0; // Find contiguous autoloads after the main program @@ -305,7 +333,7 @@ impl LinkModules { } else { format!("{:#010x}", autoload.base_address()) }; - modules.push(LcfModule::new(ModuleKind::Autoload(autoload.kind()), origin, config, config_dir)?); + modules.push(LcfModule::new(ModuleKind::Autoload(autoload.kind()), origin, config, delinks_map)?); } Ok(Self { modules, last_static_index: prev_static_index }) } diff --git a/cli/src/cmd/objdiff.rs b/cli/src/cmd/objdiff.rs index 43b9307..2d0e25c 100644 --- a/cli/src/cmd/objdiff.rs +++ b/cli/src/cmd/objdiff.rs @@ -6,14 +6,13 @@ use std::{ use anyhow::Result; use clap::Args; use ds_decomp::config::{ - config::{Config, ConfigModule}, + config::Config, delinks::{Categories, Delinks}, - module::ModuleKind, }; use objdiff_core::config::{ProjectObject, ProjectProgressCategory}; use crate::{ - config::delinks::DelinksExt, + config::delinks::{DelinksMap, DelinksMapOptions}, util::{io::create_dir_all, path::PathExt}, }; @@ -75,17 +74,18 @@ impl Objdiff { } } - let (mut units, mut categories) = - self.get_units(&config.main_module, ModuleKind::Arm9, config_path, &config, &abs_output_path)?; - for autoload in &config.autoloads { - let (new_units, new_categories) = - self.get_units(&autoload.module, ModuleKind::Autoload(autoload.kind), config_path, &config, &abs_output_path)?; - units.extend(new_units); - categories.extend(new_categories); - } - for overlay in &config.overlays { - let (new_units, new_categories) = - self.get_units(&overlay.module, ModuleKind::Overlay(overlay.id), config_path, &config, &abs_output_path)?; + let delinks_map = DelinksMap::from_config(&config, config_path, DelinksMapOptions { + // Migrating sections causes affected delink files to be separated into two or more + // files with identical names, but in objdiff prefer to view all sections in just one + // unit. + migrate_sections: false, + })?; + + let mut units = Vec::new(); + let mut categories = Categories::new(); + + for delinks in delinks_map.iter() { + let (new_units, new_categories) = self.get_units(delinks, config_path, &config, &abs_output_path)?; units.extend(new_units); categories.extend(new_categories); } @@ -156,13 +156,11 @@ impl Objdiff { fn get_units( &self, - module: &ConfigModule, - module_kind: ModuleKind, + delinks: &Delinks, config_path: &Path, config: &Config, abs_output_path: &Path, ) -> Result<(Vec, Categories)> { - let delinks: Delinks = Delinks::from_file_and_generate_gaps(config_path.join(&module.delinks), module_kind)?; let mut all_categories = delinks.global_categories.clone(); let units = delinks .files diff --git a/cli/src/config/delinks.rs b/cli/src/config/delinks.rs index f6fe570..cd5bc34 100644 --- a/cli/src/config/delinks.rs +++ b/cli/src/config/delinks.rs @@ -1,12 +1,17 @@ -use std::{cmp::Ordering, collections::HashMap, path::Path}; +use std::{ + cmp::Ordering, + collections::{BTreeMap, HashMap}, + ops::Range, + path::Path, +}; use anyhow::{Context, Result, bail}; use ds_decomp::config::{ Comments, - config::{Config, ConfigModule}, + config::Config, delinks::{Categories, DelinkFile, DelinkFileOptions, Delinks}, module::ModuleKind, - section::{DTCM_SECTION, Section, SectionInheritOptions, Sections}, + section::{MigrateSection, Section, SectionInheritOptions, Sections}, }; use ds_rom::rom::raw::AutoloadKind; use petgraph::{Graph, graph::NodeIndex}; @@ -15,54 +20,16 @@ pub trait DelinksExt where Self: Sized, { - fn from_file_and_generate_gaps>(path: P, module_kind: ModuleKind) -> Result; - fn without_dtcm_sections(self) -> Self; - fn new_dtcm>(config_path: P, config: &Config, dtcm_config: &ConfigModule) -> Result; fn sort_files(&mut self) -> Result<()>; + fn generate_gap_files(&mut self) -> Result<()>; } trait DelinksPrivExt { - fn generate_gap_files(&mut self) -> Result<()>; fn compare_files(&self, a: &DelinkFile, b: &DelinkFile) -> Ordering; fn validate_files(&self) -> Result<()>; - fn append_dtcm_sections>( - &mut self, - config_path: P, - module_config: &ConfigModule, - module_kind: ModuleKind, - ) -> Result<()>; + fn migrate_section(&mut self, section: &Section) -> Result>; } impl DelinksExt for Delinks { - fn from_file_and_generate_gaps>(path: P, module_kind: ModuleKind) -> Result { - let mut delinks = Delinks::from_file(path, module_kind)?; - delinks.generate_gap_files()?; - Ok(delinks) - } - - fn without_dtcm_sections(mut self) -> Self { - for file in &mut self.files { - file.sections.remove(DTCM_SECTION); - } - self - } - - fn new_dtcm>(config_path: P, config: &Config, dtcm_config: &ConfigModule) -> Result { - let config_path = config_path.as_ref(); - let mut delinks = - Delinks::from_file(config_path.join(&dtcm_config.delinks), ModuleKind::Autoload(AutoloadKind::Dtcm))?; - delinks.append_dtcm_sections(config_path, &config.main_module, ModuleKind::Arm9)?; - for autoload in &config.autoloads { - if autoload.kind != AutoloadKind::Dtcm { - delinks.append_dtcm_sections(config_path, &autoload.module, ModuleKind::Autoload(autoload.kind))?; - } - } - for overlay in &config.overlays { - delinks.append_dtcm_sections(config_path, &overlay.module, ModuleKind::Overlay(overlay.id))?; - } - delinks.generate_gap_files()?; - Ok(delinks) - } - fn sort_files(&mut self) -> Result<()> { let mut graph = Graph::<(), ()>::new(); @@ -109,8 +76,7 @@ impl DelinksExt for Delinks { Ok(()) } -} -impl DelinksPrivExt for Delinks { + fn generate_gap_files(&mut self) -> Result<()> { self.sort_files()?; self.validate_files()?; @@ -170,7 +136,9 @@ impl DelinksPrivExt for Delinks { Ok(()) } +} +impl DelinksPrivExt for Delinks { fn compare_files(&self, a: &DelinkFile, b: &DelinkFile) -> Ordering { for section in self.sections.iter() { let Some((_, a_section)) = a.sections.by_name(section.name()) else { @@ -243,39 +211,38 @@ impl DelinksPrivExt for Delinks { Ok(()) } - fn append_dtcm_sections>( - &mut self, - config_path: P, - module_config: &ConfigModule, - module_kind: ModuleKind, - ) -> Result<()> { - let Some((_, bss_section)) = self.sections.by_name(DTCM_SECTION) else { - return Ok(()); - }; - - let config_path = config_path.as_ref(); - let delinks_path = config_path.join(&module_config.delinks); - let delinks = Delinks::from_file(delinks_path, module_kind)?; - - for delink_file in delinks.files.into_iter() { - let Some((_, dtcm_section)) = delink_file.sections.by_name(DTCM_SECTION) else { - continue; - }; - self.files.push(DelinkFile::new(DelinkFileOptions { - name: delink_file.name, - sections: Sections::from_sections(vec![Section::inherit(bss_section, SectionInheritOptions { - start_address: dtcm_section.start_address(), - end_address: dtcm_section.end_address(), - comments: Comments::new(), - })?])?, + fn migrate_section(&mut self, section: &Section) -> Result> { + fn migrate_delink_file( + section: &Section, + address_range: Range, + delink_file: &mut DelinkFile, + ) -> Result { + let new_section = Section::inherit(section, SectionInheritOptions { + start_address: address_range.start, + end_address: address_range.end, + comments: Comments::new(), + })?; + let new_sections = Sections::from_sections(vec![new_section])?; + let new_file = DelinkFile::new(DelinkFileOptions { + name: delink_file.name.clone(), + sections: new_sections, complete: delink_file.complete, categories: delink_file.categories.clone(), gap: false, + migrated: true, comments: Comments::new(), - })); + }); + delink_file.migrate_section_by_name(section.name())?; + Ok(new_file) } - Ok(()) + self.files + .iter_mut() + .filter_map(|delink_file| { + let (_, migrated_section) = delink_file.sections.by_name(section.name())?; + Some(migrate_delink_file(section, migrated_section.address_range(), delink_file)) + }) + .collect() } } @@ -286,15 +253,17 @@ where fn new_gap(module_kind: ModuleKind, id: usize) -> Result; } +const GAP_FILE_PREFIX: &str = "_dsd_gap@"; + impl DelinkFileExt for DelinkFile { fn new_gap(module_kind: ModuleKind, id: usize) -> Result { let name = match module_kind { - ModuleKind::Arm9 => format!("_dsd_gap$main_{id}"), - ModuleKind::Overlay(overlay_id) => format!("_dsd_gap$ov{overlay_id:03}_{id}"), + ModuleKind::Arm9 => format!("{GAP_FILE_PREFIX}main_{id}"), + ModuleKind::Overlay(overlay_id) => format!("{GAP_FILE_PREFIX}ov{overlay_id:03}_{id}"), ModuleKind::Autoload(kind) => match kind { - AutoloadKind::Itcm => format!("_dsd_gap$itcm_{id}"), - AutoloadKind::Dtcm => format!("_dsd_gap$dtcm_{id}"), - AutoloadKind::Unknown(index) => format!("_dsd_gap$autoload_{index}_{id}"), + AutoloadKind::Itcm => format!("{GAP_FILE_PREFIX}itcm_{id}"), + AutoloadKind::Dtcm => format!("{GAP_FILE_PREFIX}dtcm_{id}"), + AutoloadKind::Unknown(index) => format!("{GAP_FILE_PREFIX}autoload_{index}_{id}"), }, }; @@ -304,7 +273,77 @@ impl DelinkFileExt for DelinkFile { complete: false, categories: Categories::new(), gap: true, + migrated: false, comments: Comments::new(), })) } } + +pub struct DelinksMap { + map: BTreeMap, +} + +pub struct DelinksMapOptions { + pub migrate_sections: bool, +} + +impl DelinksMap { + pub fn from_config(config: &Config, path: impl AsRef, options: DelinksMapOptions) -> Result { + let path = path.as_ref(); + let map = config + .iter_modules() + .map(|(kind, config)| { + let delinks = Delinks::from_file(path.join(&config.delinks), kind)?; + Ok((kind, delinks)) + }) + .collect::>>()?; + let mut map = DelinksMap { map }; + + if options.migrate_sections { + map.migrate_sections()?; + } + for delinks in map.map.values_mut() { + delinks.generate_gap_files()?; + } + Ok(map) + } + + fn migrate_sections(&mut self) -> Result<()> { + let modules = self.map.keys().copied().collect::>(); + + for target_module in modules.iter() { + let migrate_section = MigrateSection::from(target_module); + let Some(section_name) = migrate_section.name() else { continue }; + + let section = { + let target = self.map.get(target_module).context("Failed to find target module of section migration")?; + let Some((_, section)) = target.sections.by_name(section_name) else { + continue; + }; + section.clone() + }; + + for source_module in modules.iter() { + let source = self.map.get_mut(source_module).unwrap(); + let files = source.migrate_section(§ion)?; + + let target = self.map.get_mut(target_module).unwrap(); + target.files.extend(files.into_iter()); + } + } + + Ok(()) + } + + pub fn get(&self, kind: ModuleKind) -> Option<&Delinks> { + self.map.get(&kind) + } + + pub fn iter(&self) -> impl Iterator { + self.map.values() + } + + pub fn delink_files(&self) -> impl Iterator { + self.iter().flat_map(|delinks| delinks.files.iter()) + } +} diff --git a/cli/tests/test_roundtrip.rs b/cli/tests/test_roundtrip.rs index f76f7a4..413d550 100644 --- a/cli/tests/test_roundtrip.rs +++ b/cli/tests/test_roundtrip.rs @@ -24,7 +24,7 @@ use zip::ZipArchive; #[test] fn test_roundtrip() -> Result<()> { - env_logger::builder().filter_level(LevelFilter::Debug).init(); + env_logger::builder().filter_level(LevelFilter::Info).init(); let cwd = std::env::current_dir()?; let assets_dir = cwd.join("tests/assets"); diff --git a/lib/src/config/config.rs b/lib/src/config/config.rs index cab20f7..3146f31 100644 --- a/lib/src/config/config.rs +++ b/lib/src/config/config.rs @@ -107,6 +107,12 @@ impl Config { load_multiboot_signature: false, }) } + + pub fn iter_modules(&self) -> impl Iterator { + std::iter::once((ModuleKind::Arm9, &self.main_module)) + .chain(self.autoloads.iter().map(|a| (ModuleKind::Autoload(a.kind), &a.module))) + .chain(self.overlays.iter().map(|o| (ModuleKind::Overlay(o.id), &o.module))) + } } #[derive(Serialize, Deserialize)] diff --git a/lib/src/config/delinks.rs b/lib/src/config/delinks.rs index afce7a5..513cd7c 100644 --- a/lib/src/config/delinks.rs +++ b/lib/src/config/delinks.rs @@ -137,12 +137,15 @@ impl Display for Delinks { } } +#[derive(Clone)] pub struct DelinkFile { pub name: String, pub sections: Sections, + pub migrated_sections: Sections, pub complete: bool, pub categories: Categories, gap: bool, + migrated: bool, pub comments: Comments, } @@ -164,14 +167,15 @@ pub struct DelinkFileOptions { pub complete: bool, pub categories: Categories, pub gap: bool, + pub migrated: bool, pub comments: Comments, } impl DelinkFile { pub fn new(options: DelinkFileOptions) -> Self { - let DelinkFileOptions { name, sections, complete, categories, gap, mut comments } = options; + let DelinkFileOptions { name, sections, complete, categories, gap, migrated, mut comments } = options; comments.remove_leading_blank_lines(); - Self { name, sections, complete, categories, gap, comments } + Self { name, sections, migrated_sections: Sections::new(), complete, categories, gap, migrated, comments } } pub fn parse( @@ -222,6 +226,7 @@ impl DelinkFile { complete, categories, gap: false, + migrated: false, comments: first_line.comments.clone(), })) } @@ -233,6 +238,18 @@ impl DelinkFile { pub fn gap(&self) -> bool { self.gap } + + pub fn migrated(&self) -> bool { + self.migrated + } + + pub fn migrate_section_by_name(&mut self, name: &str) -> Result<(), SectionsError> { + let Some(section) = self.sections.remove(name) else { + return Ok(()); + }; + self.migrated_sections.add(section)?; + Ok(()) + } } impl Display for DelinkFile { diff --git a/lib/src/config/section.rs b/lib/src/config/section.rs index 5dd3563..a5f6a61 100644 --- a/lib/src/config/section.rs +++ b/lib/src/config/section.rs @@ -6,18 +6,17 @@ use std::{ ops::Range, }; +use ds_rom::rom::raw::AutoloadKind; use serde::Serialize; use snafu::Snafu; use super::{ParseContext, iter_attributes, module::Module}; use crate::{ analysis::functions::Function, - config::{CommentedLine, Comments}, + config::{CommentedLine, Comments, module::ModuleKind}, util::{bytes::FromSlice, parse::parse_u32}, }; -pub const DTCM_SECTION: &str = ".dtcm"; - #[derive(Clone, Copy)] pub struct SectionIndex(pub usize); @@ -190,15 +189,16 @@ impl Section { return EmptyLineSnafu { context: context.clone() }.fail()?; }; - let inherit_section = if name != DTCM_SECTION { - Some( + let migrate_section = MigrateSection::parse(name); + + let inherit_section = match migrate_section { + MigrateSection::None => Some( sections .by_name(name) .map(|(_, section)| section) .ok_or_else(|| NotInHeaderSnafu { context, name }.build())?, - ) - } else { - None + ), + MigrateSection::Dtcm => None, }; let mut start = None; @@ -218,8 +218,8 @@ impl Section { let start = start.ok_or_else(|| MissingAttributeSnafu { context, attribute: "start" }.build())?; let end = end.ok_or_else(|| MissingAttributeSnafu { context, attribute: "end" }.build())?; - if name == DTCM_SECTION { - Ok(Section::new(SectionOptions { + match migrate_section { + MigrateSection::Dtcm => Ok(Section::new(SectionOptions { name: name.to_string(), kind: SectionKind::Bss, start_address: start, @@ -227,15 +227,16 @@ impl Section { alignment: 4, functions: None, comments: line.comments.clone(), - })?) - } else { - let inherit_section = inherit_section.unwrap(); - Ok(Section::inherit(inherit_section, SectionInheritOptions { - start_address: start, - end_address: end, - comments: line.comments.clone(), - }) - .map_err(|error| SectionSnafu { context, error }.build())?) + })?), + MigrateSection::None => { + let inherit_section = inherit_section.unwrap(); + Ok(Section::inherit(inherit_section, SectionInheritOptions { + start_address: start, + end_address: end, + comments: line.comments.clone(), + }) + .map_err(|error| SectionSnafu { context, error }.build())?) + } } } @@ -339,6 +340,42 @@ impl Display for Section { } } +#[derive(PartialEq, Eq)] +pub enum MigrateSection { + None, + Dtcm, +} + +const DTCM_SECTION: &str = ".dtcm"; + +impl MigrateSection { + pub fn parse(name: &str) -> Self { + match name { + DTCM_SECTION => Self::Dtcm, + _ => Self::None, + } + } + + pub fn name(&self) -> Option<&str> { + match self { + MigrateSection::None => None, + MigrateSection::Dtcm => Some(DTCM_SECTION), + } + } +} + +impl From<&ModuleKind> for MigrateSection { + fn from(value: &ModuleKind) -> Self { + match value { + ModuleKind::Arm9 => MigrateSection::None, + ModuleKind::Overlay(_) => MigrateSection::None, + ModuleKind::Autoload(AutoloadKind::Dtcm) => MigrateSection::Dtcm, + ModuleKind::Autoload(AutoloadKind::Itcm) => MigrateSection::None, + ModuleKind::Autoload(AutoloadKind::Unknown(_)) => MigrateSection::None, + } + } +} + #[derive(PartialEq, Eq, Clone, Copy, Serialize)] pub enum SectionKind { Code, @@ -448,15 +485,14 @@ impl Sections { Ok(index) } - pub fn remove(&mut self, name: &str) { - let Some(index) = self.sections_by_name.remove(name) else { - return; - }; - self.sections.remove(index.0); + pub fn remove(&mut self, name: &str) -> Option
{ + let index = self.sections_by_name.remove(name)?; + let section = self.sections.remove(index.0); // Update indices in sections_by_name for (i, section) in self.sections.iter().enumerate() { self.sections_by_name.insert(section.name.clone(), SectionIndex(i)); } + Some(section) } pub fn get(&self, index: SectionIndex) -> &Section { @@ -472,6 +508,11 @@ impl Sections { Some((index, &self.sections[index.0])) } + pub fn by_name_mut(&mut self, name: &str) -> Option<(SectionIndex, &mut Section)> { + let &index = self.sections_by_name.get(name)?; + Some((index, &mut self.sections[index.0])) + } + pub fn iter(&self) -> impl Iterator { self.sections.iter() }