Skip to content

Commit 425db4b

Browse files
committed
Push code for 1.22.0
1 parent ed4800b commit 425db4b

File tree

4 files changed

+160
-14
lines changed

4 files changed

+160
-14
lines changed

crates/cobalt/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cobalt"
3-
version = "1.21.1"
3+
version = "1.22.0"
44
edition = "2021"
55

66
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

crates/cobalt/src/catalog.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ pub struct CachedCatalogEntry {
2828
pub fn from_json_hook(json: &Il2CppString, method_info: OptionalMethod) -> *const u8 {
2929
let manager = mods::manager::Manager::get();
3030

31-
if let Ok(dir) = manager.get_directory("Data/StreamingAssets/aa") {
31+
if let Ok(dir) = manager.get_directory("Data/StreamingAssets/aa/Switch") {
3232

3333
// Lookup Table to do the glue between modded bundles and the official Catalog
3434
let lut_cache: HashMap<String, String> = miniserde::json::from_str(
@@ -203,7 +203,7 @@ pub fn from_json_hook(json: &Il2CppString, method_info: OptionalMethod) -> *cons
203203
".png",
204204
".asset",
205205
".unity",
206-
".fbx"
206+
".fbx",
207207
];
208208

209209
let internal_path = suffixes.iter().fold(internal_path, |path, suffix| {
@@ -266,4 +266,4 @@ fn print_catalog_error(internal_id: &str, err: CatalogError) {
266266
CatalogError::DuplicateInternalId => panic!("InternalId `{}` already exists in catalog.bundle.\nThis can happen if you have a bundle with the same container path as an existing one.", internal_id),
267267
CatalogError::MissingInternalId => panic!("The following InternalId appears to be missing: {}", internal_id),
268268
}
269-
}
269+
}

crates/cobalt/src/sprite.rs

+155-10
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,110 @@
1-
use std::sync::OnceLock;
1+
use std::{alloc::Layout, ops::{Deref, DerefMut}, sync::OnceLock};
22

33
use engage::{
4-
gamedata::{ring::RingData, item::ItemData, unit::Unit, GodData},
4+
gamedata::{dispos::ChapterData, item::ItemData, ring::RingData, unit::Unit, GodData},
55
uniticon::UnitIcon,
66
};
77

88
use camino::Utf8PathBuf;
9+
910
use unity::{
1011
engine::{
1112
ui::{Image, IsImage},
1213
Color, FilterMode, ImageConversion, Material, Rect, Sprite, SpriteMeshType, Texture2D, Vector2,
1314
}, prelude::*, system::Dictionary
1415
};
1516

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+
16108
static mut SPRITE_MATERIAL: OnceLock<&'static Material> = OnceLock::new();
17109

18110
#[inline]
@@ -22,18 +114,33 @@ fn try_set_material(this: &mut UnitIcon) {
22114
}
23115
}
24116

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> {
26118
if let Some(this) = name {
27119
let path = Utf8PathBuf::from(filepath)
28120
.join(this.to_string())
29121
.with_extension("png");
30122

31123
if let Ok(file) = mods::manager::Manager::get().get_file(&path) {
32-
33124
let array = Il2CppArray::from_slice(file).unwrap();
34-
let new_texture = Texture2D::new(width, height);
35125

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\nLocation: {}\nDimensions: {}x{} \nExpected: {}x{}\n\nResize 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+
37144
if ImageConversion::load_image(new_texture, array) {
38145
new_texture.set_filter_mode(filter_mode);
39146

@@ -43,7 +150,7 @@ fn load_sprite(name: Option<&Il2CppString>, filepath: &str, width: i32, height:
43150

44151
return Some(Sprite::create2(new_texture, rect, pivot, 100.0, 1, SpriteMeshType::Tight));
45152
} else {
46-
println!("Could not load icon at `{}`.\n\nMake sure it is a PNG file with a dimension of {}x{} pixels", path, width, height);
153+
panic!("Could not load icon at `{}`.\n\nMake sure it is a PNG file with a dimension of {}x{} pixels", path, width, height);
47154
}
48155
}
49156
}
@@ -56,7 +163,8 @@ pub fn icon_destroy(this: &mut UnitIcon, method_info: OptionalMethod) {
56163
call_original!(this, method_info);
57164
}
58165

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+
60168
// #[skyline::hook(offset = 0x227d710)]
61169
#[unity::hook("App", "GameIcon", "TyrGetUnitIconIndex")] // What does this even do?
62170
pub fn trygetuniticonindex(name: Option<&Il2CppString>, method_info: OptionalMethod) -> &'static mut Sprite {
@@ -307,14 +415,51 @@ pub struct MapUIGauge<'a> {
307415
#[skyline::hook(offset = 0x201F830)]
308416
pub fn mapuigauge_getspritebyname(this: &MapUIGauge, name: Option<&Il2CppString>, method_info: OptionalMethod) -> &'static mut Sprite {
309417
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+
}
311421

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);
313423
match icon {
314424
Some(sprite) => {
315425
this.m_dictionary.add(name.unwrap(), sprite);
316426
sprite
317427
},
318428
None => call_original!(this, name, method_info),
319429
}
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+
}
320465
}

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ pub fn il2cpp_init_hook(domain_name: *const i8) -> i32 {
147147
cobalt::gamesound_setenumparam_gameobject,
148148
cobalt::goddata_getengagezoneprefabpath,
149149
cobalt::save::gamesavedata_procread_deserialize,
150+
cobalt::sprite::gmapinfocontent_setmapinfo_hook,
150151
);
151152

152153
// XML Patching

0 commit comments

Comments
 (0)