diff --git a/crates/darksouls3/mapper-profile.toml b/crates/darksouls3/mapper-profile.toml index aa911fa5..80204f28 100644 --- a/crates/darksouls3/mapper-profile.toml +++ b/crates/darksouls3/mapper-profile.toml @@ -22,6 +22,14 @@ vftable = "player_ins_vmt" class = "NS_SPRJ::ReplayGhostIns" vftable = "replay_ghost_ins_vmt" +[[vmts]] +class = "NS_SPRJ::WorldInfo" +vftable = "world_info_vmt" + +[[vmts]] +class = "NS_SPRJ::WorldRes" +vftable = "world_res_vmt" + [[patterns]] pattern = "48 8b 05 $ { ' } 48 85 c0 74 ? 80 b8 ? ? ? ? ? 0f 95 c0 c3" captures = ["", "app_menu_new_menu_system_ptr"] diff --git a/crates/darksouls3/src/rva/bundle.rs b/crates/darksouls3/src/rva/bundle.rs index 920a4228..0464efe0 100644 --- a/crates/darksouls3/src/rva/bundle.rs +++ b/crates/darksouls3/src/rva/bundle.rs @@ -29,4 +29,6 @@ pub struct RvaBundle { pub register_task: u32, pub replay_ghost_ins_vmt: u32, pub sprj_menu_man_ptr: u32, + pub world_info_vmt: u32, + pub world_res_vmt: u32, } diff --git a/crates/darksouls3/src/rva/rva_data.rs b/crates/darksouls3/src/rva/rva_data.rs index f3c61766..7078ee24 100644 --- a/crates/darksouls3/src/rva/rva_data.rs +++ b/crates/darksouls3/src/rva/rva_data.rs @@ -28,4 +28,6 @@ pub const RVAS: RvaBundle = RvaBundle { register_task: 0xf0fa50, replay_ghost_ins_vmt: 0x2858aa8, sprj_menu_man_ptr: 0x4763258, + world_info_vmt: 0x27d7a50, + world_res_vmt: 0x27d7fb8, }; diff --git a/crates/darksouls3/src/sprj/event_flag.rs b/crates/darksouls3/src/sprj/event_flag.rs index d92c33ad..aaf3b54e 100644 --- a/crates/darksouls3/src/sprj/event_flag.rs +++ b/crates/darksouls3/src/sprj/event_flag.rs @@ -93,6 +93,12 @@ impl TryFrom for EventFlag { } } +impl From for u32 { + fn from(value: EventFlag) -> u32 { + value.0 + } +} + #[repr(C)] // Source of name: FD4Singleton error handling /// The singleton that manages the game's event flags. @@ -113,8 +119,8 @@ impl SprjEventFlagMan { let word_index = flag.word() as usize; self.get_event_zone_mut(flag) .and_then(|z| { - let old_word = z.words().get(word_index)?; - z.words_mut()[word_index] = if state { + let old_word = z.words.get(word_index)?; + z.words[word_index] = if state { old_word | (1 << flag.bit()) } else { old_word & !(1 << flag.bit()) @@ -128,7 +134,7 @@ impl SprjEventFlagMan { /// that don't exist. pub fn get_flag(&self, flag: EventFlag) -> bool { self.get_event_zone(flag) - .and_then(|z| z.words().get(flag.word() as usize)) + .and_then(|z| z.words.get(flag.word() as usize)) .map(|word| (word >> flag.bit()) & 1 == 1) .unwrap_or_default() } @@ -162,26 +168,22 @@ impl SprjEventFlagMan { /// Returns the index of the [EventBlock] that contains `flag`. pub fn get_event_block_index(&self, flag: EventFlag) -> Option { - if flag.area() == 0 && flag.group() == 0 { - Some(0) + Some(if flag.area() == 0 && flag.group() == 0 { + 0 // Safety: If the event man is being accessed safely, the field area // should be accessible as well. } else if let Ok(field_area) = unsafe { FieldArea::instance() } { - field_area - .world_res()? - .super_world_info - .area_info() - .iter() - .find(|ai| ai.area_number == flag.area()) - .and_then(|ai| { - ai.block_info().iter().find(|bi| { - bi.block_id.group() == flag.group() && bi.block_id.area() == flag.area() - }) - }) - .map(|bi| bi.world_block_index + 1) + let (_, block_infos) = field_area + .world_info_owner + .area_and_block_info() + .find(|(area_infos, _)| area_infos.area_number == flag.area())?; + let block_info = block_infos.iter().find(|bi| { + bi.block_id.group() == flag.group() && bi.block_id.area() == flag.area() + })?; + block_info.world_block_index + 1 } else { - Some((flag.global_block_index()? + 1).into()) - } + (flag.global_block_index()? + 1).into() + }) } } @@ -192,14 +194,22 @@ pub struct FD4VirtualMemoryFlag { /// Raw backing data for event flags. This is not guaranteed to be organized /// in any particular way; access events through the dedicated methods /// instead of directly through this buffer. - pub data: [OwnedPtr; 2], + /// + /// In a literal sense, this struct owns these pointers. However, to ensure + /// Rust's aliasing rules aren't violated, we only expose safe references to + /// them through [EventZone]. + pub data: [NonNull; 2], /// The length in bytes of the corresponding buffers in [data](Self.data). pub data_length: [usize; 2], /// The array of event blocks. The length is stored as a u64 immediately /// before the head of the array. - pub blocks: OwnedPtr, + /// + /// In a literal sense, this struct owns these pointers. However, to ensure + /// Rust's aliasing rules aren't violated, we only expose safe references to + /// them through [EventRegion]. + pub blocks: NonNull, /// The event worlds. Only one is active at a time. pub worlds: [EventWorld; 2], @@ -210,7 +220,7 @@ pub struct FD4VirtualMemoryFlag { /// The index of [self.current_world] in [self.worlds]. pub current_world_index: u32, - pub _unk224: u32, + _unk224: u32, /// Whether this class's data has been initialized. pub is_initialized: bool, @@ -247,7 +257,7 @@ pub struct EventWorld { #[repr(C)] pub struct EventRegion { /// A pointer to the list of event blocks that are part of this region. - pub blocks: Option>, + pub blocks: Option>, /// The length of the [blocks](Self.blocks) array. pub blocks_length: u32, @@ -259,6 +269,7 @@ impl EventRegion { /// Returns the list of blocks that belong to this region. pub fn blocks(&self) -> &[EventBlock] { self.blocks + .as_ref() .map(|blocks| unsafe { slice::from_raw_parts(blocks.as_ptr(), self.blocks_length as usize) }) @@ -268,7 +279,8 @@ impl EventRegion { /// Returns the mutable list of blocks that belong to this region. pub fn blocks_mut(&mut self) -> &mut [EventBlock] { self.blocks - .map(|mut blocks| unsafe { + .as_mut() + .map(|blocks| unsafe { slice::from_raw_parts_mut(blocks.as_mut(), self.blocks_length as usize) }) .unwrap_or_default() @@ -283,22 +295,10 @@ pub struct EventBlock { #[repr(C)] pub struct EventZone { - pub words: NonNull<[u32; 32]>, + pub words: OwnedPtr<[u32; 32]>, _unka0: u64, } -impl EventZone { - /// The words in this zone. - pub fn words(&self) -> &[u32] { - unsafe { self.words.as_ref() } - } - - /// The mutable words in this zone. - pub fn words_mut(&mut self) -> &mut [u32] { - unsafe { self.words.as_mut() } - } -} - #[cfg(test)] mod test { use super::*; diff --git a/crates/darksouls3/src/sprj/field_area.rs b/crates/darksouls3/src/sprj/field_area.rs index 52091b77..7009a741 100644 --- a/crates/darksouls3/src/sprj/field_area.rs +++ b/crates/darksouls3/src/sprj/field_area.rs @@ -1,16 +1,16 @@ use std::{borrow::Cow, ptr::NonNull}; -use super::WorldRes; +use super::WorldInfoOwner; use crate::rva; -use shared::{FromStatic, InstanceResult}; +use shared::*; #[repr(C)] pub struct FieldArea { _vftable: usize, - pub world_res: Option>, + pub world_info_owner: OwnedPtr, - _world_res_2: Option>, // Always the same as [world_res], apparently + _world_info_owner_2: NonNull, // Always the same as [world_info_owner], apparently _game_rend: u64, _unk20: u32, @@ -25,12 +25,6 @@ pub struct FieldArea { _unke8: [u8; 8], } -impl FieldArea { - pub fn world_res(&self) -> Option<&WorldRes> { - self.world_res.map(|ptr| unsafe { ptr.as_ref() }) - } -} - impl FromStatic for FieldArea { fn name() -> Cow<'static, str> { "FieldArea".into() diff --git a/crates/darksouls3/src/sprj/world_info.rs b/crates/darksouls3/src/sprj/world_info.rs index a2f4b43e..0a92c093 100644 --- a/crates/darksouls3/src/sprj/world_info.rs +++ b/crates/darksouls3/src/sprj/world_info.rs @@ -2,9 +2,11 @@ use std::{mem::MaybeUninit, ptr::NonNull, slice}; use bitfield::bitfield; -use shared::UnknownStruct; +use shared::*; #[repr(C)] +#[derive(Superclass)] +#[superclass(children(WorldRes))] /// Source of name: RTTI pub struct WorldInfo { _vftable: usize, @@ -35,7 +37,8 @@ pub struct WorldInfo { /// Use [Self::block_info] to access this safely. pub world_block_info_list_ptr: NonNull, - _unk28: u8, + /// This name comes from debug data, but the behavior isn't yet well-understood. + pub is_lock: bool, /// The pool of [WorldAreaInfo]s. Only the first /// [world_area_info_len](Self::world_area_info_len) are initialized. @@ -92,13 +95,42 @@ impl WorldInfo { ) } } + + /// The currently initialized area infos and their corresponding block + /// infos. + pub fn area_and_block_info(&self) -> impl Iterator { + self.area_info().iter().map(|area| { + // Safety: We know there isn't a mutable reference to the block + // info because it's owned by this WorldInfo to which we have an + // immutable reference. + (area, unsafe { + slice::from_raw_parts(area.block_info.as_ptr(), area.block_info_length as usize) + }) + }) + } + + /// The mutable currently initialized area infos and their corresponding + /// block infos. + pub fn area_and_block_info_mut( + &mut self, + ) -> impl Iterator { + self.area_info_mut().iter_mut().map(|area| { + // Safety: We know there aren't any other references to the block + // info because it's owned by this WorldInfo to which we have a + // mutable reference. + let blocks = unsafe { + slice::from_raw_parts_mut(area.block_info.as_mut(), area.block_info_length as usize) + }; + (area, blocks) + }) + } } #[repr(C)] /// Source of name: RTTI pub struct WorldAreaInfo { _vftable: usize, - _pad08: [u8; 3], + _unk08: [u8; 3], /// The area's numeric identifier. /// @@ -108,8 +140,11 @@ pub struct WorldAreaInfo { /// The [WorldInfo] instance that owns this area. pub owner: NonNull, - _unk18: u32, - _unk1c: u32, + /// The index of this area in [WorldInfo::world_area_info]. + pub world_area_index: u32, + + /// The index of this area's first block in [WorldInfo::world_block_info]. + pub world_block_index: u32, /// The length of the [block_info](Self::block_info) array. pub block_info_length: u32, @@ -117,21 +152,8 @@ pub struct WorldAreaInfo { /// The block infos for this [WorldAreaInfo]. pub block_info: NonNull, - _unk30: u8, -} - -impl WorldAreaInfo { - /// The block infos for this [WorldAreaInfo]. - pub fn block_info(&self) -> &[WorldBlockInfo] { - unsafe { slice::from_raw_parts(self.block_info.as_ptr(), self.block_info_length as usize) } - } - - /// The mutable block infos for this [WorldAreaInfo]. - pub fn block_info_mut(&mut self) -> &mut [WorldBlockInfo] { - unsafe { - slice::from_raw_parts_mut(self.block_info.as_mut(), self.block_info_length as usize) - } - } + /// This name comes from debug data, but the behavior isn't yet well-understood. + pub is_lock: bool, } bitfield! { @@ -159,7 +181,7 @@ pub struct WorldBlockInfo { pub owner: NonNull, /// The [WorldAreaInfo] that contains this block. - pub world_area_info: Option>, + pub world_area_info: NonNull, /// The index of this in [WorldInfo.world_block_info]. /// @@ -182,9 +204,10 @@ pub struct WorldBlockInfo { } #[repr(C)] +#[derive(Subclass)] /// Source of name: RTTI -pub struct WorldInfoOwner { - pub super_world_info: WorldInfo, +pub struct WorldRes { + pub world_info: WorldInfo, _unk8: u64, /// The number of defined entries in [world_area_res](Self::world_area_res). @@ -269,8 +292,8 @@ impl WorldRes { } } -// WorldRes doesn't add any additional fields. -pub type WorldRes = WorldInfoOwner; +// WorldInfoOwner doesn't add any additional fields. +pub type WorldInfoOwner = WorldRes; // Source of name: RTTI pub type WorldAreaRes = UnknownStruct<0x108>; @@ -287,6 +310,6 @@ mod test { assert_eq!(0x38, size_of::()); assert_eq!(0x70, size_of::()); assert_eq!(0x1298, size_of::()); - assert_eq!(0xae90, size_of::()); + assert_eq!(0xae90, size_of::()); } } diff --git a/crates/sekiro/mapper-profile.toml b/crates/sekiro/mapper-profile.toml index 6d05e3b2..743334a0 100644 --- a/crates/sekiro/mapper-profile.toml +++ b/crates/sekiro/mapper-profile.toml @@ -26,6 +26,14 @@ vftable = "param_res_cap_vmt" class = "NS_SPRJ::SoloParamRepositoryImp" vftable = "solo_param_repository_vmt" +[[vmts]] +class = "NS_SPRJ::WorldInfo" +vftable = "world_info_vmt" + +[[vmts]] +class = "NS_SPRJ::WorldRes" +vftable = "world_res_vmt" + [[patterns]] pattern = "48 83 ec 20 48 8d 05 ${ ' } 48 8b f9 48 89 01 48 8b ca 48 8b 02 ff 50 10" captures = ["", "fd4res_cap_vmt"] @@ -34,6 +42,10 @@ captures = ["", "fd4res_cap_vmt"] pattern = "c6 47 60 00 48 8d 05 $ { ' } 48 89 07 33 f6 48 89 77 68 48 89 77 70 48 89 77 78" captures = ["", "fd4param_res_cap_vmt"] +[[patterns]] +pattern = "0f 29 74 24 30 40 80 fe 01 0f ? ? ? ? ? 48 8b 05 $ { ' }" +captures = ["", "field_area_ptr"] + [[patterns]] pattern = "c7 44 24 20 00 00 00 00 48 8b 05 $ { ' } 48 8b 58 08 8b 02" captures = ["", "game_data_man_ptr"] diff --git a/crates/sekiro/src/rva/bundle.rs b/crates/sekiro/src/rva/bundle.rs index b09f22ce..ca9a2c7e 100644 --- a/crates/sekiro/src/rva/bundle.rs +++ b/crates/sekiro/src/rva/bundle.rs @@ -16,6 +16,7 @@ pub struct RvaBundle { pub dlpseudo_async_input_stream_vmt: u32, pub fd4param_res_cap_vmt: u32, pub fd4res_cap_vmt: u32, + pub field_area_ptr: u32, pub game_data_man_ptr: u32, pub global_hinstance: u32, pub lua_event_man_remove_item: u32, @@ -27,4 +28,6 @@ pub struct RvaBundle { pub scene_obj_proxy_vmt: u32, pub solo_param_repository_vmt: u32, pub sprj_menu_man_ptr: u32, + pub world_info_vmt: u32, + pub world_res_vmt: u32, } diff --git a/crates/sekiro/src/rva/rva_data.rs b/crates/sekiro/src/rva/rva_data.rs index f95dd2ea..3b2b0ad0 100644 --- a/crates/sekiro/src/rva/rva_data.rs +++ b/crates/sekiro/src/rva/rva_data.rs @@ -15,6 +15,7 @@ pub const RVAS: RvaBundle = RvaBundle { dlpseudo_async_input_stream_vmt: 0x2de30d8, fd4param_res_cap_vmt: 0x3280eb0, fd4res_cap_vmt: 0x3284610, + field_area_ptr: 0x3d5c0a0, game_data_man_ptr: 0x3d5aac0, global_hinstance: 0x3e9edb8, lua_event_man_remove_item: 0x67b230, @@ -26,4 +27,6 @@ pub const RVAS: RvaBundle = RvaBundle { scene_obj_proxy_vmt: 0x2aa7088, solo_param_repository_vmt: 0x2b9ab48, sprj_menu_man_ptr: 0x3d67408, + world_info_vmt: 0x29dcaa0, + world_res_vmt: 0x29dd190, }; diff --git a/crates/sekiro/src/sprj.rs b/crates/sekiro/src/sprj.rs index cc0c6ef3..09e40b6b 100644 --- a/crates/sekiro/src/sprj.rs +++ b/crates/sekiro/src/sprj.rs @@ -1,3 +1,5 @@ +mod event_flag; +mod field_area; mod game_data_man; mod item; mod item_id; @@ -7,7 +9,10 @@ mod scaleform_value; mod solo_param_repository; mod task; mod window; +mod world_info; +pub use event_flag::*; +pub use field_area::*; pub use game_data_man::*; pub use item::*; pub use item_id::*; @@ -17,3 +22,4 @@ pub use scaleform_value::*; pub use solo_param_repository::*; pub use task::*; pub use window::*; +pub use world_info::*; diff --git a/crates/sekiro/src/sprj/event_flag.rs b/crates/sekiro/src/sprj/event_flag.rs new file mode 100644 index 00000000..a7072243 --- /dev/null +++ b/crates/sekiro/src/sprj/event_flag.rs @@ -0,0 +1,310 @@ +use std::{ptr::NonNull, slice}; + +use thiserror::Error; + +use super::FieldArea; +use crate::Vector; +use shared::*; + +#[derive(Debug, Error, PartialEq, Eq)] +pub enum EventFlagError { + /// The event flag is above the maximum value. + #[error("Event flag {0} is higher than the maximum value 99999999")] + TooHigh(u32), + + /// The event flag's area number is above the maximum value. + #[error("Event flag area {0} must be less than 90")] + InvalidArea(u8), +} + +/// A handle pointing to a one-bit event flag in the game's event storage. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct EventFlag(u32); + +/// A valid event flag +impl EventFlag { + /// The index of this event in [EventWorld.regions]. Always in `0..10`. + pub fn region(&self) -> u8 { + ((self.0 / 10000000) % 10) as u8 + } + + /// The `WorldAreaInfo.area_number` for the area that this selects. Always + /// less than 90. + pub fn area(&self) -> u8 { + ((self.0 / 100000) % 100) as u8 + } + + /// The `BlockInfo.group` for the area that this selects. Always in `0..10`. + pub fn group(&self) -> u8 { + ((self.0 / 10000) % 10) as u8 + } + + /// The index of this event in [EventBlock.zones]. Always in `0..10`. + pub fn zone(&self) -> u8 { + ((self.0 / 1000) % 10) as u8 + } + + /// The index of this event in [EventZone.words]. Always in `0..32`. + pub fn word(&self) -> u8 { + ((self.0 % 1000) / 32) as u8 + } + + /// The index of the bit that represents this event's value in its word. + /// Always in `0..32`. + /// + /// This index is big-endian, in the sense that a lower index refers to a + /// less-significant bit. This ensures that `1 << flag.bit()` will return + /// the mask for the bit in question. + pub fn bit(&self) -> u8 { + // Flag IDs themselves are little-endian, so we have to subtract them + // from 31 to make them usable in the more ergonomic big-endian way. + 31 - ((self.0 % 1000) % 32) as u8 + } +} + +impl TryFrom for EventFlag { + type Error = EventFlagError; + + fn try_from(value: u32) -> Result { + if value > 99999999 { + return Err(EventFlagError::TooHigh(value)); + } + + let wrapped = EventFlag(value); + if wrapped.area() >= 90 { + Err(EventFlagError::InvalidArea(wrapped.area())) + } else { + Ok(wrapped) + } + } +} + +impl From for u32 { + fn from(value: EventFlag) -> u32 { + value.0 + } +} + +#[repr(C)] +// Source of name: FD4Singleton error handling +/// The singleton that manages the game's event flags. +#[shared::singleton("SprjEventFlagMan")] +pub struct SprjEventFlagMan { + /// A struct that owns the actual flag data. + pub flags: FD4VirtualMemoryFlag, + + pub event_maker: EventMakerEx, + + _unk2a8: bool, +} + +impl SprjEventFlagMan { + /// Sets the state of the given [EventFlag]. Returns whether the flag was + /// set successfully. + pub fn set_flag(&mut self, flag: EventFlag, state: bool) -> bool { + let word_index = flag.word() as usize; + self.get_event_zone_mut(flag) + .and_then(|z| { + let old_word = z.words.get(word_index)?; + z.words[word_index] = if state { + old_word | (1 << flag.bit()) + } else { + old_word & !(1 << flag.bit()) + }; + Some(true) + }) + .unwrap_or_default() + } + + /// Retrieves the state of the given [EventFlag]. Returns `false` for flags + /// that don't exist. + pub fn get_flag(&self, flag: EventFlag) -> bool { + self.get_event_zone(flag) + .and_then(|z| z.words.get(flag.word() as usize)) + .map(|word| (word >> flag.bit()) & 1 == 1) + .unwrap_or_default() + } + + /// Returns the [EventZone] that contains the data for the given + /// [EventFlag]. + pub fn get_event_zone(&self, flag: EventFlag) -> Option<&EventZone> { + self.flags + .current_world() + .regions + .get(flag.region() as usize)? + .blocks() + .get(self.get_event_block_index(flag)? as usize)? + .zones + .get(flag.zone() as usize) + } + + /// Returns the mutable [EventZone] that contains the data for the given + /// [EventFlag]. + pub fn get_event_zone_mut(&mut self, flag: EventFlag) -> Option<&mut EventZone> { + let block_index = self.get_event_block_index(flag)?; + self.flags + .current_world_mut() + .regions + .get_mut(flag.region() as usize)? + .blocks_mut() + .get_mut(block_index as usize)? + .zones + .get_mut(flag.zone() as usize) + } + + /// Returns the index of the [EventBlock] that contains `flag`. + fn get_event_block_index(&self, flag: EventFlag) -> Option { + let (_, block_infos) = unsafe { FieldArea::instance() } + .ok()? + .world_info_owner + .area_and_block_info() + .find(|(area_infos, _)| area_infos.area_number == flag.area())?; + let block_info = block_infos + .iter() + .find(|bi| bi.block_id.group() == flag.group() && bi.block_id.area() == flag.area())?; + Some(block_info.world_block_index + 1) + } +} + +#[repr(C)] +// Source of name: Elden Ring RTTI +/// The container for the actual event data. +pub struct FD4VirtualMemoryFlag { + /// Raw backing data for event flags. This is not guaranteed to be organized + /// in any particular way; access events through the dedicated methods + /// instead of directly through this buffer. + /// + /// In a literal sense, this struct owns these pointers. However, to ensure + /// Rust's aliasing rules aren't violated, we only expose safe references to + /// them through [EventZone]. + pub data: [NonNull; 2], + + /// The length in bytes of the corresponding buffers in [data](Self.data). + pub data_length: [usize; 2], + + /// The array of event blocks. The length is stored as a u64 immediately + /// before the head of the array. + /// + /// In a literal sense, this struct owns these pointers. However, to ensure + /// Rust's aliasing rules aren't violated, we only expose safe references to + /// them through [EventRegion]. + pub blocks: NonNull, + + /// The event worlds. Only one is active at a time. + pub worlds: [EventWorld; 2], + + /// The [EventWorld] that's currently active. + pub current_world: NonNull, + + /// The index of [self.current_world] in [self.worlds]. + pub current_world_index: u32, + + _unk224: u32, + + /// Whether this class's data has been initialized. + pub is_initialized: bool, + + _unk229: [u8; 0x3], + _unk22c: [UnknownStruct<0x14>; 4], +} + +impl FD4VirtualMemoryFlag { + /// Returns the currently-active [EventWorld]. + pub fn current_world(&self) -> &EventWorld { + unsafe { self.current_world.as_ref() } + } + + /// Returns the mutable currently-active [EventWorld]. + pub fn current_world_mut(&mut self) -> &mut EventWorld { + unsafe { self.current_world.as_mut() } + } +} + +#[repr(C)] +pub struct EventWorld { + pub regions: [EventRegion; 10], + + /// The length (in bytes) of the [FD4VirtualMemoryFlag] corresponding to + /// this world. + pub data_length: usize, +} + +#[repr(C)] +// Source of name: RTTI +pub struct EventMakerEx { + _unk00: Vector, + _unk20: u64, +} + +#[repr(C)] +pub struct EventRegion { + /// A pointer to the list of event blocks that are part of this region. + pub blocks: Option>, + + /// The length of the [blocks](Self.blocks) array. + pub blocks_length: u32, + + _unk10: u64, +} + +impl EventRegion { + /// Returns the list of blocks that belong to this region. + pub fn blocks(&self) -> &[EventBlock] { + self.blocks + .as_ref() + .map(|blocks| unsafe { + slice::from_raw_parts(blocks.as_ptr(), self.blocks_length as usize) + }) + .unwrap_or_default() + } + + /// Returns the mutable list of blocks that belong to this region. + pub fn blocks_mut(&mut self) -> &mut [EventBlock] { + self.blocks + .as_mut() + .map(|blocks| unsafe { + slice::from_raw_parts_mut(blocks.as_mut(), self.blocks_length as usize) + }) + .unwrap_or_default() + } +} + +#[repr(C)] +pub struct EventBlock { + pub zones: [EventZone; 10], + _unka0: u64, +} + +#[repr(C)] +pub struct EventZone { + pub words: OwnedPtr<[u32; 32]>, + _unka0: u64, +} + +impl EventZone { + /// The words in this zone. + pub fn words(&self) -> &[u32] { + self.words.as_ref() + } + + /// The mutable words in this zone. + pub fn words_mut(&mut self) -> &mut [u32] { + self.words.as_mut() + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn proper_sizes() { + assert_eq!(0xf8, size_of::()); + assert_eq!(0x18, size_of::()); + assert_eq!(0xa8, size_of::()); + assert_eq!(0x10, size_of::()); + assert_eq!(0x28, size_of::()); + assert_eq!(0x280, size_of::()); + assert_eq!(0x2b0, size_of::()); + } +} diff --git a/crates/sekiro/src/sprj/field_area.rs b/crates/sekiro/src/sprj/field_area.rs new file mode 100644 index 00000000..949a7ac3 --- /dev/null +++ b/crates/sekiro/src/sprj/field_area.rs @@ -0,0 +1,64 @@ +use std::{borrow::Cow, ptr::NonNull}; + +use super::WorldRes; +use crate::rva; +use shared::*; + +#[repr(C)] +pub struct FieldArea { + _vftable: usize, + _unk08: usize, + + pub world_info_owner: OwnedPtr, + + _world_info_owner_2: Option>, // Always the same as [world_res], apparently + + _game_rend: UnknownPtr, + _current_world_block_info_index: u32, + _chr_cam: UnknownPtr, + _unk38: u32, + _unk40: u64, + _unk48: u64, + _unk50: u32, + _unk58: u64, + _unk60: u64, + _hit_ins: UnknownPtr, + _unk70: u64, + _backread: OwnedPtr>, + _unk80: u64, + _unk88: u64, + _unk90: u64, + _unk98: u64, + _unka0: u32, + _unka8: UnknownStruct<0x18>, // tree + _unkc0: u16, + + pub debug_measurement_display: bool, + pub debug_major_reset: bool, + + _unkc4: [u8; 0xc], + _unkd0: u32, + _unkd4: u32, + _unkd8: u32, + _unkdc: u32, +} + +impl FromStatic for FieldArea { + fn name() -> Cow<'static, str> { + "FieldArea".into() + } + + unsafe fn instance() -> InstanceResult<&'static mut Self> { + unsafe { shared::load_static_indirect(rva::get().field_area_ptr) } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn proper_sizes() { + assert_eq!(0xe0, size_of::()); + } +} diff --git a/crates/sekiro/src/sprj/world_info.rs b/crates/sekiro/src/sprj/world_info.rs new file mode 100644 index 00000000..6e86932f --- /dev/null +++ b/crates/sekiro/src/sprj/world_info.rs @@ -0,0 +1,336 @@ +use std::{mem::MaybeUninit, ptr::NonNull, slice}; + +use bitfield::bitfield; + +use shared::*; + +#[repr(C)] +#[derive(Superclass)] +#[superclass(children(WorldRes))] +/// Source of name: RTTI +pub struct WorldInfo { + _vftable: usize, + + /// The number of defined entries in + /// [world_area_info](Self::world_area_info). + /// + /// Use [Self::area_info] to access this safely. + pub world_area_info_len: u32, + + /// A pointer to the beginning of [world_area_info](Self::world_area_info). + /// + /// Use [Self::area_info] to access this safely. + pub world_area_info_list_ptr: NonNull, + + /// The number of defined entries in + /// [world_block_info](Self::world_block_info). + /// + /// Use [Self::block_info] to access this safely. + pub world_block_info_len: u32, + + /// A pointer to the beginning of + /// [world_block_info](Self::world_block_info). + /// + /// These are always an initial sublist of + /// [world_block_info](Self::world_block_info). + /// + /// Use [Self::block_info] to access this safely. + pub world_block_info_list_ptr: NonNull, + + /// This name comes from debug data, but the behavior isn't yet well-understood. + pub is_lock: bool, + + /// The pool of [WorldAreaInfo]s. Only the first + /// [world_area_info_len](Self::world_area_info_len) are initialized. + /// + /// Use [Self::area_info] to access this safely. + pub world_area_info: [MaybeUninit; 0x14], + + /// The pool of [WorldBlockInfo]s. Only the first + /// [world_block_info_len](Self::world_block_info_len) are initialized. + /// + /// Use [Self::block_info] to access this safely. + pub world_block_info: [MaybeUninit; 0x20], +} + +impl WorldInfo { + /// The currently initialized area infos. + pub fn area_info(&self) -> &[WorldAreaInfo] { + unsafe { + slice::from_raw_parts( + self.world_area_info_list_ptr.as_ptr(), + self.world_area_info_len as usize, + ) + } + } + + /// The mutable currently initialized area infos. + pub fn area_info_mut(&mut self) -> &mut [WorldAreaInfo] { + unsafe { + slice::from_raw_parts_mut( + self.world_area_info_list_ptr.as_mut(), + self.world_area_info_len as usize, + ) + } + } + + /// The currently initialized block infos. + pub fn block_info(&self) -> &[WorldBlockInfo] { + unsafe { + slice::from_raw_parts( + self.world_block_info_list_ptr.as_ptr(), + self.world_block_info_len as usize, + ) + } + } + + /// The mutable currently initialized block infos. + pub fn block_info_mut(&mut self) -> &mut [WorldBlockInfo] { + unsafe { + slice::from_raw_parts_mut( + self.world_block_info_list_ptr.as_mut(), + self.world_block_info_len as usize, + ) + } + } + + /// The currently initialized area infos and their corresponding block + /// infos. + pub fn area_and_block_info(&self) -> impl Iterator { + self.area_info().iter().map(|area| { + // Safety: We know there isn't a mutable reference to the block + // info because it's owned by this WorldInfo to which we have an + // immutable reference. + (area, unsafe { + slice::from_raw_parts(area.block_info.as_ptr(), area.block_info_length as usize) + }) + }) + } + + /// The mutable currently initialized area infos and their corresponding + /// block infos. + pub fn area_and_block_info_mut( + &mut self, + ) -> impl Iterator { + self.area_info_mut().iter_mut().map(|area| { + // Safety: We know there aren't any other references to the block + // info because it's owned by this WorldInfo to which we have a + // mutable reference. + let blocks = unsafe { + slice::from_raw_parts_mut(area.block_info.as_mut(), area.block_info_length as usize) + }; + (area, blocks) + }) + } +} + +#[repr(C)] +#[derive(Subclass)] +/// Source of name: RTTI +pub struct WorldRes { + pub world_info: WorldInfo, + + /// The number of defined entries in [world_area_res](Self::world_area_res). + /// + /// Use [Self::area_res] to access this safely. + pub world_area_res_len: u32, + + /// A pointer to the beginning of [world_area_res](Self::world_area_res). + /// + /// Use [Self::area_res] to access this safely. + pub world_area_res_list_ptr: NonNull, + + /// This name comes from debug data, but the behavior isn't yet well-understood. + pub remaining_time_to_activation: u32, + + /// This name comes from debug data, but the behavior isn't yet well-understood. + pub time_between_activations: u32, + + /// The number of defined entries in + /// [world_block_res](Self::world_block_res). + /// + /// Use [Self::block_res] to access this safely. + pub world_block_res_len: u32, + + /// A pointer to the beginning of [world_block_res](Self::world_block_res). + /// + /// Use [Self::block_res] to access this safely. + pub world_block_res_list_ptr: NonNull, + + _unk1ab8: u32, + _unk1abc: u8, + _unk1ac0: u64, + _unk1ac8: u16, + _unk1aca: u8, + _unk1acb: u8, + _unk1acc: u8, + _unk1ad0: u32, + _unk1ad4: [u8; 0xc], + + /// The pool of [WorldAreaRes]es. Only the first + /// [world_area_res_len](Self::world_area_res_len) are initialized. + /// + /// Use [Self::area_res] to access this safely. + pub world_area_res: [MaybeUninit; 0x14], + + /// The pool of [WorldBlockRes]es. Only the first + /// [world_block_res_len](Self::world_block_res_len) are initialized. + /// + /// Use [Self::block_res] to access this safely. + pub world_block_res: [MaybeUninit; 0x20], +} + +impl WorldRes { + pub fn area_res(&self) -> &[WorldAreaRes] { + unsafe { + slice::from_raw_parts( + self.world_area_res_list_ptr.as_ptr(), + self.world_area_res_len as usize, + ) + } + } + + pub fn area_res_mut(&mut self) -> &mut [WorldAreaRes] { + unsafe { + slice::from_raw_parts_mut( + self.world_area_res_list_ptr.as_mut(), + self.world_area_res_len as usize, + ) + } + } + + pub fn block_res(&self) -> &[WorldBlockRes] { + unsafe { + slice::from_raw_parts( + self.world_block_res_list_ptr.as_ptr(), + self.world_block_res_len as usize, + ) + } + } + + pub fn block_res_mut(&mut self) -> &mut [WorldBlockRes] { + unsafe { + slice::from_raw_parts_mut( + self.world_block_res_list_ptr.as_mut(), + self.world_block_res_len as usize, + ) + } + } +} + +// Source of name: RTTI +pub type WorldAreaRes = UnknownStruct<0x108>; + +// Source of name: RTTI +pub type WorldBlockRes = UnknownStruct<0x458>; + +#[repr(C)] +/// Source of name: RTTI +pub struct WorldAreaInfo { + _vftable: usize, + _unk08: u16, + _unk0a: u8, + + /// The area's numeric identifier. + /// + /// This is corresponds to the `XX00000` digits in an event flag. + pub area_number: u8, + + /// The [WorldInfo] instance that owns this area. + pub owner: NonNull, + + /// The index of this area in [WorldInfo::world_area_info]. + pub world_area_index: u32, + + /// The index of this area's first block in [WorldInfo::world_block_info]. + pub world_block_index: u32, + + /// The length of the [block_info](Self::block_info) array. + pub block_info_length: u32, + + /// The block infos for this [WorldAreaInfo]. + pub block_info: NonNull, + + /// This name comes from debug data, but the behavior isn't yet well-understood. + pub is_lock: bool, +} + +bitfield! { + /// An ID that contains information about the block's event locations. + #[derive(Copy, Clone, PartialEq, Eq, Hash)] + pub struct BlockId(u32); + impl Debug; + + /// The event group that this block belongs to. + pub u8, group, _: 23, 16; + + /// The area number that this block belongs to. + pub u8, area, _: 31, 24; +} + +#[repr(C)] +/// Source of name: RTTI +pub struct WorldBlockInfo { + _vftable: usize, + + /// The block ID that indicates which event flags this block refers to. + pub block_id: BlockId, + + /// The [WorldInfo] instance that owns this area. + pub owner: NonNull, + + /// The [WorldAreaInfo] that contains this block. + pub world_area_info: NonNull, + + /// The index of this in [WorldInfo.world_block_info]. + pub world_block_index: u32, + + _unk24: u8, + _unk25: u8, + _unk26: u8, + _unk27: u8, + + /// The index of the corresponding area in [WorldInfo::world_area_info]. + pub area_block_index: u32, + + _unk30: u64, + _unk38: u64, + _unk40: u64, + _unk48: [u64; 0x6], + _unk78: u64, + _unk80: u64, + _unk88: u64, + + /// This name comes from debug data, but the behavior isn't yet well-understood. + pub is_lock: bool, + + _unk91: [u8; 3], + + /// This name comes from debug data, but the behavior isn't yet well-understood. + pub ceremony_id: u8, + + /// This name comes from debug data, but the behavior isn't yet well-understood. + pub current_time_zone: i32, + + /// Seems to be unused. + _ceremony_id: i32, + + /// Seems to be unused. + pub debug_time_to_id_change: i32, + + _unka4: i32, + _unka8: f32, +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn proper_sizes() { + assert_eq!(0x38, size_of::()); + assert_eq!(0xb0, size_of::()); + assert_eq!(0x1a90, size_of::()); + assert_eq!(0xba80, size_of::()); + } +} diff --git a/crates/shared/src/static.rs b/crates/shared/src/static.rs index a3873b24..60316a37 100644 --- a/crates/shared/src/static.rs +++ b/crates/shared/src/static.rs @@ -36,10 +36,7 @@ pub trait FromStatic { /// The name of this object. Used for debugging purposes. fn name() -> Cow<'static, str>; - /// Looks up the single global instance of this object. - /// - /// Implementations may cache information about the object's location to - /// make this more efficient in future calls. + /// Looks up the single global instance of this object as a reference. /// /// ## Safety /// diff --git a/tools/debug-darksouls3/src/display/field_area.rs b/tools/debug-darksouls3/src/display/field_area.rs index 9b857c7d..683fae19 100644 --- a/tools/debug-darksouls3/src/display/field_area.rs +++ b/tools/debug-darksouls3/src/display/field_area.rs @@ -3,41 +3,39 @@ use hudhook::imgui::Ui; use darksouls3::sprj::*; use debug::UiExt; -use super::{DebugDisplay, DisplayUiExt}; +use super::DebugDisplay; impl DebugDisplay for FieldArea { fn render_debug(&self, ui: &Ui) { - if let Some(world_res) = self.world_res() { - world_res.super_world_info.render_debug(ui); - } else { - ui.text("World res: None"); - } + self.world_info_owner.render_debug(ui); } } -impl DebugDisplay for WorldInfo { +impl DebugDisplay for WorldInfoOwner { fn render_debug(&self, ui: &Ui) { ui.list( format!("Area infos: {} ##{:p}", self.area_info().len(), self), - self.area_info(), - |ui, _, area_info| { - ui.nested( + self.area_and_block_info(), + |ui, _, (area_info, block_infos)| { + ui.header( format!("Area {} ##{:p}", area_info.area_number, area_info), - area_info, + || { + for block_info in block_infos { + block_info.render_debug(ui); + } + }, ); }, ); } } -impl DebugDisplay for WorldAreaInfo { +impl DebugDisplay for WorldBlockInfo { fn render_debug(&self, ui: &Ui) { - for block in self.block_info() { - ui.text(format!( - "Block {}: event index {}", - block.block_id.group(), - block.world_block_index - )); - } + ui.text(format!( + "Block {}: event index {}", + self.block_id.group(), + self.world_block_index + )); } } diff --git a/tools/debug-eldenring/src/lib.rs b/tools/debug-eldenring/src/lib.rs index 027a4247..fa961e37 100644 --- a/tools/debug-eldenring/src/lib.rs +++ b/tools/debug-eldenring/src/lib.rs @@ -2,9 +2,9 @@ use std::ffi::c_void; use std::sync::Once; use std::time::Duration; -use hudhook::imgui::{Condition, Context, Ui, sys as imgui_sys}; +use hudhook::imgui::{Condition, Context, Io, Ui, sys as imgui_sys}; use hudhook::windows::Win32::Foundation::HINSTANCE; -use hudhook::{ImguiRenderLoop, eject, hooks::dx12::ImguiDx12Hooks}; +use hudhook::{ImguiRenderLoop, MessageFilter, eject, hooks::dx12::ImguiDx12Hooks}; use pelite::pe64::Pe; use rva::RVA_GLOBAL_FIELD_AREA; @@ -118,6 +118,10 @@ impl ImguiRenderLoop for EldenRingDebugGui { render_live_reload(self, ui); } } + + fn message_filter(&self, _io: &Io) -> MessageFilter { + MessageFilter::InputAll + } } #[libhotpatch::hotpatch]