Compare commits

...

29 commits

Author SHA1 Message Date
Lyle Mantooth b343a0cb80
Add Overworld locations.
Refactor dungeon module to locations to reflect what it contains.
2022-06-12 15:17:54 -04:00
Lyle Mantooth 5500d628f1
Fill in the last names of rooms. 2022-06-12 12:12:32 -04:00
Lyle Mantooth 6a56e52e58
Add room data. 2022-06-12 11:21:24 -04:00
Lyle Mantooth 13e910091b
Add graph data.
Items and dungeons so far. Will be used to make sure the resulting ROM
is beatable.
2022-06-10 11:49:07 -04:00
Lyle Mantooth 1c752e5bbd
Make RomData indexable. 2022-06-05 15:57:02 -04:00
Lyle Mantooth d1d4e31738
Format! 2022-06-05 15:56:36 -04:00
Lyle Mantooth a0150caa56
Bake enemizer base patch and asar symbols into executable. 2022-06-04 15:29:02 -04:00
Lyle Mantooth 8d8faf105a
Finally done serializing. 2022-06-04 15:28:21 -04:00
Lyle Mantooth ec37b1d583
Put the enemizer base patch next to the executable. 2022-06-04 15:27:28 -04:00
Lyle Mantooth 4972986fee
Fix byte indexes.
And standardize on lower-case hexadecimal.
2022-06-04 14:39:11 -04:00
Lyle Mantooth fed38efcc4
Add back options passed by ALttpDoorRandomizer.
Even though they aren't used, we have to declare them to parse them.
2022-06-04 14:38:35 -04:00
Lyle Mantooth 76ff991e8f
Conform to serialization used by ALttpDoorRandomizer. 2022-06-04 14:37:12 -04:00
Lyle Mantooth 975d6fd420
PatchSet only serializes patches to the file. 2022-06-04 14:35:44 -04:00
Lyle Mantooth 4cbc70eeca
Make it easy to write options file. 2022-06-04 10:00:06 -04:00
Lyle Mantooth 034af7fa92
More backwards compatibility. 2022-06-04 09:58:10 -04:00
Lyle Mantooth 27d5a4d6c0
Format! 2022-06-04 09:41:29 -04:00
Lyle Mantooth 1113adca98
Make it run (no actual randomizing yet). 2022-06-04 09:39:52 -04:00
Lyle Mantooth 87b0476b7e
Make it easy to generate necessary artifacts to run enemize-rs.
Maybe these files could be committed? It's not like they'll change very
often. But maybe there's copyrighted material in them I haven't noticed.
2022-06-04 09:36:08 -04:00
Lyle Mantooth 8901e0e8b0
Serialize option enums to u8, for backward compatibility. 2022-06-04 09:33:11 -04:00
Lyle Mantooth 41be111857
Add RomData::is_randomizer. 2022-06-01 09:53:11 -04:00
Lyle Mantooth 568360206d
Begin loading ROM data for randomization.
Use the fact that the default path for the asar symbols file is just the
ROM's file with a ".sym" extension. When enemize runs, it checks if this
file exists, and if it doesn't, runs asar on the ROM to create the
symbols file.
2022-05-31 22:48:45 -04:00
Lyle Mantooth f6eb37b6fe
Use clap for processing arguments. 2022-05-31 17:08:52 -04:00
Lyle Mantooth 4112573d85
Add base_patch_generator. 2022-05-30 01:04:39 -04:00
Lyle Mantooth 7cd01df7d6
Move Asar symbols into RomData.
Add move_room_headers function for base_patch_generator.
2022-05-30 01:03:30 -04:00
Lyle Mantooth 7a6689efd1
Understand better how SNES addressing works. 2022-05-29 19:16:49 -04:00
Lyle Mantooth a161d9090e
Add RomData, OptionFlags, and enums for them.
Fun error handling with automatic, fallible conversion to and from u8s
for the enums.
2022-05-29 10:22:34 -04:00
Lyle Mantooth c81348fb10
Fix up compiler issues. 2022-05-24 22:53:15 -04:00
Lyle Mantooth 5f8f1b5655
Ignore generated files. 2022-05-24 22:49:58 -04:00
Lyle Mantooth 1cca600505
Use MIT License for my own code.
Files in `assembly/src` written by others under the WTFPL.
2022-05-23 09:57:16 -04:00
20 changed files with 3570 additions and 28 deletions

3
.gitignore vendored
View file

@ -1 +1,4 @@
/target
asar_symbols.txt
base_patch.json
enemizer_base_patch.json

254
Cargo.lock generated
View file

@ -8,12 +8,39 @@ version = "1.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "base_patch_generator"
version = "0.1.0"
dependencies = [
"anyhow",
"enemize",
"md5",
"serde_json",
]
[[package]]
name = "bin_comp"
version = "0.1.0"
dependencies = [
"anyhow",
"serde",
"enemize",
"serde_json",
"tempfile",
]
@ -31,10 +58,55 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "enemize-rs"
name = "clap"
version = "3.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b"
dependencies = [
"atty",
"bitflags",
"clap_derive",
"clap_lex",
"indexmap",
"lazy_static",
"strsim",
"termcolor",
"textwrap",
]
[[package]]
name = "clap_derive"
version = "3.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "enemize"
version = "0.1.0"
dependencies = [
"md5",
"anyhow",
"clap",
"lazy_static",
"rand",
"serde",
"serde_json",
"thiserror",
]
[[package]]
@ -46,6 +118,48 @@ dependencies = [
"instant",
]
[[package]]
name = "getrandom"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "heck"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "indexmap"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "instant"
version = "0.1.12"
@ -61,6 +175,12 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.126"
@ -73,6 +193,42 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
[[package]]
name = "os_str_bytes"
version = "6.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa"
[[package]]
name = "ppv-lite86"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.39"
@ -91,6 +247,36 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom",
]
[[package]]
name = "redox_syscall"
version = "0.2.13"
@ -146,6 +332,12 @@ dependencies = [
"serde",
]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.95"
@ -171,12 +363,59 @@ dependencies = [
"winapi",
]
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
[[package]]
name = "thiserror"
version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "winapi"
version = "0.3.9"
@ -193,6 +432,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"

View file

@ -1,6 +1,7 @@
[workspace]
members = [
"enemize",
"base_patch_generator",
"bin_comp",
"enemize",
]

21
LICENSE.txt Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Lyle Mantooth
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,12 @@
[package]
name = "base_patch_generator"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0"
enemize = { path = "../enemize" }
md5 = "0.7.0"
serde_json = "1.0"

View file

@ -0,0 +1,58 @@
use std::env;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use enemize::asar;
use enemize::rom::RomData;
use enemize::PatchSet;
fn main() -> Result<(), anyhow::Error> {
let mut args = env::args();
let output_path = args.next_back().expect("No output file");
let symbols_path = args.next_back().expect("No symbols file");
let patch_path = args.next_back().expect("No patch file");
let rom_path = args.next_back().expect("No ROM file");
let mut rom_bytes = vec![];
let mut rom_file = File::open(&rom_path)?;
rom_file.read_to_end(&mut rom_bytes)?;
rom_bytes = if rom_bytes.len() % 1024 == 512 {
rom_bytes.into_iter().skip(512).collect()
} else {
rom_bytes
};
let expected = [
0x03, 0xa6, 0x39, 0x45, 0x39, 0x81, 0x91, 0x33, 0x7e, 0x89, 0x6e, 0x57, 0x71, 0xf7, 0x71,
0x73,
];
let actual: [u8; 16] = md5::compute(&rom_bytes).into();
assert_eq!(expected, actual, "Invalid rom file");
println!("Applying Patch to rom");
rom_bytes.resize(4 * 1024 * 1024, 0);
let mut symbols_file = File::open(&symbols_path)?;
let mut symbols_text = String::new();
symbols_file.read_to_string(&mut symbols_text)?;
let symbols = asar::load_symbols(&symbols_text)?;
let mut rom = RomData::new(symbols, rom_bytes);
let mut patches = PatchSet::load(Path::new(&patch_path))?;
patches.patch_rom(&mut rom);
rom.move_room_headers();
let rom_patches = rom.generate_patch();
patches.add_patches(rom_patches);
println!("Writing output file {}", output_path);
patches.save_to_file(Path::new(&output_path))?;
Ok(())
}

View file

@ -1,9 +1,15 @@
[package]
name = "enemize-rs"
name = "enemize"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { version = 1.0, features = ["derive"] }
anyhow = "1"
clap = { version = "3.1.18", features = ["derive"] }
lazy_static = "1.4"
rand = "0.8"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1"

42
enemize/src/asar.rs Normal file
View file

@ -0,0 +1,42 @@
use std::collections::HashMap;
pub type Symbols = HashMap<String, usize>;
pub fn load_symbols(contents: &str) -> anyhow::Result<Symbols> {
let symbols = contents
.lines()
.filter_map(|line| {
let mut words = line.split_ascii_whitespace();
match (words.next(), words.next(), words.next()) {
// Get only two-word lines.
(Some(address), Some(symbol), None) => {
Some((symbol.to_owned(), address.to_owned()))
}
_ => None,
}
})
.filter_map(|(symbol, mut address)| {
if let Some(colon_at) = address.find(':') {
address.remove(colon_at);
}
// Filter out the ones that don't have a hexadecimal number as the first word.
let snes_address = u32::from_str_radix(&address, 16).ok()?;
let pc_address = snes_to_pc_address(snes_address);
Some((symbol, pc_address))
})
.collect();
Ok(symbols)
}
pub fn snes_to_pc_address(snes: u32) -> usize {
((snes & 0x7FFF) + ((snes / 2) & 0xFF8000)) as usize
}
pub fn pc_to_snes_address(pc: usize) -> u32 {
((pc & 0x7FFF) + ((pc & 0xFF8000) * 2)) as u32
}

73
enemize/src/bosses/mod.rs Normal file
View file

@ -0,0 +1,73 @@
use std::marker::PhantomData;
use serde::{Deserialize, Serialize};
use crate::InvalidEnumError;
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
#[repr(u8)]
#[serde(into = "u8", try_from = "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 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 {
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)),
}
}
}

114
enemize/src/constants.rs Normal file
View file

@ -0,0 +1,114 @@
pub const ROOM_HEADER_BANK_LOCATION: usize = 0x0B5E7;
pub const DUNGEON_HEADER_POINTER_TABLE: usize = 0x271E2;
pub const DUNGEON_SPRITE_POINTER_TABLE: usize = 0x4D62E;
pub const OBJECT_DATA_POINTER_TABLE: usize = 0xF8000;
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;
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,
];

554
enemize/src/graph/item.rs Normal file
View file

@ -0,0 +1,554 @@
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use lazy_static::lazy_static;
#[derive(Clone, Copy)]
pub enum ItemType {
Normal,
Bottle,
Special,
Progressive { level: u8 },
Consumable { found: u8, used: u8 },
}
impl ItemType {
pub fn level(&self) -> u8 {
match self {
Self::Progressive { level } => *level,
_ => 0,
}
}
pub fn increase_level(&mut self) {
match self {
Self::Progressive { ref mut level } => *level += 1,
_ => (),
}
}
pub fn found(&self) -> u8 {
match self {
Self::Consumable { found, .. } => *found,
_ => 0,
}
}
pub fn increase_count(&mut self) {
match self {
Self::Consumable { ref mut found, .. } => *found += 1,
_ => (),
}
}
pub fn used(&self) -> u8 {
match self {
Self::Consumable { used, .. } => *used,
_ => 0,
}
}
pub fn consume(&mut self) {
match self {
Self::Consumable { ref mut used, .. } => *used += 1,
_ => (),
}
}
pub fn usable(&self) -> bool {
match self {
Self::Consumable { found, used } => found > used,
_ => false,
}
}
}
#[derive(Clone, Copy)]
pub struct Item {
pub id: ItemId,
pub name: &'static str,
pub item_type: ItemType,
}
impl Item {
pub fn normal(id: ItemId, name: &'static str) -> Self {
Self {
id,
name,
item_type: ItemType::Normal,
}
}
pub fn bottle(id: ItemId, name: &'static str) -> Self {
Self {
id,
name,
item_type: ItemType::Bottle,
}
}
pub fn special(id: ItemId, name: &'static str) -> Self {
Self {
id,
name,
item_type: ItemType::Special,
}
}
pub fn progressive(id: ItemId, name: &'static str) -> Self {
Self {
id,
name,
item_type: ItemType::Progressive { level: 0 },
}
}
pub fn consumable(id: ItemId, name: &'static str) -> Self {
Self {
id,
name,
item_type: ItemType::Consumable { found: 0, used: 0 },
}
}
}
impl PartialEq for Item {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for Item {}
impl Hash for Item {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
#[repr(u32)]
pub enum ItemId {
FightersSwordAndShield = 0x00,
MasterSword1 = 0x01,
TemperedSword = 0x02,
GoldenSword = 0x03,
FightersShield = 0x04,
FireShield = 0x05,
MirrorShield = 0x06,
FireRod = 0x07,
IceRod = 0x08,
Hammer = 0x09,
Hookshot = 0x0a,
Bow = 0x0b,
Boomerang = 0x0c,
Powder = 0x0d,
Bee = 0x0e,
Bombos = 0x0f,
Ether = 0x10,
Quake = 0x11,
Lamp = 0x12,
Shovel = 0x13,
Flute = 0x14,
Somaria = 0x15,
Bottle = 0x16,
PieceOfHeart = 0x17,
Byrna = 0x18,
Cape = 0x19,
Mirror = 0x1a,
L1Gloves = 0x1b,
L2Gloves = 0x1c,
Book = 0x1d,
Flippers = 0x1e,
MoonPearl = 0x1f,
Crystal = 0x20,
BugCatchingNet = 0x21,
BlueMail = 0x22,
RedMail = 0x23,
Key = 0x24,
Compass = 0x25,
HeartContainerNoAnimation = 0x26,
Bomb = 0x27,
ThreeBombs = 0x28,
Mushroom = 0x29,
MagicalBoomerang = 0x2a,
BottleRedPotion = 0x2b,
BottleGreenPotion = 0x2c,
BottleBluePotion = 0x2d,
RedPotion = 0x2e,
GreenPotion = 0x2f,
BluePotion = 0x30,
TenBombs = 0x31,
BigKey = 0x32,
DungeonMap = 0x33,
GreenRupee = 0x34,
BlueRupee = 0x35,
RedRupee = 0x36,
PendantGreen = 0x37,
PendantRed = 0x38,
PendantBlue = 0x39,
BowAndArrows = 0x3a,
BowAndSilverArrows = 0x3b,
BottleBee = 0x3c,
BottleFairy = 0x3d,
HeartContainer = 0x3e,
HeartContainerRefill = 0x3f,
OneHundredRupees = 0x40,
FiftyRupees = 0x41,
Heart = 0x42,
SingleArrow = 0x43,
TenArrows = 0x44,
SmallMagic = 0x45,
ThreeHundredRupees = 0x46,
RedRupee2 = 0x47,
BottleGoldenBee = 0x48,
FightersSword = 0x49,
FluteActive = 0x4a,
Boots = 0x4b,
MaxBombs = 0x4c,
MaxArrows = 0x4d,
HalfMagic = 0x4e,
QuarterMagic = 0x4f,
MasterSword2 = 0x50,
BombUpgrade5 = 0x51,
BombUpgrade10 = 0x52,
ArrowUpgrade5 = 0x53,
ArrowUpgrade10 = 0x54,
Programmable1 = 0x55,
Programmable2 = 0x56,
Programmable3 = 0x57,
SilverArrows = 0x58,
Rupoor = 0x59,
Nothing = 0x5a,
RedClock = 0x5b,
BlueClock = 0x5c,
GreenClock = 0x5d,
ProgressiveSword = 0x5e,
ProgressiveShield = 0x5f,
ProgressiveArmor = 0x60,
ProgressiveGloves = 0x61,
UniqueRngItem = 0x62,
NonUniqueRngItem = 0x63,
ProgressiveBow = 0x64,
ProgressiveBow2 = 0x65,
Triforce = 0x6a,
PowerStar = 0x6b,
TriforcePiece = 0x6c,
LightWorldMap = 0x70,
DarkWorldMap = 0x71,
GTMap = 0x72,
TurtleMap = 0x73,
ThievesMap = 0x74,
HeraMap = 0x75,
IceMap = 0x76,
SkullMap = 0x77,
MireMap = 0x78,
PodMap = 0x79,
SwampMap = 0x7a,
AgaMap = 0x7b,
DesertMap = 0x7c,
EasternMap = 0x7d,
HyruleMap = 0x7e,
SewersMap = 0x7f,
GTCompass = 0x82,
TurtleCompass = 0x83,
ThievesCompass = 0x84,
HeraCompass = 0x85,
IceCompass = 0x86,
SkullCompass = 0x87,
MireCompass = 0x88,
PodCompass = 0x89,
SwampCompass = 0x8a,
AgaCompass = 0x8b,
DesertCompass = 0x8c,
EasternCompass = 0x8d,
HyruleCompass = 0x8e,
SewersCompass = 0x8f,
SkeletonKey = 0x90,
Reserved0x91 = 0x91,
GTBigKey = 0x92,
TurtleBigKey = 0x93,
ThievesBigKey = 0x94,
HeraBigKey = 0x95,
IceBigKey = 0x96,
SkullBigKey = 0x97,
MireBigKey = 0x98,
PodBigKey = 0x99,
SwampBigKey = 0x9a,
AgaBigKey = 0x9b,
DesertBigKey = 0x9c,
EasternBigKey = 0x9d,
HyruleBigKey = 0x9e,
SewersBigKey = 0x9f,
SewersKey = 0xa0,
HyruleKey = 0xa1,
EasternKey = 0xa2,
DesertKey = 0xa3,
AgaKey = 0xa4,
SwampKey = 0xa5,
PodKey = 0xa6,
MireKey = 0xa7,
SkullKey = 0xa8,
IceKey = 0xa9,
HeraKey = 0xaa,
ThievesKey = 0xab,
TurtleKey = 0xac,
GTKey = 0xad,
Reserved0xAE = 0xae,
Reserved0xAF = 0xaf,
Agahnim1 = 0x01ff,
Agahnim1Boss = 0x02ff,
Agahnim2 = 0x03ff,
Agahnim2Boss = 0x04ff,
BigBomb = 0x05ff,
Crystal1 = 0x06ff,
Crystal2 = 0x07ff,
Crystal3 = 0x08ff,
Crystal4 = 0x09ff,
Crystal5 = 0x0aff,
Crystal6 = 0x0bff,
Crystal7 = 0x0cff,
DesertBoss = 0x0dff,
EasternBoss = 0x0eff,
Smith = 0x0fff,
Ganon = 0x10ff,
GTArmosBoss = 0x11ff,
GTLanmolasBoss = 0x12ff,
GTMoldormBoss = 0x13ff,
HeraBoss = 0x14ff,
IceBoss = 0x15ff,
IceBlock = 0x16ff,
L1Shield = 0x17ff,
L1Sword = 0x18ff,
L2Shield = 0x19ff,
L2Sword = 0x1aff,
L3Shield = 0x1bff,
L3Sword = 0x1cff,
L4Sword = 0x1dff,
MireBoss = 0x1eff,
MiseryMireToken = 0x1fff,
MireSwitch = 0x20ff,
PodBoss = 0x21ff,
PurpleChest = 0x22ff,
SkullBoss = 0x23ff,
SwampBoss = 0x24ff,
ThievesBoss = 0x25ff,
TurtleBoss = 0x26ff,
TurtleRockToken = 0x27ff,
ProgressiveMagic = 0x28ff,
}
lazy_static! {
pub static ref ITEMS: HashMap<ItemId, Item> = {
use ItemId::*;
let mut items: HashMap<ItemId, Item> = HashMap::new();
items.insert(FightersSwordAndShield, Item::normal(FightersSwordAndShield, "Fighters Sword and Shield"));
items.insert(MasterSword1, Item::normal(MasterSword1, "Master Sword"));
items.insert(TemperedSword, Item::normal(TemperedSword, "Tempered Sword"));
items.insert(GoldenSword, Item::normal(GoldenSword, "Golden Sword"));
items.insert(FightersShield, Item::normal(FightersShield, "Fighters Shield"));
items.insert(FireShield, Item::normal(FireShield, "Fire Shield"));
items.insert(FireRod, Item::normal(FireRod, "Fire Rod"));
items.insert(IceRod, Item::normal(IceRod, "Ice Rod"));
items.insert(Hammer, Item::normal(Hammer, "Hammer"));
items.insert(Hookshot, Item::normal(Hookshot, "Hookshot"));
items.insert(Bow, Item::normal(Bow, "Bow"));
items.insert(Boomerang, Item::normal(Boomerang, "Boomerang"));
items.insert(Powder, Item::normal(Powder, "Magic Powder"));
items.insert(Bee, Item::normal(Bee, "Bee"));
items.insert(Bombos, Item::normal(Bombos, "Bombos"));
items.insert(Ether, Item::normal(Ether, "Ether"));
items.insert(Quake, Item::normal(Quake, "Quake"));
items.insert(Lamp, Item::normal(Lamp, "Lamp"));
items.insert(Shovel, Item::normal(Shovel, "Shovel"));
items.insert(Flute, Item::normal(Flute, "Flute"));
items.insert(Somaria, Item::normal(Somaria, "Cane of Somaria"));
items.insert(Bottle, Item::bottle(Bottle, "Bottle"));
items.insert(PieceOfHeart, Item::normal(PieceOfHeart, "Piece of Heart"));
items.insert(Byrna, Item::normal(Byrna, "Cane of Byrna"));
items.insert(Cape, Item::normal(Cape, "Magic Cape"));
items.insert(Mirror, Item::normal(Mirror, "Magic Mirror"));
items.insert(L1Gloves, Item::progressive(L1Gloves, "Power Gloves"));
items.insert(L2Gloves, Item::progressive(L2Gloves, "Titan Mitts"));
items.insert(Book, Item::normal(Book, "Book of Mudora"));
items.insert(Flippers, Item::normal(Flippers, "Zora's Flippers"));
items.insert(MoonPearl, Item::normal(MoonPearl, "Moon Pearl"));
items.insert(Crystal, Item::special(Crystal, "Crystal"));
items.insert(BugCatchingNet, Item::normal(BugCatchingNet, "Bug Catching Net"));
items.insert(BlueMail, Item::normal(BlueMail, "Blue Mail"));
items.insert(RedMail, Item::normal(RedMail, "Red Mail"));
items.insert(Key, Item::consumable(Key, "Key"));
items.insert(Compass, Item::special(Compass, "Compass"));
items.insert(HeartContainerNoAnimation, Item::normal(HeartContainerNoAnimation, "Heart Container (no animation)"));
items.insert(Bomb, Item::normal(Bomb, "Bomb"));
items.insert(ThreeBombs, Item::normal(ThreeBombs, "3 Bombs"));
items.insert(Mushroom, Item::consumable(Mushroom, "Mushroom"));
items.insert(MagicalBoomerang, Item::normal(MagicalBoomerang, "Magical Boomerang"));
items.insert(BottleRedPotion, Item::bottle(BottleRedPotion, "Bottle (Red Potion)"));
items.insert(BottleGreenPotion, Item::bottle(BottleGreenPotion, "Bottle (Green Potion)"));
items.insert(BottleBluePotion, Item::bottle(BottleBluePotion, "Bottle (Blue Potion)"));
items.insert(RedPotion, Item::special(RedPotion, "Red Potion"));
items.insert(GreenPotion, Item::special(GreenPotion, "Green Potion"));
items.insert(BluePotion, Item::special(BluePotion, "Blue Potion"));
items.insert(TenBombs, Item::normal(TenBombs, "10 Bombs"));
items.insert(BigKey, Item::special(BigKey, "Big Key"));
items.insert(DungeonMap, Item::special(DungeonMap, "Dungeon Map"));
items.insert(GreenRupee, Item::normal(GreenRupee, "Green Rupee"));
items.insert(BlueRupee, Item::normal(BlueRupee, "Blue Rupee"));
items.insert(RedRupee, Item::normal(RedRupee, "Red Rupee"));
items.insert(PendantGreen, Item::special(PendantGreen, "Pendant of Courage"));
items.insert(PendantRed, Item::special(PendantRed, "Pendant of Wisdom"));
items.insert(PendantBlue, Item::special(PendantBlue, "Pendant of Power"));
items.insert(BowAndArrows, Item::special(BowAndArrows, "Bow and Arrows"));
items.insert(BowAndSilverArrows, Item::special(BowAndSilverArrows, "Bow and Silver Arrows"));
items.insert(BottleBee, Item::bottle(BottleBee, "Bottle (Bee)"));
items.insert(BottleFairy, Item::bottle(BottleFairy, "Bottle, (Fairy)"));
items.insert(HeartContainer, Item::normal(HeartContainer, "Heart Container"));
items.insert(HeartContainerRefill, Item::normal(HeartContainerRefill, "Heart Container (refill)"));
items.insert(OneHundredRupees, Item::normal(OneHundredRupees, "100 Rupees"));
items.insert(FiftyRupees, Item::normal(FiftyRupees, "50 Rupees"));
items.insert(Heart, Item::normal(Heart, "Heart"));
items.insert(SingleArrow, Item::normal(SingleArrow, "Single Arrow"));
items.insert(TenArrows, Item::normal(TenArrows, "10 Arrows"));
items.insert(SmallMagic, Item::normal(SmallMagic, "Small Magic Refill"));
items.insert(ThreeHundredRupees, Item::normal(ThreeHundredRupees, "300 Rupees"));
items.insert(RedRupee2, Item::normal(RedRupee2, "Red Rupee"));
items.insert(BottleGoldenBee, Item::bottle(BottleGoldenBee, "Bottle (Golden Bee)"));
items.insert(FightersSword, Item::normal(FightersSword, "Fighter's Sword"));
items.insert(FluteActive, Item::normal(FluteActive, "Flute (activated)"));
items.insert(Boots, Item::normal(Boots, "Pegasus Boots"));
items.insert(MaxBombs, Item::normal(MaxBombs, "Max Bomb Upgrade (50)"));
items.insert(MaxArrows, Item::normal(MaxArrows, "Max Arrow Upgrade (70)"));
items.insert(HalfMagic, Item::normal(HalfMagic, "Half Magic"));
items.insert(QuarterMagic, Item::normal(QuarterMagic, "Quarter Magic"));
items.insert(MasterSword2, Item::normal(MasterSword2, "Master Sword"));
items.insert(BombUpgrade5, Item::normal(BombUpgrade5, "Bomb Upgrade (5)"));
items.insert(BombUpgrade10, Item::normal(BombUpgrade10, "Bomb Upgrade (10)"));
items.insert(ArrowUpgrade5, Item::normal(ArrowUpgrade5, "Arrow Upgrade (5)"));
items.insert(ArrowUpgrade10, Item::normal(ArrowUpgrade10, "Arrow Upgrade (10)"));
items.insert(Programmable1, Item::normal(Programmable1, "Programmable 1"));
items.insert(Programmable2, Item::normal(Programmable2, "Programmable 2"));
items.insert(Programmable3, Item::normal(Programmable3, "Programmable 3"));
items.insert(SilverArrows, Item::normal(SilverArrows, "Silver Arrows"));
items.insert(Rupoor, Item::normal(Rupoor, "Rupoor"));
items.insert(Nothing, Item::normal(Nothing, "Nothing (Null Item)"));
items.insert(RedClock, Item::consumable(RedClock, "Red Clock"));
items.insert(BlueClock, Item::consumable(BlueClock, "Blue Clock"));
items.insert(GreenClock, Item::consumable(GreenClock, "Green Clock"));
items.insert(ProgressiveSword, Item::progressive(ProgressiveSword, "Progressive Sword"));
items.insert(ProgressiveShield, Item::progressive(ProgressiveShield, "Progressive Shield"));
items.insert(ProgressiveArmor, Item::progressive(ProgressiveArmor, "Progressive Armor"));
items.insert(ProgressiveGloves, Item::progressive(ProgressiveGloves, "Progressive Gloves"));
items.insert(UniqueRngItem, Item::special(UniqueRngItem, "Unique RNG Item (RNG Pool Single)"));
items.insert(NonUniqueRngItem, Item::special(NonUniqueRngItem, "Non-unique RNG Item (RNG Pool Multi)"));
items.insert(ProgressiveBow, Item::normal(ProgressiveBow, "Progressive Bow")); // *NOT* ProgressiveItem???
items.insert(ProgressiveBow2, Item::normal(ProgressiveBow2, "Progressive Bow 2"));
items.insert(Triforce, Item::special(Triforce, "Triforce (Single Goal Item)"));
items.insert(PowerStar, Item::special(PowerStar, "Power Star (Multi Goal Item)"));
items.insert(TriforcePiece, Item::special(TriforcePiece, "Triforce Piece"));
items.insert(LightWorldMap, Item::special(LightWorldMap, "Light World Map"));
items.insert(DarkWorldMap, Item::special(DarkWorldMap, "Dark World Map"));
items.insert(GTMap, Item::special(GTMap, "Ganon's Tower Map"));
items.insert(TurtleMap, Item::special(TurtleMap, "Turtle Rock Map"));
items.insert(ThievesMap, Item::special(ThievesMap, "Thieves' Town Map"));
items.insert(HeraMap, Item::special(HeraMap, "Tower of Hera Map"));
items.insert(IceMap, Item::special(IceMap, "Ice Palace Map"));
items.insert(SkullMap, Item::special(SkullMap, "Skull Woods Map"));
items.insert(MireMap, Item::special(MireMap, "Misery Mire Map"));
items.insert(PodMap, Item::special(PodMap, "Palace of Darkness Map"));
items.insert(SwampMap, Item::special(SwampMap, "Swamp Palace Map"));
items.insert(AgaMap, Item::special(AgaMap, "Agahnim's Tower Map"));
items.insert(DesertMap, Item::special(DesertMap, "Desert Palace Map"));
items.insert(EasternMap, Item::special(EasternMap, "Eastern Palace Map"));
items.insert(HyruleMap, Item::special(HyruleMap, "Hyrule Castle Map"));
items.insert(SewersMap, Item::special(SewersMap, "Sewers Map")); // not the same as HyruleMap?
items.insert(GTCompass, Item::special(GTCompass, "Ganon's Tower Compass"));
items.insert(TurtleCompass, Item::special(TurtleCompass, "Turtle Rock Compass"));
items.insert(ThievesCompass, Item::special(ThievesCompass, "Thieves' Town Compass"));
items.insert(HeraCompass, Item::special(HeraCompass, "Tower of Hera Compass"));
items.insert(IceCompass, Item::special(IceCompass, "Ice Palace Compass"));
items.insert(SkullCompass, Item::special(SkullCompass, "Skull Woods Compass"));
items.insert(MireCompass, Item::special(MireCompass, "Misery Mire Compass"));
items.insert(PodCompass, Item::special(PodCompass, "Palace of Darkness Compass"));
items.insert(SwampCompass, Item::special(SwampCompass, "Swamp Palace Compass"));
items.insert(AgaCompass, Item::special(AgaCompass, "Agahnim's Tower Compass"));
items.insert(DesertCompass, Item::special(DesertCompass, "Desert Palace Compass"));
items.insert(EasternCompass, Item::special(EasternCompass, "Eastern Palace Compass"));
items.insert(HyruleCompass, Item::special(HyruleCompass, "Hyrule Castle Compass"));
items.insert(SewersCompass, Item::special(SewersCompass, "Sewers Compass")); // not the same as HyruleCompass?
items.insert(SkeletonKey, Item::special(SkeletonKey, "Skeleton Key"));
items.insert(Reserved0x91, Item::special(Reserved0x91, "<Reserved>"));
items.insert(GTBigKey, Item::special(GTBigKey, "Ganon's Tower Big Key"));
items.insert(TurtleBigKey, Item::special(TurtleBigKey, "Turtle Rock Big Key"));
items.insert(ThievesBigKey, Item::special(ThievesBigKey, "Thieves' Town Big Key"));
items.insert(HeraBigKey, Item::special(HeraBigKey, "Tower of Hera Big Key"));
items.insert(IceBigKey, Item::special(IceBigKey, "Ice Palace Big Key"));
items.insert(SkullBigKey, Item::special(SkullBigKey, "Skull Woods Big Key"));
items.insert(MireBigKey, Item::special(MireBigKey, "Misery Mire Big Key"));
items.insert(PodBigKey, Item::special(PodBigKey, "Palace of Darkness Big Key"));
items.insert(SwampBigKey, Item::special(SwampBigKey, "Swamp Palace Big Key"));
items.insert(AgaBigKey, Item::special(AgaBigKey, "Agahnim's Tower Big Key"));
items.insert(DesertBigKey, Item::special(DesertBigKey, "Desert Palace Big Key"));
items.insert(EasternBigKey, Item::special(EasternBigKey, "Eastern Palace Big Key"));
items.insert(HyruleBigKey, Item::special(HyruleBigKey, "Hyrule Castle Big Key"));
items.insert(SewersBigKey, Item::special(SewersBigKey, "Sewers Big Key")); // not the same as HyruleBigKey?
items.insert(SewersKey, Item::consumable(SewersKey, "Sewers Key"));
items.insert(HyruleKey, Item::consumable(HyruleKey, "Hyrule Castle Key")); // not the same as SewersKey?
items.insert(EasternKey, Item::consumable(EasternKey, "Eastern Palace Key"));
items.insert(DesertKey, Item::consumable(DesertKey, "Desert Palace Key"));
items.insert(AgaKey, Item::consumable(AgaKey, "Agahnim's Tower Key"));
items.insert(SwampKey, Item::consumable(SwampKey, "Swamp Palace Key"));
items.insert(PodKey, Item::consumable(PodKey, "Palace of Darkness Key"));
items.insert(MireKey, Item::consumable(MireKey, "Misery Mire Key"));
items.insert(SkullKey, Item::consumable(SkullKey, "Skull Woods Key"));
items.insert(IceKey, Item::consumable(IceKey, "Ice Palace Key"));
items.insert(HeraKey, Item::consumable(HeraKey, "Tower of Hera Key"));
items.insert(ThievesKey, Item::consumable(ThievesKey, "Thieves' Town Key"));
items.insert(TurtleKey, Item::consumable(TurtleKey, "Turtle Rock Key"));
items.insert(GTKey, Item::consumable(GTKey, "Ganon's Tower Key"));
items.insert(Reserved0xAE, Item::special(Reserved0xAE, "<Reserved>"));
items.insert(Reserved0xAF, Item::special(Reserved0xAF, "<Reserved>"));
items.insert(Agahnim1, Item::special(Agahnim1, "Agahnim 1 Defeated"));
items.insert(Agahnim1Boss, Item::special(Agahnim1Boss, "Agahnim Boss"));
items.insert(Agahnim2, Item::special(Agahnim2, "Agahnim 2 Defeated"));
items.insert(Agahnim2Boss, Item::special(Agahnim2Boss, "Agahnim 2 Boss"));
items.insert(BigBomb, Item::special(BigBomb, "Big Bomb"));
items.insert(Crystal1, Item::special(Crystal1, "Crystal 1"));
items.insert(Crystal2, Item::special(Crystal2, "Crystal 2"));
items.insert(Crystal3, Item::special(Crystal3, "Crystal 3"));
items.insert(Crystal4, Item::special(Crystal4, "Crystal 4"));
items.insert(Crystal5, Item::special(Crystal5, "Crystal 5"));
items.insert(Crystal6, Item::special(Crystal6, "Crystal 6"));
items.insert(Crystal7, Item::special(Crystal7, "Crystal 7"));
items.insert(DesertBoss, Item::special(DesertBoss, "Desert Boss"));
items.insert(EasternBoss, Item::special(EasternBoss, "Eastern Boss"));
items.insert(Smith, Item::special(Smith, "Frog Smith"));
items.insert(Ganon, Item::special(Ganon, "Ganon"));
items.insert(GTArmosBoss, Item::special(GTArmosBoss, "GT Armos Boss"));
items.insert(GTLanmolasBoss, Item::special(GTLanmolasBoss, "GT Lanmolas Boss"));
items.insert(GTMoldormBoss, Item::special(GTMoldormBoss, "GT Moldorm Boss"));
items.insert(HeraBoss, Item::special(HeraBoss, "Hera Boss"));
items.insert(IceBoss, Item::special(IceBoss, "Ice Boss"));
items.insert(IceBlock, Item::special(IceBlock, "Ice Palace Block"));
items.insert(L1Shield, Item::progressive(L1Shield, "L1 Shield"));
items.insert(L1Sword, Item::progressive(L1Sword, "L1 Sword"));
items.insert(L2Shield, Item::progressive(L2Shield, "L2 Shield"));
items.insert(L2Sword, Item::progressive(L2Sword, "L2 Sword"));
items.insert(L3Shield, Item::progressive(L3Shield, "L3 Shield"));
items.insert(L3Sword, Item::progressive(L3Sword, "L3 Sword"));
items.insert(L4Sword, Item::progressive(L4Sword, "L4 Sword"));
items.insert(MireBoss, Item::special(MireBoss, "Mire Boss"));
items.insert(MiseryMireToken, Item::special(MiseryMireToken, "Misery Mire Entrance Token"));
items.insert(MireSwitch, Item::special(MireSwitch, "Misery Mire Switch"));
items.insert(PodBoss, Item::special(PodBoss, "PoD Boss"));
items.insert(PurpleChest, Item::special(PurpleChest, "Purple Chest"));
items.insert(SkullBoss, Item::special(SkullBoss, "Skull Boss"));
items.insert(ThievesBoss, Item::special(ThievesBoss, "Thieves Boss"));
items.insert(TurtleBoss, Item::special(TurtleBoss, "Turtle Boss"));
items.insert(ProgressiveMagic, Item::special(ProgressiveMagic, "Progressive Magic"));
items
};
}

File diff suppressed because it is too large Load diff

3
enemize/src/graph/mod.rs Normal file
View file

@ -0,0 +1,3 @@
pub mod dungeons;
pub mod item;
pub mod requirement;

View file

@ -0,0 +1,16 @@
use std::collections::HashSet;
use super::item::Item;
pub struct Requirement(HashSet<Item>);
impl Requirement {
pub fn new(items: &[Item]) -> Requirement {
Requirement(HashSet::from_iter(items.into_iter().map(|i| *i)))
}
pub fn is_met(&self, items: &[Item]) -> bool {
let items = HashSet::from_iter(items.into_iter().map(|i| *i));
self.0.is_subset(&items)
}
}

View file

@ -1,41 +1,80 @@
use std::io::Read;
use std::fs::File;
use std::marker::PhantomData;
use std::path::{Path, PathBuf};
use serde::Serialize;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::rom::RomData;
pub mod asar;
pub mod bosses;
pub mod constants;
pub mod graph;
pub mod option_flags;
pub mod randomize;
pub mod rom;
#[derive(Serialize)]
#[derive(Debug, Error)]
#[error("Not a valid value for {0:?}")]
pub struct InvalidEnumError<T>(PhantomData<T>);
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Patch {
pub address: usize,
pub patch_data: Vec<u8>,
}
#[derive(Serialize)]
#[derive(Default)]
pub struct PatchSet {
filename: PathBuf,
patches: Vec<Patch>
patches: Vec<Patch>,
}
impl PatchSet {
pub fn load(filename: Path) -> Result<PatchSet, anyhow::Error> {
pub fn load(filename: &Path) -> Result<PatchSet, anyhow::Error> {
let patches = {
let mut file = File::open(filename)?;
let mut buffer = std::io::BufReader::new(file);
let file = File::open(filename)?;
let buffer = std::io::BufReader::new(file);
serde_json::from_reader(buffer)?
};
PatchSet {
Ok(PatchSet {
filename: filename.into(),
patches: patches
}
patches: patches,
})
}
pub fn filename(&self) -> Path {
pub fn save(&self) -> anyhow::Result<()> {
let file = File::create(&self.filename)?;
let buffer = std::io::BufWriter::new(file);
serde_json::to_writer(buffer, &self.patches)?;
Ok(())
}
pub fn save_to_file(&self, filename: &Path) -> anyhow::Result<()> {
let file = File::create(filename)?;
let buffer = std::io::BufWriter::new(file);
serde_json::to_writer(buffer, &self.patches)?;
Ok(())
}
pub fn add_patch(&mut self, patch: Patch) {
self.patches.push(patch);
}
pub fn add_patches(&mut self, patches: Vec<Patch>) {
self.patches.extend(patches);
}
pub fn filename(&self) -> &Path {
self.filename.as_path()
}
pub fn patchRom(&self, &mut rom: RomData) {
for patch in self.patches {
pub fn patch_rom(&self, rom: &mut RomData) {
for patch in self.patches.iter() {
rom.patch_data(patch);
}
}

View file

@ -1,3 +1,88 @@
fn main() {
println!("Hello, world!");
use std::fs::File;
use std::io::prelude::*;
use std::path::PathBuf;
use std::time::Instant;
use anyhow::ensure;
use clap::Parser;
use enemize::{asar, rom::RomData};
const ASAR_SYMBOLS: &'static str = include_str!("../../asar_symbols.txt");
const ENEMIZER_BASE_PATCH: &'static str = include_str!("../../enemizer_base_patch.json");
/// Randomizes enemy placements in The Legend of Zelda: A Link to the Past for the Super Nintendo
/// Entertainment System
#[derive(Debug, Parser)]
#[clap(author, version, about)]
struct Args {
/// path to the base rom file
#[clap(short, long)]
rom: PathBuf,
/// path to the enemizerOptions.json
#[clap(short, long)]
enemizer: PathBuf,
/// seed number
#[clap(short, long)]
seed: Option<i32>,
/// path to the intended output file
#[clap(short, long)]
output: PathBuf,
/// operate in binary mode (takes already randomized SFC and applies enemizer directly to ROM)
#[clap(long)]
binary: bool,
/// path to base2patched.json (not used)
#[clap(long)]
base: Option<PathBuf>,
/// path to the randomizerPatch.json (not used)
#[clap(long)]
randomizer: Option<PathBuf>,
}
fn main() -> anyhow::Result<()> {
let args = Args::parse();
let stopwatch = Instant::now();
let options = {
let mut opts_file = File::open(args.enemizer)?;
let mut json = String::new();
opts_file.read_to_string(&mut json)?;
serde_json::from_str(&json)?
};
let symbols = load_symbols()?;
let mut raw_data = vec![];
let mut rom_file = File::open(args.rom)?;
rom_file.read_to_end(&mut raw_data)?;
raw_data.resize(2 * 1024 * 1024, 0);
let mut rom = RomData::new(symbols, raw_data);
ensure!(!rom.is_enemizer(), "It appears that the provided base ROM is already enemized. Please ensure you are using an original game ROM.");
// 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();
rom.randomize(ENEMIZER_BASE_PATCH, options, seed)?;
let mut out_file = File::create(&args.output)?;
if args.binary {
out_file.write_all(rom.get_rom_bytes())?;
out_file.flush()?;
println!("Generated SFC file {}", args.output.to_string_lossy());
} else {
let patches = rom.generate_patch();
serde_json::to_writer(out_file, &patches)?;
}
println!("Seed generated in: {}ms", stopwatch.elapsed().as_millis());
Ok(())
}
fn load_symbols() -> anyhow::Result<asar::Symbols> {
asar::load_symbols(ASAR_SYMBOLS)
}

774
enemize/src/option_flags.rs Normal file
View file

@ -0,0 +1,774 @@
use std::collections::HashMap;
use std::fmt;
use std::marker::PhantomData;
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use crate::bosses::BossType;
use crate::InvalidEnumError;
#[derive(Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "PascalCase")]
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(Clone, Copy, Debug, Deserialize, Serialize)]
#[serde(into = "u8", try_from = "u8")]
pub enum RandomizeEnemiesType {
Basic,
Normal,
Hard,
Chaos,
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 {
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(Clone, Copy, Debug, Deserialize, Serialize)]
#[serde(into = "u8", try_from = "u8")]
pub enum RandomizeEnemyHpType {
Easy,
Medium,
Hard,
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 {
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(Clone, Copy, Debug, Deserialize, Serialize)]
#[serde(into = "u8", try_from = "u8")]
pub enum RandomizeBossesType {
Basic,
Normal,
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 {
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(Clone, Copy, Debug, Deserialize, Serialize)]
#[serde(into = "u8", try_from = "u8")]
pub enum SwordType {
Normal,
}
impl From<SwordType> for u8 {
fn from(_t: SwordType) -> u8 {
0
}
}
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(Clone, Copy, Debug, Deserialize, Serialize)]
#[serde(into = "u8", try_from = "u8")]
pub enum ShieldType {
Normal,
}
impl From<ShieldType> for u8 {
fn from(_t: ShieldType) -> u8 {
0
}
}
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(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub enum AbsorbableType {
Heart,
GreenRupee,
BlueRupee,
RedRupee,
#[serde(alias = "Bomb_1")]
Bomb1,
#[serde(alias = "Bomb_4")]
Bomb4,
#[serde(alias = "Bomb_8")]
Bomb8,
SmallMagic,
FullMagic,
#[serde(alias = "Arrow_5")]
Arrow5,
#[serde(alias = "Arrow_10")]
Arrow10,
Fairy,
Key,
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 {
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::RedRupee),
4 => Ok(Self::Bomb1),
5 => Ok(Self::Bomb4),
6 => Ok(Self::Bomb8),
7 => Ok(Self::SmallMagic),
8 => Ok(Self::FullMagic),
9 => Ok(Self::Arrow5),
10 => Ok(Self::Arrow10),
11 => Ok(Self::Fairy),
12 => Ok(Self::Key),
13 => Ok(Self::BigKey),
_ => Err(InvalidEnumError(PhantomData)),
}
}
}
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(Clone, Copy, Debug, Deserialize, Serialize)]
#[serde(into = "u8", try_from = "u8")]
pub enum HeartBeepSpeed {
Normal,
Half,
Quarter,
Off,
}
impl Default for HeartBeepSpeed {
fn default() -> Self {
HeartBeepSpeed::Normal
}
}
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 {
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(Clone, Copy, Debug, Deserialize, Serialize)]
#[serde(into = "u8", try_from = "u8")]
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 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 {
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, Deserialize, Serialize)]
#[serde(default, rename_all = "PascalCase")]
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::RedRupee, bytes[13] != 0);
absorbable_types.insert(AbsorbableType::Bomb1, bytes[14] != 0);
absorbable_types.insert(AbsorbableType::Bomb4, bytes[15] != 0);
absorbable_types.insert(AbsorbableType::Bomb8, bytes[16] != 0);
absorbable_types.insert(AbsorbableType::SmallMagic, bytes[17] != 0);
absorbable_types.insert(AbsorbableType::FullMagic, bytes[18] != 0);
absorbable_types.insert(AbsorbableType::Arrow5, bytes[19] != 0);
absorbable_types.insert(AbsorbableType::Arrow10, bytes[20] != 0);
absorbable_types.insert(AbsorbableType::Fairy, bytes[21] != 0);
absorbable_types.insert(AbsorbableType::Key, bytes[21] != 1);
absorbable_types.insert(AbsorbableType::BigKey, bytes[23] != 0);
Ok(OptionFlags {
randomize_enemies: bytes[0] != 0,
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[24] != 0,
randomize_bosses: bytes[25] != 0,
randomize_bosses_type: bytes[26].try_into()?,
//randomize_boss_health: bytes[27] != 0,
//randomize_boss_health_min_amount: bytes[28],
//randomize_boss_health_max_amount: bytes[29],
//randomize_boss_damage: bytes[30] != 0,
//randomize_boss_damage_min_amount: bytes[31],
//randomize_boss_damage_max_amount: bytes[32],
//randomize_boss_behavior: bytes[33] != 0,
randomize_dungeon_palettes: bytes[34] != 0,
set_blackout_mode: bytes[35] != 0,
randomize_overworld_palettes: bytes[36] != 0,
randomize_sprite_palettes: bytes[37] != 0,
set_advanced_sprite_palettes: bytes[38] != 0,
puke_mode: bytes[39] != 0,
negative_mode: bytes[40] != 0,
grayscale_mode: bytes[41] != 0,
generate_spoilers: bytes[42] != 0,
randomize_link_sprite_palette: bytes[43] != 0,
randomize_pots: bytes[44] != 0,
shuffle_music: bytes[45] != 0,
bootleg_magic: bytes[46] != 0,
debug_mode: bytes[47] != 0,
//custom_bosses: bytes[48] != 0,
heart_beep_speed: bytes[49].try_into()?,
alternate_gfx: bytes[50] != 0,
// Skip byte 51 (shield_graphics)
shuffle_enemy_damage_groups: bytes[52] != 0,
enemy_damage_chaos_mode: bytes[53] != 0,
// Skip byte 54 (sword_graphics)
bee_mizer: bytes[55] != 0,
bees_level: bytes[56].try_into()?,
debug_force_enemy: bytes[57] != 0,
debug_force_enemy_id: bytes[58],
debug_force_boss: bytes[59] != 0,
debug_force_boss_id: bytes[60].try_into()?,
debug_open_shutter_doors: bytes[61] != 0,
debug_force_enemy_damage_zero: bytes[62] != 0,
debug_show_room_id_in_rupee_counter: bytes[63] != 0,
o_h_k_o: bytes[64] != 0,
randomize_tile_trap_pattern: bytes[65] != 0,
randomize_tile_trap_floor_tile: bytes[66] != 0,
allow_killable_thief: bytes[67] != 0,
randomize_sprite_on_hit: bytes[68] != 0,
hero_mode: bytes[69] != 0,
increase_brightness: bytes[70] != 0,
mute_music_enable_msu_1: bytes[71] != 0,
agahnim_bounce_balls: bytes[72] != 0,
..Default::default()
})
}
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(0); //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(0); //bytes.push(self.boss_madness as u8);
bytes.push(self.randomize_bosses as u8);
bytes.push(self.randomize_bosses_type as u8);
bytes.push(0); //bytes.push(self.randomize_boss_health as u8);
bytes.push(0); //bytes.push(self.randomize_boss_health_min_amount);
bytes.push(0); //bytes.push(self.randomize_boss_health_max_amount);
bytes.push(0); //bytes.push(self.randomize_boss_damage as u8);
bytes.push(0); //bytes.push(self.randomize_boss_damage_min_amount);
bytes.push(0); //bytes.push(self.randomize_boss_damage_max_amount);
bytes.push(0); //bytes.push(self.randomize_boss_behavior as u8);
bytes.push(self.randomize_dungeon_palettes as u8);
bytes.push(self.set_blackout_mode as u8);
bytes.push(self.randomize_overworld_palettes as u8);
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(0); //bytes.push(self.custom_bosses as u8);
bytes.push(self.heart_beep_speed as u8);
bytes.push(self.alternate_gfx as u8);
bytes.push(0); // self.shield_graphics
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(),
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_option_flags_serde() {
let empty = "{}";
let actual: OptionFlags = serde_json::from_str(empty).expect("Can't deserialize empty");
let expected =
serde_json::to_string(&OptionFlags::default()).expect("Can't serialize default");
assert_eq!(
serde_json::to_string(&actual).expect("Can't roundtrip"),
expected
);
}
}

38
enemize/src/randomize.rs Normal file
View file

@ -0,0 +1,38 @@
use anyhow::ensure;
use crate::option_flags::OptionFlags;
use crate::rom::RomData;
use crate::{Patch, PatchSet};
impl RomData {
pub fn randomize(
&mut self,
base_patch: &str,
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: Vec<Patch> = serde_json::from_str(base_patch)?;
let mut patch_set = PatchSet::default();
patch_set.add_patches(patches);
patch_set.patch_rom(self);
Ok(())
}
}

View file

@ -1,7 +1,307 @@
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::{Index, IndexMut, Range};
use anyhow::bail;
use crate::asar::{pc_to_snes_address, snes_to_pc_address, Symbols};
use crate::constants::*;
use crate::option_flags::OptionFlags;
use crate::Patch;
pub const ENEMIZER_INFO_SEED_OFFSET: usize = 0;
pub const ENEMIZER_INFO_SEED_LENGTH: usize = 12;
pub const ENEMIZER_INFO_VERSION_OFFSET: usize =
ENEMIZER_INFO_SEED_OFFSET + ENEMIZER_INFO_SEED_LENGTH;
pub const ENEMIZER_INFO_VERSION_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 {
asar_symbols: Symbols,
spoiler: String,
pub(crate) rom_data: Vec<u8>,
patch_data: BTreeMap<usize, u8>,
pub(crate) seed: i32,
}
impl RomData {
pub fn spoiler(&self) -> &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> {
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
}
pub fn get_rom_bytes(&self) -> &[u8] {
&self.rom_data
}
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.asar_symbols["enemizer_info_table"] + ENEMIZER_INFO_SEED_OFFSET]
== b'E'
&& self.rom_data
[self.asar_symbols["enemizer_info_table"] + ENEMIZER_INFO_SEED_OFFSET + 1]
== b'N'
}
pub fn is_randomizer(&self) -> bool {
let acceptable = [
b"VT", // item rando
b"ER", // entrance rando
b"DR", // door rando
b"BM", // Berserker's multiworld
b"BD", // Berserker's multiworld doors
b"AP", // Archipelago
b"AD", // Archipelago with door rando
];
acceptable
.iter()
.any(|abbr| &abbr[..] == &self.rom_data[0x7fc0..0x7fc2])
|| (self.rom_data.len() >= 0x20_0000
&& &self.rom_data[0x7fc0..0x7fcf] == b"ZELDANODENSETSU")
}
pub fn is_race(&self) -> bool {
self.is_randomizer()
&& (&self.rom_data[0x180213..0x180214] == &[1, 0]
|| &self.rom_data[0x7fc0..0x7fca] == b"VT TOURNEY")
}
fn assert_rom_length(&self) {
assert!(
self.rom_data.len() >= ENEMIZER_FILE_LENGTH,
"You need to expand the rom before you can use Enemizer features."
);
}
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.asar_symbols["enemizer_info_table"] + ENEMIZER_INFO_SEED_OFFSET;
let seed_end = seed_start + ENEMIZER_INFO_SEED_LENGTH;
let seed_bytes = &self.rom_data[seed_start..seed_end];
let seed_str = &std::str::from_utf8(seed_bytes)?.trim_end_matches('\0')[2..];
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.asar_symbols["enemizer_info_table"] + 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 expand_rom(&mut self) {
self.rom_data.resize(0x40_0000, 0);
// Update header length.
self.rom_data[0x7fd7] = 0x0c;
self.set_patch_bytes(0x7fd7..0x7fd8);
self.set_enemizer_version("6.0.32".to_owned());
}
pub fn get_enemizer_version(&self) -> anyhow::Result<&str> {
if self.is_enemizer() {
let version_start =
self.asar_symbols["enemizer_info_table"] + ENEMIZER_INFO_VERSION_OFFSET;
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.asar_symbols["enemizer_info_table"] + ENEMIZER_INFO_VERSION_OFFSET;
let version_end = version_start + ENEMIZER_INFO_VERSION_LENGTH;
self.set_rom_bytes(&bytes, version_start..version_end);
self.set_patch_bytes(version_start..version_end)
}
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.asar_symbols["enemizer_info_table"] + ENEMIZER_INFO_FLAGS_OFFSET;
let flags_end = flags_start + bytes.len();
self.set_rom_bytes(&bytes, flags_start..flags_end);
self.set_patch_bytes(flags_start..flags_end);
Ok(())
}
pub fn get_info_flags(&self) -> Option<OptionFlags> {
if self.is_enemizer() {
let flags_start = self.asar_symbols["enemizer_info_table"] + ENEMIZER_INFO_FLAGS_OFFSET;
let flags_end = flags_start + ENEMIZER_INFO_FLAGS_LENGTH;
OptionFlags::try_from_bytes(&self.rom_data[flags_start..flags_end]).ok()
} else {
None
}
}
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) {
self.set_rom_bytes(
&patch.patch_data,
patch.address..(patch.address + patch.patch_data.len()),
);
}
pub fn move_room_headers(&mut self) {
let table_base = DUNGEON_HEADER_POINTER_TABLE;
let header_base = self.asar_symbols["room_header_table"];
// Change room header bank (at 0xb5e7) to 0x04.
let new_room_bank =
self.rom_data[self.asar_symbols["moved_room_header_bank_value_address"]];
self.rom_data[ROOM_HEADER_BANK_LOCATION] = new_room_bank;
// Copy header table.
for i in 0..320 {
// Get i'th room's pointer.
// Pointers are 16bits, with a hard-coded bank.
let room_pointer = [
self.rom_data[table_base + (i * 2)],
self.rom_data[table_base + (i * 2) + 1],
4,
0,
];
let snes_address = u32::from_le_bytes(room_pointer);
let pc_address = snes_to_pc_address(snes_address);
// Copy i'th room's headers to new room_header_table.
let header_start = header_base + (i * 14);
self.rom_data
.copy_within(pc_address..(pc_address + 14), header_start);
}
// Repoint the pointer table to the new header table.
for i in 0..320 {
let snes = pc_to_snes_address(header_base + (i * 14)).to_le_bytes();
assert_eq!(snes[2], new_room_bank, "We changed banks in the middle of moving the room headers! This should have been caught by dev team, unless you were playing with files you shouldn't touch.");
self.rom_data[(table_base + (i * 2))..(table_base + (i * 2) + 1)]
.copy_from_slice(&snes[0..1]);
}
}
}
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]
}
}

9
prepare.sh Normal file
View file

@ -0,0 +1,9 @@
#!/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
cp enemizer_base_patch.json target/debug/enemizerBasePatch.json