2022-05-29 10:22:34 -04:00
use std ::collections ::BTreeMap ;
use std ::iter ;
2022-06-05 15:57:02 -04:00
use std ::ops ::{ Index , IndexMut , Range } ;
2022-05-23 09:46:44 -04:00
2022-05-29 10:22:34 -04:00
use anyhow ::bail ;
2022-06-04 09:41:29 -04:00
use crate ::asar ::{ pc_to_snes_address , snes_to_pc_address , Symbols } ;
2022-05-29 10:22:34 -04:00
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 ;
2022-06-04 09:41:29 -04:00
pub const ENEMIZER_INFO_VERSION_OFFSET : usize =
ENEMIZER_INFO_SEED_OFFSET + ENEMIZER_INFO_SEED_LENGTH ;
2022-05-29 10:22:34 -04:00
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 ;
2022-06-04 14:39:11 -04:00
pub const CHECKSUM_COMPLIMENT_ADDRESS : usize = 0x7fdc ;
pub const CHECKSUM_ADDRESS : usize = 0x7fde ;
2022-05-29 10:22:34 -04:00
pub const RANDOMIZER_MODE_FLAG : usize = 0x180032 ;
pub struct RomData {
2022-05-30 01:03:30 -04:00
asar_symbols : Symbols ,
2022-05-29 10:22:34 -04:00
spoiler : String ,
2022-06-04 09:39:52 -04:00
pub ( crate ) rom_data : Vec < u8 > ,
2022-05-29 10:22:34 -04:00
patch_data : BTreeMap < usize , u8 > ,
2022-06-04 09:39:52 -04:00
pub ( crate ) seed : i32 ,
2022-05-29 10:22:34 -04:00
}
impl RomData {
pub fn spoiler ( & self ) -> & str {
self . spoiler . as_str ( )
}
2022-06-04 09:39:52 -04:00
pub fn reset_enemizer ( & mut self ) -> i32 {
use crate ::constants ::* ;
let seed = self . get_enemizer_seed ( ) ;
self . rom_data [ ROOM_HEADER_BANK_LOCATION ] = 0x04 ;
2022-06-04 09:41:29 -04:00
let dungeon_header_range =
DUNGEON_HEADER_POINTER_TABLE .. ( DUNGEON_HEADER_POINTER_TABLE + 640 ) ;
2022-06-04 09:39:52 -04:00
self . rom_data [ dungeon_header_range ] . copy_from_slice ( & ORIGINAL_ROOM_POINTERS ) ;
2022-06-04 14:39:11 -04:00
let room_range = 0x5b97 .. ( 0x5b97 + 576 ) ;
2022-06-04 09:39:52 -04:00
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
}
2022-05-29 10:22:34 -04:00
pub fn generate_patch ( & self ) -> Vec < Patch > {
let mut patches = vec! [ ] ;
for ( & addr , & byte ) in self . patch_data . iter ( ) {
2022-06-04 09:41:29 -04:00
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 ) ,
2022-05-29 10:22:34 -04:00
}
}
patches
}
2022-06-04 09:39:52 -04:00
pub fn get_rom_bytes ( & self ) -> & [ u8 ] {
& self . rom_data
}
2022-05-29 10:22:34 -04:00
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 ( ) ] ;
2022-06-04 09:41:29 -04:00
self . patch_data
. extend ( iter ::zip ( range , slice . into_iter ( ) . map ( | & b | b ) ) ) ;
2022-05-29 10:22:34 -04:00
}
pub fn is_enemizer ( & self ) -> bool {
self . rom_data . len ( ) = = ENEMIZER_FILE_LENGTH
2022-06-04 09:41:29 -04:00
& & 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'
2022-05-29 10:22:34 -04:00
}
2022-06-01 09:53:11 -04:00
pub fn is_randomizer ( & self ) -> bool {
let acceptable = [
2022-06-04 09:41:29 -04:00
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
2022-06-01 09:53:11 -04:00
] ;
2022-06-04 09:41:29 -04:00
acceptable
. iter ( )
2022-06-04 14:39:11 -04:00
. any ( | abbr | & abbr [ .. ] = = & self . rom_data [ 0x7fc0 .. 0x7fc2 ] )
2022-06-04 09:41:29 -04:00
| | ( self . rom_data . len ( ) > = 0x20_0000
2022-06-04 14:39:11 -04:00
& & & self . rom_data [ 0x7fc0 .. 0x7fcf ] = = b " ZELDANODENSETSU " )
2022-06-01 09:53:11 -04:00
}
2022-06-04 09:39:52 -04:00
pub fn is_race ( & self ) -> bool {
2022-06-04 09:41:29 -04:00
self . is_randomizer ( )
& & ( & self . rom_data [ 0x180213 .. 0x180214 ] = = & [ 1 , 0 ]
2022-06-04 14:39:11 -04:00
| | & self . rom_data [ 0x7fc0 .. 0x7fca ] = = b " VT TOURNEY " )
2022-06-04 09:39:52 -04:00
}
2022-05-29 10:22:34 -04:00
fn assert_rom_length ( & self ) {
2022-06-04 09:41:29 -04:00
assert! (
self . rom_data . len ( ) > = ENEMIZER_FILE_LENGTH ,
" You need to expand the rom before you can use Enemizer features. "
) ;
2022-05-29 10:22:34 -04:00
}
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 ( ) {
2022-05-30 01:03:30 -04:00
let seed_start = self . asar_symbols [ " enemizer_info_table " ] + ENEMIZER_INFO_SEED_OFFSET ;
2022-05-29 10:22:34 -04:00
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 ( ) ;
2022-05-30 01:03:30 -04:00
let seed_start = self . asar_symbols [ " enemizer_info_table " ] + ENEMIZER_INFO_SEED_OFFSET ;
2022-05-29 10:22:34 -04:00
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 ) ;
}
2022-06-04 09:39:52 -04:00
pub fn expand_rom ( & mut self ) {
self . rom_data . resize ( 0x40_0000 , 0 ) ;
// Update header length.
2022-06-04 14:39:11 -04:00
self . rom_data [ 0x7fd7 ] = 0x0c ;
self . set_patch_bytes ( 0x7fd7 .. 0x7fd8 ) ;
2022-06-04 09:39:52 -04:00
self . set_enemizer_version ( " 6.0.32 " . to_owned ( ) ) ;
}
2022-05-29 10:22:34 -04:00
pub fn get_enemizer_version ( & self ) -> anyhow ::Result < & str > {
if self . is_enemizer ( ) {
2022-06-04 09:41:29 -04:00
let version_start =
self . asar_symbols [ " enemizer_info_table " ] + ENEMIZER_INFO_VERSION_OFFSET ;
2022-05-29 10:22:34 -04:00
let version_end = version_start + ENEMIZER_INFO_VERSION_LENGTH ;
2022-06-04 09:41:29 -04:00
Ok ( std ::str ::from_utf8 (
& self . rom_data [ version_start .. version_end ] ,
) ? )
2022-05-29 10:22:34 -04:00
} 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 ) ;
2022-05-30 01:03:30 -04:00
let version_start = self . asar_symbols [ " enemizer_info_table " ] + ENEMIZER_INFO_VERSION_OFFSET ;
2022-05-29 10:22:34 -04:00
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 )
}
2022-06-04 09:41:29 -04:00
pub fn set_info_flags ( & mut self , flags : OptionFlags ) -> anyhow ::Result < ( ) > {
2022-05-29 10:22:34 -04:00
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. " ) ;
}
2022-05-30 01:03:30 -04:00
let flags_start = self . asar_symbols [ " enemizer_info_table " ] + ENEMIZER_INFO_FLAGS_OFFSET ;
2022-05-29 10:22:34 -04:00
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 ( ) {
2022-05-30 01:03:30 -04:00
let flags_start = self . asar_symbols [ " enemizer_info_table " ] + ENEMIZER_INFO_FLAGS_OFFSET ;
2022-05-29 10:22:34 -04:00
let flags_end = flags_start + ENEMIZER_INFO_FLAGS_LENGTH ;
OptionFlags ::try_from_bytes ( & self . rom_data [ flags_start .. flags_end ] ) . ok ( )
} else {
None
}
}
2022-05-30 01:03:30 -04:00
pub fn new ( asar_symbols : Symbols , rom_data : Vec < u8 > ) -> 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_data ( & mut self , patch : & Patch ) {
2022-05-29 10:22:34 -04:00
self . set_rom_bytes (
& patch . patch_data ,
patch . address .. ( patch . address + patch . patch_data . len ( ) ) ,
) ;
}
2022-05-30 01:03:30 -04:00
pub fn move_room_headers ( & mut self ) {
let table_base = DUNGEON_HEADER_POINTER_TABLE ;
2022-06-04 09:39:52 -04:00
let header_base = self . asar_symbols [ " room_header_table " ] ;
2022-05-30 01:03:30 -04:00
2022-06-04 14:39:11 -04:00
// Change room header bank (at 0xb5e7) to 0x04.
2022-06-04 09:41:29 -04:00
let new_room_bank =
self . rom_data [ self . asar_symbols [ " moved_room_header_bank_value_address " ] ] ;
2022-05-30 01:03:30 -04:00
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 ,
2022-06-04 09:41:29 -04:00
0 ,
2022-05-30 01:03:30 -04:00
] ;
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 ) ;
2022-06-04 09:41:29 -04:00
self . rom_data
. copy_within ( pc_address .. ( pc_address + 14 ) , header_start ) ;
2022-05-30 01:03:30 -04:00
}
// 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. " ) ;
2022-06-04 09:41:29 -04:00
self . rom_data [ ( table_base + ( i * 2 ) ) .. ( table_base + ( i * 2 ) + 1 ) ]
. copy_from_slice ( & snes [ 0 .. 1 ] ) ;
2022-05-30 01:03:30 -04:00
}
}
2022-05-29 10:22:34 -04:00
}
2022-06-05 15:57:02 -04:00
impl Index < usize > for RomData {
type Output = u8 ;
fn index ( & self , index : usize ) -> & u8 {
& self . rom_data [ index ]
}
}
impl IndexMut < usize > for RomData {
fn index_mut ( & mut self , index : usize ) -> & mut u8 {
& mut self . rom_data [ index ]
}
}