From 7a6689efd1a6f2ec629385918328ffbb3dc88c82 Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Sun, 29 May 2022 19:16:49 -0400 Subject: [PATCH 1/3] Understand better how SNES addressing works. --- enemize/src/asar.rs | 47 ++++++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/enemize/src/asar.rs b/enemize/src/asar.rs index 6324302..f405e9d 100644 --- a/enemize/src/asar.rs +++ b/enemize/src/asar.rs @@ -1,33 +1,44 @@ use std::collections::HashMap; use std::fs::File; -use std::io::{BufReader, Read}; +use std::io::{BufReader, BufRead}; use std::path::Path; -pub type Symbols = HashMap; +pub type Symbols = HashMap; -impl Symbols { - pub fn load(filename: Path) -> anyhow::Result { - let file = File::open(filename)?; - let mut reader = BufReader::new(file); +pub fn load_symbols(filename: &Path) -> anyhow::Result { + let file = File::open(filename)?; + let reader = BufReader::new(file); - reader.lines().filter_map(|l| l.ok().and_then(|line| { - let words: Vec = line.split_ascii_whitespace().collect(); - if words.len() == 2 { - Some((words[1], words[0])) - } else { - None - } - })) - .filter_map(|(symbol, mut address)| { + let symbols = reader.lines().filter_map(|l| l.ok().and_then(|line| { + let mut words = line.split_ascii_whitespace(); + match (words.next(), words.next(), words.next()) { + // Get only two-word lines. + (Some(address), Some(symbol), None) => + Some((symbol.to_owned(), address.to_owned())), + + _ => None + } + }) + .and_then(|(symbol, mut address)| { if let Some(colon_at) = address.find(':') { address.remove(colon_at); } + // Filter out the ones that don't have a hexadecimal number as the first word. let snes_address = u32::from_str_radix(&address, 16).ok()?; - let pc_address = (snes_address & 0x7FFF) + ((addr / 2) & 0xFF8000); + let pc_address = snes_to_pc_address(snes_address); Some((symbol, pc_address)) - }).collect() - } + })).collect(); + + Ok(symbols) +} + +pub fn snes_to_pc_address(snes: u32) -> usize { + ((snes & 0x7FFF) + ((snes / 2) & 0xFF8000)) as usize +} + +pub fn pc_to_snes_address(pc: usize) -> u32 { + ((pc & 0x7FFF) + ((pc & 0xFF8000) * 2)) as u32 } From 7cd01df7d630b5374f335044c9b189ddb07eab66 Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Mon, 30 May 2022 01:03:30 -0400 Subject: [PATCH 2/3] Move Asar symbols into RomData. Add move_room_headers function for base_patch_generator. --- enemize/src/constants.rs | 2 +- enemize/src/lib.rs | 13 ++++++- enemize/src/rom.rs | 79 ++++++++++++++++++++++++++++++++++------ 3 files changed, 80 insertions(+), 14 deletions(-) diff --git a/enemize/src/constants.rs b/enemize/src/constants.rs index 716dfde..630ff5b 100644 --- a/enemize/src/constants.rs +++ b/enemize/src/constants.rs @@ -1,4 +1,4 @@ -pub const ROM_HEADER_BANK_LOCATION: usize = 0x0B5E7; +pub const ROOM_HEADER_BANK_LOCATION: usize = 0x0B5E7; pub const DUNGEON_HEADER_POINTER_TABLE: usize = 0x271E2; pub const DUNGEON_SPRITE_POINTER_TABLE: usize = 0x4D62E; pub const OBJECT_DATA_POINTER_TABLE: usize = 0xF8000; diff --git a/enemize/src/lib.rs b/enemize/src/lib.rs index 201bba8..e5c016e 100644 --- a/enemize/src/lib.rs +++ b/enemize/src/lib.rs @@ -7,6 +7,7 @@ use thiserror::Error; use crate::rom::RomData; +pub mod asar; pub mod bosses; pub mod constants; pub mod option_flags; @@ -41,12 +42,20 @@ impl PatchSet { }) } + pub fn add_patch(&mut self, patch: Patch) { + self.patches.push(patch); + } + + pub fn add_patches(&mut self, patches: Vec) { + self.patches.extend(patches); + } + pub fn filename(&self) -> &Path { self.filename.as_path() } - pub fn patch_rom(self, rom: &mut RomData) { - for patch in self.patches { + pub fn patch_rom(&self, rom: &mut RomData) { + for patch in self.patches.iter() { rom.patch_data(patch); } } diff --git a/enemize/src/rom.rs b/enemize/src/rom.rs index 2807194..8b7a2e0 100644 --- a/enemize/src/rom.rs +++ b/enemize/src/rom.rs @@ -4,6 +4,7 @@ use std::ops::Range; use anyhow::bail; +use crate::asar::{Symbols, snes_to_pc_address, pc_to_snes_address}; use crate::constants::*; use crate::option_flags::OptionFlags; use crate::Patch; @@ -28,8 +29,7 @@ pub const CHECKSUM_ADDRESS: usize = 0x7FDE; pub const RANDOMIZER_MODE_FLAG: usize = 0x180032; pub struct RomData { - pub enemizer_info_table_base_address: usize, - pub enemizer_option_flags_base_address: usize, + asar_symbols: Symbols, spoiler: String, rom_data: Vec, patch_data: BTreeMap, @@ -65,8 +65,8 @@ impl RomData { pub fn is_enemizer(&self) -> bool { self.rom_data.len() == ENEMIZER_FILE_LENGTH - && self.rom_data[self.enemizer_info_table_base_address + ENEMIZER_INFO_SEED_OFFSET] == b'E' - && self.rom_data[self.enemizer_info_table_base_address + ENEMIZER_INFO_SEED_OFFSET + 1] == b'N' + && self.rom_data[self.asar_symbols["enemizer_info_table"] + ENEMIZER_INFO_SEED_OFFSET] == b'E' + && self.rom_data[self.asar_symbols["enemizer_info_table"] + ENEMIZER_INFO_SEED_OFFSET + 1] == b'N' } fn assert_rom_length(&self) { @@ -79,7 +79,7 @@ impl RomData { pub fn derive_enemizer_seed(&mut self) -> anyhow::Result { if self.seed < 0 && self.is_enemizer() { - let seed_start = self.enemizer_info_table_base_address + ENEMIZER_INFO_SEED_OFFSET; + let seed_start = self.asar_symbols["enemizer_info_table"] + ENEMIZER_INFO_SEED_OFFSET; let seed_end = seed_start + ENEMIZER_INFO_SEED_LENGTH; let seed_bytes = &self.rom_data[seed_start..seed_end]; let seed_str = &std::str::from_utf8(seed_bytes)?.trim_end_matches('\0')[2..]; @@ -96,7 +96,7 @@ impl RomData { let seed_str = format!("EN{:<10}", seed); let bytes = seed_str.as_bytes().to_owned(); - let seed_start = self.enemizer_info_table_base_address + ENEMIZER_INFO_SEED_OFFSET; + let seed_start = self.asar_symbols["enemizer_info_table"] + ENEMIZER_INFO_SEED_OFFSET; let seed_end = seed_start + ENEMIZER_INFO_SEED_LENGTH; self.seed = seed; @@ -106,7 +106,7 @@ impl RomData { pub fn get_enemizer_version(&self) -> anyhow::Result<&str> { if self.is_enemizer() { - let version_start = self.enemizer_info_table_base_address + ENEMIZER_INFO_VERSION_OFFSET; + let version_start = self.asar_symbols["enemizer_info_table"] + ENEMIZER_INFO_VERSION_OFFSET; let version_end = version_start + ENEMIZER_INFO_VERSION_LENGTH; Ok(std::str::from_utf8(&self.rom_data[version_start..version_end])?) } else { @@ -120,7 +120,7 @@ impl RomData { let mut bytes = version.into_bytes(); bytes.resize(ENEMIZER_INFO_VERSION_LENGTH, 0); - let version_start = self.enemizer_info_table_base_address + ENEMIZER_INFO_VERSION_OFFSET; + let version_start = self.asar_symbols["enemizer_info_table"] + ENEMIZER_INFO_VERSION_OFFSET; let version_end = version_start + ENEMIZER_INFO_VERSION_LENGTH; self.set_rom_bytes(&bytes, version_start..version_end); self.set_patch_bytes(version_start..version_end) @@ -132,7 +132,7 @@ impl RomData { bail!("Option flags is too long to fit in the space allocated. Need to move data/code in asm file."); } - let flags_start = self.enemizer_info_table_base_address + ENEMIZER_INFO_FLAGS_OFFSET; + let flags_start = self.asar_symbols["enemizer_info_table"] + ENEMIZER_INFO_FLAGS_OFFSET; let flags_end = flags_start + bytes.len(); self.set_rom_bytes(&bytes, flags_start..flags_end); self.set_patch_bytes(flags_start..flags_end); @@ -142,7 +142,7 @@ impl RomData { pub fn get_info_flags(&self) -> Option { if self.is_enemizer() { - let flags_start = self.enemizer_info_table_base_address + ENEMIZER_INFO_FLAGS_OFFSET; + let flags_start = self.asar_symbols["enemizer_info_table"] + ENEMIZER_INFO_FLAGS_OFFSET; let flags_end = flags_start + ENEMIZER_INFO_FLAGS_LENGTH; OptionFlags::try_from_bytes(&self.rom_data[flags_start..flags_end]).ok() } else { @@ -150,15 +150,72 @@ impl RomData { } } + pub fn new(asar_symbols: Symbols, rom_data: Vec) -> Self { + Self { + asar_symbols, + spoiler: String::new(), + rom_data: rom_data, + patch_data: BTreeMap::new(), + seed: -1, + } + } + + fn get_flag(&self, offset: usize) -> bool { + let flag_idx = self.asar_symbols["EnemizerFlags"] + offset; + self.rom_data[flag_idx] == 1 + } + + fn set_flag(&mut self, offset: usize, val: bool) { + let flag_idx = self.asar_symbols["EnemizerFlags"] + offset; + self.rom_data[flag_idx] = if val { 1 } else { 0 }; + self.set_patch_bytes(flag_idx..(flag_idx + 1)); + } + pub fn patch_bytes(&mut self, address: usize, patch_data: Vec) { self.rom_data .splice(address..(address + patch_data.len()), patch_data); } - pub fn patch_data(&mut self, patch: Patch) { + pub fn patch_data(&mut self, patch: &Patch) { self.set_rom_bytes( &patch.patch_data, patch.address..(patch.address + patch.patch_data.len()), ); } + + pub fn move_room_headers(&mut self) { + let table_base = DUNGEON_HEADER_POINTER_TABLE; + let header_base = self.asar_symbols["rom_header_table"]; + + // Change room header bank (at 0xB5E7) to 0x04. + let new_room_bank = self.rom_data[self.asar_symbols["moved_room_header_bank_value_address"]]; + self.rom_data[ROOM_HEADER_BANK_LOCATION] = new_room_bank; + + // Copy header table. + for i in 0..320 { + // Get i'th room's pointer. + // Pointers are 16bits, with a hard-coded bank. + let room_pointer = [ + self.rom_data[table_base + (i * 2)], + self.rom_data[table_base + (i * 2) + 1], + 4, + 0 + ]; + let snes_address = u32::from_le_bytes(room_pointer); + let pc_address = snes_to_pc_address(snes_address); + + // Copy i'th room's headers to new room_header_table. + let header_start = header_base + (i * 14); + self.rom_data.copy_within(pc_address..(pc_address + 14), header_start); + } + + // Repoint the pointer table to the new header table. + for i in 0..320 { + let snes = pc_to_snes_address(header_base + (i * 14)).to_le_bytes(); + + assert_eq!(snes[2], new_room_bank, "We changed banks in the middle of moving the room headers! This should have been caught by dev team, unless you were playing with files you shouldn't touch."); + + self.rom_data[(table_base + (i * 2))..(table_base + (i * 2) + 1)].copy_from_slice(&snes[0..1]); + } + } } From 4112573d8529330439c6b777a7ba7ae057c3ce16 Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Mon, 30 May 2022 01:04:39 -0400 Subject: [PATCH 3/3] Add base_patch_generator. --- Cargo.lock | 10 ++++++ Cargo.toml | 1 + base_patch_generator/Cargo.toml | 12 +++++++ base_patch_generator/src/main.rs | 56 ++++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+) create mode 100644 base_patch_generator/Cargo.toml create mode 100644 base_patch_generator/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 77fdf28..5de52f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,16 @@ version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" +[[package]] +name = "base_patch_generator" +version = "0.1.0" +dependencies = [ + "anyhow", + "enemize", + "md5", + "serde_json", +] + [[package]] name = "bin_comp" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 16d8175..636360e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,4 +3,5 @@ members = [ "enemize", "bin_comp", + "base_patch_generator", ] diff --git a/base_patch_generator/Cargo.toml b/base_patch_generator/Cargo.toml new file mode 100644 index 0000000..746828c --- /dev/null +++ b/base_patch_generator/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "base_patch_generator" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0" +enemize = { path = "../enemize" } +md5 = "0.7.0" +serde_json = "1.0" diff --git a/base_patch_generator/src/main.rs b/base_patch_generator/src/main.rs new file mode 100644 index 0000000..4abf411 --- /dev/null +++ b/base_patch_generator/src/main.rs @@ -0,0 +1,56 @@ +use std::env; +use std::fs::File; +use std::io::Read; +use std::path::Path; + +use enemize::PatchSet; +use enemize::asar; +use enemize::rom::RomData; + +fn main() -> Result<(), anyhow::Error> { + let mut args = env::args(); + + let output_path = args.next_back().expect("No output file"); + let symbols_path = args.next_back().expect("No symbols file"); + let patch_path = args.next_back().expect("No patch file"); + let rom_path = args.next_back().expect("No ROM file"); + + let mut rom_bytes = vec![]; + let mut rom_file = File::open(&rom_path)?; + rom_file.read_to_end(&mut rom_bytes)?; + + rom_bytes = if rom_bytes.len() % 1024 == 512 { + rom_bytes.into_iter().skip(512).collect() + } else { + rom_bytes + }; + + let expected = [ + 0x03, 0xa6, 0x39, 0x45, 0x39, 0x81, 0x91, 0x33, 0x7e, 0x89, 0x6e, 0x57, 0x71, 0xf7, 0x71, + 0x73, + ]; + let actual: [u8; 16] = md5::compute(&rom_bytes).into(); + assert_eq!(expected, actual, "Invalid rom file"); + + println!("Applying Patch to rom"); + + rom_bytes.resize(4 * 1024 * 1024, 0); + + let symbols = asar::load_symbols(Path::new(&symbols_path))?; + + let mut rom = RomData::new(symbols, rom_bytes); + + let mut patches = PatchSet::load(Path::new(&patch_path))?; + patches.patch_rom(&mut rom); + + rom.move_room_headers(); + + let rom_patches = rom.generate_patch(); + patches.add_patches(rom_patches); + + println!("Writing output file {}", output_path); + let out_file = File::create(&output_path)?; + serde_json::to_writer(out_file, &patches)?; + + Ok(()) +}