diff --git a/app/build.gradle b/app/build.gradle index bf5bdb6..7f906c9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,8 +15,8 @@ android { applicationId "io.github.chinosk.gakumas.localify" minSdk 29 targetSdk 34 - versionCode 11 - versionName "v2.1.0" + versionCode 12 + versionName "v3.0.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 52fe7d6..8140e9c 100644 --- a/app/src/main/cpp/GakumasLocalify/Hook.cpp +++ b/app/src/main/cpp/GakumasLocalify/Hook.cpp @@ -1,4 +1,3 @@ -#include #include "Hook.h" #include "Plugin.h" #include "Log.h" @@ -9,14 +8,20 @@ #include #include "camera/camera.hpp" #include "config/Config.hpp" -#include "shadowhook.h" -#include +// #include #include #include #include +#include "../platformDefine.hpp" + +#ifdef GKMS_WINDOWS + #include "../windowsPlatform.hpp" + #include "cpprest/details/http_helpers.h" +#endif std::unordered_set hookedStubs{}; +extern std::filesystem::path gakumasLocalPath; #define DEFINE_HOOK(returnType, name, params) \ using name##_Type = returnType(*) params; \ @@ -24,26 +29,7 @@ std::unordered_set hookedStubs{}; name##_Type name##_Orig = nullptr; \ returnType name##_Hook params - -#define ADD_HOOK(name, addr) \ - name##_Addr = reinterpret_cast(addr); \ - if (addr) { \ - auto stub = hookInstaller->InstallHook(reinterpret_cast(addr), \ - reinterpret_cast(name##_Hook), \ - reinterpret_cast(&name##_Orig)); \ - if (stub == NULL) { \ - int error_num = shadowhook_get_errno(); \ - const char *error_msg = shadowhook_to_errmsg(error_num); \ - Log::ErrorFmt("ADD_HOOK: %s at %p failed: %s", #name, addr, error_msg); \ - } \ - else { \ - hookedStubs.emplace(stub); \ - GakumasLocal::Log::InfoFmt("ADD_HOOK: %s at %p", #name, addr); \ - } \ - } \ - else GakumasLocal::Log::ErrorFmt("Hook failed: %s is NULL", #name, addr); \ - if (Config::lazyInit) UnityResolveProgress::classProgress.current++ - +/* void UnHookAll() { for (const auto i: hookedStubs) { int result = shadowhook_unhook(i); @@ -54,7 +40,7 @@ void UnHookAll() { GakumasLocal::Log::ErrorFmt("unhook failed: %d - %s", error_num, error_msg); } } -} +}*/ namespace GakumasLocal::HookMain { using Il2cppString = UnityResolve::UnityType::String; @@ -300,9 +286,129 @@ namespace GakumasLocal::HookMain { } } + +#ifdef GKMS_WINDOWS + struct TransparentStringHash : std::hash, std::hash + { + using is_transparent = void; + }; + + typedef std::unordered_set> AssetPathsType; + std::map CustomAssetBundleAssetPaths; + std::unordered_map CustomAssetBundleHandleMap{}; + std::list g_extra_assetbundle_paths{}; + + void LoadExtraAssetBundle() { + using Il2CppString = UnityResolve::UnityType::String; + + if (g_extra_assetbundle_paths.empty()) { + return; + } + // CustomAssetBundleHandleMap.clear(); + // CustomAssetBundleAssetPaths.clear(); + // assert(!ExtraAssetBundleHandle && ExtraAssetBundleAssetPaths.empty()); + + static auto AssetBundle_GetAllAssetNames = reinterpret_cast( + Il2cppUtils::il2cpp_resolve_icall("UnityEngine.AssetBundle::GetAllAssetNames()") + ); + + for (const auto& i : g_extra_assetbundle_paths) { + if (CustomAssetBundleHandleMap.contains(i)) continue; + + const auto extraAssetBundle = WinHooks::LoadAssetBundle(i); + if (extraAssetBundle) + { + const auto allAssetPaths = AssetBundle_GetAllAssetNames(extraAssetBundle); + AssetPathsType assetPath{}; + Il2cppUtils::iterate_IEnumerable(allAssetPaths, [&assetPath](Il2CppString* path) + { + // ExtraAssetBundleAssetPaths.emplace(path->start_char); + // printf("Asset loaded: %ls\n", path->start_char); + assetPath.emplace(path->start_char); + }); + CustomAssetBundleAssetPaths.emplace(i, assetPath); + CustomAssetBundleHandleMap.emplace(i, UnityResolve::Invoke("il2cpp_gchandle_new", extraAssetBundle, false)); + } + else + { + Log::ErrorFmt("Cannot load asset bundle: %s\n", i.c_str()); + } + } + } + + uint32_t 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(), + [](wchar_t c1, wchar_t c2) { + return std::tolower(c1, std::locale()) == std::tolower(c2, std::locale()); + })) { + return CustomAssetBundleHandleMap.at(i.first); + } + } + } + return NULL; + } + + uint32_t GetBundleHandleByAssetName(std::string assetName) { + return GetBundleHandleByAssetName(utility::conversions::to_string_t(assetName)); + } + + uint32_t ReplaceFontHandle; + + void* GetReplaceFont() { + static auto FontClass = Il2cppUtils::GetClass("UnityEngine.TextRenderingModule.dll", "UnityEngine", "Font"); + static auto Font_Type = UnityResolve::Invoke("il2cpp_type_get_object", + UnityResolve::Invoke("il2cpp_class_get_type", FontClass->address)); + + using Il2CppString = UnityResolve::UnityType::String; + const auto fontPath = "assets/fonts/gkamszhfontmix.otf"; + + void* replaceFont{}; + const auto& bundleHandle = GetBundleHandleByAssetName(fontPath); + if (bundleHandle) + { + if (ReplaceFontHandle) + { + replaceFont = UnityResolve::Invoke("il2cpp_gchandle_get_target", ReplaceFontHandle); + // 加载场景时会被 Resources.UnloadUnusedAssets 干掉,且不受 DontDestroyOnLoad 影响,暂且判断是否存活,并在必要的时候重新加载 + // TODO: 考虑挂载到 GameObject 上 + // AssetBundle 不会被干掉 + if (IsNativeObjectAlive(replaceFont)) + { + return replaceFont; + } + else + { + UnityResolve::Invoke("il2cpp_gchandle_free", std::exchange(ReplaceFontHandle, 0)); + } + } + + 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)") + );; + + replaceFont = AssetBundle_LoadAsset(extraAssetBundle, Il2cppString::New(fontPath), Font_Type); + if (replaceFont) + { + ReplaceFontHandle = UnityResolve::Invoke("il2cpp_gchandle_new", replaceFont, false); + } + else + { + Log::Error("Cannot load asset font\n"); + } + } + else + { + Log::Error("Cannot find asset font\n"); + } + return replaceFont; + } +#else void* fontCache = nullptr; void* GetReplaceFont() { - static std::string fontName = Local::GetBasePath() / "local-files" / "gkamsZHFontMIX.otf"; + static auto fontName = Local::GetBasePath() / "local-files" / "gkamsZHFontMIX.otf"; if (!std::filesystem::exists(fontName)) { return nullptr; } @@ -323,10 +429,11 @@ namespace GakumasLocal::HookMain { const auto newFont = Font_klass->New(); Font_ctor->Invoke(newFont); - CreateFontFromPath(newFont, Il2cppString::New(fontName)); + CreateFontFromPath(newFont, Il2cppString::New(fontName.string())); fontCache = newFont; return newFont; } +#endif std::unordered_set updatedFontPtrs{}; void UpdateFont(void* TMP_Textself) { @@ -350,6 +457,7 @@ namespace GakumasLocal::HookMain { auto newFont = GetReplaceFont(); if (!newFont) return; + auto fontAsset = get_font->Invoke(TMP_Textself); if (fontAsset) { set_sourceFontFile->Invoke(fontAsset, newFont); @@ -359,6 +467,9 @@ namespace GakumasLocal::HookMain { } if (updatedFontPtrs.size() > 200) updatedFontPtrs.clear(); } + else { + Log::Error("UpdateFont: fontAsset is null."); + } set_font->Invoke(TMP_Textself, fontAsset); // auto fontMaterial = get_material->Invoke(fontAsset); @@ -1264,8 +1375,17 @@ namespace GakumasLocal::HookMain { void StartInjectFunctions() { const auto hookInstaller = Plugin::GetInstance().GetHookInstaller(); + +#ifdef GKMS_WINDOWS + auto il2cpp_module = GetModuleHandle("GameAssembly.dll"); + if (!il2cpp_module) { + Log::ErrorFmt("GameAssembly.dll not loaded."); + } + UnityResolve::Init(il2cpp_module, UnityResolve::Mode::Il2Cpp, Config::lazyInit); +#else UnityResolve::Init(xdl_open(hookInstaller->m_il2cppLibraryPath.c_str(), RTLD_NOW), - UnityResolve::Mode::Il2Cpp, Config::lazyInit); + UnityResolve::Mode::Il2Cpp, Config::lazyInit); +#endif ADD_HOOK(AssetBundle_LoadAssetAsync, Il2cppUtils::il2cpp_resolve_icall( "UnityEngine.AssetBundle::LoadAssetAsync_Internal(System.String,System.Type)")); @@ -1471,11 +1591,21 @@ namespace GakumasLocal::HookMain { "UnityEngine.Application::set_targetFrameRate(System.Int32)")); ADD_HOOK(EndCameraRendering, Il2cppUtils::GetMethodPointer("UnityEngine.CoreModule.dll", "UnityEngine.Rendering", "RenderPipeline", "EndCameraRendering")); + +#ifdef GKMS_WINDOWS + g_extra_assetbundle_paths.push_back((gakumasLocalPath / "local-files/gakumasassets").string()); + LoadExtraAssetBundle(); +#endif // GKMS_WINDOWS + } // 77 2640 5000 DEFINE_HOOK(int, il2cpp_init, (const char* domain_name)) { +#ifndef GKMS_WINDOWS const auto ret = il2cpp_init_Orig(domain_name); +#else + const auto ret = 0; +#endif // InjectFunctions(); Log::Info("Waiting for config..."); @@ -1524,8 +1654,13 @@ namespace GakumasLocal::Hook { Log::Info("Installing hook"); +#ifndef GKMS_WINDOWS ADD_HOOK(HookMain::il2cpp_init, - Plugin::GetInstance().GetHookInstaller()->LookupSymbol("il2cpp_init")); + Plugin::GetInstance().GetHookInstaller()->LookupSymbol("il2cpp_init")); +#else + HookMain::il2cpp_init_Hook(nullptr); +#endif + Log::Info("Hook installed"); } diff --git a/app/src/main/cpp/GakumasLocalify/Il2cppUtils.hpp b/app/src/main/cpp/GakumasLocalify/Il2cppUtils.hpp index aa29404..07f6322 100644 --- a/app/src/main/cpp/GakumasLocalify/Il2cppUtils.hpp +++ b/app/src/main/cpp/GakumasLocalify/Il2cppUtils.hpp @@ -308,6 +308,22 @@ namespace Il2cppUtils { return ret; } + template + static void iterate_IEnumerable(const void* obj, std::invocable auto&& receiver) + { + const auto klass = get_class_from_instance(obj); + const auto getEnumeratorMethod = reinterpret_cast(il2cpp_class_get_method_from_name(klass, "GetEnumerator", 0)->methodPointer); + const auto enumerator = getEnumeratorMethod(obj); + const auto enumeratorClass = get_class_from_instance(enumerator); + const auto getCurrentMethod = reinterpret_cast(il2cpp_class_get_method_from_name(enumeratorClass, "get_Current", 0)->methodPointer); + const auto moveNextMethod = reinterpret_cast(il2cpp_class_get_method_from_name(enumeratorClass, "MoveNext", 0)->methodPointer); + + while (moveNextMethod(enumerator)) + { + static_cast(receiver)(getCurrentMethod(enumerator)); + } + } + namespace Tools { template diff --git a/app/src/main/cpp/GakumasLocalify/Local.cpp b/app/src/main/cpp/GakumasLocalify/Local.cpp index 562f397..7a4f0b9 100644 --- a/app/src/main/cpp/GakumasLocalify/Local.cpp +++ b/app/src/main/cpp/GakumasLocalify/Local.cpp @@ -90,7 +90,7 @@ namespace GakumasLocal::Local { } std::ifstream file(filePath); if (!file.is_open()) { - Log::ErrorFmt("Load %s failed.\n", filePath.c_str()); + Log::ErrorFmt("Load %s failed.\n", filePath.string().c_str()); return; } std::string fileContent((std::istreambuf_iterator(file)), std::istreambuf_iterator()); @@ -112,7 +112,7 @@ namespace GakumasLocal::Local { } } catch (std::exception& e) { - Log::ErrorFmt("Load %s failed: %s\n", filePath.c_str(), e.what()); + Log::ErrorFmt("Load %s failed: %s\n", filePath.string().c_str(), e.what()); } } @@ -447,7 +447,7 @@ namespace GakumasLocal::Local { const auto targetFilePath = basePath / "local-files" / "resource" / name; // Log::DebugFmt("GetResourceText: %s", targetFilePath.c_str()); if (exists(targetFilePath)) { - auto readStr = readFileToString(targetFilePath); + auto readStr = readFileToString(targetFilePath.string()); *ret = readStr; return true; } diff --git a/app/src/main/cpp/GakumasLocalify/Log.cpp b/app/src/main/cpp/GakumasLocalify/Log.cpp index c857f36..4d984d8 100644 --- a/app/src/main/cpp/GakumasLocalify/Log.cpp +++ b/app/src/main/cpp/GakumasLocalify/Log.cpp @@ -1,14 +1,19 @@ #include "Log.h" -#include -#include +#include "Misc.hpp" #include #include #include #include +#include + +#ifndef GKMS_WINDOWS + #include + + extern JavaVM* g_javaVM; + extern jclass g_gakumasHookMainClass; + extern jmethodID showToastMethodId; +#endif // GKMS_WINDOWS -extern JavaVM* g_javaVM; -extern jclass g_gakumasHookMainClass; -extern jmethodID showToastMethodId; #define GetParamStringResult(name)\ va_list args;\ @@ -75,6 +80,7 @@ namespace GakumasLocal::Log { __android_log_write(prio, "GakumasLog", result.c_str()); } + /* void ShowToastJNI(const char* text) { DebugFmt("Toast: %s", text); @@ -99,15 +105,19 @@ namespace GakumasLocal::Log { g_javaVM->DetachCurrentThread(); }).detach(); - } + }*/ void ShowToast(const std::string& text) { +#ifndef GKMS_WINDOWS showingToasts.push(text); +#else + InfoFmt("Toast: %s", text.c_str()); +#endif } void ShowToast(const char* text) { - DebugFmt("Toast: %s", text); + // DebugFmt("Toast: %s", text); return ShowToast(std::string(text)); } @@ -125,6 +135,7 @@ namespace GakumasLocal::Log { return ret; } +#ifndef GKMS_WINDOWS void ToastLoop(JNIEnv *env, jclass clazz) { const auto toastString = GetQueuedToast(); if (toastString.empty()) return; @@ -140,4 +151,6 @@ namespace GakumasLocal::Log { _showToastMethodId = env->GetStaticMethodID(clazz, "showToast", "(Ljava/lang/String;)V"); } } +#endif + } diff --git a/app/src/main/cpp/GakumasLocalify/Log.h b/app/src/main/cpp/GakumasLocalify/Log.h index d01c2eb..f0d7927 100644 --- a/app/src/main/cpp/GakumasLocalify/Log.h +++ b/app/src/main/cpp/GakumasLocalify/Log.h @@ -1,8 +1,14 @@ #ifndef GAKUMAS_LOCALIFY_LOG_H #define GAKUMAS_LOCALIFY_LOG_H +#include "../platformDefine.hpp" + #include -#include + +#ifndef GKMS_WINDOWS + #include +#endif + namespace GakumasLocal::Log { std::string StringFormat(const char* fmt, ...); @@ -18,7 +24,9 @@ namespace GakumasLocal::Log { void ShowToast(const char* text); void ShowToastFmt(const char* fmt, ...); +#ifndef GKMS_WINDOWS void ToastLoop(JNIEnv *env, jclass clazz); +#endif } #endif //GAKUMAS_LOCALIFY_LOG_H diff --git a/app/src/main/cpp/GakumasLocalify/MasterLocal.cpp b/app/src/main/cpp/GakumasLocalify/MasterLocal.cpp index dc793cd..b1636dc 100644 --- a/app/src/main/cpp/GakumasLocalify/MasterLocal.cpp +++ b/app/src/main/cpp/GakumasLocalify/MasterLocal.cpp @@ -541,7 +541,8 @@ namespace GakumasLocal::MasterLocal { if (isFailed) break; } if (!isFailed) break; - NextLoop: + NextLoop: + ; } if (isFailed) return false; diff --git a/app/src/main/cpp/GakumasLocalify/MasterLocal_Legacy.cpp b/app/src/main/cpp/GakumasLocalify/MasterLocal_Legacy.cpp deleted file mode 100644 index b795e1a..0000000 --- a/app/src/main/cpp/GakumasLocalify/MasterLocal_Legacy.cpp +++ /dev/null @@ -1,786 +0,0 @@ -#include "MasterLocal.h" -#include "Local.h" -#include "Il2cppUtils.hpp" -#include "config/Config.hpp" -#include -#include -#include -#include -#include -#include -#include -#include - -namespace GakumasLocal::MasterLocal { - using Il2cppString = UnityResolve::UnityType::String; - - enum class JsonValueType { - JVT_String, - JVT_Int, - JVT_Object, - JVT_ArrayObject, - }; - - struct PKItem { - std::string topLevel; - std::string subField; - JsonValueType topLevelType; - JsonValueType subFieldType; - }; - - struct TableInfo { - std::vector pkItems; - std::unordered_map dataMap; - }; - - static std::unordered_map g_loadedData; - static std::unordered_map fieldSetCache; - static std::unordered_map fieldGetCache; - - class FieldController { - void* self; - std::string self_klass_name; - - static std::string capitalizeFirstLetter(const std::string& input) { - if (input.empty()) return input; - std::string result = input; - result[0] = static_cast(std::toupper(result[0])); - return result; - } - - Il2cppUtils::MethodInfo* GetGetSetMethodFromCache(const std::string& fieldName, int argsCount, - std::unordered_map& fromCache, const std::string& prefix = "set_") { - const std::string methodName = prefix + capitalizeFirstLetter(fieldName); - const std::string searchName = self_klass_name + "." + methodName; - - if (auto it = fromCache.find(searchName); it != fromCache.end()) { - return it->second; - } - auto set_mtd = Il2cppUtils::il2cpp_class_get_method_from_name( - self_klass, - methodName.c_str(), - argsCount - ); - fromCache.emplace(searchName, set_mtd); - return set_mtd; - } - - public: - Il2cppUtils::Il2CppClassHead* self_klass; - - explicit FieldController(void* from) { - self = from; - self_klass = Il2cppUtils::get_class_from_instance(self); - if (self_klass) { - self_klass_name = self_klass->name; - } - } - - template - T ReadField(const std::string& fieldName) { - auto get_mtd = GetGetSetMethodFromCache(fieldName, 0, fieldGetCache, "get_"); - if (get_mtd) { - return reinterpret_cast(get_mtd->methodPointer)(self, get_mtd); - } - - auto field = UnityResolve::Invoke( - "il2cpp_class_get_field_from_name", - self_klass, - (fieldName + '_').c_str() - ); - if (!field) { - return T(); - } - return Il2cppUtils::ClassGetFieldValue(self, field); - } - - template - void SetField(const std::string& fieldName, T value) { - auto set_mtd = GetGetSetMethodFromCache(fieldName, 1, fieldSetCache, "set_"); - if (set_mtd) { - reinterpret_cast( - set_mtd->methodPointer - )(self, value, set_mtd); - return; - } - auto field = UnityResolve::Invoke( - "il2cpp_class_get_field_from_name", - self_klass, - (fieldName + '_').c_str() - ); - if (!field) return; - Il2cppUtils::ClassSetFieldValue(self, field, value); - } - - int ReadIntField(const std::string& fieldName) { - return ReadField(fieldName); - } - - Il2cppString* ReadStringField(const std::string& fieldName) { - auto get_mtd = GetGetSetMethodFromCache(fieldName, 0, fieldGetCache, "get_"); - if (!get_mtd) { - return ReadField(fieldName); - } - auto returnClass = UnityResolve::Invoke( - "il2cpp_class_from_type", - UnityResolve::Invoke("il2cpp_method_get_return_type", get_mtd) - ); - if (!returnClass) { - return reinterpret_cast( - get_mtd->methodPointer - )(self, get_mtd); - } - auto isEnum = UnityResolve::Invoke("il2cpp_class_is_enum", returnClass); - if (!isEnum) { - return reinterpret_cast( - get_mtd->methodPointer - )(self, get_mtd); - } - auto enumMap = Il2cppUtils::EnumToValueMap(returnClass, true); - auto enumValue = reinterpret_cast( - get_mtd->methodPointer - )(self, get_mtd); - if (auto it = enumMap.find(enumValue); it != enumMap.end()) { - return Il2cppString::New(it->second); - } - return nullptr; - } - - void SetStringField(const std::string& fieldName, const std::string& value) { - auto newString = Il2cppString::New(value); - SetField(fieldName, newString); - } - - void SetStringListField(const std::string& fieldName, const std::vector& data) { - static auto List_String_klass = Il2cppUtils::get_system_class_from_reflection_type_str( - "System.Collections.Generic.List`1[System.String]" - ); - static auto List_String_ctor_mtd = Il2cppUtils::il2cpp_class_get_method_from_name( - List_String_klass, ".ctor", 0 - ); - static auto List_String_ctor = reinterpret_cast( - List_String_ctor_mtd->methodPointer - ); - - auto newList = UnityResolve::Invoke("il2cpp_object_new", List_String_klass); - List_String_ctor(newList, List_String_ctor_mtd); - - Il2cppUtils::Tools::CSListEditor newListEditor(newList); - for (auto& s : data) { - newListEditor.Add(Il2cppString::New(s)); - } - SetField(fieldName, newList); - } - - void* ReadObjectField(const std::string& fieldName) { - return ReadField(fieldName); - } - - void* ReadObjectListField(const std::string& fieldName) { - return ReadField(fieldName); - } - - static FieldController CreateSubFieldController(void* subObj) { - return FieldController(subObj); - } - }; - - //============================================================== - // 帮助函数:判断 JSON 字段类型 - //============================================================== - JsonValueType checkJsonValueType(const nlohmann::json& j) { - if (j.is_string()) return JsonValueType::JVT_String; - if (j.is_number_integer()) return JsonValueType::JVT_Int; - if (j.is_object()) return JsonValueType::JVT_Object; - if (j.is_array()) { - if (!j.empty() && j.begin()->is_object()) { - return JsonValueType::JVT_ArrayObject; - } - } - return JsonValueType::JVT_String; - } - - //============================================================== - // 解析 pkName => PKItem - //============================================================== - PKItem parsePK(const nlohmann::json& row, const std::string& pkStr) { - auto pos = pkStr.find('.'); - PKItem item; - if (pos == std::string::npos) { - item.topLevel = pkStr; - item.subField = ""; - if (!row.contains(pkStr)) { - item.topLevelType = JsonValueType::JVT_String; - } else { - item.topLevelType = checkJsonValueType(row[pkStr]); - } - item.subFieldType = JsonValueType::JVT_String; - } else { - item.topLevel = pkStr.substr(0, pos); - item.subField = pkStr.substr(pos + 1); - if (!row.contains(item.topLevel)) { - item.topLevelType = JsonValueType::JVT_Object; - } else { - auto& jTop = row[item.topLevel]; - auto t = checkJsonValueType(jTop); - if (t == JsonValueType::JVT_Object) { - item.topLevelType = JsonValueType::JVT_Object; - } else if (t == JsonValueType::JVT_ArrayObject) { - item.topLevelType = JsonValueType::JVT_ArrayObject; - } else { - item.topLevelType = JsonValueType::JVT_Object; - } - } - item.subFieldType = JsonValueType::JVT_String; - if (row.contains(item.topLevel)) { - auto& jTop = row[item.topLevel]; - if (jTop.is_object()) { - if (jTop.contains(item.subField)) { - item.subFieldType = checkJsonValueType(jTop[item.subField]); - } - } else if (jTop.is_array() && !jTop.empty()) { - auto& firstElem = *jTop.begin(); - if (firstElem.is_object() && firstElem.contains(item.subField)) { - item.subFieldType = checkJsonValueType(firstElem[item.subField]); - } - } - } - } - return item; - } - - std::vector parseAllPKItems(const nlohmann::json& row, const std::vector& pkNames) { - std::vector result; - result.reserve(pkNames.size()); - for (auto& pk : pkNames) { - auto item = parsePK(row, pk); - result.push_back(item); - } - return result; - } - - //============================================================== - // 将 jval 拼接到 uniqueKey - //============================================================== - inline void appendPKValue(std::string& uniqueKey, const nlohmann::json& jval, bool& isFirst) { - if (!isFirst) uniqueKey += "|"; - if (jval.is_string()) { - uniqueKey += jval.get(); - } else if (jval.is_number_integer()) { - uniqueKey += std::to_string(jval.get()); - } - isFirst = false; - } - - //============================================================== - // 读取文件 => 解析 => 加载 dataMap - //============================================================== - std::string ReadFileToString(const std::filesystem::path& path) { - std::ifstream ifs(path, std::ios::binary); - if (!ifs) return {}; - std::stringstream buffer; - buffer << ifs.rdbuf(); - return buffer.str(); - } - - // 判断 row 里,与 pkNames/主键相关的字段(若是数组)是否为空 - bool hasEmptyArrayForPk(const nlohmann::json& row, const std::vector& pkNames) { - // 如果行为空,直接返回 false(或 true,看你需求) - if (row.is_null() || !row.is_object()) { - return false; - } - - for (auto& pk : pkNames) { - // 先看该行是否包含此顶层字段 - auto dotPos = pk.find('.'); - std::string topLevel = (dotPos == std::string::npos) ? pk : pk.substr(0, dotPos); - - if (!row.contains(topLevel)) { - // 没有这个字段就略过 - continue; - } - - // 如果 pk 中含 '.', 说明可能是 array 类型 - // 这里仅检查 "顶层字段是否是空数组" - // 若需要更深层的判断,需扩展 - const auto& jTop = row[topLevel]; - if (jTop.is_array()) { - // 一旦发现是空数组,就返回 true - if (jTop.empty()) { - return true; - } - } - } - - return false; - } - - // 根据 pkItems 构造一个 skipSet,里面包含 "topLevel" 和 "topLevel.subField" -// 或者只包含 subField, 看你具体需求 - static std::unordered_set buildSkipFields(const std::vector& pkItems) { - std::unordered_set skipSet; - for (auto& pk : pkItems) { - if (pk.subField.empty()) { - // e.g. "id" - skipSet.insert(pk.topLevel); - } else { - // e.g. "descriptions.type" => 既要跳过 "type" 又要跳过 "descriptions"? - // 具体看你业务需要: - // skipSet.insert(pk.topLevel); // 可能不需要 - skipSet.insert(pk.subField); // "type" - } - } - return skipSet; - } - - // 递归枚举 JSON 值里的字符串并插入到 localSet - void collectLocalizableStrings_impl( - const nlohmann::json& node, - const std::unordered_set& skipSet, - std::unordered_set& localSet - ) { - if (node.is_string()) { - // node本身就是string => 这时无法知道key名,但一般情况下我们是key->value对? - // 这里仅当外层调用传入一个object时可取到key - // 先写成仅object字段时处理 - return; - } - if (node.is_object()) { - // 枚举键值 - for (auto it = node.begin(); it != node.end(); ++it) { - auto& key = it.key(); - auto& val = it.value(); - // 如果key在skipSet里,则跳过 - if (skipSet.count(key)) { - continue; - } - // 否则看val的类型 - if (val.is_string()) { - // 收集 - localSet.insert(val.get()); - // Log::DebugFmt("localSet.insert: %s", val.get().c_str()); - } else if (val.is_object() || val.is_array()) { - // 递归下去 - collectLocalizableStrings_impl(val, skipSet, localSet); - } - // 其他类型 (int/bool/float) 不做本地化 - } - } else if (node.is_array()) { - // 枚举数组元素 - for (auto& element : node) { - if (element.is_string()) { - localSet.insert(element.get()); - // Log::DebugFmt("localSet.insert: %s", element.get().c_str()); - } else if (element.is_object() || element.is_array()) { - collectLocalizableStrings_impl(element, skipSet, localSet); - } - } - } - } - - // 对外接口:根据 row + pkItems,把所有非主键字段的字符串插到 localSet - void collectLocalizableStrings(const nlohmann::json& row, const std::vector& pkItems, std::unordered_set& localSet) { - // 先构建一个 skipSet,表示"主键字段"要跳过 - auto skipSet = buildSkipFields(pkItems); - // 然后递归遍历 - collectLocalizableStrings_impl(row, skipSet, localSet); - } - - - void LoadData() { - g_loadedData.clear(); - static auto masterDir = Local::GetBasePath() / "local-files" / "masterTrans"; - if (!std::filesystem::is_directory(masterDir)) { - Log::ErrorFmt("LoadData: not found: %s", masterDir.string().c_str()); - return; - } - - bool isFirstIteration = true; - for (auto& p : std::filesystem::directory_iterator(masterDir)) { - if (isFirstIteration) { - auto totalFileCount = std::distance( - std::filesystem::directory_iterator(masterDir), - std::filesystem::directory_iterator{} - ); - UnityResolveProgress::classProgress.total = totalFileCount <= 0 ? 1 : totalFileCount; - isFirstIteration = false; - } - UnityResolveProgress::classProgress.current++; - - if (!p.is_regular_file()) continue; - const auto& path = p.path(); - if (path.extension() != ".json") continue; - - std::string tableName = path.stem().string(); - auto fileContent = ReadFileToString(path); - if (fileContent.empty()) continue; - - try { - auto j = nlohmann::json::parse(fileContent); - if (!j.contains("rules") || !j["rules"].contains("primaryKeys")) { - continue; - } - std::vector pkNames; - for (auto& x : j["rules"]["primaryKeys"]) { - pkNames.push_back(x.get()); - } - if (!j.contains("data") || !j["data"].is_array()) { - continue; - } - - TableInfo tableInfo; - if (!j["data"].empty()) { - for (auto & currRow : j["data"]) { - if (!hasEmptyArrayForPk(currRow, pkNames)) { - tableInfo.pkItems = parseAllPKItems(currRow, pkNames); - } - } - // auto& firstRow = j["data"][0]; - // tableInfo.pkItems = parseAllPKItems(firstRow, pkNames); - } - - //============================================================== - // 构建 dataMap, 支持 array + index - //============================================================== - for (auto& row : j["data"]) { - std::string uniqueKey; - bool firstKey = true; - bool failed = false; - - for (auto& pkItem : tableInfo.pkItems) { - if (!row.contains(pkItem.topLevel)) { - failed = true; - break; - } - auto& jTop = row[pkItem.topLevel]; - - // 无子字段 => 直接处理 - if (pkItem.subField.empty()) { - if (jTop.is_string() || jTop.is_number_integer()) { - appendPKValue(uniqueKey, jTop, firstKey); - } else { - failed = true; break; - } - } - else { - // 若是 array + subField,就遍历数组每个下标 + subField => 并将 index + value 拼进 uniqueKey - if (pkItem.topLevelType == JsonValueType::JVT_ArrayObject) { - if (!jTop.is_array()) { failed = true; break; } - // 遍历数组所有元素 - for (int i = 0; i < (int)jTop.size(); i++) { - auto& elem = jTop[i]; - if (!elem.is_object()) { failed = true; break; } - if (!elem.contains(pkItem.subField)) { failed = true; break; } - auto& subVal = elem[pkItem.subField]; - // 只支持 string/int - if (!subVal.is_string() && !subVal.is_number_integer()) { - failed = true; break; - } - // 拼上索引 + 值 - // e.g. "|0:xxx|1:yyy"... - if (!firstKey) uniqueKey += "|"; - uniqueKey += std::to_string(i); - uniqueKey += ":"; - if (subVal.is_string()) { - uniqueKey += subVal.get(); - } else { - uniqueKey += std::to_string(subVal.get()); - } - firstKey = false; - } - if (failed) break; - } - else if (pkItem.topLevelType == JsonValueType::JVT_Object) { - if (!jTop.is_object()) { - failed = true; - break; - } - if (!jTop.contains(pkItem.subField)) { failed = true; break; } - auto& subVal = jTop[pkItem.subField]; - if (subVal.is_string() || subVal.is_number_integer()) { - appendPKValue(uniqueKey, subVal, firstKey); - } else { - failed = true; break; - } - } - else { - failed = true; - break; - } - } - if (failed) break; - } - if (!failed && !uniqueKey.empty()) { - tableInfo.dataMap[uniqueKey] = row; - collectLocalizableStrings(row, tableInfo.pkItems, Local::translatedText); - } - } - - // Log::DebugFmt("Load table: %s, %d, %d", tableName.c_str(), tableInfo.pkItems.size(), tableInfo.dataMap.size()); - g_loadedData[tableName] = std::move(tableInfo); - - } catch (std::exception& e) { - Log::ErrorFmt("MasterLocal::LoadData: parse error in '%s': %s", - path.string().c_str(), e.what()); - } - } - } - - //============================================================== - // 在 C# 对象里,根据 pkItems 构造 uniqueKey - // 同样要支持 array + index - //============================================================== - bool buildUniqueKeyFromCSharp(FieldController& fc, const TableInfo& tableInfo, std::string& outKey) { - outKey.clear(); - bool firstKey = true; - - for (auto& pk : tableInfo.pkItems) { - if (pk.subField.empty()) { - // 顶层无子字段 - if (pk.topLevelType == JsonValueType::JVT_String) { - auto sptr = fc.ReadStringField(pk.topLevel); - if (!sptr) return false; - if (!firstKey) outKey += "|"; - outKey += sptr->ToString(); - firstKey = false; - } else if (pk.topLevelType == JsonValueType::JVT_Int) { - int ival = fc.ReadIntField(pk.topLevel); - if (!firstKey) outKey += "|"; - outKey += std::to_string(ival); - firstKey = false; - } else { - return false; - } - } - else { - // subField - if (pk.topLevelType == JsonValueType::JVT_ArrayObject) { - // => c# 里 readObjectListField - void* listPtr = fc.ReadObjectListField(pk.topLevel); - if (!listPtr) return false; - Il2cppUtils::Tools::CSListEditor listEdit(listPtr); - int arrCount = listEdit.get_Count(); - - // 遍历每个 index - for (int i = 0; i < arrCount; i++) { - auto elemPtr = listEdit.get_Item(i); - if (!elemPtr) return false; - FieldController subFC = FieldController::CreateSubFieldController(elemPtr); - - // 只支持 string/int - if (pk.subFieldType == JsonValueType::JVT_String) { - auto sptr = subFC.ReadStringField(pk.subField); - if (!sptr) return false; - if (!firstKey) outKey += "|"; - // "|i:xxx" - outKey += std::to_string(i); - outKey += ":"; - outKey += sptr->ToString(); - firstKey = false; - } - else if (pk.subFieldType == JsonValueType::JVT_Int) { - int ival = subFC.ReadIntField(pk.subField); - if (!firstKey) outKey += "|"; - outKey += std::to_string(i); - outKey += ":"; - outKey += std::to_string(ival); - firstKey = false; - } else { - return false; - } - } - } - else if (pk.topLevelType == JsonValueType::JVT_Object) { - void* subObj = fc.ReadObjectField(pk.topLevel); - if (!subObj) return false; - FieldController subFC = FieldController::CreateSubFieldController(subObj); - - if (pk.subFieldType == JsonValueType::JVT_String) { - auto sptr = subFC.ReadStringField(pk.subField); - if (!sptr) return false; - if (!firstKey) outKey += "|"; - outKey += sptr->ToString(); - firstKey = false; - } - else if (pk.subFieldType == JsonValueType::JVT_Int) { - int ival = subFC.ReadIntField(pk.subField); - if (!firstKey) outKey += "|"; - outKey += std::to_string(ival); - firstKey = false; - } - else { - return false; - } - } - else { - return false; - } - } - } - return !outKey.empty(); - } - - // 声明 - void localizeJsonToCsharp(FieldController& fc, const nlohmann::json& jdata, const std::unordered_set& skipKeySet); - void localizeArrayOfObject(FieldController& fc, const std::string& fieldName, const nlohmann::json& arrVal, const std::unordered_set& skipKeySet); - void localizeObject(FieldController& fc, const std::string& fieldName, const nlohmann::json& objVal, const std::unordered_set& skipKeySet); - - //==================================================================== - // 对 array 做一层递归 —— 需要带着 skipKeySet - //==================================================================== - void localizeArrayOfObject(FieldController& fc, const std::string& fieldName, const nlohmann::json& arrVal, const std::unordered_set& skipKeySet) { - void* listPtr = fc.ReadObjectListField(fieldName); - if (!listPtr) return; - Il2cppUtils::Tools::CSListEditor listEdit(listPtr); - int cmin = std::min(listEdit.get_Count(), (int)arrVal.size()); - for (int i = 0; i < cmin; i++) { - auto elemPtr = listEdit.get_Item(i); - if (!elemPtr) continue; - FieldController subFC = FieldController::CreateSubFieldController(elemPtr); - localizeJsonToCsharp(subFC, arrVal[i], skipKeySet); - } - } - - //==================================================================== - // 对单个 object 做一层递归 —— 需要带着 skipKeySet - //==================================================================== - void localizeObject(FieldController& fc, const std::string& fieldName, const nlohmann::json& objVal, const std::unordered_set& skipKeySet) { - void* subObj = fc.ReadObjectField(fieldName); - if (!subObj) return; - FieldController subFC = FieldController::CreateSubFieldController(subObj); - localizeJsonToCsharp(subFC, objVal, skipKeySet); - } - - //==================================================================== - // 仅一层本地化: string, string[], object, object[],带 skipKeySet - //==================================================================== - void localizeJsonToCsharp(FieldController& fc, const nlohmann::json& jdata, const std::unordered_set& skipKeySet) { - if (!jdata.is_object()) return; - for (auto it = jdata.begin(); it != jdata.end(); ++it) { - const std::string& key = it.key(); - // 如果 key 在 skipKeySet 里,则跳过本地化 - if (skipKeySet.count(key)) { - // Debug输出可以留意一下 - // Log::DebugFmt("skip field: %s", key.c_str()); - continue; - } - - const auto& val = it.value(); - if (val.is_string()) { - // 打印一下做验证 - auto origStr = fc.ReadStringField(key); - auto newStr = val.get(); - if (origStr) { - std::string oldVal = origStr->ToString(); - // Log::DebugFmt("SetStringField key: %s, oldVal: %s -> newVal: %s", key.c_str(), oldVal.c_str(), newStr.c_str()); - if (((oldVal == "\n") || (oldVal == "\r\n")) && newStr.empty()) { - continue; - } - } - fc.SetStringField(key, val.get()); - } - else if (val.is_array()) { - if (!val.empty() && val.begin()->is_string()) { - bool allStr = true; - std::vector strArray; - for (auto& x : val) { - if (!x.is_string()) { allStr = false; break; } - strArray.push_back(x.get()); - } - if (allStr) { - // Log::DebugFmt("SetStringListField in %s, key: %s", fc.self_klass->name, key.c_str()); - fc.SetStringListField(key, strArray); - continue; - } - } - // array - if (!val.empty() && val.begin()->is_object()) { - localizeArrayOfObject(fc, key, val, skipKeySet); - } - } - else if (val.is_object()) { - localizeObject(fc, key, val, skipKeySet); - } - } - } - - //==================================================================== - // 真正处理单个C#对象 - //==================================================================== - void LocalizeMasterItem(FieldController& fc, const std::string& tableName) { - auto it = g_loadedData.find(tableName); - if (it == g_loadedData.end()) return; - // Log::DebugFmt("LocalizeMasterItem: %s", tableName.c_str()); - auto& tableInfo = it->second; - if (tableInfo.dataMap.empty()) { - return; - } - - std::string uniqueKey; - if (!buildUniqueKeyFromCSharp(fc, tableInfo, uniqueKey)) { - return; - } - - auto itRow = tableInfo.dataMap.find(uniqueKey); - if (itRow == tableInfo.dataMap.end()) { - return; - } - const auto& rowData = itRow->second; - - //===================================================== - // 把「有子字段」的 pkItem 也加入 skipKeySet,但用它的 `subField` 部分 - //===================================================== - std::unordered_set skipKeySet; - for (auto& pk : tableInfo.pkItems) { - if (pk.subField.empty()) { - // 若没有子字段,说明 topLevel 本身是主键 - skipKeySet.insert(pk.topLevel); - } else { - // 如果有子字段,说明这个子字段才是 PK - // e.g. produceDescriptions.examEffectType => skipKeySet.insert("examEffectType"); - skipKeySet.insert(pk.subField); - } - } - - // 然后带着 skipKeySet 去做本地化 - localizeJsonToCsharp(fc, rowData, skipKeySet); - } - - void LocalizeMasterTables(const std::string& tableName, UnityResolve::UnityType::List* result) { - if (!result) return; - Il2cppUtils::Tools::CSListEditor resultList(result); - if (resultList.get_Count() <= 0) return; - - for (auto i : resultList) { - if (!i) continue; - FieldController fc(i); - LocalizeMasterItem(fc, tableName); - } - } - - void LocalizeMaster(const std::string& sql, UnityResolve::UnityType::List* result) { - static const std::regex tableNameRegex(R"(\bFROM\s+(?:`([^`]+)`|(\S+)))"); - std::smatch match; - if (std::regex_search(sql, match, tableNameRegex)) { - std::string tableName = match[1].matched ? match[1].str() : match[2].str(); - LocalizeMasterTables(tableName, result); - } - } - - void LocalizeMaster(const std::string& sql, void* result) { - if (!Config::useMasterTrans) return; - LocalizeMaster(sql, reinterpret_cast*>(result)); - } - - void LocalizeMaster(void* result, const std::string& tableName) { - if (!Config::useMasterTrans) return; - LocalizeMasterTables(tableName, reinterpret_cast*>(result)); - } - - void LocalizeMasterItem(void* item, const std::string& tableName) { - if (!Config::useMasterTrans) return; - FieldController fc(item); - LocalizeMasterItem(fc, tableName); - } - -} // namespace GakumasLocal::MasterLocal diff --git a/app/src/main/cpp/GakumasLocalify/MasterLocal_Legacy.h b/app/src/main/cpp/GakumasLocalify/MasterLocal_Legacy.h deleted file mode 100644 index 973c865..0000000 --- a/app/src/main/cpp/GakumasLocalify/MasterLocal_Legacy.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef GAKUMAS_LOCALIFY_MASTERLOCAL_H -#define GAKUMAS_LOCALIFY_MASTERLOCAL_H - -/* -#include - -namespace GakumasLocal::MasterLocal { - void LoadData(); - void LocalizeMaster(const std::string& sql, void* result); - void LocalizeMaster(void* result, const std::string& tableName); - void LocalizeMasterItem(void* item, const std::string& tableName); -} - */ - -#endif //GAKUMAS_LOCALIFY_MASTERLOCAL_H diff --git a/app/src/main/cpp/GakumasLocalify/Misc.cpp b/app/src/main/cpp/GakumasLocalify/Misc.cpp index 609d772..47fc3e4 100644 --- a/app/src/main/cpp/GakumasLocalify/Misc.cpp +++ b/app/src/main/cpp/GakumasLocalify/Misc.cpp @@ -2,11 +2,15 @@ #include #include -#include #include "fmt/core.h" - -extern JavaVM* g_javaVM; +#ifndef GKMS_WINDOWS + #include + + extern JavaVM* g_javaVM; +#else + #include "cpprest/details/http_helpers.h" +#endif namespace GakumasLocal::Misc { @@ -20,6 +24,13 @@ namespace GakumasLocal::Misc { return utf16conv.to_bytes(str.data(), str.data() + str.size()); } +#ifdef GKMS_WINDOWS + std::string ToUTF8(const std::wstring_view& str) { + return utility::conversions::to_utf8string(str.data()); + } +#endif + +#ifndef GKMS_WINDOWS JNIEnv* GetJNIEnv() { if (!g_javaVM) return nullptr; JNIEnv* env = nullptr; @@ -31,6 +42,7 @@ namespace GakumasLocal::Misc { } return env; } +#endif CSEnum::CSEnum(const std::string& name, const int value) { this->Add(name, value); diff --git a/app/src/main/cpp/GakumasLocalify/Misc.hpp b/app/src/main/cpp/GakumasLocalify/Misc.hpp index a0d97ee..61df393 100644 --- a/app/src/main/cpp/GakumasLocalify/Misc.hpp +++ b/app/src/main/cpp/GakumasLocalify/Misc.hpp @@ -2,11 +2,16 @@ #include #include -#include #include #include #include +#include "../platformDefine.hpp" + +#ifndef GKMS_WINDOWS + #include +#endif + namespace GakumasLocal { using OpaqueFunctionPointer = void (*)(); @@ -14,7 +19,13 @@ namespace GakumasLocal { namespace Misc { std::u16string ToUTF16(const std::string_view& str); std::string ToUTF8(const std::u16string_view& str); +#ifdef GKMS_WINDOWS + std::string ToUTF8(const std::wstring_view& str); +#endif + +#ifndef GKMS_WINDOWS JNIEnv* GetJNIEnv(); +#endif class CSEnum { public: diff --git a/app/src/main/cpp/GakumasLocalify/Plugin.h b/app/src/main/cpp/GakumasLocalify/Plugin.h index ac63013..1abf086 100644 --- a/app/src/main/cpp/GakumasLocalify/Plugin.h +++ b/app/src/main/cpp/GakumasLocalify/Plugin.h @@ -4,7 +4,13 @@ #include "Misc.hpp" #include #include -#include + +#include "../platformDefine.hpp" + +#ifndef GKMS_WINDOWS + #include +#endif // !GKMS_WINDOWS + namespace GakumasLocal { struct HookInstaller diff --git a/app/src/main/cpp/GakumasLocalify/camera/baseCamera.cpp b/app/src/main/cpp/GakumasLocalify/camera/baseCamera.cpp index 8733231..3a9e19b 100644 --- a/app/src/main/cpp/GakumasLocalify/camera/baseCamera.cpp +++ b/app/src/main/cpp/GakumasLocalify/camera/baseCamera.cpp @@ -1,6 +1,12 @@ #include "baseCamera.hpp" #include +#include "../../platformDefine.hpp" + +#ifdef GKMS_WINDOWS + #include +#endif // GKMS_WINDOWS + namespace BaseCamera { using Vector3_t = UnityResolve::UnityType::Vector3; diff --git a/app/src/main/cpp/GakumasLocalify/camera/baseCamera.hpp b/app/src/main/cpp/GakumasLocalify/camera/baseCamera.hpp index 2cfdebb..b7b6367 100644 --- a/app/src/main/cpp/GakumasLocalify/camera/baseCamera.hpp +++ b/app/src/main/cpp/GakumasLocalify/camera/baseCamera.hpp @@ -1,6 +1,6 @@ #pragma once -#include "../deps/UnityResolve/UnityResolve.hpp" +#include "../../deps/UnityResolve/UnityResolve.hpp" enum LonMoveHState { LonMoveLeftAndRight, diff --git a/app/src/main/cpp/GakumasLocalify/camera/camera.cpp b/app/src/main/cpp/GakumasLocalify/camera/camera.cpp index 5ac799e..406cc3b 100644 --- a/app/src/main/cpp/GakumasLocalify/camera/camera.cpp +++ b/app/src/main/cpp/GakumasLocalify/camera/camera.cpp @@ -1,9 +1,16 @@ #include "baseCamera.hpp" #include "camera.hpp" #include -#include "Misc.hpp" +#include "../Misc.hpp" #include "../BaseDefine.h" +#include "../../platformDefine.hpp" + +#ifdef GKMS_WINDOWS + #include +#endif // GKMS_WINDOWS + + namespace GKCamera { BaseCamera::Camera baseCamera{}; diff --git a/app/src/main/cpp/GakumasLocalify/camera/camera.hpp b/app/src/main/cpp/GakumasLocalify/camera/camera.hpp index d4b035e..ceb3629 100644 --- a/app/src/main/cpp/GakumasLocalify/camera/camera.hpp +++ b/app/src/main/cpp/GakumasLocalify/camera/camera.hpp @@ -1,6 +1,6 @@ #pragma once #include "baseCamera.hpp" -#include "Joystick/JoystickEvent.h" +#include "../../deps/Joystick/JoystickEvent.h" namespace GKCamera { enum class CameraMode { diff --git a/app/src/main/cpp/GakumasLocalify/config/Config.cpp b/app/src/main/cpp/GakumasLocalify/config/Config.cpp index 913ebc9..3fbc583 100644 --- a/app/src/main/cpp/GakumasLocalify/config/Config.cpp +++ b/app/src/main/cpp/GakumasLocalify/config/Config.cpp @@ -1,6 +1,7 @@ #include #include "nlohmann/json.hpp" #include "../Log.h" +#include namespace GakumasLocal::Config { bool isConfigInit = false; @@ -100,7 +101,6 @@ namespace GakumasLocal::Config { GetConfigItem(bLimitYy); GetConfigItem(bLimitZx); GetConfigItem(bLimitZy); - } catch (std::exception& e) { Log::ErrorFmt("LoadConfig error: %s", e.what()); diff --git a/app/src/main/cpp/platformDefine.hpp b/app/src/main/cpp/platformDefine.hpp new file mode 100644 index 0000000..7327322 --- /dev/null +++ b/app/src/main/cpp/platformDefine.hpp @@ -0,0 +1,21 @@ +#include "shadowhook.h" +#include + +#define ADD_HOOK(name, addr) \ + name##_Addr = reinterpret_cast(addr); \ + if (addr) { \ + auto stub = hookInstaller->InstallHook(reinterpret_cast(addr), \ + reinterpret_cast(name##_Hook), \ + reinterpret_cast(&name##_Orig)); \ + if (stub == NULL) { \ + int error_num = shadowhook_get_errno(); \ + const char *error_msg = shadowhook_to_errmsg(error_num); \ + Log::ErrorFmt("ADD_HOOK: %s at %p failed: %s", #name, addr, error_msg); \ + } \ + else { \ + hookedStubs.emplace(stub); \ + GakumasLocal::Log::InfoFmt("ADD_HOOK: %s at %p", #name, addr); \ + } \ + } \ + else GakumasLocal::Log::ErrorFmt("Hook failed: %s is NULL", #name, addr); \ + if (Config::lazyInit) UnityResolveProgress::classProgress.current++ \ No newline at end of file diff --git a/app/src/main/java/io/github/chinosk/gakumas/localify/ui/pages/subPages/HomePage.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/pages/subPages/HomePage.kt index 24dc69f..988fe47 100644 --- a/app/src/main/java/io/github/chinosk/gakumas/localify/ui/pages/subPages/HomePage.kt +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/pages/subPages/HomePage.kt @@ -314,8 +314,8 @@ fun HomePage(modifier: Modifier = Modifier, fontSize = 14f, value = programConfig.value.useAPIAssetsURL, onValueChange = { c -> context?.onPUseAPIAssetsURLChanged(c, 0, 0, 0)}, - label = { Text(stringResource(R.string.api_addr)) }, - keyboardOptions = keyboardOptionsNumber) + label = { Text(stringResource(R.string.api_addr)) } + ) if (downloadAble) { GakuButton(modifier = modifier @@ -411,8 +411,8 @@ fun HomePage(modifier: Modifier = Modifier, fontSize = 14f, value = programConfig.value.transRemoteZipUrl, onValueChange = { c -> context?.onPTransRemoteZipUrlChanged(c, 0, 0, 0)}, - label = { Text(stringResource(id = R.string.resource_url)) }, - keyboardOptions = keyboardOptionsNumber) + label = { Text(stringResource(id = R.string.resource_url)) } + ) if (downloadAble) { GakuButton(modifier = modifier