From 49988e4b31e907216dbd2edc421cee745c1b9ae8 Mon Sep 17 00:00:00 2001 From: AlexKnauth Date: Sat, 17 Feb 2024 10:21:00 -0500 Subject: [PATCH 1/7] Split OnObtainGhostCloth --- src/hollow_knight_memory.rs | 20 ++++++++++++++++++++ src/splits.rs | 7 +++++++ 2 files changed, 27 insertions(+) diff --git a/src/hollow_knight_memory.rs b/src/hollow_knight_memory.rs index 376ed6a2..f859e67a 100644 --- a/src/hollow_knight_memory.rs +++ b/src/hollow_knight_memory.rs @@ -2828,6 +2828,26 @@ impl PlayerDataStore { self.increased_i32(process, game_manager_finder, "royal_charm_state", &game_manager_finder.player_data_pointers.royal_charm_state) } + fn been_dead_for_a_tick(&mut self, prc: &Process, gmf: &GameManagerFinder, key: &'static str, pointer: &UnityPointer) -> bool { + if gmf.is_game_state_non_continuous(prc) { + self.map_bool.remove(key); + return false; + } + match self.map_bool.get(key) { + None | Some(false) => { + if let Ok(dead_now) = pointer.deref(prc, &gmf.module, &gmf.image) { + self.map_bool.insert(key, dead_now); + } + false + } + Some(true) => true, + } + } + + pub fn traitor_lord_been_dead_for_a_tick(&mut self, prc: &Process, gmf: &GameManagerFinder) -> bool { + self.been_dead_for_a_tick(prc, gmf, "killed_traitor_lord", &gmf.player_data_pointers.killed_traitor_lord) + } + fn kills_on_entry(&mut self, prc: &Process, gmf: &GameManagerFinder, key: &'static str, pointer: &UnityPointer) -> Option { if gmf.is_game_state_non_continuous(prc) { self.map_i32.remove(key); diff --git a/src/splits.rs b/src/splits.rs index 80aa90f4..5833f6b6 100644 --- a/src/splits.rs +++ b/src/splits.rs @@ -1578,6 +1578,10 @@ pub enum Split { /// Splits when obtaining the essence from Blue Child Joni OnObtainGhostJoni, // TODO: resolve possible confounding essence sources for Cloth, Vespa, and Revek + /// Dream Nail Cloth (Obtain) + /// + /// Splits when obtaining the essence from Cloth + OnObtainGhostCloth, // endregion: Essence, Trees, and Ghosts // region: Maps and Cornifer @@ -3857,6 +3861,9 @@ pub fn continuous_splits(s: &Split, p: &Process, g: &GameManagerFinder, pds: &mu Split::OnObtainGhostGravedigger => should_split(pds.incremented_dream_orbs(p, g) && g.get_scene_name(p).is_some_and(|s| s == "Town")), Split::OnObtainGhostJoni => should_split(pds.incremented_dream_orbs(p, g) && g.get_scene_name(p).is_some_and(|s| s == "Cliffs_05")), // TODO: resolve possible confounding essence sources for Cloth, Vespa, and Revek + Split::OnObtainGhostCloth => should_split(pds.traitor_lord_been_dead_for_a_tick(p, g) + && pds.incremented_dream_orbs(p, g) + && g.get_scene_name(p).is_some_and(|s| s == "Fungus3_23")), // endregion: Essence, Trees, and Ghosts // region: Maps and Cornifer From 04d2afff0477a1ec80a368e56e4c2c85d5eb0f18 Mon Sep 17 00:00:00 2001 From: AlexKnauth Date: Sat, 17 Feb 2024 10:39:18 -0500 Subject: [PATCH 2/7] Split OnObtainGhostVespa --- src/hollow_knight_memory.rs | 12 +++++++++++- src/splits.rs | 7 +++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/hollow_knight_memory.rs b/src/hollow_knight_memory.rs index f859e67a..0a64fb39 100644 --- a/src/hollow_knight_memory.rs +++ b/src/hollow_knight_memory.rs @@ -155,7 +155,7 @@ pub static GODHOME_LORE_SCENES: &[&str] = &[ // -------------------------------------------------------- -// const VERSION_VEC_MAJOR: usize = 0; +const VERSION_VEC_MAJOR: usize = 0; const VERSION_VEC_MINOR: usize = 1; // const VERSION_VEC_BUILD: usize = 2; // const VERSION_VEC_REVISION: usize = 3; @@ -2185,6 +2185,11 @@ impl GameManagerFinder { self.player_data_pointers.visited_hive.deref(process, &self.module, &self.image).ok() } + fn hive_knight_doesnt_exist(&self, process: &Process) -> Option { + let v = self.get_version_vec(process)?; + Some((*v.get(VERSION_VEC_MAJOR)? <= 1) && (*v.get(VERSION_VEC_MINOR)? <= 2)) + } + pub fn killed_hive_knight(&self, process: &Process) -> Option { self.player_data_pointers.killed_hive_knight.deref(process, &self.module, &self.image).ok() } @@ -2848,6 +2853,11 @@ impl PlayerDataStore { self.been_dead_for_a_tick(prc, gmf, "killed_traitor_lord", &gmf.player_data_pointers.killed_traitor_lord) } + pub fn hive_knight_been_dead_for_a_tick(&mut self, prc: &Process, gmf: &GameManagerFinder) -> bool { + gmf.hive_knight_doesnt_exist(prc).is_some_and(|d| d) + || self.been_dead_for_a_tick(prc, gmf, "killed_hive_knight", &gmf.player_data_pointers.killed_hive_knight) + } + fn kills_on_entry(&mut self, prc: &Process, gmf: &GameManagerFinder, key: &'static str, pointer: &UnityPointer) -> Option { if gmf.is_game_state_non_continuous(prc) { self.map_i32.remove(key); diff --git a/src/splits.rs b/src/splits.rs index 5833f6b6..f65f3ea0 100644 --- a/src/splits.rs +++ b/src/splits.rs @@ -1582,6 +1582,10 @@ pub enum Split { /// /// Splits when obtaining the essence from Cloth OnObtainGhostCloth, + /// Dream Nail Vespa (Obtain) + /// + /// Splits when obtaining the essence from Hive Queen Vespa + OnObtainGhostVespa, // endregion: Essence, Trees, and Ghosts // region: Maps and Cornifer @@ -3864,6 +3868,9 @@ pub fn continuous_splits(s: &Split, p: &Process, g: &GameManagerFinder, pds: &mu Split::OnObtainGhostCloth => should_split(pds.traitor_lord_been_dead_for_a_tick(p, g) && pds.incremented_dream_orbs(p, g) && g.get_scene_name(p).is_some_and(|s| s == "Fungus3_23")), + Split::OnObtainGhostVespa => should_split(pds.hive_knight_been_dead_for_a_tick(p, g) + && pds.incremented_dream_orbs(p, g) + && g.get_scene_name(p).is_some_and(|s| s == "Hive_05")), // endregion: Essence, Trees, and Ghosts // region: Maps and Cornifer From 91d0ab845c24e7e2736bdb36c6c1374ae13d9637 Mon Sep 17 00:00:00 2001 From: AlexKnauth Date: Sat, 17 Feb 2024 11:32:50 -0500 Subject: [PATCH 3/7] Fix Cloth/Vespa evaluation order make sure both PlayerDataStore methods are evaluated before the `&&` so it doesn't short-circuit --- src/splits.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/splits.rs b/src/splits.rs index f65f3ea0..bd0dfb62 100644 --- a/src/splits.rs +++ b/src/splits.rs @@ -3865,12 +3865,18 @@ pub fn continuous_splits(s: &Split, p: &Process, g: &GameManagerFinder, pds: &mu Split::OnObtainGhostGravedigger => should_split(pds.incremented_dream_orbs(p, g) && g.get_scene_name(p).is_some_and(|s| s == "Town")), Split::OnObtainGhostJoni => should_split(pds.incremented_dream_orbs(p, g) && g.get_scene_name(p).is_some_and(|s| s == "Cliffs_05")), // TODO: resolve possible confounding essence sources for Cloth, Vespa, and Revek - Split::OnObtainGhostCloth => should_split(pds.traitor_lord_been_dead_for_a_tick(p, g) - && pds.incremented_dream_orbs(p, g) - && g.get_scene_name(p).is_some_and(|s| s == "Fungus3_23")), - Split::OnObtainGhostVespa => should_split(pds.hive_knight_been_dead_for_a_tick(p, g) - && pds.incremented_dream_orbs(p, g) - && g.get_scene_name(p).is_some_and(|s| s == "Hive_05")), + Split::OnObtainGhostCloth => { + let d = pds.traitor_lord_been_dead_for_a_tick(p, g); + let o = pds.incremented_dream_orbs(p, g); + // make sure both PlayerDataStore methods are evaluated before the `&&` so it doesn't short-circuit + should_split(d && o && g.get_scene_name(p).is_some_and(|s| s == "Fungus3_23")) + } + Split::OnObtainGhostVespa => { + let d = pds.hive_knight_been_dead_for_a_tick(p, g); + let o = pds.incremented_dream_orbs(p, g); + // make sure both PlayerDataStore methods are evaluated before the `&&` so it doesn't short-circuit + should_split(d && o && g.get_scene_name(p).is_some_and(|s| s == "Hive_05")) + } // endregion: Essence, Trees, and Ghosts // region: Maps and Cornifer From 3aa93e3293e46559296721996ea253af46bcbefe Mon Sep 17 00:00:00 2001 From: AlexKnauth Date: Sat, 17 Feb 2024 11:51:04 -0500 Subject: [PATCH 4/7] Fix MaskShard/VesselFrag evaluation order make sure PlayerDataStore methods are evaluated first in the `&&` so it doesn't short-circuit --- src/splits.rs | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/splits.rs b/src/splits.rs index bd0dfb62..6e42eb86 100644 --- a/src/splits.rs +++ b/src/splits.rs @@ -3579,18 +3579,18 @@ pub fn continuous_splits(s: &Split, p: &Process, g: &GameManagerFinder, pds: &mu Split::MaskFragment14 => should_split(g.heart_pieces(p).is_some_and(|s| s == 14 || (g.max_health_base(p).is_some_and(|h| h == 8) && s == 2))), Split::MaskFragment15 => should_split(g.heart_pieces(p).is_some_and(|s| s == 15 || (g.max_health_base(p).is_some_and(|h| h == 8) && s == 3))), Split::Mask4 => should_split(g.max_health_base(p).is_some_and(|h| h == 9)), - Split::MaskShardMawlek => should_split(g.get_scene_name(p).is_some_and(|s| s == "Crossroads_09") && pds.obtained_mask_shard(p, g)), - Split::MaskShardGrubfather => should_split(g.get_scene_name(p).is_some_and(|s| s == "Crossroads_38") && pds.obtained_mask_shard(p, g)), - Split::MaskShardBretta => should_split(g.get_scene_name(p).is_some_and(|s| s == "Room_Bretta") && pds.obtained_mask_shard(p, g)), - Split::MaskShardQueensStation => should_split(g.get_scene_name(p).is_some_and(|s| s == "Fungus2_01") && pds.obtained_mask_shard(p, g)), - Split::MaskShardEnragedGuardian => should_split(g.get_scene_name(p).is_some_and(|s| s == "Mines_32") && pds.obtained_mask_shard(p, g)), - Split::MaskShardSeer => should_split(g.get_scene_name(p).is_some_and(|s| s == "RestingGrounds_07") && pds.obtained_mask_shard(p, g)), - Split::MaskShardGoam => should_split(g.get_scene_name(p).is_some_and(|s| s == "Crossroads_13") && pds.obtained_mask_shard(p, g)), - Split::MaskShardStoneSanctuary => should_split(g.get_scene_name(p).is_some_and(|s| s == "Fungus1_36") && pds.obtained_mask_shard(p, g)), - Split::MaskShardWaterways => should_split(g.get_scene_name(p).is_some_and(|s| s == "Waterways_04b") && pds.obtained_mask_shard(p, g)), - Split::MaskShardFungalCore => should_split(g.get_scene_name(p).is_some_and(|s| s == "Fungus2_25") && pds.obtained_mask_shard(p, g)), - Split::MaskShardHive => should_split(g.get_scene_name(p).is_some_and(|s| s == "Hive_04") && pds.obtained_mask_shard(p, g)), - Split::MaskShardFlower => should_split(g.get_scene_name(p).is_some_and(|s| s == "Room_Mansion") && pds.obtained_mask_shard(p, g)), + Split::MaskShardMawlek => should_split(pds.obtained_mask_shard(p, g) && g.get_scene_name(p).is_some_and(|s| s == "Crossroads_09")), + Split::MaskShardGrubfather => should_split(pds.obtained_mask_shard(p, g) && g.get_scene_name(p).is_some_and(|s| s == "Crossroads_38")), + Split::MaskShardBretta => should_split(pds.obtained_mask_shard(p, g) && g.get_scene_name(p).is_some_and(|s| s == "Room_Bretta")), + Split::MaskShardQueensStation => should_split(pds.obtained_mask_shard(p, g) && g.get_scene_name(p).is_some_and(|s| s == "Fungus2_01")), + Split::MaskShardEnragedGuardian => should_split(pds.obtained_mask_shard(p, g) && g.get_scene_name(p).is_some_and(|s| s == "Mines_32")), + Split::MaskShardSeer => should_split(pds.obtained_mask_shard(p, g) && g.get_scene_name(p).is_some_and(|s| s == "RestingGrounds_07")), + Split::MaskShardGoam => should_split(pds.obtained_mask_shard(p, g) && g.get_scene_name(p).is_some_and(|s| s == "Crossroads_13")), + Split::MaskShardStoneSanctuary => should_split(pds.obtained_mask_shard(p, g) && g.get_scene_name(p).is_some_and(|s| s == "Fungus1_36")), + Split::MaskShardWaterways => should_split(pds.obtained_mask_shard(p, g) && g.get_scene_name(p).is_some_and(|s| s == "Waterways_04b")), + Split::MaskShardFungalCore => should_split(pds.obtained_mask_shard(p, g) && g.get_scene_name(p).is_some_and(|s| s == "Fungus2_25")), + Split::MaskShardHive => should_split(pds.obtained_mask_shard(p, g) && g.get_scene_name(p).is_some_and(|s| s == "Hive_04")), + Split::MaskShardFlower => should_split(pds.obtained_mask_shard(p, g) && g.get_scene_name(p).is_some_and(|s| s == "Room_Mansion")), // endregion: Masks and Mask Shards // region: Vessels and Vessel Fragments Split::OnObtainVesselFragment => should_split(pds.obtained_vessel_fragment(p, g)), @@ -3603,13 +3603,13 @@ pub fn continuous_splits(s: &Split, p: &Process, g: &GameManagerFinder, pds: &mu Split::VesselFragment7 => should_split(g.vessel_fragments(p).is_some_and(|f| f == 7 || (g.mp_reserve_max(p).is_some_and(|mp| mp == 66) && f == 1))), Split::VesselFragment8 => should_split(g.vessel_fragments(p).is_some_and(|f| f == 8 || (g.mp_reserve_max(p).is_some_and(|mp| mp == 66) && f == 2))), Split::Vessel3 => should_split(g.mp_reserve_max(p).is_some_and(|mp| mp == 99)), - Split::VesselFragGreenpath => should_split(g.get_scene_name(p).is_some_and(|s| s == "Fungus1_13") && pds.obtained_vessel_fragment(p, g)), - Split::VesselFragCrossroadsLift => should_split(g.get_scene_name(p).is_some_and(|s| s == "Crossroads_37") && pds.obtained_vessel_fragment(p, g)), - Split::VesselFragKingsStation => should_split(g.get_scene_name(p).is_some_and(|s| s == "Ruins2_09") && pds.obtained_vessel_fragment(p, g)), - Split::VesselFragGarpedes => should_split(g.get_scene_name(p).is_some_and(|s| s == "Deepnest_38") && pds.obtained_vessel_fragment(p, g)), - Split::VesselFragStagNest => should_split(g.get_scene_name(p).is_some_and(|s| s == "Cliffs_03") && pds.obtained_vessel_fragment(p, g)), - Split::VesselFragSeer => should_split(g.get_scene_name(p).is_some_and(|s| s == "RestingGrounds_07") && pds.obtained_vessel_fragment(p, g)), - Split::VesselFragFountain => should_split(g.get_scene_name(p).is_some_and(|s| s == "Abyss_04") && pds.obtained_vessel_fragment(p, g)), + Split::VesselFragGreenpath => should_split(pds.obtained_vessel_fragment(p, g) && g.get_scene_name(p).is_some_and(|s| s == "Fungus1_13")), + Split::VesselFragCrossroadsLift => should_split(pds.obtained_vessel_fragment(p, g) && g.get_scene_name(p).is_some_and(|s| s == "Crossroads_37")), + Split::VesselFragKingsStation => should_split(pds.obtained_vessel_fragment(p, g) && g.get_scene_name(p).is_some_and(|s| s == "Ruins2_09")), + Split::VesselFragGarpedes => should_split(pds.obtained_vessel_fragment(p, g) && g.get_scene_name(p).is_some_and(|s| s == "Deepnest_38")), + Split::VesselFragStagNest => should_split(pds.obtained_vessel_fragment(p, g) && g.get_scene_name(p).is_some_and(|s| s == "Cliffs_03")), + Split::VesselFragSeer => should_split(pds.obtained_vessel_fragment(p, g) && g.get_scene_name(p).is_some_and(|s| s == "RestingGrounds_07")), + Split::VesselFragFountain => should_split(pds.obtained_vessel_fragment(p, g) && g.get_scene_name(p).is_some_and(|s| s == "Abyss_04")), // endregion: Vessels and Vessel Fragments // region: Charm Notches Split::NotchShrumalOgres => should_split(g.notch_shroom_ogres(p).is_some_and(|n| n)), From eede473373c91e2895509f5fb82773c6c8facbf5 Mon Sep 17 00:00:00 2001 From: AlexKnauth Date: Sat, 17 Feb 2024 16:42:31 -0500 Subject: [PATCH 5/7] Cargo.toml: remove strip = true --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8dd45180..9d7d8c3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,8 @@ crate-type = ["cdylib"] lto = true panic = "abort" codegen-units = 1 -strip = true +# strip = true +# debug = true [profile.release.build-override] opt-level = 0 From 154a6fe96d05c0a42ef3958c5baeabc16f2e1ebf Mon Sep 17 00:00:00 2001 From: AlexKnauth Date: Sat, 17 Feb 2024 17:29:54 -0500 Subject: [PATCH 6/7] More heap, less stack --- src/hollow_knight_memory.rs | 8 ++++---- src/lib.rs | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/hollow_knight_memory.rs b/src/hollow_knight_memory.rs index 0a64fb39..073db3b1 100644 --- a/src/hollow_knight_memory.rs +++ b/src/hollow_knight_memory.rs @@ -989,8 +989,8 @@ pub struct BossSequenceDoorCompletion { // -------------------------------------------------------- pub struct GameManagerFinder { - string_list_offests: StringListOffsets, - module: mono::Module, + string_list_offests: Box, + module: Box, image: mono::Image, pointers: Box, player_data_pointers: Box, @@ -1001,8 +1001,8 @@ pub struct GameManagerFinder { impl GameManagerFinder { fn new(pointer_size: PointerSize, module: mono::Module, image: mono::Image) -> GameManagerFinder { GameManagerFinder { - string_list_offests: StringListOffsets::new(pointer_size), - module, + string_list_offests: Box::new(StringListOffsets::new(pointer_size)), + module: Box::new(module), image, pointers: Box::new(GameManagerPointers::new()), player_data_pointers: Box::new(PlayerDataPointers::new()), diff --git a/src/lib.rs b/src/lib.rs index 5142a571..594d1158 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,7 @@ async fn main() { asr::print_message("Hello, World!"); - let mut gui = SettingsGui::wait_load_merge_register().await; + let mut gui = Box::new(SettingsGui::wait_load_merge_register().await); let mut ticks_since_gui = 0; let mut splits = gui.get_splits(); @@ -40,17 +40,17 @@ async fn main() { let mut auto_reset = splits::auto_reset_safe(&splits); loop { - let process = wait_attach_hollow_knight(&mut gui).await; + let process = wait_attach_hollow_knight(&mut *gui).await; process .until_closes(async { // TODO: Load some initial information from the process. - let mut scene_store = SceneStore::new(); + let mut scene_store = Box::new(SceneStore::new()); let mut timing_method = gui.get_timing_method(); - let mut load_remover = TimingMethodLoadRemover::new(timing_method); + let mut load_remover = Box::new(TimingMethodLoadRemover::new(timing_method)); next_tick().await; - let game_manager_finder = GameManagerFinder::wait_attach(&process).await; - let mut player_data_store = PlayerDataStore::new(); + let game_manager_finder = Box::new(GameManagerFinder::wait_attach(&process).await); + let mut player_data_store = Box::new(PlayerDataStore::new()); #[cfg(debug_assertions)] asr::print_message(&format!("geo: {:?}", game_manager_finder.get_geo(&process))); @@ -88,7 +88,7 @@ async fn main() { let new_timing_method = gui.get_timing_method(); if new_timing_method != timing_method { timing_method = new_timing_method; - load_remover = TimingMethodLoadRemover::new(timing_method); + *load_remover = TimingMethodLoadRemover::new(timing_method); asr::print_message(&format!("timing_method: {:?}", timing_method)); } } From b604b10eba2e858f22e66c4198c21bf12c082c95 Mon Sep 17 00:00:00 2001 From: AlexKnauth Date: Sat, 17 Feb 2024 18:25:55 -0500 Subject: [PATCH 7/7] Remember modded --- src/hollow_knight_memory.rs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/hollow_knight_memory.rs b/src/hollow_knight_memory.rs index 073db3b1..c807d335 100644 --- a/src/hollow_knight_memory.rs +++ b/src/hollow_knight_memory.rs @@ -996,6 +996,7 @@ pub struct GameManagerFinder { player_data_pointers: Box, completion_pointers: Box, ui_state_offset: OnceCell, + modded: OnceCell, } impl GameManagerFinder { @@ -1008,6 +1009,7 @@ impl GameManagerFinder { player_data_pointers: Box::new(PlayerDataPointers::new()), completion_pointers: Box::new(CompletionPointers::new()), ui_state_offset: OnceCell::new(), + modded: OnceCell::new(), } } @@ -1077,12 +1079,22 @@ impl GameManagerFinder { let ui_state_offset = ui_manager_class.get_field_offset(process, &self.module, "uiState")?; self.ui_state_offset.get_or_init(|| ui_state_offset) }; - let ui = if let Ok(ui) = self.pointers.ui_state_vanilla.deref(process, &self.module, &self.image) { - ui - } else if let Ok(ui) = self.pointers.ui_state_modded.deref(process, &self.module, &self.image) { - ui + let ui = if let Some(&modded) = self.modded.get() { + if modded { + self.pointers.ui_state_modded.deref(process, &self.module, &self.image).ok()? + } else { + self.pointers.ui_state_vanilla.deref(process, &self.module, &self.image).ok()? + } } else { - return None; + if let Ok(ui) = self.pointers.ui_state_vanilla.deref(process, &self.module, &self.image) { + self.modded.set(false).ok(); + ui + } else if let Ok(ui) = self.pointers.ui_state_modded.deref(process, &self.module, &self.image) { + self.modded.set(true).ok(); + ui + } else { + return None; + } }; if ui_state_offset != &0x124 && ui >= 2 { Some(ui + 2)