From 1cca6005050fd7cb8d64c8f7d7719d78d7f3ebe2 Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Mon, 23 May 2022 09:57:16 -0400 Subject: [PATCH 01/29] Use MIT License for my own code. Files in `assembly/src` written by others under the WTFPL. --- LICENSE.txt | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE.txt diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..c18e449 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Lyle Mantooth + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 5f8f1b5655924b88baba9ee8c4ec3d1681c1380b Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Tue, 24 May 2022 22:49:58 -0400 Subject: [PATCH 02/29] Ignore generated files. --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index ea8c4bf..ade3621 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ /target +exported_symbols.txt +patchData.json From c81348fb10cdc074b1aa112252cf351759ab2f55 Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Tue, 24 May 2022 22:53:15 -0400 Subject: [PATCH 03/29] Fix up compiler issues. --- Cargo.lock | 8 +++++--- enemize/Cargo.toml | 6 ++++-- enemize/src/lib.rs | 26 +++++++++++++------------- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 664456c..d1c609f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,7 +13,7 @@ name = "bin_comp" version = "0.1.0" dependencies = [ "anyhow", - "serde", + "enemize", "serde_json", "tempfile", ] @@ -31,10 +31,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "enemize-rs" +name = "enemize" version = "0.1.0" dependencies = [ - "md5", + "anyhow", + "serde", + "serde_json", ] [[package]] diff --git a/enemize/Cargo.toml b/enemize/Cargo.toml index c59205e..a2cf87f 100644 --- a/enemize/Cargo.toml +++ b/enemize/Cargo.toml @@ -1,9 +1,11 @@ [package] -name = "enemize-rs" +name = "enemize" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -serde = { version = 1.0, features = ["derive"] } +anyhow = "1" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" diff --git a/enemize/src/lib.rs b/enemize/src/lib.rs index d2ba5b0..eda29ef 100644 --- a/enemize/src/lib.rs +++ b/enemize/src/lib.rs @@ -1,40 +1,40 @@ -use std::io::Read; +use std::fs::File; use std::path::{Path, PathBuf}; -use serde::Serialize; +use serde::{Deserialize, Serialize}; pub mod rom; -#[derive(Serialize)] +#[derive(Deserialize, Serialize)] pub struct Patch { pub address: usize, pub patch_data: Vec, } -#[derive(Serialize)] +#[derive(Deserialize, Serialize)] pub struct PatchSet { filename: PathBuf, - patches: Vec + patches: Vec, } impl PatchSet { - pub fn load(filename: Path) -> Result { + pub fn load(filename: &Path) -> Result { let patches = { - let mut file = File::open(filename)?; - let mut buffer = std::io::BufReader::new(file); + let file = File::open(filename)?; + let buffer = std::io::BufReader::new(file); serde_json::from_reader(buffer)? }; - PatchSet { + Ok(PatchSet { filename: filename.into(), - patches: patches - } + patches: patches, + }) } - pub fn filename(&self) -> Path { + pub fn filename(&self) -> &Path { self.filename.as_path() } - pub fn patchRom(&self, &mut rom: RomData) { + pub fn patch_rom(self, rom: &mut RomData) { for patch in self.patches { rom.patch_data(patch); } From a161d9090e43709289a5ec5f0448dd0f546fcdcb Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Sun, 29 May 2022 10:22:34 -0400 Subject: [PATCH 04/29] Add RomData, OptionFlags, and enums for them. Fun error handling with automatic, fallible conversion to and from u8s for the enums. --- Cargo.lock | 21 ++ enemize/Cargo.toml | 1 + enemize/src/asar.rs | 33 +++ enemize/src/bosses/mod.rs | 48 +++ enemize/src/constants.rs | 12 + enemize/src/lib.rs | 11 + enemize/src/option_flags.rs | 571 ++++++++++++++++++++++++++++++++++++ enemize/src/rom.rs | 169 ++++++++++- 8 files changed, 860 insertions(+), 6 deletions(-) create mode 100644 enemize/src/asar.rs create mode 100644 enemize/src/bosses/mod.rs create mode 100644 enemize/src/constants.rs create mode 100644 enemize/src/option_flags.rs diff --git a/Cargo.lock b/Cargo.lock index d1c609f..77fdf28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,6 +37,7 @@ dependencies = [ "anyhow", "serde", "serde_json", + "thiserror", ] [[package]] @@ -173,6 +174,26 @@ dependencies = [ "winapi", ] +[[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.0" diff --git a/enemize/Cargo.toml b/enemize/Cargo.toml index a2cf87f..a612509 100644 --- a/enemize/Cargo.toml +++ b/enemize/Cargo.toml @@ -9,3 +9,4 @@ edition = "2021" anyhow = "1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +thiserror = "1" diff --git a/enemize/src/asar.rs b/enemize/src/asar.rs new file mode 100644 index 0000000..6324302 --- /dev/null +++ b/enemize/src/asar.rs @@ -0,0 +1,33 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::{BufReader, Read}; +use std::path::Path; + +pub type Symbols = HashMap; + +impl Symbols { + pub fn load(filename: Path) -> anyhow::Result { + let file = File::open(filename)?; + let mut 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)| { + if let Some(colon_at) = address.find(':') { + address.remove(colon_at); + } + + let snes_address = u32::from_str_radix(&address, 16).ok()?; + + let pc_address = (snes_address & 0x7FFF) + ((addr / 2) & 0xFF8000); + + Some((symbol, pc_address)) + }).collect() + } +} diff --git a/enemize/src/bosses/mod.rs b/enemize/src/bosses/mod.rs new file mode 100644 index 0000000..872412d --- /dev/null +++ b/enemize/src/bosses/mod.rs @@ -0,0 +1,48 @@ +use std::marker::PhantomData; + +use crate::InvalidEnumError; + +#[derive(Debug)] +#[repr(u8)] +pub enum BossType { + Kholdstare, + Moldorm, + Mothula, + Vitreous, + Helmasaur, + Armos, + Lanmola, + Blind, + Arrghus, + Trinexx, + // Don't use these. They are only for manual settings passed in by the randomizer web app. + Agahnim, + Agahnim2, + Ganon, + NoBoss = 255, +} + +impl TryFrom for BossType { + type Error = InvalidEnumError; + + fn try_from(byte: u8) -> Result { + match byte { + 0 => Ok(Self::Kholdstare), + 1 => Ok(Self::Moldorm), + 2 => Ok(Self::Mothula), + 3 => Ok(Self::Vitreous), + 4 => Ok(Self::Helmasaur), + 5 => Ok(Self::Armos), + 6 => Ok(Self::Lanmola), + 7 => Ok(Self::Blind), + 8 => Ok(Self::Arrghus), + 9 => Ok(Self::Trinexx), + 10 => Ok(Self::Agahnim), + 11 => Ok(Self::Agahnim2), + 12 => Ok(Self::Ganon), + 255 => Ok(Self::NoBoss), + _ => Err(InvalidEnumError(PhantomData)) + } + } +} + diff --git a/enemize/src/constants.rs b/enemize/src/constants.rs new file mode 100644 index 0000000..716dfde --- /dev/null +++ b/enemize/src/constants.rs @@ -0,0 +1,12 @@ +pub const ROM_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; +pub const OVERWORLD_AREA_GRAPHICS_BLOCK: usize = 0x007A81; +pub const OVERWORLD_SPRITE_POINTER_TABLE: usize = 0x04C901; +pub const MOLDORM_EYE_COUNT_ADDRESS_VANILLA: usize = 0x0EDBB3; +pub const MOLDORM_EYE_COUNT_ADDRESS_ENEMIZER: usize = 0x1B0102; +pub const NEW_BOSS_GRAPHICS: usize = 0x1B0000; +pub const RANDOM_SPRITE_GRAPHICS: usize = 0x300000; +pub const ENEMIZER_FILE_LENGTH: usize = 0x200000; +pub const HIDDEN_ENEMY_CHANCE_POOL: usize = 0xD7BBB; diff --git a/enemize/src/lib.rs b/enemize/src/lib.rs index eda29ef..201bba8 100644 --- a/enemize/src/lib.rs +++ b/enemize/src/lib.rs @@ -1,10 +1,21 @@ use std::fs::File; +use std::marker::PhantomData; use std::path::{Path, PathBuf}; use serde::{Deserialize, Serialize}; +use thiserror::Error; +use crate::rom::RomData; + +pub mod bosses; +pub mod constants; +pub mod option_flags; pub mod rom; +#[derive(Debug, Error)] +#[error("Not a valid value for {0:?}")] +pub struct InvalidEnumError(PhantomData); + #[derive(Deserialize, Serialize)] pub struct Patch { pub address: usize, diff --git a/enemize/src/option_flags.rs b/enemize/src/option_flags.rs new file mode 100644 index 0000000..2170c52 --- /dev/null +++ b/enemize/src/option_flags.rs @@ -0,0 +1,571 @@ +use std::collections::HashMap; +use std::fmt; +use std::marker::PhantomData; +use std::path::PathBuf; + +use crate::bosses::BossType; +use crate::InvalidEnumError; + +#[derive(Debug, Default)] +pub struct ManualBosses { + pub eastern_palace: String, + pub desert_palace: String, + pub tower_of_hera: String, + pub agahnims_tower: String, + pub palace_of_darkness: String, + pub swamp_palace: String, + pub skull_woods: String, + pub thieves_town: String, + pub ice_palace: String, + pub misery_mire: String, + pub turtle_rock: String, + pub ganons_tower1: String, + pub ganons_tower2: String, + pub ganons_tower3: String, + pub ganons_tower4: String, + pub ganon: String, +} + +#[derive(Debug)] +pub enum RandomizeEnemiesType { + Basic, + Normal, + Hard, + Chaos, + Insanity, +} + +impl TryFrom for RandomizeEnemiesType { + type Error = InvalidEnumError; + + fn try_from(byte: u8) -> Result { + match byte { + 0 => Ok(Self::Basic), + 1 => Ok(Self::Normal), + 2 => Ok(Self::Hard), + 3 => Ok(Self::Chaos), + 4 => Ok(Self::Insanity), + _ => Err(InvalidEnumError(PhantomData)) + } + } +} + +#[derive(Debug)] +pub enum RandomizeEnemyHpType { + Easy, + Medium, + Hard, + Patty +} + +impl TryFrom for RandomizeEnemyHpType { + type Error = InvalidEnumError; + + fn try_from(byte: u8) -> Result { + match byte { + 0 => Ok(Self::Easy), + 1 => Ok(Self::Medium), + 2 => Ok(Self::Hard), + 3 => Ok(Self::Patty), + _ => Err(InvalidEnumError(PhantomData)) + } + } +} + +#[derive(Debug)] +pub enum RandomizeBossesType { + Basic, + Normal, + Chaos +} + +impl TryFrom for RandomizeBossesType { + type Error = InvalidEnumError; + + fn try_from(byte: u8) -> Result { + match byte { + 0 => Ok(Self::Basic), + 1 => Ok(Self::Normal), + 2 => Ok(Self::Chaos), + _ => Err(InvalidEnumError(PhantomData)) + } + } +} + +#[derive(Debug)] +pub enum SwordType { + Normal +} + +impl TryFrom for SwordType { + type Error = InvalidEnumError; + + fn try_from(byte: u8) -> Result { + match byte { + 0 => Ok(Self::Normal), + _ => Err(InvalidEnumError(PhantomData)) + } + } +} + +#[derive(Debug)] +pub enum ShieldType { + Normal +} + +impl TryFrom for ShieldType { + type Error = InvalidEnumError; + + fn try_from(byte: u8) -> Result { + match byte { + 0 => Ok(Self::Normal), + _ => Err(InvalidEnumError(PhantomData)) + } + } +} + +#[derive(Debug, PartialEq, Eq, Hash)] +pub enum AbsorbableType { + Heart, + GreenRupee, + BlueRupee, + RedRupee, + Bomb1, + Bomb4, + Bomb8, + SmallMagic, + FullMagic, + Arrow5, + Arrow10, + Fairy, + Key, + BigKey, +} + +impl TryFrom for AbsorbableType { + type Error = InvalidEnumError; + + fn try_from(byte: u8) -> Result { + match byte { + 0 => Ok(Self::Heart), + 1 => Ok(Self::GreenRupee), + 2 => Ok(Self::BlueRupee), + 3 => Ok(Self::Bomb1), + 4 => Ok(Self::Bomb4), + 5 => Ok(Self::Bomb8), + 6 => Ok(Self::SmallMagic), + 7 => Ok(Self::FullMagic), + 8 => Ok(Self::Arrow5), + 9 => Ok(Self::Arrow10), + 10 => Ok(Self::Fairy), + 11 => Ok(Self::Key), + 12 => Ok(Self::BigKey), + _ => Err(InvalidEnumError(PhantomData)) + } + } +} + +impl fmt::Display for AbsorbableType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use AbsorbableType::*; + + let description = match self { + Heart => "Heart", + GreenRupee => "Green Rupee", + BlueRupee => "Blue Rupee", + RedRupee => "Red Rupee", + Bomb1 => "Bomb (1)", + Bomb4 => "Bomb (4)", + Bomb8 => "Bomb (8)", + SmallMagic => "Small Magic", + FullMagic => "Full Magic", + Arrow5 => "Arrow (5)", + Arrow10 => "Arrow (10)", + Fairy => "Fairy", + Key => "Key", + BigKey => "Big Key", + }; + + write!(f, "{}", description) + } +} + +#[derive(Debug)] +pub enum HeartBeepSpeed { + Normal, + Half, + Quarter, + Off, +} + +impl Default for HeartBeepSpeed { + fn default() -> Self { + HeartBeepSpeed::Normal + } +} + +impl TryFrom for HeartBeepSpeed { + type Error = InvalidEnumError; + + fn try_from(byte: u8) -> Result { + match byte { + 0 => Ok(Self::Normal), + 1 => Ok(Self::Half), + 2 => Ok(Self::Quarter), + 3 => Ok(Self::Off), + _ => Err(InvalidEnumError(PhantomData)) + } + } +} + +#[derive(Debug)] +pub enum BeeLevel { + Level1, + Level2, + Level3, + Level4, +} + +impl fmt::Display for BeeLevel { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use BeeLevel::*; + + let description = match self { + Level1 => "Bees??", + Level2 => "Bees!", + Level3 => "Beeeeees!?", + Level4 => "Beeeeeeeeeeeeeeeeeeeees", + }; + + write!(f, "{}", description) + } +} + +impl TryFrom for BeeLevel { + type Error = InvalidEnumError; + + fn try_from(byte: u8) -> Result { + match byte { + 0 => Ok(Self::Level1), + 1 => Ok(Self::Level2), + 2 => Ok(Self::Level3), + 3 => Ok(Self::Level4), + _ => Err(InvalidEnumError(PhantomData)) + } + } +} + +#[derive(Debug)] +pub struct OptionFlags { + pub randomize_enemies: bool, + pub randomize_enemies_type: RandomizeEnemiesType, + pub randomize_bush_enemy_chance: bool, + + pub randomize_enemy_health_range: bool, + pub randomize_enemy_health_type: RandomizeEnemyHpType, + + pub randomize_enemy_damage: bool, + pub allow_enemy_zero_damage: bool, + pub shuffle_enemy_damage_groups: bool, + pub enemy_damage_chaos_mode: bool, + + pub easy_mode_escape: bool, + + pub enemies_absorbable: bool, + pub absorbable_spawn_rate: u8, + pub absorbable_types: HashMap, + + 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_damage: bool, + pub randomize_boss_damage_min_amount: u8, + pub randomize_boss_damage_max_amount: u8, + + pub randomize_boss_behavior: bool, + + pub randomize_dungeon_palettes: bool, + pub set_blackout_mode: bool, + + pub randomize_overworld_palettes: bool, + + pub randomize_sprite_palettes: bool, + pub set_advanced_sprite_palettes: bool, + pub puke_mode: bool, + pub negative_mode: bool, + pub grayscale_mode: bool, + + pub generate_spoilers: bool, + pub randomize_link_sprite_palette: bool, + pub randomize_pots: bool, + pub shuffle_music: bool, + pub bootleg_magic: bool, + pub debug_mode: bool, + pub custom_bosses: bool, + pub heart_beep_speed: HeartBeepSpeed, + pub alternate_gfx: bool, + pub shield_graphics: PathBuf, + pub sword_graphics: PathBuf, + pub bee_mizer: bool, + pub bees_level: BeeLevel, + pub debug_force_enemy: bool, + pub debug_force_enemy_id: u8, + pub debug_force_boss: bool, + pub debug_force_boss_id: BossType, + pub debug_open_shutter_doors: bool, + pub debug_force_enemy_damage_zero: bool, + pub debug_show_room_id_in_rupee_counter: bool, + pub o_h_k_o: bool, + pub randomize_tile_trap_pattern: bool, + pub randomize_tile_trap_floor_tile: bool, + pub allow_killable_thief: bool, + pub randomize_sprite_on_hit: bool, + pub hero_mode: bool, + pub increase_brightness: bool, + pub mute_music_enable_msu_1: bool, + pub agahnim_bounce_balls: bool, + + pub use_manual_bosses: bool, + pub manual_bosses: ManualBosses, +} + +impl OptionFlags { + pub fn try_from_bytes(bytes: &[u8]) -> anyhow::Result { + let mut absorbable_types = HashMap::new(); + absorbable_types.insert(AbsorbableType::Heart, bytes[10] != 0); + absorbable_types.insert(AbsorbableType::GreenRupee, bytes[11] != 0); + absorbable_types.insert(AbsorbableType::BlueRupee, bytes[12] != 0); + absorbable_types.insert(AbsorbableType::Bomb1, bytes[13] != 0); + absorbable_types.insert(AbsorbableType::Bomb4, bytes[14] != 0); + absorbable_types.insert(AbsorbableType::Bomb8, bytes[15] != 0); + absorbable_types.insert(AbsorbableType::SmallMagic, bytes[16] != 0); + absorbable_types.insert(AbsorbableType::FullMagic, bytes[17] != 0); + absorbable_types.insert(AbsorbableType::Arrow5, bytes[18] != 0); + absorbable_types.insert(AbsorbableType::Arrow10, bytes[19] != 0); + absorbable_types.insert(AbsorbableType::Fairy, bytes[20] != 0); + absorbable_types.insert(AbsorbableType::Key, bytes[21] != 0); + absorbable_types.insert(AbsorbableType::BigKey, bytes[22] != 0); + + Ok(OptionFlags { + randomize_enemies: bytes[0] != 0, + randomize_enemies_type: bytes[1].try_into()?, + randomize_bush_enemy_chance: bytes[2] != 0, + randomize_enemy_health_range: bytes[3] != 0, + 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, + enemies_absorbable: bytes[8] != 0, + absorbable_spawn_rate: bytes[9], + absorbable_types, + boss_madness: bytes[23] != 0, + randomize_bosses: bytes[24] != 0, + randomize_bosses_type: bytes[25].try_into()?, + randomize_boss_health: bytes[26] != 0, + randomize_boss_health_min_amount: bytes[27], + randomize_boss_health_max_amount: bytes[28], + randomize_boss_damage: bytes[29] != 0, + randomize_boss_damage_min_amount: bytes[30], + randomize_boss_damage_max_amount: bytes[31], + randomize_boss_behavior: bytes[32] != 0, + randomize_dungeon_palettes: bytes[33] != 0, + set_blackout_mode: bytes[34] != 0, + randomize_overworld_palettes: bytes[35] != 0, + randomize_sprite_palettes: bytes[36] != 0, + set_advanced_sprite_palettes: bytes[37] != 0, + puke_mode: bytes[38] != 0, + negative_mode: bytes[39] != 0, + grayscale_mode: bytes[40] != 0, + generate_spoilers: bytes[41] != 0, + randomize_link_sprite_palette: bytes[42] != 0, + randomize_pots: bytes[43] != 0, + shuffle_music: bytes[44] != 0, + bootleg_magic: bytes[45] != 0, + debug_mode: bytes[46] != 0, + custom_bosses: bytes[47] != 0, + heart_beep_speed: bytes[48].try_into()?, + alternate_gfx: bytes[49] != 0, + // Skip byte 50 (shield_graphics) + shuffle_enemy_damage_groups: bytes[51] != 0, + enemy_damage_chaos_mode: bytes[52] != 0, + // Skip byte 53 (sword_graphics) + bee_mizer: bytes[54] != 0, + bees_level: bytes[55].try_into()?, + debug_force_enemy: bytes[56] != 0, + debug_force_enemy_id: bytes[57], + debug_force_boss: bytes[58] != 0, + debug_force_boss_id: bytes[59].try_into()?, + debug_open_shutter_doors: bytes[60] != 0, + debug_force_enemy_damage_zero: bytes[61] != 0, + debug_show_room_id_in_rupee_counter: bytes[62] != 0, + o_h_k_o: bytes[63] != 0, + randomize_tile_trap_pattern: bytes[64] != 0, + randomize_tile_trap_floor_tile: bytes[65] != 0, + allow_killable_thief: bytes[66] != 0, + randomize_sprite_on_hit: bytes[67] != 0, + hero_mode: bytes[68] != 0, + increase_brightness: bytes[69] != 0, + mute_music_enable_msu_1: bytes[70] != 0, + agahnim_bounce_balls: bytes[71] != 0, + ..Default::default() + }) + } + + pub fn into_bytes(self) -> Vec { + let mut bytes = Vec::with_capacity(crate::rom::ENEMIZER_INFO_FLAGS_LENGTH); + + bytes.push(self.randomize_enemies as u8); + bytes.push(self.randomize_enemies_type as u8); + bytes.push(self.randomize_bush_enemy_chance as u8); + bytes.push(self.randomize_enemy_health_range as u8); + 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(self.enemies_absorbable as u8); + bytes.push(self.absorbable_spawn_rate); + + bytes.push(self.absorbable_types.get(&AbsorbableType::Heart).copied().unwrap_or(false) as u8); + bytes.push(self.absorbable_types.get(&AbsorbableType::GreenRupee).copied().unwrap_or(false) as u8); + bytes.push(self.absorbable_types.get(&AbsorbableType::BlueRupee).copied().unwrap_or(false) as u8); + bytes.push(self.absorbable_types.get(&AbsorbableType::RedRupee).copied().unwrap_or(false) as u8); + bytes.push(self.absorbable_types.get(&AbsorbableType::Bomb1).copied().unwrap_or(false) as u8); + bytes.push(self.absorbable_types.get(&AbsorbableType::Bomb4).copied().unwrap_or(false) as u8); + bytes.push(self.absorbable_types.get(&AbsorbableType::Bomb8).copied().unwrap_or(false) as u8); + bytes.push(self.absorbable_types.get(&AbsorbableType::SmallMagic).copied().unwrap_or(false) as u8); + bytes.push(self.absorbable_types.get(&AbsorbableType::FullMagic).copied().unwrap_or(false) as u8); + bytes.push(self.absorbable_types.get(&AbsorbableType::Arrow5).copied().unwrap_or(false) as u8); + bytes.push(self.absorbable_types.get(&AbsorbableType::Arrow10).copied().unwrap_or(false) as u8); + bytes.push(self.absorbable_types.get(&AbsorbableType::Fairy).copied().unwrap_or(false) as u8); + bytes.push(self.absorbable_types.get(&AbsorbableType::Key).copied().unwrap_or(false) as u8); + bytes.push(self.absorbable_types.get(&AbsorbableType::BigKey).copied().unwrap_or(false) as u8); + + 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(self.randomize_dungeon_palettes as u8); + bytes.push(self.set_blackout_mode as u8); + bytes.push(self.randomize_overworld_palettes as u8); + bytes.push(self.randomize_sprite_palettes as u8); + bytes.push(self.set_advanced_sprite_palettes as u8); + bytes.push(self.puke_mode as u8); + bytes.push(self.negative_mode as u8); + bytes.push(self.grayscale_mode as u8); + bytes.push(self.generate_spoilers as u8); + bytes.push(self.randomize_link_sprite_palette as u8); + bytes.push(self.randomize_pots as u8); + 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(self.heart_beep_speed as u8); + bytes.push(self.alternate_gfx as u8); + bytes.push(0); // self.shield_graphics + bytes.push(self.shuffle_enemy_damage_groups as u8); + bytes.push(self.enemy_damage_chaos_mode as u8); + bytes.push(0); // self.sword_graphics + bytes.push(self.bee_mizer as u8); + bytes.push(self.bees_level as u8); + + bytes.push(self.debug_force_enemy as u8); + bytes.push(self.debug_force_enemy_id as u8); + bytes.push(self.debug_force_boss as u8); + bytes.push(self.debug_force_boss_id as u8); + bytes.push(self.debug_open_shutter_doors as u8); + bytes.push(self.debug_force_enemy_damage_zero as u8); + bytes.push(self.debug_show_room_id_in_rupee_counter as u8); + bytes.push(self.o_h_k_o as u8); + bytes.push(self.randomize_tile_trap_pattern as u8); + bytes.push(self.randomize_tile_trap_floor_tile as u8); + bytes.push(self.allow_killable_thief as u8); + bytes.push(self.randomize_sprite_on_hit as u8); + bytes.push(self.hero_mode as u8); + bytes.push(self.increase_brightness as u8); + bytes.push(self.mute_music_enable_msu_1 as u8); + bytes.push(self.agahnim_bounce_balls as u8); + + bytes + } +} + +impl Default for OptionFlags { + fn default() -> Self { + Self { + randomize_enemies: true, + randomize_enemies_type: RandomizeEnemiesType::Chaos, + randomize_bush_enemy_chance: true, + randomize_enemy_health_range: false, + randomize_enemy_health_type: RandomizeEnemyHpType::Medium, + randomize_enemy_damage: false, + allow_enemy_zero_damage: false, + shuffle_enemy_damage_groups: false, + enemy_damage_chaos_mode: false, + easy_mode_escape: false, + enemies_absorbable: false, + absorbable_spawn_rate: 0, + absorbable_types: HashMap::new(), + 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_dungeon_palettes: true, + set_blackout_mode: false, + randomize_overworld_palettes: true, + randomize_sprite_palettes: true, + set_advanced_sprite_palettes: false, + puke_mode: false, + negative_mode: false, + grayscale_mode: false, + generate_spoilers: true, + randomize_link_sprite_palette: false, + randomize_pots: true, + shuffle_music: false, + bootleg_magic: false, + debug_mode: false, + custom_bosses: false, + heart_beep_speed: HeartBeepSpeed::Half, + alternate_gfx: false, + shield_graphics: ["shield_gfx", "normal.gfx"].iter().collect(), + sword_graphics: ["sword_gfx", "normal.gfx"].iter().collect(), + bee_mizer: false, + bees_level: BeeLevel::Level1, + debug_force_enemy: false, + debug_force_enemy_id: 0, + debug_force_boss: false, + debug_force_boss_id: BossType::Trinexx, + debug_open_shutter_doors: false, + debug_force_enemy_damage_zero: false, + debug_show_room_id_in_rupee_counter: false, + o_h_k_o: false, + randomize_tile_trap_pattern: false, + randomize_tile_trap_floor_tile: false, + allow_killable_thief: false, + randomize_sprite_on_hit: false, + hero_mode: false, + increase_brightness: false, + mute_music_enable_msu_1: false, + agahnim_bounce_balls: false, + use_manual_bosses: false, + manual_bosses: Default::default(), + } + } +} diff --git a/enemize/src/rom.rs b/enemize/src/rom.rs index 11a9874..2807194 100644 --- a/enemize/src/rom.rs +++ b/enemize/src/rom.rs @@ -1,7 +1,164 @@ -const ENEMIZER_INFO_SEED_OFFSET: usize = 0; -const ENEMIZER_INFO_SEED_LENGTH: usize = 12; -const ENEMIZER_INFO_VERSION_OFFSET: usize = ENEMIZER_INFO_SEED_OFFSET + ENEMIZER_INFO_SEED_LENGTH; -const ENEMIZER_INFO_VERSION_LENGTH: usize = 8; -const ENEMIZER_INFO_FLAGS_OFFSET: usize = ENEMIZER_INFO_VERSION_OFFSET + ENEMIZER_INFO_VERSION_LENGTH; -const ENEMIZER_INFO_FLAGS_LENGTH: usize = 0x50; +use std::collections::BTreeMap; +use std::iter; +use std::ops::Range; +use anyhow::bail; + +use crate::constants::*; +use crate::option_flags::OptionFlags; +use crate::Patch; + +pub const ENEMIZER_INFO_SEED_OFFSET: usize = 0; +pub const ENEMIZER_INFO_SEED_LENGTH: usize = 12; +pub const ENEMIZER_INFO_VERSION_OFFSET: usize = ENEMIZER_INFO_SEED_OFFSET + ENEMIZER_INFO_SEED_LENGTH; +pub const ENEMIZER_INFO_VERSION_LENGTH: usize = 8; +pub const ENEMIZER_INFO_FLAGS_OFFSET: usize = + ENEMIZER_INFO_VERSION_OFFSET + ENEMIZER_INFO_VERSION_LENGTH; +pub const ENEMIZER_INFO_FLAGS_LENGTH: usize = 0x50; + +pub const RANDOMIZER_HIDDEN_ENEMIES_FLAG: usize = 0x00; +pub const CLOSE_BLIND_DOOR_FLAG: usize = 0x01; +pub const MOLDORM_EYES_FLAG: usize = 0x02; +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 RANDOMIZER_MODE_FLAG: usize = 0x180032; + +pub struct RomData { + pub enemizer_info_table_base_address: usize, + pub enemizer_option_flags_base_address: usize, + spoiler: String, + rom_data: Vec, + patch_data: BTreeMap, + seed: i32, +} + +impl RomData { + pub fn spoiler(&self) -> &str { + self.spoiler.as_str() + } + + pub fn generate_patch(&self) -> Vec { + let mut patches = vec![]; + + for (&addr, &byte) in self.patch_data.iter() { + match patches.last_mut().filter(|p: &&mut Patch| p.address + 1 == addr) { + None => patches.push(Patch { address: addr, patch_data: vec![byte] }), + Some(patch) => patch.patch_data.push(byte) + } + } + + patches + } + + fn set_rom_bytes(&mut self, bytes: &[u8], range: Range) { + self.rom_data.splice(range, bytes.into_iter().map(|&b| b)); + } + + fn set_patch_bytes(&mut self, range: Range) { + let slice = &self.rom_data[range.clone()]; + self.patch_data.extend(iter::zip(range, slice.into_iter().map(|&b| b))); + } + + 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' + } + + fn assert_rom_length(&self) { + assert!(self.rom_data.len() >= ENEMIZER_FILE_LENGTH, "You need to expand the rom before you can use Enemizer features."); + } + + pub fn get_enemizer_seed(&self) -> i32 { + self.seed + } + + 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_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..]; + + self.seed = i32::from_str_radix(seed_str, 10)?; + } + + Ok(self.seed) + } + + pub fn set_enemizer_seed(&mut self, seed: i32) { + self.assert_rom_length(); + + 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_end = seed_start + ENEMIZER_INFO_SEED_LENGTH; + self.seed = seed; + + self.set_rom_bytes(&bytes, seed_start..seed_end); + self.set_patch_bytes(seed_start..seed_end); + } + + 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_end = version_start + ENEMIZER_INFO_VERSION_LENGTH; + Ok(std::str::from_utf8(&self.rom_data[version_start..version_end])?) + } else { + bail!("Not Enemizer Rom") + } + } + + pub fn set_enemizer_version(&mut self, version: String) { + self.assert_rom_length(); + + 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_end = version_start + ENEMIZER_INFO_VERSION_LENGTH; + self.set_rom_bytes(&bytes, version_start..version_end); + self.set_patch_bytes(version_start..version_end) + } + + pub fn set_info_flags(&mut self, flags: OptionFlags) -> anyhow::Result<()>{ + let bytes = flags.into_bytes(); + if bytes.len() > 0x100 - ENEMIZER_INFO_FLAGS_OFFSET { + 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_end = flags_start + bytes.len(); + self.set_rom_bytes(&bytes, flags_start..flags_end); + self.set_patch_bytes(flags_start..flags_end); + + Ok(()) + } + + 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_end = flags_start + ENEMIZER_INFO_FLAGS_LENGTH; + OptionFlags::try_from_bytes(&self.rom_data[flags_start..flags_end]).ok() + } else { + None + } + } + + 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) { + self.set_rom_bytes( + &patch.patch_data, + patch.address..(patch.address + patch.patch_data.len()), + ); + } +} From 7a6689efd1a6f2ec629385918328ffbb3dc88c82 Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Sun, 29 May 2022 19:16:49 -0400 Subject: [PATCH 05/29] 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 06/29] 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 07/29] 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(()) +} From f6eb37b6febffcc789f26d713c7bfd849bb08af2 Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Tue, 31 May 2022 17:08:52 -0400 Subject: [PATCH 08/29] Use clap for processing arguments. --- Cargo.lock | 160 ++++++++++++++++++++++++++++++++++++++++++++ enemize/Cargo.toml | 1 + enemize/src/main.rs | 34 +++++++++- 3 files changed, 193 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5de52f3..6ad2c11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,23 @@ version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "base_patch_generator" version = "0.1.0" @@ -40,11 +57,51 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "3.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "clap_lex", + "indexmap", + "lazy_static", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_derive" +version = "3.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "enemize" version = "0.1.0" dependencies = [ "anyhow", + "clap", "serde", "serde_json", "thiserror", @@ -59,6 +116,37 @@ dependencies = [ "instant", ] +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "indexmap" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" +dependencies = [ + "autocfg", + "hashbrown", +] + [[package]] name = "instant" version = "0.1.12" @@ -74,6 +162,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.126" @@ -86,6 +180,36 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" +[[package]] +name = "os_str_bytes" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.39" @@ -159,6 +283,12 @@ dependencies = [ "serde", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "1.0.95" @@ -184,6 +314,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" + [[package]] name = "thiserror" version = "1.0.31" @@ -210,6 +355,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "winapi" version = "0.3.9" @@ -226,6 +377,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/enemize/Cargo.toml b/enemize/Cargo.toml index a612509..8becd9b 100644 --- a/enemize/Cargo.toml +++ b/enemize/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] anyhow = "1" +clap = { version = "3.1.18", features = ["derive"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "1" diff --git a/enemize/src/main.rs b/enemize/src/main.rs index e7a11a9..f032483 100644 --- a/enemize/src/main.rs +++ b/enemize/src/main.rs @@ -1,3 +1,33 @@ -fn main() { - println!("Hello, world!"); +use std::path::PathBuf; + +use clap::Parser; + +/// Randomizes enemy placements in The Legend of Zelda: A Link to the Past for the Super Nintendo +/// Entertainment System +#[derive(Debug, Parser)] +#[clap(author, version, about)] +struct Args { + /// path to the base rom file + rom: PathBuf, + /// seed number + #[clap(short, long)] + seed: Option, + /// path to the base2patched.json (not used in binary mode) + #[clap(short, long, required_unless_present = "binary")] + base: Option, + /// path to the randomizerPatch.json (not used in binary mode) + #[clap(short, long, required_unless_present = "binary")] + randomizer: Option, + /// path to the enemizerOptions.json + #[clap(short, long)] + enemizer: PathBuf, + /// path to the intended output file + output: PathBuf, + /// operate in binary mode (takes already randomized SFC and applies enemizer directly to ROM) + #[clap(long)] + binary: bool, +} + +fn main() { + let args = Args::parse(); } From 568360206d53bbb879a67cb9a33ffcbc09ce6fd3 Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Tue, 31 May 2022 22:48:45 -0400 Subject: [PATCH 09/29] Begin loading ROM data for randomization. Use the fact that the default path for the asar symbols file is just the ROM's file with a ".sym" extension. When enemize runs, it checks if this file exists, and if it doesn't, runs asar on the ROM to create the symbols file. --- Cargo.lock | 54 ++++++++++++++++++++++++++++++++++++++ Cargo.toml | 4 +-- enemize/Cargo.toml | 1 + enemize/src/main.rs | 63 +++++++++++++++++++++++++++++++++++++++------ 4 files changed, 112 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ad2c11..c449041 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -102,6 +102,7 @@ version = "0.1.0" dependencies = [ "anyhow", "clap", + "rand", "serde", "serde_json", "thiserror", @@ -116,6 +117,17 @@ dependencies = [ "instant", ] +[[package]] +name = "getrandom" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "hashbrown" version = "0.11.2" @@ -186,6 +198,12 @@ version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -228,6 +246,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + [[package]] name = "redox_syscall" version = "0.2.13" @@ -361,6 +409,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 636360e..aee92b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [workspace] members = [ - "enemize", - "bin_comp", "base_patch_generator", + "bin_comp", + "enemize", ] diff --git a/enemize/Cargo.toml b/enemize/Cargo.toml index 8becd9b..a763a91 100644 --- a/enemize/Cargo.toml +++ b/enemize/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] anyhow = "1" clap = { version = "3.1.18", features = ["derive"] } +rand = "0.8" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "1" diff --git a/enemize/src/main.rs b/enemize/src/main.rs index f032483..ea0f310 100644 --- a/enemize/src/main.rs +++ b/enemize/src/main.rs @@ -1,7 +1,13 @@ -use std::path::PathBuf; +use std::fs::File; +use std::io::prelude::*; +use std::path::{Path, PathBuf}; +use std::time::Instant; +use anyhow::ensure; use clap::Parser; +use enemize::{asar, rom::RomData}; + /// Randomizes enemy placements in The Legend of Zelda: A Link to the Past for the Super Nintendo /// Entertainment System #[derive(Debug, Parser)] @@ -12,12 +18,6 @@ struct Args { /// seed number #[clap(short, long)] seed: Option, - /// path to the base2patched.json (not used in binary mode) - #[clap(short, long, required_unless_present = "binary")] - base: Option, - /// path to the randomizerPatch.json (not used in binary mode) - #[clap(short, long, required_unless_present = "binary")] - randomizer: Option, /// path to the enemizerOptions.json #[clap(short, long)] enemizer: PathBuf, @@ -28,6 +28,53 @@ struct Args { binary: bool, } -fn main() { +fn main() -> anyhow::Result<()> { let args = Args::parse(); + + let stopwatch = Instant::now(); + + let options = serde_json::from_reader(File::open(args.enemizer)?)?; + let symbols = load_symbols(&args.rom)?; + + let mut raw_data = vec![]; + let mut rom_file = File::open(args.rom)?; + rom_file.read_to_end(&mut raw_data)?; + + raw_data.resize(2 * 1024 * 1024, 0); + + let mut rom_data = RomData::new(symbols, raw_data); + ensure!(!rom_data.is_enemizer(), "It appears that the provided base ROM is already enemized. Please ensure you are using an original game ROM."); + + let seed = args.seed.unwrap_or_else(|| rand::random()); + + //let randomized_rom = randomize_rom(seed, raw_data, options); + + if args.binary { + + } else { + + } + + println!("Seed generated in: {}ms", stopwatch.elapsed().as_millis()); + + Ok(()) +} + +fn load_symbols(rom: &Path) -> anyhow::Result { + let sym_path = rom.with_extension("sym"); + + if let Err(_) = std::fs::metadata(&sym_path) { + let status = std::process::Command::new("asar") + .arg("--no-title-check") + .arg("--fix-checksum=off") + .arg("--symbols=wla") + .arg("-Iassembly/src") + .arg("assembly/src/main.asm") + .arg(rom) + .status()?; + + ensure!(status.success(), "could not generate symbols"); + } + + asar::load_symbols(sym_path.as_path()) } From 41be111857e8660b44378525d47e409852f3b40b Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Wed, 1 Jun 2022 09:53:11 -0400 Subject: [PATCH 10/29] Add RomData::is_randomizer. --- enemize/src/rom.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/enemize/src/rom.rs b/enemize/src/rom.rs index 8b7a2e0..95b1fb6 100644 --- a/enemize/src/rom.rs +++ b/enemize/src/rom.rs @@ -69,6 +69,29 @@ impl RomData { && self.rom_data[self.asar_symbols["enemizer_info_table"] + ENEMIZER_INFO_SEED_OFFSET + 1] == b'N' } + pub fn is_randomizer(&self) -> bool { + let acceptable = [ + // item rando + b"VT", + // entrance rando + b"ER", + // door rando + b"DR", + // Berserker's multiworld + b"BM", + // Berserker's multiworld doors + b"BD", + // Archipelago + b"AP", + // Archipelago with door rando + b"AD", + ]; + + acceptable.iter().any(|abbr| &abbr[..] == &self.rom_data[0x7FC0..0x7Fc1]) || + (self.rom_data.len() >= 0x20_0000 && + &self.rom_data[0x7FC0..0x7FCE] == b"ZELDANODENSETSU") + } + fn assert_rom_length(&self) { assert!(self.rom_data.len() >= ENEMIZER_FILE_LENGTH, "You need to expand the rom before you can use Enemizer features."); } From 8901e0e8b0c0e0a17455ed9792b59747bfd8589b Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Sat, 4 Jun 2022 09:29:47 -0400 Subject: [PATCH 11/29] Serialize option enums to u8, for backward compatibility. --- enemize/src/bosses/mod.rs | 28 +++- enemize/src/option_flags.rs | 269 +++++++++++++++++++++++++----------- 2 files changed, 217 insertions(+), 80 deletions(-) diff --git a/enemize/src/bosses/mod.rs b/enemize/src/bosses/mod.rs index 872412d..a5f02c7 100644 --- a/enemize/src/bosses/mod.rs +++ b/enemize/src/bosses/mod.rs @@ -1,9 +1,12 @@ use std::marker::PhantomData; +use serde::{Deserialize, Serialize}; + use crate::InvalidEnumError; -#[derive(Debug)] +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] #[repr(u8)] +#[serde(into = "u8", try_from = "u8")] pub enum BossType { Kholdstare, Moldorm, @@ -22,6 +25,29 @@ pub enum BossType { NoBoss = 255, } +impl From for u8 { + fn from(t: BossType) -> u8 { + use BossType::*; + + match t { + Kholdstare => 0, + Moldorm => 1, + Mothula => 2, + Vitreous => 3, + Helmasaur => 4, + Armos => 5, + Lanmola => 6, + Blind => 7, + Arrghus => 8, + Trinexx => 9, + Agahnim => 10, + Agahnim2 => 11, + Ganon => 12, + NoBoss => 255, + } + } +} + impl TryFrom for BossType { type Error = InvalidEnumError; diff --git a/enemize/src/option_flags.rs b/enemize/src/option_flags.rs index 2170c52..e0fca4e 100644 --- a/enemize/src/option_flags.rs +++ b/enemize/src/option_flags.rs @@ -3,10 +3,12 @@ use std::fmt; use std::marker::PhantomData; use std::path::PathBuf; +use serde::{Deserialize, Serialize}; + use crate::bosses::BossType; use crate::InvalidEnumError; -#[derive(Debug, Default)] +#[derive(Debug, Default, Deserialize, Serialize)] pub struct ManualBosses { pub eastern_palace: String, pub desert_palace: String, @@ -26,7 +28,8 @@ pub struct ManualBosses { pub ganon: String, } -#[derive(Debug)] +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[serde(into = "u8", try_from = "u8")] pub enum RandomizeEnemiesType { Basic, Normal, @@ -35,6 +38,20 @@ pub enum RandomizeEnemiesType { Insanity, } +impl From for u8 { + fn from(t: RandomizeEnemiesType) -> u8 { + use RandomizeEnemiesType::*; + + match t { + Basic => 0, + Normal => 1, + Hard => 2, + Chaos => 3, + Insanity => 4, + } + } +} + impl TryFrom for RandomizeEnemiesType { type Error = InvalidEnumError; @@ -50,7 +67,8 @@ impl TryFrom for RandomizeEnemiesType { } } -#[derive(Debug)] +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[serde(into = "u8", try_from = "u8")] pub enum RandomizeEnemyHpType { Easy, Medium, @@ -58,6 +76,19 @@ pub enum RandomizeEnemyHpType { Patty } +impl From for u8 { + fn from(t: RandomizeEnemyHpType) -> u8 { + use RandomizeEnemyHpType::*; + + match t { + Easy => 0, + Medium => 1, + Hard => 2, + Patty => 3, + } + } +} + impl TryFrom for RandomizeEnemyHpType { type Error = InvalidEnumError; @@ -72,13 +103,26 @@ impl TryFrom for RandomizeEnemyHpType { } } -#[derive(Debug)] +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[serde(into = "u8", try_from = "u8")] pub enum RandomizeBossesType { Basic, Normal, Chaos } +impl From for u8 { + fn from(t: RandomizeBossesType) -> u8 { + use RandomizeBossesType::*; + + match t { + Basic => 0, + Normal => 1, + Chaos => 2, + } + } +} + impl TryFrom for RandomizeBossesType { type Error = InvalidEnumError; @@ -92,11 +136,18 @@ impl TryFrom for RandomizeBossesType { } } -#[derive(Debug)] +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[serde(into = "u8", try_from = "u8")] pub enum SwordType { Normal } +impl From for u8 { + fn from(_t: SwordType) -> u8 { + 0 + } +} + impl TryFrom for SwordType { type Error = InvalidEnumError; @@ -108,11 +159,18 @@ impl TryFrom for SwordType { } } -#[derive(Debug)] +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[serde(into = "u8", try_from = "u8")] pub enum ShieldType { Normal } +impl From for u8 { + fn from(_t: ShieldType) -> u8 { + 0 + } +} + impl TryFrom for ShieldType { type Error = InvalidEnumError; @@ -124,7 +182,8 @@ impl TryFrom for ShieldType { } } -#[derive(Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +#[serde(into = "u8", try_from = "u8")] pub enum AbsorbableType { Heart, GreenRupee, @@ -142,6 +201,29 @@ pub enum AbsorbableType { BigKey, } +impl From for u8 { + fn from(t: AbsorbableType) -> u8 { + use AbsorbableType::*; + + match t { + Heart => 0, + GreenRupee => 1, + BlueRupee => 2, + RedRupee => 3, + Bomb1 => 4, + Bomb4 => 5, + Bomb8 => 6, + SmallMagic => 7, + FullMagic => 8, + Arrow5 => 9, + Arrow10 => 10, + Fairy => 11, + Key => 12, + BigKey => 13, + } + } +} + impl TryFrom for AbsorbableType { type Error = InvalidEnumError; @@ -150,16 +232,17 @@ impl TryFrom for AbsorbableType { 0 => Ok(Self::Heart), 1 => Ok(Self::GreenRupee), 2 => Ok(Self::BlueRupee), - 3 => Ok(Self::Bomb1), - 4 => Ok(Self::Bomb4), - 5 => Ok(Self::Bomb8), - 6 => Ok(Self::SmallMagic), - 7 => Ok(Self::FullMagic), - 8 => Ok(Self::Arrow5), - 9 => Ok(Self::Arrow10), - 10 => Ok(Self::Fairy), - 11 => Ok(Self::Key), - 12 => Ok(Self::BigKey), + 3 => Ok(Self::RedRupee), + 4 => Ok(Self::Bomb1), + 5 => Ok(Self::Bomb4), + 6 => Ok(Self::Bomb8), + 7 => Ok(Self::SmallMagic), + 8 => Ok(Self::FullMagic), + 9 => Ok(Self::Arrow5), + 10 => Ok(Self::Arrow10), + 11 => Ok(Self::Fairy), + 12 => Ok(Self::Key), + 13 => Ok(Self::BigKey), _ => Err(InvalidEnumError(PhantomData)) } } @@ -190,7 +273,8 @@ impl fmt::Display for AbsorbableType { } } -#[derive(Debug)] +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[serde(into = "u8", try_from = "u8")] pub enum HeartBeepSpeed { Normal, Half, @@ -204,6 +288,19 @@ impl Default for HeartBeepSpeed { } } +impl From for u8 { + fn from(speed: HeartBeepSpeed) -> u8 { + use HeartBeepSpeed::*; + + match speed { + Normal => 0, + Half => 1, + Quarter => 2, + Off => 3, + } + } +} + impl TryFrom for HeartBeepSpeed { type Error = InvalidEnumError; @@ -218,7 +315,7 @@ impl TryFrom for HeartBeepSpeed { } } -#[derive(Debug)] +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] pub enum BeeLevel { Level1, Level2, @@ -241,6 +338,19 @@ impl fmt::Display for BeeLevel { } } +impl From for u8 { + fn from(level: BeeLevel) -> u8 { + use BeeLevel::*; + + match level { + Level1 => 0, + Level2 => 1, + Level3 => 2, + Level4 => 3, + } + } +} + impl TryFrom for BeeLevel { type Error = InvalidEnumError; @@ -255,7 +365,7 @@ impl TryFrom for BeeLevel { } } -#[derive(Debug)] +#[derive(Debug, Deserialize, Serialize)] pub struct OptionFlags { pub randomize_enemies: bool, pub randomize_enemies_type: RandomizeEnemiesType, @@ -341,16 +451,17 @@ impl OptionFlags { absorbable_types.insert(AbsorbableType::Heart, bytes[10] != 0); absorbable_types.insert(AbsorbableType::GreenRupee, bytes[11] != 0); absorbable_types.insert(AbsorbableType::BlueRupee, bytes[12] != 0); - absorbable_types.insert(AbsorbableType::Bomb1, bytes[13] != 0); - absorbable_types.insert(AbsorbableType::Bomb4, bytes[14] != 0); - absorbable_types.insert(AbsorbableType::Bomb8, bytes[15] != 0); - absorbable_types.insert(AbsorbableType::SmallMagic, bytes[16] != 0); - absorbable_types.insert(AbsorbableType::FullMagic, bytes[17] != 0); - absorbable_types.insert(AbsorbableType::Arrow5, bytes[18] != 0); - absorbable_types.insert(AbsorbableType::Arrow10, bytes[19] != 0); - absorbable_types.insert(AbsorbableType::Fairy, bytes[20] != 0); - absorbable_types.insert(AbsorbableType::Key, bytes[21] != 0); - absorbable_types.insert(AbsorbableType::BigKey, bytes[22] != 0); + absorbable_types.insert(AbsorbableType::RedRupee, bytes[13] != 0); + absorbable_types.insert(AbsorbableType::Bomb1, bytes[14] != 0); + absorbable_types.insert(AbsorbableType::Bomb4, bytes[15] != 0); + absorbable_types.insert(AbsorbableType::Bomb8, bytes[16] != 0); + absorbable_types.insert(AbsorbableType::SmallMagic, bytes[17] != 0); + absorbable_types.insert(AbsorbableType::FullMagic, bytes[18] != 0); + absorbable_types.insert(AbsorbableType::Arrow5, bytes[19] != 0); + absorbable_types.insert(AbsorbableType::Arrow10, bytes[20] != 0); + absorbable_types.insert(AbsorbableType::Fairy, bytes[21] != 0); + absorbable_types.insert(AbsorbableType::Key, bytes[21] != 1); + absorbable_types.insert(AbsorbableType::BigKey, bytes[23] != 0); Ok(OptionFlags { randomize_enemies: bytes[0] != 0, @@ -364,55 +475,55 @@ impl OptionFlags { enemies_absorbable: bytes[8] != 0, absorbable_spawn_rate: bytes[9], absorbable_types, - boss_madness: bytes[23] != 0, - randomize_bosses: bytes[24] != 0, - randomize_bosses_type: bytes[25].try_into()?, - randomize_boss_health: bytes[26] != 0, - randomize_boss_health_min_amount: bytes[27], - randomize_boss_health_max_amount: bytes[28], - randomize_boss_damage: bytes[29] != 0, - randomize_boss_damage_min_amount: bytes[30], - randomize_boss_damage_max_amount: bytes[31], - randomize_boss_behavior: bytes[32] != 0, - randomize_dungeon_palettes: bytes[33] != 0, - set_blackout_mode: bytes[34] != 0, - randomize_overworld_palettes: bytes[35] != 0, - randomize_sprite_palettes: bytes[36] != 0, - set_advanced_sprite_palettes: bytes[37] != 0, - puke_mode: bytes[38] != 0, - negative_mode: bytes[39] != 0, - grayscale_mode: bytes[40] != 0, - generate_spoilers: bytes[41] != 0, - randomize_link_sprite_palette: bytes[42] != 0, - randomize_pots: bytes[43] != 0, - shuffle_music: bytes[44] != 0, - bootleg_magic: bytes[45] != 0, - debug_mode: bytes[46] != 0, - custom_bosses: bytes[47] != 0, - heart_beep_speed: bytes[48].try_into()?, - alternate_gfx: bytes[49] != 0, - // Skip byte 50 (shield_graphics) - shuffle_enemy_damage_groups: bytes[51] != 0, - enemy_damage_chaos_mode: bytes[52] != 0, - // Skip byte 53 (sword_graphics) - bee_mizer: bytes[54] != 0, - bees_level: bytes[55].try_into()?, - debug_force_enemy: bytes[56] != 0, - debug_force_enemy_id: bytes[57], - debug_force_boss: bytes[58] != 0, - debug_force_boss_id: bytes[59].try_into()?, - debug_open_shutter_doors: bytes[60] != 0, - debug_force_enemy_damage_zero: bytes[61] != 0, - debug_show_room_id_in_rupee_counter: bytes[62] != 0, - o_h_k_o: bytes[63] != 0, - randomize_tile_trap_pattern: bytes[64] != 0, - randomize_tile_trap_floor_tile: bytes[65] != 0, - allow_killable_thief: bytes[66] != 0, - randomize_sprite_on_hit: bytes[67] != 0, - hero_mode: bytes[68] != 0, - increase_brightness: bytes[69] != 0, - mute_music_enable_msu_1: bytes[70] != 0, - agahnim_bounce_balls: bytes[71] != 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_dungeon_palettes: bytes[34] != 0, + set_blackout_mode: bytes[35] != 0, + randomize_overworld_palettes: bytes[36] != 0, + randomize_sprite_palettes: bytes[37] != 0, + set_advanced_sprite_palettes: bytes[38] != 0, + puke_mode: bytes[39] != 0, + negative_mode: bytes[40] != 0, + grayscale_mode: bytes[41] != 0, + generate_spoilers: bytes[42] != 0, + randomize_link_sprite_palette: bytes[43] != 0, + randomize_pots: bytes[44] != 0, + shuffle_music: bytes[45] != 0, + bootleg_magic: bytes[46] != 0, + debug_mode: bytes[47] != 0, + custom_bosses: bytes[48] != 0, + heart_beep_speed: bytes[49].try_into()?, + alternate_gfx: bytes[50] != 0, + // Skip byte 51 (shield_graphics) + shuffle_enemy_damage_groups: bytes[52] != 0, + enemy_damage_chaos_mode: bytes[53] != 0, + // Skip byte 54 (sword_graphics) + bee_mizer: bytes[55] != 0, + bees_level: bytes[56].try_into()?, + debug_force_enemy: bytes[57] != 0, + debug_force_enemy_id: bytes[58], + debug_force_boss: bytes[59] != 0, + debug_force_boss_id: bytes[60].try_into()?, + debug_open_shutter_doors: bytes[61] != 0, + debug_force_enemy_damage_zero: bytes[62] != 0, + debug_show_room_id_in_rupee_counter: bytes[63] != 0, + o_h_k_o: bytes[64] != 0, + randomize_tile_trap_pattern: bytes[65] != 0, + randomize_tile_trap_floor_tile: bytes[66] != 0, + allow_killable_thief: bytes[67] != 0, + randomize_sprite_on_hit: bytes[68] != 0, + hero_mode: bytes[69] != 0, + increase_brightness: bytes[70] != 0, + mute_music_enable_msu_1: bytes[71] != 0, + agahnim_bounce_balls: bytes[72] != 0, ..Default::default() }) } From 87b0476b7e5844a6cc355612aeb262fd20f3cc30 Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Sat, 4 Jun 2022 09:36:08 -0400 Subject: [PATCH 12/29] Make it easy to generate necessary artifacts to run enemize-rs. Maybe these files could be committed? It's not like they'll change very often. But maybe there's copyrighted material in them I haven't noticed. --- .gitignore | 5 +++-- prepare.sh | 8 ++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 prepare.sh diff --git a/.gitignore b/.gitignore index ade3621..23fb76a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target -exported_symbols.txt -patchData.json +asar_symbols.txt +base_patch.json +enemizer_base_patch.json diff --git a/prepare.sh b/prepare.sh new file mode 100644 index 0000000..ecb6269 --- /dev/null +++ b/prepare.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +if [[ ! -s base_patch.json || ! -s asar_symbols.txt ]]; then +cargo run -p bin_comp -- assembly/src/main.asm base_patch.json asar_symbols.txt +fi +if [[ ! -s enemizer_base_patch.json ]]; then +cargo run -p base_patch_generator -- "$1" base_patch.json asar_symbols.txt enemizer_base_patch.json +fi From 1113adca98e811f8416da82bb6318211d829e67d Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Sat, 4 Jun 2022 09:39:52 -0400 Subject: [PATCH 13/29] Make it run (no actual randomizing yet). --- enemize/src/constants.rs | 102 +++++++++++++++++++++++++++++++++++++++ enemize/src/lib.rs | 1 + enemize/src/main.rs | 47 +++++++++--------- enemize/src/randomize.rs | 29 +++++++++++ enemize/src/rom.rs | 44 +++++++++++++++-- 5 files changed, 196 insertions(+), 27 deletions(-) create mode 100644 enemize/src/randomize.rs diff --git a/enemize/src/constants.rs b/enemize/src/constants.rs index 630ff5b..4cd503c 100644 --- a/enemize/src/constants.rs +++ b/enemize/src/constants.rs @@ -10,3 +10,105 @@ pub const NEW_BOSS_GRAPHICS: usize = 0x1B0000; pub const RANDOM_SPRITE_GRAPHICS: usize = 0x300000; pub const ENEMIZER_FILE_LENGTH: usize = 0x200000; pub const HIDDEN_ENEMY_CHANCE_POOL: usize = 0xD7BBB; + +pub(crate) const ORIGINAL_ROOM_POINTERS: [u8; 640] = [ 0x62, 0xF4, 0x6C, 0xF4, 0x7A, 0xF4, 0xDD, 0xF5, 0x85, + 0xF4, 0x90, 0xF4, 0x90, 0xF4, 0x97, 0xF4, 0xA2, 0xF4, 0xA9, 0xF4, 0xB5, 0xF4, 0xC0, 0xF4, + 0xCB, 0xF4, 0xD8, 0xF4, 0xDF, 0xF4, 0xEA, 0xF4, 0xEA, 0xF4, 0xF1, 0xF4, 0xFC, 0xF4, 0x03, + 0xF5, 0x11, 0xF5, 0x18, 0xF5, 0x23, 0xF5, 0x2E, 0xF5, 0x73, 0xFC, 0x3A, 0xF5, 0x41, 0xF5, + 0x4D, 0xF5, 0x58, 0xF5, 0x63, 0xF5, 0x6E, 0xF5, 0x79, 0xF5, 0x84, 0xF5, 0x8B, 0xF5, 0x8B, + 0xF5, 0x03, 0xF5, 0x92, 0xF5, 0x99, 0xF5, 0x99, 0xF5, 0xA6, 0xF5, 0xB2, 0xF5, 0xBD, 0xF5, + 0xC4, 0xF5, 0xCB, 0xF5, 0x73, 0xFC, 0xD6, 0xF5, 0xD6, 0xF5, 0xDD, 0xF5, 0xE4, 0xF5, 0xEF, + 0xF5, 0xFB, 0xF5, 0x06, 0xF6, 0x0D, 0xF6, 0x18, 0xF6, 0x1F, 0xF6, 0x18, 0xF6, 0x26, 0xF6, + 0x31, 0xF6, 0x3B, 0xF6, 0x46, 0xF6, 0x51, 0xF6, 0x58, 0xF6, 0x63, 0xF6, 0x6E, 0xF6, 0x7A, + 0xF6, 0x86, 0xF6, 0x91, 0xF6, 0x9D, 0xF6, 0xA4, 0xF6, 0xAB, 0xF6, 0xB6, 0xF6, 0xBD, 0xF6, + 0xBD, 0xF6, 0xBD, 0xF6, 0xC4, 0xF6, 0xD0, 0xF6, 0xDA, 0xF6, 0xE5, 0xF6, 0xF0, 0xF6, 0xFB, + 0xF6, 0x05, 0xF7, 0x13, 0xF7, 0x1E, 0xF7, 0x2C, 0xF7, 0x37, 0xF7, 0x42, 0xF7, 0x49, 0xF7, + 0x50, 0xF7, 0x57, 0xF7, 0x5E, 0xF7, 0x65, 0xF7, 0x6C, 0xF7, 0x73, 0xF7, 0x7E, 0xF7, 0x89, + 0xF7, 0x94, 0xF7, 0xA0, 0xF7, 0xA7, 0xF7, 0xA0, 0xF7, 0xB2, 0xF7, 0xBD, 0xF7, 0xC8, 0xF7, + 0xD2, 0xF7, 0xDD, 0xF7, 0xE4, 0xF7, 0xEB, 0xF7, 0xEB, 0xF7, 0xF7, 0xF7, 0x02, 0xF8, 0x0D, + 0xF8, 0x14, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x2B, 0xF8, 0x36, 0xF8, 0x41, 0xF8, 0x48, 0xF8, + 0x4F, 0xF8, 0x56, 0xF8, 0x63, 0xF8, 0x70, 0xF8, 0x70, 0xF8, 0x70, 0xF8, 0x70, 0xF8, 0x7A, + 0xF8, 0x81, 0xF8, 0x8B, 0xF8, 0x96, 0xF8, 0xA1, 0xF8, 0xAC, 0xF8, 0xAC, 0xF8, 0xB3, 0xF8, + 0xBA, 0xF8, 0xC1, 0xF8, 0xC8, 0xF8, 0xC8, 0xF8, 0xD4, 0xF8, 0xD4, 0xF8, 0xDE, 0xF8, 0xDE, + 0xF8, 0xE5, 0xF8, 0xF2, 0xF8, 0xF9, 0xF8, 0x04, 0xF9, 0x04, 0xF9, 0x0B, 0xF9, 0x16, 0xF9, + 0x1D, 0xF9, 0x28, 0xF9, 0x28, 0xF9, 0x2F, 0xF9, 0x3A, 0xF9, 0x45, 0xF9, 0x50, 0xF9, 0x5B, + 0xF9, 0x5B, 0xF9, 0x65, 0xF9, 0x6C, 0xF9, 0x76, 0xF9, 0x81, 0xF9, 0x88, 0xF9, 0x93, 0xF9, + 0x9A, 0xF9, 0x93, 0xF9, 0xA5, 0xF9, 0xAC, 0xF9, 0xB7, 0xF9, 0xC2, 0xF9, 0xCC, 0xF9, 0xD3, + 0xF9, 0xDD, 0xF9, 0xE4, 0xF9, 0xEF, 0xF9, 0xF6, 0xF9, 0xF6, 0xF9, 0x01, 0xFA, 0x08, 0xFA, + 0x14, 0xFA, 0x1E, 0xFA, 0x25, 0xFA, 0x2C, 0xFA, 0x37, 0xFA, 0x42, 0xFA, 0x0A, 0xF5, 0x4D, + 0xFA, 0x54, 0xFA, 0x5B, 0xFA, 0x62, 0xFA, 0x69, 0xFA, 0x74, 0xFA, 0x74, 0xFA, 0x7F, 0xFA, + 0x86, 0xFA, 0x92, 0xFA, 0x99, 0xFA, 0xA0, 0xFA, 0xA7, 0xFA, 0xB2, 0xFA, 0x0A, 0xF5, 0xB9, + 0xFA, 0xC0, 0xFA, 0xC7, 0xFA, 0xCE, 0xFA, 0xCE, 0xFA, 0xCE, 0xFA, 0xD5, 0xFA, 0xD5, 0xFA, + 0xDF, 0xFA, 0xDF, 0xFA, 0xEB, 0xFA, 0xF6, 0xFA, 0x01, 0xFB, 0x01, 0xFB, 0xB2, 0xFA, 0x0A, + 0xF5, 0x01, 0xFB, 0x01, 0xFB, 0x08, 0xFB, 0x0F, 0xFB, 0xCE, 0xFA, 0xCE, 0xFA, 0x1A, 0xFB, + 0x1A, 0xFB, 0x21, 0xFB, 0x2C, 0xFB, 0x37, 0xFB, 0x3E, 0xFB, 0x45, 0xFB, 0x4C, 0xFB, 0x4C, + 0xFB, 0x53, 0xFB, 0x53, 0xFB, 0x5A, 0xFB, 0x68, 0xFB, 0x68, 0xFB, 0x73, 0xFB, 0x7E, 0xFB, + 0x7E, 0xFB, 0x8A, 0xFB, 0x94, 0xFB, 0x53, 0xFB, 0x53, 0xFB, 0xA0, 0xFB, 0xA0, 0xFB, 0xA5, + 0xFB, 0xA5, 0xFB, 0xAC, 0xFB, 0xAC, 0xFB, 0xAC, 0xFB, 0xBA, 0xFB, 0xC1, 0xFB, 0xCC, 0xFB, + 0xD7, 0xFB, 0xD7, 0xFB, 0xBA, 0xFB, 0xE3, 0xFB, 0xEE, 0xFB, 0xFC, 0xFB, 0x03, 0xFC, 0x0A, + 0xFC, 0x11, 0xFC, 0x18, 0xFC, 0x1F, 0xFC, 0x26, 0xFC, 0x2D, 0xFC, 0x34, 0xFC, 0x3B, 0xFC, + 0x42, 0xFC, 0x49, 0xFC, 0x50, 0xFC, 0x57, 0xFC, 0xF5, 0xFB, 0xF5, 0xFB, 0x5E, 0xFC, 0x65, + 0xFC, 0x6C, 0xFC, 0x73, 0xFC, 0x73, 0xFC, 0x7A, 0xFC, 0x81, 0xFC, 0x0A, 0xFC, 0x88, 0xFC, + 0x93, 0xFC, 0x9A, 0xFC, 0xF5, 0xFB, 0xA1, 0xFC, 0xAC, 0xFC, 0xB3, 0xFC, 0xBA, 0xFC, 0x5E, + 0xFC, 0x5E, 0xFC, 0xC1, 0xFC, 0xC8, 0xFC, 0xC8, 0xFC, 0xC8, 0xFC, 0xAC, 0xFC, 0xCF, 0xFC, + 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, + 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, + 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, + 0xFC]; + +pub(crate) const ORIGINAL_ROOM_BLOCKS: [u8; 576] = [ 0x00, 0x49, 0x00, 0x00, 0x46, 0x49, 0x0C, 0x1D, +0x48, 0x49, 0x13, 0x1D, 0x46, 0x49, 0x13, 0x0E, 0x48, 0x49, 0x0C, 0x11, 0x48, 0x49, 0x0C, 0x10, +0x4F, 0x49, 0x4A, 0x50, 0x0E, 0x49, 0x4A, 0x11, 0x46, 0x49, 0x12, 0x00, 0x00, 0x49, 0x00, 0x50, +0x00, 0x49, 0x00, 0x11, 0x48, 0x49, 0x0C, 0x00, 0x00, 0x00, 0x37, 0x36, 0x48, 0x49, 0x4C, 0x11, +0x5D, 0x2C, 0x0C, 0x44, 0x00, 0x00, 0x4E, 0x00, 0x0F, 0x00, 0x12, 0x10, 0x00, 0x00, 0x00, 0x4C, +0x00, 0x0D, 0x17, 0x00, 0x16, 0x0D, 0x17, 0x1B, 0x16, 0x0D, 0x17, 0x14, 0x15, 0x0D, 0x17, 0x15, +0x16, 0x0D, 0x18, 0x19, 0x16, 0x0D, 0x17, 0x19, 0x16, 0x0D, 0x00, 0x00, 0x16, 0x0D, 0x18, 0x1B, +0x0F, 0x49, 0x4A, 0x11, 0x4B, 0x2A, 0x5C, 0x15, 0x16, 0x49, 0x17, 0x1D, 0x00, 0x00, 0x00, 0x15, +0x16, 0x0D, 0x17, 0x10, 0x16, 0x49, 0x12, 0x00, 0x16, 0x49, 0x0C, 0x11, 0x00, 0x00, 0x12, 0x10, +0x16, 0x0D, 0x00, 0x11, 0x16, 0x49, 0x0C, 0x00, 0x16, 0x0D, 0x4C, 0x11, 0x0E, 0x0D, 0x4A, 0x11, +0x16, 0x1A, 0x17, 0x1B, 0x4F, 0x34, 0x4A, 0x50, 0x35, 0x4D, 0x65, 0x36, 0x4A, 0x34, 0x4E, 0x00, +0x0E, 0x34, 0x4A, 0x11, 0x51, 0x34, 0x5D, 0x59, 0x4B, 0x49, 0x4C, 0x11, 0x2D, 0x00, 0x00, 0x00, +0x5D, 0x00, 0x12, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x49, 0x2B, 0x2D, 0x46, 0x49, 0x1C, 0x52, +0x00, 0x49, 0x1C, 0x52, 0x5D, 0x49, 0x00, 0x52, 0x46, 0x49, 0x13, 0x52, 0x4B, 0x4D, 0x4A, 0x5A, +0x47, 0x49, 0x1C, 0x52, 0x4B, 0x4D, 0x39, 0x36, 0x1F, 0x2C, 0x2E, 0x52, 0x1F, 0x2C, 0x2E, 0x1D, +0x2F, 0x2C, 0x2E, 0x52, 0x2F, 0x2C, 0x2E, 0x31, 0x1F, 0x1E, 0x30, 0x52, 0x51, 0x49, 0x13, 0x00, +0x4F, 0x49, 0x13, 0x50, 0x4F, 0x4D, 0x4A, 0x50, 0x4B, 0x49, 0x4C, 0x2B, 0x1F, 0x20, 0x22, 0x53, +0x55, 0x3D, 0x42, 0x43, 0x1F, 0x1E, 0x23, 0x52, 0x1F, 0x1E, 0x39, 0x3A, 0x1F, 0x1E, 0x3A, 0x3E, +0x1F, 0x1E, 0x3C, 0x3D, 0x40, 0x1E, 0x27, 0x3F, 0x55, 0x1A, 0x42, 0x43, 0x1F, 0x1E, 0x2A, 0x52, +0x1F, 0x1E, 0x38, 0x52, 0x1F, 0x20, 0x28, 0x52, 0x1F, 0x20, 0x26, 0x52, 0x1F, 0x2C, 0x25, 0x52, +0x1F, 0x20, 0x27, 0x52, 0x1F, 0x1E, 0x29, 0x52, 0x1F, 0x2C, 0x3B, 0x52, 0x46, 0x49, 0x24, 0x52, +0x21, 0x41, 0x45, 0x33, 0x1F, 0x2C, 0x28, 0x31, 0x1F, 0x0D, 0x29, 0x52, 0x1F, 0x1E, 0x27, 0x52, +0x1F, 0x20, 0x27, 0x53, 0x48, 0x49, 0x13, 0x52, 0x0E, 0x1E, 0x4A, 0x50, 0x1F, 0x20, 0x26, 0x53, +0x15, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x2A, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, +0x5D, 0x49, 0x00, 0x52, 0x55, 0x49, 0x42, 0x43, 0x61, 0x62, 0x63, 0x50, 0x61, 0x62, 0x63, 0x50, +0x61, 0x62, 0x63, 0x50, 0x61, 0x62, 0x63, 0x50, 0x61, 0x62, 0x63, 0x50, 0x61, 0x62, 0x63, 0x50, +0x61, 0x56, 0x57, 0x50, 0x61, 0x62, 0x63, 0x50, 0x61, 0x62, 0x63, 0x50, 0x61, 0x56, 0x57, 0x50, +0x61, 0x56, 0x63, 0x50, 0x61, 0x56, 0x57, 0x50, 0x61, 0x56, 0x33, 0x50, 0x61, 0x56, 0x57, 0x50, +0x61, 0x62, 0x63, 0x50, 0x61, 0x62, 0x63, 0x50 ]; + +pub(crate) const ORIGINAL_OVERWORLD_BLOCKS: [u8; 272] = [ 0x07, 0x07, 0x07, 0x10, 0x10, 0x10, 0x10, +0x10, 0x07, 0x07, 0x07, 0x10, 0x10, 0x10, 0x10, 0x04, 0x06, 0x06, 0x00, 0x03, 0x03, 0x00, 0x0D, +0x0A, 0x06, 0x06, 0x01, 0x01, 0x01, 0x04, 0x05, 0x05, 0x06, 0x06, 0x06, 0x01, 0x01, 0x04, 0x05, +0x05, 0x06, 0x09, 0x0F, 0x00, 0x00, 0x0B, 0x0B, 0x05, 0x08, 0x08, 0x0A, 0x04, 0x04, 0x04, 0x04, +0x04, 0x08, 0x08, 0x0A, 0x04, 0x04, 0x04, 0x04, 0x04, 0x07, 0x07, 0x1A, 0x10, 0x10, 0x10, 0x10, +0x10, 0x07, 0x07, 0x1A, 0x10, 0x10, 0x10, 0x10, 0x04, 0x06, 0x06, 0x00, 0x03, 0x03, 0x00, 0x0D, +0x0A, 0x06, 0x06, 0x1C, 0x1C, 0x1C, 0x02, 0x05, 0x05, 0x06, 0x06, 0x06, 0x1C, 0x1C, 0x00, 0x05, +0x05, 0x06, 0x00, 0x0F, 0x00, 0x00, 0x23, 0x23, 0x05, 0x1F, 0x1F, 0x0A, 0x20, 0x20, 0x20, 0x20, +0x20, 0x1F, 0x1F, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x13, 0x13, 0x17, 0x14, 0x14, 0x14, 0x14, +0x14, 0x13, 0x13, 0x17, 0x14, 0x14, 0x14, 0x14, 0x16, 0x15, 0x15, 0x12, 0x13, 0x13, 0x18, 0x16, +0x16, 0x15, 0x15, 0x13, 0x26, 0x26, 0x13, 0x17, 0x17, 0x15, 0x15, 0x15, 0x26, 0x26, 0x13, 0x17, +0x17, 0x1B, 0x1D, 0x11, 0x13, 0x13, 0x18, 0x18, 0x17, 0x16, 0x16, 0x13, 0x13, 0x13, 0x19, 0x19, +0x19, 0x16, 0x16, 0x18, 0x13, 0x18, 0x19, 0x19, 0x19, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x05, 0x05, 0x06, 0x09, 0x09, 0x09, 0x09, +0x09, 0x05, 0x05, 0x06, 0x09, 0x09, 0x09, 0x09, 0x03 ]; diff --git a/enemize/src/lib.rs b/enemize/src/lib.rs index e5c016e..aae5429 100644 --- a/enemize/src/lib.rs +++ b/enemize/src/lib.rs @@ -11,6 +11,7 @@ pub mod asar; pub mod bosses; pub mod constants; pub mod option_flags; +pub mod randomize; pub mod rom; #[derive(Debug, Error)] diff --git a/enemize/src/main.rs b/enemize/src/main.rs index ea0f310..e74877b 100644 --- a/enemize/src/main.rs +++ b/enemize/src/main.rs @@ -3,11 +3,14 @@ use std::io::prelude::*; use std::path::{Path, PathBuf}; use std::time::Instant; -use anyhow::ensure; +use anyhow::{bail, ensure}; use clap::Parser; use enemize::{asar, rom::RomData}; +const ASAR_SYMBOLS: &'static str = "asar_symbols.txt"; +const ENEMIZER_BASE_PATCH: &'static str = "enemizer_base_patch.json"; + /// Randomizes enemy placements in The Legend of Zelda: A Link to the Past for the Super Nintendo /// Entertainment System #[derive(Debug, Parser)] @@ -17,7 +20,7 @@ struct Args { rom: PathBuf, /// seed number #[clap(short, long)] - seed: Option, + seed: Option, /// path to the enemizerOptions.json #[clap(short, long)] enemizer: PathBuf, @@ -34,7 +37,7 @@ fn main() -> anyhow::Result<()> { let stopwatch = Instant::now(); let options = serde_json::from_reader(File::open(args.enemizer)?)?; - let symbols = load_symbols(&args.rom)?; + let symbols = load_symbols()?; let mut raw_data = vec![]; let mut rom_file = File::open(args.rom)?; @@ -42,17 +45,24 @@ fn main() -> anyhow::Result<()> { raw_data.resize(2 * 1024 * 1024, 0); - let mut rom_data = RomData::new(symbols, raw_data); - ensure!(!rom_data.is_enemizer(), "It appears that the provided base ROM is already enemized. Please ensure you are using an original game ROM."); + let mut rom = RomData::new(symbols, raw_data); + ensure!(!rom.is_enemizer(), "It appears that the provided base ROM is already enemized. Please ensure you are using an original game ROM."); - let seed = args.seed.unwrap_or_else(|| rand::random()); + // Oh noes! The max seed number is twice as likely to show up as any other! + // (That is, 2 out of 2 billion instead of 1.) + let seed = args.seed.unwrap_or_else(|| rand::random()).saturating_abs(); - //let randomized_rom = randomize_rom(seed, raw_data, options); + rom.randomize(Path::new(ENEMIZER_BASE_PATCH), options, seed)?; + + let mut out_file = File::create(&args.output)?; if args.binary { - + out_file.write_all(rom.get_rom_bytes())?; + out_file.flush()?; + println!("Generated SFC file {}", args.output.to_string_lossy()); } else { - + let patches = rom.generate_patch(); + serde_json::to_writer(out_file, &patches)?; } println!("Seed generated in: {}ms", stopwatch.elapsed().as_millis()); @@ -60,21 +70,10 @@ fn main() -> anyhow::Result<()> { Ok(()) } -fn load_symbols(rom: &Path) -> anyhow::Result { - let sym_path = rom.with_extension("sym"); - - if let Err(_) = std::fs::metadata(&sym_path) { - let status = std::process::Command::new("asar") - .arg("--no-title-check") - .arg("--fix-checksum=off") - .arg("--symbols=wla") - .arg("-Iassembly/src") - .arg("assembly/src/main.asm") - .arg(rom) - .status()?; - - ensure!(status.success(), "could not generate symbols"); +fn load_symbols() -> anyhow::Result { + if let Err(_) = std::fs::metadata(ASAR_SYMBOLS) { + bail!("Could not find symbols at {}. Did you run prepare.sh?", ASAR_SYMBOLS); } - asar::load_symbols(sym_path.as_path()) + asar::load_symbols(Path::new(ASAR_SYMBOLS)) } diff --git a/enemize/src/randomize.rs b/enemize/src/randomize.rs new file mode 100644 index 0000000..a1abdb9 --- /dev/null +++ b/enemize/src/randomize.rs @@ -0,0 +1,29 @@ +use std::path::Path; + +use anyhow::ensure; + +use crate::PatchSet; +use crate::option_flags::OptionFlags; +use crate::rom::RomData; + +impl RomData { + pub fn randomize(&mut self, base_patch: &Path, option_flags: OptionFlags, seed: i32) -> anyhow::Result<()> { + ensure!(self.is_randomizer(), "Enemizer only supports randomizer ROMs for input."); + ensure!(!self.is_race(), "Enemizer does not support race roms."); + + if self.is_enemizer() { + // Reuse seed in ROM, not the one given. + self.reset_enemizer(); + } else { + self.set_enemizer_seed(seed); + } + + self.expand_rom(); + self.set_info_flags(option_flags)?; + + let patches = PatchSet::load(base_patch)?; + patches.patch_rom(self); + + Ok(()) + } +} diff --git a/enemize/src/rom.rs b/enemize/src/rom.rs index 95b1fb6..6b264ed 100644 --- a/enemize/src/rom.rs +++ b/enemize/src/rom.rs @@ -31,9 +31,9 @@ pub const RANDOMIZER_MODE_FLAG: usize = 0x180032; pub struct RomData { asar_symbols: Symbols, spoiler: String, - rom_data: Vec, + pub(crate) rom_data: Vec, patch_data: BTreeMap, - seed: i32, + pub(crate) seed: i32, } impl RomData { @@ -41,6 +41,25 @@ impl RomData { self.spoiler.as_str() } + pub fn reset_enemizer(&mut self) -> i32 { + use crate::constants::*; + + let seed = self.get_enemizer_seed(); + + self.rom_data[ROOM_HEADER_BANK_LOCATION] = 0x04; + + let dungeon_header_range = 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); + self.rom_data[room_range].copy_from_slice(&ORIGINAL_ROOM_BLOCKS); + + let ow_gfx_range = OVERWORLD_AREA_GRAPHICS_BLOCK..(OVERWORLD_AREA_GRAPHICS_BLOCK + 272); + self.rom_data[ow_gfx_range].copy_from_slice(&ORIGINAL_OVERWORLD_BLOCKS); + + seed + } + pub fn generate_patch(&self) -> Vec { let mut patches = vec![]; @@ -54,6 +73,10 @@ impl RomData { patches } + pub fn get_rom_bytes(&self) -> &[u8] { + &self.rom_data + } + fn set_rom_bytes(&mut self, bytes: &[u8], range: Range) { self.rom_data.splice(range, bytes.into_iter().map(|&b| b)); } @@ -92,6 +115,12 @@ impl RomData { &self.rom_data[0x7FC0..0x7FCE] == 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") + } + fn assert_rom_length(&self) { assert!(self.rom_data.len() >= ENEMIZER_FILE_LENGTH, "You need to expand the rom before you can use Enemizer features."); } @@ -127,6 +156,15 @@ impl RomData { self.set_patch_bytes(seed_start..seed_end); } + 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.set_enemizer_version("6.0.32".to_owned()); + } + pub fn get_enemizer_version(&self) -> anyhow::Result<&str> { if self.is_enemizer() { let version_start = self.asar_symbols["enemizer_info_table"] + ENEMIZER_INFO_VERSION_OFFSET; @@ -208,7 +246,7 @@ impl RomData { pub fn move_room_headers(&mut self) { let table_base = DUNGEON_HEADER_POINTER_TABLE; - let header_base = self.asar_symbols["rom_header_table"]; + let header_base = self.asar_symbols["room_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"]]; From 27d5a4d6c00b68b6c0ccd736d84b82da8f6e58fb Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Sat, 4 Jun 2022 09:41:29 -0400 Subject: [PATCH 14/29] Format! --- base_patch_generator/src/main.rs | 2 +- enemize/src/asar.rs | 45 ++++--- enemize/src/bosses/mod.rs | 3 +- enemize/src/constants.rs | 198 +++++++++++++++---------------- enemize/src/main.rs | 5 +- enemize/src/option_flags.rs | 122 +++++++++++++++---- enemize/src/randomize.rs | 14 ++- enemize/src/rom.rs | 90 ++++++++------ 8 files changed, 291 insertions(+), 188 deletions(-) diff --git a/base_patch_generator/src/main.rs b/base_patch_generator/src/main.rs index 4abf411..e089a9a 100644 --- a/base_patch_generator/src/main.rs +++ b/base_patch_generator/src/main.rs @@ -3,9 +3,9 @@ use std::fs::File; use std::io::Read; use std::path::Path; -use enemize::PatchSet; use enemize::asar; use enemize::rom::RomData; +use enemize::PatchSet; fn main() -> Result<(), anyhow::Error> { let mut args = env::args(); diff --git a/enemize/src/asar.rs b/enemize/src/asar.rs index f405e9d..b4afba8 100644 --- a/enemize/src/asar.rs +++ b/enemize/src/asar.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; use std::fs::File; -use std::io::{BufReader, BufRead}; +use std::io::{BufRead, BufReader}; use std::path::Path; pub type Symbols = HashMap; @@ -9,28 +9,35 @@ pub fn load_symbols(filename: &Path) -> anyhow::Result { let file = File::open(filename)?; let reader = BufReader::new(file); - 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())), + 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); - } + _ => 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()?; + // 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_to_pc_address(snes_address); + let pc_address = snes_to_pc_address(snes_address); - Some((symbol, pc_address)) - })).collect(); + Some((symbol, pc_address)) + }) + }) + .collect(); Ok(symbols) } diff --git a/enemize/src/bosses/mod.rs b/enemize/src/bosses/mod.rs index a5f02c7..ea7692b 100644 --- a/enemize/src/bosses/mod.rs +++ b/enemize/src/bosses/mod.rs @@ -67,8 +67,7 @@ impl TryFrom for BossType { 11 => Ok(Self::Agahnim2), 12 => Ok(Self::Ganon), 255 => Ok(Self::NoBoss), - _ => Err(InvalidEnumError(PhantomData)) + _ => Err(InvalidEnumError(PhantomData)), } } } - diff --git a/enemize/src/constants.rs b/enemize/src/constants.rs index 4cd503c..3becd7a 100644 --- a/enemize/src/constants.rs +++ b/enemize/src/constants.rs @@ -11,104 +11,104 @@ pub const RANDOM_SPRITE_GRAPHICS: usize = 0x300000; pub const ENEMIZER_FILE_LENGTH: usize = 0x200000; pub const HIDDEN_ENEMY_CHANCE_POOL: usize = 0xD7BBB; -pub(crate) const ORIGINAL_ROOM_POINTERS: [u8; 640] = [ 0x62, 0xF4, 0x6C, 0xF4, 0x7A, 0xF4, 0xDD, 0xF5, 0x85, - 0xF4, 0x90, 0xF4, 0x90, 0xF4, 0x97, 0xF4, 0xA2, 0xF4, 0xA9, 0xF4, 0xB5, 0xF4, 0xC0, 0xF4, - 0xCB, 0xF4, 0xD8, 0xF4, 0xDF, 0xF4, 0xEA, 0xF4, 0xEA, 0xF4, 0xF1, 0xF4, 0xFC, 0xF4, 0x03, - 0xF5, 0x11, 0xF5, 0x18, 0xF5, 0x23, 0xF5, 0x2E, 0xF5, 0x73, 0xFC, 0x3A, 0xF5, 0x41, 0xF5, - 0x4D, 0xF5, 0x58, 0xF5, 0x63, 0xF5, 0x6E, 0xF5, 0x79, 0xF5, 0x84, 0xF5, 0x8B, 0xF5, 0x8B, - 0xF5, 0x03, 0xF5, 0x92, 0xF5, 0x99, 0xF5, 0x99, 0xF5, 0xA6, 0xF5, 0xB2, 0xF5, 0xBD, 0xF5, - 0xC4, 0xF5, 0xCB, 0xF5, 0x73, 0xFC, 0xD6, 0xF5, 0xD6, 0xF5, 0xDD, 0xF5, 0xE4, 0xF5, 0xEF, - 0xF5, 0xFB, 0xF5, 0x06, 0xF6, 0x0D, 0xF6, 0x18, 0xF6, 0x1F, 0xF6, 0x18, 0xF6, 0x26, 0xF6, - 0x31, 0xF6, 0x3B, 0xF6, 0x46, 0xF6, 0x51, 0xF6, 0x58, 0xF6, 0x63, 0xF6, 0x6E, 0xF6, 0x7A, - 0xF6, 0x86, 0xF6, 0x91, 0xF6, 0x9D, 0xF6, 0xA4, 0xF6, 0xAB, 0xF6, 0xB6, 0xF6, 0xBD, 0xF6, - 0xBD, 0xF6, 0xBD, 0xF6, 0xC4, 0xF6, 0xD0, 0xF6, 0xDA, 0xF6, 0xE5, 0xF6, 0xF0, 0xF6, 0xFB, - 0xF6, 0x05, 0xF7, 0x13, 0xF7, 0x1E, 0xF7, 0x2C, 0xF7, 0x37, 0xF7, 0x42, 0xF7, 0x49, 0xF7, - 0x50, 0xF7, 0x57, 0xF7, 0x5E, 0xF7, 0x65, 0xF7, 0x6C, 0xF7, 0x73, 0xF7, 0x7E, 0xF7, 0x89, - 0xF7, 0x94, 0xF7, 0xA0, 0xF7, 0xA7, 0xF7, 0xA0, 0xF7, 0xB2, 0xF7, 0xBD, 0xF7, 0xC8, 0xF7, - 0xD2, 0xF7, 0xDD, 0xF7, 0xE4, 0xF7, 0xEB, 0xF7, 0xEB, 0xF7, 0xF7, 0xF7, 0x02, 0xF8, 0x0D, - 0xF8, 0x14, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x2B, 0xF8, 0x36, 0xF8, 0x41, 0xF8, 0x48, 0xF8, - 0x4F, 0xF8, 0x56, 0xF8, 0x63, 0xF8, 0x70, 0xF8, 0x70, 0xF8, 0x70, 0xF8, 0x70, 0xF8, 0x7A, - 0xF8, 0x81, 0xF8, 0x8B, 0xF8, 0x96, 0xF8, 0xA1, 0xF8, 0xAC, 0xF8, 0xAC, 0xF8, 0xB3, 0xF8, - 0xBA, 0xF8, 0xC1, 0xF8, 0xC8, 0xF8, 0xC8, 0xF8, 0xD4, 0xF8, 0xD4, 0xF8, 0xDE, 0xF8, 0xDE, - 0xF8, 0xE5, 0xF8, 0xF2, 0xF8, 0xF9, 0xF8, 0x04, 0xF9, 0x04, 0xF9, 0x0B, 0xF9, 0x16, 0xF9, - 0x1D, 0xF9, 0x28, 0xF9, 0x28, 0xF9, 0x2F, 0xF9, 0x3A, 0xF9, 0x45, 0xF9, 0x50, 0xF9, 0x5B, - 0xF9, 0x5B, 0xF9, 0x65, 0xF9, 0x6C, 0xF9, 0x76, 0xF9, 0x81, 0xF9, 0x88, 0xF9, 0x93, 0xF9, - 0x9A, 0xF9, 0x93, 0xF9, 0xA5, 0xF9, 0xAC, 0xF9, 0xB7, 0xF9, 0xC2, 0xF9, 0xCC, 0xF9, 0xD3, - 0xF9, 0xDD, 0xF9, 0xE4, 0xF9, 0xEF, 0xF9, 0xF6, 0xF9, 0xF6, 0xF9, 0x01, 0xFA, 0x08, 0xFA, - 0x14, 0xFA, 0x1E, 0xFA, 0x25, 0xFA, 0x2C, 0xFA, 0x37, 0xFA, 0x42, 0xFA, 0x0A, 0xF5, 0x4D, - 0xFA, 0x54, 0xFA, 0x5B, 0xFA, 0x62, 0xFA, 0x69, 0xFA, 0x74, 0xFA, 0x74, 0xFA, 0x7F, 0xFA, - 0x86, 0xFA, 0x92, 0xFA, 0x99, 0xFA, 0xA0, 0xFA, 0xA7, 0xFA, 0xB2, 0xFA, 0x0A, 0xF5, 0xB9, - 0xFA, 0xC0, 0xFA, 0xC7, 0xFA, 0xCE, 0xFA, 0xCE, 0xFA, 0xCE, 0xFA, 0xD5, 0xFA, 0xD5, 0xFA, - 0xDF, 0xFA, 0xDF, 0xFA, 0xEB, 0xFA, 0xF6, 0xFA, 0x01, 0xFB, 0x01, 0xFB, 0xB2, 0xFA, 0x0A, - 0xF5, 0x01, 0xFB, 0x01, 0xFB, 0x08, 0xFB, 0x0F, 0xFB, 0xCE, 0xFA, 0xCE, 0xFA, 0x1A, 0xFB, - 0x1A, 0xFB, 0x21, 0xFB, 0x2C, 0xFB, 0x37, 0xFB, 0x3E, 0xFB, 0x45, 0xFB, 0x4C, 0xFB, 0x4C, - 0xFB, 0x53, 0xFB, 0x53, 0xFB, 0x5A, 0xFB, 0x68, 0xFB, 0x68, 0xFB, 0x73, 0xFB, 0x7E, 0xFB, - 0x7E, 0xFB, 0x8A, 0xFB, 0x94, 0xFB, 0x53, 0xFB, 0x53, 0xFB, 0xA0, 0xFB, 0xA0, 0xFB, 0xA5, - 0xFB, 0xA5, 0xFB, 0xAC, 0xFB, 0xAC, 0xFB, 0xAC, 0xFB, 0xBA, 0xFB, 0xC1, 0xFB, 0xCC, 0xFB, - 0xD7, 0xFB, 0xD7, 0xFB, 0xBA, 0xFB, 0xE3, 0xFB, 0xEE, 0xFB, 0xFC, 0xFB, 0x03, 0xFC, 0x0A, - 0xFC, 0x11, 0xFC, 0x18, 0xFC, 0x1F, 0xFC, 0x26, 0xFC, 0x2D, 0xFC, 0x34, 0xFC, 0x3B, 0xFC, - 0x42, 0xFC, 0x49, 0xFC, 0x50, 0xFC, 0x57, 0xFC, 0xF5, 0xFB, 0xF5, 0xFB, 0x5E, 0xFC, 0x65, - 0xFC, 0x6C, 0xFC, 0x73, 0xFC, 0x73, 0xFC, 0x7A, 0xFC, 0x81, 0xFC, 0x0A, 0xFC, 0x88, 0xFC, - 0x93, 0xFC, 0x9A, 0xFC, 0xF5, 0xFB, 0xA1, 0xFC, 0xAC, 0xFC, 0xB3, 0xFC, 0xBA, 0xFC, 0x5E, - 0xFC, 0x5E, 0xFC, 0xC1, 0xFC, 0xC8, 0xFC, 0xC8, 0xFC, 0xC8, 0xFC, 0xAC, 0xFC, 0xCF, 0xFC, - 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, - 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, - 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, - 0xFC]; +pub(crate) const ORIGINAL_ROOM_POINTERS: [u8; 640] = [ + 0x62, 0xF4, 0x6C, 0xF4, 0x7A, 0xF4, 0xDD, 0xF5, 0x85, 0xF4, 0x90, 0xF4, 0x90, 0xF4, 0x97, 0xF4, + 0xA2, 0xF4, 0xA9, 0xF4, 0xB5, 0xF4, 0xC0, 0xF4, 0xCB, 0xF4, 0xD8, 0xF4, 0xDF, 0xF4, 0xEA, 0xF4, + 0xEA, 0xF4, 0xF1, 0xF4, 0xFC, 0xF4, 0x03, 0xF5, 0x11, 0xF5, 0x18, 0xF5, 0x23, 0xF5, 0x2E, 0xF5, + 0x73, 0xFC, 0x3A, 0xF5, 0x41, 0xF5, 0x4D, 0xF5, 0x58, 0xF5, 0x63, 0xF5, 0x6E, 0xF5, 0x79, 0xF5, + 0x84, 0xF5, 0x8B, 0xF5, 0x8B, 0xF5, 0x03, 0xF5, 0x92, 0xF5, 0x99, 0xF5, 0x99, 0xF5, 0xA6, 0xF5, + 0xB2, 0xF5, 0xBD, 0xF5, 0xC4, 0xF5, 0xCB, 0xF5, 0x73, 0xFC, 0xD6, 0xF5, 0xD6, 0xF5, 0xDD, 0xF5, + 0xE4, 0xF5, 0xEF, 0xF5, 0xFB, 0xF5, 0x06, 0xF6, 0x0D, 0xF6, 0x18, 0xF6, 0x1F, 0xF6, 0x18, 0xF6, + 0x26, 0xF6, 0x31, 0xF6, 0x3B, 0xF6, 0x46, 0xF6, 0x51, 0xF6, 0x58, 0xF6, 0x63, 0xF6, 0x6E, 0xF6, + 0x7A, 0xF6, 0x86, 0xF6, 0x91, 0xF6, 0x9D, 0xF6, 0xA4, 0xF6, 0xAB, 0xF6, 0xB6, 0xF6, 0xBD, 0xF6, + 0xBD, 0xF6, 0xBD, 0xF6, 0xC4, 0xF6, 0xD0, 0xF6, 0xDA, 0xF6, 0xE5, 0xF6, 0xF0, 0xF6, 0xFB, 0xF6, + 0x05, 0xF7, 0x13, 0xF7, 0x1E, 0xF7, 0x2C, 0xF7, 0x37, 0xF7, 0x42, 0xF7, 0x49, 0xF7, 0x50, 0xF7, + 0x57, 0xF7, 0x5E, 0xF7, 0x65, 0xF7, 0x6C, 0xF7, 0x73, 0xF7, 0x7E, 0xF7, 0x89, 0xF7, 0x94, 0xF7, + 0xA0, 0xF7, 0xA7, 0xF7, 0xA0, 0xF7, 0xB2, 0xF7, 0xBD, 0xF7, 0xC8, 0xF7, 0xD2, 0xF7, 0xDD, 0xF7, + 0xE4, 0xF7, 0xEB, 0xF7, 0xEB, 0xF7, 0xF7, 0xF7, 0x02, 0xF8, 0x0D, 0xF8, 0x14, 0xF8, 0x1F, 0xF8, + 0x1F, 0xF8, 0x2B, 0xF8, 0x36, 0xF8, 0x41, 0xF8, 0x48, 0xF8, 0x4F, 0xF8, 0x56, 0xF8, 0x63, 0xF8, + 0x70, 0xF8, 0x70, 0xF8, 0x70, 0xF8, 0x70, 0xF8, 0x7A, 0xF8, 0x81, 0xF8, 0x8B, 0xF8, 0x96, 0xF8, + 0xA1, 0xF8, 0xAC, 0xF8, 0xAC, 0xF8, 0xB3, 0xF8, 0xBA, 0xF8, 0xC1, 0xF8, 0xC8, 0xF8, 0xC8, 0xF8, + 0xD4, 0xF8, 0xD4, 0xF8, 0xDE, 0xF8, 0xDE, 0xF8, 0xE5, 0xF8, 0xF2, 0xF8, 0xF9, 0xF8, 0x04, 0xF9, + 0x04, 0xF9, 0x0B, 0xF9, 0x16, 0xF9, 0x1D, 0xF9, 0x28, 0xF9, 0x28, 0xF9, 0x2F, 0xF9, 0x3A, 0xF9, + 0x45, 0xF9, 0x50, 0xF9, 0x5B, 0xF9, 0x5B, 0xF9, 0x65, 0xF9, 0x6C, 0xF9, 0x76, 0xF9, 0x81, 0xF9, + 0x88, 0xF9, 0x93, 0xF9, 0x9A, 0xF9, 0x93, 0xF9, 0xA5, 0xF9, 0xAC, 0xF9, 0xB7, 0xF9, 0xC2, 0xF9, + 0xCC, 0xF9, 0xD3, 0xF9, 0xDD, 0xF9, 0xE4, 0xF9, 0xEF, 0xF9, 0xF6, 0xF9, 0xF6, 0xF9, 0x01, 0xFA, + 0x08, 0xFA, 0x14, 0xFA, 0x1E, 0xFA, 0x25, 0xFA, 0x2C, 0xFA, 0x37, 0xFA, 0x42, 0xFA, 0x0A, 0xF5, + 0x4D, 0xFA, 0x54, 0xFA, 0x5B, 0xFA, 0x62, 0xFA, 0x69, 0xFA, 0x74, 0xFA, 0x74, 0xFA, 0x7F, 0xFA, + 0x86, 0xFA, 0x92, 0xFA, 0x99, 0xFA, 0xA0, 0xFA, 0xA7, 0xFA, 0xB2, 0xFA, 0x0A, 0xF5, 0xB9, 0xFA, + 0xC0, 0xFA, 0xC7, 0xFA, 0xCE, 0xFA, 0xCE, 0xFA, 0xCE, 0xFA, 0xD5, 0xFA, 0xD5, 0xFA, 0xDF, 0xFA, + 0xDF, 0xFA, 0xEB, 0xFA, 0xF6, 0xFA, 0x01, 0xFB, 0x01, 0xFB, 0xB2, 0xFA, 0x0A, 0xF5, 0x01, 0xFB, + 0x01, 0xFB, 0x08, 0xFB, 0x0F, 0xFB, 0xCE, 0xFA, 0xCE, 0xFA, 0x1A, 0xFB, 0x1A, 0xFB, 0x21, 0xFB, + 0x2C, 0xFB, 0x37, 0xFB, 0x3E, 0xFB, 0x45, 0xFB, 0x4C, 0xFB, 0x4C, 0xFB, 0x53, 0xFB, 0x53, 0xFB, + 0x5A, 0xFB, 0x68, 0xFB, 0x68, 0xFB, 0x73, 0xFB, 0x7E, 0xFB, 0x7E, 0xFB, 0x8A, 0xFB, 0x94, 0xFB, + 0x53, 0xFB, 0x53, 0xFB, 0xA0, 0xFB, 0xA0, 0xFB, 0xA5, 0xFB, 0xA5, 0xFB, 0xAC, 0xFB, 0xAC, 0xFB, + 0xAC, 0xFB, 0xBA, 0xFB, 0xC1, 0xFB, 0xCC, 0xFB, 0xD7, 0xFB, 0xD7, 0xFB, 0xBA, 0xFB, 0xE3, 0xFB, + 0xEE, 0xFB, 0xFC, 0xFB, 0x03, 0xFC, 0x0A, 0xFC, 0x11, 0xFC, 0x18, 0xFC, 0x1F, 0xFC, 0x26, 0xFC, + 0x2D, 0xFC, 0x34, 0xFC, 0x3B, 0xFC, 0x42, 0xFC, 0x49, 0xFC, 0x50, 0xFC, 0x57, 0xFC, 0xF5, 0xFB, + 0xF5, 0xFB, 0x5E, 0xFC, 0x65, 0xFC, 0x6C, 0xFC, 0x73, 0xFC, 0x73, 0xFC, 0x7A, 0xFC, 0x81, 0xFC, + 0x0A, 0xFC, 0x88, 0xFC, 0x93, 0xFC, 0x9A, 0xFC, 0xF5, 0xFB, 0xA1, 0xFC, 0xAC, 0xFC, 0xB3, 0xFC, + 0xBA, 0xFC, 0x5E, 0xFC, 0x5E, 0xFC, 0xC1, 0xFC, 0xC8, 0xFC, 0xC8, 0xFC, 0xC8, 0xFC, 0xAC, 0xFC, + 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, + 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, + 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, 0xCF, 0xFC, +]; -pub(crate) const ORIGINAL_ROOM_BLOCKS: [u8; 576] = [ 0x00, 0x49, 0x00, 0x00, 0x46, 0x49, 0x0C, 0x1D, -0x48, 0x49, 0x13, 0x1D, 0x46, 0x49, 0x13, 0x0E, 0x48, 0x49, 0x0C, 0x11, 0x48, 0x49, 0x0C, 0x10, -0x4F, 0x49, 0x4A, 0x50, 0x0E, 0x49, 0x4A, 0x11, 0x46, 0x49, 0x12, 0x00, 0x00, 0x49, 0x00, 0x50, -0x00, 0x49, 0x00, 0x11, 0x48, 0x49, 0x0C, 0x00, 0x00, 0x00, 0x37, 0x36, 0x48, 0x49, 0x4C, 0x11, -0x5D, 0x2C, 0x0C, 0x44, 0x00, 0x00, 0x4E, 0x00, 0x0F, 0x00, 0x12, 0x10, 0x00, 0x00, 0x00, 0x4C, -0x00, 0x0D, 0x17, 0x00, 0x16, 0x0D, 0x17, 0x1B, 0x16, 0x0D, 0x17, 0x14, 0x15, 0x0D, 0x17, 0x15, -0x16, 0x0D, 0x18, 0x19, 0x16, 0x0D, 0x17, 0x19, 0x16, 0x0D, 0x00, 0x00, 0x16, 0x0D, 0x18, 0x1B, -0x0F, 0x49, 0x4A, 0x11, 0x4B, 0x2A, 0x5C, 0x15, 0x16, 0x49, 0x17, 0x1D, 0x00, 0x00, 0x00, 0x15, -0x16, 0x0D, 0x17, 0x10, 0x16, 0x49, 0x12, 0x00, 0x16, 0x49, 0x0C, 0x11, 0x00, 0x00, 0x12, 0x10, -0x16, 0x0D, 0x00, 0x11, 0x16, 0x49, 0x0C, 0x00, 0x16, 0x0D, 0x4C, 0x11, 0x0E, 0x0D, 0x4A, 0x11, -0x16, 0x1A, 0x17, 0x1B, 0x4F, 0x34, 0x4A, 0x50, 0x35, 0x4D, 0x65, 0x36, 0x4A, 0x34, 0x4E, 0x00, -0x0E, 0x34, 0x4A, 0x11, 0x51, 0x34, 0x5D, 0x59, 0x4B, 0x49, 0x4C, 0x11, 0x2D, 0x00, 0x00, 0x00, -0x5D, 0x00, 0x12, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x49, 0x2B, 0x2D, 0x46, 0x49, 0x1C, 0x52, -0x00, 0x49, 0x1C, 0x52, 0x5D, 0x49, 0x00, 0x52, 0x46, 0x49, 0x13, 0x52, 0x4B, 0x4D, 0x4A, 0x5A, -0x47, 0x49, 0x1C, 0x52, 0x4B, 0x4D, 0x39, 0x36, 0x1F, 0x2C, 0x2E, 0x52, 0x1F, 0x2C, 0x2E, 0x1D, -0x2F, 0x2C, 0x2E, 0x52, 0x2F, 0x2C, 0x2E, 0x31, 0x1F, 0x1E, 0x30, 0x52, 0x51, 0x49, 0x13, 0x00, -0x4F, 0x49, 0x13, 0x50, 0x4F, 0x4D, 0x4A, 0x50, 0x4B, 0x49, 0x4C, 0x2B, 0x1F, 0x20, 0x22, 0x53, -0x55, 0x3D, 0x42, 0x43, 0x1F, 0x1E, 0x23, 0x52, 0x1F, 0x1E, 0x39, 0x3A, 0x1F, 0x1E, 0x3A, 0x3E, -0x1F, 0x1E, 0x3C, 0x3D, 0x40, 0x1E, 0x27, 0x3F, 0x55, 0x1A, 0x42, 0x43, 0x1F, 0x1E, 0x2A, 0x52, -0x1F, 0x1E, 0x38, 0x52, 0x1F, 0x20, 0x28, 0x52, 0x1F, 0x20, 0x26, 0x52, 0x1F, 0x2C, 0x25, 0x52, -0x1F, 0x20, 0x27, 0x52, 0x1F, 0x1E, 0x29, 0x52, 0x1F, 0x2C, 0x3B, 0x52, 0x46, 0x49, 0x24, 0x52, -0x21, 0x41, 0x45, 0x33, 0x1F, 0x2C, 0x28, 0x31, 0x1F, 0x0D, 0x29, 0x52, 0x1F, 0x1E, 0x27, 0x52, -0x1F, 0x20, 0x27, 0x53, 0x48, 0x49, 0x13, 0x52, 0x0E, 0x1E, 0x4A, 0x50, 0x1F, 0x20, 0x26, 0x53, -0x15, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x2A, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, -0x5D, 0x49, 0x00, 0x52, 0x55, 0x49, 0x42, 0x43, 0x61, 0x62, 0x63, 0x50, 0x61, 0x62, 0x63, 0x50, -0x61, 0x62, 0x63, 0x50, 0x61, 0x62, 0x63, 0x50, 0x61, 0x62, 0x63, 0x50, 0x61, 0x62, 0x63, 0x50, -0x61, 0x56, 0x57, 0x50, 0x61, 0x62, 0x63, 0x50, 0x61, 0x62, 0x63, 0x50, 0x61, 0x56, 0x57, 0x50, -0x61, 0x56, 0x63, 0x50, 0x61, 0x56, 0x57, 0x50, 0x61, 0x56, 0x33, 0x50, 0x61, 0x56, 0x57, 0x50, -0x61, 0x62, 0x63, 0x50, 0x61, 0x62, 0x63, 0x50 ]; +pub(crate) const ORIGINAL_ROOM_BLOCKS: [u8; 576] = [ + 0x00, 0x49, 0x00, 0x00, 0x46, 0x49, 0x0C, 0x1D, 0x48, 0x49, 0x13, 0x1D, 0x46, 0x49, 0x13, 0x0E, + 0x48, 0x49, 0x0C, 0x11, 0x48, 0x49, 0x0C, 0x10, 0x4F, 0x49, 0x4A, 0x50, 0x0E, 0x49, 0x4A, 0x11, + 0x46, 0x49, 0x12, 0x00, 0x00, 0x49, 0x00, 0x50, 0x00, 0x49, 0x00, 0x11, 0x48, 0x49, 0x0C, 0x00, + 0x00, 0x00, 0x37, 0x36, 0x48, 0x49, 0x4C, 0x11, 0x5D, 0x2C, 0x0C, 0x44, 0x00, 0x00, 0x4E, 0x00, + 0x0F, 0x00, 0x12, 0x10, 0x00, 0x00, 0x00, 0x4C, 0x00, 0x0D, 0x17, 0x00, 0x16, 0x0D, 0x17, 0x1B, + 0x16, 0x0D, 0x17, 0x14, 0x15, 0x0D, 0x17, 0x15, 0x16, 0x0D, 0x18, 0x19, 0x16, 0x0D, 0x17, 0x19, + 0x16, 0x0D, 0x00, 0x00, 0x16, 0x0D, 0x18, 0x1B, 0x0F, 0x49, 0x4A, 0x11, 0x4B, 0x2A, 0x5C, 0x15, + 0x16, 0x49, 0x17, 0x1D, 0x00, 0x00, 0x00, 0x15, 0x16, 0x0D, 0x17, 0x10, 0x16, 0x49, 0x12, 0x00, + 0x16, 0x49, 0x0C, 0x11, 0x00, 0x00, 0x12, 0x10, 0x16, 0x0D, 0x00, 0x11, 0x16, 0x49, 0x0C, 0x00, + 0x16, 0x0D, 0x4C, 0x11, 0x0E, 0x0D, 0x4A, 0x11, 0x16, 0x1A, 0x17, 0x1B, 0x4F, 0x34, 0x4A, 0x50, + 0x35, 0x4D, 0x65, 0x36, 0x4A, 0x34, 0x4E, 0x00, 0x0E, 0x34, 0x4A, 0x11, 0x51, 0x34, 0x5D, 0x59, + 0x4B, 0x49, 0x4C, 0x11, 0x2D, 0x00, 0x00, 0x00, 0x5D, 0x00, 0x12, 0x59, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x47, 0x49, 0x2B, 0x2D, 0x46, 0x49, 0x1C, 0x52, 0x00, 0x49, 0x1C, 0x52, 0x5D, 0x49, 0x00, 0x52, + 0x46, 0x49, 0x13, 0x52, 0x4B, 0x4D, 0x4A, 0x5A, 0x47, 0x49, 0x1C, 0x52, 0x4B, 0x4D, 0x39, 0x36, + 0x1F, 0x2C, 0x2E, 0x52, 0x1F, 0x2C, 0x2E, 0x1D, 0x2F, 0x2C, 0x2E, 0x52, 0x2F, 0x2C, 0x2E, 0x31, + 0x1F, 0x1E, 0x30, 0x52, 0x51, 0x49, 0x13, 0x00, 0x4F, 0x49, 0x13, 0x50, 0x4F, 0x4D, 0x4A, 0x50, + 0x4B, 0x49, 0x4C, 0x2B, 0x1F, 0x20, 0x22, 0x53, 0x55, 0x3D, 0x42, 0x43, 0x1F, 0x1E, 0x23, 0x52, + 0x1F, 0x1E, 0x39, 0x3A, 0x1F, 0x1E, 0x3A, 0x3E, 0x1F, 0x1E, 0x3C, 0x3D, 0x40, 0x1E, 0x27, 0x3F, + 0x55, 0x1A, 0x42, 0x43, 0x1F, 0x1E, 0x2A, 0x52, 0x1F, 0x1E, 0x38, 0x52, 0x1F, 0x20, 0x28, 0x52, + 0x1F, 0x20, 0x26, 0x52, 0x1F, 0x2C, 0x25, 0x52, 0x1F, 0x20, 0x27, 0x52, 0x1F, 0x1E, 0x29, 0x52, + 0x1F, 0x2C, 0x3B, 0x52, 0x46, 0x49, 0x24, 0x52, 0x21, 0x41, 0x45, 0x33, 0x1F, 0x2C, 0x28, 0x31, + 0x1F, 0x0D, 0x29, 0x52, 0x1F, 0x1E, 0x27, 0x52, 0x1F, 0x20, 0x27, 0x53, 0x48, 0x49, 0x13, 0x52, + 0x0E, 0x1E, 0x4A, 0x50, 0x1F, 0x20, 0x26, 0x53, 0x15, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x2A, 0x52, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x5D, 0x49, 0x00, 0x52, 0x55, 0x49, 0x42, 0x43, + 0x61, 0x62, 0x63, 0x50, 0x61, 0x62, 0x63, 0x50, 0x61, 0x62, 0x63, 0x50, 0x61, 0x62, 0x63, 0x50, + 0x61, 0x62, 0x63, 0x50, 0x61, 0x62, 0x63, 0x50, 0x61, 0x56, 0x57, 0x50, 0x61, 0x62, 0x63, 0x50, + 0x61, 0x62, 0x63, 0x50, 0x61, 0x56, 0x57, 0x50, 0x61, 0x56, 0x63, 0x50, 0x61, 0x56, 0x57, 0x50, + 0x61, 0x56, 0x33, 0x50, 0x61, 0x56, 0x57, 0x50, 0x61, 0x62, 0x63, 0x50, 0x61, 0x62, 0x63, 0x50, +]; -pub(crate) const ORIGINAL_OVERWORLD_BLOCKS: [u8; 272] = [ 0x07, 0x07, 0x07, 0x10, 0x10, 0x10, 0x10, -0x10, 0x07, 0x07, 0x07, 0x10, 0x10, 0x10, 0x10, 0x04, 0x06, 0x06, 0x00, 0x03, 0x03, 0x00, 0x0D, -0x0A, 0x06, 0x06, 0x01, 0x01, 0x01, 0x04, 0x05, 0x05, 0x06, 0x06, 0x06, 0x01, 0x01, 0x04, 0x05, -0x05, 0x06, 0x09, 0x0F, 0x00, 0x00, 0x0B, 0x0B, 0x05, 0x08, 0x08, 0x0A, 0x04, 0x04, 0x04, 0x04, -0x04, 0x08, 0x08, 0x0A, 0x04, 0x04, 0x04, 0x04, 0x04, 0x07, 0x07, 0x1A, 0x10, 0x10, 0x10, 0x10, -0x10, 0x07, 0x07, 0x1A, 0x10, 0x10, 0x10, 0x10, 0x04, 0x06, 0x06, 0x00, 0x03, 0x03, 0x00, 0x0D, -0x0A, 0x06, 0x06, 0x1C, 0x1C, 0x1C, 0x02, 0x05, 0x05, 0x06, 0x06, 0x06, 0x1C, 0x1C, 0x00, 0x05, -0x05, 0x06, 0x00, 0x0F, 0x00, 0x00, 0x23, 0x23, 0x05, 0x1F, 0x1F, 0x0A, 0x20, 0x20, 0x20, 0x20, -0x20, 0x1F, 0x1F, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x13, 0x13, 0x17, 0x14, 0x14, 0x14, 0x14, -0x14, 0x13, 0x13, 0x17, 0x14, 0x14, 0x14, 0x14, 0x16, 0x15, 0x15, 0x12, 0x13, 0x13, 0x18, 0x16, -0x16, 0x15, 0x15, 0x13, 0x26, 0x26, 0x13, 0x17, 0x17, 0x15, 0x15, 0x15, 0x26, 0x26, 0x13, 0x17, -0x17, 0x1B, 0x1D, 0x11, 0x13, 0x13, 0x18, 0x18, 0x17, 0x16, 0x16, 0x13, 0x13, 0x13, 0x19, 0x19, -0x19, 0x16, 0x16, 0x18, 0x13, 0x18, 0x19, 0x19, 0x19, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, -0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, -0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, -0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, -0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x05, 0x05, 0x06, 0x09, 0x09, 0x09, 0x09, -0x09, 0x05, 0x05, 0x06, 0x09, 0x09, 0x09, 0x09, 0x03 ]; +pub(crate) const ORIGINAL_OVERWORLD_BLOCKS: [u8; 272] = [ + 0x07, 0x07, 0x07, 0x10, 0x10, 0x10, 0x10, 0x10, 0x07, 0x07, 0x07, 0x10, 0x10, 0x10, 0x10, 0x04, + 0x06, 0x06, 0x00, 0x03, 0x03, 0x00, 0x0D, 0x0A, 0x06, 0x06, 0x01, 0x01, 0x01, 0x04, 0x05, 0x05, + 0x06, 0x06, 0x06, 0x01, 0x01, 0x04, 0x05, 0x05, 0x06, 0x09, 0x0F, 0x00, 0x00, 0x0B, 0x0B, 0x05, + 0x08, 0x08, 0x0A, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x08, 0x0A, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x07, 0x07, 0x1A, 0x10, 0x10, 0x10, 0x10, 0x10, 0x07, 0x07, 0x1A, 0x10, 0x10, 0x10, 0x10, 0x04, + 0x06, 0x06, 0x00, 0x03, 0x03, 0x00, 0x0D, 0x0A, 0x06, 0x06, 0x1C, 0x1C, 0x1C, 0x02, 0x05, 0x05, + 0x06, 0x06, 0x06, 0x1C, 0x1C, 0x00, 0x05, 0x05, 0x06, 0x00, 0x0F, 0x00, 0x00, 0x23, 0x23, 0x05, + 0x1F, 0x1F, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x1F, 0x1F, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x13, 0x13, 0x17, 0x14, 0x14, 0x14, 0x14, 0x14, 0x13, 0x13, 0x17, 0x14, 0x14, 0x14, 0x14, 0x16, + 0x15, 0x15, 0x12, 0x13, 0x13, 0x18, 0x16, 0x16, 0x15, 0x15, 0x13, 0x26, 0x26, 0x13, 0x17, 0x17, + 0x15, 0x15, 0x15, 0x26, 0x26, 0x13, 0x17, 0x17, 0x1B, 0x1D, 0x11, 0x13, 0x13, 0x18, 0x18, 0x17, + 0x16, 0x16, 0x13, 0x13, 0x13, 0x19, 0x19, 0x19, 0x16, 0x16, 0x18, 0x13, 0x18, 0x19, 0x19, 0x19, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x05, 0x05, 0x06, 0x09, 0x09, 0x09, 0x09, 0x09, 0x05, 0x05, 0x06, 0x09, 0x09, 0x09, 0x09, 0x03, +]; diff --git a/enemize/src/main.rs b/enemize/src/main.rs index e74877b..0e3cc09 100644 --- a/enemize/src/main.rs +++ b/enemize/src/main.rs @@ -72,7 +72,10 @@ fn main() -> anyhow::Result<()> { fn load_symbols() -> anyhow::Result { if let Err(_) = std::fs::metadata(ASAR_SYMBOLS) { - bail!("Could not find symbols at {}. Did you run prepare.sh?", ASAR_SYMBOLS); + bail!( + "Could not find symbols at {}. Did you run prepare.sh?", + ASAR_SYMBOLS + ); } asar::load_symbols(Path::new(ASAR_SYMBOLS)) diff --git a/enemize/src/option_flags.rs b/enemize/src/option_flags.rs index e0fca4e..4ff62cd 100644 --- a/enemize/src/option_flags.rs +++ b/enemize/src/option_flags.rs @@ -62,7 +62,7 @@ impl TryFrom for RandomizeEnemiesType { 2 => Ok(Self::Hard), 3 => Ok(Self::Chaos), 4 => Ok(Self::Insanity), - _ => Err(InvalidEnumError(PhantomData)) + _ => Err(InvalidEnumError(PhantomData)), } } } @@ -73,7 +73,7 @@ pub enum RandomizeEnemyHpType { Easy, Medium, Hard, - Patty + Patty, } impl From for u8 { @@ -98,7 +98,7 @@ impl TryFrom for RandomizeEnemyHpType { 1 => Ok(Self::Medium), 2 => Ok(Self::Hard), 3 => Ok(Self::Patty), - _ => Err(InvalidEnumError(PhantomData)) + _ => Err(InvalidEnumError(PhantomData)), } } } @@ -108,7 +108,7 @@ impl TryFrom for RandomizeEnemyHpType { pub enum RandomizeBossesType { Basic, Normal, - Chaos + Chaos, } impl From for u8 { @@ -131,7 +131,7 @@ impl TryFrom for RandomizeBossesType { 0 => Ok(Self::Basic), 1 => Ok(Self::Normal), 2 => Ok(Self::Chaos), - _ => Err(InvalidEnumError(PhantomData)) + _ => Err(InvalidEnumError(PhantomData)), } } } @@ -139,7 +139,7 @@ impl TryFrom for RandomizeBossesType { #[derive(Clone, Copy, Debug, Deserialize, Serialize)] #[serde(into = "u8", try_from = "u8")] pub enum SwordType { - Normal + Normal, } impl From for u8 { @@ -154,7 +154,7 @@ impl TryFrom for SwordType { fn try_from(byte: u8) -> Result { match byte { 0 => Ok(Self::Normal), - _ => Err(InvalidEnumError(PhantomData)) + _ => Err(InvalidEnumError(PhantomData)), } } } @@ -162,7 +162,7 @@ impl TryFrom for SwordType { #[derive(Clone, Copy, Debug, Deserialize, Serialize)] #[serde(into = "u8", try_from = "u8")] pub enum ShieldType { - Normal + Normal, } impl From for u8 { @@ -177,7 +177,7 @@ impl TryFrom for ShieldType { fn try_from(byte: u8) -> Result { match byte { 0 => Ok(Self::Normal), - _ => Err(InvalidEnumError(PhantomData)) + _ => Err(InvalidEnumError(PhantomData)), } } } @@ -243,7 +243,7 @@ impl TryFrom for AbsorbableType { 11 => Ok(Self::Fairy), 12 => Ok(Self::Key), 13 => Ok(Self::BigKey), - _ => Err(InvalidEnumError(PhantomData)) + _ => Err(InvalidEnumError(PhantomData)), } } } @@ -310,7 +310,7 @@ impl TryFrom for HeartBeepSpeed { 1 => Ok(Self::Half), 2 => Ok(Self::Quarter), 3 => Ok(Self::Off), - _ => Err(InvalidEnumError(PhantomData)) + _ => Err(InvalidEnumError(PhantomData)), } } } @@ -360,7 +360,7 @@ impl TryFrom for BeeLevel { 1 => Ok(Self::Level2), 2 => Ok(Self::Level3), 3 => Ok(Self::Level4), - _ => Err(InvalidEnumError(PhantomData)) + _ => Err(InvalidEnumError(PhantomData)), } } } @@ -542,20 +542,90 @@ impl OptionFlags { bytes.push(self.enemies_absorbable as u8); bytes.push(self.absorbable_spawn_rate); - bytes.push(self.absorbable_types.get(&AbsorbableType::Heart).copied().unwrap_or(false) as u8); - bytes.push(self.absorbable_types.get(&AbsorbableType::GreenRupee).copied().unwrap_or(false) as u8); - bytes.push(self.absorbable_types.get(&AbsorbableType::BlueRupee).copied().unwrap_or(false) as u8); - bytes.push(self.absorbable_types.get(&AbsorbableType::RedRupee).copied().unwrap_or(false) as u8); - bytes.push(self.absorbable_types.get(&AbsorbableType::Bomb1).copied().unwrap_or(false) as u8); - bytes.push(self.absorbable_types.get(&AbsorbableType::Bomb4).copied().unwrap_or(false) as u8); - bytes.push(self.absorbable_types.get(&AbsorbableType::Bomb8).copied().unwrap_or(false) as u8); - bytes.push(self.absorbable_types.get(&AbsorbableType::SmallMagic).copied().unwrap_or(false) as u8); - bytes.push(self.absorbable_types.get(&AbsorbableType::FullMagic).copied().unwrap_or(false) as u8); - bytes.push(self.absorbable_types.get(&AbsorbableType::Arrow5).copied().unwrap_or(false) as u8); - bytes.push(self.absorbable_types.get(&AbsorbableType::Arrow10).copied().unwrap_or(false) as u8); - bytes.push(self.absorbable_types.get(&AbsorbableType::Fairy).copied().unwrap_or(false) as u8); - bytes.push(self.absorbable_types.get(&AbsorbableType::Key).copied().unwrap_or(false) as u8); - bytes.push(self.absorbable_types.get(&AbsorbableType::BigKey).copied().unwrap_or(false) as u8); + bytes.push( + self.absorbable_types + .get(&AbsorbableType::Heart) + .copied() + .unwrap_or(false) as u8, + ); + bytes.push( + self.absorbable_types + .get(&AbsorbableType::GreenRupee) + .copied() + .unwrap_or(false) as u8, + ); + bytes.push( + self.absorbable_types + .get(&AbsorbableType::BlueRupee) + .copied() + .unwrap_or(false) as u8, + ); + bytes.push( + self.absorbable_types + .get(&AbsorbableType::RedRupee) + .copied() + .unwrap_or(false) as u8, + ); + bytes.push( + self.absorbable_types + .get(&AbsorbableType::Bomb1) + .copied() + .unwrap_or(false) as u8, + ); + bytes.push( + self.absorbable_types + .get(&AbsorbableType::Bomb4) + .copied() + .unwrap_or(false) as u8, + ); + bytes.push( + self.absorbable_types + .get(&AbsorbableType::Bomb8) + .copied() + .unwrap_or(false) as u8, + ); + bytes.push( + self.absorbable_types + .get(&AbsorbableType::SmallMagic) + .copied() + .unwrap_or(false) as u8, + ); + bytes.push( + self.absorbable_types + .get(&AbsorbableType::FullMagic) + .copied() + .unwrap_or(false) as u8, + ); + bytes.push( + self.absorbable_types + .get(&AbsorbableType::Arrow5) + .copied() + .unwrap_or(false) as u8, + ); + bytes.push( + self.absorbable_types + .get(&AbsorbableType::Arrow10) + .copied() + .unwrap_or(false) as u8, + ); + bytes.push( + self.absorbable_types + .get(&AbsorbableType::Fairy) + .copied() + .unwrap_or(false) as u8, + ); + bytes.push( + self.absorbable_types + .get(&AbsorbableType::Key) + .copied() + .unwrap_or(false) as u8, + ); + bytes.push( + self.absorbable_types + .get(&AbsorbableType::BigKey) + .copied() + .unwrap_or(false) as u8, + ); bytes.push(self.boss_madness as u8); bytes.push(self.randomize_bosses as u8); diff --git a/enemize/src/randomize.rs b/enemize/src/randomize.rs index a1abdb9..4fe78ae 100644 --- a/enemize/src/randomize.rs +++ b/enemize/src/randomize.rs @@ -2,13 +2,21 @@ use std::path::Path; use anyhow::ensure; -use crate::PatchSet; use crate::option_flags::OptionFlags; use crate::rom::RomData; +use crate::PatchSet; impl RomData { - pub fn randomize(&mut self, base_patch: &Path, option_flags: OptionFlags, seed: i32) -> anyhow::Result<()> { - ensure!(self.is_randomizer(), "Enemizer only supports randomizer ROMs for input."); + pub fn randomize( + &mut self, + base_patch: &Path, + option_flags: OptionFlags, + seed: i32, + ) -> anyhow::Result<()> { + ensure!( + self.is_randomizer(), + "Enemizer only supports randomizer ROMs for input." + ); ensure!(!self.is_race(), "Enemizer does not support race roms."); if self.is_enemizer() { diff --git a/enemize/src/rom.rs b/enemize/src/rom.rs index 6b264ed..2ae8d66 100644 --- a/enemize/src/rom.rs +++ b/enemize/src/rom.rs @@ -4,14 +4,15 @@ use std::ops::Range; use anyhow::bail; -use crate::asar::{Symbols, snes_to_pc_address, pc_to_snes_address}; +use crate::asar::{pc_to_snes_address, snes_to_pc_address, Symbols}; use crate::constants::*; use crate::option_flags::OptionFlags; use crate::Patch; pub const ENEMIZER_INFO_SEED_OFFSET: usize = 0; pub const ENEMIZER_INFO_SEED_LENGTH: usize = 12; -pub const ENEMIZER_INFO_VERSION_OFFSET: usize = ENEMIZER_INFO_SEED_OFFSET + ENEMIZER_INFO_SEED_LENGTH; +pub const ENEMIZER_INFO_VERSION_OFFSET: usize = + ENEMIZER_INFO_SEED_OFFSET + ENEMIZER_INFO_SEED_LENGTH; pub const ENEMIZER_INFO_VERSION_LENGTH: usize = 8; pub const ENEMIZER_INFO_FLAGS_OFFSET: usize = ENEMIZER_INFO_VERSION_OFFSET + ENEMIZER_INFO_VERSION_LENGTH; @@ -48,7 +49,8 @@ impl RomData { self.rom_data[ROOM_HEADER_BANK_LOCATION] = 0x04; - let dungeon_header_range = DUNGEON_HEADER_POINTER_TABLE..(DUNGEON_HEADER_POINTER_TABLE + 640); + let dungeon_header_range = + 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); @@ -64,9 +66,15 @@ impl RomData { let mut patches = vec![]; for (&addr, &byte) in self.patch_data.iter() { - match patches.last_mut().filter(|p: &&mut Patch| p.address + 1 == addr) { - None => patches.push(Patch { address: addr, patch_data: vec![byte] }), - Some(patch) => patch.patch_data.push(byte) + match patches + .last_mut() + .filter(|p: &&mut Patch| p.address + 1 == addr) + { + None => patches.push(Patch { + address: addr, + patch_data: vec![byte], + }), + Some(patch) => patch.patch_data.push(byte), } } @@ -83,46 +91,48 @@ impl RomData { fn set_patch_bytes(&mut self, range: Range) { let slice = &self.rom_data[range.clone()]; - self.patch_data.extend(iter::zip(range, slice.into_iter().map(|&b| b))); + self.patch_data + .extend(iter::zip(range, slice.into_iter().map(|&b| b))); } pub fn is_enemizer(&self) -> bool { self.rom_data.len() == ENEMIZER_FILE_LENGTH - && 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' + && 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' } pub fn is_randomizer(&self) -> bool { let acceptable = [ - // item rando - b"VT", - // entrance rando - b"ER", - // door rando - b"DR", - // Berserker's multiworld - b"BM", - // Berserker's multiworld doors - b"BD", - // Archipelago - b"AP", - // Archipelago with door rando - b"AD", + b"VT", // item rando + b"ER", // entrance rando + b"DR", // door rando + b"BM", // Berserker's multiworld + b"BD", // Berserker's multiworld doors + b"AP", // Archipelago + b"AD", // Archipelago with door rando ]; - acceptable.iter().any(|abbr| &abbr[..] == &self.rom_data[0x7FC0..0x7Fc1]) || - (self.rom_data.len() >= 0x20_0000 && - &self.rom_data[0x7FC0..0x7FCE] == b"ZELDANODENSETSU") + acceptable + .iter() + .any(|abbr| &abbr[..] == &self.rom_data[0x7FC0..0x7Fc1]) + || (self.rom_data.len() >= 0x20_0000 + && &self.rom_data[0x7FC0..0x7FCE] == 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.is_randomizer() + && (&self.rom_data[0x180213..0x180214] == &[1, 0] + || &self.rom_data[0x7FC0..0x7FC9] == b"VT TOURNEY") } fn assert_rom_length(&self) { - assert!(self.rom_data.len() >= ENEMIZER_FILE_LENGTH, "You need to expand the rom before you can use Enemizer features."); + assert!( + self.rom_data.len() >= ENEMIZER_FILE_LENGTH, + "You need to expand the rom before you can use Enemizer features." + ); } pub fn get_enemizer_seed(&self) -> i32 { @@ -167,9 +177,12 @@ impl RomData { pub fn get_enemizer_version(&self) -> anyhow::Result<&str> { if self.is_enemizer() { - let version_start = self.asar_symbols["enemizer_info_table"] + 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])?) + Ok(std::str::from_utf8( + &self.rom_data[version_start..version_end], + )?) } else { bail!("Not Enemizer Rom") } @@ -187,7 +200,7 @@ impl RomData { self.set_patch_bytes(version_start..version_end) } - pub fn set_info_flags(&mut self, flags: OptionFlags) -> anyhow::Result<()>{ + pub fn set_info_flags(&mut self, flags: OptionFlags) -> anyhow::Result<()> { let bytes = flags.into_bytes(); if bytes.len() > 0x100 - ENEMIZER_INFO_FLAGS_OFFSET { bail!("Option flags is too long to fit in the space allocated. Need to move data/code in asm file."); @@ -249,7 +262,8 @@ impl RomData { let header_base = self.asar_symbols["room_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"]]; + 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. @@ -260,14 +274,15 @@ impl RomData { self.rom_data[table_base + (i * 2)], self.rom_data[table_base + (i * 2) + 1], 4, - 0 + 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); + self.rom_data + .copy_within(pc_address..(pc_address + 14), header_start); } // Repoint the pointer table to the new header table. @@ -276,7 +291,8 @@ impl RomData { 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]); + self.rom_data[(table_base + (i * 2))..(table_base + (i * 2) + 1)] + .copy_from_slice(&snes[0..1]); } } } From 034af7fa92c9e99441ca6c67b777f23f3af193a1 Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Sat, 4 Jun 2022 09:58:10 -0400 Subject: [PATCH 15/29] More backwards compatibility. --- enemize/src/option_flags.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/enemize/src/option_flags.rs b/enemize/src/option_flags.rs index 4ff62cd..259f160 100644 --- a/enemize/src/option_flags.rs +++ b/enemize/src/option_flags.rs @@ -366,6 +366,7 @@ impl TryFrom for BeeLevel { } #[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "PascalCase")] pub struct OptionFlags { pub randomize_enemies: bool, pub randomize_enemies_type: RandomizeEnemiesType, From 4cbc70eeca3e2f3702a6d63cd33e25bc0a443260 Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Sat, 4 Jun 2022 10:00:06 -0400 Subject: [PATCH 16/29] Make it easy to write options file. --- enemize/src/option_flags.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enemize/src/option_flags.rs b/enemize/src/option_flags.rs index 259f160..a8d2fd9 100644 --- a/enemize/src/option_flags.rs +++ b/enemize/src/option_flags.rs @@ -366,7 +366,7 @@ impl TryFrom for BeeLevel { } #[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "PascalCase")] +#[serde(default, rename_all = "PascalCase")] pub struct OptionFlags { pub randomize_enemies: bool, pub randomize_enemies_type: RandomizeEnemiesType, From 975d6fd4202b9218f98d636312533c13d5f81e5f Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Sat, 4 Jun 2022 14:35:44 -0400 Subject: [PATCH 17/29] PatchSet only serializes `patches` to the file. --- base_patch_generator/src/main.rs | 3 +-- enemize/src/lib.rs | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) 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); } From 76ff991e8f8ab95e4902170b685e3ee78dcd2d89 Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Sat, 4 Jun 2022 14:37:12 -0400 Subject: [PATCH 18/29] Conform to serialization used by ALttpDoorRandomizer. --- enemize/src/option_flags.rs | 102 +++++++++++++++++++++--------------- 1 file changed, 61 insertions(+), 41 deletions(-) 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); + } +} From fed38efcc4d2c5e4e93bf0168322d194248792da Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Sat, 4 Jun 2022 14:38:35 -0400 Subject: [PATCH 19/29] Add back options passed by ALttpDoorRandomizer. Even though they aren't used, we have to declare them to parse them. --- enemize/src/main.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) 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![]; From 4972986feec372235db62ac2546ad1d55a976179 Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Sat, 4 Jun 2022 14:39:11 -0400 Subject: [PATCH 20/29] Fix byte indexes. And standardize on lower-case hexadecimal. --- enemize/src/rom.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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; From ec37b1d583085af9e50bbf8e30c6db24e4aa87bf Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Sat, 4 Jun 2022 15:27:28 -0400 Subject: [PATCH 21/29] Put the enemizer base patch next to the executable. --- prepare.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/prepare.sh b/prepare.sh index ecb6269..d532d49 100644 --- a/prepare.sh +++ b/prepare.sh @@ -6,3 +6,4 @@ fi if [[ ! -s enemizer_base_patch.json ]]; then cargo run -p base_patch_generator -- "$1" base_patch.json asar_symbols.txt enemizer_base_patch.json fi +cp enemizer_base_patch.json target/debug/enemizerBasePatch.json From 8d8faf105ab52e82310282e8ec1f950707bd19e7 Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Sat, 4 Jun 2022 15:28:21 -0400 Subject: [PATCH 22/29] Finally done serializing. --- enemize/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/enemize/src/lib.rs b/enemize/src/lib.rs index 63e07af..a499767 100644 --- a/enemize/src/lib.rs +++ b/enemize/src/lib.rs @@ -19,11 +19,13 @@ pub mod rom; pub struct InvalidEnumError(PhantomData); #[derive(Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] pub struct Patch { pub address: usize, pub patch_data: Vec, } +#[derive(Default)] pub struct PatchSet { filename: PathBuf, patches: Vec, From a0150caa56bda21f7a622e0f4b6724d8cdf519c6 Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Sat, 4 Jun 2022 15:29:02 -0400 Subject: [PATCH 23/29] Bake enemizer base patch and asar symbols into executable. --- base_patch_generator/src/main.rs | 5 ++- enemize/src/asar.rs | 57 ++++++++++++++------------------ enemize/src/main.rs | 19 ++++------- enemize/src/randomize.rs | 13 ++++---- 4 files changed, 41 insertions(+), 53 deletions(-) diff --git a/base_patch_generator/src/main.rs b/base_patch_generator/src/main.rs index f3412d3..9d47946 100644 --- a/base_patch_generator/src/main.rs +++ b/base_patch_generator/src/main.rs @@ -36,7 +36,10 @@ fn main() -> Result<(), anyhow::Error> { rom_bytes.resize(4 * 1024 * 1024, 0); - let symbols = asar::load_symbols(Path::new(&symbols_path))?; + let mut symbols_file = File::open(&symbols_path)?; + let mut symbols_text = String::new(); + symbols_file.read_to_string(&mut symbols_text)?; + let symbols = asar::load_symbols(&symbols_text)?; let mut rom = RomData::new(symbols, rom_bytes); diff --git a/enemize/src/asar.rs b/enemize/src/asar.rs index b4afba8..6a34cd0 100644 --- a/enemize/src/asar.rs +++ b/enemize/src/asar.rs @@ -1,43 +1,34 @@ use std::collections::HashMap; -use std::fs::File; -use std::io::{BufRead, BufReader}; -use std::path::Path; pub type Symbols = HashMap; -pub fn load_symbols(filename: &Path) -> anyhow::Result { - let file = File::open(filename)?; - let reader = BufReader::new(file); - - let symbols = reader +pub fn load_symbols(contents: &str) -> anyhow::Result { + let symbols = contents .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())) - } + .filter_map(|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_to_pc_address(snes_address); - - Some((symbol, pc_address)) - }) + _ => None, + } }) - .collect(); + .filter_map(|(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_to_pc_address(snes_address); + + Some((symbol, pc_address)) + }) + .collect(); Ok(symbols) } diff --git a/enemize/src/main.rs b/enemize/src/main.rs index 17da211..fcb4f5e 100644 --- a/enemize/src/main.rs +++ b/enemize/src/main.rs @@ -1,15 +1,15 @@ use std::fs::File; use std::io::prelude::*; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::time::Instant; -use anyhow::{bail, ensure}; +use anyhow::ensure; use clap::Parser; use enemize::{asar, rom::RomData}; -const ASAR_SYMBOLS: &'static str = "asar_symbols.txt"; -const ENEMIZER_BASE_PATCH: &'static str = "enemizer_base_patch.json"; +const ASAR_SYMBOLS: &'static str = include_str!("../../asar_symbols.txt"); +const ENEMIZER_BASE_PATCH: &'static str = include_str!("../../enemizer_base_patch.json"); /// Randomizes enemy placements in The Legend of Zelda: A Link to the Past for the Super Nintendo /// Entertainment System @@ -65,7 +65,7 @@ fn main() -> anyhow::Result<()> { // (That is, 2 out of 2 billion instead of 1.) let seed = args.seed.unwrap_or_else(|| rand::random()).saturating_abs(); - rom.randomize(Path::new(ENEMIZER_BASE_PATCH), options, seed)?; + rom.randomize(ENEMIZER_BASE_PATCH, options, seed)?; let mut out_file = File::create(&args.output)?; @@ -84,12 +84,5 @@ fn main() -> anyhow::Result<()> { } fn load_symbols() -> anyhow::Result { - if let Err(_) = std::fs::metadata(ASAR_SYMBOLS) { - bail!( - "Could not find symbols at {}. Did you run prepare.sh?", - ASAR_SYMBOLS - ); - } - - asar::load_symbols(Path::new(ASAR_SYMBOLS)) + asar::load_symbols(ASAR_SYMBOLS) } diff --git a/enemize/src/randomize.rs b/enemize/src/randomize.rs index 4fe78ae..a42438e 100644 --- a/enemize/src/randomize.rs +++ b/enemize/src/randomize.rs @@ -1,15 +1,13 @@ -use std::path::Path; - use anyhow::ensure; use crate::option_flags::OptionFlags; use crate::rom::RomData; -use crate::PatchSet; +use crate::{Patch, PatchSet}; impl RomData { pub fn randomize( &mut self, - base_patch: &Path, + base_patch: &str, option_flags: OptionFlags, seed: i32, ) -> anyhow::Result<()> { @@ -29,8 +27,11 @@ impl RomData { self.expand_rom(); self.set_info_flags(option_flags)?; - let patches = PatchSet::load(base_patch)?; - patches.patch_rom(self); + let patches: Vec = serde_json::from_str(base_patch)?; + + let mut patch_set = PatchSet::default(); + patch_set.add_patches(patches); + patch_set.patch_rom(self); Ok(()) } From d1d4e3173887e7ecc97aeafc0d2127e3d9bbf7ae Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Sun, 5 Jun 2022 15:56:36 -0400 Subject: [PATCH 24/29] Format! --- enemize/src/asar.rs | 20 ++++++++++---------- enemize/src/option_flags.rs | 11 ++++++----- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/enemize/src/asar.rs b/enemize/src/asar.rs index 6a34cd0..033f07d 100644 --- a/enemize/src/asar.rs +++ b/enemize/src/asar.rs @@ -16,19 +16,19 @@ pub fn load_symbols(contents: &str) -> anyhow::Result { _ => None, } }) - .filter_map(|(symbol, mut address)| { - if let Some(colon_at) = address.find(':') { - address.remove(colon_at); - } + .filter_map(|(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()?; + // 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_to_pc_address(snes_address); + let pc_address = snes_to_pc_address(snes_address); - Some((symbol, pc_address)) - }) - .collect(); + Some((symbol, pc_address)) + }) + .collect(); Ok(symbols) } diff --git a/enemize/src/option_flags.rs b/enemize/src/option_flags.rs index c3f1f0d..0f683e3 100644 --- a/enemize/src/option_flags.rs +++ b/enemize/src/option_flags.rs @@ -387,13 +387,11 @@ pub struct OptionFlags { pub enemy_damage_chaos_mode: bool, //pub easy_mode_escape: bool, - pub enemies_absorbable: bool, pub absorbable_spawn_rate: u8, pub absorbable_types: HashMap, //pub boss_madness: bool, - pub randomize_bosses: bool, pub randomize_bosses_type: RandomizeBossesType, @@ -406,7 +404,6 @@ pub struct OptionFlags { //pub randomize_boss_damage_max_amount: u8, //pub randomize_boss_behavior: bool, - pub randomize_dungeon_palettes: bool, pub set_blackout_mode: bool, @@ -766,8 +763,12 @@ mod 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"); + 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); + assert_eq!( + serde_json::to_string(&actual).expect("Can't roundtrip"), + expected + ); } } From 1c752e5bbde7a6454ebc5aa46d250cd9a1d96cd2 Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Sun, 5 Jun 2022 15:57:02 -0400 Subject: [PATCH 25/29] Make RomData indexable. --- enemize/src/rom.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/enemize/src/rom.rs b/enemize/src/rom.rs index fe22d41..b4e8700 100644 --- a/enemize/src/rom.rs +++ b/enemize/src/rom.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; use std::iter; -use std::ops::Range; +use std::ops::{Index, IndexMut, Range}; use anyhow::bail; @@ -245,11 +245,6 @@ impl RomData { 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) { self.set_rom_bytes( &patch.patch_data, @@ -296,3 +291,17 @@ impl RomData { } } } + +impl Index for RomData { + type Output = u8; + + fn index(&self, index: usize) -> &u8 { + &self.rom_data[index] + } +} + +impl IndexMut for RomData { + fn index_mut(&mut self, index: usize) -> &mut u8 { + &mut self.rom_data[index] + } +} From 13e910091b2d9d4332308e10a8c4f4453b31f729 Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Fri, 10 Jun 2022 11:49:07 -0400 Subject: [PATCH 26/29] Add graph data. Items and dungeons so far. Will be used to make sure the resulting ROM is beatable. --- Cargo.lock | 1 + enemize/Cargo.toml | 1 + enemize/src/graph/dungeons.rs | 144 ++++++++ enemize/src/graph/item.rs | 554 +++++++++++++++++++++++++++++++ enemize/src/graph/mod.rs | 3 + enemize/src/graph/requirement.rs | 16 + enemize/src/lib.rs | 1 + 7 files changed, 720 insertions(+) create mode 100644 enemize/src/graph/dungeons.rs create mode 100644 enemize/src/graph/item.rs create mode 100644 enemize/src/graph/mod.rs create mode 100644 enemize/src/graph/requirement.rs diff --git a/Cargo.lock b/Cargo.lock index c449041..e82f6c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -102,6 +102,7 @@ version = "0.1.0" dependencies = [ "anyhow", "clap", + "lazy_static", "rand", "serde", "serde_json", diff --git a/enemize/Cargo.toml b/enemize/Cargo.toml index a763a91..6a55151 100644 --- a/enemize/Cargo.toml +++ b/enemize/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] anyhow = "1" clap = { version = "3.1.18", features = ["derive"] } +lazy_static = "1.4" rand = "0.8" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/enemize/src/graph/dungeons.rs b/enemize/src/graph/dungeons.rs new file mode 100644 index 0000000..5d5bfbc --- /dev/null +++ b/enemize/src/graph/dungeons.rs @@ -0,0 +1,144 @@ +use std::collections::HashMap; + +use lazy_static::lazy_static; + +use super::item::ItemId; + +#[derive(Debug, thiserror::Error)] +#[error("{0} is not a dungeon room")] +pub struct NotADungeonRoom(u8); + +#[derive(Clone, Copy, Eq, Hash, PartialEq)] +pub enum Dungeon { + HyruleCastle, + EasternPalace, + DesertPalace, + TowerOfHera, + AgahnimsTower, + PalaceOfDarkness, + SwampPalace, + SkullWoods, + ThievesTown, + IcePalace, + MiseryMire, + TurtleRock, + GanonsTower, +} + +pub fn get_dungeon_from_room(room_id: u8) -> Result { + for (dungeon, rooms) in DUNGEON_ROOMS.iter() { + if let Ok(_) = rooms.binary_search(&room_id) { + return Ok(*dungeon); + } + } + + Err(NotADungeonRoom(room_id)) +} + +const HYRULE_CASTLE_ROOMS: [u8; 21] = [ + 1, 2, 17, 18, 33, 34, 50, 65, 66, 80, 81, 82, 96, 97, 98, 112, 113, 114, 128, 129, 130, +]; + +const EASTERN_PALACE_ROOMS: [u8; 13] = [ + 137, 153, 168, 169, 170, 184, 185, 186, 200, 201, 216, 217, 218, +]; + +const DESERT_PALACE_ROOMS: [u8; 10] = [51, 67, 83, 99, 115, 116, 117, 131, 132, 133]; + +const TOWER_OF_HERA_ROOMS: [u8; 7] = [7, 23, 39, 49, 119, 135, 167]; + +const AGAHNIMS_TOWER_ROOMS: [u8; 7] = [32, 48, 64, 176, 192, 208, 224]; + +const PALACE_OF_DARKNESS_ROOMS: [u8; 14] = [9, 10, 11, 25, 26, 27, 42, 43, 58, 59, 74, 75, 90, 106]; + +const SWAMP_PALACE_ROOMS: [u8; 13] = [6, 22, 38, 40, 52, 53, 54, 55, 56, 70, 84, 102, 118]; + +const SKULL_WOODS_ROOMS: [u8; 9] = [41, 57, 73, 86, 87, 88, 89, 103, 104]; + +const THIEVES_TOWN_ROOMS: [u8; 12] = [68, 69, 100, 101, 171, 172, 187, 188, 203, 204, 219, 220]; + +const ICE_PALACE_ROOMS: [u8; 22] = [ + 14, 30, 31, 46, 62, 63, 78, 79, 94, 95, 110, 126, 127, 142, 158, 159, 174, 175, 190, 191, 206, + 222, +]; + +const MISERY_MIRE_ROOMS: [u8; 18] = [ + 144, 145, 146, 147, 151, 152, 160, 161, 162, 163, 177, 178, 179, 193, 194, 195, 209, 210, +]; + +const TURTLE_ROCK_ROOMS: [u8; 17] = [ + 4, 19, 20, 21, 35, 36, 164, 180, 181, 182, 183, 196, 197, 198, 199, 213, 214, +]; + +const GANONS_TOWER_ROOMS: [u8; 26] = [ + 12, 13, 28, 29, 61, 76, 77, 91, 92, 93, 107, 108, 109, 123, 124, 125, 139, 140, 141, 149, 150, + 155, 156, 157, 165, 166, +]; + +lazy_static! { + static ref DUNGEON_ROOMS: HashMap = { + use Dungeon::*; + + let mut rooms = HashMap::new(); + + rooms.insert(HyruleCastle, &HYRULE_CASTLE_ROOMS[..]); + rooms.insert(EasternPalace, &EASTERN_PALACE_ROOMS[..]); + rooms.insert(DesertPalace, &DESERT_PALACE_ROOMS[..]); + rooms.insert(TowerOfHera, &TOWER_OF_HERA_ROOMS[..]); + rooms.insert(AgahnimsTower, &AGAHNIMS_TOWER_ROOMS[..]); + rooms.insert(PalaceOfDarkness, &PALACE_OF_DARKNESS_ROOMS[..]); + rooms.insert(SwampPalace, &SWAMP_PALACE_ROOMS[..]); + rooms.insert(SkullWoods, &SKULL_WOODS_ROOMS[..]); + rooms.insert(ThievesTown, &THIEVES_TOWN_ROOMS[..]); + rooms.insert(IcePalace, &ICE_PALACE_ROOMS[..]); + rooms.insert(MiseryMire, &MISERY_MIRE_ROOMS[..]); + rooms.insert(TurtleRock, &TURTLE_ROCK_ROOMS[..]); + rooms.insert(GanonsTower, &GANONS_TOWER_ROOMS[..]); + + rooms + }; + pub static ref DUNGEON_KEYS: HashMap = { + use Dungeon::*; + use ItemId::*; + + let mut keys = HashMap::new(); + + keys.insert(HyruleCastle, HyruleKey); + keys.insert(EasternPalace, EasternKey); + keys.insert(DesertPalace, DesertKey); + keys.insert(TowerOfHera, HeraKey); + keys.insert(AgahnimsTower, AgaKey); + keys.insert(PalaceOfDarkness, PodKey); + keys.insert(SwampPalace, SwampKey); + keys.insert(SkullWoods, SkullKey); + keys.insert(ThievesTown, ThievesKey); + keys.insert(IcePalace, IceKey); + keys.insert(MiseryMire, MireKey); + keys.insert(TurtleRock, TurtleKey); + keys.insert(GanonsTower, GTKey); + + keys + }; + pub static ref DUNGEON_BIG_KEYS: HashMap = { + use Dungeon::*; + use ItemId::*; + + let mut keys = HashMap::new(); + + keys.insert(HyruleCastle, HyruleBigKey); + keys.insert(EasternPalace, EasternBigKey); + keys.insert(DesertPalace, DesertBigKey); + keys.insert(TowerOfHera, HeraBigKey); + keys.insert(AgahnimsTower, AgaBigKey); + keys.insert(PalaceOfDarkness, PodBigKey); + keys.insert(SwampPalace, SwampBigKey); + keys.insert(SkullWoods, SkullBigKey); + keys.insert(ThievesTown, ThievesBigKey); + keys.insert(IcePalace, IceBigKey); + keys.insert(MiseryMire, MireBigKey); + keys.insert(TurtleRock, TurtleBigKey); + keys.insert(GanonsTower, GTBigKey); + + keys + }; +} diff --git a/enemize/src/graph/item.rs b/enemize/src/graph/item.rs new file mode 100644 index 0000000..13f3f16 --- /dev/null +++ b/enemize/src/graph/item.rs @@ -0,0 +1,554 @@ +use std::collections::HashMap; +use std::hash::{Hash, Hasher}; + +use lazy_static::lazy_static; + +#[derive(Clone, Copy)] +pub enum ItemType { + Normal, + Bottle, + Special, + Progressive { level: u8 }, + Consumable { found: u8, used: u8 }, +} + +impl ItemType { + pub fn level(&self) -> u8 { + match self { + Self::Progressive { level } => *level, + _ => 0, + } + } + + pub fn increase_level(&mut self) { + match self { + Self::Progressive { ref mut level } => *level += 1, + _ => (), + } + } + + pub fn found(&self) -> u8 { + match self { + Self::Consumable { found, .. } => *found, + _ => 0, + } + } + + pub fn increase_count(&mut self) { + match self { + Self::Consumable { ref mut found, .. } => *found += 1, + _ => (), + } + } + + pub fn used(&self) -> u8 { + match self { + Self::Consumable { used, .. } => *used, + _ => 0, + } + } + + pub fn consume(&mut self) { + match self { + Self::Consumable { ref mut used, .. } => *used += 1, + _ => (), + } + } + + pub fn usable(&self) -> bool { + match self { + Self::Consumable { found, used } => found > used, + _ => false, + } + } +} + +#[derive(Clone, Copy)] +pub struct Item { + pub id: ItemId, + pub name: &'static str, + pub item_type: ItemType, +} + +impl Item { + pub fn normal(id: ItemId, name: &'static str) -> Self { + Self { + id, + name, + item_type: ItemType::Normal, + } + } + + pub fn bottle(id: ItemId, name: &'static str) -> Self { + Self { + id, + name, + item_type: ItemType::Bottle, + } + } + + pub fn special(id: ItemId, name: &'static str) -> Self { + Self { + id, + name, + item_type: ItemType::Special, + } + } + + pub fn progressive(id: ItemId, name: &'static str) -> Self { + Self { + id, + name, + item_type: ItemType::Progressive { level: 0 }, + } + } + + pub fn consumable(id: ItemId, name: &'static str) -> Self { + Self { + id, + name, + item_type: ItemType::Consumable { found: 0, used: 0 }, + } + } +} + +impl PartialEq for Item { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl Eq for Item {} + +impl Hash for Item { + fn hash(&self, state: &mut H) { + self.id.hash(state); + } +} + +#[derive(Clone, Copy, Eq, Hash, PartialEq)] +#[repr(u32)] +pub enum ItemId { + FightersSwordAndShield = 0x00, + MasterSword1 = 0x01, + TemperedSword = 0x02, + GoldenSword = 0x03, + FightersShield = 0x04, + FireShield = 0x05, + MirrorShield = 0x06, + FireRod = 0x07, + IceRod = 0x08, + Hammer = 0x09, + Hookshot = 0x0a, + Bow = 0x0b, + Boomerang = 0x0c, + Powder = 0x0d, + Bee = 0x0e, + Bombos = 0x0f, + Ether = 0x10, + Quake = 0x11, + Lamp = 0x12, + Shovel = 0x13, + Flute = 0x14, + Somaria = 0x15, + Bottle = 0x16, + PieceOfHeart = 0x17, + Byrna = 0x18, + Cape = 0x19, + Mirror = 0x1a, + L1Gloves = 0x1b, + L2Gloves = 0x1c, + Book = 0x1d, + Flippers = 0x1e, + MoonPearl = 0x1f, + Crystal = 0x20, + BugCatchingNet = 0x21, + BlueMail = 0x22, + RedMail = 0x23, + Key = 0x24, + Compass = 0x25, + HeartContainerNoAnimation = 0x26, + Bomb = 0x27, + ThreeBombs = 0x28, + Mushroom = 0x29, + MagicalBoomerang = 0x2a, + BottleRedPotion = 0x2b, + BottleGreenPotion = 0x2c, + BottleBluePotion = 0x2d, + RedPotion = 0x2e, + GreenPotion = 0x2f, + BluePotion = 0x30, + TenBombs = 0x31, + BigKey = 0x32, + DungeonMap = 0x33, + GreenRupee = 0x34, + BlueRupee = 0x35, + RedRupee = 0x36, + PendantGreen = 0x37, + PendantRed = 0x38, + PendantBlue = 0x39, + BowAndArrows = 0x3a, + BowAndSilverArrows = 0x3b, + BottleBee = 0x3c, + BottleFairy = 0x3d, + HeartContainer = 0x3e, + HeartContainerRefill = 0x3f, + OneHundredRupees = 0x40, + FiftyRupees = 0x41, + Heart = 0x42, + SingleArrow = 0x43, + TenArrows = 0x44, + SmallMagic = 0x45, + ThreeHundredRupees = 0x46, + RedRupee2 = 0x47, + BottleGoldenBee = 0x48, + FightersSword = 0x49, + FluteActive = 0x4a, + Boots = 0x4b, + MaxBombs = 0x4c, + MaxArrows = 0x4d, + HalfMagic = 0x4e, + QuarterMagic = 0x4f, + MasterSword2 = 0x50, + BombUpgrade5 = 0x51, + BombUpgrade10 = 0x52, + ArrowUpgrade5 = 0x53, + ArrowUpgrade10 = 0x54, + Programmable1 = 0x55, + Programmable2 = 0x56, + Programmable3 = 0x57, + SilverArrows = 0x58, + Rupoor = 0x59, + Nothing = 0x5a, + RedClock = 0x5b, + BlueClock = 0x5c, + GreenClock = 0x5d, + ProgressiveSword = 0x5e, + ProgressiveShield = 0x5f, + ProgressiveArmor = 0x60, + ProgressiveGloves = 0x61, + UniqueRngItem = 0x62, + NonUniqueRngItem = 0x63, + ProgressiveBow = 0x64, + ProgressiveBow2 = 0x65, + Triforce = 0x6a, + PowerStar = 0x6b, + TriforcePiece = 0x6c, + LightWorldMap = 0x70, + DarkWorldMap = 0x71, + GTMap = 0x72, + TurtleMap = 0x73, + ThievesMap = 0x74, + HeraMap = 0x75, + IceMap = 0x76, + SkullMap = 0x77, + MireMap = 0x78, + PodMap = 0x79, + SwampMap = 0x7a, + AgaMap = 0x7b, + DesertMap = 0x7c, + EasternMap = 0x7d, + HyruleMap = 0x7e, + SewersMap = 0x7f, + GTCompass = 0x82, + TurtleCompass = 0x83, + ThievesCompass = 0x84, + HeraCompass = 0x85, + IceCompass = 0x86, + SkullCompass = 0x87, + MireCompass = 0x88, + PodCompass = 0x89, + SwampCompass = 0x8a, + AgaCompass = 0x8b, + DesertCompass = 0x8c, + EasternCompass = 0x8d, + HyruleCompass = 0x8e, + SewersCompass = 0x8f, + SkeletonKey = 0x90, + Reserved0x91 = 0x91, + GTBigKey = 0x92, + TurtleBigKey = 0x93, + ThievesBigKey = 0x94, + HeraBigKey = 0x95, + IceBigKey = 0x96, + SkullBigKey = 0x97, + MireBigKey = 0x98, + PodBigKey = 0x99, + SwampBigKey = 0x9a, + AgaBigKey = 0x9b, + DesertBigKey = 0x9c, + EasternBigKey = 0x9d, + HyruleBigKey = 0x9e, + SewersBigKey = 0x9f, + SewersKey = 0xa0, + HyruleKey = 0xa1, + EasternKey = 0xa2, + DesertKey = 0xa3, + AgaKey = 0xa4, + SwampKey = 0xa5, + PodKey = 0xa6, + MireKey = 0xa7, + SkullKey = 0xa8, + IceKey = 0xa9, + HeraKey = 0xaa, + ThievesKey = 0xab, + TurtleKey = 0xac, + GTKey = 0xad, + Reserved0xAE = 0xae, + Reserved0xAF = 0xaf, + Agahnim1 = 0x01ff, + Agahnim1Boss = 0x02ff, + Agahnim2 = 0x03ff, + Agahnim2Boss = 0x04ff, + BigBomb = 0x05ff, + Crystal1 = 0x06ff, + Crystal2 = 0x07ff, + Crystal3 = 0x08ff, + Crystal4 = 0x09ff, + Crystal5 = 0x0aff, + Crystal6 = 0x0bff, + Crystal7 = 0x0cff, + DesertBoss = 0x0dff, + EasternBoss = 0x0eff, + Smith = 0x0fff, + Ganon = 0x10ff, + GTArmosBoss = 0x11ff, + GTLanmolasBoss = 0x12ff, + GTMoldormBoss = 0x13ff, + HeraBoss = 0x14ff, + IceBoss = 0x15ff, + IceBlock = 0x16ff, + L1Shield = 0x17ff, + L1Sword = 0x18ff, + L2Shield = 0x19ff, + L2Sword = 0x1aff, + L3Shield = 0x1bff, + L3Sword = 0x1cff, + L4Sword = 0x1dff, + MireBoss = 0x1eff, + MiseryMireToken = 0x1fff, + MireSwitch = 0x20ff, + PodBoss = 0x21ff, + PurpleChest = 0x22ff, + SkullBoss = 0x23ff, + SwampBoss = 0x24ff, + ThievesBoss = 0x25ff, + TurtleBoss = 0x26ff, + TurtleRockToken = 0x27ff, + ProgressiveMagic = 0x28ff, +} + +lazy_static! { + pub static ref ITEMS: HashMap = { + use ItemId::*; + + let mut items: HashMap = HashMap::new(); + + items.insert(FightersSwordAndShield, Item::normal(FightersSwordAndShield, "Fighters Sword and Shield")); + items.insert(MasterSword1, Item::normal(MasterSword1, "Master Sword")); + items.insert(TemperedSword, Item::normal(TemperedSword, "Tempered Sword")); + items.insert(GoldenSword, Item::normal(GoldenSword, "Golden Sword")); + items.insert(FightersShield, Item::normal(FightersShield, "Fighters Shield")); + items.insert(FireShield, Item::normal(FireShield, "Fire Shield")); + items.insert(FireRod, Item::normal(FireRod, "Fire Rod")); + items.insert(IceRod, Item::normal(IceRod, "Ice Rod")); + items.insert(Hammer, Item::normal(Hammer, "Hammer")); + items.insert(Hookshot, Item::normal(Hookshot, "Hookshot")); + items.insert(Bow, Item::normal(Bow, "Bow")); + items.insert(Boomerang, Item::normal(Boomerang, "Boomerang")); + items.insert(Powder, Item::normal(Powder, "Magic Powder")); + items.insert(Bee, Item::normal(Bee, "Bee")); + items.insert(Bombos, Item::normal(Bombos, "Bombos")); + items.insert(Ether, Item::normal(Ether, "Ether")); + items.insert(Quake, Item::normal(Quake, "Quake")); + items.insert(Lamp, Item::normal(Lamp, "Lamp")); + items.insert(Shovel, Item::normal(Shovel, "Shovel")); + items.insert(Flute, Item::normal(Flute, "Flute")); + items.insert(Somaria, Item::normal(Somaria, "Cane of Somaria")); + items.insert(Bottle, Item::bottle(Bottle, "Bottle")); + items.insert(PieceOfHeart, Item::normal(PieceOfHeart, "Piece of Heart")); + items.insert(Byrna, Item::normal(Byrna, "Cane of Byrna")); + items.insert(Cape, Item::normal(Cape, "Magic Cape")); + items.insert(Mirror, Item::normal(Mirror, "Magic Mirror")); + items.insert(L1Gloves, Item::progressive(L1Gloves, "Power Gloves")); + items.insert(L2Gloves, Item::progressive(L2Gloves, "Titan Mitts")); + items.insert(Book, Item::normal(Book, "Book of Mudora")); + items.insert(Flippers, Item::normal(Flippers, "Zora's Flippers")); + items.insert(MoonPearl, Item::normal(MoonPearl, "Moon Pearl")); + items.insert(Crystal, Item::special(Crystal, "Crystal")); + items.insert(BugCatchingNet, Item::normal(BugCatchingNet, "Bug Catching Net")); + items.insert(BlueMail, Item::normal(BlueMail, "Blue Mail")); + items.insert(RedMail, Item::normal(RedMail, "Red Mail")); + items.insert(Key, Item::consumable(Key, "Key")); + items.insert(Compass, Item::special(Compass, "Compass")); + items.insert(HeartContainerNoAnimation, Item::normal(HeartContainerNoAnimation, "Heart Container (no animation)")); + items.insert(Bomb, Item::normal(Bomb, "Bomb")); + items.insert(ThreeBombs, Item::normal(ThreeBombs, "3 Bombs")); + items.insert(Mushroom, Item::consumable(Mushroom, "Mushroom")); + items.insert(MagicalBoomerang, Item::normal(MagicalBoomerang, "Magical Boomerang")); + items.insert(BottleRedPotion, Item::bottle(BottleRedPotion, "Bottle (Red Potion)")); + items.insert(BottleGreenPotion, Item::bottle(BottleGreenPotion, "Bottle (Green Potion)")); + items.insert(BottleBluePotion, Item::bottle(BottleBluePotion, "Bottle (Blue Potion)")); + items.insert(RedPotion, Item::special(RedPotion, "Red Potion")); + items.insert(GreenPotion, Item::special(GreenPotion, "Green Potion")); + items.insert(BluePotion, Item::special(BluePotion, "Blue Potion")); + items.insert(TenBombs, Item::normal(TenBombs, "10 Bombs")); + items.insert(BigKey, Item::special(BigKey, "Big Key")); + items.insert(DungeonMap, Item::special(DungeonMap, "Dungeon Map")); + items.insert(GreenRupee, Item::normal(GreenRupee, "Green Rupee")); + items.insert(BlueRupee, Item::normal(BlueRupee, "Blue Rupee")); + items.insert(RedRupee, Item::normal(RedRupee, "Red Rupee")); + items.insert(PendantGreen, Item::special(PendantGreen, "Pendant of Courage")); + items.insert(PendantRed, Item::special(PendantRed, "Pendant of Wisdom")); + items.insert(PendantBlue, Item::special(PendantBlue, "Pendant of Power")); + items.insert(BowAndArrows, Item::special(BowAndArrows, "Bow and Arrows")); + items.insert(BowAndSilverArrows, Item::special(BowAndSilverArrows, "Bow and Silver Arrows")); + items.insert(BottleBee, Item::bottle(BottleBee, "Bottle (Bee)")); + items.insert(BottleFairy, Item::bottle(BottleFairy, "Bottle, (Fairy)")); + items.insert(HeartContainer, Item::normal(HeartContainer, "Heart Container")); + items.insert(HeartContainerRefill, Item::normal(HeartContainerRefill, "Heart Container (refill)")); + items.insert(OneHundredRupees, Item::normal(OneHundredRupees, "100 Rupees")); + items.insert(FiftyRupees, Item::normal(FiftyRupees, "50 Rupees")); + items.insert(Heart, Item::normal(Heart, "Heart")); + items.insert(SingleArrow, Item::normal(SingleArrow, "Single Arrow")); + items.insert(TenArrows, Item::normal(TenArrows, "10 Arrows")); + items.insert(SmallMagic, Item::normal(SmallMagic, "Small Magic Refill")); + items.insert(ThreeHundredRupees, Item::normal(ThreeHundredRupees, "300 Rupees")); + items.insert(RedRupee2, Item::normal(RedRupee2, "Red Rupee")); + items.insert(BottleGoldenBee, Item::bottle(BottleGoldenBee, "Bottle (Golden Bee)")); + items.insert(FightersSword, Item::normal(FightersSword, "Fighter's Sword")); + items.insert(FluteActive, Item::normal(FluteActive, "Flute (activated)")); + items.insert(Boots, Item::normal(Boots, "Pegasus Boots")); + items.insert(MaxBombs, Item::normal(MaxBombs, "Max Bomb Upgrade (50)")); + items.insert(MaxArrows, Item::normal(MaxArrows, "Max Arrow Upgrade (70)")); + items.insert(HalfMagic, Item::normal(HalfMagic, "Half Magic")); + items.insert(QuarterMagic, Item::normal(QuarterMagic, "Quarter Magic")); + items.insert(MasterSword2, Item::normal(MasterSword2, "Master Sword")); + items.insert(BombUpgrade5, Item::normal(BombUpgrade5, "Bomb Upgrade (5)")); + items.insert(BombUpgrade10, Item::normal(BombUpgrade10, "Bomb Upgrade (10)")); + items.insert(ArrowUpgrade5, Item::normal(ArrowUpgrade5, "Arrow Upgrade (5)")); + items.insert(ArrowUpgrade10, Item::normal(ArrowUpgrade10, "Arrow Upgrade (10)")); + items.insert(Programmable1, Item::normal(Programmable1, "Programmable 1")); + items.insert(Programmable2, Item::normal(Programmable2, "Programmable 2")); + items.insert(Programmable3, Item::normal(Programmable3, "Programmable 3")); + items.insert(SilverArrows, Item::normal(SilverArrows, "Silver Arrows")); + items.insert(Rupoor, Item::normal(Rupoor, "Rupoor")); + items.insert(Nothing, Item::normal(Nothing, "Nothing (Null Item)")); + items.insert(RedClock, Item::consumable(RedClock, "Red Clock")); + items.insert(BlueClock, Item::consumable(BlueClock, "Blue Clock")); + items.insert(GreenClock, Item::consumable(GreenClock, "Green Clock")); + items.insert(ProgressiveSword, Item::progressive(ProgressiveSword, "Progressive Sword")); + items.insert(ProgressiveShield, Item::progressive(ProgressiveShield, "Progressive Shield")); + items.insert(ProgressiveArmor, Item::progressive(ProgressiveArmor, "Progressive Armor")); + items.insert(ProgressiveGloves, Item::progressive(ProgressiveGloves, "Progressive Gloves")); + items.insert(UniqueRngItem, Item::special(UniqueRngItem, "Unique RNG Item (RNG Pool Single)")); + items.insert(NonUniqueRngItem, Item::special(NonUniqueRngItem, "Non-unique RNG Item (RNG Pool Multi)")); + items.insert(ProgressiveBow, Item::normal(ProgressiveBow, "Progressive Bow")); // *NOT* ProgressiveItem??? + items.insert(ProgressiveBow2, Item::normal(ProgressiveBow2, "Progressive Bow 2")); + items.insert(Triforce, Item::special(Triforce, "Triforce (Single Goal Item)")); + items.insert(PowerStar, Item::special(PowerStar, "Power Star (Multi Goal Item)")); + items.insert(TriforcePiece, Item::special(TriforcePiece, "Triforce Piece")); + items.insert(LightWorldMap, Item::special(LightWorldMap, "Light World Map")); + items.insert(DarkWorldMap, Item::special(DarkWorldMap, "Dark World Map")); + items.insert(GTMap, Item::special(GTMap, "Ganon's Tower Map")); + items.insert(TurtleMap, Item::special(TurtleMap, "Turtle Rock Map")); + items.insert(ThievesMap, Item::special(ThievesMap, "Thieves' Town Map")); + items.insert(HeraMap, Item::special(HeraMap, "Tower of Hera Map")); + items.insert(IceMap, Item::special(IceMap, "Ice Palace Map")); + items.insert(SkullMap, Item::special(SkullMap, "Skull Woods Map")); + items.insert(MireMap, Item::special(MireMap, "Misery Mire Map")); + items.insert(PodMap, Item::special(PodMap, "Palace of Darkness Map")); + items.insert(SwampMap, Item::special(SwampMap, "Swamp Palace Map")); + items.insert(AgaMap, Item::special(AgaMap, "Agahnim's Tower Map")); + items.insert(DesertMap, Item::special(DesertMap, "Desert Palace Map")); + items.insert(EasternMap, Item::special(EasternMap, "Eastern Palace Map")); + items.insert(HyruleMap, Item::special(HyruleMap, "Hyrule Castle Map")); + items.insert(SewersMap, Item::special(SewersMap, "Sewers Map")); // not the same as HyruleMap? + items.insert(GTCompass, Item::special(GTCompass, "Ganon's Tower Compass")); + items.insert(TurtleCompass, Item::special(TurtleCompass, "Turtle Rock Compass")); + items.insert(ThievesCompass, Item::special(ThievesCompass, "Thieves' Town Compass")); + items.insert(HeraCompass, Item::special(HeraCompass, "Tower of Hera Compass")); + items.insert(IceCompass, Item::special(IceCompass, "Ice Palace Compass")); + items.insert(SkullCompass, Item::special(SkullCompass, "Skull Woods Compass")); + items.insert(MireCompass, Item::special(MireCompass, "Misery Mire Compass")); + items.insert(PodCompass, Item::special(PodCompass, "Palace of Darkness Compass")); + items.insert(SwampCompass, Item::special(SwampCompass, "Swamp Palace Compass")); + items.insert(AgaCompass, Item::special(AgaCompass, "Agahnim's Tower Compass")); + items.insert(DesertCompass, Item::special(DesertCompass, "Desert Palace Compass")); + items.insert(EasternCompass, Item::special(EasternCompass, "Eastern Palace Compass")); + items.insert(HyruleCompass, Item::special(HyruleCompass, "Hyrule Castle Compass")); + items.insert(SewersCompass, Item::special(SewersCompass, "Sewers Compass")); // not the same as HyruleCompass? + items.insert(SkeletonKey, Item::special(SkeletonKey, "Skeleton Key")); + items.insert(Reserved0x91, Item::special(Reserved0x91, "")); + items.insert(GTBigKey, Item::special(GTBigKey, "Ganon's Tower Big Key")); + items.insert(TurtleBigKey, Item::special(TurtleBigKey, "Turtle Rock Big Key")); + items.insert(ThievesBigKey, Item::special(ThievesBigKey, "Thieves' Town Big Key")); + items.insert(HeraBigKey, Item::special(HeraBigKey, "Tower of Hera Big Key")); + items.insert(IceBigKey, Item::special(IceBigKey, "Ice Palace Big Key")); + items.insert(SkullBigKey, Item::special(SkullBigKey, "Skull Woods Big Key")); + items.insert(MireBigKey, Item::special(MireBigKey, "Misery Mire Big Key")); + items.insert(PodBigKey, Item::special(PodBigKey, "Palace of Darkness Big Key")); + items.insert(SwampBigKey, Item::special(SwampBigKey, "Swamp Palace Big Key")); + items.insert(AgaBigKey, Item::special(AgaBigKey, "Agahnim's Tower Big Key")); + items.insert(DesertBigKey, Item::special(DesertBigKey, "Desert Palace Big Key")); + items.insert(EasternBigKey, Item::special(EasternBigKey, "Eastern Palace Big Key")); + items.insert(HyruleBigKey, Item::special(HyruleBigKey, "Hyrule Castle Big Key")); + items.insert(SewersBigKey, Item::special(SewersBigKey, "Sewers Big Key")); // not the same as HyruleBigKey? + items.insert(SewersKey, Item::consumable(SewersKey, "Sewers Key")); + items.insert(HyruleKey, Item::consumable(HyruleKey, "Hyrule Castle Key")); // not the same as SewersKey? + items.insert(EasternKey, Item::consumable(EasternKey, "Eastern Palace Key")); + items.insert(DesertKey, Item::consumable(DesertKey, "Desert Palace Key")); + items.insert(AgaKey, Item::consumable(AgaKey, "Agahnim's Tower Key")); + items.insert(SwampKey, Item::consumable(SwampKey, "Swamp Palace Key")); + items.insert(PodKey, Item::consumable(PodKey, "Palace of Darkness Key")); + items.insert(MireKey, Item::consumable(MireKey, "Misery Mire Key")); + items.insert(SkullKey, Item::consumable(SkullKey, "Skull Woods Key")); + items.insert(IceKey, Item::consumable(IceKey, "Ice Palace Key")); + items.insert(HeraKey, Item::consumable(HeraKey, "Tower of Hera Key")); + items.insert(ThievesKey, Item::consumable(ThievesKey, "Thieves' Town Key")); + items.insert(TurtleKey, Item::consumable(TurtleKey, "Turtle Rock Key")); + items.insert(GTKey, Item::consumable(GTKey, "Ganon's Tower Key")); + items.insert(Reserved0xAE, Item::special(Reserved0xAE, "")); + items.insert(Reserved0xAF, Item::special(Reserved0xAF, "")); + items.insert(Agahnim1, Item::special(Agahnim1, "Agahnim 1 Defeated")); + items.insert(Agahnim1Boss, Item::special(Agahnim1Boss, "Agahnim Boss")); + items.insert(Agahnim2, Item::special(Agahnim2, "Agahnim 2 Defeated")); + items.insert(Agahnim2Boss, Item::special(Agahnim2Boss, "Agahnim 2 Boss")); + items.insert(BigBomb, Item::special(BigBomb, "Big Bomb")); + items.insert(Crystal1, Item::special(Crystal1, "Crystal 1")); + items.insert(Crystal2, Item::special(Crystal2, "Crystal 2")); + items.insert(Crystal3, Item::special(Crystal3, "Crystal 3")); + items.insert(Crystal4, Item::special(Crystal4, "Crystal 4")); + items.insert(Crystal5, Item::special(Crystal5, "Crystal 5")); + items.insert(Crystal6, Item::special(Crystal6, "Crystal 6")); + items.insert(Crystal7, Item::special(Crystal7, "Crystal 7")); + items.insert(DesertBoss, Item::special(DesertBoss, "Desert Boss")); + items.insert(EasternBoss, Item::special(EasternBoss, "Eastern Boss")); + items.insert(Smith, Item::special(Smith, "Frog Smith")); + items.insert(Ganon, Item::special(Ganon, "Ganon")); + items.insert(GTArmosBoss, Item::special(GTArmosBoss, "GT Armos Boss")); + items.insert(GTLanmolasBoss, Item::special(GTLanmolasBoss, "GT Lanmolas Boss")); + items.insert(GTMoldormBoss, Item::special(GTMoldormBoss, "GT Moldorm Boss")); + items.insert(HeraBoss, Item::special(HeraBoss, "Hera Boss")); + items.insert(IceBoss, Item::special(IceBoss, "Ice Boss")); + items.insert(IceBlock, Item::special(IceBlock, "Ice Palace Block")); + items.insert(L1Shield, Item::progressive(L1Shield, "L1 Shield")); + items.insert(L1Sword, Item::progressive(L1Sword, "L1 Sword")); + items.insert(L2Shield, Item::progressive(L2Shield, "L2 Shield")); + items.insert(L2Sword, Item::progressive(L2Sword, "L2 Sword")); + items.insert(L3Shield, Item::progressive(L3Shield, "L3 Shield")); + items.insert(L3Sword, Item::progressive(L3Sword, "L3 Sword")); + items.insert(L4Sword, Item::progressive(L4Sword, "L4 Sword")); + items.insert(MireBoss, Item::special(MireBoss, "Mire Boss")); + items.insert(MiseryMireToken, Item::special(MiseryMireToken, "Misery Mire Entrance Token")); + items.insert(MireSwitch, Item::special(MireSwitch, "Misery Mire Switch")); + items.insert(PodBoss, Item::special(PodBoss, "PoD Boss")); + items.insert(PurpleChest, Item::special(PurpleChest, "Purple Chest")); + items.insert(SkullBoss, Item::special(SkullBoss, "Skull Boss")); + items.insert(ThievesBoss, Item::special(ThievesBoss, "Thieves Boss")); + items.insert(TurtleBoss, Item::special(TurtleBoss, "Turtle Boss")); + items.insert(ProgressiveMagic, Item::special(ProgressiveMagic, "Progressive Magic")); + + items + }; +} diff --git a/enemize/src/graph/mod.rs b/enemize/src/graph/mod.rs new file mode 100644 index 0000000..eb2d62b --- /dev/null +++ b/enemize/src/graph/mod.rs @@ -0,0 +1,3 @@ +pub mod dungeons; +pub mod item; +pub mod requirement; diff --git a/enemize/src/graph/requirement.rs b/enemize/src/graph/requirement.rs new file mode 100644 index 0000000..2bbece0 --- /dev/null +++ b/enemize/src/graph/requirement.rs @@ -0,0 +1,16 @@ +use std::collections::HashSet; + +use super::item::Item; + +pub struct Requirement(HashSet); + +impl Requirement { + pub fn new(items: &[Item]) -> Requirement { + Requirement(HashSet::from_iter(items.into_iter().map(|i| *i))) + } + + pub fn is_met(&self, items: &[Item]) -> bool { + let items = HashSet::from_iter(items.into_iter().map(|i| *i)); + self.0.is_subset(&items) + } +} diff --git a/enemize/src/lib.rs b/enemize/src/lib.rs index a499767..d130be2 100644 --- a/enemize/src/lib.rs +++ b/enemize/src/lib.rs @@ -10,6 +10,7 @@ use crate::rom::RomData; pub mod asar; pub mod bosses; pub mod constants; +pub mod graph; pub mod option_flags; pub mod randomize; pub mod rom; From 6a56e52e58c725e6ea4652ae39125f282284677a Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Sun, 12 Jun 2022 11:21:24 -0400 Subject: [PATCH 27/29] Add room data. --- enemize/src/graph/dungeons.rs | 920 ++++++++++++++++++++++++++++++++++ 1 file changed, 920 insertions(+) diff --git a/enemize/src/graph/dungeons.rs b/enemize/src/graph/dungeons.rs index 5d5bfbc..4d25b9a 100644 --- a/enemize/src/graph/dungeons.rs +++ b/enemize/src/graph/dungeons.rs @@ -142,3 +142,923 @@ lazy_static! { keys }; } + +#[derive(Clone, Copy, Eq, PartialEq, Hash)] +#[repr(u16)] +pub enum Room { + Ganon = 0, + HyruleCastleNorthCorridor, + HyruleCastleSwitchRoom, + HoulihanRoom, + TurtleRockCrystalRollerRoom, + EmptyCloneRoom0x05, + SwampPalaceArrghus, + TowerOfHeraMoldorm, + CaveHealingFairy, + PalaceOfDarkness0x09, + PalaceOfDarknessStalfossTrapRoom, + PalaceOfDarknessTurtleRoom, + GanonsTowerEntranceRoom, + GanonsTowerAgahnim2, + IcePalaceEntranceRoom, + EmptyCloneRoom0x0F, + GanonEvacuationRoute, + HyruleCastleBombableStockRoom, + Sanctuary, + TurtleRockHokkuBokkuKeyRoom2, + TurtleRockBigKeyRoom, + TurtleRock0x15, + SwampPalaceSwimmingTreadmill, + TowerOfHeraMoldormFallRoom, + Cave0x18BigFairyDropEntrance, + PalaceOfDarknessDarkMaze, + PalaceOfDarknessBigChestRoom, + PalaceOfDarknessMimicsMovingWallRoom, + GanonsTowerIceArmos, + GanonsTowerFinalHallway, + IcePalaceBombFloorBariRoom, + IcePalacePengatorBigKeyRoom, + AgahnimsTowerAgahnim, + HyruleCastleKeyRatRoom, + HyruleCastleSewerTextTriggerRoom, + TurtleRockWestExitToBalcony, + TurtleRockDoubleHokkuBokkuBigChestRoom, + EmptyCloneRoom0x25, + SwampPalaceStatueRoom, + TowerOfHeraBigChest, + SwampPalaceEntranceRoom, + SkullWoodsMothula, + PalaceOfDarknessBigHubRoom, + PalaceOfDarknessMapChestFairyRoom, + CaveHookshotCaveBackdoor, + EmptyCloneRoom0x2D, + IcePalaceCompassRoom, + CaveKakarikoWellHP, + AgahnimsTowerMaidenSacrificeChamber, + TowerOfHeraHardhatBeetleRoom, + HyruleCastleSewerKeyChestRoom, + DesertPalaceLanmolas, + SwampPalacePushBlockPuzzlePreBigKeyRoom, + SwampPalaceBigKeyBsRoom, + SwampPalaceBigChestRoom, + SwampPalaceMapChestWaterFillRoom, + SwampPalaceKeyPotRoom, + SkullWoodsGibdoKeyMothulaHoleRoom, + PalaceOfDarknessBombableFloorRoom, + PalaceOfDarknessSpikeBlockConveyorRoom, + CaveHookshotCave, + GanonsTowerTorchRoom2, + IcePalaceStalfosKnightConveyorHellway, + IcePalaceMapChestRoom, + AgahnimsTowerFinalBridgeRoom, + HyruleCastleFirstDarkRoom, + HyruleCastle6RopesRoom, + DesertPalaceTorchPuzzleMovingWallRoom, + ThievesTownBigChestRoom, + ThievesTownJailCellsRoom, + SwampPalaceCompassChestRoom, + EmptyCloneRoom0x47, + EmptyCloneRoom0x48, + SkullWoodsGibdoTorchPuzzleRoom, + PalaceOfDarknessEntranceRoom, + PalaceOfDarknessWarpsSouthMimicRoom, + GanonsTowerMiniHelmasaurConveyorRoom, + GanonsTowerMoldormRoom, + IcePalaceBombJumpRoom, + IcePalaceCloneRoomFairyRoom, + HyruleCastleWestCorridor, + HyruleCastleThroneRoom, + HyruleCastleEastCorridor, + DesertPalacePopos2BeamosHellwayRoom, + SwampPalaceUpstairsPitsRoom, + CastleSecretEntranceUncleRoom, + SkullWoodsKeyPotTrapRoom, + SkullWoodsBigKeyRoom, + SkullWoodsBigChestRoom, + SkullWoodsFinalSectionEntranceRoom, + PalaceOfDarknessHelmasaurKing, + GanonsTowerSpikePitRoom, + GanonsTowerGanonBallZ, + GanonsTowerGauntlet123, + IcePalaceLonelyFirebar, + IcePalaceHiddenChestSpikeFloorRoom, + HyruleCastleWestEntranceRoom, + HyruleCastleMainEntranceRoom, + HyruleCastleEastEntranceRoom, + DesertPalaceFinalSectionEntranceRoom, + ThievesTownWestAtticRoom, + ThievesTownEastAtticRoom, + SwampPalaceHiddenChestHiddenDoorRoom, + SkullWoodsCompassChestRoom, + SkullWoodsKeyChestTrapRoom, + EmptyCloneRoom0x69, + PalaceOfDarknessRupeeRoom, + GanonsTowerMimicsRoom, + GanonsTowerLanmolasRoom, + GanonsTowerGauntlet45, + IcePalacePengatorsRoom, + EmptyCloneRoom0x6F, + HyruleCastleSmallCorridorToJailCells, + HyruleCastleBoomerangChestRoom, + HyruleCastleMapChestRoom, + DesertPalaceBigChestRoom, + DesertPalaceMapChestRoom, + DesertPalaceBigKeyChestRoom, + SwampPalaceWaterDrainRoom, + TowerOfHeraEntranceRoom, + EmptyCloneRoom0x78, + EmptyCloneRoom0x79, + EmptyCloneRoom0x7A, + GanonsTowerMisc, + GanonsTowerEastSideCollapsingBridgeExplodingWallRoom, + GanonsTowerWinderWarpMazeRoom, + IcePalaceHiddenChestBombableFloorRoom, + IcePalaceBigSpikeTrapsRoom, + HyruleCastleJailCellRoom, + HyruleCastleNextToChasmRoom, + HyruleCastleBasementChasmRoom, + DesertPalaceWestEntranceRoom, + DesertPalaceMainEntranceRoom, + DesertPalaceEastEntranceRoom, + EmptyCloneRoom0x86, + TowerOfHeraTileRoom, + EmptyCloneRoom0x88, + EasternPalaceFairyRoom, + EmptyCloneRoom0x8A, + GanonsTowerBlockPuzzleSpikeSkipMapChestRoom, + GanonsTowerEastAndWestDownstairsBigChestRoom, + GanonsTowerTileTorchPuzzleRoom, + IcePalaceBlobsWithTetrisBarrier, + EmptyCloneRoom0x8F, + MiseryMireVitreous, + MiseryMireFinalSwitchRoom, + MiseryMireDarkBombWallSwitchesRoom, + MiseryMireDarkCaneFLoorSwitchPuzzleRoom, + EmptyCloneRoom0x94, + GanonsTowerFinalCollapsingBridgeRoom, + GanonsTowerTorches1Room, + MiseryMireTorchPuzzleMovingWallRoom, + MiseryMireEntranceRoom, + EasternPalaceEyegoreKeyRoom, + EmptyCloneRoom0x9A, + GanonsTowerManySpikesWarpMazeRoom, + GanonsTowerInvisibleFloorMazeRoom, + GanonsTowerCompassChestInvisibleFloorRoom, + IcePalaceBigChestRoom, + IcePalaceIceFloorPotsKeyAndSwitch, + MiseryMirePreVitreousRoom, + MiseryMireFishRoom, + MiseryMireBridgeKeyChestRoom, + MiseryMireEmptyLConnectingRoom, + TurtleRockTrinexx, + GanonsTowerWizzrobesRooms, + GanonsTowerMoldormFallRoom, + TowerOfHeraFairyRoom, + EasternPalaceStalfosSpawnRoom, + EasternPalaceBigChestRoom, + EasternPalaceMapChestRoom, + ThievesTownMovingSpikesKeyPotRoom, + ThievesTownBlindTheThief, + EmptyCloneRoom0xAD, + IcePalaceIceTRoom, + IcePalaceIceBridgeRoom, + AgahnimsTowerCircleOfPots, + MiseryMireHourglassRoom, + MiseryMireSlugRoom, + MiseryMireSpikeKeyChestRoom, + TurtleRockPreTrinexxRoom, + TurtleRockDarkMaze, + TurtleRockChainChompsRoom, + TurtleRockMapChestKeyChestRollerRoom, + EasternPalaceBigKeyRoom, + EasternPalaceLobbyCannonballsRoom, + EasternPalaceDarkAntifairyKeyPotRoom, + ThievesTownHellway, + ThievesTownConveyorToilet, + EmptyCloneRoom0xBD, + IcePalaceBlockPuzzleRoom, + IcePalaceCloneRoomSwitchRoom, + AgahnimsTowerDarkBridgeRoom, + MiseryMireCompassChestTileRoom, + MiseryMireBigHubRoom, + MiseryMireBigChestRoom, + TurtleRockFinalCrystalSwitchPuzzleRoom, + TurtleRockLaserBridge, + TurtleRockSomariaHub, + TurtleRockTorchPuzzle, + EasternPalaceArmosKnights, + EasternPalaceEntranceRoom, + UnknownRoom, + ThievesTownNorthWestEntranceRoom, + ThievesTownNorthEastEntranceRoom, + EmptyCloneRoom0xCD, + IcePalaceHoleToKholdstareRoom, + EmptyCloneRoom0xCF, + AgahnimsTowerDarkMaze, + MiseryMireConveyorSlugBigKeyRoom, + MiseryMireWizzrobesRoom, + EmptyCloneRoom0xD3, + EmptyCloneRoom0xD4, + TurtleRockLaserKeyRoom, + TurtleRockEntranceRoom, + EmptyCloneRoom0xD7, + EasternPalacePreArmosKnightsRoom, + EasternPalaceCannonballRoom, + EasternPalacePotSwitchRoom, + ThievesTownSouthWestEntranceRoom, + ThievesTownSouthEastEntranceRoom, + EmptyCloneRoom0xDD, + IcePalaceKholdstare, + CaveBackwardsDeathMountainTopFloor, + AgahnimsTowerEntranceRoom, + CaveLostWoodsHP, + CaveLumberjacksTreeHP, + CaveMagicBat, + CaveLostOldManHouse, + CaveLostOldManHouseBack, + Cave0xE6, + Cave0xE7, + CaveSuperBunnyTop, + EmptyCloneRoom0xE9, + CaveSpectacleRockHP, + CaveBumperCaveTop, + EmptyCloneRoom0xEC, + Cave0xED, + CaveSpiralCave, + CaveCrystalSwitch5ChestsRoom, + CaveLostOldManStartingCaveBottom, + CaveLostOldManStartingCaveTop, + HouseOldWomanNextDoor, + HouseOldWomanSahasrahlasWifeMaybe, + HouseAngryBrothersWest, + HouseAngryBrothersEast, + EmptyCloneRoom0xF6, + EmptyCloneRoom0xF7, + CaveSuperBunnyBottom, + CaveSpectacleRockExit, + CaveSpectacleRockPrizeEntrance, + CaveBumperCaveBottom, + EmptyCloneRoom0xFC, + Cave0xFD, + CaveSpiralCaveExit, + CaveParadoxCaveMiddleEntrance, + ShopInLostWoods0x100, + ScaredLadyHouses, + SickKid, + InnBushHouse, + LinksHouse, + ShabadooHouse, + ChestGameBombHouse, + LibraryBombFarmRoom, + ChickenHouse, + WitchHut, + Aginah, + Dam, + MimicCave, + CaveOutsideMiseryMire, + Cave0x10E, + Shop0x10F, + Shop0x110, + ArcherGame, + CaveShop0x112, + KingsTomb, + WishingWellCave0x114, + WishingWellBigFairy, + FatFairy, + SpikeCave, + Shop0x118, + BlindsHouse, + Mutant, + MirrorCaveGroveAndTomb, + BombShop, + BlindsBasement, + HypeCave, + Shop0x11F, + IceRodCave, + SmithHouse, + FortuneTellers, + MiniMoldormCave, + UnknownCaveBonkCave, + Cave0x125, + CheckerBoardCave, + HammerPegCave, +} + +use Room::*; + +lazy_static! { + pub static ref ROOM_NAMES: HashMap = { + + let names = [ + (Ganon, "Ganon"), + (HyruleCastleNorthCorridor, "Hyrule Castle (North Corridor)"), + (HyruleCastleSwitchRoom, "Hyrule Castle (Switch Room)"), + (HoulihanRoom, "Houlihan Room"), + (TurtleRockCrystalRollerRoom, "Turtle Rock (Crystal Roller Room)"), + (EmptyCloneRoom0x05, "Empty Clone Room"), + (SwampPalaceArrghus, "Swamp Palace (Arrghus[Boss])"), + (TowerOfHeraMoldorm, "Tower of Hera (Moldorm[Boss])"), + (CaveHealingFairy, "Cave (Healing Fairy)"), + (PalaceOfDarkness0x09, "Palace of Darkness"), + (PalaceOfDarknessStalfossTrapRoom, "Palace of Darkness (Stalfoss Trap Room)"), + (PalaceOfDarknessTurtleRoom, "Palace of Darkness (Turtle Room)"), + (GanonsTowerEntranceRoom, "Ganon's Tower (Entrance Room)"), + (GanonsTowerAgahnim2, "Ganaon's Tower (Agahnim2[Boss])"), + (IcePalaceEntranceRoom, "Ice Palace (Entrance Room)"), + (EmptyCloneRoom0x0F, "Empty Clone Room"), + (GanonEvacuationRoute, "Ganon Evacuation Route"), + (HyruleCastleBombableStockRoom, "Hyrule Castle (Bombable Stock Room)"), + (Sanctuary, "Sanctuary"), + (TurtleRockHokkuBokkuKeyRoom2, "Turtle Rock (Hokku-Bokku Key Room 2)"), + (TurtleRockBigKeyRoom, "Turtle Rock (Big Key Room)"), + (TurtleRock0x15, "Turtle Rock"), + (SwampPalaceSwimmingTreadmill, "Swamp Palace (Swimming Treadmill)"), + (TowerOfHeraMoldormFallRoom, "Tower of Hera (Moldorm Fall Room)"), + (Cave0x18BigFairyDropEntrance, "Big Fairy Drop Entrance Cave"), + (PalaceOfDarknessDarkMaze, "Palace of Darkness (Dark Maze)"), + (PalaceOfDarknessBigChestRoom, "Palace of Darkness (Big Chest Room)"), + (PalaceOfDarknessMimicsMovingWallRoom, "Palace of Darkness (Mimics / Moving Wall Room"), + (GanonsTowerIceArmos, "Ganon's Tower (Ice Armos)"), + (GanonsTowerFinalHallway, "Ganon's Tower (Final Hallway)"), + (IcePalaceBombFloorBariRoom, "Ice Palace (Bomb Floor / Bari Room)"), + (IcePalacePengatorBigKeyRoom, "Ice Palace (Pengator / Big Key Room)"), + (AgahnimsTowerAgahnim, "Agahnim's Tower (Agahnim[Boss])"), + (HyruleCastleKeyRatRoom, "Hyrule Castle (Key Rat Room)"), + (HyruleCastleSewerTextTriggerRoom, "Hyrule Castle (Sewer Text Trigger Room)"), + (TurtleRockWestExitToBalcony, "Turtle Rock (West Exit to Balcony)"), + (TurtleRockDoubleHokkuBokkuBigChestRoom, "Turtle Rock (Double Hokku-Bokku / Big Chest Room)"), + (EmptyCloneRoom0x25, "Empty Clone Room"), + (SwampPalaceStatueRoom, "Swamp Palace (Statue Room)"), + (TowerOfHeraBigChest, "Tower of Hera (Big Chest)"), + (SwampPalaceEntranceRoom, "Swamp Palace (Entrance Room)"), + (SkullWoodsMothula, "Skull Woods (Mothula[Boss])"), + (PalaceOfDarknessBigHubRoom, "Palace of Darkness (Big Hub Room)"), + (PalaceOfDarknessMapChestFairyRoom, "Palace of Darkness (Map Chest / Fairy Room)"), + (CaveHookshotCaveBackdoor, "Hookshot Cave Backdoor (Big Fairy)"), + (EmptyCloneRoom0x2D, "Empty Clone Room"), + (IcePalaceCompassRoom, "Ice Palace (Compass Room)"), + (CaveKakarikoWellHP, "Kakariko Well HP"), + (AgahnimsTowerMaidenSacrificeChamber, "Agahnim's Tower (Maiden Sacrifice Chamber)"), + (TowerOfHeraHardhatBeetleRoom, "Tower of Hera (Hardhat Beetles Room)"), + (HyruleCastleSewerKeyChestRoom, "Hyrule Castle (Sewer Key Chest Room)"), + (DesertPalaceLanmolas, "Desert Palace (Lanmolas[Boss])"), + (SwampPalacePushBlockPuzzlePreBigKeyRoom, "Swamp Palace (Push Block Puzzle / Pre-Big-Key Room"), + (SwampPalaceBigKeyBsRoom, "Swamp Palace (Big Key / BS Room)"), + (SwampPalaceBigChestRoom, "Swamp Palace (Big Chest Room)"), + (SwampPalaceMapChestWaterFillRoom, "Swamp Palace (Map Chest / Water Fill Room)"), + (SwampPalaceKeyPotRoom, "Swamp Palace (Key Pot Room)"), + (SkullWoodsGibdoKeyMothulaHoleRoom, "Skull Woods (Gibdo Key / Mothula Hole Room)"), + (PalaceOfDarknessBombableFloorRoom, "Palace of Darkness (Bombable Floor Room)"), + (PalaceOfDarknessSpikeBlockConveyorRoom, "Palace of Darkness (Spike Block / Conveyor Room)"), + (CaveHookshotCave, "Hookshot Cave"), + (GanonsTowerTorchRoom2, "Ganon's Tower (Torch Room 2)"), + (IcePalaceStalfosKnightConveyorHellway, "Ice Palace (Stalfos Knight / Conveyor Hellway)"), + (IcePalaceMapChestRoom, "Ice Palace (Map Chest Room)"), + (AgahnimsTowerFinalBridgeRoom, "Agahnim's Tower (Final Bridge Room)"), + (HyruleCastleFirstDarkRoom, "Hyrule Castle (First Dark Room)"), + (HyruleCastle6RopesRoom, "Hyrule Castle (6 Ropes Room)"), + (DesertPalaceTorchPuzzleMovingWallRoom, "Desert Palace (Torch Puzzle / Moving Wall Room)"), + (ThievesTownBigChestRoom, "Thieves' Town (Big Chest Room)"), + (ThievesTownJailCellsRoom, "Thieves' Town (Jail Cells Room)"), + (SwampPalaceCompassChestRoom, "Swamp Palace (Compass Chest Room)"), + (EmptyCloneRoom0x47, "Empty Clone Room"), + (EmptyCloneRoom0x48, "Empty Clone Room"), + (SkullWoodsGibdoTorchPuzzleRoom, "Skull Woods (Gibdo Torch Puzzle Room)"), + (PalaceOfDarknessEntranceRoom, "Palace of Darkness (Entrance Room)"), + (PalaceOfDarknessWarpsSouthMimicRoom, "Palace of Darkness (Warps / South Mimic Room)"), + (GanonsTowerMiniHelmasaurConveyorRoom, "Ganon's Tower (Mini-Helmasaur Conveyor Room)"), + (GanonsTowerMoldormRoom, "Ganon's Tower (Moldorm Room)"), + (IcePalaceBombJumpRoom, "Ice Palace (Bomb Jump Room)"), + (IcePalaceCloneRoomFairyRoom, "Ice Palace Clone Room (Fairy Room)"), + (HyruleCastleWestCorridor, "Hyrule Castle (West Corridor)"), + (HyruleCastleThroneRoom, "Hyrule Castle (Throne Room)"), + (HyruleCastleEastCorridor, "Hyrule Castle (East Corridor)"), + (DesertPalacePopos2BeamosHellwayRoom, "Desert Palace (Popos 2 / Beamos Hellway Room)"), + (SwampPalaceUpstairsPitsRoom, "Swamp Palace (Upstairs Pits Room)"), + (CastleSecretEntranceUncleRoom, "Castle Secret Entrance / Uncle Room"), + (SkullWoodsKeyPotTrapRoom, "Skull Woods (Key Pot / Trap Room)"), + (SkullWoodsBigKeyRoom, "Skull Woods (Big Key Room)"), + (SkullWoodsBigChestRoom, "Skull Woods (Big Chest Room)"), + (SkullWoodsFinalSectionEntranceRoom, "Skull Woods (Final Section Entrance Room)"), + (PalaceOfDarknessHelmasaurKing, "Palace of Darkness (Helmasaur King[Boss])"), + (GanonsTowerSpikePitRoom, "Ganon's Tower (Spike Pit Room)"), + (GanonsTowerGanonBallZ, "Ganon's Tower (Ganon-Ball Z)"), + (GanonsTowerGauntlet123, "Ganon's Tower (Gauntlet 1/2/3)"), + (IcePalaceLonelyFirebar, "Ice Palace (Lonely Firebar)"), + (IcePalaceHiddenChestSpikeFloorRoom, "Ice Palace (Hidden Chest / Spike Floor Room)"), + (HyruleCastleWestEntranceRoom, "Hyrule Castle (West Entrance Room)"), + (HyruleCastleMainEntranceRoom, "Hyrule Castle (Main Entrance Room)"), + (HyruleCastleEastEntranceRoom, "Hyrule Castle (East Entrance Room)"), + (DesertPalaceFinalSectionEntranceRoom, "Desert Palace (Final Section Entrance Room)"), + (ThievesTownWestAtticRoom, "Thieves' Town (West Attic Room)"), + (ThievesTownEastAtticRoom, "Thieves' Town (East Attic Room)"), + (SwampPalaceHiddenChestHiddenDoorRoom, "Swamp Palace (Hidden Chest / Hidden Door Room)"), + (SkullWoodsCompassChestRoom, "Skull Woods (Compass Chest Room)"), + (SkullWoodsKeyChestTrapRoom, "Skull Woods (Key Chest / Trap Room)"), + (EmptyCloneRoom0x69, "Empty Clone Room"), + (PalaceOfDarknessRupeeRoom, "Palace of Darkness (Rupee Room)"), + (GanonsTowerMimicsRoom, "Ganon's Tower (Mimics Room)"), + (GanonsTowerLanmolasRoom, "Ganon's Tower (Lanmolas Room"), + (GanonsTowerGauntlet45, "Ganon's Tower (Gauntlet 4/5)"), + (IcePalacePengatorsRoom, "Ice Palace (Pengators Room)"), + (EmptyCloneRoom0x6F, "Empty Clone Room"), + (HyruleCastleSmallCorridorToJailCells, "Hyrule Castle (Small Corridor to Jail Cells"), + (HyruleCastleBoomerangChestRoom, "Hyrule Castle (Boomerang Chest Room)"), + (HyruleCastleMapChestRoom, "Hyrule Castle (Map Chest Room)"), + (DesertPalaceBigChestRoom, "Desert Palace (Big Chest Room)"), + (DesertPalaceMapChestRoom, "Desert Palace (Map Chest Room)"), + (DesertPalaceBigKeyChestRoom, "Desert Palace (Big Key Chest Room)"), + (SwampPalaceWaterDrainRoom, "Swamp Palace (Water Drain Room)"), + (TowerOfHeraEntranceRoom, "Tower of Hera (Entrance Room)"), + (EmptyCloneRoom0x78, "Empty Clone Room"), + (EmptyCloneRoom0x79, "Empty Clone Room"), + (EmptyCloneRoom0x7A, "Empty Clone Room"), + (GanonsTowerMisc, "Ganon's Tower (Sideways Conveyors / 4 Chest 4 Shooter / Square Pit"), + (GanonsTowerEastSideCollapsingBridgeExplodingWallRoom, "Ganon's Tower (East Side Collapsing Bridge / Exploding Wall Room)"), + (GanonsTowerWinderWarpMazeRoom, "Ganon's Tower (Winder / Warp Maze Room)"), + (IcePalaceHiddenChestBombableFloorRoom, "Ice Palace (Hidden Chest / Bombable Floor Room)"), + (IcePalaceBigSpikeTrapsRoom, "Ice Palace (Big Spike Traps Room)"), + (HyruleCastleJailCellRoom, "Hyrule Castle (Jail Cell Room)"), + (HyruleCastleNextToChasmRoom, "Hyrule Castle (Next to Chasm Room)"), + (HyruleCastleBasementChasmRoom, "Hyrule Castle (Basement Chasm Room)"), + (DesertPalaceWestEntranceRoom, "Desert Palace (West Entrance Room)"), + (DesertPalaceMainEntranceRoom, "Desert Palace (Main Entrance Room)"), + (DesertPalaceEastEntranceRoom, "Desert Palace (East Entrance Room)"), + (EmptyCloneRoom0x86, "Empty Clone Room"), + (TowerOfHeraTileRoom, "Tower of Hera (Tile Room)"), + (EmptyCloneRoom0x88, "Empty Clone Room"), + (EasternPalaceFairyRoom, "Eastern Palace (Fairy Room)"), + (EmptyCloneRoom0x8A, "Empty Clone Room"), + (GanonsTowerBlockPuzzleSpikeSkipMapChestRoom, "Ganon's Tower (Block Puzzle / Spike Skip / Map Chest Room)"), + (GanonsTowerEastAndWestDownstairsBigChestRoom, "Ganon's Tower (East and West Downstairs / Big Chest Room)"), + (GanonsTowerTileTorchPuzzleRoom, "Ganon's Tower (Tile / Torch Puzzle Room)"), + (IcePalaceBlobsWithTetrisBarrier, "Ice Palace (Blobs with Tetris Barrier)"), + (EmptyCloneRoom0x8F, "Empty Clone Room"), + (MiseryMireVitreous, "Misery Mire (Vitreous[Boss])"), + (MiseryMireFinalSwitchRoom, "Misery Mire (Final Switch Room)"), + (MiseryMireDarkBombWallSwitchesRoom, "Misery Mire (Dark Bomb Wall / Switches Room)"), + (MiseryMireDarkCaneFLoorSwitchPuzzleRoom, "Misery Mire (Dark Cane FLoor Switch Puzzle Room)"), + (EmptyCloneRoom0x94, "Empty Clone Room"), + (GanonsTowerFinalCollapsingBridgeRoom, "Ganon's Tower (Final Collapsing Bridge Room)"), + (GanonsTowerTorches1Room, "Ganon's Tower (Torches 1 Room)"), + (MiseryMireTorchPuzzleMovingWallRoom, "Misery Mire (Torch Puzzle / Moving Wall Room)"), + (MiseryMireEntranceRoom, "Misery Mire (Entrance Room)"), + (EasternPalaceEyegoreKeyRoom, "Eastern Palace (Eyegore Key Room)"), + (EmptyCloneRoom0x9A, "Empty Clone Room"), + (GanonsTowerManySpikesWarpMazeRoom, "Ganon's Tower (Many Spikes / Warp Maze Room)"), + (GanonsTowerInvisibleFloorMazeRoom, "Ganon's Tower (Invisible Floor Maze Room)"), + (GanonsTowerCompassChestInvisibleFloorRoom, "Ganon's Tower (Compass Chest / Invisible Floor Room)"), + (IcePalaceBigChestRoom, "Ice Palace (Big Chest Room)"), + (IcePalaceIceFloorPotsKeyAndSwitch, "Ice Palace (Room with ice floor, key, and 4 wall rats)"), + (MiseryMirePreVitreousRoom, "Misery Mire (Pre-Vitreous Room)"), + (MiseryMireFishRoom, "Misery Mire (Fish Room)"), + (MiseryMireBridgeKeyChestRoom, "Misery Mire (Bridge Key Chest Room)"), + (MiseryMireEmptyLConnectingRoom, "Misery Mire (Empty 'L' Connecting Room)"), + (TurtleRockTrinexx, "Turtle Rock (Trinexx[Boss])"), + (GanonsTowerWizzrobesRooms, "Ganon's Tower (Wizzrobes Rooms)"), + (GanonsTowerMoldormFallRoom, "Ganon's Tower (Moldorm Fall Room)"), + (TowerOfHeraFairyRoom, "Tower of Hera (Fairy Room)"), + (EasternPalaceStalfosSpawnRoom, "Eastern Palace (Staflos Spawn Room)"), + (EasternPalaceBigChestRoom, "Eastern Palace (Big Chest Room)"), + (EasternPalaceMapChestRoom, "Eastern Palace (Map Chest Room)"), + (ThievesTownMovingSpikesKeyPotRoom, "Thieves' Town (Moving Spikes / Key Pot Room)"), + (ThievesTownBlindTheThief, "Thieves' Town (Blind the Thief[Boss])"), + (EmptyCloneRoom0xAD, "Empty Clone Room"), + (IcePalaceIceTRoom, "Ice Palace (Ice T Room)"), + (IcePalaceIceBridgeRoom, "Ice Palace (Ice Bridge Room)"), + (AgahnimsTowerCircleOfPots, "Agahnim's Tower (Circle of Pots)"), + (MiseryMireHourglassRoom, "Misery Mire (Hourglass Room)"), + (MiseryMireSlugRoom, "Misery Mire (Slug Room)"), + (MiseryMireSpikeKeyChestRoom, "Misery Mire (Spike Key Chest Room)"), + (TurtleRockPreTrinexxRoom, "Turtle Rock (Pre-Trinexx Room)"), + (TurtleRockDarkMaze, "Turtle Rock (Dark Maze)"), + (TurtleRockChainChompsRoom, "Turtle Rock (Chain Chomps Room)"), + (TurtleRockMapChestKeyChestRollerRoom, "Turtle Rock (Map Chest / Key Chest / Roller Room)"), + (EasternPalaceBigKeyRoom, "Eastern Palace (Big Key Room)"), + (EasternPalaceLobbyCannonballsRoom, "Eastern Palace (Lobby Cannonballs Room)"), + (EasternPalaceDarkAntifairyKeyPotRoom, "Eastern Palace (Dark Antifairy / Key Pot Room)"), + (ThievesTownHellway, "Thieves' Town (Hellway)"), + (ThievesTownConveyorToilet, "Thieves' Town (Conveyor Toilet)"), + (EmptyCloneRoom0xBD, "Empty Clone Room"), + (IcePalaceBlockPuzzleRoom, "Ice Palace (Block Puzzle Room)"), + (IcePalaceCloneRoomSwitchRoom, "Ice Palace Clone Room (Switch Room)"), + (AgahnimsTowerDarkBridgeRoom, "Agahnim's Tower (Dark Bridge Room)"), + (MiseryMireCompassChestTileRoom, "Misery Mire (Compass Chest / Tile Room)"), + (MiseryMireBigHubRoom, "Misery Mire (Big Hub Room)"), + (MiseryMireBigChestRoom, "Misery Mire (Big Chest Room)"), + (TurtleRockFinalCrystalSwitchPuzzleRoom, "Turtle Rock (Final Crystal Switch Puzzle Room)"), + (TurtleRockLaserBridge, "Turtle Rock (Laser Bridge)"), + (TurtleRockSomariaHub, "Turtle Rock (Somaria Hub Room)"), + (TurtleRockTorchPuzzle, "Turtle Rock (Torch Puzzle)"), + (EasternPalaceArmosKnights, "Eastern Palace (Armos Knights[Boss])"), + (EasternPalaceEntranceRoom, "Eastern Palace (Entrance Room)"), + (UnknownRoom, "Unused Room??"), + (ThievesTownNorthWestEntranceRoom, "Thieves' Town (North West Entrance Room)"), + (ThievesTownNorthEastEntranceRoom, "Thieves' Town (North East Entrance Room)"), + (EmptyCloneRoom0xCD, "Empty Clone Room"), + (IcePalaceHoleToKholdstareRoom, "Ice Palace (Hole to Kholdstare Room)"), + (EmptyCloneRoom0xCF, "Empty Clone Room"), + (AgahnimsTowerDarkMaze, "Agahnim's Tower (Dark Maze)"), + (MiseryMireConveyorSlugBigKeyRoom, "Misery Mire (Conveyor Slug / Big Key Room)"), + (MiseryMireWizzrobesRoom, "Misery Mire (Wizzrobes Room)"), + (EmptyCloneRoom0xD3, "Empty Clone Room"), + (EmptyCloneRoom0xD4, "Empty Clone Room"), + (TurtleRockLaserKeyRoom, "Turtle Rock (Laser Key Room)"), + (TurtleRockEntranceRoom, "Turtle Rock (Entrance Room)"), + (EmptyCloneRoom0xD7, "Empty Clone Room"), + (EasternPalacePreArmosKnightsRoom, "Eastern Palace (Pre-Armos-Knights Room)"), + (EasternPalaceCannonballRoom, "Eastern Palace (Cannonball Room)"), + (EasternPalacePotSwitchRoom, "Eastern Palace (Pot Switch Room)"), + (ThievesTownSouthWestEntranceRoom, "Thieves' Town (South West Entrance Room)"), + (ThievesTownSouthEastEntranceRoom, "Thieves Town (South East Entrance Room)"), + (EmptyCloneRoom0xDD, "Empty Clone Room"), + (IcePalaceKholdstare, "Ice Palace (Kholdstare[Boss])"), + (CaveBackwardsDeathMountainTopFloor, "Death Mountain Exit (Top)"), + (AgahnimsTowerEntranceRoom, "Agahnim's Tower (Entrance Room)"), + (CaveLostWoodsHP, "Cave (Lost Woods HP)"), + (CaveLumberjacksTreeHP, "Cave (Lumberjacks' Tree HP)"), + (CaveMagicBat, "Cave (Magic Bat)"), + (CaveLostOldManHouse, "Cave (Lost Old Man's House)"), + (CaveLostOldManHouseBack, "Cave (Lost Old Man's House Back)"), + (Cave0xE6, "Cave (Bunch of Keese)"), + (Cave0xE7, "Cave (Bunch of Keese 2)"), + (CaveSuperBunnyTop, "Cave (Super Bunny Top)"), + (EmptyCloneRoom0xE9, "Empty Clone Room"), + (CaveSpectacleRockHP, "Cave (Inside Spectacle Rock HP)"), + (CaveBumperCaveTop, "Cave (Bumper Cave Top)"), + (EmptyCloneRoom0xEC, "Empty Clone Room"), + (Cave0xED, "Cave (??)"), + (CaveSpiralCave, "Cave (Spiral Cave)"), + (CaveCrystalSwitch5ChestsRoom, "Cave (Crystal Switch / 5 Chests Room)"), + (CaveLostOldManStartingCaveBottom, "Cave (Lost Old Man Starting Cave Bottom)"), + (CaveLostOldManStartingCaveTop, "Cave (Lost Old Man Starting Cave Top)"), + (HouseOldWomanNextDoor, "House (Old Woman Next Door)"), + (HouseOldWomanSahasrahlasWifeMaybe, "House (Old Woman / Sahasrahla's Wife?)"), + (HouseAngryBrothersWest, "House (Angry Brothers West)"), + (HouseAngryBrothersEast, "House (Angry Brothers East)"), + (EmptyCloneRoom0xF6, "Empty Clone Room"), + (EmptyCloneRoom0xF7, "Empty Clone Room"), + (CaveSuperBunnyBottom, "Cave (Super Bunny Bottom)"), + (CaveSpectacleRockExit, "Cave (Spectacle Rock Exit)"), + (CaveSpectacleRockPrizeEntrance, "Cave (Spectacle Rock Prize Entrance)"), + (CaveBumperCaveBottom, "Cave (Bumper Cave Bottom)"), + (EmptyCloneRoom0xFC, "Empty Clone Room"), + (Cave0xFD, "Cave (Death Mountain)"), + (CaveSpiralCaveExit, "Cave (Spiral Cave Exit)"), + (CaveParadoxCaveMiddleEntrance, "Cave (Paradox Cave 'Middle' Entrance)"), + (ShopInLostWoods0x100, "Shop in Lost Woods"), + (ScaredLadyHouses, "Scared Ladies' Houses"), + (SickKid, "Sick Kid"), + (InnBushHouse, "Inn / BushHouse"), + (LinksHouse, "Link's House"), + (ShabadooHouse, "Sahasrahla's House"), + (ChestGameBombHouse, "Chest Game / Outcast Village Bomb House"), + (LibraryBombFarmRoom, "Library / Bomb Farm Room"), + (ChickenHouse, "Chicken House"), + (WitchHut, "Witch Hut"), + (Aginah, "Aginah's Cave"), + (Dam, "Dam"), + (MimicCave, "Mimic Cave"), + (CaveOutsideMiseryMire, "Cave Outside Misery Mire"), + (Cave0x10E, "Cave0x10E"), + (Shop0x10F, "Shop0x10F"), + (Shop0x110, "Shop0x110"), + (ArcherGame, "Archery Game"), + (CaveShop0x112, "CaveShop0x112"), + (KingsTomb, "King's Tomb"), + (WishingWellCave0x114, "Wishing Well / Cave 0x114"), + (WishingWellBigFairy, "Wishing Well / BigFairy"), + (FatFairy, "Fat Fairy"), + (SpikeCave, "Spike Cave"), + (Shop0x118, "Shop0x118"), + (BlindsHouse, "Blind's House"), + (Mutant, "Mutant Hut"), + (MirrorCaveGroveAndTomb, "Mirror Caves (South of Grove / Graveyard Ledge)"), + (BombShop, "Bomb Shop"), + (BlindsBasement, "Blind's Basement"), + (HypeCave, "Hype Cave"), + (Shop0x11F, "Shop0x11F"), + (IceRodCave, "Ice Rod Cave"), + (SmithHouse, "Smiths' House"), + (FortuneTellers, "Fortune Tellers"), + (MiniMoldormCave, "Mini-Moldorm Cave"), + (UnknownCaveBonkCave, "Unknown Cave / BonkCave"), + (Cave0x125, "Cave0x125"), + (CheckerBoardCave, "Checker Board Cave"), + (HammerPegCave, "Hammer Peg Cave"), + ]; + + HashMap::from_iter(names.into_iter()) + }; +} + +pub const NEED_KILLABLE_ROOMS: [Room; 39] = [ + EasternPalaceBigKeyRoom, // For anti-fairy circle around pot switch + PalaceOfDarknessTurtleRoom, + PalaceOfDarknessMimicsMovingWallRoom, + PalaceOfDarknessWarpsSouthMimicRoom, + TurtleRockCrystalRollerRoom, // Greed room has a kill-all section + TurtleRockDoubleHokkuBokkuBigChestRoom, + TurtleRockChainChompsRoom, + SwampPalaceEntranceRoom, + IcePalaceEntranceRoom, // Not normally required to backtrack, except in doors intensity 3 + IcePalaceCompassRoom, + IcePalaceStalfosKnightConveyorHellway, + IcePalacePengatorsRoom, + TowerOfHeraHardhatBeetleRoom, + TowerOfHeraTileRoom, + ThievesTownBigChestRoom, // For trap room north of big chest + ThievesTownJailCellsRoom, // For room with stairs and big lift block + DesertPalacePopos2BeamosHellwayRoom, + DesertPalaceBigKeyChestRoom, + DesertPalaceEastEntranceRoom, + GanonsTowerTorchRoom2, + GanonsTowerGauntlet123, + GanonsTowerMimicsRoom, + GanonsTowerGauntlet45, + GanonsTowerMisc, + GanonsTowerWinderWarpMazeRoom, + GanonsTowerTileTorchPuzzleRoom, + GanonsTowerTorches1Room, + GanonsTowerWizzrobesRooms, + HyruleCastleBoomerangChestRoom, + EasternPalaceStalfosSpawnRoom, + EasternPalacePreArmosKnightsRoom, + AgahnimsTowerCircleOfPots, + AgahnimsTowerDarkBridgeRoom, // Not normally required to go backwards, except in basic/crossed doors, or doing glitch shenanigans. + AgahnimsTowerEntranceRoom, + MiseryMireSlugRoom, + MiseryMireWizzrobesRoom, + CaveCrystalSwitch5ChestsRoom, + MimicCave, + MiniMoldormCave, +]; + +// All the rooms that require special handling. + +/// These rooms need to be locked to GFX ID 28. +pub const FREEZOR_ROOMS: [Room; 5] = [ + IcePalaceEntranceRoom, + IcePalaceHiddenChestBombableFloorRoom, + IcePalaceBlobsWithTetrisBarrier, + IcePalaceBigChestRoom, + IcePalaceBlockPuzzleRoom, +]; + +/// These rooms need to be locked to GFX ID 17. +pub const WATER_ROOMS: [Room; 33] = [ + GanonsTowerEntranceRoom, + GanonsTowerAgahnim2, + SwampPalaceSwimmingTreadmill, + GanonsTowerIceArmos, + GanonsTowerFinalHallway, + SwampPalaceEntranceRoom, + SwampPalacePushBlockPuzzlePreBigKeyRoom, + SwampPalaceBigChestRoom, + SwampPalaceKeyPotRoom, + GanonsTowerTorchRoom2, + SwampPalaceCompassChestRoom, + GanonsTowerMiniHelmasaurConveyorRoom, + GanonsTowerMoldormRoom, + GanonsTowerSpikePitRoom, + GanonsTowerGanonBallZ, + GanonsTowerGauntlet123, + SwampPalaceHiddenChestHiddenDoorRoom, + GanonsTowerMimicsRoom, + GanonsTowerLanmolasRoom, + GanonsTowerGauntlet45, + GanonsTowerMisc, + GanonsTowerEastSideCollapsingBridgeExplodingWallRoom, + GanonsTowerWinderWarpMazeRoom, + GanonsTowerBlockPuzzleSpikeSkipMapChestRoom, + GanonsTowerEastAndWestDownstairsBigChestRoom, + GanonsTowerTileTorchPuzzleRoom, + GanonsTowerFinalCollapsingBridgeRoom, + GanonsTowerTorches1Room, + GanonsTowerManySpikesWarpMazeRoom, + GanonsTowerInvisibleFloorMazeRoom, + GanonsTowerCompassChestInvisibleFloorRoom, + GanonsTowerWizzrobesRooms, + GanonsTowerMoldormFallRoom, +]; + +pub const SHADOW_ROOMS: [Room; 2] = [ + IcePalaceStalfosKnightConveyorHellway, + IcePalaceIceFloorPotsKeyAndSwitch, +]; + +pub const WALL_MASTER_ROOMS: [Room; 5] = [ + SkullWoodsGibdoKeyMothulaHoleRoom, + SkullWoodsGibdoTorchPuzzleRoom, + SkullWoodsKeyPotTrapRoom, + SkullWoodsKeyChestTrapRoom, + GanonsTowerTileTorchPuzzleRoom, +]; + +pub const BUMPER_CRYSTAL_LASER_ROOMS: [Room; 42] = [ + TurtleRockCrystalRollerRoom, + PalaceOfDarknessTurtleRoom, + TurtleRockHokkuBokkuKeyRoom2, + TowerOfHeraMoldormFallRoom, + PalaceOfDarknessMimicsMovingWallRoom, + IcePalaceBombFloorBariRoom, + PalaceOfDarknessBigHubRoom, + PalaceOfDarknessMapChestFairyRoom, + TowerOfHeraHardhatBeetleRoom, + GanonsTowerTorchRoom2, + AgahnimsTowerFinalBridgeRoom, + ThievesTownBigChestRoom, + PalaceOfDarknessEntranceRoom, + GanonsTowerMiniHelmasaurConveyorRoom, + SkullWoodsBigChestRoom, + SkullWoodsFinalSectionEntranceRoom, + GanonsTowerSpikePitRoom, + SkullWoodsCompassChestRoom, + SkullWoodsKeyChestTrapRoom, + GanonsTowerMimicsRoom, + TowerOfHeraEntranceRoom, + IcePalaceHiddenChestBombableFloorRoom, + TowerOfHeraTileRoom, + GanonsTowerBlockPuzzleSpikeSkipMapChestRoom, + MiseryMireFinalSwitchRoom, + MiseryMireDarkBombWallSwitchesRoom, + GanonsTowerTorches1Room, + GanonsTowerManySpikesWarpMazeRoom, + GanonsTowerCompassChestInvisibleFloorRoom, + MiseryMireFishRoom, + GanonsTowerWizzrobesRooms, + ThievesTownMovingSpikesKeyPotRoom, + TurtleRockChainChompsRoom, + IcePalaceCloneRoomSwitchRoom, + MiseryMireCompassChestTileRoom, + MiseryMireBigChestRoom, + TurtleRockFinalCrystalSwitchPuzzleRoom, + TurtleRockLaserBridge, + TurtleRockLaserKeyRoom, + TurtleRockEntranceRoom, + CaveBumperCaveTop, + CaveCrystalSwitch5ChestsRoom, +]; + +pub const PULL_SWITCH_ROOMS: [Room; 4] = [ + HyruleCastleSwitchRoom, + SkullWoodsBigChestRoom, + ThievesTownWestAtticRoom, + Dam, +]; + +pub const TONGUE_ROOMS: [Room; 7] = [ + TurtleRockCrystalRollerRoom, + TurtleRockWestExitToBalcony, + SwampPalaceBigKeyBsRoom, + SwampPalaceMapChestWaterFillRoom, + IcePalaceMapChestRoom, + SwampPalaceWaterDrainRoom, + IcePalaceHoleToKholdstareRoom, +]; + +pub const NO_STATUE_ROOMS: [Room; 12] = [ + SwampPalaceSwimmingTreadmill, + SwampPalaceStatueRoom, + SwampPalaceEntranceRoom, + PalaceOfDarknessMapChestFairyRoom, + SwampPalacePushBlockPuzzlePreBigKeyRoom, + SwampPalaceBigChestRoom, + SwampPalaceCompassChestRoom, + SkullWoodsBigKeyRoom, + SwampPalaceWaterDrainRoom, + MiseryMireHourglassRoom, + MiseryMireBigHubRoom, + AgahnimsTowerDarkMaze, +]; + +pub const MOVING_CANNONS_ROOMS: [Room; 2] = [ + GanonsTowerGanonBallZ, + DesertPalaceBigKeyChestRoom, +]; + +pub const CANNON_ROOMS: [Room; 3] = [ + EasternPalaceLobbyCannonballsRoom, + EasternPalaceCannonballRoom, + MimicCave, // ??? +]; + +/// These rooms have no enemies to randomize, or are special bosses that can't be switched out. +pub const DONT_RANDOMIZE_ROOMS: [Room; 8] = [ + Ganon, + HyruleCastleNorthCorridor, + HoulihanRoom, + GanonsTowerAgahnim2, + TurtleRockBigKeyRoom, + AgahnimsTowerAgahnim, + AgahnimsTowerMaidenSacrificeChamber, + IcePalaceBigSpikeTrapsRoom, // Anything else would be too easy? +]; + +pub const NO_SPECIAL_ENEMIES_IN_STANDARD_MODE_ROOMS: [Room; 21] = [ + HyruleCastleNorthCorridor, + HyruleCastleSwitchRoom, + HyruleCastleBombableStockRoom, + HyruleCastleKeyRatRoom, + HyruleCastleSewerTextTriggerRoom, + HyruleCastleSewerKeyChestRoom, + HyruleCastleFirstDarkRoom, + HyruleCastle6RopesRoom, + HyruleCastleWestCorridor, + HyruleCastleThroneRoom, + HyruleCastleEastCorridor, + CastleSecretEntranceUncleRoom, + HyruleCastleWestEntranceRoom, + HyruleCastleMainEntranceRoom, + HyruleCastleEastEntranceRoom, + HyruleCastleSmallCorridorToJailCells, + HyruleCastleBoomerangChestRoom, + HyruleCastleMapChestRoom, + HyruleCastleJailCellRoom, + HyruleCastleNextToChasmRoom, + HyruleCastleBasementChasmRoom, +]; + +pub const BOSS_ROOMS: [Room; 16] = [ + Ganon, + SwampPalaceArrghus, + TowerOfHeraMoldorm, + GanonsTowerAgahnim2, + GanonsTowerIceArmos, + AgahnimsTowerAgahnim, + SkullWoodsMothula, + DesertPalaceLanmolas, + GanonsTowerMoldormRoom, + PalaceOfDarknessHelmasaurKing, + GanonsTowerLanmolasRoom, + MiseryMireVitreous, + TurtleRockTrinexx, + ThievesTownBlindTheThief, + EasternPalaceArmosKnights, + IcePalaceKholdstare, +]; + +pub const DONT_USE_IMMOVABLE_ENEMIES: [Room; 57] = [ + PalaceOfDarknessTurtleRoom, // for single terrorpin in L shaped section + SwampPalaceSwimmingTreadmill, + PalaceOfDarknessDarkMaze, // top placement will probably block maze + IcePalaceBombFloorBariRoom, // TODO: test + SwampPalaceStatueRoom, + TowerOfHeraBigChest, // TODO: test, top left placement before stairs up + SwampPalaceBigChestRoom, // bottom left waterbug + IcePalaceMapChestRoom, // spikes block stuff + HyruleCastle6RopesRoom, // only if two of them stack, but why chance it? + AgahnimsTowerFinalBridgeRoom, // spikes are bad here + SwampPalaceCompassChestRoom, + SkullWoodsGibdoTorchPuzzleRoom, + PalaceOfDarknessWarpsSouthMimicRoom, + IcePalaceBombJumpRoom, // for the zols in the small next room + CastleSecretEntranceUncleRoom, // TODO: test + SkullWoodsBigKeyRoom, + IcePalaceHiddenChestSpikeFloorRoom, // TODO: would this cause problems in OHKO since you can't hookshot across if middle mob is beamos, etc? + ThievesTownEastAtticRoom, // only if both bottom rats + PalaceOfDarknessRupeeRoom, // only if two terrorpins next to each other (but can happen twice) + DesertPalaceMapChestRoom, // only if both antlions in hallway to the south of chest + SwampPalaceWaterDrainRoom, // would need 3 mobs to be impassible to possibly softlock... + GanonsTowerWinderWarpMazeRoom, // very low chance + IcePalaceBigSpikeTrapsRoom, // TODO: what happens to beamos over a pit? + DesertPalaceWestEntranceRoom, // TODO: test + DesertPalaceMainEntranceRoom, // TODO: test + DesertPalaceEastEntranceRoom, // TODO: test + GanonsTowerEastAndWestDownstairsBigChestRoom, // TODO: test, probably safe? + GanonsTowerTileTorchPuzzleRoom, // TODO: test + MiseryMireDarkBombWallSwitchesRoom, // TODO: test + GanonsTowerFinalCollapsingBridgeRoom, // TODO: test, probably safe because of conveyor belts + MiseryMireEntranceRoom, + GanonsTowerManySpikesWarpMazeRoom, // TODO: test, middle spike covers warp, are we randomizing those? + GanonsTowerInvisibleFloorMazeRoom, // TODO: test + GanonsTowerCompassChestInvisibleFloorRoom, // TODO: test + IcePalaceBigChestRoom, // Big spikes will block + MiseryMirePreVitreousRoom, // TODO: test + EasternPalaceMapChestRoom, // TODO: test + IcePalaceIceBridgeRoom, + MiseryMireSpikeKeyChestRoom, // TODO: test lower stalfos blocking door + EasternPalaceDarkAntifairyKeyPotRoom, // TODO: test lower stalfos blocking door + ThievesTownHellway, // TODO: test, but should be ok + ThievesTownConveyorToilet, // TODO: test + TurtleRockSomariaHub, // technically a door is blocked off, but who would ever go there? (Door rando would) + ThievesTownNorthWestEntranceRoom, + IcePalaceHoleToKholdstareRoom, // spikes block stuff + AgahnimsTowerDarkMaze, // TODO: test + MiseryMireWizzrobesRoom, + TurtleRockLaserKeyRoom, + EasternPalacePreArmosKnightsRoom, + ThievesTownSouthEastEntranceRoom, // TODO: test + CaveBackwardsDeathMountainTopFloor, + CaveLostOldManHouse, + Cave0xE7, + CaveSpiralCave, + CaveSpectacleRockExit, // TODO: test, probably can get past + Cave0xFD, + MimicCave, +]; + +pub const DONT_USE_FLYING_ENEMIES: [Room; 2] = [ + MiseryMireWizzrobesRoom, + MimicCave, +]; From 5500d628f1a166022357ba29df207bff8dd5d3c9 Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Sun, 12 Jun 2022 12:12:32 -0400 Subject: [PATCH 28/29] Fill in the last names of rooms. --- enemize/src/graph/dungeons.rs | 48 +++++++++++++++++------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/enemize/src/graph/dungeons.rs b/enemize/src/graph/dungeons.rs index 4d25b9a..e1d1fe4 100644 --- a/enemize/src/graph/dungeons.rs +++ b/enemize/src/graph/dungeons.rs @@ -155,7 +155,7 @@ pub enum Room { SwampPalaceArrghus, TowerOfHeraMoldorm, CaveHealingFairy, - PalaceOfDarkness0x09, + PalaceOfDarknessMedusaChestRoom, PalaceOfDarknessStalfossTrapRoom, PalaceOfDarknessTurtleRoom, GanonsTowerEntranceRoom, @@ -167,7 +167,7 @@ pub enum Room { Sanctuary, TurtleRockHokkuBokkuKeyRoom2, TurtleRockBigKeyRoom, - TurtleRock0x15, + TurtleRockUselessTubes, SwampPalaceSwimmingTreadmill, TowerOfHeraMoldormFallRoom, Cave0x18BigFairyDropEntrance, @@ -416,30 +416,30 @@ pub enum Room { Dam, MimicCave, CaveOutsideMiseryMire, - Cave0x10E, - Shop0x10F, - Shop0x110, + CaveDarkHyliaLedgeHint, + ShopDarkWorldShops, + ShopFireShieldShop, ArcherGame, - CaveShop0x112, + CaveShop, KingsTomb, - WishingWellCave0x114, - WishingWellBigFairy, + WaterfallCaveMireHint, + BigFairy, FatFairy, SpikeCave, - Shop0x118, + ChestGamblingGame, BlindsHouse, Mutant, MirrorCaveGroveAndTomb, BombShop, BlindsBasement, HypeCave, - Shop0x11F, + KakarikoShopLumberjackHouse, IceRodCave, SmithHouse, FortuneTellers, MiniMoldormCave, - UnknownCaveBonkCave, - Cave0x125, + FiftyRupeeCaveBonkCave, + TwentyRupeeCaveDarkHyliaSpikeHintCave, CheckerBoardCave, HammerPegCave, } @@ -459,7 +459,7 @@ lazy_static! { (SwampPalaceArrghus, "Swamp Palace (Arrghus[Boss])"), (TowerOfHeraMoldorm, "Tower of Hera (Moldorm[Boss])"), (CaveHealingFairy, "Cave (Healing Fairy)"), - (PalaceOfDarkness0x09, "Palace of Darkness"), + (PalaceOfDarknessMedusaChestRoom, "Palace of Darkness (Medusa Chest Room)"), (PalaceOfDarknessStalfossTrapRoom, "Palace of Darkness (Stalfoss Trap Room)"), (PalaceOfDarknessTurtleRoom, "Palace of Darkness (Turtle Room)"), (GanonsTowerEntranceRoom, "Ganon's Tower (Entrance Room)"), @@ -471,7 +471,7 @@ lazy_static! { (Sanctuary, "Sanctuary"), (TurtleRockHokkuBokkuKeyRoom2, "Turtle Rock (Hokku-Bokku Key Room 2)"), (TurtleRockBigKeyRoom, "Turtle Rock (Big Key Room)"), - (TurtleRock0x15, "Turtle Rock"), + (TurtleRockUselessTubes, "Turtle Rock (Useless Tubes)"), (SwampPalaceSwimmingTreadmill, "Swamp Palace (Swimming Treadmill)"), (TowerOfHeraMoldormFallRoom, "Tower of Hera (Moldorm Fall Room)"), (Cave0x18BigFairyDropEntrance, "Big Fairy Drop Entrance Cave"), @@ -720,30 +720,30 @@ lazy_static! { (Dam, "Dam"), (MimicCave, "Mimic Cave"), (CaveOutsideMiseryMire, "Cave Outside Misery Mire"), - (Cave0x10E, "Cave0x10E"), - (Shop0x10F, "Shop0x10F"), - (Shop0x110, "Shop0x110"), + (CaveDarkHyliaLedgeHint, "Cave (Dark Hylia Ledge Hint)"), + (ShopDarkWorldShops, "Shop (Dark World)"), + (ShopFireShieldShop, "Shop (Fire Shield Shop)"), (ArcherGame, "Archery Game"), - (CaveShop0x112, "CaveShop0x112"), + (CaveShop, "Cave (Shop)"), (KingsTomb, "King's Tomb"), - (WishingWellCave0x114, "Wishing Well / Cave 0x114"), - (WishingWellBigFairy, "Wishing Well / BigFairy"), + (WaterfallCaveMireHint, "Waterfall Cave / Cave (Mire Hint)"), + (BigFairy, "Big Fairy"), (FatFairy, "Fat Fairy"), (SpikeCave, "Spike Cave"), - (Shop0x118, "Shop0x118"), + (ChestGamblingGame, "Chest Gambling Game"), (BlindsHouse, "Blind's House"), (Mutant, "Mutant Hut"), (MirrorCaveGroveAndTomb, "Mirror Caves (South of Grove / Graveyard Ledge)"), (BombShop, "Bomb Shop"), (BlindsBasement, "Blind's Basement"), (HypeCave, "Hype Cave"), - (Shop0x11F, "Shop0x11F"), + (KakarikoShopLumberjackHouse, "Kakariko Shop / Lumberjacks' House"), (IceRodCave, "Ice Rod Cave"), (SmithHouse, "Smiths' House"), (FortuneTellers, "Fortune Tellers"), (MiniMoldormCave, "Mini-Moldorm Cave"), - (UnknownCaveBonkCave, "Unknown Cave / BonkCave"), - (Cave0x125, "Cave0x125"), + (FiftyRupeeCaveBonkCave, "50 Rupee Cave / Bonk Cave"), + (TwentyRupeeCaveDarkHyliaSpikeHintCave, "20 Rupee Cave / Dark Hylia Spike Hint Cave"), (CheckerBoardCave, "Checker Board Cave"), (HammerPegCave, "Hammer Peg Cave"), ]; From b343a0cb80dc16dd7c3f7427d19af78cf1da77b7 Mon Sep 17 00:00:00 2001 From: Lyle Mantooth Date: Sun, 12 Jun 2022 15:17:54 -0400 Subject: [PATCH 29/29] Add Overworld locations. Refactor dungeon module to locations to reflect what it contains. --- .../src/graph/{dungeons.rs => locations.rs} | 720 ++++++++++-------- 1 file changed, 401 insertions(+), 319 deletions(-) rename enemize/src/graph/{dungeons.rs => locations.rs} (71%) diff --git a/enemize/src/graph/dungeons.rs b/enemize/src/graph/locations.rs similarity index 71% rename from enemize/src/graph/dungeons.rs rename to enemize/src/graph/locations.rs index e1d1fe4..1192e1a 100644 --- a/enemize/src/graph/dungeons.rs +++ b/enemize/src/graph/locations.rs @@ -5,8 +5,8 @@ use lazy_static::lazy_static; use super::item::ItemId; #[derive(Debug, thiserror::Error)] -#[error("{0} is not a dungeon room")] -pub struct NotADungeonRoom(u8); +#[error("{0} is not a dungeon super tile")] +pub struct NotADungeonSuperTile(u8); #[derive(Clone, Copy, Eq, Hash, PartialEq)] pub enum Dungeon { @@ -25,14 +25,14 @@ pub enum Dungeon { GanonsTower, } -pub fn get_dungeon_from_room(room_id: u8) -> Result { +pub fn get_dungeon_from_room(room_id: u8) -> Result { for (dungeon, rooms) in DUNGEON_ROOMS.iter() { if let Ok(_) = rooms.binary_search(&room_id) { return Ok(*dungeon); } } - Err(NotADungeonRoom(room_id)) + Err(NotADungeonSuperTile(room_id)) } const HYRULE_CASTLE_ROOMS: [u8; 21] = [ @@ -145,309 +145,391 @@ lazy_static! { #[derive(Clone, Copy, Eq, PartialEq, Hash)] #[repr(u16)] -pub enum Room { - Ganon = 0, - HyruleCastleNorthCorridor, - HyruleCastleSwitchRoom, - HoulihanRoom, - TurtleRockCrystalRollerRoom, - EmptyCloneRoom0x05, - SwampPalaceArrghus, - TowerOfHeraMoldorm, - CaveHealingFairy, - PalaceOfDarknessMedusaChestRoom, - PalaceOfDarknessStalfossTrapRoom, - PalaceOfDarknessTurtleRoom, - GanonsTowerEntranceRoom, - GanonsTowerAgahnim2, - IcePalaceEntranceRoom, - EmptyCloneRoom0x0F, - GanonEvacuationRoute, - HyruleCastleBombableStockRoom, - Sanctuary, - TurtleRockHokkuBokkuKeyRoom2, - TurtleRockBigKeyRoom, - TurtleRockUselessTubes, - SwampPalaceSwimmingTreadmill, - TowerOfHeraMoldormFallRoom, - Cave0x18BigFairyDropEntrance, - PalaceOfDarknessDarkMaze, - PalaceOfDarknessBigChestRoom, - PalaceOfDarknessMimicsMovingWallRoom, - GanonsTowerIceArmos, - GanonsTowerFinalHallway, - IcePalaceBombFloorBariRoom, - IcePalacePengatorBigKeyRoom, - AgahnimsTowerAgahnim, - HyruleCastleKeyRatRoom, - HyruleCastleSewerTextTriggerRoom, - TurtleRockWestExitToBalcony, - TurtleRockDoubleHokkuBokkuBigChestRoom, - EmptyCloneRoom0x25, - SwampPalaceStatueRoom, - TowerOfHeraBigChest, - SwampPalaceEntranceRoom, - SkullWoodsMothula, - PalaceOfDarknessBigHubRoom, - PalaceOfDarknessMapChestFairyRoom, - CaveHookshotCaveBackdoor, - EmptyCloneRoom0x2D, - IcePalaceCompassRoom, - CaveKakarikoWellHP, - AgahnimsTowerMaidenSacrificeChamber, - TowerOfHeraHardhatBeetleRoom, - HyruleCastleSewerKeyChestRoom, - DesertPalaceLanmolas, - SwampPalacePushBlockPuzzlePreBigKeyRoom, - SwampPalaceBigKeyBsRoom, - SwampPalaceBigChestRoom, - SwampPalaceMapChestWaterFillRoom, - SwampPalaceKeyPotRoom, - SkullWoodsGibdoKeyMothulaHoleRoom, - PalaceOfDarknessBombableFloorRoom, - PalaceOfDarknessSpikeBlockConveyorRoom, - CaveHookshotCave, - GanonsTowerTorchRoom2, - IcePalaceStalfosKnightConveyorHellway, - IcePalaceMapChestRoom, - AgahnimsTowerFinalBridgeRoom, - HyruleCastleFirstDarkRoom, - HyruleCastle6RopesRoom, - DesertPalaceTorchPuzzleMovingWallRoom, - ThievesTownBigChestRoom, - ThievesTownJailCellsRoom, - SwampPalaceCompassChestRoom, - EmptyCloneRoom0x47, - EmptyCloneRoom0x48, - SkullWoodsGibdoTorchPuzzleRoom, - PalaceOfDarknessEntranceRoom, - PalaceOfDarknessWarpsSouthMimicRoom, - GanonsTowerMiniHelmasaurConveyorRoom, - GanonsTowerMoldormRoom, - IcePalaceBombJumpRoom, - IcePalaceCloneRoomFairyRoom, - HyruleCastleWestCorridor, - HyruleCastleThroneRoom, - HyruleCastleEastCorridor, - DesertPalacePopos2BeamosHellwayRoom, - SwampPalaceUpstairsPitsRoom, - CastleSecretEntranceUncleRoom, - SkullWoodsKeyPotTrapRoom, - SkullWoodsBigKeyRoom, - SkullWoodsBigChestRoom, - SkullWoodsFinalSectionEntranceRoom, - PalaceOfDarknessHelmasaurKing, - GanonsTowerSpikePitRoom, - GanonsTowerGanonBallZ, - GanonsTowerGauntlet123, - IcePalaceLonelyFirebar, - IcePalaceHiddenChestSpikeFloorRoom, - HyruleCastleWestEntranceRoom, - HyruleCastleMainEntranceRoom, - HyruleCastleEastEntranceRoom, - DesertPalaceFinalSectionEntranceRoom, - ThievesTownWestAtticRoom, - ThievesTownEastAtticRoom, - SwampPalaceHiddenChestHiddenDoorRoom, - SkullWoodsCompassChestRoom, - SkullWoodsKeyChestTrapRoom, - EmptyCloneRoom0x69, - PalaceOfDarknessRupeeRoom, - GanonsTowerMimicsRoom, - GanonsTowerLanmolasRoom, - GanonsTowerGauntlet45, - IcePalacePengatorsRoom, - EmptyCloneRoom0x6F, - HyruleCastleSmallCorridorToJailCells, - HyruleCastleBoomerangChestRoom, - HyruleCastleMapChestRoom, - DesertPalaceBigChestRoom, - DesertPalaceMapChestRoom, - DesertPalaceBigKeyChestRoom, - SwampPalaceWaterDrainRoom, - TowerOfHeraEntranceRoom, - EmptyCloneRoom0x78, - EmptyCloneRoom0x79, - EmptyCloneRoom0x7A, - GanonsTowerMisc, - GanonsTowerEastSideCollapsingBridgeExplodingWallRoom, - GanonsTowerWinderWarpMazeRoom, - IcePalaceHiddenChestBombableFloorRoom, - IcePalaceBigSpikeTrapsRoom, - HyruleCastleJailCellRoom, - HyruleCastleNextToChasmRoom, - HyruleCastleBasementChasmRoom, - DesertPalaceWestEntranceRoom, - DesertPalaceMainEntranceRoom, - DesertPalaceEastEntranceRoom, - EmptyCloneRoom0x86, - TowerOfHeraTileRoom, - EmptyCloneRoom0x88, - EasternPalaceFairyRoom, - EmptyCloneRoom0x8A, - GanonsTowerBlockPuzzleSpikeSkipMapChestRoom, - GanonsTowerEastAndWestDownstairsBigChestRoom, - GanonsTowerTileTorchPuzzleRoom, - IcePalaceBlobsWithTetrisBarrier, - EmptyCloneRoom0x8F, - MiseryMireVitreous, - MiseryMireFinalSwitchRoom, - MiseryMireDarkBombWallSwitchesRoom, - MiseryMireDarkCaneFLoorSwitchPuzzleRoom, - EmptyCloneRoom0x94, - GanonsTowerFinalCollapsingBridgeRoom, - GanonsTowerTorches1Room, - MiseryMireTorchPuzzleMovingWallRoom, - MiseryMireEntranceRoom, - EasternPalaceEyegoreKeyRoom, - EmptyCloneRoom0x9A, - GanonsTowerManySpikesWarpMazeRoom, - GanonsTowerInvisibleFloorMazeRoom, - GanonsTowerCompassChestInvisibleFloorRoom, - IcePalaceBigChestRoom, - IcePalaceIceFloorPotsKeyAndSwitch, - MiseryMirePreVitreousRoom, - MiseryMireFishRoom, - MiseryMireBridgeKeyChestRoom, - MiseryMireEmptyLConnectingRoom, - TurtleRockTrinexx, - GanonsTowerWizzrobesRooms, - GanonsTowerMoldormFallRoom, - TowerOfHeraFairyRoom, - EasternPalaceStalfosSpawnRoom, - EasternPalaceBigChestRoom, - EasternPalaceMapChestRoom, - ThievesTownMovingSpikesKeyPotRoom, - ThievesTownBlindTheThief, - EmptyCloneRoom0xAD, - IcePalaceIceTRoom, - IcePalaceIceBridgeRoom, - AgahnimsTowerCircleOfPots, - MiseryMireHourglassRoom, - MiseryMireSlugRoom, - MiseryMireSpikeKeyChestRoom, - TurtleRockPreTrinexxRoom, - TurtleRockDarkMaze, - TurtleRockChainChompsRoom, - TurtleRockMapChestKeyChestRollerRoom, - EasternPalaceBigKeyRoom, - EasternPalaceLobbyCannonballsRoom, - EasternPalaceDarkAntifairyKeyPotRoom, - ThievesTownHellway, - ThievesTownConveyorToilet, - EmptyCloneRoom0xBD, - IcePalaceBlockPuzzleRoom, - IcePalaceCloneRoomSwitchRoom, - AgahnimsTowerDarkBridgeRoom, - MiseryMireCompassChestTileRoom, - MiseryMireBigHubRoom, - MiseryMireBigChestRoom, - TurtleRockFinalCrystalSwitchPuzzleRoom, - TurtleRockLaserBridge, - TurtleRockSomariaHub, - TurtleRockTorchPuzzle, - EasternPalaceArmosKnights, - EasternPalaceEntranceRoom, - UnknownRoom, - ThievesTownNorthWestEntranceRoom, - ThievesTownNorthEastEntranceRoom, - EmptyCloneRoom0xCD, - IcePalaceHoleToKholdstareRoom, - EmptyCloneRoom0xCF, - AgahnimsTowerDarkMaze, - MiseryMireConveyorSlugBigKeyRoom, - MiseryMireWizzrobesRoom, - EmptyCloneRoom0xD3, - EmptyCloneRoom0xD4, - TurtleRockLaserKeyRoom, - TurtleRockEntranceRoom, - EmptyCloneRoom0xD7, - EasternPalacePreArmosKnightsRoom, - EasternPalaceCannonballRoom, - EasternPalacePotSwitchRoom, - ThievesTownSouthWestEntranceRoom, - ThievesTownSouthEastEntranceRoom, - EmptyCloneRoom0xDD, - IcePalaceKholdstare, - CaveBackwardsDeathMountainTopFloor, - AgahnimsTowerEntranceRoom, - CaveLostWoodsHP, - CaveLumberjacksTreeHP, - CaveMagicBat, - CaveLostOldManHouse, - CaveLostOldManHouseBack, - Cave0xE6, - Cave0xE7, - CaveSuperBunnyTop, - EmptyCloneRoom0xE9, - CaveSpectacleRockHP, - CaveBumperCaveTop, - EmptyCloneRoom0xEC, - Cave0xED, - CaveSpiralCave, - CaveCrystalSwitch5ChestsRoom, - CaveLostOldManStartingCaveBottom, - CaveLostOldManStartingCaveTop, - HouseOldWomanNextDoor, - HouseOldWomanSahasrahlasWifeMaybe, - HouseAngryBrothersWest, - HouseAngryBrothersEast, - EmptyCloneRoom0xF6, - EmptyCloneRoom0xF7, - CaveSuperBunnyBottom, - CaveSpectacleRockExit, - CaveSpectacleRockPrizeEntrance, - CaveBumperCaveBottom, - EmptyCloneRoom0xFC, - Cave0xFD, - CaveSpiralCaveExit, - CaveParadoxCaveMiddleEntrance, - ShopInLostWoods0x100, - ScaredLadyHouses, - SickKid, - InnBushHouse, - LinksHouse, - ShabadooHouse, - ChestGameBombHouse, - LibraryBombFarmRoom, - ChickenHouse, - WitchHut, - Aginah, - Dam, - MimicCave, - CaveOutsideMiseryMire, - CaveDarkHyliaLedgeHint, - ShopDarkWorldShops, - ShopFireShieldShop, - ArcherGame, - CaveShop, - KingsTomb, - WaterfallCaveMireHint, - BigFairy, - FatFairy, - SpikeCave, - ChestGamblingGame, - BlindsHouse, - Mutant, - MirrorCaveGroveAndTomb, - BombShop, - BlindsBasement, - HypeCave, - KakarikoShopLumberjackHouse, - IceRodCave, - SmithHouse, - FortuneTellers, - MiniMoldormCave, - FiftyRupeeCaveBonkCave, - TwentyRupeeCaveDarkHyliaSpikeHintCave, - CheckerBoardCave, - HammerPegCave, +pub enum SuperTile { + Ganon = 0x0, + HyruleCastleNorthCorridor = 0x1, + HyruleCastleSwitchRoom = 0x2, + HoulihanRoom = 0x3, + TurtleRockCrystalRollerRoom = 0x4, + EmptyCloneRoom0x05 = 0x5, + SwampPalaceArrghus = 0x6, + TowerOfHeraMoldorm = 0x7, + CaveHealingFairy = 0x8, + PalaceOfDarknessMedusaChestRoom = 0x9, + PalaceOfDarknessStalfossTrapRoom = 0xa, + PalaceOfDarknessTurtleRoom = 0xb, + GanonsTowerEntranceRoom = 0xc, + GanonsTowerAgahnim2 = 0xd, + IcePalaceEntranceRoom = 0xe, + EmptyCloneRoom0x0F = 0xf, + GanonEvacuationRoute = 0x10, + HyruleCastleBombableStockRoom = 0x11, + InsideSanctuary = 0x12, + TurtleRockHokkuBokkuKeyRoom2 = 0x13, + TurtleRockBigKeyRoom = 0x14, + TurtleRockUselessTubes = 0x15, + SwampPalaceSwimmingTreadmill = 0x16, + TowerOfHeraMoldormFallRoom = 0x17, + Cave0x18BigFairyDropEntrance = 0x18, + PalaceOfDarknessDarkMaze = 0x19, + PalaceOfDarknessBigChestRoom = 0x1a, + PalaceOfDarknessMimicsMovingWallRoom = 0x1b, + GanonsTowerIceArmos = 0x1c, + GanonsTowerFinalHallway = 0x1d, + IcePalaceBombFloorBariRoom = 0x1e, + IcePalacePengatorBigKeyRoom = 0x1f, + AgahnimsTowerAgahnim = 0x20, + HyruleCastleKeyRatRoom = 0x21, + HyruleCastleSewerTextTriggerRoom = 0x22, + TurtleRockWestExitToBalcony = 0x23, + TurtleRockDoubleHokkuBokkuBigChestRoom = 0x24, + EmptyCloneRoom0x25 = 0x25, + SwampPalaceStatueRoom = 0x26, + TowerOfHeraBigChest = 0x27, + SwampPalaceEntranceRoom = 0x28, + SkullWoodsMothula = 0x29, + PalaceOfDarknessBigHubRoom = 0x2a, + PalaceOfDarknessMapChestFairyRoom = 0x2b, + CaveHookshotCaveBackdoor = 0x2c, + EmptyCloneRoom0x2D = 0x2d, + IcePalaceCompassRoom = 0x2e, + CaveKakarikoWellHP = 0x2f, + AgahnimsTowerMaidenSacrificeChamber = 0x30, + TowerOfHeraHardhatBeetleRoom = 0x31, + HyruleCastleSewerKeyChestRoom = 0x32, + DesertPalaceLanmolas = 0x33, + SwampPalacePushBlockPuzzlePreBigKeyRoom = 0x34, + SwampPalaceBigKeyBsRoom = 0x35, + SwampPalaceBigChestRoom = 0x36, + SwampPalaceMapChestWaterFillRoom = 0x37, + SwampPalaceKeyPotRoom = 0x38, + SkullWoodsGibdoKeyMothulaHoleRoom = 0x39, + PalaceOfDarknessBombableFloorRoom = 0x3a, + PalaceOfDarknessSpikeBlockConveyorRoom = 0x3b, + CaveHookshotCave = 0x3c, + GanonsTowerTorchRoom2 = 0x3d, + IcePalaceStalfosKnightConveyorHellway = 0x3e, + IcePalaceMapChestRoom = 0x3f, + AgahnimsTowerFinalBridgeRoom = 0x40, + HyruleCastleFirstDarkRoom = 0x41, + HyruleCastle6RopesRoom = 0x42, + DesertPalaceTorchPuzzleMovingWallRoom = 0x43, + ThievesTownBigChestRoom = 0x44, + ThievesTownJailCellsRoom = 0x45, + SwampPalaceCompassChestRoom = 0x46, + EmptyCloneRoom0x47 = 0x47, + EmptyCloneRoom0x48 = 0x48, + SkullWoodsGibdoTorchPuzzleRoom = 0x49, + PalaceOfDarknessEntranceRoom = 0x4a, + PalaceOfDarknessWarpsSouthMimicRoom = 0x4b, + GanonsTowerMiniHelmasaurConveyorRoom = 0x4c, + GanonsTowerMoldormRoom = 0x4d, + IcePalaceBombJumpRoom = 0x4e, + IcePalaceCloneRoomFairyRoom = 0x4f, + HyruleCastleWestCorridor = 0x50, + HyruleCastleThroneRoom = 0x51, + HyruleCastleEastCorridor = 0x52, + DesertPalacePopos2BeamosHellwayRoom = 0x53, + SwampPalaceUpstairsPitsRoom = 0x54, + CastleSecretEntranceUncleRoom = 0x55, + SkullWoodsKeyPotTrapRoom = 0x56, + SkullWoodsBigKeyRoom = 0x57, + SkullWoodsBigChestRoom = 0x58, + SkullWoodsFinalSectionEntranceRoom = 0x59, + PalaceOfDarknessHelmasaurKing = 0x5a, + GanonsTowerSpikePitRoom = 0x5b, + GanonsTowerGanonBallZ = 0x5c, + GanonsTowerGauntlet123 = 0x5d, + IcePalaceLonelyFirebar = 0x5e, + IcePalaceHiddenChestSpikeFloorRoom = 0x5f, + HyruleCastleWestEntranceRoom = 0x60, + HyruleCastleMainEntranceRoom = 0x61, + HyruleCastleEastEntranceRoom = 0x62, + DesertPalaceFinalSectionEntranceRoom = 0x63, + ThievesTownWestAtticRoom = 0x64, + ThievesTownEastAtticRoom = 0x65, + SwampPalaceHiddenChestHiddenDoorRoom = 0x66, + SkullWoodsCompassChestRoom = 0x67, + SkullWoodsKeyChestTrapRoom = 0x68, + EmptyCloneRoom0x69 = 0x69, + PalaceOfDarknessRupeeRoom = 0x6a, + GanonsTowerMimicsRoom = 0x6b, + GanonsTowerLanmolasRoom = 0x6c, + GanonsTowerGauntlet45 = 0x6d, + IcePalacePengatorsRoom = 0x6e, + EmptyCloneRoom0x6F = 0x6f, + HyruleCastleSmallCorridorToJailCells = 0x70, + HyruleCastleBoomerangChestRoom = 0x71, + HyruleCastleMapChestRoom = 0x72, + DesertPalaceBigChestRoom = 0x73, + DesertPalaceMapChestRoom = 0x74, + DesertPalaceBigKeyChestRoom = 0x75, + SwampPalaceWaterDrainRoom = 0x76, + TowerOfHeraEntranceRoom = 0x77, + EmptyCloneRoom0x78 = 0x78, + EmptyCloneRoom0x79 = 0x79, + EmptyCloneRoom0x7A = 0x7a, + GanonsTowerMisc = 0x7b, + GanonsTowerEastSideCollapsingBridgeExplodingWallRoom = 0x7c, + GanonsTowerWinderWarpMazeRoom = 0x7d, + IcePalaceHiddenChestBombableFloorRoom = 0x7e, + IcePalaceBigSpikeTrapsRoom = 0x7f, + HyruleCastleJailCellRoom = 0x80, + HyruleCastleNextToChasmRoom = 0x81, + HyruleCastleBasementChasmRoom = 0x82, + DesertPalaceWestEntranceRoom = 0x83, + DesertPalaceMainEntranceRoom = 0x84, + DesertPalaceEastEntranceRoom = 0x85, + EmptyCloneRoom0x86 = 0x86, + TowerOfHeraTileRoom = 0x87, + EmptyCloneRoom0x88 = 0x88, + EasternPalaceFairyRoom = 0x89, + EmptyCloneRoom0x8A = 0x8a, + GanonsTowerBlockPuzzleSpikeSkipMapChestRoom = 0x8b, + GanonsTowerEastAndWestDownstairsBigChestRoom = 0x8c, + GanonsTowerTileTorchPuzzleRoom = 0x8d, + IcePalaceBlobsWithTetrisBarrier = 0x8e, + EmptyCloneRoom0x8F = 0x8f, + MiseryMireVitreous = 0x90, + MiseryMireFinalSwitchRoom = 0x91, + MiseryMireDarkBombWallSwitchesRoom = 0x92, + MiseryMireDarkCaneFLoorSwitchPuzzleRoom = 0x93, + EmptyCloneRoom0x94 = 0x94, + GanonsTowerFinalCollapsingBridgeRoom = 0x95, + GanonsTowerTorches1Room = 0x96, + MiseryMireTorchPuzzleMovingWallRoom = 0x97, + MiseryMireEntranceRoom = 0x98, + EasternPalaceEyegoreKeyRoom = 0x99, + EmptyCloneRoom0x9A = 0x9a, + GanonsTowerManySpikesWarpMazeRoom = 0x9b, + GanonsTowerInvisibleFloorMazeRoom = 0x9c, + GanonsTowerCompassChestInvisibleFloorRoom = 0x9d, + IcePalaceBigChestRoom = 0x9e, + IcePalaceIceFloorPotsKeyAndSwitch = 0x9f, + MiseryMirePreVitreousRoom = 0xa0, + MiseryMireFishRoom = 0xa1, + MiseryMireBridgeKeyChestRoom = 0xa2, + MiseryMireEmptyLConnectingRoom = 0xa3, + TurtleRockTrinexx = 0xa4, + GanonsTowerWizzrobesRooms = 0xa5, + GanonsTowerMoldormFallRoom = 0xa6, + TowerOfHeraFairyRoom = 0xa7, + EasternPalaceStalfosSpawnRoom = 0xa8, + EasternPalaceBigChestRoom = 0xa9, + EasternPalaceMapChestRoom = 0xaa, + ThievesTownMovingSpikesKeyPotRoom = 0xab, + ThievesTownBlindTheThief = 0xac, + EmptyCloneRoom0xAD = 0xad, + IcePalaceIceTRoom = 0xae, + IcePalaceIceBridgeRoom = 0xaf, + AgahnimsTowerCircleOfPots = 0xb0, + MiseryMireHourglassRoom = 0xb1, + MiseryMireSlugRoom = 0xb2, + MiseryMireSpikeKeyChestRoom = 0xb3, + TurtleRockPreTrinexxRoom = 0xb4, + TurtleRockDarkMaze = 0xb5, + TurtleRockChainChompsRoom = 0xb6, + TurtleRockMapChestKeyChestRollerRoom = 0xb7, + EasternPalaceBigKeyRoom = 0xb8, + EasternPalaceLobbyCannonballsRoom = 0xb9, + EasternPalaceDarkAntifairyKeyPotRoom = 0xba, + ThievesTownHellway = 0xbb, + ThievesTownConveyorToilet = 0xbc, + EmptyCloneRoom0xBD = 0xbd, + IcePalaceBlockPuzzleRoom = 0xbe, + IcePalaceCloneRoomSwitchRoom = 0xbf, + AgahnimsTowerDarkBridgeRoom = 0xc0, + MiseryMireCompassChestTileRoom = 0xc1, + MiseryMireBigHubRoom = 0xc2, + MiseryMireBigChestRoom = 0xc3, + TurtleRockFinalCrystalSwitchPuzzleRoom = 0xc4, + TurtleRockLaserBridge = 0xc5, + TurtleRockSomariaHub = 0xc6, + TurtleRockTorchPuzzle = 0xc7, + EasternPalaceArmosKnights = 0xc8, + EasternPalaceEntranceRoom = 0xc9, + UnknownRoom = 0xca, + ThievesTownNorthWestEntranceRoom = 0xcb, + ThievesTownNorthEastEntranceRoom = 0xcc, + EmptyCloneRoom0xCD = 0xcd, + IcePalaceHoleToKholdstareRoom = 0xce, + EmptyCloneRoom0xCF = 0xcf, + AgahnimsTowerDarkMaze = 0xd0, + MiseryMireConveyorSlugBigKeyRoom = 0xd1, + MiseryMireWizzrobesRoom = 0xd2, + EmptyCloneRoom0xD3 = 0xd3, + EmptyCloneRoom0xD4 = 0xd4, + TurtleRockLaserKeyRoom = 0xd5, + TurtleRockEntranceRoom = 0xd6, + EmptyCloneRoom0xD7 = 0xd7, + EasternPalacePreArmosKnightsRoom = 0xd8, + EasternPalaceCannonballRoom = 0xd9, + EasternPalacePotSwitchRoom = 0xda, + ThievesTownSouthWestEntranceRoom = 0xdb, + ThievesTownSouthEastEntranceRoom = 0xdc, + EmptyCloneRoom0xDD = 0xdd, + IcePalaceKholdstare = 0xde, + CaveBackwardsDeathMountainTopFloor = 0xdf, + AgahnimsTowerEntranceRoom = 0xe0, + CaveLostWoodsHP = 0xe1, + CaveLumberjacksTreeHP = 0xe2, + CaveMagicBat = 0xe3, + CaveLostOldManHouse = 0xe4, + CaveLostOldManHouseBack = 0xe5, + Cave0xE6 = 0xe6, + Cave0xE7 = 0xe7, + CaveSuperBunnyTop = 0xe8, + EmptyCloneRoom0xE9 = 0xe9, + CaveSpectacleRockHP = 0xea, + CaveBumperCaveTop = 0xeb, + EmptyCloneRoom0xEC = 0xec, + Cave0xED = 0xed, + CaveSpiralCave = 0xee, + CaveCrystalSwitch5ChestsRoom = 0xef, + CaveLostOldManStartingCaveBottom = 0xf0, + CaveLostOldManStartingCaveTop = 0xf1, + HouseOldWomanNextDoor = 0xf2, + HouseOldWomanSahasrahlasWifeMaybe = 0xf3, + HouseAngryBrothersWest = 0xf4, + HouseAngryBrothersEast = 0xf5, + EmptyCloneRoom0xF6 = 0xf6, + EmptyCloneRoom0xF7 = 0xf7, + CaveSuperBunnyBottom = 0xf8, + CaveSpectacleRockExit = 0xf9, + CaveSpectacleRockPrizeEntrance = 0xfa, + CaveBumperCaveBottom = 0xfb, + EmptyCloneRoom0xFC = 0xfc, + Cave0xFD = 0xfd, + CaveSpiralCaveExit = 0xfe, + CaveParadoxCaveMiddleEntrance = 0xff, + ShopInLostWoods0x100 = 0x100, + ScaredLadyHouses = 0x101, + SickKid = 0x102, + InnBushHouse = 0x103, + InsideLinksHouse = 0x104, + ShabadooHouse = 0x105, + ChestGameBombHouse = 0x106, + LibraryBombFarmRoom = 0x107, + ChickenHouse = 0x108, + InsideWitchHut = 0x109, + Aginah = 0x10a, + Dam = 0x10b, + MimicCave = 0x10c, + CaveOutsideMiseryMire = 0x10d, + CaveDarkHyliaLedgeHint = 0x10e, + ShopDarkWorldShops = 0x10f, + ShopFireShieldShop = 0x110, + ArcherGame = 0x111, + CaveShop = 0x112, + KingsTomb = 0x113, + WaterfallCaveMireHint = 0x114, + BigFairy = 0x115, + FatFairy = 0x116, + SpikeCave = 0x117, + ChestGamblingGame = 0x118, + BlindsHouse = 0x119, + Mutant = 0x11a, + MirrorCaveGroveAndTomb = 0x11b, + InsideBombShop = 0x11c, + BlindsBasement = 0x11d, + HypeCave = 0x11e, + KakarikoShopLumberjackHouse = 0x11f, + IceRodCave = 0x120, + SmithHouse = 0x121, + FortuneTellers = 0x122, + MiniMoldormCave = 0x123, + FiftyRupeeCaveBonkCave = 0x124, + TwentyRupeeCaveDarkHyliaSpikeHintCave = 0x125, + CheckerBoardCave = 0x126, + HammerPegCave = 0x127, + LostWoods = 0x8000, + LumberjackHouse = 0x8002, + DeathMountainWest = 0x8003, + DeathMountainEast = 0x8005, + DeathMountainTurtleWarp = 0x8007, + EntranceDeathMountain = 0x800a, + EntranceZorasDomain = 0x800f, + PathFromLostWoodsToKakariko = 0x8010, + KakarikoFortuneTeller = 0x8011, + NorthWestWhirlpoolWarp = 0x8012, + Sanctuary = 0x8013, + Graveyard = 0x8014, + River = 0x8015, + WitchHut = 0x8016, + EastOfWitchHut = 0x8017, + Kakariko = 0x8018, + CastleWoods = 0x801a, + HyruleCastle = 0x801b, + WitchGraveyardBridge = 0x801d, + EasternPalace = 0x801e, + Smithy = 0x8022, + StonyFieldNorth = 0x8025, + KakarikoMazeRace = 0x8028, + KakarikoLibrary = 0x8029, + HauntedGrove = 0x802a, + BetweenGroveAndLinksHouse = 0x802b, + LinksHouse = 0x802c, + StonyFieldSouth = 0x802d, + BetweenLakeHyliaAndEasternPalace = 0x802e, + FluteSpot5 = 0x802f, + Desert = 0x8030, + SouthOfHauntedGrove = 0x8032, + NorthwestSwamp = 0x8033, + NortheastSwamp = 0x8034, + LakeHylia = 0x8035, + IceCave = 0x8037, + DesertPass = 0x803a, + SouthwestSwamp = 0x803b, + SoutheastSwamp = 0x803c, + SouthShore = 0x803f, + ShullWoods = 0x8040, + DarkLumberjackShop = 0x8042, + DarkDeathMountainWest = 0x8043, + DarkDeathMountainEast = 0x8045, + TurtleRock = 0x8047, + BumperCave = 0x804a, + Catfish = 0x804f, + PathFromSkullWoodsToVillage = 0x8050, + OutcastFortuneTeller = 0x8051, + NorthWestPond = 0x8052, + DarkChapel = 0x8053, + DarkGraveyard = 0x8054, + DarkRiver = 0x8055, + DarkWitchHut = 0x8056, + EastOfDarkWitchHut = 0x8057, + VillageOfOutcasts = 0x8058, + FireShieldShopForest = 0x805a, + Pyramid = 0x805b, + BrokenBridge = 0x805d, + PalaceOfDarkness = 0x805e, + PurpleChestHouse = 0x8062, + DarkStonyFieldNorth = 0x8065, + DiggingGame = 0x8068, + TrappedFrog = 0x8069, + StumpysGrove = 0x806a, + BetweenGroveAndBombShop = 0x806b, + BombShop = 0x806c, + HammerPegBridge = 0x806d, + BetweenPODAndIceLake = 0x806e, + DarkFluteSpot5 = 0x806f, + MiseryMire = 0x8070, + SouthOfStumpysGrove = 0x8072, + DarkNorthwestSwamp = 0x8073, + DarkNortheastSwamp = 0x8074, + IceLake = 0x8075, + DarkIceCave = 0x8077, + BoxCanyonBetweenMireAndSwamp = 0x807a, + DarkSouthwestSwamp = 0x807b, + DarkSoutheastSwamp = 0x807c, + DarkSouthShore = 0x807f, + MasterSwordGladeAndUnderBridge = 0x8080, + ZorasDomain = 0x8081, } -use Room::*; +use SuperTile::*; lazy_static! { - pub static ref ROOM_NAMES: HashMap = { + pub static ref ROOM_NAMES: HashMap = { let names = [ (Ganon, "Ganon"), @@ -752,7 +834,7 @@ lazy_static! { }; } -pub const NEED_KILLABLE_ROOMS: [Room; 39] = [ +pub const NEED_KILLABLE_ROOMS: [SuperTile; 39] = [ EasternPalaceBigKeyRoom, // For anti-fairy circle around pot switch PalaceOfDarknessTurtleRoom, PalaceOfDarknessMimicsMovingWallRoom, @@ -797,7 +879,7 @@ pub const NEED_KILLABLE_ROOMS: [Room; 39] = [ // All the rooms that require special handling. /// These rooms need to be locked to GFX ID 28. -pub const FREEZOR_ROOMS: [Room; 5] = [ +pub const FREEZOR_ROOMS: [SuperTile; 5] = [ IcePalaceEntranceRoom, IcePalaceHiddenChestBombableFloorRoom, IcePalaceBlobsWithTetrisBarrier, @@ -806,7 +888,7 @@ pub const FREEZOR_ROOMS: [Room; 5] = [ ]; /// These rooms need to be locked to GFX ID 17. -pub const WATER_ROOMS: [Room; 33] = [ +pub const WATER_ROOMS: [SuperTile; 33] = [ GanonsTowerEntranceRoom, GanonsTowerAgahnim2, SwampPalaceSwimmingTreadmill, @@ -842,12 +924,12 @@ pub const WATER_ROOMS: [Room; 33] = [ GanonsTowerMoldormFallRoom, ]; -pub const SHADOW_ROOMS: [Room; 2] = [ +pub const SHADOW_ROOMS: [SuperTile; 2] = [ IcePalaceStalfosKnightConveyorHellway, IcePalaceIceFloorPotsKeyAndSwitch, ]; -pub const WALL_MASTER_ROOMS: [Room; 5] = [ +pub const WALL_MASTER_ROOMS: [SuperTile; 5] = [ SkullWoodsGibdoKeyMothulaHoleRoom, SkullWoodsGibdoTorchPuzzleRoom, SkullWoodsKeyPotTrapRoom, @@ -855,7 +937,7 @@ pub const WALL_MASTER_ROOMS: [Room; 5] = [ GanonsTowerTileTorchPuzzleRoom, ]; -pub const BUMPER_CRYSTAL_LASER_ROOMS: [Room; 42] = [ +pub const BUMPER_CRYSTAL_LASER_ROOMS: [SuperTile; 42] = [ TurtleRockCrystalRollerRoom, PalaceOfDarknessTurtleRoom, TurtleRockHokkuBokkuKeyRoom2, @@ -900,14 +982,14 @@ pub const BUMPER_CRYSTAL_LASER_ROOMS: [Room; 42] = [ CaveCrystalSwitch5ChestsRoom, ]; -pub const PULL_SWITCH_ROOMS: [Room; 4] = [ +pub const PULL_SWITCH_ROOMS: [SuperTile; 4] = [ HyruleCastleSwitchRoom, SkullWoodsBigChestRoom, ThievesTownWestAtticRoom, Dam, ]; -pub const TONGUE_ROOMS: [Room; 7] = [ +pub const TONGUE_ROOMS: [SuperTile; 7] = [ TurtleRockCrystalRollerRoom, TurtleRockWestExitToBalcony, SwampPalaceBigKeyBsRoom, @@ -917,7 +999,7 @@ pub const TONGUE_ROOMS: [Room; 7] = [ IcePalaceHoleToKholdstareRoom, ]; -pub const NO_STATUE_ROOMS: [Room; 12] = [ +pub const NO_STATUE_ROOMS: [SuperTile; 12] = [ SwampPalaceSwimmingTreadmill, SwampPalaceStatueRoom, SwampPalaceEntranceRoom, @@ -932,19 +1014,19 @@ pub const NO_STATUE_ROOMS: [Room; 12] = [ AgahnimsTowerDarkMaze, ]; -pub const MOVING_CANNONS_ROOMS: [Room; 2] = [ +pub const MOVING_CANNONS_ROOMS: [SuperTile; 2] = [ GanonsTowerGanonBallZ, DesertPalaceBigKeyChestRoom, ]; -pub const CANNON_ROOMS: [Room; 3] = [ +pub const CANNON_ROOMS: [SuperTile; 3] = [ EasternPalaceLobbyCannonballsRoom, EasternPalaceCannonballRoom, MimicCave, // ??? ]; /// These rooms have no enemies to randomize, or are special bosses that can't be switched out. -pub const DONT_RANDOMIZE_ROOMS: [Room; 8] = [ +pub const DONT_RANDOMIZE_ROOMS: [SuperTile; 8] = [ Ganon, HyruleCastleNorthCorridor, HoulihanRoom, @@ -955,7 +1037,7 @@ pub const DONT_RANDOMIZE_ROOMS: [Room; 8] = [ IcePalaceBigSpikeTrapsRoom, // Anything else would be too easy? ]; -pub const NO_SPECIAL_ENEMIES_IN_STANDARD_MODE_ROOMS: [Room; 21] = [ +pub const NO_SPECIAL_ENEMIES_IN_STANDARD_MODE_ROOMS: [SuperTile; 21] = [ HyruleCastleNorthCorridor, HyruleCastleSwitchRoom, HyruleCastleBombableStockRoom, @@ -979,7 +1061,7 @@ pub const NO_SPECIAL_ENEMIES_IN_STANDARD_MODE_ROOMS: [Room; 21] = [ HyruleCastleBasementChasmRoom, ]; -pub const BOSS_ROOMS: [Room; 16] = [ +pub const BOSS_ROOMS: [SuperTile; 16] = [ Ganon, SwampPalaceArrghus, TowerOfHeraMoldorm, @@ -998,7 +1080,7 @@ pub const BOSS_ROOMS: [Room; 16] = [ IcePalaceKholdstare, ]; -pub const DONT_USE_IMMOVABLE_ENEMIES: [Room; 57] = [ +pub const DONT_USE_IMMOVABLE_ENEMIES: [SuperTile; 57] = [ PalaceOfDarknessTurtleRoom, // for single terrorpin in L shaped section SwampPalaceSwimmingTreadmill, PalaceOfDarknessDarkMaze, // top placement will probably block maze @@ -1058,7 +1140,7 @@ pub const DONT_USE_IMMOVABLE_ENEMIES: [Room; 57] = [ MimicCave, ]; -pub const DONT_USE_FLYING_ENEMIES: [Room; 2] = [ +pub const DONT_USE_FLYING_ENEMIES: [SuperTile; 2] = [ MiseryMireWizzrobesRoom, MimicCave, ];