Compare commits
4 commits
4cbc70eeca
...
4972986fee
Author | SHA1 | Date | |
---|---|---|---|
Lyle Mantooth | 4972986fee | ||
Lyle Mantooth | fed38efcc4 | ||
Lyle Mantooth | 76ff991e8f | ||
Lyle Mantooth | 975d6fd420 |
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ pub struct Patch {
|
|||
pub patch_data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct PatchSet {
|
||||
filename: PathBuf,
|
||||
patches: Vec<Patch>,
|
||||
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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<i32>,
|
||||
rom: PathBuf,
|
||||
/// path to the enemizerOptions.json
|
||||
#[clap(short, long)]
|
||||
enemizer: PathBuf,
|
||||
/// seed number
|
||||
#[clap(short, long)]
|
||||
seed: Option<i32>,
|
||||
/// 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<PathBuf>,
|
||||
/// path to the randomizerPatch.json (not used)
|
||||
#[clap(long)]
|
||||
randomizer: Option<PathBuf>,
|
||||
}
|
||||
|
||||
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![];
|
||||
|
|
|
@ -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<u8> 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<u8> 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<AbsorbableType, bool>,
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue