diff --git a/base_patch_generator/src/main.rs b/base_patch_generator/src/main.rs index e089a9a..f3412d3 100644 --- a/base_patch_generator/src/main.rs +++ b/base_patch_generator/src/main.rs @@ -49,8 +49,7 @@ fn main() -> Result<(), anyhow::Error> { 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)?; + patches.save_to_file(Path::new(&output_path))?; Ok(()) } diff --git a/enemize/src/lib.rs b/enemize/src/lib.rs index aae5429..63e07af 100644 --- a/enemize/src/lib.rs +++ b/enemize/src/lib.rs @@ -24,7 +24,6 @@ pub struct Patch { pub patch_data: Vec, } -#[derive(Deserialize, Serialize)] pub struct PatchSet { filename: PathBuf, patches: Vec, @@ -43,6 +42,22 @@ impl PatchSet { }) } + pub fn save(&self) -> anyhow::Result<()> { + let file = File::create(&self.filename)?; + let buffer = std::io::BufWriter::new(file); + serde_json::to_writer(buffer, &self.patches)?; + + Ok(()) + } + + pub fn save_to_file(&self, filename: &Path) -> anyhow::Result<()> { + let file = File::create(filename)?; + let buffer = std::io::BufWriter::new(file); + serde_json::to_writer(buffer, &self.patches)?; + + Ok(()) + } + pub fn add_patch(&mut self, patch: Patch) { self.patches.push(patch); } diff --git a/enemize/src/main.rs b/enemize/src/main.rs index 0e3cc09..17da211 100644 --- a/enemize/src/main.rs +++ b/enemize/src/main.rs @@ -17,18 +17,26 @@ const ENEMIZER_BASE_PATCH: &'static str = "enemizer_base_patch.json"; #[clap(author, version, about)] struct Args { /// path to the base rom file - rom: PathBuf, - /// seed number #[clap(short, long)] - seed: Option, + rom: PathBuf, /// path to the enemizerOptions.json #[clap(short, long)] enemizer: PathBuf, + /// seed number + #[clap(short, long)] + seed: Option, /// path to the intended output file + #[clap(short, long)] output: PathBuf, /// operate in binary mode (takes already randomized SFC and applies enemizer directly to ROM) #[clap(long)] binary: bool, + /// path to base2patched.json (not used) + #[clap(long)] + base: Option, + /// path to the randomizerPatch.json (not used) + #[clap(long)] + randomizer: Option, } fn main() -> anyhow::Result<()> { @@ -36,7 +44,12 @@ fn main() -> anyhow::Result<()> { let stopwatch = Instant::now(); - let options = serde_json::from_reader(File::open(args.enemizer)?)?; + let options = { + let mut opts_file = File::open(args.enemizer)?; + let mut json = String::new(); + opts_file.read_to_string(&mut json)?; + serde_json::from_str(&json)? + }; let symbols = load_symbols()?; let mut raw_data = vec![]; diff --git a/enemize/src/option_flags.rs b/enemize/src/option_flags.rs index a8d2fd9..c3f1f0d 100644 --- a/enemize/src/option_flags.rs +++ b/enemize/src/option_flags.rs @@ -9,6 +9,7 @@ use crate::bosses::BossType; use crate::InvalidEnumError; #[derive(Debug, Default, Deserialize, Serialize)] +#[serde(rename_all = "PascalCase")] pub struct ManualBosses { pub eastern_palace: String, pub desert_palace: String, @@ -183,18 +184,22 @@ impl TryFrom for ShieldType { } #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] -#[serde(into = "u8", try_from = "u8")] pub enum AbsorbableType { Heart, GreenRupee, BlueRupee, RedRupee, + #[serde(alias = "Bomb_1")] Bomb1, + #[serde(alias = "Bomb_4")] Bomb4, + #[serde(alias = "Bomb_8")] Bomb8, SmallMagic, FullMagic, + #[serde(alias = "Arrow_5")] Arrow5, + #[serde(alias = "Arrow_10")] Arrow10, Fairy, Key, @@ -316,6 +321,7 @@ impl TryFrom for HeartBeepSpeed { } #[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[serde(into = "u8", try_from = "u8")] pub enum BeeLevel { Level1, Level2, @@ -380,26 +386,26 @@ pub struct OptionFlags { pub shuffle_enemy_damage_groups: bool, pub enemy_damage_chaos_mode: bool, - pub easy_mode_escape: bool, + //pub easy_mode_escape: bool, pub enemies_absorbable: bool, pub absorbable_spawn_rate: u8, pub absorbable_types: HashMap, - pub boss_madness: bool, + //pub boss_madness: bool, pub randomize_bosses: bool, pub randomize_bosses_type: RandomizeBossesType, - pub randomize_boss_health: bool, - pub randomize_boss_health_min_amount: u8, - pub randomize_boss_health_max_amount: u8, + //pub randomize_boss_health: bool, + //pub randomize_boss_health_min_amount: u8, + //pub randomize_boss_health_max_amount: u8, - pub randomize_boss_damage: bool, - pub randomize_boss_damage_min_amount: u8, - pub randomize_boss_damage_max_amount: u8, + //pub randomize_boss_damage: bool, + //pub randomize_boss_damage_min_amount: u8, + //pub randomize_boss_damage_max_amount: u8, - pub randomize_boss_behavior: bool, + //pub randomize_boss_behavior: bool, pub randomize_dungeon_palettes: bool, pub set_blackout_mode: bool, @@ -418,7 +424,7 @@ pub struct OptionFlags { pub shuffle_music: bool, pub bootleg_magic: bool, pub debug_mode: bool, - pub custom_bosses: bool, + //pub custom_bosses: bool, pub heart_beep_speed: HeartBeepSpeed, pub alternate_gfx: bool, pub shield_graphics: PathBuf, @@ -472,20 +478,20 @@ impl OptionFlags { randomize_enemy_health_type: bytes[4].try_into()?, randomize_enemy_damage: bytes[5] != 0, allow_enemy_zero_damage: bytes[6] != 0, - easy_mode_escape: bytes[7] != 0, + //easy_mode_escape: bytes[7] != 0, enemies_absorbable: bytes[8] != 0, absorbable_spawn_rate: bytes[9], absorbable_types, - boss_madness: bytes[24] != 0, + //boss_madness: bytes[24] != 0, randomize_bosses: bytes[25] != 0, randomize_bosses_type: bytes[26].try_into()?, - randomize_boss_health: bytes[27] != 0, - randomize_boss_health_min_amount: bytes[28], - randomize_boss_health_max_amount: bytes[29], - randomize_boss_damage: bytes[30] != 0, - randomize_boss_damage_min_amount: bytes[31], - randomize_boss_damage_max_amount: bytes[32], - randomize_boss_behavior: bytes[33] != 0, + //randomize_boss_health: bytes[27] != 0, + //randomize_boss_health_min_amount: bytes[28], + //randomize_boss_health_max_amount: bytes[29], + //randomize_boss_damage: bytes[30] != 0, + //randomize_boss_damage_min_amount: bytes[31], + //randomize_boss_damage_max_amount: bytes[32], + //randomize_boss_behavior: bytes[33] != 0, randomize_dungeon_palettes: bytes[34] != 0, set_blackout_mode: bytes[35] != 0, randomize_overworld_palettes: bytes[36] != 0, @@ -500,7 +506,7 @@ impl OptionFlags { shuffle_music: bytes[45] != 0, bootleg_magic: bytes[46] != 0, debug_mode: bytes[47] != 0, - custom_bosses: bytes[48] != 0, + //custom_bosses: bytes[48] != 0, heart_beep_speed: bytes[49].try_into()?, alternate_gfx: bytes[50] != 0, // Skip byte 51 (shield_graphics) @@ -539,7 +545,7 @@ impl OptionFlags { bytes.push(self.randomize_enemy_health_type as u8); bytes.push(self.randomize_enemy_damage as u8); bytes.push(self.allow_enemy_zero_damage as u8); - bytes.push(self.easy_mode_escape as u8); + bytes.push(0); //bytes.push(self.easy_mode_escape as u8); bytes.push(self.enemies_absorbable as u8); bytes.push(self.absorbable_spawn_rate); @@ -628,16 +634,16 @@ impl OptionFlags { .unwrap_or(false) as u8, ); - bytes.push(self.boss_madness as u8); + bytes.push(0); //bytes.push(self.boss_madness as u8); bytes.push(self.randomize_bosses as u8); bytes.push(self.randomize_bosses_type as u8); - bytes.push(self.randomize_boss_health as u8); - bytes.push(self.randomize_boss_health_min_amount); - bytes.push(self.randomize_boss_health_max_amount); - bytes.push(self.randomize_boss_damage as u8); - bytes.push(self.randomize_boss_damage_min_amount); - bytes.push(self.randomize_boss_damage_max_amount); - bytes.push(self.randomize_boss_behavior as u8); + bytes.push(0); //bytes.push(self.randomize_boss_health as u8); + bytes.push(0); //bytes.push(self.randomize_boss_health_min_amount); + bytes.push(0); //bytes.push(self.randomize_boss_health_max_amount); + bytes.push(0); //bytes.push(self.randomize_boss_damage as u8); + bytes.push(0); //bytes.push(self.randomize_boss_damage_min_amount); + bytes.push(0); //bytes.push(self.randomize_boss_damage_max_amount); + bytes.push(0); //bytes.push(self.randomize_boss_behavior as u8); bytes.push(self.randomize_dungeon_palettes as u8); bytes.push(self.set_blackout_mode as u8); bytes.push(self.randomize_overworld_palettes as u8); @@ -652,7 +658,7 @@ impl OptionFlags { bytes.push(self.shuffle_music as u8); bytes.push(self.bootleg_magic as u8); bytes.push(self.debug_mode as u8); - bytes.push(self.custom_bosses as u8); + bytes.push(0); //bytes.push(self.custom_bosses as u8); bytes.push(self.heart_beep_speed as u8); bytes.push(self.alternate_gfx as u8); bytes.push(0); // self.shield_graphics @@ -695,20 +701,20 @@ impl Default for OptionFlags { allow_enemy_zero_damage: false, shuffle_enemy_damage_groups: false, enemy_damage_chaos_mode: false, - easy_mode_escape: false, + //easy_mode_escape: false, enemies_absorbable: false, absorbable_spawn_rate: 0, absorbable_types: HashMap::new(), - boss_madness: false, + //boss_madness: false, randomize_bosses: true, randomize_bosses_type: RandomizeBossesType::Chaos, - randomize_boss_health: false, - randomize_boss_health_min_amount: 0, - randomize_boss_health_max_amount: 0, - randomize_boss_damage: false, - randomize_boss_damage_min_amount: 0, - randomize_boss_damage_max_amount: 0, - randomize_boss_behavior: false, + //randomize_boss_health: false, + //randomize_boss_health_min_amount: 0, + //randomize_boss_health_max_amount: 0, + //randomize_boss_damage: false, + //randomize_boss_damage_min_amount: 0, + //randomize_boss_damage_max_amount: 0, + //randomize_boss_behavior: false, randomize_dungeon_palettes: true, set_blackout_mode: false, randomize_overworld_palettes: true, @@ -723,7 +729,7 @@ impl Default for OptionFlags { shuffle_music: false, bootleg_magic: false, debug_mode: false, - custom_bosses: false, + //custom_bosses: false, heart_beep_speed: HeartBeepSpeed::Half, alternate_gfx: false, shield_graphics: ["shield_gfx", "normal.gfx"].iter().collect(), @@ -751,3 +757,17 @@ impl Default for OptionFlags { } } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_option_flags_serde() { + let empty = "{}"; + let actual: OptionFlags = serde_json::from_str(empty).expect("Can't deserialize empty"); + let expected = serde_json::to_string(&OptionFlags::default()).expect("Can't serialize default"); + + assert_eq!(serde_json::to_string(&actual).expect("Can't roundtrip"), expected); + } +} diff --git a/enemize/src/rom.rs b/enemize/src/rom.rs index 2ae8d66..fe22d41 100644 --- a/enemize/src/rom.rs +++ b/enemize/src/rom.rs @@ -25,8 +25,8 @@ pub const RANDOM_SPRITE_FLAG: usize = 0x03; pub const AGAHNIM_BOUNCE_BALLS_FLAG: usize = 0x04; pub const ENABLE_MIMIC_OVERRIDE_FLAG: usize = 0x05; pub const ENABLE_TERRORPIN_AI_FIX_FLAG: usize = 0x06; -pub const CHECKSUM_COMPLIMENT_ADDRESS: usize = 0x7FDC; -pub const CHECKSUM_ADDRESS: usize = 0x7FDE; +pub const CHECKSUM_COMPLIMENT_ADDRESS: usize = 0x7fdc; +pub const CHECKSUM_ADDRESS: usize = 0x7fde; pub const RANDOMIZER_MODE_FLAG: usize = 0x180032; pub struct RomData { @@ -53,7 +53,7 @@ impl RomData { DUNGEON_HEADER_POINTER_TABLE..(DUNGEON_HEADER_POINTER_TABLE + 640); self.rom_data[dungeon_header_range].copy_from_slice(&ORIGINAL_ROOM_POINTERS); - let room_range = 0x5B97..(0x5B97 + 576); + let room_range = 0x5b97..(0x5b97 + 576); self.rom_data[room_range].copy_from_slice(&ORIGINAL_ROOM_BLOCKS); let ow_gfx_range = OVERWORLD_AREA_GRAPHICS_BLOCK..(OVERWORLD_AREA_GRAPHICS_BLOCK + 272); @@ -117,15 +117,15 @@ impl RomData { acceptable .iter() - .any(|abbr| &abbr[..] == &self.rom_data[0x7FC0..0x7Fc1]) + .any(|abbr| &abbr[..] == &self.rom_data[0x7fc0..0x7fc2]) || (self.rom_data.len() >= 0x20_0000 - && &self.rom_data[0x7FC0..0x7FCE] == b"ZELDANODENSETSU") + && &self.rom_data[0x7fc0..0x7fcf] == b"ZELDANODENSETSU") } pub fn is_race(&self) -> bool { self.is_randomizer() && (&self.rom_data[0x180213..0x180214] == &[1, 0] - || &self.rom_data[0x7FC0..0x7FC9] == b"VT TOURNEY") + || &self.rom_data[0x7fc0..0x7fca] == b"VT TOURNEY") } fn assert_rom_length(&self) { @@ -169,8 +169,8 @@ impl RomData { pub fn expand_rom(&mut self) { self.rom_data.resize(0x40_0000, 0); // Update header length. - self.rom_data[0x7FD7] = 0x0C; - self.set_patch_bytes(0x7FD7..0x7FD8); + self.rom_data[0x7fd7] = 0x0c; + self.set_patch_bytes(0x7fd7..0x7fd8); self.set_enemizer_version("6.0.32".to_owned()); } @@ -261,7 +261,7 @@ impl RomData { let table_base = DUNGEON_HEADER_POINTER_TABLE; let header_base = self.asar_symbols["room_header_table"]; - // Change room header bank (at 0xB5E7) to 0x04. + // 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;