Compare commits
6 commits
9cb0e0b2bf
...
4cbc70eeca
Author | SHA1 | Date | |
---|---|---|---|
Lyle Mantooth | 4cbc70eeca | ||
Lyle Mantooth | 034af7fa92 | ||
Lyle Mantooth | 27d5a4d6c0 | ||
Lyle Mantooth | 1113adca98 | ||
Lyle Mantooth | 87b0476b7e | ||
Lyle Mantooth | 8901e0e8b0 |
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
||||||
/target
|
/target
|
||||||
exported_symbols.txt
|
asar_symbols.txt
|
||||||
patchData.json
|
base_patch.json
|
||||||
|
enemizer_base_patch.json
|
||||||
|
|
|
@ -3,9 +3,9 @@ use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use enemize::PatchSet;
|
|
||||||
use enemize::asar;
|
use enemize::asar;
|
||||||
use enemize::rom::RomData;
|
use enemize::rom::RomData;
|
||||||
|
use enemize::PatchSet;
|
||||||
|
|
||||||
fn main() -> Result<(), anyhow::Error> {
|
fn main() -> Result<(), anyhow::Error> {
|
||||||
let mut args = env::args();
|
let mut args = env::args();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{BufReader, BufRead};
|
use std::io::{BufRead, BufReader};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
pub type Symbols = HashMap<String, usize>;
|
pub type Symbols = HashMap<String, usize>;
|
||||||
|
@ -9,28 +9,35 @@ pub fn load_symbols(filename: &Path) -> anyhow::Result<Symbols> {
|
||||||
let file = File::open(filename)?;
|
let file = File::open(filename)?;
|
||||||
let reader = BufReader::new(file);
|
let reader = BufReader::new(file);
|
||||||
|
|
||||||
let symbols = reader.lines().filter_map(|l| l.ok().and_then(|line| {
|
let symbols = reader
|
||||||
let mut words = line.split_ascii_whitespace();
|
.lines()
|
||||||
match (words.next(), words.next(), words.next()) {
|
.filter_map(|l| {
|
||||||
// Get only two-word lines.
|
l.ok()
|
||||||
(Some(address), Some(symbol), None) =>
|
.and_then(|line| {
|
||||||
Some((symbol.to_owned(), address.to_owned())),
|
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
|
_ => None,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.and_then(|(symbol, mut address)| {
|
.and_then(|(symbol, mut address)| {
|
||||||
if let Some(colon_at) = address.find(':') {
|
if let Some(colon_at) = address.find(':') {
|
||||||
address.remove(colon_at);
|
address.remove(colon_at);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter out the ones that don't have a hexadecimal number as the first word.
|
// 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 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))
|
Some((symbol, pc_address))
|
||||||
})).collect();
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
Ok(symbols)
|
Ok(symbols)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::InvalidEnumError;
|
use crate::InvalidEnumError;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
|
#[serde(into = "u8", try_from = "u8")]
|
||||||
pub enum BossType {
|
pub enum BossType {
|
||||||
Kholdstare,
|
Kholdstare,
|
||||||
Moldorm,
|
Moldorm,
|
||||||
|
@ -22,6 +25,29 @@ pub enum BossType {
|
||||||
NoBoss = 255,
|
NoBoss = 255,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<BossType> 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<u8> for BossType {
|
impl TryFrom<u8> for BossType {
|
||||||
type Error = InvalidEnumError<Self>;
|
type Error = InvalidEnumError<Self>;
|
||||||
|
|
||||||
|
@ -41,8 +67,7 @@ impl TryFrom<u8> for BossType {
|
||||||
11 => Ok(Self::Agahnim2),
|
11 => Ok(Self::Agahnim2),
|
||||||
12 => Ok(Self::Ganon),
|
12 => Ok(Self::Ganon),
|
||||||
255 => Ok(Self::NoBoss),
|
255 => Ok(Self::NoBoss),
|
||||||
_ => Err(InvalidEnumError(PhantomData))
|
_ => Err(InvalidEnumError(PhantomData)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,3 +10,105 @@ pub const NEW_BOSS_GRAPHICS: usize = 0x1B0000;
|
||||||
pub const RANDOM_SPRITE_GRAPHICS: usize = 0x300000;
|
pub const RANDOM_SPRITE_GRAPHICS: usize = 0x300000;
|
||||||
pub const ENEMIZER_FILE_LENGTH: usize = 0x200000;
|
pub const ENEMIZER_FILE_LENGTH: usize = 0x200000;
|
||||||
pub const HIDDEN_ENEMY_CHANCE_POOL: usize = 0xD7BBB;
|
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,
|
||||||
|
];
|
||||||
|
|
|
@ -11,6 +11,7 @@ pub mod asar;
|
||||||
pub mod bosses;
|
pub mod bosses;
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
pub mod option_flags;
|
pub mod option_flags;
|
||||||
|
pub mod randomize;
|
||||||
pub mod rom;
|
pub mod rom;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
|
|
@ -3,11 +3,14 @@ use std::io::prelude::*;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use anyhow::ensure;
|
use anyhow::{bail, ensure};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
use enemize::{asar, rom::RomData};
|
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
|
/// Randomizes enemy placements in The Legend of Zelda: A Link to the Past for the Super Nintendo
|
||||||
/// Entertainment System
|
/// Entertainment System
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
|
@ -17,7 +20,7 @@ struct Args {
|
||||||
rom: PathBuf,
|
rom: PathBuf,
|
||||||
/// seed number
|
/// seed number
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
seed: Option<u32>,
|
seed: Option<i32>,
|
||||||
/// path to the enemizerOptions.json
|
/// path to the enemizerOptions.json
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
enemizer: PathBuf,
|
enemizer: PathBuf,
|
||||||
|
@ -34,7 +37,7 @@ fn main() -> anyhow::Result<()> {
|
||||||
let stopwatch = Instant::now();
|
let stopwatch = Instant::now();
|
||||||
|
|
||||||
let options = serde_json::from_reader(File::open(args.enemizer)?)?;
|
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 raw_data = vec![];
|
||||||
let mut rom_file = File::open(args.rom)?;
|
let mut rom_file = File::open(args.rom)?;
|
||||||
|
@ -42,17 +45,24 @@ fn main() -> anyhow::Result<()> {
|
||||||
|
|
||||||
raw_data.resize(2 * 1024 * 1024, 0);
|
raw_data.resize(2 * 1024 * 1024, 0);
|
||||||
|
|
||||||
let mut rom_data = RomData::new(symbols, raw_data);
|
let mut rom = 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.");
|
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 {
|
if args.binary {
|
||||||
|
out_file.write_all(rom.get_rom_bytes())?;
|
||||||
|
out_file.flush()?;
|
||||||
|
println!("Generated SFC file {}", args.output.to_string_lossy());
|
||||||
} else {
|
} else {
|
||||||
|
let patches = rom.generate_patch();
|
||||||
|
serde_json::to_writer(out_file, &patches)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Seed generated in: {}ms", stopwatch.elapsed().as_millis());
|
println!("Seed generated in: {}ms", stopwatch.elapsed().as_millis());
|
||||||
|
@ -60,21 +70,13 @@ fn main() -> anyhow::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_symbols(rom: &Path) -> anyhow::Result<asar::Symbols> {
|
fn load_symbols() -> anyhow::Result<asar::Symbols> {
|
||||||
let sym_path = rom.with_extension("sym");
|
if let Err(_) = std::fs::metadata(ASAR_SYMBOLS) {
|
||||||
|
bail!(
|
||||||
if let Err(_) = std::fs::metadata(&sym_path) {
|
"Could not find symbols at {}. Did you run prepare.sh?",
|
||||||
let status = std::process::Command::new("asar")
|
ASAR_SYMBOLS
|
||||||
.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())
|
asar::load_symbols(Path::new(ASAR_SYMBOLS))
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,12 @@ use std::fmt;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::bosses::BossType;
|
use crate::bosses::BossType;
|
||||||
use crate::InvalidEnumError;
|
use crate::InvalidEnumError;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||||
pub struct ManualBosses {
|
pub struct ManualBosses {
|
||||||
pub eastern_palace: String,
|
pub eastern_palace: String,
|
||||||
pub desert_palace: String,
|
pub desert_palace: String,
|
||||||
|
@ -26,7 +28,8 @@ pub struct ManualBosses {
|
||||||
pub ganon: String,
|
pub ganon: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(into = "u8", try_from = "u8")]
|
||||||
pub enum RandomizeEnemiesType {
|
pub enum RandomizeEnemiesType {
|
||||||
Basic,
|
Basic,
|
||||||
Normal,
|
Normal,
|
||||||
|
@ -35,6 +38,20 @@ pub enum RandomizeEnemiesType {
|
||||||
Insanity,
|
Insanity,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<RandomizeEnemiesType> for u8 {
|
||||||
|
fn from(t: RandomizeEnemiesType) -> u8 {
|
||||||
|
use RandomizeEnemiesType::*;
|
||||||
|
|
||||||
|
match t {
|
||||||
|
Basic => 0,
|
||||||
|
Normal => 1,
|
||||||
|
Hard => 2,
|
||||||
|
Chaos => 3,
|
||||||
|
Insanity => 4,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<u8> for RandomizeEnemiesType {
|
impl TryFrom<u8> for RandomizeEnemiesType {
|
||||||
type Error = InvalidEnumError<Self>;
|
type Error = InvalidEnumError<Self>;
|
||||||
|
|
||||||
|
@ -45,17 +62,31 @@ impl TryFrom<u8> for RandomizeEnemiesType {
|
||||||
2 => Ok(Self::Hard),
|
2 => Ok(Self::Hard),
|
||||||
3 => Ok(Self::Chaos),
|
3 => Ok(Self::Chaos),
|
||||||
4 => Ok(Self::Insanity),
|
4 => Ok(Self::Insanity),
|
||||||
_ => Err(InvalidEnumError(PhantomData))
|
_ => Err(InvalidEnumError(PhantomData)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(into = "u8", try_from = "u8")]
|
||||||
pub enum RandomizeEnemyHpType {
|
pub enum RandomizeEnemyHpType {
|
||||||
Easy,
|
Easy,
|
||||||
Medium,
|
Medium,
|
||||||
Hard,
|
Hard,
|
||||||
Patty
|
Patty,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RandomizeEnemyHpType> for u8 {
|
||||||
|
fn from(t: RandomizeEnemyHpType) -> u8 {
|
||||||
|
use RandomizeEnemyHpType::*;
|
||||||
|
|
||||||
|
match t {
|
||||||
|
Easy => 0,
|
||||||
|
Medium => 1,
|
||||||
|
Hard => 2,
|
||||||
|
Patty => 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<u8> for RandomizeEnemyHpType {
|
impl TryFrom<u8> for RandomizeEnemyHpType {
|
||||||
|
@ -67,16 +98,29 @@ impl TryFrom<u8> for RandomizeEnemyHpType {
|
||||||
1 => Ok(Self::Medium),
|
1 => Ok(Self::Medium),
|
||||||
2 => Ok(Self::Hard),
|
2 => Ok(Self::Hard),
|
||||||
3 => Ok(Self::Patty),
|
3 => Ok(Self::Patty),
|
||||||
_ => Err(InvalidEnumError(PhantomData))
|
_ => Err(InvalidEnumError(PhantomData)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(into = "u8", try_from = "u8")]
|
||||||
pub enum RandomizeBossesType {
|
pub enum RandomizeBossesType {
|
||||||
Basic,
|
Basic,
|
||||||
Normal,
|
Normal,
|
||||||
Chaos
|
Chaos,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RandomizeBossesType> for u8 {
|
||||||
|
fn from(t: RandomizeBossesType) -> u8 {
|
||||||
|
use RandomizeBossesType::*;
|
||||||
|
|
||||||
|
match t {
|
||||||
|
Basic => 0,
|
||||||
|
Normal => 1,
|
||||||
|
Chaos => 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<u8> for RandomizeBossesType {
|
impl TryFrom<u8> for RandomizeBossesType {
|
||||||
|
@ -87,14 +131,21 @@ impl TryFrom<u8> for RandomizeBossesType {
|
||||||
0 => Ok(Self::Basic),
|
0 => Ok(Self::Basic),
|
||||||
1 => Ok(Self::Normal),
|
1 => Ok(Self::Normal),
|
||||||
2 => Ok(Self::Chaos),
|
2 => Ok(Self::Chaos),
|
||||||
_ => Err(InvalidEnumError(PhantomData))
|
_ => Err(InvalidEnumError(PhantomData)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(into = "u8", try_from = "u8")]
|
||||||
pub enum SwordType {
|
pub enum SwordType {
|
||||||
Normal
|
Normal,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SwordType> for u8 {
|
||||||
|
fn from(_t: SwordType) -> u8 {
|
||||||
|
0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<u8> for SwordType {
|
impl TryFrom<u8> for SwordType {
|
||||||
|
@ -103,14 +154,21 @@ impl TryFrom<u8> for SwordType {
|
||||||
fn try_from(byte: u8) -> Result<Self, Self::Error> {
|
fn try_from(byte: u8) -> Result<Self, Self::Error> {
|
||||||
match byte {
|
match byte {
|
||||||
0 => Ok(Self::Normal),
|
0 => Ok(Self::Normal),
|
||||||
_ => Err(InvalidEnumError(PhantomData))
|
_ => Err(InvalidEnumError(PhantomData)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(into = "u8", try_from = "u8")]
|
||||||
pub enum ShieldType {
|
pub enum ShieldType {
|
||||||
Normal
|
Normal,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ShieldType> for u8 {
|
||||||
|
fn from(_t: ShieldType) -> u8 {
|
||||||
|
0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<u8> for ShieldType {
|
impl TryFrom<u8> for ShieldType {
|
||||||
|
@ -119,12 +177,13 @@ impl TryFrom<u8> for ShieldType {
|
||||||
fn try_from(byte: u8) -> Result<Self, Self::Error> {
|
fn try_from(byte: u8) -> Result<Self, Self::Error> {
|
||||||
match byte {
|
match byte {
|
||||||
0 => Ok(Self::Normal),
|
0 => Ok(Self::Normal),
|
||||||
_ => Err(InvalidEnumError(PhantomData))
|
_ => Err(InvalidEnumError(PhantomData)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||||
|
#[serde(into = "u8", try_from = "u8")]
|
||||||
pub enum AbsorbableType {
|
pub enum AbsorbableType {
|
||||||
Heart,
|
Heart,
|
||||||
GreenRupee,
|
GreenRupee,
|
||||||
|
@ -142,6 +201,29 @@ pub enum AbsorbableType {
|
||||||
BigKey,
|
BigKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<AbsorbableType> 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<u8> for AbsorbableType {
|
impl TryFrom<u8> for AbsorbableType {
|
||||||
type Error = InvalidEnumError<Self>;
|
type Error = InvalidEnumError<Self>;
|
||||||
|
|
||||||
|
@ -150,17 +232,18 @@ impl TryFrom<u8> for AbsorbableType {
|
||||||
0 => Ok(Self::Heart),
|
0 => Ok(Self::Heart),
|
||||||
1 => Ok(Self::GreenRupee),
|
1 => Ok(Self::GreenRupee),
|
||||||
2 => Ok(Self::BlueRupee),
|
2 => Ok(Self::BlueRupee),
|
||||||
3 => Ok(Self::Bomb1),
|
3 => Ok(Self::RedRupee),
|
||||||
4 => Ok(Self::Bomb4),
|
4 => Ok(Self::Bomb1),
|
||||||
5 => Ok(Self::Bomb8),
|
5 => Ok(Self::Bomb4),
|
||||||
6 => Ok(Self::SmallMagic),
|
6 => Ok(Self::Bomb8),
|
||||||
7 => Ok(Self::FullMagic),
|
7 => Ok(Self::SmallMagic),
|
||||||
8 => Ok(Self::Arrow5),
|
8 => Ok(Self::FullMagic),
|
||||||
9 => Ok(Self::Arrow10),
|
9 => Ok(Self::Arrow5),
|
||||||
10 => Ok(Self::Fairy),
|
10 => Ok(Self::Arrow10),
|
||||||
11 => Ok(Self::Key),
|
11 => Ok(Self::Fairy),
|
||||||
12 => Ok(Self::BigKey),
|
12 => Ok(Self::Key),
|
||||||
_ => Err(InvalidEnumError(PhantomData))
|
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 {
|
pub enum HeartBeepSpeed {
|
||||||
Normal,
|
Normal,
|
||||||
Half,
|
Half,
|
||||||
|
@ -204,6 +288,19 @@ impl Default for HeartBeepSpeed {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<HeartBeepSpeed> for u8 {
|
||||||
|
fn from(speed: HeartBeepSpeed) -> u8 {
|
||||||
|
use HeartBeepSpeed::*;
|
||||||
|
|
||||||
|
match speed {
|
||||||
|
Normal => 0,
|
||||||
|
Half => 1,
|
||||||
|
Quarter => 2,
|
||||||
|
Off => 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<u8> for HeartBeepSpeed {
|
impl TryFrom<u8> for HeartBeepSpeed {
|
||||||
type Error = InvalidEnumError<Self>;
|
type Error = InvalidEnumError<Self>;
|
||||||
|
|
||||||
|
@ -213,12 +310,12 @@ impl TryFrom<u8> for HeartBeepSpeed {
|
||||||
1 => Ok(Self::Half),
|
1 => Ok(Self::Half),
|
||||||
2 => Ok(Self::Quarter),
|
2 => Ok(Self::Quarter),
|
||||||
3 => Ok(Self::Off),
|
3 => Ok(Self::Off),
|
||||||
_ => Err(InvalidEnumError(PhantomData))
|
_ => Err(InvalidEnumError(PhantomData)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
||||||
pub enum BeeLevel {
|
pub enum BeeLevel {
|
||||||
Level1,
|
Level1,
|
||||||
Level2,
|
Level2,
|
||||||
|
@ -241,6 +338,19 @@ impl fmt::Display for BeeLevel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<BeeLevel> for u8 {
|
||||||
|
fn from(level: BeeLevel) -> u8 {
|
||||||
|
use BeeLevel::*;
|
||||||
|
|
||||||
|
match level {
|
||||||
|
Level1 => 0,
|
||||||
|
Level2 => 1,
|
||||||
|
Level3 => 2,
|
||||||
|
Level4 => 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<u8> for BeeLevel {
|
impl TryFrom<u8> for BeeLevel {
|
||||||
type Error = InvalidEnumError<Self>;
|
type Error = InvalidEnumError<Self>;
|
||||||
|
|
||||||
|
@ -250,12 +360,13 @@ impl TryFrom<u8> for BeeLevel {
|
||||||
1 => Ok(Self::Level2),
|
1 => Ok(Self::Level2),
|
||||||
2 => Ok(Self::Level3),
|
2 => Ok(Self::Level3),
|
||||||
3 => Ok(Self::Level4),
|
3 => Ok(Self::Level4),
|
||||||
_ => Err(InvalidEnumError(PhantomData))
|
_ => Err(InvalidEnumError(PhantomData)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(default, rename_all = "PascalCase")]
|
||||||
pub struct OptionFlags {
|
pub struct OptionFlags {
|
||||||
pub randomize_enemies: bool,
|
pub randomize_enemies: bool,
|
||||||
pub randomize_enemies_type: RandomizeEnemiesType,
|
pub randomize_enemies_type: RandomizeEnemiesType,
|
||||||
|
@ -341,16 +452,17 @@ impl OptionFlags {
|
||||||
absorbable_types.insert(AbsorbableType::Heart, bytes[10] != 0);
|
absorbable_types.insert(AbsorbableType::Heart, bytes[10] != 0);
|
||||||
absorbable_types.insert(AbsorbableType::GreenRupee, bytes[11] != 0);
|
absorbable_types.insert(AbsorbableType::GreenRupee, bytes[11] != 0);
|
||||||
absorbable_types.insert(AbsorbableType::BlueRupee, bytes[12] != 0);
|
absorbable_types.insert(AbsorbableType::BlueRupee, bytes[12] != 0);
|
||||||
absorbable_types.insert(AbsorbableType::Bomb1, bytes[13] != 0);
|
absorbable_types.insert(AbsorbableType::RedRupee, bytes[13] != 0);
|
||||||
absorbable_types.insert(AbsorbableType::Bomb4, bytes[14] != 0);
|
absorbable_types.insert(AbsorbableType::Bomb1, bytes[14] != 0);
|
||||||
absorbable_types.insert(AbsorbableType::Bomb8, bytes[15] != 0);
|
absorbable_types.insert(AbsorbableType::Bomb4, bytes[15] != 0);
|
||||||
absorbable_types.insert(AbsorbableType::SmallMagic, bytes[16] != 0);
|
absorbable_types.insert(AbsorbableType::Bomb8, bytes[16] != 0);
|
||||||
absorbable_types.insert(AbsorbableType::FullMagic, bytes[17] != 0);
|
absorbable_types.insert(AbsorbableType::SmallMagic, bytes[17] != 0);
|
||||||
absorbable_types.insert(AbsorbableType::Arrow5, bytes[18] != 0);
|
absorbable_types.insert(AbsorbableType::FullMagic, bytes[18] != 0);
|
||||||
absorbable_types.insert(AbsorbableType::Arrow10, bytes[19] != 0);
|
absorbable_types.insert(AbsorbableType::Arrow5, bytes[19] != 0);
|
||||||
absorbable_types.insert(AbsorbableType::Fairy, bytes[20] != 0);
|
absorbable_types.insert(AbsorbableType::Arrow10, bytes[20] != 0);
|
||||||
absorbable_types.insert(AbsorbableType::Key, bytes[21] != 0);
|
absorbable_types.insert(AbsorbableType::Fairy, bytes[21] != 0);
|
||||||
absorbable_types.insert(AbsorbableType::BigKey, bytes[22] != 0);
|
absorbable_types.insert(AbsorbableType::Key, bytes[21] != 1);
|
||||||
|
absorbable_types.insert(AbsorbableType::BigKey, bytes[23] != 0);
|
||||||
|
|
||||||
Ok(OptionFlags {
|
Ok(OptionFlags {
|
||||||
randomize_enemies: bytes[0] != 0,
|
randomize_enemies: bytes[0] != 0,
|
||||||
|
@ -364,55 +476,55 @@ impl OptionFlags {
|
||||||
enemies_absorbable: bytes[8] != 0,
|
enemies_absorbable: bytes[8] != 0,
|
||||||
absorbable_spawn_rate: bytes[9],
|
absorbable_spawn_rate: bytes[9],
|
||||||
absorbable_types,
|
absorbable_types,
|
||||||
boss_madness: bytes[23] != 0,
|
boss_madness: bytes[24] != 0,
|
||||||
randomize_bosses: bytes[24] != 0,
|
randomize_bosses: bytes[25] != 0,
|
||||||
randomize_bosses_type: bytes[25].try_into()?,
|
randomize_bosses_type: bytes[26].try_into()?,
|
||||||
randomize_boss_health: bytes[26] != 0,
|
randomize_boss_health: bytes[27] != 0,
|
||||||
randomize_boss_health_min_amount: bytes[27],
|
randomize_boss_health_min_amount: bytes[28],
|
||||||
randomize_boss_health_max_amount: bytes[28],
|
randomize_boss_health_max_amount: bytes[29],
|
||||||
randomize_boss_damage: bytes[29] != 0,
|
randomize_boss_damage: bytes[30] != 0,
|
||||||
randomize_boss_damage_min_amount: bytes[30],
|
randomize_boss_damage_min_amount: bytes[31],
|
||||||
randomize_boss_damage_max_amount: bytes[31],
|
randomize_boss_damage_max_amount: bytes[32],
|
||||||
randomize_boss_behavior: bytes[32] != 0,
|
randomize_boss_behavior: bytes[33] != 0,
|
||||||
randomize_dungeon_palettes: bytes[33] != 0,
|
randomize_dungeon_palettes: bytes[34] != 0,
|
||||||
set_blackout_mode: bytes[34] != 0,
|
set_blackout_mode: bytes[35] != 0,
|
||||||
randomize_overworld_palettes: bytes[35] != 0,
|
randomize_overworld_palettes: bytes[36] != 0,
|
||||||
randomize_sprite_palettes: bytes[36] != 0,
|
randomize_sprite_palettes: bytes[37] != 0,
|
||||||
set_advanced_sprite_palettes: bytes[37] != 0,
|
set_advanced_sprite_palettes: bytes[38] != 0,
|
||||||
puke_mode: bytes[38] != 0,
|
puke_mode: bytes[39] != 0,
|
||||||
negative_mode: bytes[39] != 0,
|
negative_mode: bytes[40] != 0,
|
||||||
grayscale_mode: bytes[40] != 0,
|
grayscale_mode: bytes[41] != 0,
|
||||||
generate_spoilers: bytes[41] != 0,
|
generate_spoilers: bytes[42] != 0,
|
||||||
randomize_link_sprite_palette: bytes[42] != 0,
|
randomize_link_sprite_palette: bytes[43] != 0,
|
||||||
randomize_pots: bytes[43] != 0,
|
randomize_pots: bytes[44] != 0,
|
||||||
shuffle_music: bytes[44] != 0,
|
shuffle_music: bytes[45] != 0,
|
||||||
bootleg_magic: bytes[45] != 0,
|
bootleg_magic: bytes[46] != 0,
|
||||||
debug_mode: bytes[46] != 0,
|
debug_mode: bytes[47] != 0,
|
||||||
custom_bosses: bytes[47] != 0,
|
custom_bosses: bytes[48] != 0,
|
||||||
heart_beep_speed: bytes[48].try_into()?,
|
heart_beep_speed: bytes[49].try_into()?,
|
||||||
alternate_gfx: bytes[49] != 0,
|
alternate_gfx: bytes[50] != 0,
|
||||||
// Skip byte 50 (shield_graphics)
|
// Skip byte 51 (shield_graphics)
|
||||||
shuffle_enemy_damage_groups: bytes[51] != 0,
|
shuffle_enemy_damage_groups: bytes[52] != 0,
|
||||||
enemy_damage_chaos_mode: bytes[52] != 0,
|
enemy_damage_chaos_mode: bytes[53] != 0,
|
||||||
// Skip byte 53 (sword_graphics)
|
// Skip byte 54 (sword_graphics)
|
||||||
bee_mizer: bytes[54] != 0,
|
bee_mizer: bytes[55] != 0,
|
||||||
bees_level: bytes[55].try_into()?,
|
bees_level: bytes[56].try_into()?,
|
||||||
debug_force_enemy: bytes[56] != 0,
|
debug_force_enemy: bytes[57] != 0,
|
||||||
debug_force_enemy_id: bytes[57],
|
debug_force_enemy_id: bytes[58],
|
||||||
debug_force_boss: bytes[58] != 0,
|
debug_force_boss: bytes[59] != 0,
|
||||||
debug_force_boss_id: bytes[59].try_into()?,
|
debug_force_boss_id: bytes[60].try_into()?,
|
||||||
debug_open_shutter_doors: bytes[60] != 0,
|
debug_open_shutter_doors: bytes[61] != 0,
|
||||||
debug_force_enemy_damage_zero: bytes[61] != 0,
|
debug_force_enemy_damage_zero: bytes[62] != 0,
|
||||||
debug_show_room_id_in_rupee_counter: bytes[62] != 0,
|
debug_show_room_id_in_rupee_counter: bytes[63] != 0,
|
||||||
o_h_k_o: bytes[63] != 0,
|
o_h_k_o: bytes[64] != 0,
|
||||||
randomize_tile_trap_pattern: bytes[64] != 0,
|
randomize_tile_trap_pattern: bytes[65] != 0,
|
||||||
randomize_tile_trap_floor_tile: bytes[65] != 0,
|
randomize_tile_trap_floor_tile: bytes[66] != 0,
|
||||||
allow_killable_thief: bytes[66] != 0,
|
allow_killable_thief: bytes[67] != 0,
|
||||||
randomize_sprite_on_hit: bytes[67] != 0,
|
randomize_sprite_on_hit: bytes[68] != 0,
|
||||||
hero_mode: bytes[68] != 0,
|
hero_mode: bytes[69] != 0,
|
||||||
increase_brightness: bytes[69] != 0,
|
increase_brightness: bytes[70] != 0,
|
||||||
mute_music_enable_msu_1: bytes[70] != 0,
|
mute_music_enable_msu_1: bytes[71] != 0,
|
||||||
agahnim_bounce_balls: bytes[71] != 0,
|
agahnim_bounce_balls: bytes[72] != 0,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -431,20 +543,90 @@ impl OptionFlags {
|
||||||
bytes.push(self.enemies_absorbable as u8);
|
bytes.push(self.enemies_absorbable as u8);
|
||||||
bytes.push(self.absorbable_spawn_rate);
|
bytes.push(self.absorbable_spawn_rate);
|
||||||
|
|
||||||
bytes.push(self.absorbable_types.get(&AbsorbableType::Heart).copied().unwrap_or(false) as u8);
|
bytes.push(
|
||||||
bytes.push(self.absorbable_types.get(&AbsorbableType::GreenRupee).copied().unwrap_or(false) as u8);
|
self.absorbable_types
|
||||||
bytes.push(self.absorbable_types.get(&AbsorbableType::BlueRupee).copied().unwrap_or(false) as u8);
|
.get(&AbsorbableType::Heart)
|
||||||
bytes.push(self.absorbable_types.get(&AbsorbableType::RedRupee).copied().unwrap_or(false) as u8);
|
.copied()
|
||||||
bytes.push(self.absorbable_types.get(&AbsorbableType::Bomb1).copied().unwrap_or(false) as u8);
|
.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(
|
||||||
bytes.push(self.absorbable_types.get(&AbsorbableType::SmallMagic).copied().unwrap_or(false) as u8);
|
self.absorbable_types
|
||||||
bytes.push(self.absorbable_types.get(&AbsorbableType::FullMagic).copied().unwrap_or(false) as u8);
|
.get(&AbsorbableType::GreenRupee)
|
||||||
bytes.push(self.absorbable_types.get(&AbsorbableType::Arrow5).copied().unwrap_or(false) as u8);
|
.copied()
|
||||||
bytes.push(self.absorbable_types.get(&AbsorbableType::Arrow10).copied().unwrap_or(false) as u8);
|
.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(
|
||||||
bytes.push(self.absorbable_types.get(&AbsorbableType::BigKey).copied().unwrap_or(false) as u8);
|
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.boss_madness as u8);
|
||||||
bytes.push(self.randomize_bosses as u8);
|
bytes.push(self.randomize_bosses as u8);
|
||||||
|
|
37
enemize/src/randomize.rs
Normal file
37
enemize/src/randomize.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use anyhow::ensure;
|
||||||
|
|
||||||
|
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."
|
||||||
|
);
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,14 +4,15 @@ use std::ops::Range;
|
||||||
|
|
||||||
use anyhow::bail;
|
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::constants::*;
|
||||||
use crate::option_flags::OptionFlags;
|
use crate::option_flags::OptionFlags;
|
||||||
use crate::Patch;
|
use crate::Patch;
|
||||||
|
|
||||||
pub const ENEMIZER_INFO_SEED_OFFSET: usize = 0;
|
pub const ENEMIZER_INFO_SEED_OFFSET: usize = 0;
|
||||||
pub const ENEMIZER_INFO_SEED_LENGTH: usize = 12;
|
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_VERSION_LENGTH: usize = 8;
|
||||||
pub const ENEMIZER_INFO_FLAGS_OFFSET: usize =
|
pub const ENEMIZER_INFO_FLAGS_OFFSET: usize =
|
||||||
ENEMIZER_INFO_VERSION_OFFSET + ENEMIZER_INFO_VERSION_LENGTH;
|
ENEMIZER_INFO_VERSION_OFFSET + ENEMIZER_INFO_VERSION_LENGTH;
|
||||||
|
@ -31,9 +32,9 @@ pub const RANDOMIZER_MODE_FLAG: usize = 0x180032;
|
||||||
pub struct RomData {
|
pub struct RomData {
|
||||||
asar_symbols: Symbols,
|
asar_symbols: Symbols,
|
||||||
spoiler: String,
|
spoiler: String,
|
||||||
rom_data: Vec<u8>,
|
pub(crate) rom_data: Vec<u8>,
|
||||||
patch_data: BTreeMap<usize, u8>,
|
patch_data: BTreeMap<usize, u8>,
|
||||||
seed: i32,
|
pub(crate) seed: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RomData {
|
impl RomData {
|
||||||
|
@ -41,59 +42,97 @@ impl RomData {
|
||||||
self.spoiler.as_str()
|
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<Patch> {
|
pub fn generate_patch(&self) -> Vec<Patch> {
|
||||||
let mut patches = vec![];
|
let mut patches = vec![];
|
||||||
|
|
||||||
for (&addr, &byte) in self.patch_data.iter() {
|
for (&addr, &byte) in self.patch_data.iter() {
|
||||||
match patches.last_mut().filter(|p: &&mut Patch| p.address + 1 == addr) {
|
match patches
|
||||||
None => patches.push(Patch { address: addr, patch_data: vec![byte] }),
|
.last_mut()
|
||||||
Some(patch) => patch.patch_data.push(byte)
|
.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
|
patches
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_rom_bytes(&self) -> &[u8] {
|
||||||
|
&self.rom_data
|
||||||
|
}
|
||||||
|
|
||||||
fn set_rom_bytes(&mut self, bytes: &[u8], range: Range<usize>) {
|
fn set_rom_bytes(&mut self, bytes: &[u8], range: Range<usize>) {
|
||||||
self.rom_data.splice(range, bytes.into_iter().map(|&b| b));
|
self.rom_data.splice(range, bytes.into_iter().map(|&b| b));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_patch_bytes(&mut self, range: Range<usize>) {
|
fn set_patch_bytes(&mut self, range: Range<usize>) {
|
||||||
let slice = &self.rom_data[range.clone()];
|
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 {
|
pub fn is_enemizer(&self) -> bool {
|
||||||
self.rom_data.len() == ENEMIZER_FILE_LENGTH
|
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]
|
||||||
&& self.rom_data[self.asar_symbols["enemizer_info_table"] + ENEMIZER_INFO_SEED_OFFSET + 1] == b'N'
|
== b'E'
|
||||||
|
&& self.rom_data
|
||||||
|
[self.asar_symbols["enemizer_info_table"] + ENEMIZER_INFO_SEED_OFFSET + 1]
|
||||||
|
== b'N'
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_randomizer(&self) -> bool {
|
pub fn is_randomizer(&self) -> bool {
|
||||||
let acceptable = [
|
let acceptable = [
|
||||||
// item rando
|
b"VT", // item rando
|
||||||
b"VT",
|
b"ER", // entrance rando
|
||||||
// entrance rando
|
b"DR", // door rando
|
||||||
b"ER",
|
b"BM", // Berserker's multiworld
|
||||||
// door rando
|
b"BD", // Berserker's multiworld doors
|
||||||
b"DR",
|
b"AP", // Archipelago
|
||||||
// Berserker's multiworld
|
b"AD", // Archipelago with door rando
|
||||||
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]) ||
|
acceptable
|
||||||
(self.rom_data.len() >= 0x20_0000 &&
|
.iter()
|
||||||
&self.rom_data[0x7FC0..0x7FCE] == b"ZELDANODENSETSU")
|
.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")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_rom_length(&self) {
|
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 {
|
pub fn get_enemizer_seed(&self) -> i32 {
|
||||||
|
@ -127,11 +166,23 @@ impl RomData {
|
||||||
self.set_patch_bytes(seed_start..seed_end);
|
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> {
|
pub fn get_enemizer_version(&self) -> anyhow::Result<&str> {
|
||||||
if self.is_enemizer() {
|
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;
|
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 {
|
} else {
|
||||||
bail!("Not Enemizer Rom")
|
bail!("Not Enemizer Rom")
|
||||||
}
|
}
|
||||||
|
@ -149,7 +200,7 @@ impl RomData {
|
||||||
self.set_patch_bytes(version_start..version_end)
|
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();
|
let bytes = flags.into_bytes();
|
||||||
if bytes.len() > 0x100 - ENEMIZER_INFO_FLAGS_OFFSET {
|
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.");
|
bail!("Option flags is too long to fit in the space allocated. Need to move data/code in asm file.");
|
||||||
|
@ -208,10 +259,11 @@ impl RomData {
|
||||||
|
|
||||||
pub fn move_room_headers(&mut self) {
|
pub fn move_room_headers(&mut self) {
|
||||||
let table_base = DUNGEON_HEADER_POINTER_TABLE;
|
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.
|
// 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;
|
self.rom_data[ROOM_HEADER_BANK_LOCATION] = new_room_bank;
|
||||||
|
|
||||||
// Copy header table.
|
// Copy header table.
|
||||||
|
@ -222,14 +274,15 @@ impl RomData {
|
||||||
self.rom_data[table_base + (i * 2)],
|
self.rom_data[table_base + (i * 2)],
|
||||||
self.rom_data[table_base + (i * 2) + 1],
|
self.rom_data[table_base + (i * 2) + 1],
|
||||||
4,
|
4,
|
||||||
0
|
0,
|
||||||
];
|
];
|
||||||
let snes_address = u32::from_le_bytes(room_pointer);
|
let snes_address = u32::from_le_bytes(room_pointer);
|
||||||
let pc_address = snes_to_pc_address(snes_address);
|
let pc_address = snes_to_pc_address(snes_address);
|
||||||
|
|
||||||
// Copy i'th room's headers to new room_header_table.
|
// Copy i'th room's headers to new room_header_table.
|
||||||
let header_start = header_base + (i * 14);
|
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.
|
// Repoint the pointer table to the new header table.
|
||||||
|
@ -238,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.");
|
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]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
8
prepare.sh
Normal file
8
prepare.sh
Normal file
|
@ -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
|
Loading…
Reference in a new issue