Compatible with Unity 6

This commit is contained in:
chinosk
2026-06-13 15:26:08 +08:00
parent b39dc37801
commit 1af809c11e
6 changed files with 381 additions and 95 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ android {
minSdk 29 minSdk 29
targetSdk 34 targetSdk 34
versionCode 12 versionCode 12
versionName "v3.3.1" versionName "v3.4.0"
buildConfigField "String", "VERSION_NAME", "\"${versionName}\"" buildConfigField "String", "VERSION_NAME", "\"${versionName}\""
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+269 -45
View File
@@ -52,6 +52,7 @@ void UnHookAll() {
namespace GakumasLocal::HookMain { namespace GakumasLocal::HookMain {
using Il2cppString = UnityResolve::UnityType::String; using Il2cppString = UnityResolve::UnityType::String;
using Il2CppGCHandle = void*;
UnityResolve::UnityType::String* environment_get_stacktrace() { UnityResolve::UnityType::String* environment_get_stacktrace() {
/* /*
@@ -71,23 +72,46 @@ namespace GakumasLocal::HookMain {
return toString_mtd->Invoke<Il2cppString*>(klassInstance); return toString_mtd->Invoke<Il2cppString*>(klassInstance);
} }
DEFINE_HOOK(void, Internal_LogException, (void* ex, void* obj)) { // Unity 6: DebugLogHandler.Internal_Log* is resolved as a managed method
Internal_LogException_Orig(ex, obj); // 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"); static auto Exception_ToString = Il2cppUtils::GetMethod("mscorlib.dll", "System", "Exception", "ToString");
Log::LogUnityLog(ANDROID_LOG_ERROR, "UnityLog - Internal_LogException:\n%s", Exception_ToString->Invoke<Il2cppString*>(ex)->ToString().c_str()); Log::LogUnityLog(ANDROID_LOG_ERROR, "UnityLog - Internal_LogException:\n%s", Exception_ToString->Invoke<Il2cppString*>(ex)->ToString().c_str());
} }
DEFINE_HOOK(void, Internal_Log, (int logType, int logOption, UnityResolve::UnityType::String* content, void* context)) { DEFINE_HOOK(void, Internal_Log, (int logType, int logOption, UnityResolve::UnityType::String* content, void* context, void* mtd)) {
Internal_Log_Orig(logType, logOption, content, context); Internal_Log_Orig(logType, logOption, content, context, mtd);
// 2022.3.21f1
Log::LogUnityLog(ANDROID_LOG_VERBOSE, "Internal_Log:\n%s", content->ToString().c_str()); Log::LogUnityLog(ANDROID_LOG_VERBOSE, "Internal_Log:\n%s", content->ToString().c_str());
} }
bool IsNativeObjectAlive(void* obj) { bool IsNativeObjectAlive(void* obj) {
static UnityResolve::Method* IsNativeObjectAliveMtd = nullptr; if (!obj) {
if (!IsNativeObjectAliveMtd) IsNativeObjectAliveMtd = Il2cppUtils::GetMethod("UnityEngine.CoreModule.dll", "UnityEngine", return false;
"Object", "IsNativeObjectAlive"); }
return IsNativeObjectAliveMtd->Invoke<bool>(obj);
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<IsNativeObjectAliveFn>(
IsNativeObjectAliveMtd->function
);
return isNativeObjectAlive(
obj,
IsNativeObjectAliveMtd->address
);
} }
UnityResolve::UnityType::Camera* mainCameraCache = nullptr; UnityResolve::UnityType::Camera* mainCameraCache = nullptr;
@@ -343,7 +367,10 @@ namespace GakumasLocal::HookMain {
} }
DEFINE_HOOK(void, CanvasRenderer_SetTexture, (void* self, void* texture)) { 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)) { DEFINE_HOOK(void, SpriteRenderer_set_sprite, (void* self, void* sprite)) {
@@ -372,7 +399,6 @@ namespace GakumasLocal::HookMain {
} }
} }
#ifdef GKMS_WINDOWS #ifdef GKMS_WINDOWS
struct TransparentStringHash : std::hash<std::wstring>, std::hash<std::wstring_view> struct TransparentStringHash : std::hash<std::wstring>, std::hash<std::wstring_view>
{ {
@@ -381,7 +407,7 @@ namespace GakumasLocal::HookMain {
typedef std::unordered_set<std::wstring, TransparentStringHash, std::equal_to<void>> AssetPathsType; typedef std::unordered_set<std::wstring, TransparentStringHash, std::equal_to<void>> AssetPathsType;
std::map<std::string, AssetPathsType> CustomAssetBundleAssetPaths; std::map<std::string, AssetPathsType> CustomAssetBundleAssetPaths;
std::unordered_map<std::string, uint32_t> CustomAssetBundleHandleMap{}; std::unordered_map<std::string, Il2CppGCHandle> CustomAssetBundleHandleMap{};
std::list<std::string> g_extra_assetbundle_paths{}; std::list<std::string> g_extra_assetbundle_paths{};
void LoadExtraAssetBundle() { void LoadExtraAssetBundle() {
@@ -394,9 +420,12 @@ namespace GakumasLocal::HookMain {
// CustomAssetBundleAssetPaths.clear(); // CustomAssetBundleAssetPaths.clear();
// assert(!ExtraAssetBundleHandle && ExtraAssetBundleAssetPaths.empty()); // assert(!ExtraAssetBundleHandle && ExtraAssetBundleAssetPaths.empty());
static auto AssetBundle_GetAllAssetNames = reinterpret_cast<void* (*)(void*)>( static auto AssetBundle_GetAllAssetNames = Il2cppUtils::GetMethod(
Il2cppUtils::il2cpp_resolve_icall("UnityEngine.AssetBundle::GetAllAssetNames()") "UnityEngine.AssetBundleModule.dll",
); "UnityEngine",
"AssetBundle",
"GetAllAssetNames"
);
for (const auto& i : g_extra_assetbundle_paths) { for (const auto& i : g_extra_assetbundle_paths) {
if (CustomAssetBundleHandleMap.contains(i)) continue; if (CustomAssetBundleHandleMap.contains(i)) continue;
@@ -404,7 +433,7 @@ namespace GakumasLocal::HookMain {
const auto extraAssetBundle = WinHooks::LoadAssetBundle(i); const auto extraAssetBundle = WinHooks::LoadAssetBundle(i);
if (extraAssetBundle) if (extraAssetBundle)
{ {
const auto allAssetPaths = AssetBundle_GetAllAssetNames(extraAssetBundle); const auto allAssetPaths = AssetBundle_GetAllAssetNames->Invoke<void*>(extraAssetBundle);
AssetPathsType assetPath{}; AssetPathsType assetPath{};
Il2cppUtils::iterate_IEnumerable<Il2CppString*>(allAssetPaths, [&assetPath](Il2CppString* path) Il2cppUtils::iterate_IEnumerable<Il2CppString*>(allAssetPaths, [&assetPath](Il2CppString* path)
{ {
@@ -413,7 +442,8 @@ namespace GakumasLocal::HookMain {
assetPath.emplace(path->start_char); assetPath.emplace(path->start_char);
}); });
CustomAssetBundleAssetPaths.emplace(i, assetPath); CustomAssetBundleAssetPaths.emplace(i, assetPath);
CustomAssetBundleHandleMap.emplace(i, UnityResolve::Invoke<uint32_t>("il2cpp_gchandle_new", extraAssetBundle, false)); const auto bundleHandle = UnityResolve::Invoke<Il2CppGCHandle>("il2cpp_gchandle_new", extraAssetBundle, false);
CustomAssetBundleHandleMap.emplace(i, bundleHandle);
} }
else 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& i : CustomAssetBundleAssetPaths) {
for (const auto& m : i.second) { for (const auto& m : i.second) {
if (std::equal(m.begin(), m.end(), assetName.begin(), assetName.end(), 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)); return GetBundleHandleByAssetName(utility::conversions::to_string_t(assetName));
} }
uint32_t ReplaceFontHandle; Il2CppGCHandle ReplaceFontHandle = nullptr;
void* GetReplaceFont() { void* GetReplaceFont() {
static auto FontClass = Il2cppUtils::GetClass("UnityEngine.TextRenderingModule.dll", "UnityEngine", "Font"); static auto FontClass = Il2cppUtils::GetClass("UnityEngine.TextRenderingModule.dll", "UnityEngine", "Font");
@@ -451,7 +481,7 @@ namespace GakumasLocal::HookMain {
const auto fontPath = "assets/fonts/gkamszhfontmix.otf"; const auto fontPath = "assets/fonts/gkamszhfontmix.otf";
void* replaceFont{}; void* replaceFont{};
const auto& bundleHandle = GetBundleHandleByAssetName(fontPath); const auto bundleHandle = GetBundleHandleByAssetName(fontPath);
if (bundleHandle) if (bundleHandle)
{ {
if (ReplaceFontHandle) if (ReplaceFontHandle)
@@ -466,19 +496,23 @@ namespace GakumasLocal::HookMain {
} }
else else
{ {
UnityResolve::Invoke<void>("il2cpp_gchandle_free", std::exchange(ReplaceFontHandle, 0)); UnityResolve::Invoke<void>("il2cpp_gchandle_free", std::exchange(ReplaceFontHandle, nullptr));
} }
} }
const auto extraAssetBundle = UnityResolve::Invoke<void*>("il2cpp_gchandle_get_target", bundleHandle); const auto extraAssetBundle = UnityResolve::Invoke<void*>("il2cpp_gchandle_get_target", bundleHandle);
static auto AssetBundle_LoadAsset = reinterpret_cast<void* (*)(void* _this, Il2CppString* name, Il2cppUtils::Il2CppReflectionType* type)>( static auto AssetBundle_LoadAsset = Il2cppUtils::GetMethod(
Il2cppUtils::il2cpp_resolve_icall("UnityEngine.AssetBundle::LoadAsset_Internal(System.String,System.Type)") "UnityEngine.AssetBundleModule.dll",
);; "UnityEngine",
"AssetBundle",
"LoadAsset_Internal",
{ "System.String", "System.Type" }
);
replaceFont = AssetBundle_LoadAsset(extraAssetBundle, Il2cppString::New(fontPath), Font_Type); replaceFont = AssetBundle_LoadAsset->Invoke<void*>(extraAssetBundle, Il2cppString::New(fontPath), Font_Type);
if (replaceFont) if (replaceFont)
{ {
ReplaceFontHandle = UnityResolve::Invoke<uint32_t>("il2cpp_gchandle_new", replaceFont, false); ReplaceFontHandle = UnityResolve::Invoke<Il2CppGCHandle>("il2cpp_gchandle_new", replaceFont, false);
} }
else else
{ {
@@ -493,19 +527,72 @@ namespace GakumasLocal::HookMain {
} }
#else #else
void* fontCache = nullptr; 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<void (*)(void* self, Il2cppString* path)>(
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<CreateFontFromPathManagedFn>(
CreateFontFromPathMethod->function
);
createFontFromPath(font, fontPath, CreateFontFromPathMethod->address);
return true;
}
void* GetReplaceFont() { void* GetReplaceFont() {
static auto fontName = Local::GetBasePath() / "local-files" / "gkamsZHFontMIX.otf"; static auto fontName = Local::GetBasePath() / "local-files" / "gkamsZHFontMIX.otf";
if (!std::filesystem::exists(fontName)) { if (!std::filesystem::exists(fontName)) {
return nullptr; return nullptr;
} }
static auto CreateFontFromPath = reinterpret_cast<void (*)(void* self, Il2cppString* path)>(
Il2cppUtils::il2cpp_resolve_icall("UnityEngine.Font::Internal_CreateFontFromPath(UnityEngine.Font,System.String)")
);
static auto Font_klass = Il2cppUtils::GetClass("UnityEngine.TextRenderingModule.dll", static auto Font_klass = Il2cppUtils::GetClass("UnityEngine.TextRenderingModule.dll",
"UnityEngine", "Font"); "UnityEngine", "Font");
static auto Font_ctor = Il2cppUtils::GetMethod("UnityEngine.TextRenderingModule.dll", static auto Font_ctor = Il2cppUtils::GetMethod("UnityEngine.TextRenderingModule.dll",
"UnityEngine", "Font", ".ctor"); "UnityEngine", "Font", ".ctor");
if (!Font_klass || !Font_ctor) {
Log::Error("GetReplaceFont failed: Font class or constructor not found");
return nullptr;
}
if (fontCache) { if (fontCache) {
if (IsNativeObjectAlive(fontCache)) { if (IsNativeObjectAlive(fontCache)) {
return fontCache; return fontCache;
@@ -513,9 +600,16 @@ namespace GakumasLocal::HookMain {
} }
const auto newFont = Font_klass->New<void*>(); const auto newFont = Font_klass->New<void*>();
if (!newFont) {
Log::Error("GetReplaceFont failed: cannot create Font instance");
return nullptr;
}
Font_ctor->Invoke<void>(newFont); Font_ctor->Invoke<void>(newFont);
CreateFontFromPath(newFont, Il2cppString::New(fontName.string())); if (!CreateFontFromPath(newFont, fontName)) {
return nullptr;
}
fontCache = newFont; fontCache = newFont;
return newFont; return newFont;
} }
@@ -1753,7 +1847,18 @@ namespace GakumasLocal::HookMain {
UnityResolve::Mode::Il2Cpp, Config::lazyInit); UnityResolve::Mode::Il2Cpp, Config::lazyInit);
#endif #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_LoadAsset, ResolveAssetBundleLoadAssetHookAddress());
// ADD_HOOK(AssetBundle_LoadAssetAsync, ResolveAssetBundleLoadAssetAsyncHookAddress()); // ADD_HOOK(AssetBundle_LoadAssetAsync, ResolveAssetBundleLoadAssetAsyncHookAddress());
// ADD_HOOK(AssetBundleRequest_GetResult, ResolveAssetBundleRequestResultHookAddress()); // ADD_HOOK(AssetBundleRequest_GetResult, ResolveAssetBundleRequestResultHookAddress());
@@ -1992,10 +2097,41 @@ namespace GakumasLocal::HookMain {
Il2cppUtils::GetMethodPointer("campus-submodule.Runtime.dll", "Campus.Common", Il2cppUtils::GetMethodPointer("campus-submodule.Runtime.dll", "Campus.Common",
"CampusQualityManager", "set_TargetFrameRate")); "CampusQualityManager", "set_TargetFrameRate"));
ADD_HOOK(Internal_LogException, Il2cppUtils::il2cpp_resolve_icall( // Unity 6 no longer exposes these two methods through the old icall
"UnityEngine.DebugLogHandler::Internal_LogException(System.Exception,UnityEngine.Object)")); // names in this player. Resolve their managed IL2CPP methodPointer
ADD_HOOK(Internal_Log, Il2cppUtils::il2cpp_resolve_icall( // through GetMethod() and use hook signatures with a trailing MethodInfo*.
"UnityEngine.DebugLogHandler::Internal_Log(UnityEngine.LogType,UnityEngine.LogOption,System.String,UnityEngine.Object)")); 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, ADD_HOOK(InternalSetOrientationAsync,
@@ -2032,14 +2168,102 @@ namespace GakumasLocal::HookMain {
// "AspectRatioHandler", "WindowProc")); // "AspectRatioHandler", "WindowProc"));
if (GakumasLocal::Config::dmmUnlockSize) { if (GakumasLocal::Config::dmmUnlockSize) {
std::thread([]() { std::thread([]() {
std::this_thread::sleep_for(std::chrono::seconds(3)); std::this_thread::sleep_for(std::chrono::seconds(3));
auto hWnd = FindWindowW(L"UnityWndClass", L"gakumas");
// 添加可调整大小的边框和最大化按钮 const auto currentProcessId = GetCurrentProcessId();
LONG style = GetWindowLong(hWnd, GWL_STYLE); HWND hWnd = nullptr;
style |= WS_THICKFRAME | WS_MAXIMIZEBOX; HWND candidate = nullptr;
SetWindowLong(hWnd, GWL_STYLE, style);
}).detach(); 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()); g_extra_assetbundle_paths.push_back((gakumasLocalPath / "local-files/gakumasassets").string());
+108 -49
View File
@@ -20,6 +20,7 @@
namespace GakumasLocal::HookMain namespace GakumasLocal::HookMain
{ {
using Il2cppString = UnityResolve::UnityType::String; using Il2cppString = UnityResolve::UnityType::String;
using Il2CppGCHandle = void*;
extern void* (*Sprite_get_texture_Orig)(void* self); extern void* (*Sprite_get_texture_Orig)(void* self);
@@ -27,7 +28,7 @@ namespace GakumasLocal::HookMain
Il2cppUtils::Il2CppClassHead* Texture2DClass = nullptr; Il2cppUtils::Il2CppClassHead* Texture2DClass = nullptr;
Il2cppUtils::Il2CppClassHead* SpriteClass = nullptr; Il2cppUtils::Il2CppClassHead* SpriteClass = nullptr;
std::unordered_map<std::string, uint32_t> LoadedLocalTextureHandles{}; std::unordered_map<std::string, Il2CppGCHandle> LoadedLocalTextureHandles{};
std::unordered_set<std::string> AppliedLocalTextureKeys{}; std::unordered_set<std::string> AppliedLocalTextureKeys{};
Il2cppUtils::Il2CppClassHead* GetTexture2DClass() { Il2cppUtils::Il2CppClassHead* GetTexture2DClass() {
@@ -73,9 +74,35 @@ namespace GakumasLocal::HookMain
Il2cppString* GetObjectName(void* obj) { Il2cppString* GetObjectName(void* obj) {
if (!obj) return nullptr; if (!obj) return nullptr;
static auto Object_GetName = reinterpret_cast<Il2cppString * (*)(void*)>( static const auto Object_get_name =
Il2cppUtils::il2cpp_resolve_icall("UnityEngine.Object::GetName(UnityEngine.Object)")); Il2cppUtils::GetMethod(
return Object_GetName ? Object_GetName(obj) : nullptr; "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<ObjectGetNameFn>(
Object_get_name->function
);
return getName(
obj,
Object_get_name->address
);
} }
void SetDontUnloadUnusedAsset(void* obj) { 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<LoadImageFn>(
LoadImageMethod->function
);
return loadImage(
texture,
byteArray,
markNonReadable,
LoadImageMethod->address
);
}
void* LoadLocalTexture2D(const std::filesystem::path& path) { void* LoadLocalTexture2D(const std::filesystem::path& path) {
if (!std::filesystem::is_regular_file(path)) return nullptr; 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; const auto ctor = textureClass ? Il2cppUtils::il2cpp_class_get_method_from_name(textureClass, ".ctor", 2) : nullptr;
return ctor ? reinterpret_cast<void (*)(void*, int, int)>(ctor->methodPointer) : nullptr; return ctor ? reinterpret_cast<void (*)(void*, int, int)>(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<LoadImageFn>(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<LoadImageFn>(method->methodPointer);
}
}
return static_cast<LoadImageFn>(nullptr);
}();
static auto File_ReadAllBytes = [] { static auto File_ReadAllBytes = [] {
const auto fileClass = Il2cppUtils::GetClass("mscorlib.dll", "System.IO", "File"); 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; const auto method = fileClass ? Il2cppUtils::il2cpp_class_get_method_from_name(fileClass->address, "ReadAllBytes", 1) : nullptr;
return method ? reinterpret_cast<void* (*)(Il2cppString*)>(method->methodPointer) : nullptr; return method ? reinterpret_cast<void* (*)(Il2cppString*)>(method->methodPointer) : nullptr;
}(); }();
if (!Texture2D_ctor || !ImageConversion_LoadImage || !File_ReadAllBytes) { if (!Texture2D_ctor || !File_ReadAllBytes) {
Log::Error("LoadLocalTexture2D failed: Unity Texture2D/ImageConversion/File API not found."); Log::Error("LoadLocalTexture2D failed: Unity Texture2D/File API not found.");
return nullptr; return nullptr;
} }
@@ -551,19 +610,28 @@ namespace GakumasLocal::HookMain
const auto texture = UnityResolve::Invoke<void*>("il2cpp_object_new", textureClass); const auto texture = UnityResolve::Invoke<void*>("il2cpp_object_new", textureClass);
Texture2D_ctor(texture, 2, 2); 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()); Log::ErrorFmt("LoadLocalTexture2D failed: %s", path.string().c_str());
return nullptr; return nullptr;
} }
SetDontUnloadUnusedAsset(texture); SetDontUnloadUnusedAsset(texture);
LoadedLocalTextureHandles.emplace(cacheKey, UnityResolve::Invoke<uint32_t>("il2cpp_gchandle_new", texture, false)); const auto textureHandle = UnityResolve::Invoke<Il2CppGCHandle>("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()); Log::InfoFmt("Texture replaced from local file: %s", path.string().c_str());
return texture; return texture;
} }
void* LoadLocalTexture2DFromCandidates(const std::vector<std::filesystem::path>& candidates) { void* LoadLocalTexture2DFromCandidates(const std::vector<std::filesystem::path>& candidates) {
for (const auto& candidate : candidates) { for (const auto& candidate : candidates) {
if (!std::filesystem::is_regular_file(candidate)) {
continue;
}
if (auto texture = LoadLocalTexture2D(candidate)) { if (auto texture = LoadLocalTexture2D(candidate)) {
return texture; return texture;
} }
@@ -578,40 +646,21 @@ namespace GakumasLocal::HookMain
+ "|" + std::to_string(reinterpret_cast<std::uintptr_t>(texture2D)); + "|" + std::to_string(reinterpret_cast<std::uintptr_t>(texture2D));
if (AppliedLocalTextureKeys.contains(cacheKey)) return true; 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<LoadImageFn>(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<LoadImageFn>(method->methodPointer);
}
}
return static_cast<LoadImageFn>(nullptr);
}();
static auto File_ReadAllBytes = [] { static auto File_ReadAllBytes = [] {
const auto fileClass = Il2cppUtils::GetClass("mscorlib.dll", "System.IO", "File"); 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; const auto method = fileClass ? Il2cppUtils::il2cpp_class_get_method_from_name(fileClass->address, "ReadAllBytes", 1) : nullptr;
return method ? reinterpret_cast<void* (*)(Il2cppString*)>(method->methodPointer) : nullptr; return method ? reinterpret_cast<void* (*)(Il2cppString*)>(method->methodPointer) : nullptr;
}(); }();
if (!ImageConversion_LoadImage || !File_ReadAllBytes) { if (!File_ReadAllBytes) {
Log::Error("ApplyLocalImageToTexture2D failed: Unity ImageConversion/File API not found."); Log::Error("ApplyLocalImageToTexture2D failed: Unity File API not found.");
return false; return false;
} }
const auto fileBytes = File_ReadAllBytes(Il2cppString::New(path.string())); const auto fileBytes = File_ReadAllBytes(Il2cppString::New(path.string()));
if (!fileBytes) return false; if (!fileBytes) return false;
if (!ImageConversion_LoadImage(texture2D, fileBytes, false)) { if (!LoadImageFromByteArray(texture2D, fileBytes, false)) {
Log::ErrorFmt("ApplyLocalImageToTexture2D failed: %s", path.string().c_str()); Log::ErrorFmt("ApplyLocalImageToTexture2D failed: %s", path.string().c_str());
return false; return false;
} }
@@ -736,6 +785,16 @@ namespace GakumasLocal::HookMain
return texture2D; 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() { void* ResolveSpriteGetTextureHookAddress() {
if (const auto addr = Il2cppUtils::il2cpp_resolve_icall("UnityEngine.Sprite::get_texture(UnityEngine.Sprite)")) { if (const auto addr = Il2cppUtils::il2cpp_resolve_icall("UnityEngine.Sprite::get_texture(UnityEngine.Sprite)")) {
return addr; return addr;
@@ -3,6 +3,7 @@
<string name="gakumas_localify">Gakumas Localify</string> <string name="gakumas_localify">Gakumas Localify</string>
<string name="enable_plugin">啟用插件 (不可熱重載)</string> <string name="enable_plugin">啟用插件 (不可熱重載)</string>
<string name="replace_font">替換字體</string> <string name="replace_font">替換字體</string>
<string name="replace_texture">替換貼圖</string>
<string name="lazy_init">快速初始化(懶人設定)</string> <string name="lazy_init">快速初始化(懶人設定)</string>
<string name="enable_free_camera">啟用自由視角(可熱重載; 需使用實體鍵盤)</string> <string name="enable_free_camera">啟用自由視角(可熱重載; 需使用實體鍵盤)</string>
<string name="start_game">以上述設定啟動遊戲/重載設定</string> <string name="start_game">以上述設定啟動遊戲/重載設定</string>
@@ -3,6 +3,7 @@
<string name="gakumas_localify">Gakumas Localify</string> <string name="gakumas_localify">Gakumas Localify</string>
<string name="enable_plugin">啟用插件 (不可熱重載)</string> <string name="enable_plugin">啟用插件 (不可熱重載)</string>
<string name="replace_font">替換字體</string> <string name="replace_font">替換字體</string>
<string name="replace_texture">替換貼圖</string>
<string name="lazy_init">快速初始化(懶人設定)</string> <string name="lazy_init">快速初始化(懶人設定)</string>
<string name="enable_free_camera">啟用自由視角(可熱重載; 需使用實體鍵盤)</string> <string name="enable_free_camera">啟用自由視角(可熱重載; 需使用實體鍵盤)</string>
<string name="start_game">以上述設定啟動遊戲/重載設定</string> <string name="start_game">以上述設定啟動遊戲/重載設定</string>
@@ -3,6 +3,7 @@
<string name="gakumas_localify">Gakumas Localify</string> <string name="gakumas_localify">Gakumas Localify</string>
<string name="enable_plugin">啟用插件 (不可熱重載)</string> <string name="enable_plugin">啟用插件 (不可熱重載)</string>
<string name="replace_font">替換字體</string> <string name="replace_font">替換字體</string>
<string name="replace_texture">替換貼圖</string>
<string name="lazy_init">快速初始化(懶人設定)</string> <string name="lazy_init">快速初始化(懶人設定)</string>
<string name="enable_free_camera">啟用自由視角(可熱重載; 需使用實體鍵盤)</string> <string name="enable_free_camera">啟用自由視角(可熱重載; 需使用實體鍵盤)</string>
<string name="start_game">以上述設定啟動遊戲/重載設定</string> <string name="start_game">以上述設定啟動遊戲/重載設定</string>