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
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
use std::{fmt::Display, ptr::NonNull};
use crate::{CSFixedList, dltx::DLString};
use shared::{F32Vector4, OwnedPtr};
use super::{CSMenuManImp, FieldInsHandle};
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum CSFeManHudState {
/// Fully hide hp, fp, stamina
HideAll = 0,
/// Fully show hp, fp, stamina
/// Used when esc menu is open
ShowAll = 1,
/// Special state used when popup menus are open
PopupMenu = 2,
/// Default state, used in normal gameplay
Default = 3,
}
#[repr(C)]
/// Source of name: RTTI
#[shared::singleton("CSFeMan")]
pub struct CSFeManImp {
vftable: usize,
unk8: usize,
game_rend: usize,
pub menu_man: NonNull<CSMenuManImp>,
/// Object, containing all scaleform data for FrontEnd (FE) scene
pub front_end_view: OwnedPtr<FrontEndView>,
fade_screen: usize,
fe_system_announce_view: usize,
fe_summon_message_view: usize,
clock_view: usize,
unk48: [u8; 40],
unk70: usize,
/// Toggle, to enable/disable the HUD
pub hud_state: CSFeManHudState,
unk79: [u8; 7],
/// Used for holding intermediate data FrontEndView
/// read from menu window jobs
pub frontend_values: FrontEndViewValues,
auto_hide_ctrl_ptr: usize,
unk4e68: [u8; 8],
auto_hide_ctrl: [u8; 0xb04],
unk5974: [u8; 4],
menu_resist_gauge: [u8; 0x28],
unk59a0: [u8; 4],
/// Ring buffer for proc status message ids
pub proc_status_messages: [i32; 6],
/// Index of the last proc status message
/// Wraps around when it reaches 6
pub proc_status_messages_read_index: u32,
/// Index of the next proc status message slot
/// Wraps around when it reaches 6
pub proc_status_messages_write_index: u32,
unk59c4: [u8; 12],
unk59d0: F32Vector4,
unk59e0: u32,
unk59e4: [u8; 12],
/// Data used for the enemy character tags
/// Will be copied to the enemy character tags in FrontEndView
/// and then passed to the scaleform
pub enemy_chr_tag_displays: [ChrEnemyTagEntry; 8],
/// Data used for the boss health display
/// Will be copied to the boss health tags in FrontEndView
pub boss_health_displays: [BossHealthDisplayEntry; 3],
unk5c50: [u8; 0x10],
/// Data used for the friendly character tags
/// Will be copied to the friendly character tags in FrontEndView
/// and then passed to the scaleform
pub friendly_chr_tag_displays: [ChrFriendTagEntry; 7],
/// Time in seconds, after which the damage number will be hidden
/// Enemy tag will use constant value of 1.5f instead of this
pub damage_number_decay_time: f32,
unk6134: [u8; 20],
fe_menu_chr_state_data: [u8; 0x168],
pub summon_msg_queue: SummonMsgQueue,
unk6530: u32,
/// ID of the fmg text entry for the area welcome message.
/// Set by EMEVD `2003[13]` [`DisplaySubareaWelcomeMessage`].
///
/// [`DisplaySubareaWelcomeMessage`]: https://soulsmods.github.io/emedf/er-emedf.html#DisplaySubareaWelcomeMessage
pub subarea_name_popup_message_id: i32,
unk6538: [u8; 4],
unk653c: u32,
unk6540: [u8; 8],
/// ID of the fmg text entry for the blinking message.
/// Set by EMEVD `2007[12]` [`DisplayBlinkingMessageWithPriority`].
///
/// [`DisplayBlinkingMessageWithPriority`]: https://soulsmods.github.io/emedf/er-emedf.html#DisplayBlinkingMessageWithPriority
pub blinking_message_id: i32,
/// Priority of the currently displayed blinking message
pub blinking_message_priority: u8,
pad654d: [u8; 3],
unk6550: u32,
/// Toggle, requesting the area welcome message
/// to be displayed
pub area_welcome_message_request: bool,
unk6555: [u8; 11],
pub get_item_log_view_model: [u8; 0x1d48],
unk82a8: [u8; 8],
pub clock_view_model: usize,
unk82b0: [u8; 8],
/// Don't update intermediate `frontend_values` data each frame in the `CSMenuMan` update task
pub disable_updates: bool,
unk82c1: [u8; 7],
/// Tag of the debug player
pub debug_tag: TagHudData,
unk83f0: [u8; 48],
}
#[repr(C)]
pub struct BossHealthDisplayEntry {
/// Id of the fmg text entry for the boss name
pub fmg_id: i32,
unk4: [u8; 4],
pub field_ins_handle: FieldInsHandle,
pub damage_taken: i32,
unk14: u32,
unk18: [u8; 0x8],
}
#[repr(C)]
pub struct SummonMsgQueue {
vftable: usize,
pub current: SummonMsgData,
pub list: CSFixedList<SummonMsgData, 4>,
unk278: u32,
unk27c: u32,
}
#[repr(C)]
pub struct SummonMsgData {
vftable: usize,
pub priority: i16,
pub force_play: bool,
unkb: [u8; 5],
pub text: MenuString,
unk48: bool,
unk49: [u8; 7],
}
#[repr(C)]
pub struct FrontEndView {
menu_window: [u8; 0xa31],
unka31: [u8; 0x7],
root_scene: [u8; 0x251c8],
pub front_end_view_values: NonNull<FrontEndViewValues>,
}
#[repr(C)]
/// Values that will be read in FrontEndView update procedure
/// from the menu window jobs
pub struct FrontEndViewValues {
unk0: [u8; 4],
/// Current player hp
pub player_hp: u32,
/// Max recoverable hp
/// Only works if rally mechanic is active
/// by using the malenia rune arc
pub max_recoverable_hp: u32,
/// Difference between the max hp and uncapped max hp
pub hp_max_uncapped_difference: u32,
/// Uncapped max player hp
pub hp_max_uncapped: u32,
/// Is hp rally mechanic enabled?
/// Makes the player able to recover hp by attacking enemies
pub enable_hp_rally: bool,
unk1a: [u8; 3],
/// Current player fp
pub fp: u32,
unk20: [u8; 8],
/// Max player fp
pub fp_max: u32,
/// Toggle that enables the equipment hud
/// eg equipped weapons, consumables and magic
pub enable_equip_hud: bool,
unk2a: [u8; 3],
/// Current player stamina
pub stamina: u32,
unk30: [u8; 8],
/// Max player stamina
pub stamina_max: u32,
unk38: [u8; 0xf74],
/// String, containing the name of the current usable Ash of War
pub sword_arts_name_string: MenuString,
unkfe8: [u8; 0x19c],
/// Number of eliminations in the arena
pub quickmatch_elimination_count: i32,
unk1184: [u8; 0x8],
quickmatch_data: [u8; 0x40],
pub enemy_chr_tag_data: [TagHudData; 8],
pub boss_list_tag_data: [TagHudData; 3],
pub friendly_chr_tag_data: [TagHudData; 7],
unk26a0: [u8; 0xfb8],
unk3658: MenuString,
unk3690: [u8; 0x10],
/// String, containing name from the latest proc status message
pub proc_status_message: MenuString,
/// When the timer exceeds 3.0f, the message will be removed
/// and read counter will be increased
pub proc_status_message_timer: f32,
unk36dc: [u8; 4],
/// Id of the full screen message to be displayed
/// Eg. "You Died", "Victory", "Defeat"
pub full_screen_message_request_id: FullScreenMessage,
unk36e4: [u8; 4],
unk36e8: MenuString,
unk3720: MenuString,
unk3758: MenuString,
unk3790: MenuString,
unk37c8: MenuString,
unk3800: [u8; 0x1558],
pub summoned_spirit_ash_count: u32,
unk4d5c: [u8; 4],
pub spirit_ash_display: [CSFeSpiritAshDisplay; 5],
unk4dd8: [u8; 8],
}
#[repr(i32)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum FullScreenMessage {
None = -1,
DemigodFelled = 1,
LegendFelled = 2,
GreatEnemyFelled = 3,
EnemyFelled = 4,
YouDied = 5,
HostVanquished = 7,
BloodFingerVanquished = 8,
DutyFullFilled = 9,
LostGraceDiscovered = 11,
Commence = 13,
Victory = 14,
Stalemate = 15,
Defeat = 16,
MapFound = 17,
GreatRuneRestored = 21,
GodSlain = 22,
DuelistVanquished = 23,
RecusantVanquished = 24,
InvaderVanquished = 25,
FurledFingerRankAdvanced = 26,
FurledFingerRankAdvanced2 = 31,
DuelistRankAdvanced = 32,
DuelistRankAdvanced2 = 33,
BloodyFingerRankAdvanced = 34,
BloodyFingerRankAdvanced2 = 35,
RecusantRankAdvanced = 36,
RecusantRankAdvanced2 = 37,
HunterRankAdvanced = 38,
HunterRankAdvanced2 = 39,
HeartStolen = 40,
MenuText = 41,
YouDiedWithFade = 42,
}
#[repr(C)]
pub struct CSFeSpiritAshDisplay {
pub field_ins_handle: FieldInsHandle,
pub hp: u32,
unkc: u32,
pub hp_max_uncapped_difference: u32,
pub hp_max_uncapped: u32,
}
#[repr(C)]
/// Custom string type used to interact with the scaleform
/// If string value is static, it will be stored in the static_string pointer
/// otherwise it will be stored in the DLString and the static_string pointer will be null
/// Source of name: RTTI of std::function
pub struct MenuString {
pub static_string: *const u16,
pub allocated_string: DLString,
}
impl Display for MenuString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if !self.allocated_string.is_empty() {
return write!(f, "{}", self.allocated_string);
}
if self.static_string.is_null() {
return write!(f, "");
}
unsafe {
let string_size = (0..)
.map(|i| *self.static_string.add(i))
.take_while(|&c| c != 0)
.count();
if string_size == 0 {
return write!(f, "");
}
let slice = std::slice::from_raw_parts(self.static_string, string_size);
write!(f, "{}", String::from_utf16_lossy(slice))
}
}
}
#[repr(C)]
/// Used to display the tag on the screen
/// Read by menu window jobs and written by CSMenuManImp update task
/// by copying the data from the ChrFriendTagEntry and ChrEnemyTagEntry on CSFeManImp
pub struct TagHudData {
/// Is this tag shown on screen?
pub is_visible: bool,
/// Should this tag screen position be updated?
pub update_position: bool,
/// Is the tag owner character currently not on screen?
/// If this is true, the tag will be rendered on the left side of the screen
pub is_not_on_screen: bool,
unk3: [u8; 5],
/// Handle to the tag owner character
pub field_ins_handle: FieldInsHandle,
/// Position of the tag on the screen - X
pub screen_pos_x: i32,
/// Position of the tag on the screen - Y
pub screen_pos_y: i32,
/// Current hp of the character
pub hp: u32,
unk1c: [u8; 0x4],
/// Difference between the max hp and uncapped max hp
pub hp_max_uncapped_difference: u32,
/// Uncapped max hp of the character
pub hp_max_uncapped: u32,
/// Name of the character
pub chr_name: MenuString,
/// Role of the character
/// eg. "Duelist"
pub role_string: MenuString,
unk98: [u8; 0x40],
/// Is this character downscaled?
/// True when character has the sp effect 590
pub is_down_scaled: bool,
unkd9: [u8; 0x3],
/// Number of the damage taken to show on the tag
pub last_damage_taken: i32,
/// Last hp value before the damage taken
/// Used to render the hp bar depletion
pub last_hp_value: u32,
/// Enum of the tag text color
/// 1 for friend summon (white)
/// 2 for enemy summon (red)
pub role_name_color: u8,
/// Does this character have an active rune arc?
/// True when PlayerGameData->0xa58 bit 1 is set
/// Will render circle icon on the tag
pub has_rune_arc: bool,
unke6: [u8; 0x42],
}
#[repr(C)]
/// Used to store and update the tag data for friendly characters
/// Data from here will be copied to the TagHudData in FrontEndViewValues
/// and then passed to the scaleform
pub struct ChrFriendTagEntry {
/// Screen position in format:
/// X, Y - screen position
/// Z - depth, seems to be increased when character is further away, not used elsewhere
/// W - unused, always 0
pub screen_pos: F32Vector4,
/// Should tag be visible on screen?
pub is_visible: bool,
/// Is line of sight to this character blocked?
/// Casts a ray to the world position where tag will be rendered
/// and checks if it hits anything
/// If this is true, the tag will be rendered on the left side of the screen
/// is_not_on_screen will be true and is_visible will be false
pub is_line_of_sight_blocked: bool,
/// Is this character currently not on screen?
/// If this is true, is_visible will be false and tag will be rendered
/// on the left side of the screen
pub is_not_on_screen: bool,
/// Enum of the tag text color
/// 1 for friend summon (white)
/// 2 for enemy summon (red)
pub role_name_color: u8,
_pad14: [u8; 4],
/// String, containing the role of the character
pub role_string: DLString,
/// String, containing the name of the character
pub name_string: DLString,
/// The max hp of the character, uncapped
pub hp_max_uncapped: u32,
/// The current hp of the character
pub hp: u32,
/// The max recoverable hp of the character
/// Only works if character has malenia rune arc active
pub max_recoverable_hp: u32,
/// The max hp of the character
pub max_hp: u32,
unk88: [u8; 4],
/// Time since the last damage taken
pub last_damage_time_delta: f32,
pub voice_chat_state: u32,
_pad94: [u8; 4],
/// Handle to the character
pub field_ins_handle: FieldInsHandle,
_pada0: [u8; 2],
/// The team type of the character
pub team_type: u8,
/// Is this character a downscaled summon?
/// True when character has the sp effect 590
pub is_down_scaled: bool,
/// Enables the rune arc icon on the tag
/// True when PlayerGameData->0xa58 bit 1 is set
pub has_rune_arc: bool,
/// Is this summon a debug summon
/// Will replace the name to "Debug" in japanese
/// and role to white summon
pub is_debug_summon: bool,
_pada6: [u8; 10],
}
#[repr(C)]
/// Used to store and update the tag data for enemy
/// (everyone you can lock on) characters
/// Data from here will be copied to the TagHudData in FrontEndViewValues
/// and then passed to the scaleform
pub struct ChrEnemyTagEntry {
/// Handle of the character
pub field_ins_handle: FieldInsHandle,
unk8: [u8; 0x8],
/// Screen position in format:
/// X, Y - screen position
/// Z - depth, seems to be increased when character is further away, not used elsewhere
/// W - unused, always 0
pub screen_pos: F32Vector4,
unk20: [u8; 0x4],
/// Amount of hp lost
/// Used to render the damage number on the tag
pub damage_taken: i32,
/// Last hp value before the damage taken
/// Used to render the hp bar depletion
pub pre_damage_hp: u32,
/// Delta time from last update of the tag
/// If it exceeds 1.5f, the tag will be removed
pub last_update_time_delta: f32,
/// Delta time from last damage taken
/// If it exceeds 1.5f, the damage number will be removed
pub last_damage_time_delta: f32,
/// Is this tag shown on screen?
pub is_visible: bool,
unk35: [u8; 0xb],
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn proper_sizes() {
assert_eq!(0x8420, size_of::<CSFeManImp>());
assert_eq!(0x280, size_of::<SummonMsgQueue>());
assert_eq!(0x25c08, size_of::<FrontEndView>());
assert_eq!(0x38, size_of::<MenuString>());
}
}