1
- use std:: sync:: OnceLock ;
1
+ use std:: { alloc :: Layout , ops :: { Deref , DerefMut } , sync:: OnceLock } ;
2
2
3
3
use engage:: {
4
- gamedata:: { ring :: RingData , item:: ItemData , unit:: Unit , GodData } ,
4
+ gamedata:: { dispos :: ChapterData , item:: ItemData , ring :: RingData , unit:: Unit , GodData } ,
5
5
uniticon:: UnitIcon ,
6
6
} ;
7
7
8
8
use camino:: Utf8PathBuf ;
9
+
9
10
use unity:: {
10
11
engine:: {
11
12
ui:: { Image , IsImage } ,
12
13
Color , FilterMode , ImageConversion , Material , Rect , Sprite , SpriteMeshType , Texture2D , Vector2 ,
13
14
} , prelude:: * , system:: Dictionary
14
15
} ;
15
16
17
+ #[ repr( i32 ) ]
18
+ #[ derive( Debug , PartialEq ) ]
19
+
20
+ pub enum PngResult {
21
+ Ok = 0 ,
22
+ Error ,
23
+ OutOfMemory
24
+ }
25
+
26
+ #[ repr( C ) ]
27
+ #[ derive( Debug ) ]
28
+ pub struct Dimension {
29
+ width : i32 ,
30
+ height : i32
31
+ }
32
+
33
+ #[ repr( C ) ]
34
+ pub struct PngDecoder {
35
+ unk : [ u8 ; 0x8C ] ,
36
+ }
37
+
38
+ impl PngDecoder {
39
+ extern "C" fn pngdecoder_malloc ( size : usize , _user_data : * const u8 ) -> * mut u8 {
40
+ if let Ok ( layout) = Layout :: from_size_align ( size, 16 ) {
41
+ unsafe { std:: alloc:: alloc ( layout) }
42
+ } else {
43
+ // I don't think it's wise to panic in a allocator but who knows
44
+ std:: ptr:: null_mut ( )
45
+ }
46
+ }
47
+
48
+ extern "C" fn pngdecoder_free ( ptr : * mut u8 , user_data : * const u8 ) {
49
+ // Switch's global allocator is malloc/free and therefore do not care about the Layout. I don't really see the point of this API.
50
+ unsafe { std:: alloc:: dealloc ( ptr, Layout :: new :: < ( ) > ( ) ) }
51
+ }
52
+
53
+ pub fn new ( ) -> Self {
54
+ let this = Self {
55
+ unk : [ 0u8 ; 0x8c ] ,
56
+ } ;
57
+
58
+ unsafe { ctor ( & this) }
59
+
60
+ this
61
+ }
62
+
63
+ pub fn initialize ( & mut self ) {
64
+ unsafe { initialize ( self , Self :: pngdecoder_malloc, 0 as _ , Self :: pngdecoder_free, 0 as _ ) }
65
+ }
66
+
67
+ pub fn set_image_data ( & mut self , png : impl AsRef < [ u8 ] > ) {
68
+ let data = png. as_ref ( ) ;
69
+ unsafe { set_image_data ( self , data. as_ptr ( ) , data. len ( ) ) }
70
+ }
71
+
72
+ pub fn analyze ( & mut self ) -> PngResult {
73
+ unsafe { analyze ( self ) }
74
+ }
75
+
76
+ pub fn get_dimension ( & self ) -> Dimension {
77
+ unsafe { get_dimension ( self ) }
78
+ }
79
+
80
+ }
81
+
82
+ impl Drop for PngDecoder {
83
+ fn drop ( & mut self ) {
84
+ unsafe { dtor ( self ) }
85
+ }
86
+ }
87
+
88
+ extern "C" {
89
+ #[ link_name = "_ZN2nn5image10PngDecoderC1Ev" ]
90
+ pub fn ctor ( this : & PngDecoder ) ;
91
+
92
+ #[ link_name = "_ZN2nn5image10PngDecoderD1Ev" ]
93
+ pub fn dtor ( this : & PngDecoder ) ;
94
+
95
+ #[ link_name = "_ZN2nn5image10PngDecoder10InitializeEPFPvmS2_ES2_PFvS2_S2_ES2_" ]
96
+ pub fn initialize ( this : & PngDecoder , alloc_fn : extern "C" fn ( size : usize , data : * const u8 ) -> * mut u8 , allocate_data : * const u8 , free_fn : extern "C" fn ( ptr : * mut u8 , data : * const u8 ) , free_data : * const u8 ) ;
97
+
98
+ #[ link_name = "_ZN2nn5image10PngDecoder12SetImageDataEPKvm" ]
99
+ pub fn set_image_data ( this : & mut PngDecoder , data : * const u8 , size : usize ) ;
100
+
101
+ #[ link_name = "_ZN2nn5image10PngDecoder7AnalyzeEv" ]
102
+ pub fn analyze ( this : & mut PngDecoder ) -> PngResult ;
103
+
104
+ #[ link_name = "_ZNK2nn5image10PngDecoder12GetDimensionEv" ]
105
+ pub fn get_dimension ( this : & PngDecoder ) -> Dimension ;
106
+ }
107
+
16
108
static mut SPRITE_MATERIAL : OnceLock < & ' static Material > = OnceLock :: new ( ) ;
17
109
18
110
#[ inline]
@@ -22,18 +114,33 @@ fn try_set_material(this: &mut UnitIcon) {
22
114
}
23
115
}
24
116
25
- fn load_sprite ( name : Option < & Il2CppString > , filepath : & str , width : i32 , height : i32 , filter_mode : FilterMode ) -> Option < & ' static mut Sprite > {
117
+ fn load_sprite ( name : Option < & Il2CppString > , filepath : & str , mut width : i32 , mut height : i32 , filter_mode : FilterMode ) -> Option < & ' static mut Sprite > {
26
118
if let Some ( this) = name {
27
119
let path = Utf8PathBuf :: from ( filepath)
28
120
. join ( this. to_string ( ) )
29
121
. with_extension ( "png" ) ;
30
122
31
123
if let Ok ( file) = mods:: manager:: Manager :: get ( ) . get_file ( & path) {
32
-
33
124
let array = Il2CppArray :: from_slice ( file) . unwrap ( ) ;
34
- let new_texture = Texture2D :: new ( width, height) ;
35
125
36
- // println!("Before LoadImage");
126
+ let mut decoder = PngDecoder :: new ( ) ;
127
+
128
+ decoder. initialize ( ) ;
129
+ decoder. set_image_data ( array. fields . deref ( ) ) ;
130
+
131
+ if PngResult :: Ok == decoder. analyze ( ) {
132
+ let dim = decoder. get_dimension ( ) ;
133
+ if ( width, height) != ( dim. width , dim. height ) {
134
+ if ( width, height) == ( 0 , 0 ) { // WxH of 0x0 implies that it could be anything
135
+ ( width, height) = ( dim. width , dim. height ) ;
136
+ } else {
137
+ panic ! ( "Malformed sprite file\n Location: {}\n Dimensions: {}x{} \n Expected: {}x{}\n \n Resize the image to avoid stretching" , path, dim. width, dim. height, width, height) ;
138
+ }
139
+ }
140
+ }
141
+
142
+ let new_texture = Texture2D :: new ( width, height) ;
143
+
37
144
if ImageConversion :: load_image ( new_texture, array) {
38
145
new_texture. set_filter_mode ( filter_mode) ;
39
146
@@ -43,7 +150,7 @@ fn load_sprite(name: Option<&Il2CppString>, filepath: &str, width: i32, height:
43
150
44
151
return Some ( Sprite :: create2 ( new_texture, rect, pivot, 100.0 , 1 , SpriteMeshType :: Tight ) ) ;
45
152
} else {
46
- println ! ( "Could not load icon at `{}`.\n \n Make sure it is a PNG file with a dimension of {}x{} pixels" , path, width, height) ;
153
+ panic ! ( "Could not load icon at `{}`.\n \n Make sure it is a PNG file with a dimension of {}x{} pixels" , path, width, height) ;
47
154
}
48
155
}
49
156
}
@@ -56,7 +163,8 @@ pub fn icon_destroy(this: &mut UnitIcon, method_info: OptionalMethod) {
56
163
call_original ! ( this, method_info) ;
57
164
}
58
165
59
- // App.GameIcon$$TyrGetUnitIconIndex
166
+ // TODO: Investigate why load_sprite is called twice when dealing with the Unit Selection menu or highlighting the unit on a battle map
167
+
60
168
// #[skyline::hook(offset = 0x227d710)]
61
169
#[ unity:: hook( "App" , "GameIcon" , "TyrGetUnitIconIndex" ) ] // What does this even do?
62
170
pub fn trygetuniticonindex ( name : Option < & Il2CppString > , method_info : OptionalMethod ) -> & ' static mut Sprite {
@@ -307,14 +415,51 @@ pub struct MapUIGauge<'a> {
307
415
#[ skyline:: hook( offset = 0x201F830 ) ]
308
416
pub fn mapuigauge_getspritebyname ( this : & MapUIGauge , name : Option < & Il2CppString > , method_info : OptionalMethod ) -> & ' static mut Sprite {
309
417
let mut result = Sprite :: instantiate ( ) . unwrap ( ) ;
310
- if this. m_dictionary . try_get_value ( name. unwrap ( ) , & mut result) { return call_original ! ( this, name, method_info) ; }
418
+ if this. m_dictionary . try_get_value ( name. unwrap ( ) , & mut result) {
419
+ return call_original ! ( this, name, method_info) ;
420
+ }
311
421
312
- let icon = load_sprite ( name, "patches/icon/mapstatus" , 64 , 64 , FilterMode :: Point ) ;
422
+ let icon = load_sprite ( name, "patches/icon/mapstatus" , 0 , 0 , FilterMode :: Point ) ;
313
423
match icon {
314
424
Some ( sprite) => {
315
425
this. m_dictionary . add ( name. unwrap ( ) , sprite) ;
316
426
sprite
317
427
} ,
318
428
None => call_original ! ( this, name, method_info) ,
319
429
}
430
+ }
431
+
432
+ #[ unity:: class( "App" , "GmapMapInfoContent" ) ]
433
+ pub struct GmapMapInfoContent {
434
+ unk1 : [ u8 ; 0x18 ] ,
435
+ pub map_info_image : & ' static mut Image ,
436
+ unk2 : [ u8 ; 0x90 ] ,
437
+ pub map_info_sprite : & ' static Sprite ,
438
+ // ...
439
+ }
440
+
441
+ #[ unity:: class( "App" , "GmapSpot" ) ]
442
+ pub struct GmapSpot { }
443
+
444
+ impl GmapSpot {
445
+ pub fn get_chapter ( & self ) -> & ' static mut ChapterData {
446
+ unsafe { gmapspot_get_chapter ( self , None ) }
447
+ }
448
+ }
449
+
450
+ #[ unity:: from_offset( "App" , "GmapSpot" , "get_Chapter" ) ]
451
+ pub fn gmapspot_get_chapter ( this : & GmapSpot , method_info : OptionalMethod ) -> & ' static mut ChapterData ;
452
+
453
+ #[ unity:: hook( "App" , "GmapMapInfoContent" , "SetMapInfo" ) ]
454
+ pub fn gmapinfocontent_setmapinfo_hook ( this : & mut GmapMapInfoContent , gmap_spot : & GmapSpot , method_info : OptionalMethod ) {
455
+ // Call it first so the game can assign everything that is not related to the InfoThumb.
456
+ call_original ! ( this, gmap_spot, method_info) ;
457
+
458
+ let chapter = gmap_spot. get_chapter ( ) ;
459
+ let prefixless_cid = chapter. get_prefixless_cid ( ) ;
460
+
461
+ if let Some ( infothumb) = load_sprite ( Some ( prefixless_cid) , "patches/ui/gmap/infothumb" , 468 , 256 , FilterMode :: Trilinear ) {
462
+ this. map_info_sprite = infothumb;
463
+ this. map_info_image . set_sprite ( infothumb) ;
464
+ }
320
465
}
0 commit comments