1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
use std::{ptr::NonNull, slice};
use thiserror::Error;
use super::FieldArea;
use crate::stl::DLVector;
use shared::{FromStatic, OwnedPtr, UnknownStruct};
#[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
}
/// Gets the block index to use when there's no global FieldArea currently
/// available.
fn global_block_index(&self) -> Option<u8> {
Some(match (self.area(), self.group()) {
(12, 1) => 0,
(20, 1) => 1,
(21, 0) => 3,
(29, 0) => 4,
(29, 1) => 5,
(29, 2) => 6,
_ => return None,
})
}
}
impl TryFrom<u32> for EventFlag {
type Error = EventFlagError;
fn try_from(value: u32) -> Result<Self, Self::Error> {
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<EventFlag> 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,
_unk230: [u8; 0x20],
pub event_maker: EventMakerEx,
}
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`.
pub fn get_event_block_index(&self, flag: EventFlag) -> Option<u32> {
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() } {
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 {
(flag.global_block_index()? + 1).into()
})
}
}
#[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<u32>; 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<EventBlock>,
/// The event worlds. Only one is active at a time.
pub worlds: [EventWorld; 2],
/// The [EventWorld] that's currently active.
pub current_world: NonNull<EventWorld>,
/// 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,
}
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)]
// Source of name: RTTI
pub struct EventMakerEx {
_unk00: DLVector<UnknownStruct<0x30>>,
_unk20: u64, // debug related?
}
#[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)]
pub struct EventRegion {
/// A pointer to the list of event blocks that are part of this region.
pub blocks: Option<OwnedPtr<EventBlock>>,
/// The length of the [blocks](Self.blocks) array.
pub blocks_length: u32,
_blocks_length_times_1280: 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,
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn proper_sizes() {
assert_eq!(0xf8, size_of::<EventWorld>());
assert_eq!(0x18, size_of::<EventRegion>());
assert_eq!(0xa8, size_of::<EventBlock>());
assert_eq!(0x10, size_of::<EventZone>());
assert_eq!(0x28, size_of::<EventMakerEx>());
assert_eq!(0x230, size_of::<FD4VirtualMemoryFlag>());
assert_eq!(0x278, size_of::<SprjEventFlagMan>());
}
}