From 1af809c11e1927cf049aeffdd11bfb3bb7ff6edc Mon Sep 17 00:00:00 2001 From: chinosk <2248589280@qq.com> Date: Sat, 13 Jun 2026 15:26:08 +0800 Subject: [PATCH] Compatible with Unity 6 --- app/build.gradle | 2 +- app/src/main/cpp/GakumasLocalify/Hook.cpp | 314 +++++++++++++++--- .../main/cpp/GakumasLocalify/HookTexture.cpp | 157 ++++++--- app/src/main/res/values-zh-rHK/strings.xml | 1 + app/src/main/res/values-zh-rMO/strings.xml | 1 + app/src/main/res/values-zh-rTW/strings.xml | 1 + 6 files changed, 381 insertions(+), 95 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 997b241..7044d47 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,7 +16,7 @@ android { minSdk 29 targetSdk 34 versionCode 12 - versionName "v3.3.1" + versionName "v3.4.0" buildConfigField "String", "VERSION_NAME", "\"${versionName}\"" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/cpp/GakumasLocalify/Hook.cpp b/app/src/main/cpp/GakumasLocalify/Hook.cpp index 46f8c29..4fab861 100644 --- a/app/src/main/cpp/GakumasLocalify/Hook.cpp +++ b/app/src/main/cpp/GakumasLocalify/Hook.cpp @@ -52,6 +52,7 @@ void UnHookAll() { namespace GakumasLocal::HookMain { using Il2cppString = UnityResolve::UnityType::String; + using Il2CppGCHandle = void*; UnityResolve::UnityType::String* environment_get_stacktrace() { /* @@ -71,23 +72,46 @@ namespace GakumasLocal::HookMain { return toString_mtd->Invoke(klassInstance); } - DEFINE_HOOK(void, Internal_LogException, (void* ex, void* obj)) { - Internal_LogException_Orig(ex, obj); + // Unity 6: DebugLogHandler.Internal_Log* is resolved as a managed method + // through GetMethod(), so the generated IL2CPP function includes the + // trailing MethodInfo* argument. + DEFINE_HOOK(void, Internal_LogException, (void* ex, void* obj, void* mtd)) { + Internal_LogException_Orig(ex, obj, mtd); static auto Exception_ToString = Il2cppUtils::GetMethod("mscorlib.dll", "System", "Exception", "ToString"); Log::LogUnityLog(ANDROID_LOG_ERROR, "UnityLog - Internal_LogException:\n%s", Exception_ToString->Invoke(ex)->ToString().c_str()); } - DEFINE_HOOK(void, Internal_Log, (int logType, int logOption, UnityResolve::UnityType::String* content, void* context)) { - Internal_Log_Orig(logType, logOption, content, context); - // 2022.3.21f1 + DEFINE_HOOK(void, Internal_Log, (int logType, int logOption, UnityResolve::UnityType::String* content, void* context, void* mtd)) { + Internal_Log_Orig(logType, logOption, content, context, mtd); Log::LogUnityLog(ANDROID_LOG_VERBOSE, "Internal_Log:\n%s", content->ToString().c_str()); } bool IsNativeObjectAlive(void* obj) { - static UnityResolve::Method* IsNativeObjectAliveMtd = nullptr; - if (!IsNativeObjectAliveMtd) IsNativeObjectAliveMtd = Il2cppUtils::GetMethod("UnityEngine.CoreModule.dll", "UnityEngine", - "Object", "IsNativeObjectAlive"); - return IsNativeObjectAliveMtd->Invoke(obj); + if (!obj) { + return false; + } + + static const auto IsNativeObjectAliveMtd = Il2cppUtils::GetMethod( + "UnityEngine.CoreModule.dll", + "UnityEngine", + "Object", + "IsNativeObjectAlive", + { "UnityEngine.Object" } + ); + + if (!IsNativeObjectAliveMtd || !IsNativeObjectAliveMtd->function) { + return false; + } + + using IsNativeObjectAliveFn = bool (*)(void* obj, void* method); + const auto isNativeObjectAlive = reinterpret_cast( + IsNativeObjectAliveMtd->function + ); + + return isNativeObjectAlive( + obj, + IsNativeObjectAliveMtd->address + ); } UnityResolve::UnityType::Camera* mainCameraCache = nullptr; @@ -343,7 +367,10 @@ namespace GakumasLocal::HookMain { } DEFINE_HOOK(void, CanvasRenderer_SetTexture, (void* self, void* texture)) { - CanvasRenderer_SetTexture_Orig(self, ReplaceTextureOrSpriteByObjectName(texture)); + CanvasRenderer_SetTexture_Orig( + self, + ReplaceTextureOrSpriteByObjectName(texture) + ); } DEFINE_HOOK(void, SpriteRenderer_set_sprite, (void* self, void* sprite)) { @@ -372,7 +399,6 @@ namespace GakumasLocal::HookMain { } } - #ifdef GKMS_WINDOWS struct TransparentStringHash : std::hash, std::hash { @@ -381,7 +407,7 @@ namespace GakumasLocal::HookMain { typedef std::unordered_set> AssetPathsType; std::map CustomAssetBundleAssetPaths; - std::unordered_map CustomAssetBundleHandleMap{}; + std::unordered_map CustomAssetBundleHandleMap{}; std::list g_extra_assetbundle_paths{}; void LoadExtraAssetBundle() { @@ -394,9 +420,12 @@ namespace GakumasLocal::HookMain { // CustomAssetBundleAssetPaths.clear(); // assert(!ExtraAssetBundleHandle && ExtraAssetBundleAssetPaths.empty()); - static auto AssetBundle_GetAllAssetNames = reinterpret_cast( - Il2cppUtils::il2cpp_resolve_icall("UnityEngine.AssetBundle::GetAllAssetNames()") - ); + static auto AssetBundle_GetAllAssetNames = Il2cppUtils::GetMethod( + "UnityEngine.AssetBundleModule.dll", + "UnityEngine", + "AssetBundle", + "GetAllAssetNames" + ); for (const auto& i : g_extra_assetbundle_paths) { if (CustomAssetBundleHandleMap.contains(i)) continue; @@ -404,7 +433,7 @@ namespace GakumasLocal::HookMain { const auto extraAssetBundle = WinHooks::LoadAssetBundle(i); if (extraAssetBundle) { - const auto allAssetPaths = AssetBundle_GetAllAssetNames(extraAssetBundle); + const auto allAssetPaths = AssetBundle_GetAllAssetNames->Invoke(extraAssetBundle); AssetPathsType assetPath{}; Il2cppUtils::iterate_IEnumerable(allAssetPaths, [&assetPath](Il2CppString* path) { @@ -413,7 +442,8 @@ namespace GakumasLocal::HookMain { assetPath.emplace(path->start_char); }); CustomAssetBundleAssetPaths.emplace(i, assetPath); - CustomAssetBundleHandleMap.emplace(i, UnityResolve::Invoke("il2cpp_gchandle_new", extraAssetBundle, false)); + const auto bundleHandle = UnityResolve::Invoke("il2cpp_gchandle_new", extraAssetBundle, false); + CustomAssetBundleHandleMap.emplace(i, bundleHandle); } else { @@ -422,7 +452,7 @@ namespace GakumasLocal::HookMain { } } - uint32_t GetBundleHandleByAssetName(std::wstring assetName) { + Il2CppGCHandle GetBundleHandleByAssetName(std::wstring assetName) { for (const auto& i : CustomAssetBundleAssetPaths) { for (const auto& m : i.second) { if (std::equal(m.begin(), m.end(), assetName.begin(), assetName.end(), @@ -433,14 +463,14 @@ namespace GakumasLocal::HookMain { } } } - return NULL; + return nullptr; } - uint32_t GetBundleHandleByAssetName(std::string assetName) { + Il2CppGCHandle GetBundleHandleByAssetName(std::string assetName) { return GetBundleHandleByAssetName(utility::conversions::to_string_t(assetName)); } - uint32_t ReplaceFontHandle; + Il2CppGCHandle ReplaceFontHandle = nullptr; void* GetReplaceFont() { static auto FontClass = Il2cppUtils::GetClass("UnityEngine.TextRenderingModule.dll", "UnityEngine", "Font"); @@ -451,7 +481,7 @@ namespace GakumasLocal::HookMain { const auto fontPath = "assets/fonts/gkamszhfontmix.otf"; void* replaceFont{}; - const auto& bundleHandle = GetBundleHandleByAssetName(fontPath); + const auto bundleHandle = GetBundleHandleByAssetName(fontPath); if (bundleHandle) { if (ReplaceFontHandle) @@ -466,19 +496,23 @@ namespace GakumasLocal::HookMain { } else { - UnityResolve::Invoke("il2cpp_gchandle_free", std::exchange(ReplaceFontHandle, 0)); + UnityResolve::Invoke("il2cpp_gchandle_free", std::exchange(ReplaceFontHandle, nullptr)); } } const auto extraAssetBundle = UnityResolve::Invoke("il2cpp_gchandle_get_target", bundleHandle); - static auto AssetBundle_LoadAsset = reinterpret_cast( - Il2cppUtils::il2cpp_resolve_icall("UnityEngine.AssetBundle::LoadAsset_Internal(System.String,System.Type)") - );; + static auto AssetBundle_LoadAsset = Il2cppUtils::GetMethod( + "UnityEngine.AssetBundleModule.dll", + "UnityEngine", + "AssetBundle", + "LoadAsset_Internal", + { "System.String", "System.Type" } + ); - replaceFont = AssetBundle_LoadAsset(extraAssetBundle, Il2cppString::New(fontPath), Font_Type); + replaceFont = AssetBundle_LoadAsset->Invoke(extraAssetBundle, Il2cppString::New(fontPath), Font_Type); if (replaceFont) { - ReplaceFontHandle = UnityResolve::Invoke("il2cpp_gchandle_new", replaceFont, false); + ReplaceFontHandle = UnityResolve::Invoke("il2cpp_gchandle_new", replaceFont, false); } else { @@ -493,19 +527,72 @@ namespace GakumasLocal::HookMain { } #else void* fontCache = nullptr; + bool CreateFontFromPath(void* font, const std::filesystem::path& fontName) { + if (!font) { + return false; + } + + const auto fontPath = Il2cppString::New(fontName.string()); + if (!fontPath) { + Log::Error("CreateFontFromPath failed: cannot create path string"); + return false; + } + + static auto CreateFontFromPathIcall = reinterpret_cast( + Il2cppUtils::il2cpp_resolve_icall("UnityEngine.Font::Internal_CreateFontFromPath(UnityEngine.Font,System.String)") + ); + if (CreateFontFromPathIcall) { + CreateFontFromPathIcall(font, fontPath); + return true; + } + + static auto CreateFontFromPathMethod = [] { + auto method = Il2cppUtils::GetMethod( + "UnityEngine.TextRenderingModule.dll", + "UnityEngine", + "Font", + "Internal_CreateFontFromPath", + { "UnityEngine.Font", "System.String" } + ); + if (method) { + return method; + } + + return Il2cppUtils::GetMethod( + "UnityEngine.TextRenderingModule.dll", + "UnityEngine", + "Font", + "Internal_CreateFontFromPath" + ); + }(); + if (!CreateFontFromPathMethod || !CreateFontFromPathMethod->function || !CreateFontFromPathMethod->address) { + Log::Error("CreateFontFromPath failed: method not found"); + return false; + } + + using CreateFontFromPathManagedFn = void (*)(void* font, Il2cppString* path, void* method); + const auto createFontFromPath = reinterpret_cast( + CreateFontFromPathMethod->function + ); + createFontFromPath(font, fontPath, CreateFontFromPathMethod->address); + return true; + } + void* GetReplaceFont() { static auto fontName = Local::GetBasePath() / "local-files" / "gkamsZHFontMIX.otf"; if (!std::filesystem::exists(fontName)) { return nullptr; } - static auto CreateFontFromPath = reinterpret_cast( - Il2cppUtils::il2cpp_resolve_icall("UnityEngine.Font::Internal_CreateFontFromPath(UnityEngine.Font,System.String)") - ); static auto Font_klass = Il2cppUtils::GetClass("UnityEngine.TextRenderingModule.dll", "UnityEngine", "Font"); static auto Font_ctor = Il2cppUtils::GetMethod("UnityEngine.TextRenderingModule.dll", "UnityEngine", "Font", ".ctor"); + if (!Font_klass || !Font_ctor) { + Log::Error("GetReplaceFont failed: Font class or constructor not found"); + return nullptr; + } + if (fontCache) { if (IsNativeObjectAlive(fontCache)) { return fontCache; @@ -513,9 +600,16 @@ namespace GakumasLocal::HookMain { } const auto newFont = Font_klass->New(); + if (!newFont) { + Log::Error("GetReplaceFont failed: cannot create Font instance"); + return nullptr; + } Font_ctor->Invoke(newFont); - CreateFontFromPath(newFont, Il2cppString::New(fontName.string())); + if (!CreateFontFromPath(newFont, fontName)) { + return nullptr; + } + fontCache = newFont; return newFont; } @@ -1753,7 +1847,18 @@ namespace GakumasLocal::HookMain { UnityResolve::Mode::Il2Cpp, Config::lazyInit); #endif - // Temporarily isolate texture replacement to CanvasRenderer.SetTexture only. + // Texture replacement is currently isolated to CanvasRenderer.SetTexture. + // + // Unity 6 compatibility warning: + // The resolver helpers below may return either a native icall address or + // a managed IL2CPP methodPointer fallback. These two targets do not share + // the same ABI: a managed methodPointer normally has a trailing MethodInfo* + // argument, while the current hook declarations use the icall-style + // signatures. Do not simply uncomment these ADD_HOOK calls on Unity 6. + // Before restoring them, either: + // 1. make each resolver return only a verified icall address; or + // 2. return ABI metadata and install a separate managed hook signature + // that preserves and forwards the trailing MethodInfo* argument. // ADD_HOOK(AssetBundle_LoadAsset, ResolveAssetBundleLoadAssetHookAddress()); // ADD_HOOK(AssetBundle_LoadAssetAsync, ResolveAssetBundleLoadAssetAsyncHookAddress()); // ADD_HOOK(AssetBundleRequest_GetResult, ResolveAssetBundleRequestResultHookAddress()); @@ -1992,10 +2097,41 @@ namespace GakumasLocal::HookMain { Il2cppUtils::GetMethodPointer("campus-submodule.Runtime.dll", "Campus.Common", "CampusQualityManager", "set_TargetFrameRate")); - ADD_HOOK(Internal_LogException, Il2cppUtils::il2cpp_resolve_icall( - "UnityEngine.DebugLogHandler::Internal_LogException(System.Exception,UnityEngine.Object)")); - ADD_HOOK(Internal_Log, Il2cppUtils::il2cpp_resolve_icall( - "UnityEngine.DebugLogHandler::Internal_Log(UnityEngine.LogType,UnityEngine.LogOption,System.String,UnityEngine.Object)")); + // Unity 6 no longer exposes these two methods through the old icall + // names in this player. Resolve their managed IL2CPP methodPointer + // through GetMethod() and use hook signatures with a trailing MethodInfo*. + const auto Internal_LogException_Method = Il2cppUtils::GetMethod( + "UnityEngine.CoreModule.dll", + "UnityEngine", + "DebugLogHandler", + "Internal_LogException", + { "System.Exception", "UnityEngine.Object" } + ); + ADD_HOOK( + Internal_LogException, + Internal_LogException_Method + ? Internal_LogException_Method->function + : nullptr + ); + + const auto Internal_Log_Method = Il2cppUtils::GetMethod( + "UnityEngine.CoreModule.dll", + "UnityEngine", + "DebugLogHandler", + "Internal_Log", + { + "UnityEngine.LogType", + "UnityEngine.LogOption", + "System.String", + "UnityEngine.Object" + } + ); + ADD_HOOK( + Internal_Log, + Internal_Log_Method + ? Internal_Log_Method->function + : nullptr + ); // 双端 ADD_HOOK(InternalSetOrientationAsync, @@ -2032,14 +2168,102 @@ namespace GakumasLocal::HookMain { // "AspectRatioHandler", "WindowProc")); if (GakumasLocal::Config::dmmUnlockSize) { - std::thread([]() { - std::this_thread::sleep_for(std::chrono::seconds(3)); - auto hWnd = FindWindowW(L"UnityWndClass", L"gakumas"); - // 添加可调整大小的边框和最大化按钮 - LONG style = GetWindowLong(hWnd, GWL_STYLE); - style |= WS_THICKFRAME | WS_MAXIMIZEBOX; - SetWindowLong(hWnd, GWL_STYLE, style); - }).detach(); + std::thread([]() { + std::this_thread::sleep_for(std::chrono::seconds(3)); + + const auto currentProcessId = GetCurrentProcessId(); + HWND hWnd = nullptr; + HWND candidate = nullptr; + + while ((candidate = FindWindowExW( + nullptr, + candidate, + L"UnityWndClass", + nullptr + )) != nullptr) { + DWORD windowProcessId = 0; + GetWindowThreadProcessId( + candidate, + &windowProcessId + ); + + if (windowProcessId == currentProcessId) { + hWnd = candidate; + break; + } + } + + if (!hWnd) { + Log::Error( + "DMM unlock size failed: Unity window not found." + ); + return; + } + + SetLastError(ERROR_SUCCESS); + + auto style = GetWindowLongPtrW( + hWnd, + GWL_STYLE + ); + + if (style == 0 && + GetLastError() != ERROR_SUCCESS) { + Log::ErrorFmt( + "DMM unlock size failed: " + "GetWindowLongPtrW error=%lu", + GetLastError() + ); + return; + } + + style |= WS_THICKFRAME | + WS_MAXIMIZEBOX; + + SetLastError(ERROR_SUCCESS); + + const auto previousStyle = + SetWindowLongPtrW( + hWnd, + GWL_STYLE, + style + ); + + if (previousStyle == 0 && + GetLastError() != ERROR_SUCCESS) { + Log::ErrorFmt( + "DMM unlock size failed: " + "SetWindowLongPtrW error=%lu", + GetLastError() + ); + return; + } + + if (!SetWindowPos( + hWnd, + nullptr, + 0, + 0, + 0, + 0, + SWP_NOMOVE | + SWP_NOSIZE | + SWP_NOZORDER | + SWP_NOACTIVATE | + SWP_FRAMECHANGED + )) { + Log::ErrorFmt( + "DMM unlock size failed: " + "SetWindowPos error=%lu", + GetLastError() + ); + return; + } + + Log::Info( + "DMM window size unlocked." + ); + }).detach(); } g_extra_assetbundle_paths.push_back((gakumasLocalPath / "local-files/gakumasassets").string()); diff --git a/app/src/main/cpp/GakumasLocalify/HookTexture.cpp b/app/src/main/cpp/GakumasLocalify/HookTexture.cpp index 5d416dc..89aa2d3 100644 --- a/app/src/main/cpp/GakumasLocalify/HookTexture.cpp +++ b/app/src/main/cpp/GakumasLocalify/HookTexture.cpp @@ -20,6 +20,7 @@ namespace GakumasLocal::HookMain { using Il2cppString = UnityResolve::UnityType::String; + using Il2CppGCHandle = void*; extern void* (*Sprite_get_texture_Orig)(void* self); @@ -27,7 +28,7 @@ namespace GakumasLocal::HookMain Il2cppUtils::Il2CppClassHead* Texture2DClass = nullptr; Il2cppUtils::Il2CppClassHead* SpriteClass = nullptr; - std::unordered_map LoadedLocalTextureHandles{}; + std::unordered_map LoadedLocalTextureHandles{}; std::unordered_set AppliedLocalTextureKeys{}; Il2cppUtils::Il2CppClassHead* GetTexture2DClass() { @@ -73,9 +74,35 @@ namespace GakumasLocal::HookMain Il2cppString* GetObjectName(void* obj) { if (!obj) return nullptr; - static auto Object_GetName = reinterpret_cast( - Il2cppUtils::il2cpp_resolve_icall("UnityEngine.Object::GetName(UnityEngine.Object)")); - return Object_GetName ? Object_GetName(obj) : nullptr; + static const auto Object_get_name = + Il2cppUtils::GetMethod( + "UnityEngine.CoreModule.dll", + "UnityEngine", + "Object", + "get_name" + ); + + if (!Object_get_name || + !Object_get_name->function) { + Log::Error("Cannot find UnityEngine.Object.get_name"); + return nullptr; + } + + using ObjectGetNameFn = + Il2cppString* (*)( + void* self, + void* method + ); + + const auto getName = + reinterpret_cast( + Object_get_name->function + ); + + return getName( + obj, + Object_get_name->address + ); } void SetDontUnloadUnusedAsset(void* obj) { @@ -494,6 +521,57 @@ namespace GakumasLocal::HookMain } } + bool LoadImageFromByteArray( + void* texture, + void* byteArray, + bool markNonReadable + ) { + if (!texture || !byteArray) { + return false; + } + + static const auto LoadImageMethod = + Il2cppUtils::GetMethod( + "UnityEngine.ImageConversionModule.dll", + "UnityEngine", + "ImageConversion", + "LoadImage", + { + "UnityEngine.Texture2D", + "System.Byte[]", + "System.Boolean" + } + ); + + if (!LoadImageMethod || + !LoadImageMethod->function) { + Log::Error( + "Cannot find ImageConversion.LoadImage(Texture2D, Byte[], Boolean)" + ); + return false; + } + + using LoadImageFn = + bool (*)( + void* texture, + void* byteArray, + bool markNonReadable, + void* method + ); + + const auto loadImage = + reinterpret_cast( + LoadImageMethod->function + ); + + return loadImage( + texture, + byteArray, + markNonReadable, + LoadImageMethod->address + ); + } + void* LoadLocalTexture2D(const std::filesystem::path& path) { if (!std::filesystem::is_regular_file(path)) return nullptr; @@ -516,33 +594,14 @@ namespace GakumasLocal::HookMain const auto ctor = textureClass ? Il2cppUtils::il2cpp_class_get_method_from_name(textureClass, ".ctor", 2) : nullptr; return ctor ? reinterpret_cast(ctor->methodPointer) : nullptr; }(); - static auto ImageConversion_LoadImage = [] { - using LoadImageFn = bool (*)(void*, void*, bool); - if (const auto icall = Il2cppUtils::il2cpp_resolve_icall( - "UnityEngine.ImageConversion::LoadImage(UnityEngine.Texture2D,System.Byte[],System.Boolean)")) { - return reinterpret_cast(icall); - } - - for (const auto& assemblyName : {"UnityEngine.ImageConversionModule.dll", "UnityEngine.CoreModule.dll"}) { - const auto assembly = UnityResolve::Get(assemblyName); - const auto imageConversionClass = assembly ? assembly->Get("ImageConversion", "UnityEngine") : nullptr; - const auto method = imageConversionClass - ? Il2cppUtils::il2cpp_class_get_method_from_name(imageConversionClass->address, "LoadImage", 3) - : nullptr; - if (method) { - return reinterpret_cast(method->methodPointer); - } - } - return static_cast(nullptr); - }(); static auto File_ReadAllBytes = [] { const auto fileClass = Il2cppUtils::GetClass("mscorlib.dll", "System.IO", "File"); const auto method = fileClass ? Il2cppUtils::il2cpp_class_get_method_from_name(fileClass->address, "ReadAllBytes", 1) : nullptr; return method ? reinterpret_cast(method->methodPointer) : nullptr; }(); - if (!Texture2D_ctor || !ImageConversion_LoadImage || !File_ReadAllBytes) { - Log::Error("LoadLocalTexture2D failed: Unity Texture2D/ImageConversion/File API not found."); + if (!Texture2D_ctor || !File_ReadAllBytes) { + Log::Error("LoadLocalTexture2D failed: Unity Texture2D/File API not found."); return nullptr; } @@ -551,19 +610,28 @@ namespace GakumasLocal::HookMain const auto texture = UnityResolve::Invoke("il2cpp_object_new", textureClass); Texture2D_ctor(texture, 2, 2); - if (!ImageConversion_LoadImage(texture, fileBytes, false)) { + if (!LoadImageFromByteArray(texture, fileBytes, false)) { Log::ErrorFmt("LoadLocalTexture2D failed: %s", path.string().c_str()); return nullptr; } SetDontUnloadUnusedAsset(texture); - LoadedLocalTextureHandles.emplace(cacheKey, UnityResolve::Invoke("il2cpp_gchandle_new", texture, false)); + const auto textureHandle = UnityResolve::Invoke("il2cpp_gchandle_new", texture, false); + if (!textureHandle) { + Log::ErrorFmt("Cannot create texture GCHandle: %s", path.string().c_str()); + return nullptr; + } + LoadedLocalTextureHandles.emplace(cacheKey, textureHandle); Log::InfoFmt("Texture replaced from local file: %s", path.string().c_str()); return texture; } void* LoadLocalTexture2DFromCandidates(const std::vector& candidates) { for (const auto& candidate : candidates) { + if (!std::filesystem::is_regular_file(candidate)) { + continue; + } + if (auto texture = LoadLocalTexture2D(candidate)) { return texture; } @@ -578,40 +646,21 @@ namespace GakumasLocal::HookMain + "|" + std::to_string(reinterpret_cast(texture2D)); if (AppliedLocalTextureKeys.contains(cacheKey)) return true; - static auto ImageConversion_LoadImage = [] { - using LoadImageFn = bool (*)(void*, void*, bool); - if (const auto icall = Il2cppUtils::il2cpp_resolve_icall( - "UnityEngine.ImageConversion::LoadImage(UnityEngine.Texture2D,System.Byte[],System.Boolean)")) { - return reinterpret_cast(icall); - } - - for (const auto& assemblyName : {"UnityEngine.ImageConversionModule.dll", "UnityEngine.CoreModule.dll"}) { - const auto assembly = UnityResolve::Get(assemblyName); - const auto imageConversionClass = assembly ? assembly->Get("ImageConversion", "UnityEngine") : nullptr; - const auto method = imageConversionClass - ? Il2cppUtils::il2cpp_class_get_method_from_name(imageConversionClass->address, "LoadImage", 3) - : nullptr; - if (method) { - return reinterpret_cast(method->methodPointer); - } - } - return static_cast(nullptr); - }(); static auto File_ReadAllBytes = [] { const auto fileClass = Il2cppUtils::GetClass("mscorlib.dll", "System.IO", "File"); const auto method = fileClass ? Il2cppUtils::il2cpp_class_get_method_from_name(fileClass->address, "ReadAllBytes", 1) : nullptr; return method ? reinterpret_cast(method->methodPointer) : nullptr; }(); - if (!ImageConversion_LoadImage || !File_ReadAllBytes) { - Log::Error("ApplyLocalImageToTexture2D failed: Unity ImageConversion/File API not found."); + if (!File_ReadAllBytes) { + Log::Error("ApplyLocalImageToTexture2D failed: Unity File API not found."); return false; } const auto fileBytes = File_ReadAllBytes(Il2cppString::New(path.string())); if (!fileBytes) return false; - if (!ImageConversion_LoadImage(texture2D, fileBytes, false)) { + if (!LoadImageFromByteArray(texture2D, fileBytes, false)) { Log::ErrorFmt("ApplyLocalImageToTexture2D failed: %s", path.string().c_str()); return false; } @@ -736,6 +785,16 @@ namespace GakumasLocal::HookMain return texture2D; } + // Unity 6 compatibility warning: + // The resolver functions below currently mix two possible target ABIs: + // - il2cpp_resolve_icall(): native icall signature + // - GetMethodPointer(): managed IL2CPP methodPointer, normally with a + // trailing MethodInfo* argument + // + // The corresponding AssetBundle / Resources / Sprite hooks in Hook.cpp are + // intentionally disabled. They must not be restored until the resolver and + // hook declarations distinguish these ABI forms, or until the managed + // fallback is removed and a verified icall is required. void* ResolveSpriteGetTextureHookAddress() { if (const auto addr = Il2cppUtils::il2cpp_resolve_icall("UnityEngine.Sprite::get_texture(UnityEngine.Sprite)")) { return addr; diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 09d21dd..78b778b 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -3,6 +3,7 @@ Gakumas Localify 啟用插件 (不可熱重載) 替換字體 + 替換貼圖 快速初始化(懶人設定) 啟用自由視角(可熱重載; 需使用實體鍵盤) 以上述設定啟動遊戲/重載設定 diff --git a/app/src/main/res/values-zh-rMO/strings.xml b/app/src/main/res/values-zh-rMO/strings.xml index 09d21dd..78b778b 100644 --- a/app/src/main/res/values-zh-rMO/strings.xml +++ b/app/src/main/res/values-zh-rMO/strings.xml @@ -3,6 +3,7 @@ Gakumas Localify 啟用插件 (不可熱重載) 替換字體 + 替換貼圖 快速初始化(懶人設定) 啟用自由視角(可熱重載; 需使用實體鍵盤) 以上述設定啟動遊戲/重載設定 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 09d21dd..78b778b 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -3,6 +3,7 @@ Gakumas Localify 啟用插件 (不可熱重載) 替換字體 + 替換貼圖 快速初始化(懶人設定) 啟用自由視角(可熱重載; 需使用實體鍵盤) 以上述設定啟動遊戲/重載設定