Add RomData, OptionFlags, and enums for them.
Fun error handling with automatic, fallible conversion to and from u8s for the enums.
This commit is contained in:
		
							parent
							
								
									c81348fb10
								
							
						
					
					
						commit
						a161d9090e
					
				
					 8 changed files with 860 additions and 6 deletions
				
			
		
							
								
								
									
										33
									
								
								enemize/src/asar.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								enemize/src/asar.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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<String, u32>;
 | 
			
		||||
 | 
			
		||||
impl Symbols {
 | 
			
		||||
    pub fn load(filename: Path) -> anyhow::Result<Symbols> {
 | 
			
		||||
        let file = File::open(filename)?;
 | 
			
		||||
        let mut reader = BufReader::new(file);
 | 
			
		||||
 | 
			
		||||
        reader.lines().filter_map(|l| l.ok().and_then(|line| {
 | 
			
		||||
            let words: Vec<String> = 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()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										48
									
								
								enemize/src/bosses/mod.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								enemize/src/bosses/mod.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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<u8> for BossType {
 | 
			
		||||
    type Error = InvalidEnumError<Self>;
 | 
			
		||||
 | 
			
		||||
    fn try_from(byte: u8) -> Result<Self, Self::Error> {
 | 
			
		||||
        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))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										12
									
								
								enemize/src/constants.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								enemize/src/constants.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			@ -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<T>(PhantomData<T>);
 | 
			
		||||
 | 
			
		||||
#[derive(Deserialize, Serialize)]
 | 
			
		||||
pub struct Patch {
 | 
			
		||||
    pub address: usize,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										571
									
								
								enemize/src/option_flags.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										571
									
								
								enemize/src/option_flags.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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<u8> for RandomizeEnemiesType {
 | 
			
		||||
    type Error = InvalidEnumError<Self>;
 | 
			
		||||
 | 
			
		||||
    fn try_from(byte: u8) -> Result<Self, Self::Error> {
 | 
			
		||||
        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<u8> for RandomizeEnemyHpType {
 | 
			
		||||
    type Error = InvalidEnumError<Self>;
 | 
			
		||||
 | 
			
		||||
    fn try_from(byte: u8) -> Result<Self, Self::Error> {
 | 
			
		||||
        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<u8> for RandomizeBossesType {
 | 
			
		||||
    type Error = InvalidEnumError<Self>;
 | 
			
		||||
 | 
			
		||||
    fn try_from(byte: u8) -> Result<Self, Self::Error> {
 | 
			
		||||
        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<u8> for SwordType {
 | 
			
		||||
    type Error = InvalidEnumError<Self>;
 | 
			
		||||
 | 
			
		||||
    fn try_from(byte: u8) -> Result<Self, Self::Error> {
 | 
			
		||||
        match byte {
 | 
			
		||||
            0 => Ok(Self::Normal),
 | 
			
		||||
            _ => Err(InvalidEnumError(PhantomData))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub enum ShieldType {
 | 
			
		||||
    Normal
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl TryFrom<u8> for ShieldType {
 | 
			
		||||
    type Error = InvalidEnumError<Self>;
 | 
			
		||||
 | 
			
		||||
    fn try_from(byte: u8) -> Result<Self, Self::Error> {
 | 
			
		||||
        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<u8> for AbsorbableType {
 | 
			
		||||
    type Error = InvalidEnumError<Self>;
 | 
			
		||||
 | 
			
		||||
    fn try_from(byte: u8) -> Result<Self, Self::Error> {
 | 
			
		||||
        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<u8> for HeartBeepSpeed {
 | 
			
		||||
    type Error = InvalidEnumError<Self>;
 | 
			
		||||
 | 
			
		||||
    fn try_from(byte: u8) -> Result<Self, Self::Error> {
 | 
			
		||||
        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<u8> for BeeLevel {
 | 
			
		||||
    type Error = InvalidEnumError<Self>;
 | 
			
		||||
 | 
			
		||||
    fn try_from(byte: u8) -> Result<Self, Self::Error> {
 | 
			
		||||
        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<AbsorbableType, 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_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<Self> {
 | 
			
		||||
        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<u8> {
 | 
			
		||||
        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(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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<u8>,
 | 
			
		||||
    patch_data: BTreeMap<usize, u8>,
 | 
			
		||||
    seed: i32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RomData {
 | 
			
		||||
    pub fn spoiler(&self) -> &str {
 | 
			
		||||
        self.spoiler.as_str()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn generate_patch(&self) -> Vec<Patch> {
 | 
			
		||||
        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<usize>) {
 | 
			
		||||
        self.rom_data.splice(range, bytes.into_iter().map(|&b| b));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn set_patch_bytes(&mut self, range: Range<usize>) {
 | 
			
		||||
        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<i32> {
 | 
			
		||||
        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<OptionFlags> {
 | 
			
		||||
        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<u8>) {
 | 
			
		||||
        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()),
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue