1
+ use std:: sync:: atomic:: AtomicBool ;
2
+
3
+ use unity:: {
4
+ prelude:: * ,
5
+ system:: Dictionary
6
+ } ;
7
+
8
+ use engage:: {
9
+ gamedata:: unit:: Unit ,
10
+ gamesound:: GameSound ,
11
+ combat:: {
12
+ Character ,
13
+ CharacterSound
14
+ } ,
15
+ } ;
16
+
17
+ use mods:: manager:: Manager ;
18
+
19
+ use camino:: Utf8PathBuf ;
20
+
21
+ pub mod gamesound;
22
+ pub mod soundmanager;
23
+ pub mod soundplay;
24
+ pub mod wwise;
25
+
26
+ pub const COBALT_EVENT_MARKER_PREFIX : & str = "CobaltEvent:" ;
27
+ pub const ORIGINAL_SUFFIX : & str = "_Original" ;
28
+
29
+ pub static PLAY_ORIGINAL_V_PICK : AtomicBool = AtomicBool :: new ( false ) ;
30
+ pub static mut PREVIOUS_LAST_PICK_VOICE : u8 = 0 ;
31
+ pub static mut UNSAFE_CHARACTER_PTR : * const Character = std:: ptr:: null ( ) ;
32
+
33
+ fn get_event_or_fallback ( event_name : & Il2CppString ) -> & Il2CppString {
34
+ match ParsedVoice :: parse ( event_name) {
35
+ ParsedVoice :: ModdedVoiceEvent ( mod_voice) => {
36
+ let mod_string = mod_voice. mod_event . to_string ( ) ;
37
+ let mod_str = mod_string. as_str ( ) ;
38
+ let fallback_string = mod_voice. fallback_event . to_string ( ) ;
39
+ let fallback_str = fallback_string. as_str ( ) ;
40
+
41
+ match mod_str. rsplit_once ( '_' ) {
42
+ Some ( ( event_body_str, event_main_str) ) => {
43
+ let event_main = Il2CppString :: new ( format ! ( "{}_{}" , event_body_str, event_main_str) ) ;
44
+
45
+ if GameSound :: is_event_loaded ( event_main) {
46
+ event_main
47
+ } else {
48
+ let event_fallback = Il2CppString :: new ( format ! ( "{}_{}" , event_body_str, fallback_str) ) ;
49
+ event_fallback
50
+ }
51
+ } ,
52
+ None => event_name,
53
+ }
54
+ } ,
55
+ ParsedVoice :: DefaultVoiceEvent ( default_voice) => {
56
+ println ! ( "Getting Default voice: {}" , default_voice. event) ;
57
+ default_voice. event
58
+ } ,
59
+ }
60
+ }
61
+
62
+ enum ParsedVoice < ' a > {
63
+ ModdedVoiceEvent ( ModdedVoiceEvent < ' a > ) ,
64
+ DefaultVoiceEvent ( DefaultVoiceEvent < ' a > ) ,
65
+ }
66
+
67
+ /// Represents a modded voice name with a fallback option.
68
+ ///
69
+ /// For example, in `Voice="SeasideDragon!PlayerF"`, `SeasideDragon` is the modded voice name,
70
+ /// and `PlayerF` is the fallback voice name that will be used if the modded voice is not available.
71
+ struct ModdedVoiceEvent < ' a > {
72
+ /// The name of the modded voice to be used, e.g., `SeasideDragon`.
73
+ mod_event : & ' a Il2CppString ,
74
+ /// The fallback voice name, e.g., `PlayerF`, used if the modded voice is unavailable.
75
+ /// This is not necessarily a voice name from the vanilla game - it could be another modded voice name (though this has not been tested).
76
+ fallback_event : & ' a Il2CppString ,
77
+ }
78
+
79
+ /// Represents an original voice name without any fallbacks.
80
+ struct DefaultVoiceEvent < ' a > {
81
+ event : & ' a Il2CppString ,
82
+ }
83
+
84
+ impl ParsedVoice < ' _ > {
85
+ fn parse ( event_name : & Il2CppString ) -> ParsedVoice {
86
+ let event_string = event_name. to_string ( ) ;
87
+ let parts: Vec < & str > = event_string. split ( '!' ) . collect ( ) ;
88
+
89
+ if parts. len ( ) == 2 {
90
+ return ParsedVoice :: ModdedVoiceEvent ( ModdedVoiceEvent {
91
+ mod_event : Il2CppString :: new ( parts[ 0 ] ) ,
92
+ fallback_event : Il2CppString :: new ( parts[ 1 ] ) ,
93
+ } ) ;
94
+ } else {
95
+ return ParsedVoice :: DefaultVoiceEvent ( DefaultVoiceEvent { event : event_name } ) ;
96
+ }
97
+ }
98
+ }
99
+
100
+ fn get_switchname_fallback ( switch_name : & Il2CppString ) -> & Il2CppString {
101
+ match switch_name. to_string ( ) . split ( '!' ) . nth ( 1 ) {
102
+ Some ( switch_fallback) => Il2CppString :: new ( switch_fallback) ,
103
+ None => switch_name,
104
+ }
105
+ }
106
+
107
+ #[ skyline:: hook( offset = 0x1F87570 ) ]
108
+ pub fn unitinfo_reservecharavoice (
109
+ side : i32 ,
110
+ person_switch_name : & Il2CppString ,
111
+ engage_switch_name : Option < & Il2CppString > ,
112
+ event_name : & Il2CppString ,
113
+ method_info : OptionalMethod ,
114
+ ) {
115
+ let event_string = event_name. to_string ( ) ;
116
+ let person_string = person_switch_name. to_string ( ) ;
117
+
118
+ // println!("[UnitInfo] Event name: {}", event_string);
119
+ // println!("[UnitInfo] Person Switch name: {}", person_string);
120
+
121
+ match event_string. as_str ( ) {
122
+ "V_Engage_Respond" => {
123
+ let modded_event = Il2CppString :: new ( format ! ( "{}_{}" , event_string, person_string) ) ;
124
+
125
+ match GameSound :: is_event_loaded ( modded_event) {
126
+ true => call_original ! ( side, person_switch_name, engage_switch_name, modded_event, method_info) ,
127
+ false => call_original ! ( side, person_switch_name, engage_switch_name, event_name, method_info) ,
128
+ }
129
+ } ,
130
+
131
+ _ => call_original ! ( side, person_switch_name, engage_switch_name, event_name, method_info) ,
132
+ }
133
+ }
134
+
135
+ // AkMarkerCallbackInfo$$get_strLabel 7102f26bd0 System_String_o * AkMarkerCallbackInfo$$get_strLabel(AkMarkerCallbackInfo_o * __this, MethodInfo * method) 172
136
+ #[ unity:: from_offset( "" , "AkMarkerCallbackInfo" , "get_strLabel" ) ]
137
+ fn akmarkercallbackinfo_get_strlabel ( this : & ( ) , method_info : OptionalMethod ) -> & ' static Il2CppString ;
138
+
139
+ // Combat.Character$$get_Sound 7102b00c40 Combat_CharacterSound_o * Combat.Character$$get_Sound(Combat_Character_o * __this, MethodInfo * method) 140
140
+ #[ unity:: from_offset( "Combat" , "Character" , "get_Sound" ) ]
141
+ fn combat_character_get_sound ( this : & Character , method_info : OptionalMethod ) -> & ' static CharacterSound ;
142
+
143
+ // Combat.CharacterSound$$PlayVoice 71025f0be0 void Combat.CharacterSound$$PlayVoice(Combat_CharacterSound_o * __this, System_String_o * eventName, MethodInfo * method) 556
144
+ #[ unity:: from_offset( "Combat" , "CharacterSound" , "PlayVoice" ) ]
145
+ fn combat_charactersound_play_voice ( this : & CharacterSound , event_name : & Il2CppString , method_info : OptionalMethod ) ;
146
+
147
+ #[ unity:: from_offset( "App" , "FileCommon" , "GetFullPath" ) ]
148
+ fn filecommon_getfullpath ( path : & Il2CppString , method_info : OptionalMethod ) -> & ' static mut Il2CppString ;
149
+
150
+ #[ unity:: class( "App" , "FileCommon" ) ]
151
+ pub struct FileCommon { }
152
+
153
+ #[ repr( C ) ]
154
+ pub struct FileCommonStaticFields < ' a > {
155
+ lock_object : & ' a ( ) ,
156
+ dictionary : & ' a Dictionary < & ' a Il2CppString , & ' a mut FileData > ,
157
+ // Meh
158
+ }
159
+
160
+ #[ unity:: class( "App" , "FileHandle" ) ]
161
+ pub struct FileHandle {
162
+ data : & ' static mut FileData ,
163
+ }
164
+
165
+ impl FileHandle {
166
+ pub fn unload ( & self ) {
167
+ let method = self . get_class ( ) . get_method_from_name ( "Unload" , 0 ) . unwrap ( ) ;
168
+
169
+ let unload = unsafe { std:: mem:: transmute :: < _ , extern "C" fn ( & Self , & MethodInfo ) > ( method. method_ptr ) } ;
170
+
171
+ unload ( & self , method) ;
172
+ }
173
+ }
174
+
175
+ #[ unity:: class( "App" , "FileData" ) ]
176
+ pub struct FileData {
177
+ state : i32 ,
178
+ path : & ' static Il2CppString ,
179
+ data : & ' static mut Il2CppArray < u8 > ,
180
+ refer : & ' static mut BindHolder ,
181
+ }
182
+
183
+ #[ unity:: class( "App" , "BindHolder" ) ]
184
+ pub struct BindHolder {
185
+ bind : i32 ,
186
+ }
187
+
188
+ #[ unity:: from_offset( "App" , "BindHolder" , "Bind" ) ]
189
+ fn bindholder_bind ( this : & mut BindHolder , method_info : OptionalMethod ) -> bool ;
190
+
191
+ #[ skyline:: hook( offset = 0x328fff0 ) ]
192
+ fn filehandle_loadasync ( this : & ' static mut FileHandle , path : & Il2CppString , method_info : OptionalMethod ) {
193
+ // println!("[FileHandle::LoadAsync] Path: {}", path.to_string());
194
+
195
+ let mod_path = Utf8PathBuf :: from ( "Data/StreamingAssets/" ) . join ( path. to_string ( ) ) ;
196
+
197
+ // Check if we have that voiceline file in our mods and load it ourselves if we do
198
+ if Manager :: get ( ) . exists ( & mod_path) {
199
+ // Resets FileHandle
200
+ this. unload ( ) ;
201
+
202
+ let full_path = unsafe { filecommon_getfullpath ( path, None ) } ;
203
+
204
+ let static_fields = FileCommon :: class ( ) . get_static_fields_mut :: < FileCommonStaticFields > ( ) ;
205
+
206
+ // Check if there's already a cached version of the voiceline
207
+ if static_fields. dictionary . try_get_value ( full_path, & mut this. data ) == false {
208
+ let mut file = Manager :: get ( ) . get_file ( mod_path) . unwrap ( ) ;
209
+
210
+ // Initialize the FileData pointer
211
+ this. data = FileData :: instantiate ( ) . unwrap ( ) ;
212
+ this. data . path = full_path;
213
+ this. data . data = Il2CppArray :: < u8 > :: from_slice ( & mut file) . unwrap ( ) ;
214
+ this. data . state = 2 ;
215
+ this. data . refer = BindHolder :: instantiate ( ) . unwrap ( ) ;
216
+
217
+ // Add our fully loaded file to the cache list
218
+ static_fields. dictionary . add ( full_path, this. data ) ;
219
+ }
220
+
221
+ unsafe {
222
+ bindholder_bind ( this. data . refer , None ) ;
223
+ }
224
+ } else {
225
+ call_original ! ( this, path, method_info)
226
+ }
227
+ }
228
+
229
+ // Combat.CharacterAppearance$$CreateForSound 7102b0f340 Combat_CharacterAppearance_o * Combat.CharacterAppearance$$CreateForSound(App_Unit_o * unit, MethodInfo * method) 280
230
+ #[ unity:: from_offset( "Combat" , "CharacterAppearance" , "CreateForSound" ) ]
231
+ pub fn combat_character_appearance_create_for_sound ( unit : & Unit , method_info : OptionalMethod ) -> & ' static CharacterAppearance ;
232
+
233
+ #[ unity:: class( "App" , "AssetTable.Sound" ) ]
234
+ pub struct AssetTableSound {
235
+ voice_id : & ' static Il2CppString ,
236
+ footstep_id : & ' static Il2CppString ,
237
+ material_id : & ' static Il2CppString ,
238
+ }
239
+
240
+ #[ unity:: class( "Combat" , "CharacterAppearance" ) ]
241
+ pub struct CharacterAppearance {
242
+ padding : [ u8 ; 0x88 ] ,
243
+ sound : AssetTableSound ,
244
+ }
0 commit comments