mirror of
https://git.chinosk6.cn/chinosk/gkms-local.git
synced 2026-02-04 17:13:59 +00:00
Compare commits
24 Commits
v1.6.1
...
v3.0.0Beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d09904643f | ||
|
|
d83854c755 | ||
|
|
ade47131f9 | ||
|
|
0218543935 | ||
|
|
60c846b2f0 | ||
|
|
01591e61c0 | ||
|
|
56c066bf42 | ||
|
|
35c2b9f489 | ||
|
|
c50fdfd678 | ||
|
|
3c1d1f139a | ||
|
|
6ac94178fa | ||
|
|
c27085772f | ||
|
|
361c48e2c9 | ||
|
|
e9ba8b58fd | ||
|
|
e03736bd7d | ||
|
|
06b552a097 | ||
|
|
bd9bcae01d | ||
|
|
8c850ad7db | ||
|
|
7bf429336b | ||
|
|
c7e3d4f718 | ||
|
|
b74713be78 | ||
|
|
67945c86dd | ||
|
|
06a96a450e | ||
|
|
6e512d9380 |
11
README.md
11
README.md
@@ -8,16 +8,7 @@
|
||||
# Usage
|
||||
|
||||
- 这是一个 XPosed 插件,已 Root 用户可以使用 [LSPosed](https://github.com/LSPosed/LSPosed),未 Root 用户可以使用 [LSPatch](https://github.com/LSPosed/LSPatch)。
|
||||
|
||||
|
||||
|
||||
# TODO
|
||||
|
||||
- [x] 卡片信息、TIPS 等部分的文本 hook (`generic`)
|
||||
- [ ] 更多类型的文件替换
|
||||
- [ ] LSPatch 集成模式无效
|
||||
|
||||
... and more
|
||||
- 安卓 15 及以上的用户,请使用 [JingMatrix/LSPosed](https://github.com/JingMatrix/LSPosed) 或 [JingMatrix/LSPatch](https://github.com/JingMatrix/LSPatch)。因为原版已停止更新。
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -15,8 +15,9 @@ android {
|
||||
applicationId "io.github.chinosk.gakumas.localify"
|
||||
minSdk 29
|
||||
targetSdk 34
|
||||
versionCode 4
|
||||
versionName "v1.6.1"
|
||||
versionCode 12
|
||||
versionName "v3.0.0"
|
||||
buildConfigField "String", "VERSION_NAME", "\"${versionName}\""
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
3
app/lint.xml
Normal file
3
app/lint.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<lint>
|
||||
<issue id="ExtraTranslation" severity="ignore" />
|
||||
</lint>
|
||||
Submodule app/src/main/assets/gakumas-local updated: 52aefc27eb...df448152cf
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -42,6 +42,7 @@ add_library(${CMAKE_PROJECT_NAME} SHARED
|
||||
GakumasLocalify/Log.cpp
|
||||
GakumasLocalify/Misc.cpp
|
||||
GakumasLocalify/Local.cpp
|
||||
GakumasLocalify/MasterLocal.cpp
|
||||
GakumasLocalify/camera/baseCamera.cpp
|
||||
GakumasLocalify/camera/camera.cpp
|
||||
GakumasLocalify/config/Config.cpp
|
||||
|
||||
@@ -1,29 +1,58 @@
|
||||
#define KEY_W 51
|
||||
#define KEY_S 47
|
||||
#define KEY_A 29
|
||||
#define KEY_D 32
|
||||
#define KEY_R 46
|
||||
#define KEY_Q 45
|
||||
#define KEY_E 33
|
||||
#define KEY_F 34
|
||||
#define KEY_I 37
|
||||
#define KEY_K 39
|
||||
#define KEY_J 38
|
||||
#define KEY_L 40
|
||||
#define KEY_V 50
|
||||
#define KEY_UP 19
|
||||
#define KEY_DOWN 20
|
||||
#define KEY_LEFT 21
|
||||
#define KEY_RIGHT 22
|
||||
#define KEY_CTRL 113
|
||||
#define KEY_SHIFT 59
|
||||
#define KEY_ALT 57
|
||||
#define KEY_SPACE 62
|
||||
#define KEY_ADD 70
|
||||
#define KEY_SUB 69
|
||||
#include "../platformDefine.hpp"
|
||||
|
||||
#define WM_KEYDOWN 0
|
||||
#define WM_KEYUP 1
|
||||
#ifndef GKMS_WINDOWS
|
||||
#define KEY_W 51
|
||||
#define KEY_S 47
|
||||
#define KEY_A 29
|
||||
#define KEY_D 32
|
||||
#define KEY_R 46
|
||||
#define KEY_Q 45
|
||||
#define KEY_E 33
|
||||
#define KEY_F 34
|
||||
#define KEY_I 37
|
||||
#define KEY_K 39
|
||||
#define KEY_J 38
|
||||
#define KEY_L 40
|
||||
#define KEY_V 50
|
||||
#define KEY_UP 19
|
||||
#define KEY_DOWN 20
|
||||
#define KEY_LEFT 21
|
||||
#define KEY_RIGHT 22
|
||||
#define KEY_CTRL 113
|
||||
#define KEY_SHIFT 59
|
||||
#define KEY_ALT 57
|
||||
#define KEY_SPACE 62
|
||||
#define KEY_ADD 70
|
||||
#define KEY_SUB 69
|
||||
|
||||
#define WM_KEYDOWN 0
|
||||
#define WM_KEYUP 1
|
||||
#else
|
||||
#define KEY_W 'W'
|
||||
#define KEY_S 'S'
|
||||
#define KEY_A 'A'
|
||||
#define KEY_D 'D'
|
||||
#define KEY_R 'R'
|
||||
#define KEY_Q 'Q'
|
||||
#define KEY_E 'E'
|
||||
#define KEY_F 'F'
|
||||
#define KEY_I 'I'
|
||||
#define KEY_K 'K'
|
||||
#define KEY_J 'J'
|
||||
#define KEY_L 'L'
|
||||
#define KEY_V 'V'
|
||||
#define KEY_UP 38
|
||||
#define KEY_DOWN 40
|
||||
#define KEY_LEFT 37
|
||||
#define KEY_RIGHT 39
|
||||
#define KEY_CTRL 17
|
||||
#define KEY_SHIFT 16
|
||||
#define KEY_ALT 18
|
||||
#define KEY_SPACE 32
|
||||
|
||||
#define KEY_ADD 187
|
||||
#define KEY_SUB 189
|
||||
#endif
|
||||
|
||||
#define BTN_A 96
|
||||
#define BTN_B 97
|
||||
|
||||
@@ -1,20 +1,28 @@
|
||||
#include <android/log.h>
|
||||
#include "Hook.h"
|
||||
#include "Plugin.h"
|
||||
#include "Log.h"
|
||||
#include "../deps/UnityResolve/UnityResolve.hpp"
|
||||
#include "Il2cppUtils.hpp"
|
||||
#include "Local.h"
|
||||
#include "MasterLocal.h"
|
||||
#include <unordered_set>
|
||||
#include "camera/camera.hpp"
|
||||
#include "config/Config.hpp"
|
||||
#include "shadowhook.h"
|
||||
#include <jni.h>
|
||||
// #include <jni.h>
|
||||
#include <thread>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include "../platformDefine.hpp"
|
||||
|
||||
#ifdef GKMS_WINDOWS
|
||||
#include "../windowsPlatform.hpp"
|
||||
#include "cpprest/details/http_helpers.h"
|
||||
#include "../resourceUpdate/resourceUpdate.hpp"
|
||||
#endif
|
||||
|
||||
|
||||
std::unordered_set<void*> hookedStubs{};
|
||||
extern std::filesystem::path gakumasLocalPath;
|
||||
|
||||
#define DEFINE_HOOK(returnType, name, params) \
|
||||
using name##_Type = returnType(*) params; \
|
||||
@@ -22,26 +30,7 @@ std::unordered_set<void*> hookedStubs{};
|
||||
name##_Type name##_Orig = nullptr; \
|
||||
returnType name##_Hook params
|
||||
|
||||
|
||||
#define ADD_HOOK(name, addr) \
|
||||
name##_Addr = reinterpret_cast<name##_Type>(addr); \
|
||||
if (addr) { \
|
||||
auto stub = hookInstaller->InstallHook(reinterpret_cast<void*>(addr), \
|
||||
reinterpret_cast<void*>(name##_Hook), \
|
||||
reinterpret_cast<void**>(&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);
|
||||
@@ -52,7 +41,7 @@ void UnHookAll() {
|
||||
GakumasLocal::Log::ErrorFmt("unhook failed: %d - %s", error_num, error_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
namespace GakumasLocal::HookMain {
|
||||
using Il2cppString = UnityResolve::UnityType::String;
|
||||
@@ -222,14 +211,25 @@ namespace GakumasLocal::HookMain {
|
||||
return Unity_set_position_Injected_Orig(self, data);
|
||||
}
|
||||
|
||||
#ifdef GKMS_WINDOWS
|
||||
DEFINE_HOOK(void*, InternalSetOrientationAsync, (void* retstr, void* self, int type, void* c, void* tc, void* mtd)) {
|
||||
switch (Config::gameOrientation) {
|
||||
case 1: type = 0x2; break; // FixedPortrait
|
||||
case 2: type = 0x3; break; // FixedLandscape
|
||||
default: break;
|
||||
}
|
||||
return InternalSetOrientationAsync_Orig(retstr, self, type, c, tc, mtd);
|
||||
}
|
||||
#else
|
||||
DEFINE_HOOK(void*, InternalSetOrientationAsync, (void* self, int type, void* c, void* tc, void* mtd)) {
|
||||
switch (Config::gameOrientation) {
|
||||
case 1: type = 0x2; break; // FixedPortrait
|
||||
case 2: type = 0x3; break; // FixedLandscape
|
||||
default: break;
|
||||
case 1: type = 0x2; break; // FixedPortrait
|
||||
case 2: type = 0x3; break; // FixedLandscape
|
||||
default: break;
|
||||
}
|
||||
return InternalSetOrientationAsync_Orig(self, type, c, tc, mtd);
|
||||
}
|
||||
#endif
|
||||
|
||||
DEFINE_HOOK(void, EndCameraRendering, (void* ctx, void* camera, void* method)) {
|
||||
EndCameraRendering_Orig(ctx, camera, method);
|
||||
@@ -298,9 +298,129 @@ namespace GakumasLocal::HookMain {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef GKMS_WINDOWS
|
||||
struct TransparentStringHash : std::hash<std::wstring>, std::hash<std::wstring_view>
|
||||
{
|
||||
using is_transparent = void;
|
||||
};
|
||||
|
||||
typedef std::unordered_set<std::wstring, TransparentStringHash, std::equal_to<void>> AssetPathsType;
|
||||
std::map<std::string, AssetPathsType> CustomAssetBundleAssetPaths;
|
||||
std::unordered_map<std::string, uint32_t> CustomAssetBundleHandleMap{};
|
||||
std::list<std::string> 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<void* (*)(void*)>(
|
||||
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<Il2CppString*>(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<uint32_t>("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<Il2cppUtils::Il2CppReflectionType*>("il2cpp_type_get_object",
|
||||
UnityResolve::Invoke<void*>("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<void*>("il2cpp_gchandle_get_target", ReplaceFontHandle);
|
||||
// 加载场景时会被 Resources.UnloadUnusedAssets 干掉,且不受 DontDestroyOnLoad 影响,暂且判断是否存活,并在必要的时候重新加载
|
||||
// TODO: 考虑挂载到 GameObject 上
|
||||
// AssetBundle 不会被干掉
|
||||
if (IsNativeObjectAlive(replaceFont))
|
||||
{
|
||||
return replaceFont;
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityResolve::Invoke<void>("il2cpp_gchandle_free", std::exchange(ReplaceFontHandle, 0));
|
||||
}
|
||||
}
|
||||
|
||||
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)>(
|
||||
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<uint32_t>("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;
|
||||
}
|
||||
@@ -321,10 +441,11 @@ namespace GakumasLocal::HookMain {
|
||||
const auto newFont = Font_klass->New<void*>();
|
||||
Font_ctor->Invoke<void>(newFont);
|
||||
|
||||
CreateFontFromPath(newFont, Il2cppString::New(fontName));
|
||||
CreateFontFromPath(newFont, Il2cppString::New(fontName.string()));
|
||||
fontCache = newFont;
|
||||
return newFont;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::unordered_set<void*> updatedFontPtrs{};
|
||||
void UpdateFont(void* TMP_Textself) {
|
||||
@@ -348,6 +469,7 @@ namespace GakumasLocal::HookMain {
|
||||
|
||||
auto newFont = GetReplaceFont();
|
||||
if (!newFont) return;
|
||||
|
||||
auto fontAsset = get_font->Invoke<void*>(TMP_Textself);
|
||||
if (fontAsset) {
|
||||
set_sourceFontFile->Invoke<void>(fontAsset, newFont);
|
||||
@@ -357,6 +479,9 @@ namespace GakumasLocal::HookMain {
|
||||
}
|
||||
if (updatedFontPtrs.size() > 200) updatedFontPtrs.clear();
|
||||
}
|
||||
else {
|
||||
Log::Error("UpdateFont: fontAsset is null.");
|
||||
}
|
||||
set_font->Invoke<void>(TMP_Textself, fontAsset);
|
||||
|
||||
// auto fontMaterial = get_material->Invoke<void*>(fontAsset);
|
||||
@@ -421,6 +546,66 @@ namespace GakumasLocal::HookMain {
|
||||
TextField_set_value_Orig(self, value);
|
||||
}
|
||||
|
||||
// 未使用的 Hook
|
||||
DEFINE_HOOK(void, EffectGroup_ctor, (void* self, void* mtd)) {
|
||||
// auto self_klass = Il2cppUtils::get_class_from_instance(self);
|
||||
// Log::DebugFmt("EffectGroup_ctor: self: %s::%s", self_klass->namespaze, self_klass->name);
|
||||
EffectGroup_ctor_Orig(self, mtd);
|
||||
}
|
||||
|
||||
// 用于本地化 MasterDB
|
||||
DEFINE_HOOK(void, MessageExtensions_MergeFrom, (void* message, void* span, void* mtd)) {
|
||||
MessageExtensions_MergeFrom_Orig(message, span, mtd);
|
||||
if (message) {
|
||||
auto ret_klass = Il2cppUtils::get_class_from_instance(message);
|
||||
if (ret_klass) {
|
||||
// Log::DebugFmt("LocalizeMasterItem: %s", ret_klass->name);
|
||||
MasterLocal::LocalizeMasterItem(message, ret_klass->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// 未使用的 Hook
|
||||
DEFINE_HOOK(void, MasterBase_GetAll, (void* self, UnityResolve::UnityType::Array<UnityResolve::UnityType::Byte>* getAllSQL,
|
||||
int sqlLength, UnityResolve::UnityType::List<void*>* result, void* predicate, void* comparison, void* mtd)) {
|
||||
// result: List<Campus.Common.Proto.Client.Master.*>, 和 query 的表名一致
|
||||
|
||||
MasterBase_GetAll_Orig(self, getAllSQL, sqlLength, result, predicate, comparison, mtd);
|
||||
|
||||
auto data_ptr = reinterpret_cast<std::uint8_t*>(getAllSQL->GetData());
|
||||
std::string qS(data_ptr, data_ptr + sqlLength);
|
||||
|
||||
|
||||
Il2cppUtils::Tools::CSListEditor resultList(result);
|
||||
MasterLocal::LocalizeMaster(qS, result);
|
||||
}
|
||||
|
||||
void LocalizeFindByKey(void* result, void* self) {
|
||||
return; // 暂时不需要了
|
||||
auto self_klass = Il2cppUtils::get_class_from_instance(self);
|
||||
Log::DebugFmt("Localize: %s", self_klass->name); // FeatureLockMaster
|
||||
// return;
|
||||
|
||||
if (!result) return;
|
||||
auto result_klass = Il2cppUtils::get_class_from_instance(result);
|
||||
std::string klassName = result_klass->name;
|
||||
|
||||
auto MasterBase_klass = Il2cppUtils::get_class_from_instance(self);
|
||||
auto MasterBase_GetTableName = Il2cppUtils::il2cpp_class_get_method_from_name(MasterBase_klass, "GetTableName", 0);
|
||||
if (MasterBase_GetTableName) {
|
||||
auto tableName = reinterpret_cast<Il2cppString* (*)(void*, void*)>(MasterBase_GetTableName->methodPointer)(self, MasterBase_GetTableName);
|
||||
// Log::DebugFmt("MasterBase_FindByKey: %s", tableName->ToString().c_str());
|
||||
|
||||
if (klassName == "List`1") {
|
||||
MasterLocal::LocalizeMaster(result, tableName->ToString());
|
||||
}
|
||||
else {
|
||||
MasterLocal::LocalizeMasterItem(result, tableName->ToString());
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
DEFINE_HOOK(Il2cppString*, OctoCaching_GetResourceFileName, (void* data, void* method)) {
|
||||
auto ret = OctoCaching_GetResourceFileName_Orig(data, method);
|
||||
//Log::DebugFmt("OctoCaching_GetResourceFileName: %s", ret->ToString().c_str());
|
||||
@@ -466,20 +651,260 @@ namespace GakumasLocal::HookMain {
|
||||
// return UnityResolve::UnityType::String::New("[I18]" + ret->ToString());
|
||||
}
|
||||
|
||||
DEFINE_HOOK(void, PictureBookLiveThumbnailView_SetData, (void* self, void* liveData, bool isUnlocked, bool isNew, void* ct, void* mtd)) {
|
||||
// Log::DebugFmt("PictureBookLiveThumbnailView_SetData: isUnlocked: %d, isNew: %d", isUnlocked, isNew);
|
||||
/*
|
||||
DEFINE_HOOK(void*, UserDataManagerBase_get__userIdolCardSkinList, (void* self, void* mtd)) { // Live默认选择
|
||||
auto ret = UserDataManagerBase_get__userIdolCardSkinList_Orig(self, mtd);
|
||||
Log::DebugFmt("UserDataManagerBase_get__userIdolCardSkinList: %p", ret);
|
||||
return ret;
|
||||
}
|
||||
DEFINE_HOOK(void*, UserDataManagerBase_get__userCostumeList, (void* self, void* mtd)) { // 服装选择界面
|
||||
auto ret = UserDataManagerBase_get__userCostumeList_Orig(self, mtd);
|
||||
Log::DebugFmt("UserDataManagerBase_get__userCostumeList: %p", ret);
|
||||
return ret;
|
||||
}
|
||||
DEFINE_HOOK(void*, UserDataManagerBase_get__userCostumeHeadList, (void* self, void* mtd)) { // 服装选择界面
|
||||
auto ret = UserDataManagerBase_get__userCostumeHeadList_Orig(self, mtd);
|
||||
Log::DebugFmt("UserDataManagerBase_get__userCostumeHeadList: %p", ret);
|
||||
return ret;
|
||||
}*/
|
||||
|
||||
DEFINE_HOOK(bool, UserIdolCardSkinCollection_Exists, (void* self, Il2cppString* id, void* mtd)) { // Live默认选择
|
||||
auto ret = UserIdolCardSkinCollection_Exists_Orig(self, id, mtd);
|
||||
// Log::DebugFmt("UserIdolCardSkinCollection_Exists: %s, ret: %d", id->ToString().c_str(), ret);
|
||||
if (!Config::unlockAllLive) return ret;
|
||||
|
||||
if (id) {
|
||||
std::string idStr = id->ToString();
|
||||
if (idStr.starts_with("music") || idStr.starts_with("i_card-skin")) { // eg. music-all-kllj-006, i_card-skin-hski-3-002
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef GKMS_WINDOWS
|
||||
DEFINE_HOOK(void, PictureBookLiveThumbnailView_SetDataAsync, (void* retstr, void* self, void* liveData, bool isReleased, bool isUnlocked, bool isNew, bool hasLiveSkin, void* ct, void* mtd)) {
|
||||
// Log::DebugFmt("PictureBookLiveThumbnailView_SetDataAsync: isReleased: %d, isUnlocked: %d, isNew: %d, hasLiveSkin: %d", isReleased, isUnlocked, isNew, hasLiveSkin);
|
||||
if (Config::dbgMode && Config::unlockAllLive) {
|
||||
isUnlocked = true;
|
||||
isReleased = true;
|
||||
hasLiveSkin = true;
|
||||
}
|
||||
PictureBookLiveThumbnailView_SetData_Orig(self, liveData, isUnlocked, isNew, ct, mtd);
|
||||
PictureBookLiveThumbnailView_SetDataAsync_Orig(retstr, self, liveData, isReleased, isUnlocked, isNew, hasLiveSkin, ct, mtd);
|
||||
}
|
||||
#else
|
||||
DEFINE_HOOK(void, PictureBookLiveThumbnailView_SetDataAsync, (void* self, void* liveData, bool isReleased, bool isUnlocked, bool isNew, bool hasLiveSkin, void* ct, void* mtd)) {
|
||||
// Log::DebugFmt("PictureBookLiveThumbnailView_SetDataAsync: isReleased: %d, isUnlocked: %d, isNew: %d, hasLiveSkin: %d", isReleased, isUnlocked, isNew, hasLiveSkin);
|
||||
if (Config::dbgMode && Config::unlockAllLive) {
|
||||
isUnlocked = true;
|
||||
isReleased = true;
|
||||
hasLiveSkin = true;
|
||||
}
|
||||
PictureBookLiveThumbnailView_SetDataAsync_Orig(self, liveData, isReleased, isUnlocked, isNew, hasLiveSkin, ct, mtd);
|
||||
}
|
||||
#endif
|
||||
|
||||
enum class GetIdolIdType {
|
||||
MusicId,
|
||||
CostumeId,
|
||||
CostumeHeadId
|
||||
};
|
||||
|
||||
std::vector<std::string> GetIdolMusicIdAll(const std::string& charaNameId = "", GetIdolIdType getType = GetIdolIdType::MusicId) {
|
||||
// 传入例: fktn
|
||||
// System.Collections.Generic.List`1<valuetype [mscorlib]System.ValueTuple`2<class Campus.Common.Proto.Client.Master.IdolCardSkin, class Campus.Common.Proto.Client.Master.Music>>
|
||||
static auto get_IdolCardSkinMaster = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Master", "MasterManager", "get_IdolCardSkinMaster");
|
||||
static auto Master_GetAllWithSortByKey = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Master", "IdolCardSkinMaster", "GetAllWithSortByKey");
|
||||
static auto IdolCardSkin_get_Id = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Master", "IdolCardSkin", "get_Id");
|
||||
static auto IdolCardSkin_get_IdolCardId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Master", "IdolCardSkin", "get_IdolCardId");
|
||||
static auto IdolCardSkin_GetMusic = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Master", "IdolCardSkin", "GetMusic");
|
||||
static auto IdolCardSkin_get_MusicId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Master", "IdolCardSkin", "get_MusicId");
|
||||
static auto IdolCardSkin_get_CostumeId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Master", "IdolCardSkin", "get_CostumeId");
|
||||
static auto IdolCardSkin_get_CostumeHeadId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Master", "IdolCardSkin", "get_CostumeHeadId");
|
||||
static auto GetLiveMusics = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.OutGame",
|
||||
"PictureBookWindowPresenter", "GetLiveMusics");
|
||||
|
||||
auto idolCardSkinMaster = get_IdolCardSkinMaster->Invoke<void*>(nullptr); // IdolCardSkinMaster
|
||||
|
||||
std::vector<std::string> ret{};
|
||||
|
||||
if (!idolCardSkinMaster) {
|
||||
Log::ErrorFmt("get_IdolCardSkinMaster failed: %p", idolCardSkinMaster);
|
||||
return ret;
|
||||
}
|
||||
// List<IdolCardSkin>
|
||||
auto idolCardSkinList = Master_GetAllWithSortByKey->Invoke<UnityResolve::UnityType::List<void*>*>(idolCardSkinMaster, 0x0, nullptr);
|
||||
|
||||
auto idolCardSkins = idolCardSkinList->ToArray()->ToVector();
|
||||
const auto checkStartCharaId = "i_card-" + charaNameId;
|
||||
// Log::DebugFmt("checkStartCharaId: %s", checkStartCharaId.c_str());
|
||||
|
||||
// origMusics->Clear();
|
||||
UnityResolve::Method* idGetFunc = nullptr;
|
||||
switch (getType) {
|
||||
case GetIdolIdType::MusicId: idGetFunc = IdolCardSkin_get_MusicId;
|
||||
break;
|
||||
case GetIdolIdType::CostumeId: idGetFunc = IdolCardSkin_get_CostumeId;
|
||||
break;
|
||||
case GetIdolIdType::CostumeHeadId: idGetFunc = IdolCardSkin_get_CostumeHeadId;
|
||||
break;
|
||||
default:
|
||||
idGetFunc = IdolCardSkin_get_MusicId;
|
||||
}
|
||||
|
||||
for (auto i : idolCardSkins) {
|
||||
if (!i) continue;
|
||||
// auto charaId = IdolCardSkin_get_Id->Invoke<Il2cppString*>(i);
|
||||
auto targetId = idGetFunc->Invoke<Il2cppString*>(i);
|
||||
auto cardId = IdolCardSkin_get_IdolCardId->Invoke<Il2cppString*>(i)->ToString();
|
||||
auto music = IdolCardSkin_GetMusic->Invoke<void*>(i);
|
||||
|
||||
if (charaNameId.empty() || cardId.starts_with(checkStartCharaId)) {
|
||||
std::string musicIdStr = targetId->ToString();
|
||||
// Log::DebugFmt("Add cardId: %s, musicId: %s", cardId.c_str(), musicIdStr.c_str());
|
||||
if (std::find(ret.begin(), ret.end(), musicIdStr) == ret.end()) {
|
||||
ret.emplace_back(musicIdStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* AddIdsToUserDataCollectionFromMaster(void* origList, std::vector<std::string>& allIds,
|
||||
UnityResolve::Method* get_CostumeId, UnityResolve::Method* set_CostumeId, UnityResolve::Method* Clone) {
|
||||
std::unordered_set<std::string> existIds{};
|
||||
Il2cppUtils::Tools::CSListEditor listEditor(origList);
|
||||
if (listEditor.get_Count() <= 0) {
|
||||
return origList;
|
||||
}
|
||||
|
||||
for (auto i : listEditor) {
|
||||
auto costumeId = get_CostumeId->Invoke<Il2cppString*>(i);
|
||||
if (!costumeId) continue;
|
||||
existIds.emplace(costumeId->ToString());
|
||||
}
|
||||
|
||||
for (auto& i : allIds) {
|
||||
if (i.empty()) continue;
|
||||
// Log::DebugFmt("Try add %s", i.c_str());
|
||||
if (existIds.contains(i)) continue;
|
||||
|
||||
auto userCostume = Clone->Invoke<void*>(listEditor.get_Item(0));
|
||||
set_CostumeId->Invoke<void>(userCostume, Il2cppString::New(i));
|
||||
listEditor.Add(userCostume);
|
||||
}
|
||||
return origList;
|
||||
}
|
||||
|
||||
DEFINE_HOOK(void*, UserCostumeCollection_FindBy, (void* self, void* predicate, void* mtd)) {
|
||||
auto ret = UserCostumeCollection_FindBy_Orig(self, predicate, mtd);
|
||||
if (!Config::unlockAllLiveCostume) return ret;
|
||||
|
||||
auto this_klass = Il2cppUtils::get_class_from_instance(self);
|
||||
// auto predicate_klass = Il2cppUtils::get_class_from_instance(predicate); // System::Predicate`1
|
||||
// Log::DebugFmt("UserCostumeCollection_FindBy this: %s::%s, predicate: %s::%s", this_klass->namespaze, this_klass->name,
|
||||
// predicate_klass->namespaze, predicate_klass->name);
|
||||
|
||||
static auto UserCostumeCollection_klass = Il2cppUtils::GetClass("Assembly-CSharp.dll", "Campus.Common.User",
|
||||
"UserCostumeCollection");
|
||||
static auto UserCostumeCollection_GetAllList_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(
|
||||
UserCostumeCollection_klass->address, "GetAllList", 1);
|
||||
static auto UserCostumeCollection_GetAllList = reinterpret_cast<void* (*)(void*, void*)>(UserCostumeCollection_GetAllList_mtd->methodPointer);
|
||||
|
||||
std::string thisKlassName(this_klass->name);
|
||||
// Campus.Common.User::UserCostumeHeadCollection || Campus.Common.User::UserCostumeCollection
|
||||
// 两个 class 的 GetAllList 均使用的父类 Qua.UserDataManagement.UserDataCollectionBase`2 的方法,地址一致
|
||||
if (thisKlassName == "UserCostumeHeadCollection") {
|
||||
static auto UserCostume_Clone = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Transaction", "UserCostumeHead", "Clone");
|
||||
static auto UserCostume_get_CostumeHeadId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Transaction", "UserCostumeHead", "get_CostumeHeadId");
|
||||
static auto UserCostume_set_CostumeHeadId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Transaction", "UserCostumeHead", "set_CostumeHeadId");
|
||||
|
||||
// auto ret_klass = Il2cppUtils::get_class_from_instance(ret); // WhereEnumerableIterator
|
||||
auto origList = UserCostumeCollection_GetAllList(self, nullptr);
|
||||
|
||||
auto allIds = GetIdolMusicIdAll("", GetIdolIdType::CostumeHeadId);
|
||||
|
||||
// List<Campus.Common.Proto.Client.Transaction.UserCostumeHead>
|
||||
return AddIdsToUserDataCollectionFromMaster(origList, allIds, UserCostume_get_CostumeHeadId, UserCostume_set_CostumeHeadId, UserCostume_Clone);
|
||||
}
|
||||
else if (thisKlassName == "UserCostumeCollection") {
|
||||
// static auto UserCostume_klass = Il2cppUtils::GetClass("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Transaction", "UserCostume");
|
||||
static auto UserCostume_Clone = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Transaction", "UserCostume", "Clone");
|
||||
static auto UserCostume_get_CostumeId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Transaction", "UserCostume", "get_CostumeId");
|
||||
static auto UserCostume_set_CostumeId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Transaction", "UserCostume", "set_CostumeId");
|
||||
|
||||
// auto ret_klass = Il2cppUtils::get_class_from_instance(ret); // WhereEnumerableIterator
|
||||
auto origList = UserCostumeCollection_GetAllList(self, nullptr);
|
||||
|
||||
auto allIds = GetIdolMusicIdAll("", GetIdolIdType::CostumeId);
|
||||
|
||||
// List<Campus.Common.Proto.Client.Transaction.UserCostume>
|
||||
return AddIdsToUserDataCollectionFromMaster(origList, allIds, UserCostume_get_CostumeId, UserCostume_set_CostumeId, UserCostume_Clone);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* PictureBookWindowPresenter_instance = nullptr;
|
||||
std::string PictureBookWindowPresenter_charaId;
|
||||
DEFINE_HOOK(void*, PictureBookWindowPresenter_GetLiveMusics, (void* self, Il2cppString* charaId, void* mtd)) {
|
||||
// Log::DebugFmt("GetLiveMusics: %s", charaId->ToString().c_str());
|
||||
|
||||
if (Config::unlockAllLive) {
|
||||
PictureBookWindowPresenter_instance = self;
|
||||
PictureBookWindowPresenter_charaId = charaId->ToString();
|
||||
|
||||
static auto PictureBookWindowPresenter_klass = Il2cppUtils::GetClass("Assembly-CSharp.dll", "Campus.OutGame",
|
||||
"PictureBookWindowPresenter");
|
||||
static auto existsMusicIds_field = PictureBookWindowPresenter_klass->Get<UnityResolve::Field>("_existsMusicIds");
|
||||
auto existsMusicIds = Il2cppUtils::ClassGetFieldValue<UnityResolve::UnityType::List<Il2cppString*>*>(self, existsMusicIds_field);
|
||||
|
||||
if (!existsMusicIds) {
|
||||
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<void (*)(void*, void*)>(List_String_ctor_mtd->methodPointer);
|
||||
|
||||
auto newList = UnityResolve::Invoke<void*>("il2cpp_object_new", List_String_klass);
|
||||
List_String_ctor(newList, List_String_ctor_mtd);
|
||||
Il2cppUtils::Tools::CSListEditor<Il2cppString*> newListEditor(newList);
|
||||
|
||||
auto fullIds = GetIdolMusicIdAll();
|
||||
|
||||
for (auto& i : fullIds) {
|
||||
// Log::DebugFmt("GetLiveMusics - Add: %s", i.c_str());
|
||||
newListEditor.Add(Il2cppString::New(i));
|
||||
}
|
||||
Il2cppUtils::ClassSetFieldValue(self, existsMusicIds_field, newList);
|
||||
// Log::DebugFmt("GetLiveMusics - set end: %d", fullIds.size());
|
||||
}
|
||||
}
|
||||
|
||||
return PictureBookWindowPresenter_GetLiveMusics_Orig(self, charaId, mtd);
|
||||
}
|
||||
|
||||
DEFINE_HOOK(void, PictureBookLiveSelectScreenModel_ctor, (void* self, void* transitionParam, UnityResolve::UnityType::List<void*>* musics, void* mtd)) {
|
||||
// Log::DebugFmt("PictureBookLiveSelectScreenModel_ctor");
|
||||
|
||||
if (Config::unlockAllLive) {
|
||||
static auto GetLiveMusics = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.OutGame",
|
||||
"PictureBookWindowPresenter", "GetLiveMusics");
|
||||
if (PictureBookWindowPresenter_instance && !PictureBookWindowPresenter_charaId.empty()) {
|
||||
auto fullMusics = GetLiveMusics->Invoke<UnityResolve::UnityType::List<void*>*>(PictureBookWindowPresenter_instance,
|
||||
Il2cppString::New(PictureBookWindowPresenter_charaId));
|
||||
return PictureBookLiveSelectScreenModel_ctor_Orig(self, transitionParam, fullMusics, mtd);
|
||||
}
|
||||
}
|
||||
|
||||
return PictureBookLiveSelectScreenModel_ctor_Orig(self, transitionParam, musics, mtd);
|
||||
}
|
||||
|
||||
bool needRestoreHides = false;
|
||||
DEFINE_HOOK(void*, PictureBookLiveSelectScreenPresenter_MoveLiveScene, (void* self, void* produceLive,
|
||||
Il2cppString* characterId, Il2cppString* costumeId, Il2cppString* costumeHeadId)) {
|
||||
Il2cppString* characterId, Il2cppString* idolCardId, Il2cppString* costumeId, Il2cppString* costumeHeadId, void* mtd)) {
|
||||
needRestoreHides = false;
|
||||
Log::InfoFmt("MoveLiveScene: characterId: %s, costumeId: %s, costumeHeadId: %s,",
|
||||
characterId->ToString().c_str(), costumeId->ToString().c_str(), costumeHeadId->ToString().c_str());
|
||||
Log::InfoFmt("MoveLiveScene: characterId: %s, idolCardId: %s, costumeId: %s, costumeHeadId: %s,",
|
||||
characterId->ToString().c_str(), idolCardId->ToString().c_str(), costumeId->ToString().c_str(), costumeHeadId->ToString().c_str());
|
||||
|
||||
/*
|
||||
characterId: hski, costumeId: hski-cstm-0002, costumeHeadId: costume_head_hski-cstm-0002,
|
||||
@@ -488,15 +913,22 @@ namespace GakumasLocal::HookMain {
|
||||
|
||||
if (Config::dbgMode && Config::enableLiveCustomeDress) {
|
||||
// 修改 LiveFixedData_GetCharacter 可以更改 Loading 角色和演唱者名字,而不变更实际登台人
|
||||
return PictureBookLiveSelectScreenPresenter_MoveLiveScene_Orig(self, produceLive, characterId,
|
||||
return PictureBookLiveSelectScreenPresenter_MoveLiveScene_Orig(self, produceLive, characterId, idolCardId,
|
||||
Config::liveCustomeCostumeId.empty() ? costumeId : Il2cppString::New(Config::liveCustomeCostumeId),
|
||||
Config::liveCustomeHeadId.empty() ? costumeHeadId : Il2cppString::New(Config::liveCustomeHeadId));
|
||||
Config::liveCustomeHeadId.empty() ? costumeHeadId : Il2cppString::New(Config::liveCustomeHeadId),
|
||||
mtd);
|
||||
}
|
||||
|
||||
return PictureBookLiveSelectScreenPresenter_MoveLiveScene_Orig(self, produceLive, characterId, costumeId, costumeHeadId);
|
||||
return PictureBookLiveSelectScreenPresenter_MoveLiveScene_Orig(self, produceLive, characterId, idolCardId, costumeId, costumeHeadId, mtd);
|
||||
}
|
||||
|
||||
// std::string lastMusicId;
|
||||
#ifdef GKMS_WINDOWS
|
||||
DEFINE_HOOK(void*, PictureBookLiveSelectScreenPresenter_OnSelectMusic, (void* retstr, void* self, void* itemModel, void* ct, void* mtd)) {
|
||||
// if (!itemModel) return nullptr;
|
||||
return PictureBookLiveSelectScreenPresenter_OnSelectMusic_Orig(retstr, self, itemModel, ct, mtd);
|
||||
}
|
||||
#else
|
||||
DEFINE_HOOK(void, PictureBookLiveSelectScreenPresenter_OnSelectMusic, (void* self, void* itemModel, void* ct, void* mtd)) {
|
||||
/* // 修改角色后,Live 结束返回时, itemModel 为 null
|
||||
Log::DebugFmt("OnSelectMusic itemModel at %p", itemModel);
|
||||
@@ -530,6 +962,7 @@ namespace GakumasLocal::HookMain {
|
||||
if (!itemModel) return;
|
||||
return PictureBookLiveSelectScreenPresenter_OnSelectMusic_Orig(self, itemModel, ct, mtd);
|
||||
}
|
||||
#endif
|
||||
|
||||
DEFINE_HOOK(bool, VLDOF_IsActive, (void* self)) {
|
||||
if (Config::enableFreeCamera) return false;
|
||||
@@ -733,6 +1166,81 @@ namespace GakumasLocal::HookMain {
|
||||
CampusActorController_LateUpdate_Orig(self, mtd);
|
||||
}
|
||||
|
||||
DEFINE_HOOK(bool, PlatformInformation_get_IsAndroid, ()) {
|
||||
if (Config::loginAsIOS) {
|
||||
return false;
|
||||
}
|
||||
// Log::DebugFmt("PlatformInformation_get_IsAndroid: 0x%x", ret);
|
||||
return PlatformInformation_get_IsAndroid_Orig();
|
||||
}
|
||||
|
||||
DEFINE_HOOK(bool, PlatformInformation_get_IsIOS, ()) {
|
||||
if (Config::loginAsIOS) {
|
||||
return true;
|
||||
}
|
||||
// Log::DebugFmt("PlatformInformation_get_IsIOS: 0x%x", ret);
|
||||
return PlatformInformation_get_IsIOS_Orig();
|
||||
}
|
||||
|
||||
DEFINE_HOOK(Il2cppString*, ApiBase_GetPlatformString, (void* self, void* mtd)) {
|
||||
if (Config::loginAsIOS) {
|
||||
return Il2cppString::New("iOS");
|
||||
}
|
||||
// Log::DebugFmt("ApiBase_GetPlatformString: %s", ret->ToString().c_str());
|
||||
return ApiBase_GetPlatformString_Orig(self, mtd);
|
||||
}
|
||||
|
||||
void ProcessApiBase(void* self) {
|
||||
static void* processedIOS = nullptr;
|
||||
|
||||
if (Config::loginAsIOS) {
|
||||
if (self == processedIOS) return;
|
||||
|
||||
static auto ApiBase_klass = Il2cppUtils::get_class_from_instance(self);
|
||||
static auto platform_field = UnityResolve::Invoke<Il2cppUtils::FieldInfo*>("il2cpp_class_get_field_from_name", ApiBase_klass, "_platform");
|
||||
auto platform = Il2cppUtils::ClassGetFieldValue<Il2cppString*>(self, platform_field);
|
||||
Log::DebugFmt("ProcessApiBase platform: %s", platform ? platform->ToString().c_str() : "null");
|
||||
if (platform) {
|
||||
const auto origPlatform = platform->ToString();
|
||||
if (origPlatform != "iOS") {
|
||||
Il2cppUtils::ClassSetFieldValue(self, platform_field, Il2cppString::New("iOS"));
|
||||
processedIOS = self;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Il2cppUtils::ClassSetFieldValue(self, platform_field, Il2cppString::New("iOS"));
|
||||
processedIOS = self;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (processedIOS) {
|
||||
Log::DebugFmt("Restore API");
|
||||
static auto ApiBase_klass = Il2cppUtils::get_class_from_instance(self);
|
||||
static auto platform_field = UnityResolve::Invoke<Il2cppUtils::FieldInfo*>("il2cpp_class_get_field_from_name", ApiBase_klass, "_platform");
|
||||
#ifdef GKMS_WINDOWS
|
||||
Il2cppUtils::ClassSetFieldValue(self, platform_field, Il2cppString::New("dmm"));
|
||||
#else
|
||||
Il2cppUtils::ClassSetFieldValue(self, platform_field, Il2cppString::New("Android"));
|
||||
#endif
|
||||
processedIOS = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DEFINE_HOOK(void, ApiBase_ctor, (void* self, void* mtd)) {
|
||||
ApiBase_ctor_Orig(self, mtd);
|
||||
ProcessApiBase(self);
|
||||
}
|
||||
|
||||
DEFINE_HOOK(void*, ApiBase_get_Instance, (void* mtd)) {
|
||||
auto ret = ApiBase_get_Instance_Orig(mtd);
|
||||
if (ret) {
|
||||
ProcessApiBase(ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
void UpdateSwingBreastBonesData(void* initializeData) {
|
||||
if (!Config::enableBreastParam) return;
|
||||
static auto CampusActorAnimationInitializeData_klass = Il2cppUtils::GetClass("campus-submodule.Runtime.dll", "ActorAnimation",
|
||||
@@ -838,10 +1346,82 @@ namespace GakumasLocal::HookMain {
|
||||
return CampusActorAnimation_Setup_Orig(self, rootTrans, initializeData);
|
||||
}
|
||||
|
||||
/*
|
||||
std::map<std::string, std::pair<uintptr_t, void*>> findByKeyHookAddress{};
|
||||
void* FindByKeyHooks(void* self, void* key, void* mtd) {
|
||||
auto self_klass = Il2cppUtils::get_class_from_instance(self);
|
||||
|
||||
if (auto it = findByKeyHookAddress.find(self_klass->name); it != findByKeyHookAddress.end()) {
|
||||
Log::DebugFmt("FindByKeyHooks Call cache: %s, %p, %p", self_klass->name, it->second.first, it->second.second);
|
||||
return reinterpret_cast<decltype(FindByKeyHooks)*>(it->second.second)(self, key, mtd);
|
||||
}
|
||||
Log::DebugFmt("FindByKeyHooks not in cache: %s", self_klass->name);
|
||||
|
||||
auto FindByKey_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(self_klass, "FindByKey", 1);
|
||||
for (auto& [k, v] : findByKeyHookAddress) {
|
||||
if (FindByKey_mtd->methodPointer == v.first) {
|
||||
findByKeyHookAddress.emplace(self_klass->name, std::make_pair(FindByKey_mtd->methodPointer, v.second));
|
||||
Log::DebugFmt("FindByKeyHooks add to cache: %s", self_klass->name);
|
||||
return reinterpret_cast<decltype(FindByKeyHooks)*>(v.second)(self, key, mtd);
|
||||
}
|
||||
}
|
||||
|
||||
Log::ErrorFmt("FindByKeyHooks not found hook: %s", self_klass->name);
|
||||
return SHADOWHOOK_CALL_PREV(FindByKeyHooks, self, key, mtd);
|
||||
}
|
||||
|
||||
static inline std::vector<void(*)(HookInstaller* hookInstaller)> g_registerMasterFindByKeyHookFuncs;
|
||||
|
||||
#define DEF_AND_ADD_MASTER_FINDBYKEY_HOOK(name) \
|
||||
using name##_FindByKey_Type = void* (*)(void* self, void* key, void* idx, void* mtd); \
|
||||
inline name##_FindByKey_Type name##_FindByKey_Addr = nullptr; \
|
||||
inline void* name##_FindByKey_Orig = nullptr; \
|
||||
inline void* name##_FindByKey_Hook(void* self, void* key, void* idx, void* mtd) { \
|
||||
auto result = reinterpret_cast<decltype(name##_FindByKey_Hook)*>( \
|
||||
name##_FindByKey_Orig)(self, key, idx, mtd); \
|
||||
LocalizeFindByKey(result, self); \
|
||||
return result; \
|
||||
} \
|
||||
inline void name##_RegisterHook(HookInstaller* hookInstaller) { \
|
||||
auto klass = Il2cppUtils::GetClass( \
|
||||
"Assembly-CSharp.dll", "Campus.Common.Master", #name); \
|
||||
auto mtd = Il2cppUtils::il2cpp_class_get_method_from_name( \
|
||||
klass->address, "GetData", 2); \
|
||||
ADD_HOOK(name##_FindByKey, mtd->methodPointer); \
|
||||
} \
|
||||
struct name##_RegisterHookPusher { \
|
||||
name##_RegisterHookPusher() { \
|
||||
g_registerMasterFindByKeyHookFuncs.push_back(&name##_RegisterHook);\
|
||||
} \
|
||||
} g_##name##_RegisterHookPusherInst;
|
||||
|
||||
DEF_AND_ADD_MASTER_FINDBYKEY_HOOK(AchievementMaster)
|
||||
DEF_AND_ADD_MASTER_FINDBYKEY_HOOK(ProduceSkillMaster)
|
||||
DEF_AND_ADD_MASTER_FINDBYKEY_HOOK(FeatureLockMaster)
|
||||
DEF_AND_ADD_MASTER_FINDBYKEY_HOOK(ProduceCardMaster)
|
||||
|
||||
// 安装 DEF_AND_ADD_MASTER_FINDBYKEY_HOOK 的 hook
|
||||
void InitMasterHooks(HookInstaller* hookInstaller) {
|
||||
for (auto& func : g_registerMasterFindByKeyHookFuncs) {
|
||||
func(hookInstaller);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
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);
|
||||
GakumasLocal::WinHooks::Keyboard::InstallWndProcHook();
|
||||
#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)"));
|
||||
@@ -867,6 +1447,39 @@ namespace GakumasLocal::HookMain {
|
||||
|
||||
ADD_HOOK(TextField_set_value, Il2cppUtils::GetMethodPointer("UnityEngine.UIElementsModule.dll", "UnityEngine.UIElements",
|
||||
"TextField", "set_value"));
|
||||
/* SQL 查询相关函数,不好用
|
||||
// 下面是 byte[] u8 string 转 std::string 的例子
|
||||
auto query = reinterpret_cast<UnityResolve::UnityType::Array<UnityResolve::UnityType::Byte>*>(mtd);
|
||||
auto data_ptr = reinterpret_cast<std::uint8_t*>(query->GetData());
|
||||
std::string qS(data_ptr, data_ptr + lastLength);
|
||||
|
||||
ADD_HOOK(PreparedStatement_ExecuteQuery, Il2cppUtils::GetMethodPointer("quaunity-master-manager.Runtime.dll", "Qua.Master.SQLite",
|
||||
"PreparedStatement", "ExecuteQuery", {"System.String"}));
|
||||
ADD_HOOK(PreparedStatement_ExecuteQuery_u8, Il2cppUtils::GetMethodPointer("quaunity-master-manager.Runtime.dll", "Qua.Master.SQLite",
|
||||
"PreparedStatement", "ExecuteQuery", {"*", "*"}));
|
||||
ADD_HOOK(PreparedStatement_FinalizeStatement, Il2cppUtils::GetMethodPointer("quaunity-master-manager.Runtime.dll", "Qua.Master.SQLite",
|
||||
"PreparedStatement", "FinalizeStatement"));
|
||||
*/
|
||||
|
||||
// ADD_HOOK(EffectGroup_ctor, Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Master",
|
||||
// "EffectGroup", ".ctor"));
|
||||
|
||||
ADD_HOOK(MessageExtensions_MergeFrom, Il2cppUtils::GetMethodPointer("Google.Protobuf.dll", "Google.Protobuf",
|
||||
"MessageExtensions", "MergeFrom", {"Google.Protobuf.IMessage", "System.ReadOnlySpan<System.Byte>"}));
|
||||
|
||||
/* // 此 block 为 MasterBase 相关的 hook,后来发现它们最后都会调用 MessageExtensions.MergeFrom 进行构造,遂停用。现留档以备用
|
||||
// ADD_HOOK(MasterBase_GetAll, Il2cppUtils::GetMethodPointer("quaunity-master-manager.Runtime.dll", "Qua.Master",
|
||||
// "MasterBase`2", "GetAll", {"*", "*", "*", "*", "*"}));
|
||||
|
||||
// 安装 DEF_AND_ADD_MASTER_FINDBYKEY_HOOK 的 hook
|
||||
InitMasterHooks(hookInstaller);
|
||||
|
||||
auto AchievementMaster_klass = Il2cppUtils::GetClass("Assembly-CSharp.dll", "Campus.Common.Master", "AchievementMaster");
|
||||
auto AchievementMaster_GetAll_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(AchievementMaster_klass->address, "GetAll", 5);
|
||||
// auto AchievementMaster_FindByKey_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(AchievementMaster_klass->address, "FindByKey", 1);
|
||||
// Log::DebugFmt("AchievementMaster_GetAll_mtd at %p", AchievementMaster_GetAll_mtd);
|
||||
ADD_HOOK(MasterBase_GetAll, AchievementMaster_GetAll_mtd->methodPointer);
|
||||
*/
|
||||
|
||||
ADD_HOOK(OctoCaching_GetResourceFileName, Il2cppUtils::GetMethodPointer("Octo.dll", "Octo.Caching",
|
||||
"OctoCaching", "GetResourceFileName"));
|
||||
@@ -880,16 +1493,62 @@ namespace GakumasLocal::HookMain {
|
||||
Il2cppUtils::GetMethodPointer("Octo.dll", "Octo",
|
||||
"OnDownloadProgress", "Invoke"));
|
||||
|
||||
ADD_HOOK(PictureBookLiveThumbnailView_SetData,
|
||||
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame.PictureBook",
|
||||
"PictureBookLiveThumbnailView", "SetDataAsync", {"*", "*", "*", "*"}));
|
||||
/*
|
||||
auto UserDataManager_klass = Il2cppUtils::GetClass("Assembly-CSharp.dll", "Campus.Common.User",
|
||||
"UserDataManager");
|
||||
if (UserDataManager_klass) {
|
||||
auto UserDataManagerBase_klass = UnityResolve::Invoke<Il2cppUtils::Il2CppClassHead*>("il2cpp_class_get_parent", UserDataManager_klass->address);
|
||||
if (UserDataManagerBase_klass) {
|
||||
auto get_userIdolCardSkinList_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(UserDataManagerBase_klass, "get__userIdolCardSkinList", 0);
|
||||
if (get_userIdolCardSkinList_mtd) {
|
||||
ADD_HOOK(UserDataManagerBase_get__userIdolCardSkinList, get_userIdolCardSkinList_mtd->methodPointer);
|
||||
}
|
||||
auto get_userCostumeList_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(UserDataManagerBase_klass, "get__userCostumeList", 0);
|
||||
if (get_userCostumeList_mtd) {
|
||||
ADD_HOOK(UserDataManagerBase_get__userCostumeList, get_userCostumeList_mtd->methodPointer);
|
||||
}
|
||||
auto get_userCostumeHeadList_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(UserDataManagerBase_klass, "get__userCostumeHeadList", 0);
|
||||
if (get_userCostumeHeadList_mtd) {
|
||||
ADD_HOOK(UserDataManagerBase_get__userCostumeHeadList, get_userCostumeHeadList_mtd->methodPointer);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
auto UserIdolCardSkinCollection_klass = Il2cppUtils::GetClass("Assembly-CSharp.dll", "Campus.Common.User",
|
||||
"UserIdolCardSkinCollection");
|
||||
auto UserIdolCardSkinCollection_Exists_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(UserIdolCardSkinCollection_klass->address, "Exists", 1);
|
||||
if (UserIdolCardSkinCollection_Exists_mtd) {
|
||||
ADD_HOOK(UserIdolCardSkinCollection_Exists, UserIdolCardSkinCollection_Exists_mtd->methodPointer);
|
||||
}
|
||||
|
||||
auto UserCostumeCollection_klass = Il2cppUtils::GetClass("Assembly-CSharp.dll", "Campus.Common.User",
|
||||
"UserCostumeCollection");
|
||||
auto UserCostumeCollection_FindBy_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(
|
||||
UserCostumeCollection_klass->address, "FindBy", 1);
|
||||
if (UserCostumeCollection_FindBy_mtd) {
|
||||
ADD_HOOK(UserCostumeCollection_FindBy, UserCostumeCollection_FindBy_mtd->methodPointer);
|
||||
}
|
||||
|
||||
// 双端
|
||||
ADD_HOOK(PictureBookLiveThumbnailView_SetDataAsync,
|
||||
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame.PictureBook",
|
||||
"PictureBookLiveThumbnailView", "SetDataAsync", { "*", "*", "*", "*", "*" }));
|
||||
|
||||
ADD_HOOK(PictureBookWindowPresenter_GetLiveMusics,
|
||||
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame",
|
||||
"PictureBookWindowPresenter", "GetLiveMusics"));
|
||||
ADD_HOOK(PictureBookLiveSelectScreenModel_ctor,
|
||||
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame",
|
||||
"PictureBookLiveSelectScreenModel", ".ctor"));
|
||||
|
||||
ADD_HOOK(PictureBookLiveSelectScreenPresenter_MoveLiveScene,
|
||||
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame",
|
||||
"PictureBookLiveSelectScreenPresenter", "MoveLiveScene"));
|
||||
|
||||
// 双端
|
||||
ADD_HOOK(PictureBookLiveSelectScreenPresenter_OnSelectMusic,
|
||||
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame",
|
||||
"PictureBookLiveSelectScreenPresenter", "OnSelectMusicAsync"));
|
||||
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame",
|
||||
"PictureBookLiveSelectScreenPresenter", "OnSelectMusicAsync"));
|
||||
|
||||
ADD_HOOK(VLDOF_IsActive,
|
||||
Il2cppUtils::GetMethodPointer("Unity.RenderPipelines.Universal.Runtime.dll", "VL.Rendering",
|
||||
@@ -918,6 +1577,24 @@ namespace GakumasLocal::HookMain {
|
||||
ADD_HOOK(CampusActorController_LateUpdate,
|
||||
Il2cppUtils::GetMethodPointer("campus-submodule.Runtime.dll", "Campus.Common",
|
||||
"CampusActorController", "LateUpdate"));
|
||||
|
||||
ADD_HOOK(PlatformInformation_get_IsAndroid, Il2cppUtils::GetMethodPointer("Firebase.Platform.dll", "Firebase.Platform",
|
||||
"PlatformInformation", "get_IsAndroid"));
|
||||
ADD_HOOK(PlatformInformation_get_IsIOS, Il2cppUtils::GetMethodPointer("Firebase.Platform.dll", "Firebase.Platform",
|
||||
"PlatformInformation", "get_IsIOS"));
|
||||
|
||||
auto api_klass = Il2cppUtils::GetClass("Assembly-CSharp.dll", "Campus.Common.Network", "Api");
|
||||
if (api_klass) {
|
||||
// Qua.Network.ApiBase
|
||||
auto api_parent = UnityResolve::Invoke<Il2cppUtils::Il2CppClassHead*>("il2cpp_class_get_parent", api_klass->address);
|
||||
if (api_parent) {
|
||||
// Log::DebugFmt("api_parent at %p, name: %s::%s", api_parent, api_parent->namespaze, api_parent->name);
|
||||
ADD_HOOK(ApiBase_GetPlatformString, Il2cppUtils::il2cpp_class_get_method_pointer_from_name(api_parent, "GetPlatformString", 0));
|
||||
ADD_HOOK(ApiBase_ctor, Il2cppUtils::il2cpp_class_get_method_pointer_from_name(api_parent, ".ctor", 0));
|
||||
ADD_HOOK(ApiBase_get_Instance, Il2cppUtils::il2cpp_class_get_method_pointer_from_name(api_parent, "get_Instance", 0));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
static auto CampusActorController_klass = Il2cppUtils::GetClass("campus-submodule.Runtime.dll",
|
||||
"Campus.Common", "CampusActorController");
|
||||
@@ -938,9 +1615,10 @@ namespace GakumasLocal::HookMain {
|
||||
ADD_HOOK(Internal_Log, Il2cppUtils::il2cpp_resolve_icall(
|
||||
"UnityEngine.DebugLogHandler::Internal_Log(UnityEngine.LogType,UnityEngine.LogOption,System.String,UnityEngine.Object)"));
|
||||
|
||||
// 双端
|
||||
ADD_HOOK(InternalSetOrientationAsync,
|
||||
Il2cppUtils::GetMethodPointer("campus-submodule.Runtime.dll", "Campus.Common",
|
||||
"ScreenOrientationControllerBase", "InternalSetOrientationAsync"));
|
||||
Il2cppUtils::GetMethodPointer("campus-submodule.Runtime.dll", "Campus.Common",
|
||||
"ScreenOrientationControllerBase", "InternalSetOrientationAsync"));
|
||||
|
||||
ADD_HOOK(Unity_set_position_Injected, Il2cppUtils::il2cpp_resolve_icall(
|
||||
"UnityEngine.Transform::set_position_Injected(UnityEngine.Vector3&)"));
|
||||
@@ -954,11 +1632,22 @@ 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();
|
||||
GkmsResourceUpdate::CheckUpdateFromAPI(false);
|
||||
#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...");
|
||||
@@ -991,11 +1680,9 @@ namespace GakumasLocal::HookMain {
|
||||
}
|
||||
|
||||
Local::LoadData();
|
||||
MasterLocal::LoadData();
|
||||
|
||||
if (Config::lazyInit) {
|
||||
UnityResolveProgress::classProgress.current = 1;
|
||||
UnityResolveProgress::startInit = false;
|
||||
}
|
||||
UnityResolveProgress::startInit = false;
|
||||
|
||||
Log::Info("Plugin init finished.");
|
||||
return ret;
|
||||
@@ -1009,8 +1696,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");
|
||||
}
|
||||
|
||||
@@ -14,14 +14,96 @@ namespace Il2cppUtils {
|
||||
const char* namespaze;
|
||||
};
|
||||
|
||||
struct Il2CppObject
|
||||
{
|
||||
union
|
||||
{
|
||||
void* klass;
|
||||
void* vtable;
|
||||
};
|
||||
void* monitor;
|
||||
};
|
||||
|
||||
enum Il2CppTypeEnum
|
||||
{
|
||||
IL2CPP_TYPE_END = 0x00, /* End of List */
|
||||
IL2CPP_TYPE_VOID = 0x01,
|
||||
IL2CPP_TYPE_BOOLEAN = 0x02,
|
||||
IL2CPP_TYPE_CHAR = 0x03,
|
||||
IL2CPP_TYPE_I1 = 0x04,
|
||||
IL2CPP_TYPE_U1 = 0x05,
|
||||
IL2CPP_TYPE_I2 = 0x06,
|
||||
IL2CPP_TYPE_U2 = 0x07,
|
||||
IL2CPP_TYPE_I4 = 0x08,
|
||||
IL2CPP_TYPE_U4 = 0x09,
|
||||
IL2CPP_TYPE_I8 = 0x0a,
|
||||
IL2CPP_TYPE_U8 = 0x0b,
|
||||
IL2CPP_TYPE_R4 = 0x0c,
|
||||
IL2CPP_TYPE_R8 = 0x0d,
|
||||
IL2CPP_TYPE_STRING = 0x0e,
|
||||
IL2CPP_TYPE_PTR = 0x0f,
|
||||
IL2CPP_TYPE_BYREF = 0x10,
|
||||
IL2CPP_TYPE_VALUETYPE = 0x11,
|
||||
IL2CPP_TYPE_CLASS = 0x12,
|
||||
IL2CPP_TYPE_VAR = 0x13,
|
||||
IL2CPP_TYPE_ARRAY = 0x14,
|
||||
IL2CPP_TYPE_GENERICINST = 0x15,
|
||||
IL2CPP_TYPE_TYPEDBYREF = 0x16,
|
||||
IL2CPP_TYPE_I = 0x18,
|
||||
IL2CPP_TYPE_U = 0x19,
|
||||
IL2CPP_TYPE_FNPTR = 0x1b,
|
||||
IL2CPP_TYPE_OBJECT = 0x1c,
|
||||
IL2CPP_TYPE_SZARRAY = 0x1d,
|
||||
IL2CPP_TYPE_MVAR = 0x1e,
|
||||
IL2CPP_TYPE_CMOD_REQD = 0x1f,
|
||||
IL2CPP_TYPE_CMOD_OPT = 0x20,
|
||||
IL2CPP_TYPE_INTERNAL = 0x21,
|
||||
|
||||
IL2CPP_TYPE_MODIFIER = 0x40,
|
||||
IL2CPP_TYPE_SENTINEL = 0x41,
|
||||
IL2CPP_TYPE_PINNED = 0x45,
|
||||
|
||||
IL2CPP_TYPE_ENUM = 0x55
|
||||
};
|
||||
|
||||
typedef struct Il2CppType
|
||||
{
|
||||
void* dummy;
|
||||
unsigned int attrs : 16;
|
||||
Il2CppTypeEnum type : 8;
|
||||
unsigned int num_mods : 6;
|
||||
unsigned int byref : 1;
|
||||
unsigned int pinned : 1;
|
||||
} Il2CppType;
|
||||
|
||||
struct Il2CppReflectionType
|
||||
{
|
||||
Il2CppObject object;
|
||||
const Il2CppType* type;
|
||||
};
|
||||
|
||||
struct Resolution_t {
|
||||
int width;
|
||||
int height;
|
||||
int herz;
|
||||
};
|
||||
|
||||
struct FieldInfo {
|
||||
const char* name;
|
||||
const Il2CppType* type;
|
||||
uintptr_t parent;
|
||||
int32_t offset;
|
||||
uint32_t token;
|
||||
};
|
||||
|
||||
struct MethodInfo {
|
||||
uintptr_t methodPointer;
|
||||
uintptr_t invoker_method;
|
||||
const char* name;
|
||||
uintptr_t klass;
|
||||
//const Il2CppType* return_type;
|
||||
const Il2CppType* return_type;
|
||||
//const ParameterInfo* parameters;
|
||||
const void* return_type;
|
||||
// const void* return_type;
|
||||
const void* parameters;
|
||||
uintptr_t methodDefinition;
|
||||
uintptr_t genericContainer;
|
||||
@@ -36,13 +118,7 @@ namespace Il2cppUtils {
|
||||
uint8_t is_marshaled_from_native : 1;
|
||||
};
|
||||
|
||||
struct Resolution_t {
|
||||
int width;
|
||||
int height;
|
||||
int herz;
|
||||
};
|
||||
|
||||
UnityResolve::Class* GetClass(const std::string& assemblyName, const std::string& nameSpaceName,
|
||||
static UnityResolve::Class* GetClass(const std::string& assemblyName, const std::string& nameSpaceName,
|
||||
const std::string& className) {
|
||||
const auto assembly = UnityResolve::Get(assemblyName);
|
||||
if (!assembly) {
|
||||
@@ -81,7 +157,7 @@ namespace Il2cppUtils {
|
||||
return ret;
|
||||
}*/
|
||||
|
||||
UnityResolve::Method* GetMethod(const std::string& assemblyName, const std::string& nameSpaceName,
|
||||
static UnityResolve::Method* GetMethod(const std::string& assemblyName, const std::string& nameSpaceName,
|
||||
const std::string& className, const std::string& methodName, const std::vector<std::string>& args = {}) {
|
||||
const auto assembly = UnityResolve::Get(assemblyName);
|
||||
if (!assembly) {
|
||||
@@ -108,7 +184,7 @@ namespace Il2cppUtils {
|
||||
return method;
|
||||
}
|
||||
|
||||
void* GetMethodPointer(const std::string& assemblyName, const std::string& nameSpaceName,
|
||||
static void* GetMethodPointer(const std::string& assemblyName, const std::string& nameSpaceName,
|
||||
const std::string& className, const std::string& methodName, const std::vector<std::string>& args = {}) {
|
||||
auto method = GetMethod(assemblyName, nameSpaceName, className, methodName, args);
|
||||
if (method) {
|
||||
@@ -117,20 +193,28 @@ namespace Il2cppUtils {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* il2cpp_resolve_icall(const char* s) {
|
||||
static void* il2cpp_resolve_icall(const char* s) {
|
||||
return UnityResolve::Invoke<void*>("il2cpp_resolve_icall", s);
|
||||
}
|
||||
|
||||
Il2CppClassHead* get_class_from_instance(const void* instance) {
|
||||
static Il2CppClassHead* get_class_from_instance(const void* instance) {
|
||||
return static_cast<Il2CppClassHead*>(*static_cast<void* const*>(std::assume_aligned<alignof(void*)>(instance)));
|
||||
}
|
||||
|
||||
MethodInfo* il2cpp_class_get_method_from_name(void* klass, const char* name, int argsCount) {
|
||||
static MethodInfo* il2cpp_class_get_method_from_name(void* klass, const char* name, int argsCount) {
|
||||
return UnityResolve::Invoke<MethodInfo*>("il2cpp_class_get_method_from_name", klass, name, argsCount);
|
||||
}
|
||||
|
||||
void* find_nested_class(void* klass, std::predicate<void*> auto&& predicate)
|
||||
{
|
||||
static uintptr_t il2cpp_class_get_method_pointer_from_name(void* klass, const char* name, int argsCount) {
|
||||
auto findKlass = il2cpp_class_get_method_from_name(klass, name, argsCount);
|
||||
if (findKlass) {
|
||||
return findKlass->methodPointer;
|
||||
}
|
||||
Log::ErrorFmt("method: %s not found", name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void* find_nested_class(void* klass, std::predicate<void*> auto&& predicate) {
|
||||
void* iter{};
|
||||
while (const auto curNestedClass = UnityResolve::Invoke<void*>("il2cpp_class_get_nested_types", klass, &iter))
|
||||
{
|
||||
@@ -143,22 +227,191 @@ namespace Il2cppUtils {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* find_nested_class_from_name(void* klass, const char* name)
|
||||
{
|
||||
static void* find_nested_class_from_name(void* klass, const char* name) {
|
||||
return find_nested_class(klass, [name = std::string_view(name)](void* nestedClass) {
|
||||
return static_cast<Il2CppClassHead*>(nestedClass)->name == name;
|
||||
});
|
||||
}
|
||||
|
||||
template <typename RType>
|
||||
auto ClassGetFieldValue(void* obj, UnityResolve::Field* field) -> RType {
|
||||
static auto ClassGetFieldValue(void* obj, UnityResolve::Field* field) -> RType {
|
||||
return *reinterpret_cast<RType*>(reinterpret_cast<uintptr_t>(obj) + field->offset);
|
||||
}
|
||||
|
||||
template <typename RType>
|
||||
auto ClassSetFieldValue(void* obj, UnityResolve::Field* field, RType value) -> void {
|
||||
static auto ClassGetFieldValue(void* obj, FieldInfo* field) -> RType {
|
||||
return *reinterpret_cast<RType*>(reinterpret_cast<uintptr_t>(obj) + field->offset);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static auto ClassSetFieldValue(void* obj, UnityResolve::Field* field, T value) -> void {
|
||||
const auto fieldPtr = static_cast<std::byte*>(obj) + field->offset;
|
||||
std::memcpy(fieldPtr, std::addressof(value), sizeof(T));
|
||||
}
|
||||
|
||||
template <typename RType>
|
||||
static auto ClassSetFieldValue(void* obj, FieldInfo* field, RType value) -> void {
|
||||
*reinterpret_cast<RType*>(reinterpret_cast<uintptr_t>(obj) + field->offset) = value;
|
||||
}
|
||||
|
||||
static void* get_system_class_from_reflection_type_str(const char* typeStr, const char* assemblyName = "mscorlib") {
|
||||
using Il2CppString = UnityResolve::UnityType::String;
|
||||
|
||||
static auto assemblyLoad = reinterpret_cast<void* (*)(Il2CppString*)>(
|
||||
GetMethodPointer("mscorlib.dll", "System.Reflection",
|
||||
"Assembly", "Load", {"*"})
|
||||
);
|
||||
static auto assemblyGetType = reinterpret_cast<Il2CppReflectionType * (*)(void*, Il2CppString*)>(
|
||||
GetMethodPointer("mscorlib.dll", "System.Reflection",
|
||||
"Assembly", "GetType", {"*"})
|
||||
);
|
||||
|
||||
static auto reflectionAssembly = assemblyLoad(Il2CppString::New(assemblyName));
|
||||
auto reflectionType = assemblyGetType(reflectionAssembly, Il2CppString::New(typeStr));
|
||||
return UnityResolve::Invoke<void*>("il2cpp_class_from_system_type", reflectionType);
|
||||
}
|
||||
|
||||
static std::unordered_map<std::string, std::unordered_map<int, std::string>> enumToValueMapCache{};
|
||||
static std::unordered_map<int, std::string> EnumToValueMap(Il2CppClassHead* enumClass, bool useCache) {
|
||||
std::unordered_map<int, std::string> ret{};
|
||||
auto isEnum = UnityResolve::Invoke<bool>("il2cpp_class_is_enum", enumClass);
|
||||
|
||||
if (isEnum) {
|
||||
Il2cppUtils::FieldInfo* field = nullptr;
|
||||
void* iter = nullptr;
|
||||
|
||||
std::string cacheName = std::string(enumClass->namespaze) + "::" + enumClass->name;
|
||||
if (useCache) {
|
||||
if (auto it = enumToValueMapCache.find(cacheName); it != enumToValueMapCache.end()) {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
|
||||
while ((field = UnityResolve::Invoke<Il2cppUtils::FieldInfo*>("il2cpp_class_get_fields", enumClass, &iter))) {
|
||||
// Log::DebugFmt("field: %s, off: %d", field->name, field->offset);
|
||||
if (field->offset > 0) continue; // 非 static
|
||||
if (strcmp(field->name, "value__") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int value;
|
||||
UnityResolve::Invoke<void>("il2cpp_field_static_get_value", field, &value);
|
||||
// Log::DebugFmt("returnClass: %s - %s: 0x%x", enumClass->name, field->name, value);
|
||||
std::string itemName = std::string(enumClass->name) + "_" + field->name;
|
||||
ret.emplace(value, std::move(itemName));
|
||||
}
|
||||
|
||||
if (useCache) {
|
||||
enumToValueMapCache.emplace(std::move(cacheName), ret);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename T = void*>
|
||||
static void iterate_IEnumerable(const void* obj, std::invocable<T> auto&& receiver)
|
||||
{
|
||||
const auto klass = get_class_from_instance(obj);
|
||||
const auto getEnumeratorMethod = reinterpret_cast<void* (*)(const void*)>(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<T(*)(void*)>(il2cpp_class_get_method_from_name(enumeratorClass, "get_Current", 0)->methodPointer);
|
||||
const auto moveNextMethod = reinterpret_cast<bool(*)(void*)>(il2cpp_class_get_method_from_name(enumeratorClass, "MoveNext", 0)->methodPointer);
|
||||
|
||||
while (moveNextMethod(enumerator))
|
||||
{
|
||||
static_cast<decltype(receiver)>(receiver)(getCurrentMethod(enumerator));
|
||||
}
|
||||
}
|
||||
|
||||
namespace Tools {
|
||||
|
||||
template <typename T = void*>
|
||||
class CSListEditor {
|
||||
public:
|
||||
CSListEditor(void* list) {
|
||||
list_klass = get_class_from_instance(list);
|
||||
lst = list;
|
||||
|
||||
lst_get_Count_method = il2cpp_class_get_method_from_name(list_klass, "get_Count", 0);
|
||||
lst_get_Item_method = il2cpp_class_get_method_from_name(list_klass, "get_Item", 1);
|
||||
lst_set_Item_method = il2cpp_class_get_method_from_name(list_klass, "set_Item", 2);
|
||||
lst_Add_method = il2cpp_class_get_method_from_name(list_klass, "Add", 1);
|
||||
|
||||
lst_get_Count = reinterpret_cast<lst_get_Count_t>(lst_get_Count_method->methodPointer);
|
||||
lst_get_Item = reinterpret_cast<lst_get_Item_t>(lst_get_Item_method->methodPointer);
|
||||
lst_set_Item = reinterpret_cast<lst_set_Item_t>(lst_set_Item_method->methodPointer);
|
||||
lst_Add = reinterpret_cast<lst_Add_t>(lst_Add_method->methodPointer);
|
||||
}
|
||||
|
||||
void Add(T value) {
|
||||
lst_Add(lst, value, lst_Add_method);
|
||||
}
|
||||
|
||||
T get_Item(int index) {
|
||||
return lst_get_Item(lst, index, lst_get_Item_method);
|
||||
}
|
||||
|
||||
void set_Item(int index, T value) {
|
||||
return lst_set_Item(lst, index, value, lst_set_Item_method);
|
||||
}
|
||||
|
||||
int get_Count() {
|
||||
return lst_get_Count(lst, lst_get_Count_method);
|
||||
}
|
||||
|
||||
T operator[] (int key) {
|
||||
return get_Item(key);
|
||||
}
|
||||
|
||||
class Iterator {
|
||||
public:
|
||||
Iterator(CSListEditor<T>* editor, int index) : editor(editor), index(index) {}
|
||||
|
||||
T operator*() const {
|
||||
return editor->get_Item(index);
|
||||
}
|
||||
|
||||
Iterator& operator++() {
|
||||
++index;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator!=(const Iterator& other) const {
|
||||
return index != other.index;
|
||||
}
|
||||
|
||||
private:
|
||||
CSListEditor<T>* editor;
|
||||
int index;
|
||||
};
|
||||
|
||||
Iterator begin() {
|
||||
return Iterator(this, 0);
|
||||
}
|
||||
|
||||
Iterator end() {
|
||||
return Iterator(this, get_Count());
|
||||
}
|
||||
|
||||
void* lst;
|
||||
void* list_klass;
|
||||
private:
|
||||
typedef T(*lst_get_Item_t)(void*, int, void* mtd);
|
||||
typedef void(*lst_Add_t)(void*, T, void* mtd);
|
||||
typedef void(*lst_set_Item_t)(void*, int, T, void* mtd);
|
||||
typedef int(*lst_get_Count_t)(void*, void* mtd);
|
||||
|
||||
MethodInfo* lst_get_Item_method;
|
||||
MethodInfo* lst_Add_method;
|
||||
MethodInfo* lst_get_Count_method;
|
||||
MethodInfo* lst_set_Item_method;
|
||||
|
||||
lst_get_Item_t lst_get_Item;
|
||||
lst_set_Item_t lst_set_Item;
|
||||
lst_Add_t lst_Add;
|
||||
lst_get_Count_t lst_get_Count;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<char>(file)), std::istreambuf_iterator<char>());
|
||||
@@ -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;
|
||||
}
|
||||
@@ -521,6 +521,17 @@ namespace GakumasLocal::Local {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 匹配升级卡名
|
||||
if (auto plusPos = origText.find_last_not_of('+'); plusPos != std::string::npos) {
|
||||
const auto noPlusText = origText.substr(0, plusPos + 1);
|
||||
|
||||
if (const auto iter = genericText.find(noPlusText); iter != genericText.end()) {
|
||||
size_t plusCount = origText.length() - (plusPos + 1);
|
||||
*newStr = iter->second + std::string(plusCount, '+');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// fmt 文本
|
||||
auto fmtText = StringParser::ParseItems::parse(origText, false);
|
||||
if (fmtText.isValid) {
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace GakumasLocal::Local {
|
||||
extern std::unordered_set<std::string> translatedText;
|
||||
|
||||
std::filesystem::path GetBasePath();
|
||||
void LoadData();
|
||||
bool GetI18n(const std::string& key, std::string* ret);
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
#include "Log.h"
|
||||
#include <android/log.h>
|
||||
#include <Misc.hpp>
|
||||
#include "Misc.hpp"
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <queue>
|
||||
#include <cstdarg>
|
||||
|
||||
#ifndef GKMS_WINDOWS
|
||||
#include <android/log.h>
|
||||
|
||||
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
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
#ifndef GAKUMAS_LOCALIFY_LOG_H
|
||||
#define GAKUMAS_LOCALIFY_LOG_H
|
||||
|
||||
#include "../platformDefine.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <jni.h>
|
||||
|
||||
#ifndef GKMS_WINDOWS
|
||||
#include <jni.h>
|
||||
#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
|
||||
|
||||
809
app/src/main/cpp/GakumasLocalify/MasterLocal.cpp
Normal file
809
app/src/main/cpp/GakumasLocalify/MasterLocal.cpp
Normal file
@@ -0,0 +1,809 @@
|
||||
#include "MasterLocal.h"
|
||||
#include "Local.h"
|
||||
#include "Il2cppUtils.hpp"
|
||||
#include "config/Config.hpp"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
#include <regex>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace GakumasLocal::MasterLocal {
|
||||
using Il2cppString = UnityResolve::UnityType::String;
|
||||
|
||||
static std::unordered_map<std::string, Il2cppUtils::MethodInfo*> fieldSetCache;
|
||||
static std::unordered_map<std::string, Il2cppUtils::MethodInfo*> fieldGetCache;
|
||||
|
||||
enum class JsonValueType {
|
||||
JVT_String,
|
||||
JVT_Int,
|
||||
JVT_Object,
|
||||
JVT_ArrayObject,
|
||||
JVT_ArrayString,
|
||||
JVT_Unsupported,
|
||||
JVT_NeedMore_EmptyArray
|
||||
};
|
||||
|
||||
struct ItemRule {
|
||||
std::vector<std::string> mainPrimaryKey;
|
||||
std::map<std::string, std::vector<std::string>> subPrimaryKey;
|
||||
|
||||
std::vector<std::string> mainLocalKey;
|
||||
std::map<std::string, std::vector<std::string>> subLocalKey;
|
||||
};
|
||||
|
||||
struct TableLocalData {
|
||||
ItemRule itemRule;
|
||||
|
||||
std::unordered_map<std::string, JsonValueType> mainKeyType;
|
||||
std::unordered_map<std::string, std::unordered_map<std::string, JsonValueType>> subKeyType;
|
||||
|
||||
std::unordered_map<std::string, std::string> transData;
|
||||
std::unordered_map<std::string, std::vector<std::string>> transStrListData;
|
||||
|
||||
[[nodiscard]] JsonValueType GetMainKeyType(const std::string& mainKey) const {
|
||||
if (auto it = mainKeyType.find(mainKey); it != mainKeyType.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return JsonValueType::JVT_Unsupported;
|
||||
}
|
||||
|
||||
[[nodiscard]] JsonValueType GetSubKeyType(const std::string& parentKey, const std::string& subKey) const {
|
||||
if (auto it = subKeyType.find(parentKey); it != subKeyType.end()) {
|
||||
if (auto subIt = it->second.find(subKey); subIt != it->second.end()) {
|
||||
return subIt->second;
|
||||
}
|
||||
}
|
||||
return JsonValueType::JVT_Unsupported;
|
||||
}
|
||||
};
|
||||
|
||||
static std::unordered_map<std::string, TableLocalData> masterLocalData;
|
||||
|
||||
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<char>(std::toupper(result[0]));
|
||||
return result;
|
||||
}
|
||||
|
||||
Il2cppUtils::MethodInfo* GetGetSetMethodFromCache(const std::string& fieldName, int argsCount,
|
||||
std::unordered_map<std::string, Il2cppUtils::MethodInfo*>& 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) {
|
||||
if (!from) {
|
||||
self = nullptr;
|
||||
return;
|
||||
}
|
||||
self = from;
|
||||
self_klass = Il2cppUtils::get_class_from_instance(self);
|
||||
if (self_klass) {
|
||||
self_klass_name = self_klass->name;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T ReadField(const std::string& fieldName) {
|
||||
if (!self) return T();
|
||||
auto get_mtd = GetGetSetMethodFromCache(fieldName, 0, fieldGetCache, "get_");
|
||||
if (get_mtd) {
|
||||
return reinterpret_cast<T (*)(void*, void*)>(get_mtd->methodPointer)(self, get_mtd);
|
||||
}
|
||||
|
||||
auto field = UnityResolve::Invoke<Il2cppUtils::FieldInfo*>(
|
||||
"il2cpp_class_get_field_from_name",
|
||||
self_klass,
|
||||
(fieldName + '_').c_str()
|
||||
);
|
||||
if (!field) {
|
||||
return T();
|
||||
}
|
||||
return Il2cppUtils::ClassGetFieldValue<T>(self, field);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void SetField(const std::string& fieldName, T value) {
|
||||
if (!self) return;
|
||||
auto set_mtd = GetGetSetMethodFromCache(fieldName, 1, fieldSetCache, "set_");
|
||||
if (set_mtd) {
|
||||
reinterpret_cast<void (*)(void*, T, void*)>(
|
||||
set_mtd->methodPointer
|
||||
)(self, value, set_mtd);
|
||||
return;
|
||||
}
|
||||
auto field = UnityResolve::Invoke<Il2cppUtils::FieldInfo*>(
|
||||
"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<int>(fieldName);
|
||||
}
|
||||
|
||||
Il2cppString* ReadStringField(const std::string& fieldName) {
|
||||
if (!self) return nullptr;
|
||||
auto get_mtd = GetGetSetMethodFromCache(fieldName, 0, fieldGetCache, "get_");
|
||||
if (!get_mtd) {
|
||||
return ReadField<Il2cppString*>(fieldName);
|
||||
}
|
||||
auto returnClass = UnityResolve::Invoke<Il2cppUtils::Il2CppClassHead*>(
|
||||
"il2cpp_class_from_type",
|
||||
UnityResolve::Invoke<void*>("il2cpp_method_get_return_type", get_mtd)
|
||||
);
|
||||
if (!returnClass) {
|
||||
return reinterpret_cast<Il2cppString* (*)(void*, void*)>(
|
||||
get_mtd->methodPointer
|
||||
)(self, get_mtd);
|
||||
}
|
||||
auto isEnum = UnityResolve::Invoke<bool>("il2cpp_class_is_enum", returnClass);
|
||||
if (!isEnum) {
|
||||
return reinterpret_cast<Il2cppString* (*)(void*, void*)>(
|
||||
get_mtd->methodPointer
|
||||
)(self, get_mtd);
|
||||
}
|
||||
auto enumMap = Il2cppUtils::EnumToValueMap(returnClass, true);
|
||||
auto enumValue = reinterpret_cast<int (*)(void*, void*)>(
|
||||
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) {
|
||||
if (!self) return;
|
||||
auto newString = Il2cppString::New(value);
|
||||
SetField(fieldName, newString);
|
||||
}
|
||||
|
||||
void SetStringListField(const std::string& fieldName, const std::vector<std::string>& data) {
|
||||
if (!self) return;
|
||||
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<void (*)(void*, void*)>(
|
||||
List_String_ctor_mtd->methodPointer
|
||||
);
|
||||
|
||||
auto newList = UnityResolve::Invoke<void*>("il2cpp_object_new", List_String_klass);
|
||||
List_String_ctor(newList, List_String_ctor_mtd);
|
||||
|
||||
Il2cppUtils::Tools::CSListEditor<Il2cppString*> newListEditor(newList);
|
||||
for (auto& s : data) {
|
||||
newListEditor.Add(Il2cppString::New(s));
|
||||
}
|
||||
SetField(fieldName, newList);
|
||||
}
|
||||
|
||||
void* ReadObjectField(const std::string& fieldName) {
|
||||
if (!self) return nullptr;
|
||||
return ReadField<void*>(fieldName);
|
||||
}
|
||||
|
||||
void* ReadObjectListField(const std::string& fieldName) {
|
||||
if (!self) return nullptr;
|
||||
return ReadField<void*>(fieldName);
|
||||
}
|
||||
|
||||
static FieldController CreateSubFieldController(void* subObj) {
|
||||
return FieldController(subObj);
|
||||
}
|
||||
|
||||
FieldController CreateSubFieldController(const std::string& subObjName) {
|
||||
auto field = ReadObjectField(subObjName);
|
||||
return FieldController(field);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
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()) {
|
||||
if (j.begin()->is_object()) {
|
||||
return JsonValueType::JVT_ArrayObject;
|
||||
}
|
||||
else if (j.begin()->is_string()) {
|
||||
return JsonValueType::JVT_ArrayString;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return JsonValueType::JVT_NeedMore_EmptyArray;
|
||||
}
|
||||
}
|
||||
return JsonValueType::JVT_Unsupported;
|
||||
}
|
||||
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
namespace Load {
|
||||
std::vector<std::string> ArrayStrJsonToVec(nlohmann::json& data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
bool BuildObjectItemLocalRule(nlohmann::json& transData, ItemRule& itemRule) {
|
||||
// transData: data[]
|
||||
bool hasSuccess = false;
|
||||
for (auto& data : transData) {
|
||||
// data: {"id": "xxx", "produceDescriptions": [{"k", "v"}], "descriptions": {"k2", "v2"}}
|
||||
if (!data.is_object()) continue;
|
||||
for (auto& [key, value] : data.items()) {
|
||||
// key: "id", value: "xxx"
|
||||
// key: "produceDescriptions", value: [{"k", "v"}]
|
||||
const auto valueType = checkJsonValueType(value);
|
||||
switch (valueType) {
|
||||
case JsonValueType::JVT_String:
|
||||
// case JsonValueType::JVT_Int:
|
||||
case JsonValueType::JVT_ArrayString: {
|
||||
if (std::find(itemRule.mainPrimaryKey.begin(), itemRule.mainPrimaryKey.end(), key) != itemRule.mainPrimaryKey.end()) {
|
||||
continue;
|
||||
}
|
||||
if (auto it = std::find(itemRule.mainLocalKey.begin(), itemRule.mainLocalKey.end(), key); it == itemRule.mainLocalKey.end()) {
|
||||
itemRule.mainLocalKey.emplace_back(key);
|
||||
}
|
||||
hasSuccess = true;
|
||||
} break;
|
||||
|
||||
case JsonValueType::JVT_Object: {
|
||||
ItemRule currRule{ .mainPrimaryKey = itemRule.subPrimaryKey[key] };
|
||||
|
||||
auto vJson = nlohmann::json::array();
|
||||
vJson.push_back(value);
|
||||
|
||||
if (BuildObjectItemLocalRule(vJson, currRule)) {
|
||||
itemRule.subLocalKey.emplace(key, currRule.mainLocalKey);
|
||||
hasSuccess = true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case JsonValueType::JVT_ArrayObject: {
|
||||
for (auto& obj : value) {
|
||||
// obj: {"k", "v"}
|
||||
ItemRule currRule{ .mainPrimaryKey = itemRule.subPrimaryKey[key] };
|
||||
if (BuildObjectItemLocalRule(value, currRule)) {
|
||||
itemRule.subLocalKey.emplace(key, currRule.mainLocalKey);
|
||||
hasSuccess = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case JsonValueType::JVT_Unsupported:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hasSuccess) break;
|
||||
}
|
||||
return hasSuccess;
|
||||
}
|
||||
|
||||
bool GetItemRule(nlohmann::json& fullData, ItemRule& itemRule) {
|
||||
auto& primaryKeys = fullData["rules"]["primaryKeys"];
|
||||
auto& transData = fullData["data"];
|
||||
if (!primaryKeys.is_array()) return false;
|
||||
if (!transData.is_array()) return false;
|
||||
|
||||
// 首先构造 mainPrimaryKey 规则
|
||||
for (auto& pkItem : primaryKeys) {
|
||||
if (!pkItem.is_string()) {
|
||||
return false;
|
||||
}
|
||||
std::string pk = pkItem;
|
||||
auto dotCount = std::ranges::count(pk, '.');
|
||||
if (dotCount == 0) {
|
||||
itemRule.mainPrimaryKey.emplace_back(pk);
|
||||
}
|
||||
else if (dotCount == 1) {
|
||||
auto [parentKey, subKey] = Misc::StringFormat::split_once(pk, ".");
|
||||
if (itemRule.subPrimaryKey.contains(parentKey)) {
|
||||
itemRule.subPrimaryKey[parentKey].emplace_back(subKey);
|
||||
}
|
||||
else {
|
||||
itemRule.subPrimaryKey.emplace(parentKey, std::vector<std::string>{subKey});
|
||||
}
|
||||
}
|
||||
else {
|
||||
Log::ErrorFmt("Unsupported depth: %d", dotCount);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return BuildObjectItemLocalRule(transData, itemRule);
|
||||
}
|
||||
|
||||
std::string BuildBaseMainUniqueKey(nlohmann::json& data, TableLocalData& tableLocalData) {
|
||||
try {
|
||||
std::string mainBaseUniqueKey;
|
||||
for (auto& mainPrimaryKey : tableLocalData.itemRule.mainPrimaryKey) {
|
||||
if (!data.contains(mainPrimaryKey)) {
|
||||
return "";
|
||||
}
|
||||
auto& value = data[mainPrimaryKey];
|
||||
if (value.is_number_integer()) {
|
||||
mainBaseUniqueKey.append(std::to_string(value.get<int>()));
|
||||
}
|
||||
else {
|
||||
mainBaseUniqueKey.append(value);
|
||||
}
|
||||
mainBaseUniqueKey.push_back('|');
|
||||
}
|
||||
return mainBaseUniqueKey;
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
Log::ErrorFmt("LoadData - BuildBaseMainUniqueKey failed: %s", e.what());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
void BuildBaseObjectSubUniqueKey(nlohmann::json& value, JsonValueType valueType, std::string& currLocalKey) {
|
||||
switch (valueType) {
|
||||
case JsonValueType::JVT_String:
|
||||
currLocalKey.append(value.get<std::string>()); // p_card-00-acc-0_002|0|produceDescriptions|ProduceDescriptionType_Exam|
|
||||
currLocalKey.push_back('|');
|
||||
break;
|
||||
case JsonValueType::JVT_Int:
|
||||
currLocalKey.append(std::to_string(value.get<int>()));
|
||||
currLocalKey.push_back('|');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool BuildUniqueKeyValue(nlohmann::json& data, TableLocalData& tableLocalData) {
|
||||
// 首先处理 main 部分
|
||||
const std::string mainBaseUniqueKey = BuildBaseMainUniqueKey(data, tableLocalData); // p_card-00-acc-0_002|0|
|
||||
if (mainBaseUniqueKey.empty()) return false;
|
||||
for (auto& mainLocalKey : tableLocalData.itemRule.mainLocalKey) {
|
||||
if (!data.contains(mainLocalKey)) continue;
|
||||
auto& currLocalValue = data[mainLocalKey];
|
||||
auto currUniqueKey = mainBaseUniqueKey + mainLocalKey; // p_card-00-acc-0_002|0|name
|
||||
if (tableLocalData.GetMainKeyType(mainLocalKey) == JsonValueType::JVT_ArrayString) {
|
||||
tableLocalData.transStrListData.emplace(currUniqueKey, ArrayStrJsonToVec(currLocalValue));
|
||||
}
|
||||
else {
|
||||
tableLocalData.transData.emplace(currUniqueKey, currLocalValue);
|
||||
}
|
||||
}
|
||||
// 然后处理 sub 部分
|
||||
/*
|
||||
for (const auto& [subPrimaryParentKey, subPrimarySubKeys] : tableLocalData.itemRule.subPrimaryKey) {
|
||||
if (!data.contains(subPrimaryParentKey)) continue;
|
||||
|
||||
const std::string subBaseUniqueKey = mainBaseUniqueKey + subPrimaryParentKey + '|'; // p_card-00-acc-0_002|0|produceDescriptions|
|
||||
|
||||
auto subValueType = checkJsonValueType(data[subPrimaryParentKey]);
|
||||
std::string currLocalKey = subBaseUniqueKey; // p_card-00-acc-0_002|0|produceDescriptions|
|
||||
switch (subValueType) {
|
||||
case JsonValueType::JVT_Object: {
|
||||
for (auto& subPrimarySubKey : subPrimarySubKeys) {
|
||||
if (!data[subPrimaryParentKey].contains(subPrimarySubKey)) continue;
|
||||
auto& value = data[subPrimaryParentKey][subPrimarySubKey];
|
||||
auto valueType = tableLocalData.GetSubKeyType(subPrimaryParentKey, subPrimarySubKey);
|
||||
BuildBaseObjectSubUniqueKey(value, valueType, currLocalKey); // p_card-00-acc-0_002|0|produceDescriptions|ProduceDescriptionType_Exam|
|
||||
}
|
||||
} break;
|
||||
case JsonValueType::JVT_ArrayObject: {
|
||||
int currIndex = 0;
|
||||
for (auto& obj : data[subPrimaryParentKey]) {
|
||||
for (auto& subPrimarySubKey : subPrimarySubKeys) {
|
||||
|
||||
}
|
||||
currIndex++;
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
|
||||
for (const auto& [subLocalParentKey, subLocalSubKeys] : tableLocalData.itemRule.subLocalKey) {
|
||||
if (!data.contains(subLocalParentKey)) continue;
|
||||
|
||||
const std::string subBaseUniqueKey = mainBaseUniqueKey + subLocalParentKey + '|'; // p_card-00-acc-0_002|0|produceDescriptions|
|
||||
auto subValueType = checkJsonValueType(data[subLocalParentKey]);
|
||||
if (subValueType != JsonValueType::JVT_NeedMore_EmptyArray) {
|
||||
tableLocalData.mainKeyType.emplace(subLocalParentKey, subValueType); // 在这里插入 subParent 的类型
|
||||
}
|
||||
switch (subValueType) {
|
||||
case JsonValueType::JVT_Object: {
|
||||
for (auto& localSubKey : subLocalSubKeys) {
|
||||
const std::string currLocalUniqueKey = subBaseUniqueKey + localSubKey; // p_card-00-acc-0_002|0|produceDescriptions|text
|
||||
if (tableLocalData.GetSubKeyType(subLocalParentKey, localSubKey) == JsonValueType::JVT_ArrayString) {
|
||||
tableLocalData.transStrListData.emplace(currLocalUniqueKey, ArrayStrJsonToVec(data[subLocalParentKey][localSubKey]));
|
||||
}
|
||||
else {
|
||||
tableLocalData.transData.emplace(currLocalUniqueKey, data[subLocalParentKey][localSubKey]);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case JsonValueType::JVT_ArrayObject: {
|
||||
int currIndex = 0;
|
||||
for (auto& obj : data[subLocalParentKey]) {
|
||||
for (auto& localSubKey : subLocalSubKeys) {
|
||||
std::string currLocalUniqueKey = subBaseUniqueKey; // p_card-00-acc-0_002|0|produceDescriptions|
|
||||
currLocalUniqueKey.push_back('[');
|
||||
currLocalUniqueKey.append(std::to_string(currIndex));
|
||||
currLocalUniqueKey.append("]|");
|
||||
currLocalUniqueKey.append(localSubKey); // p_card-00-acc-0_002|0|produceDescriptions|[0]|text
|
||||
|
||||
if (tableLocalData.GetSubKeyType(subLocalParentKey, localSubKey) == JsonValueType::JVT_ArrayString) {
|
||||
// if (obj[localSubKey].is_array()) {
|
||||
tableLocalData.transStrListData.emplace(currLocalUniqueKey, ArrayStrJsonToVec(obj[localSubKey]));
|
||||
}
|
||||
else if (obj[localSubKey].is_string()) {
|
||||
tableLocalData.transData.emplace(currLocalUniqueKey, obj[localSubKey]);
|
||||
}
|
||||
}
|
||||
currIndex++;
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#define MainKeyTypeProcess() if (!data.contains(mainPrimaryKey)) { Log::ErrorFmt("mainPrimaryKey: %s not found", mainPrimaryKey.c_str()); isFailed = true; break; } \
|
||||
auto currType = checkJsonValueType(data[mainPrimaryKey]); \
|
||||
if (currType == JsonValueType::JVT_NeedMore_EmptyArray) goto NextLoop; \
|
||||
tableLocalData.mainKeyType[mainPrimaryKey] = currType
|
||||
#define SubKeyTypeProcess() if (!data.contains(subKeyParent)) { Log::ErrorFmt("subKeyParent: %s not found", subKeyParent.c_str()); isFailed = true; break; } \
|
||||
for (auto& subKey : subKeys) { \
|
||||
auto& subKeyValue = data[subKeyParent]; \
|
||||
if (subKeyValue.is_object()) { \
|
||||
if (!subKeyValue.contains(subKey)) { \
|
||||
Log::ErrorFmt("subKey: %s not in subKeyParent: %s", subKey.c_str(), subKeyParent.c_str()); isFailed = true; break; \
|
||||
} \
|
||||
auto currType = checkJsonValueType(subKeyValue[subKey]); \
|
||||
if (currType == JsonValueType::JVT_NeedMore_EmptyArray) goto NextLoop; \
|
||||
tableLocalData.subKeyType[subKeyParent].emplace(subKey, currType); \
|
||||
} \
|
||||
else if (subKeyValue.is_array()) { \
|
||||
if (subKeyValue.empty()) goto NextLoop; \
|
||||
for (auto& i : subKeyValue) { \
|
||||
if (!i.is_object()) continue; \
|
||||
if (!i.contains(subKey)) continue; \
|
||||
auto currType = checkJsonValueType(i[subKey]); \
|
||||
if (currType == JsonValueType::JVT_NeedMore_EmptyArray) goto NextLoop; \
|
||||
tableLocalData.subKeyType[subKeyParent].emplace(subKey, currType); \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
else { \
|
||||
goto NextLoop;\
|
||||
} \
|
||||
}
|
||||
|
||||
bool GetTableLocalData(nlohmann::json& fullData, TableLocalData& tableLocalData) {
|
||||
bool isFailed = false;
|
||||
|
||||
// 首先 Build mainKeyType 和 subKeyType
|
||||
for (auto& data : fullData["data"]) {
|
||||
if (!data.is_object()) continue;
|
||||
|
||||
for (auto& mainPrimaryKey : tableLocalData.itemRule.mainPrimaryKey) {
|
||||
MainKeyTypeProcess();
|
||||
}
|
||||
for (auto& mainPrimaryKey : tableLocalData.itemRule.mainLocalKey) {
|
||||
MainKeyTypeProcess();
|
||||
}
|
||||
|
||||
for (const auto& [subKeyParent, subKeys] : tableLocalData.itemRule.subPrimaryKey) {
|
||||
SubKeyTypeProcess()
|
||||
|
||||
if (isFailed) break;
|
||||
}
|
||||
for (const auto& [subKeyParent, subKeys] : tableLocalData.itemRule.subLocalKey) {
|
||||
SubKeyTypeProcess()
|
||||
if (isFailed) break;
|
||||
}
|
||||
if (!isFailed) break;
|
||||
NextLoop:
|
||||
;
|
||||
}
|
||||
|
||||
if (isFailed) return false;
|
||||
|
||||
bool hasSuccess = false;
|
||||
// 然后构造 transData
|
||||
for (auto& data : fullData["data"]) {
|
||||
if (!data.is_object()) continue;
|
||||
if (BuildUniqueKeyValue(data, tableLocalData)) {
|
||||
hasSuccess = true;
|
||||
}
|
||||
}
|
||||
if (!hasSuccess) {
|
||||
Log::ErrorFmt("BuildUniqueKeyValue failed.");
|
||||
}
|
||||
return hasSuccess;
|
||||
}
|
||||
|
||||
void LoadData() {
|
||||
masterLocalData.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;
|
||||
}
|
||||
ItemRule currRule;
|
||||
if (!GetItemRule(j, currRule)) {
|
||||
Log::ErrorFmt("GetItemRule failed: %s", path.string().c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
if (tableName == "ProduceStepEventDetail") {
|
||||
for (auto& i : currRule.mainLocalKey) {
|
||||
Log::DebugFmt("currRule.mainLocalKey: %s", i.c_str());
|
||||
}
|
||||
for (auto& i : currRule.mainPrimaryKey) {
|
||||
Log::DebugFmt("currRule.mainPrimaryKey: %s", i.c_str());
|
||||
}
|
||||
for (auto& i : currRule.subLocalKey) {
|
||||
for (auto& m : i.second) {
|
||||
Log::DebugFmt("currRule.subLocalKey: %s - %s", i.first.c_str(), m.c_str());
|
||||
}
|
||||
}
|
||||
for (auto& i : currRule.subPrimaryKey) {
|
||||
for (auto& m : i.second) {
|
||||
Log::DebugFmt("currRule.subPrimaryKey: %s - %s", i.first.c_str(), m.c_str());
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
TableLocalData tableLocalData{ .itemRule = currRule };
|
||||
if (GetTableLocalData(j, tableLocalData)) {
|
||||
for (auto& i : tableLocalData.transData) {
|
||||
// Log::DebugFmt("%s: %s -> %s", tableName.c_str(), i.first.c_str(), i.second.c_str());
|
||||
Local::translatedText.emplace(i.second);
|
||||
}
|
||||
for (auto& i : tableLocalData.transStrListData) {
|
||||
for (auto& str : i.second) {
|
||||
// Log::DebugFmt("%s[]: %s -> %s", tableName.c_str(), i.first.c_str(), str.c_str());
|
||||
Local::translatedText.emplace(str);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
if (tableName == "ProduceStepEventDetail") {
|
||||
for (auto& i : tableLocalData.mainKeyType) {
|
||||
Log::DebugFmt("mainKeyType: %s -> %d", i.first.c_str(), i.second);
|
||||
}
|
||||
for (auto& i : tableLocalData.subKeyType) {
|
||||
for (auto& m : i.second) {
|
||||
Log::DebugFmt("subKeyType: %s - %s -> %d", i.first.c_str(), m.first.c_str(), m.second);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
// JVT_ArrayString in HelpCategory, ProduceStory, Tutorial
|
||||
|
||||
masterLocalData.emplace(tableName, std::move(tableLocalData));
|
||||
}
|
||||
else {
|
||||
Log::ErrorFmt("GetTableLocalData failed: %s", path.string().c_str());
|
||||
}
|
||||
} catch (std::exception& e) {
|
||||
Log::ErrorFmt("MasterLocal::LoadData: parse error in '%s': %s",
|
||||
path.string().c_str(), e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LoadData() {
|
||||
return Load::LoadData();
|
||||
}
|
||||
|
||||
std::string GetTransString(const std::string& key, const TableLocalData& localData) {
|
||||
if (auto it = localData.transData.find(key); it != localData.transData.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::string> GetTransArrayString(const std::string& key, const TableLocalData& localData) {
|
||||
if (auto it = localData.transStrListData.find(key); it != localData.transStrListData.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void LocalizeMasterItem(FieldController& fc, const std::string& tableName) {
|
||||
auto it = masterLocalData.find(tableName);
|
||||
if (it == masterLocalData.end()) return;
|
||||
const auto& localData = it->second;
|
||||
|
||||
// 首先拼 BasePrimaryKey
|
||||
std::string baseDataKey; // p_card-00-acc-0_002|0|
|
||||
for (auto& mainPk : localData.itemRule.mainPrimaryKey) {
|
||||
auto mainPkType = localData.GetMainKeyType(mainPk);
|
||||
switch (mainPkType) {
|
||||
case JsonValueType::JVT_Int: {
|
||||
auto readValue = std::to_string(fc.ReadIntField(mainPk));
|
||||
baseDataKey.append(readValue);
|
||||
baseDataKey.push_back('|');
|
||||
} break;
|
||||
case JsonValueType::JVT_String: {
|
||||
auto readValue = fc.ReadStringField(mainPk);
|
||||
if (!readValue) return;
|
||||
baseDataKey.append(readValue->ToString());
|
||||
baseDataKey.push_back('|');
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 然后本地化 mainLocal
|
||||
for (auto& mainLocal : localData.itemRule.mainLocalKey) {
|
||||
std::string currSearchKey = baseDataKey;
|
||||
currSearchKey.append(mainLocal); // p_card-00-acc-0_002|0|name
|
||||
auto localVType = localData.GetMainKeyType(mainLocal);
|
||||
switch (localVType) {
|
||||
case JsonValueType::JVT_String: {
|
||||
auto localValue = GetTransString(currSearchKey, localData);
|
||||
if (!localValue.empty()) {
|
||||
fc.SetStringField(mainLocal, localValue);
|
||||
}
|
||||
} break;
|
||||
case JsonValueType::JVT_ArrayString: {
|
||||
auto localValue = GetTransArrayString(currSearchKey, localData);
|
||||
if (!localValue.empty()) {
|
||||
fc.SetStringListField(mainLocal, localValue);
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理 sub
|
||||
for (const auto& [subParentKey, subLocalKeys] : localData.itemRule.subLocalKey) {
|
||||
const auto subBaseSearchKey = baseDataKey + subParentKey + '|'; // p_card-00-acc-0_002|0|produceDescriptions|
|
||||
|
||||
const auto subParentType = localData.GetMainKeyType(subParentKey);
|
||||
switch (subParentType) {
|
||||
case JsonValueType::JVT_Object: {
|
||||
auto subParentField = fc.CreateSubFieldController(subParentKey);
|
||||
for (const auto& subLocalKey : subLocalKeys) {
|
||||
const auto currSearchKey = subBaseSearchKey + subLocalKey; // p_card-00-acc-0_002|0|produceDescriptions|text
|
||||
auto localKeyType = localData.GetSubKeyType(subParentKey, subLocalKey);
|
||||
if (localKeyType == JsonValueType::JVT_String) {
|
||||
auto setData = GetTransString(currSearchKey, localData);
|
||||
if (!setData.empty()) {
|
||||
subParentField.SetStringField(subLocalKey, setData);
|
||||
}
|
||||
}
|
||||
else if (localKeyType == JsonValueType::JVT_ArrayString) {
|
||||
auto setData = GetTransArrayString(currSearchKey, localData);
|
||||
if (!setData.empty()) {
|
||||
subParentField.SetStringListField(subLocalKey, setData);
|
||||
}
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case JsonValueType::JVT_ArrayObject: {
|
||||
auto subArrField = fc.ReadObjectListField(subParentKey);
|
||||
if (!subArrField) continue;
|
||||
Il2cppUtils::Tools::CSListEditor<void*> subListEdit(subArrField);
|
||||
auto count = subListEdit.get_Count();
|
||||
for (int idx = 0; idx < count; idx++) {
|
||||
auto currItem = subListEdit.get_Item(idx);
|
||||
if (!currItem) continue;
|
||||
auto currFc = FieldController::CreateSubFieldController(currItem);
|
||||
|
||||
std::string currSearchBaseKey = subBaseSearchKey; // p_card-00-acc-0_002|0|produceDescriptions|
|
||||
currSearchBaseKey.push_back('[');
|
||||
currSearchBaseKey.append(std::to_string(idx));
|
||||
currSearchBaseKey.append("]|"); // p_card-00-acc-0_002|0|produceDescriptions|[0]|
|
||||
|
||||
for (const auto& subLocalKey : subLocalKeys) {
|
||||
std::string currSearchKey = currSearchBaseKey + subLocalKey; // p_card-00-acc-0_002|0|produceDescriptions|[0]|text
|
||||
|
||||
auto localKeyType = localData.GetSubKeyType(subParentKey, subLocalKey);
|
||||
|
||||
/*
|
||||
if (tableName == "ProduceStepEventDetail") {
|
||||
Log::DebugFmt("localKeyType: %d currSearchKey: %s", localKeyType, currSearchKey.c_str());
|
||||
}*/
|
||||
|
||||
if (localKeyType == JsonValueType::JVT_String) {
|
||||
auto setData = GetTransString(currSearchKey, localData);
|
||||
if (!setData.empty()) {
|
||||
currFc.SetStringField(subLocalKey, setData);
|
||||
}
|
||||
}
|
||||
else if (localKeyType == JsonValueType::JVT_ArrayString) {
|
||||
auto setData = GetTransArrayString(currSearchKey, localData);
|
||||
if (!setData.empty()) {
|
||||
currFc.SetStringListField(subLocalKey, setData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void LocalizeMasterItem(void* item, const std::string& tableName) {
|
||||
if (!Config::useMasterTrans) return;
|
||||
// Log::DebugFmt("LocalizeMasterItem: %s", tableName.c_str());
|
||||
FieldController fc(item);
|
||||
LocalizeMasterItem(fc, tableName);
|
||||
}
|
||||
|
||||
} // namespace GakumasLocal::MasterLocal
|
||||
12
app/src/main/cpp/GakumasLocalify/MasterLocal.h
Normal file
12
app/src/main/cpp/GakumasLocalify/MasterLocal.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#ifndef GAKUMAS_LOCALIFY_MASTERLOCAL_H
|
||||
#define GAKUMAS_LOCALIFY_MASTERLOCAL_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace GakumasLocal::MasterLocal {
|
||||
void LoadData();
|
||||
|
||||
void LocalizeMasterItem(void* item, const std::string& tableName);
|
||||
}
|
||||
|
||||
#endif //GAKUMAS_LOCALIFY_MASTERLOCAL_H
|
||||
@@ -2,11 +2,15 @@
|
||||
|
||||
#include <codecvt>
|
||||
#include <locale>
|
||||
#include <jni.h>
|
||||
#include "fmt/core.h"
|
||||
|
||||
|
||||
extern JavaVM* g_javaVM;
|
||||
#ifndef GKMS_WINDOWS
|
||||
#include <jni.h>
|
||||
|
||||
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);
|
||||
@@ -168,6 +180,33 @@ namespace GakumasLocal::Misc {
|
||||
return fmt;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> split(const std::string& str, char delimiter) {
|
||||
std::vector<std::string> result;
|
||||
std::string current;
|
||||
for (char c : str) {
|
||||
if (c == delimiter) {
|
||||
if (!current.empty()) {
|
||||
result.push_back(current);
|
||||
}
|
||||
current.clear();
|
||||
} else {
|
||||
current += c;
|
||||
}
|
||||
}
|
||||
if (!current.empty()) {
|
||||
result.push_back(current);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::pair<std::string, std::string> split_once(const std::string& str, const std::string& delimiter) {
|
||||
size_t pos = str.find(delimiter);
|
||||
if (pos != std::string::npos) {
|
||||
return {str.substr(0, pos), str.substr(pos + delimiter.size())};
|
||||
}
|
||||
return {str, ""};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,11 +2,16 @@
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <jni.h>
|
||||
#include <deque>
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
|
||||
#include "../platformDefine.hpp"
|
||||
|
||||
#ifndef GKMS_WINDOWS
|
||||
#include <jni.h>
|
||||
#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:
|
||||
@@ -76,6 +87,8 @@ namespace GakumasLocal {
|
||||
|
||||
namespace StringFormat {
|
||||
std::string stringFormatString(const std::string& fmt, const std::vector<std::string>& vec);
|
||||
std::vector<std::string> split(const std::string& str, char delimiter);
|
||||
std::pair<std::string, std::string> split_once(const std::string& str, const std::string& delimiter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,13 @@
|
||||
#include "Misc.hpp"
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <jni.h>
|
||||
|
||||
#include "../platformDefine.hpp"
|
||||
|
||||
#ifndef GKMS_WINDOWS
|
||||
#include <jni.h>
|
||||
#endif // !GKMS_WINDOWS
|
||||
|
||||
|
||||
namespace GakumasLocal {
|
||||
struct HookInstaller
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
#include "baseCamera.hpp"
|
||||
#include <thread>
|
||||
|
||||
#include "../../platformDefine.hpp"
|
||||
|
||||
#ifdef GKMS_WINDOWS
|
||||
#include <corecrt_math_defines.h>
|
||||
#endif // GKMS_WINDOWS
|
||||
|
||||
|
||||
namespace BaseCamera {
|
||||
using Vector3_t = UnityResolve::UnityType::Vector3;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "../deps/UnityResolve/UnityResolve.hpp"
|
||||
#include "../../deps/UnityResolve/UnityResolve.hpp"
|
||||
|
||||
enum LonMoveHState {
|
||||
LonMoveLeftAndRight,
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
#include "baseCamera.hpp"
|
||||
#include "camera.hpp"
|
||||
#include <thread>
|
||||
#include "Misc.hpp"
|
||||
#include "../Misc.hpp"
|
||||
#include "../BaseDefine.h"
|
||||
#include "../../platformDefine.hpp"
|
||||
|
||||
#ifdef GKMS_WINDOWS
|
||||
#include <corecrt_math_defines.h>
|
||||
#endif // GKMS_WINDOWS
|
||||
|
||||
|
||||
|
||||
namespace GKCamera {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
#include "baseCamera.hpp"
|
||||
#include "Joystick/JoystickEvent.h"
|
||||
#include "../../deps/Joystick/JoystickEvent.h"
|
||||
|
||||
namespace GKCamera {
|
||||
enum class CameraMode {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include <string>
|
||||
#include "nlohmann/json.hpp"
|
||||
#include "../Log.h"
|
||||
#include <thread>
|
||||
#include <fstream>
|
||||
|
||||
namespace GakumasLocal::Config {
|
||||
bool isConfigInit = false;
|
||||
@@ -11,16 +13,20 @@ namespace GakumasLocal::Config {
|
||||
bool replaceFont = true;
|
||||
bool forceExportResource = true;
|
||||
bool textTest = false;
|
||||
bool useMasterTrans = true;
|
||||
int gameOrientation = 0;
|
||||
bool dumpText = false;
|
||||
bool enableFreeCamera = false;
|
||||
int targetFrameRate = 0;
|
||||
bool unlockAllLive = false;
|
||||
bool unlockAllLiveCostume = false;
|
||||
|
||||
bool enableLiveCustomeDress = false;
|
||||
std::string liveCustomeHeadId = "";
|
||||
std::string liveCustomeCostumeId = "";
|
||||
|
||||
bool loginAsIOS = false;
|
||||
|
||||
bool useCustomeGraphicSettings = false;
|
||||
float renderScale = 0.77f;
|
||||
int qualitySettingsLevel = 3;
|
||||
@@ -61,13 +67,16 @@ namespace GakumasLocal::Config {
|
||||
GetConfigItem(forceExportResource);
|
||||
GetConfigItem(gameOrientation);
|
||||
GetConfigItem(textTest);
|
||||
GetConfigItem(useMasterTrans);
|
||||
GetConfigItem(dumpText);
|
||||
GetConfigItem(targetFrameRate);
|
||||
GetConfigItem(enableFreeCamera);
|
||||
GetConfigItem(unlockAllLive);
|
||||
GetConfigItem(unlockAllLiveCostume);
|
||||
GetConfigItem(enableLiveCustomeDress);
|
||||
GetConfigItem(liveCustomeHeadId);
|
||||
GetConfigItem(liveCustomeCostumeId);
|
||||
GetConfigItem(loginAsIOS);
|
||||
GetConfigItem(useCustomeGraphicSettings);
|
||||
GetConfigItem(renderScale);
|
||||
GetConfigItem(qualitySettingsLevel);
|
||||
@@ -93,11 +102,72 @@ namespace GakumasLocal::Config {
|
||||
GetConfigItem(bLimitYy);
|
||||
GetConfigItem(bLimitZx);
|
||||
GetConfigItem(bLimitZy);
|
||||
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
Log::ErrorFmt("LoadConfig error: %s", e.what());
|
||||
}
|
||||
isConfigInit = true;
|
||||
}
|
||||
|
||||
void SaveConfig(const std::string& configPath) {
|
||||
try {
|
||||
nlohmann::json config;
|
||||
|
||||
#define SetConfigItem(name) config[#name] = name
|
||||
|
||||
SetConfigItem(dbgMode);
|
||||
SetConfigItem(enabled);
|
||||
SetConfigItem(lazyInit);
|
||||
SetConfigItem(replaceFont);
|
||||
SetConfigItem(forceExportResource);
|
||||
SetConfigItem(gameOrientation);
|
||||
SetConfigItem(textTest);
|
||||
SetConfigItem(useMasterTrans);
|
||||
SetConfigItem(dumpText);
|
||||
SetConfigItem(targetFrameRate);
|
||||
SetConfigItem(enableFreeCamera);
|
||||
SetConfigItem(unlockAllLive);
|
||||
SetConfigItem(unlockAllLiveCostume);
|
||||
SetConfigItem(enableLiveCustomeDress);
|
||||
SetConfigItem(liveCustomeHeadId);
|
||||
SetConfigItem(liveCustomeCostumeId);
|
||||
SetConfigItem(loginAsIOS);
|
||||
SetConfigItem(useCustomeGraphicSettings);
|
||||
SetConfigItem(renderScale);
|
||||
SetConfigItem(qualitySettingsLevel);
|
||||
SetConfigItem(volumeIndex);
|
||||
SetConfigItem(maxBufferPixel);
|
||||
SetConfigItem(reflectionQualityLevel);
|
||||
SetConfigItem(lodQualityLevel);
|
||||
SetConfigItem(enableBreastParam);
|
||||
SetConfigItem(bDamping);
|
||||
SetConfigItem(bStiffness);
|
||||
SetConfigItem(bSpring);
|
||||
SetConfigItem(bPendulum);
|
||||
SetConfigItem(bPendulumRange);
|
||||
SetConfigItem(bAverage);
|
||||
SetConfigItem(bRootWeight);
|
||||
SetConfigItem(bUseArmCorrection);
|
||||
SetConfigItem(bUseScale);
|
||||
SetConfigItem(bScale);
|
||||
SetConfigItem(bUseLimit);
|
||||
SetConfigItem(bLimitXx);
|
||||
SetConfigItem(bLimitXy);
|
||||
SetConfigItem(bLimitYx);
|
||||
SetConfigItem(bLimitYy);
|
||||
SetConfigItem(bLimitZx);
|
||||
SetConfigItem(bLimitZy);
|
||||
|
||||
std::ofstream out(configPath);
|
||||
if (!out) {
|
||||
Log::ErrorFmt("SaveConfig error: Cannot open file: %s", configPath.c_str());
|
||||
return;
|
||||
}
|
||||
out << config.dump(4);
|
||||
Log::Info("SaveConfig success");
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
Log::ErrorFmt("SaveConfig error: %s", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,15 +10,19 @@ namespace GakumasLocal::Config {
|
||||
extern bool forceExportResource;
|
||||
extern int gameOrientation;
|
||||
extern bool textTest;
|
||||
extern bool useMasterTrans;
|
||||
extern bool dumpText;
|
||||
extern bool enableFreeCamera;
|
||||
extern int targetFrameRate;
|
||||
extern bool unlockAllLive;
|
||||
extern bool unlockAllLiveCostume;
|
||||
|
||||
extern bool enableLiveCustomeDress;
|
||||
extern std::string liveCustomeHeadId;
|
||||
extern std::string liveCustomeCostumeId;
|
||||
|
||||
extern bool loginAsIOS;
|
||||
|
||||
extern bool useCustomeGraphicSettings;
|
||||
extern float renderScale;
|
||||
extern int qualitySettingsLevel;
|
||||
@@ -48,4 +52,5 @@ namespace GakumasLocal::Config {
|
||||
extern float bLimitZy;
|
||||
|
||||
void LoadConfig(const std::string& configStr);
|
||||
void SaveConfig(const std::string& configPath);
|
||||
}
|
||||
|
||||
@@ -309,7 +309,7 @@ public:
|
||||
pDomain = Invoke<void*>("il2cpp_domain_get");
|
||||
Invoke<void*>("il2cpp_thread_attach", pDomain);
|
||||
ForeachAssembly();
|
||||
if (!lazyInit) UnityResolveProgress::startInit = false;
|
||||
// if (!lazyInit) UnityResolveProgress::startInit = false;
|
||||
}
|
||||
else {
|
||||
pDomain = Invoke<void*>("mono_get_root_domain");
|
||||
|
||||
21
app/src/main/cpp/platformDefine.hpp
Normal file
21
app/src/main/cpp/platformDefine.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#include "shadowhook.h"
|
||||
#include <android/log.h>
|
||||
|
||||
#define ADD_HOOK(name, addr) \
|
||||
name##_Addr = reinterpret_cast<name##_Type>(addr); \
|
||||
if (addr) { \
|
||||
auto stub = hookInstaller->InstallHook(reinterpret_cast<void*>(addr), \
|
||||
reinterpret_cast<void*>(name##_Hook), \
|
||||
reinterpret_cast<void**>(&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++
|
||||
@@ -107,6 +107,7 @@ fun <T> T.onClickStartGame() where T : Activity, T : IHasConfigItems {
|
||||
getProgramConfigContent(listOf("transRemoteZipUrl", "useAPIAssetsURL",
|
||||
"localAPIAssetsVersion", "p"), programConfig)
|
||||
)
|
||||
putExtra("lVerName", version)
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
}
|
||||
|
||||
@@ -128,7 +129,15 @@ fun <T> T.onClickStartGame() where T : Activity, T : IHasConfigItems {
|
||||
"io.github.chinosk.gakumas.localify.fileprovider",
|
||||
File(targetFile.absolutePath)
|
||||
)
|
||||
intent.setDataAndType(dirUri, "resource/file")
|
||||
// intent.setDataAndType(dirUri, "resource/file")
|
||||
|
||||
grantUriPermission(
|
||||
"com.bandainamcoent.idolmaster_gakuen",
|
||||
dirUri,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
intent.putExtra("resource_file", dirUri)
|
||||
// intent.clipData = ClipData.newRawUri("resource_file", dirUri)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
}
|
||||
|
||||
|
||||
@@ -17,12 +17,15 @@ import kotlinx.coroutines.runBlocking
|
||||
interface ConfigListener {
|
||||
fun onEnabledChanged(value: Boolean)
|
||||
fun onForceExportResourceChanged(value: Boolean)
|
||||
fun onLoginAsIOSChanged(value: Boolean)
|
||||
fun onTextTestChanged(value: Boolean)
|
||||
fun onUseMasterTransChanged(value: Boolean)
|
||||
fun onReplaceFontChanged(value: Boolean)
|
||||
fun onLazyInitChanged(value: Boolean)
|
||||
fun onEnableFreeCameraChanged(value: Boolean)
|
||||
fun onTargetFpsChanged(s: CharSequence, start: Int, before: Int, count: Int)
|
||||
fun onUnlockAllLiveChanged(value: Boolean)
|
||||
fun onUnlockAllLiveCostumeChanged(value: Boolean)
|
||||
fun onLiveCustomeDressChanged(value: Boolean)
|
||||
fun onLiveCustomeHeadIdChanged(s: CharSequence, start: Int, before: Int, count: Int)
|
||||
fun onLiveCustomeCostumeIdChanged(s: CharSequence, start: Int, before: Int, count: Int)
|
||||
@@ -115,6 +118,11 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
|
||||
pushKeyEvent(KeyEvent(1145, 30))
|
||||
}
|
||||
|
||||
override fun onLoginAsIOSChanged(value: Boolean) {
|
||||
config.loginAsIOS = value
|
||||
saveConfig()
|
||||
}
|
||||
|
||||
override fun onReplaceFontChanged(value: Boolean) {
|
||||
config.replaceFont = value
|
||||
saveConfig()
|
||||
@@ -131,6 +139,11 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
|
||||
saveConfig()
|
||||
}
|
||||
|
||||
override fun onUseMasterTransChanged(value: Boolean) {
|
||||
config.useMasterTrans = value
|
||||
saveConfig()
|
||||
}
|
||||
|
||||
override fun onDumpTextChanged(value: Boolean) {
|
||||
config.dumpText = value
|
||||
saveConfig()
|
||||
@@ -146,6 +159,11 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
|
||||
saveConfig()
|
||||
}
|
||||
|
||||
override fun onUnlockAllLiveCostumeChanged(value: Boolean) {
|
||||
config.unlockAllLiveCostume = value
|
||||
saveConfig()
|
||||
}
|
||||
|
||||
override fun onTargetFpsChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||
try {
|
||||
val valueStr = s.toString()
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
@@ -247,6 +248,9 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
|
||||
val gkmsData = intent.getStringExtra("gkmsData")
|
||||
val programData = intent.getStringExtra("localData")
|
||||
if (gkmsData != null) {
|
||||
val readVersion = intent.getStringExtra("lVerName")
|
||||
checkPluginVersion(activity, readVersion)
|
||||
|
||||
gkmsDataInited = true
|
||||
val initConfig = try {
|
||||
json.decodeFromString<GakumasConfig>(gkmsData)
|
||||
@@ -282,7 +286,14 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
|
||||
|
||||
// 使用热更新文件
|
||||
if ((programConfig?.useRemoteAssets == true) || (programConfig?.useAPIAssets == true)) {
|
||||
val dataUri = intent.data
|
||||
// val dataUri = intent.data
|
||||
val dataUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
intent.getParcelableExtra("resource_file", Uri::class.java)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
intent.getParcelableExtra<Uri>("resource_file")
|
||||
}
|
||||
|
||||
if (dataUri != null) {
|
||||
if (!externalFilesChecked) {
|
||||
externalFilesChecked = true
|
||||
@@ -305,6 +316,40 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkPluginVersion(activity: Activity, readVersion: String?) {
|
||||
val buildVersionName = BuildConfig.VERSION_NAME
|
||||
Log.i(TAG, "Checking Plugin Version: Build: $buildVersionName, Request: $readVersion")
|
||||
if (readVersion?.trim() == buildVersionName.trim()) {
|
||||
return
|
||||
}
|
||||
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
val infoBuilder = AlertDialog.Builder(activity)
|
||||
builder.setTitle("Warning")
|
||||
builder.setCancelable(false)
|
||||
builder.setMessage(when (getCurrentLanguage(activity)) {
|
||||
"zh" -> "检测到插件版本不一致\n内置版本: $buildVersionName\n请求版本: $readVersion\n\n这可能是使用了 LSPatch 的集成模式,仅更新了插件本体,未重新修补游戏导致的。请使用 $readVersion 版本的插件重新修补或使用本地模式。"
|
||||
else -> "Detected plugin version mismatch\nBuilt-in version: $buildVersionName\nRequested version: $readVersion\n\nThis may be caused by using the LSPatch integration mode, where only the plugin itself was updated without re-patching the game. Please re-patch the game using the $readVersion version of the plugin or use the local mode."
|
||||
})
|
||||
|
||||
builder.setPositiveButton("OK") { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
builder.setNegativeButton("Exit") { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
activity.finishAffinity()
|
||||
}
|
||||
|
||||
val dialog = builder.create()
|
||||
|
||||
infoBuilder.setOnCancelListener {
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
private fun showGetConfigFailedImpl(activity: Context, title: String, msg: String, infoButton: String, dlButton: String, okButton: String) {
|
||||
if (getConfigError == null) return
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
|
||||
@@ -645,7 +645,7 @@ class PatchActivity : ComponentActivity() {
|
||||
val copyFilesCmd: MutableList<String> = mutableListOf()
|
||||
val movedFiles: MutableList<String> = mutableListOf()
|
||||
savedFileNames.forEach { file ->
|
||||
val movedFileName = "$installDS/${file}"
|
||||
val movedFileName = "\"$installDS/${file}\""
|
||||
movedFiles.add(movedFileName)
|
||||
val dlSaveFileName = File(targetDirectory, file)
|
||||
copyFilesCmd.add("$action ${dlSaveFileName.absolutePath} $movedFileName")
|
||||
|
||||
@@ -84,7 +84,7 @@ object FilesChecker {
|
||||
for (i in assets.list(localizationFilesDir)!!) {
|
||||
if (i.toString() == "version.txt") {
|
||||
val stream = assets.open("$localizationFilesDir/$i")
|
||||
return convertToString(stream)
|
||||
return convertToString(stream).trim()
|
||||
}
|
||||
}
|
||||
return "0.0"
|
||||
@@ -96,7 +96,7 @@ object FilesChecker {
|
||||
|
||||
val versionFile = File(pluginFilesDir, "version.txt")
|
||||
if (!versionFile.exists()) return "0.0"
|
||||
return versionFile.readText()
|
||||
return versionFile.readText().trim()
|
||||
}
|
||||
|
||||
fun convertToString(inputStream: InputStream?): String {
|
||||
@@ -146,6 +146,7 @@ object FilesChecker {
|
||||
val genericTransDir = File(localFilesDir, "genericTrans")
|
||||
val genericTransFile = File(localFilesDir, "generic.json")
|
||||
val i18nFile = File(localFilesDir, "localization.json")
|
||||
val masterTransDir = File(localFilesDir, "masterTrans")
|
||||
|
||||
if (fontFile.exists()) {
|
||||
fontFile.delete()
|
||||
@@ -156,6 +157,9 @@ object FilesChecker {
|
||||
if (deleteRecursively(genericTransDir)) {
|
||||
genericTransDir.mkdirs()
|
||||
}
|
||||
if (deleteRecursively(masterTransDir)) {
|
||||
masterTransDir.mkdirs()
|
||||
}
|
||||
if (genericTransFile.exists()) {
|
||||
genericTransFile.writeText("{}")
|
||||
}
|
||||
|
||||
@@ -9,16 +9,20 @@ data class GakumasConfig (
|
||||
var lazyInit: Boolean = true,
|
||||
var replaceFont: Boolean = true,
|
||||
var textTest: Boolean = false,
|
||||
var useMasterTrans: Boolean = true,
|
||||
var dumpText: Boolean = false,
|
||||
var gameOrientation: Int = 0,
|
||||
var forceExportResource: Boolean = false,
|
||||
var enableFreeCamera: Boolean = false,
|
||||
var targetFrameRate: Int = 0,
|
||||
var unlockAllLive: Boolean = false,
|
||||
var unlockAllLiveCostume: Boolean = false,
|
||||
var enableLiveCustomeDress: Boolean = false,
|
||||
var liveCustomeHeadId: String = "",
|
||||
var liveCustomeCostumeId: String = "",
|
||||
|
||||
var loginAsIOS: Boolean = false,
|
||||
|
||||
var useCustomeGraphicSettings: Boolean = false,
|
||||
var renderScale: Float = 0.77f,
|
||||
var qualitySettingsLevel: Int = 3,
|
||||
|
||||
@@ -75,6 +75,10 @@ fun AdvanceSettingsPage(modifier: Modifier = Modifier,
|
||||
item {
|
||||
GakuGroupBox(modifier, stringResource(R.string.debug_settings)) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
GakuSwitch(modifier, stringResource(R.string.useMasterDBTrans), checked = config.value.useMasterTrans) {
|
||||
v -> context?.onUseMasterTransChanged(v)
|
||||
}
|
||||
|
||||
GakuSwitch(modifier, stringResource(R.string.text_hook_test_mode), checked = config.value.textTest) {
|
||||
v -> context?.onTextTestChanged(v)
|
||||
}
|
||||
@@ -86,6 +90,10 @@ fun AdvanceSettingsPage(modifier: Modifier = Modifier,
|
||||
GakuSwitch(modifier, stringResource(R.string.force_export_resource), checked = config.value.forceExportResource) {
|
||||
v -> context?.onForceExportResourceChanged(v)
|
||||
}
|
||||
|
||||
GakuSwitch(modifier, stringResource(R.string.login_as_ios), checked = config.value.loginAsIOS) {
|
||||
v -> context?.onLoginAsIOSChanged(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,6 +361,10 @@ fun AdvanceSettingsPage(modifier: Modifier = Modifier,
|
||||
checked = config.value.unlockAllLive) {
|
||||
v -> context?.onUnlockAllLiveChanged(v)
|
||||
}
|
||||
GakuSwitch(modifier, stringResource(R.string.unlockAllLiveCostume),
|
||||
checked = config.value.unlockAllLiveCostume) {
|
||||
v -> context?.onUnlockAllLiveCostumeChanged(v)
|
||||
}
|
||||
HorizontalDivider(
|
||||
thickness = 1.dp,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
|
||||
|
||||
@@ -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
|
||||
|
||||
355
app/src/main/res/values-ja/strings.xml
Normal file
355
app/src/main/res/values-ja/strings.xml
Normal file
@@ -0,0 +1,355 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="abc_action_bar_home_description">ホームに戻る</string>
|
||||
<string name="abc_action_bar_up_description">前に戻る</string>
|
||||
<string name="abc_action_menu_overflow_description">その他のオプション</string>
|
||||
<string name="abc_action_mode_done">完了</string>
|
||||
<string name="abc_activity_chooser_view_see_all">すべて表示</string>
|
||||
<string name="abc_activitychooserview_choose_application">アプリの選択</string>
|
||||
<string name="abc_capital_off">OFF</string>
|
||||
<string name="abc_capital_on">ON</string>
|
||||
<string name="abc_menu_alt_shortcut_label">Alt+</string>
|
||||
<string name="abc_menu_ctrl_shortcut_label">Ctrl+</string>
|
||||
<string name="abc_menu_delete_shortcut_label">Delete</string>
|
||||
<string name="abc_menu_enter_shortcut_label">Enter</string>
|
||||
<string name="abc_menu_function_shortcut_label">Function+</string>
|
||||
<string name="abc_menu_meta_shortcut_label">Meta+</string>
|
||||
<string name="abc_menu_shift_shortcut_label">Shift+</string>
|
||||
<string name="abc_menu_space_shortcut_label">Space</string>
|
||||
<string name="abc_menu_sym_shortcut_label">Sym+</string>
|
||||
<string name="abc_prepend_shortcut_label">Menu+</string>
|
||||
<string name="abc_search_hint">検索…</string>
|
||||
<string name="abc_searchview_description_clear">検索キーワードを削除</string>
|
||||
<string name="abc_searchview_description_query">検索キーワード</string>
|
||||
<string name="abc_searchview_description_search">検索</string>
|
||||
<string name="abc_searchview_description_submit">検索キーワードを送信</string>
|
||||
<string name="abc_searchview_description_voice">音声検索</string>
|
||||
<string name="abc_shareactionprovider_share_with">共有</string>
|
||||
<string name="abc_shareactionprovider_share_with_application">%sと共有</string>
|
||||
<string name="abc_toolbar_collapse_description">折りたたむ</string>
|
||||
<string name="about">情報</string>
|
||||
<string name="about_about_p1">このプラグインは完全に無料で提供されます。このプラグインで料金を支払ってしまった場合は、販売者に報告をしてください。</string>
|
||||
<string name="about_about_p2">プラグインの QQ グループ: 975854705</string>
|
||||
<string name="about_about_title">このプラグインについて</string>
|
||||
<string name="about_contributors_asset_file">about_contributors_en.json</string>
|
||||
<string name="about_warn_p1">このプラグインは学習とコミュニケーションのみを目的としています。</string>
|
||||
<string name="about_warn_p2">外部プラグインは関連する TOS に違反するため、自己責任でご使用ください。</string>
|
||||
<string name="about_warn_title">警告</string>
|
||||
<string name="advanced_settings">高度な設定</string>
|
||||
<string name="androidx_startup">androidx.startup</string>
|
||||
<string name="api_addr">API アドレス (GitHub 最新リリース API)</string>
|
||||
<string name="app_name">Gakumas Localify</string>
|
||||
<string name="appbar_scrolling_view_behavior">com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior</string>
|
||||
<string name="average">平均</string>
|
||||
<string name="axisx_x">X 軸.x</string>
|
||||
<string name="axisx_y">X 軸.y</string>
|
||||
<string name="axisy_x">Y 軸.x</string>
|
||||
<string name="axisy_y">Y 軸.y</string>
|
||||
<string name="axisz_x">Z 軸.x</string>
|
||||
<string name="axisz_y">Z 軸.y</string>
|
||||
<string name="basic_settings">基本設定</string>
|
||||
<string name="bottom_sheet_behavior">com.google.android.material.bottomsheet.BottomSheetBehavior</string>
|
||||
<string name="bottomsheet_action_collapse">ボトムシートを閉じる</string>
|
||||
<string name="bottomsheet_action_expand">ボトムシートを開く</string>
|
||||
<string name="bottomsheet_action_expand_halfway">下半分を展開</string>
|
||||
<string name="bottomsheet_drag_handle_clicked">ハンドルをダブルタップしてドラッグ</string>
|
||||
<string name="bottomsheet_drag_handle_content_description">ドラッグハンドル</string>
|
||||
<string name="breast_param">胸のパラメーター</string>
|
||||
<string name="breast_scale">胸の大きさ</string>
|
||||
<string name="call_notification_answer_action">応答</string>
|
||||
<string name="call_notification_answer_video_action">動画</string>
|
||||
<string name="call_notification_decline_action">拒否</string>
|
||||
<string name="call_notification_hang_up_action">通話終了</string>
|
||||
<string name="call_notification_incoming_text">着信</string>
|
||||
<string name="call_notification_ongoing_text">通話中</string>
|
||||
<string name="call_notification_screening_text">着信をスクリーニング中</string>
|
||||
<string name="camera_settings">カメラ設定</string>
|
||||
<string name="cancel">キャンセル</string>
|
||||
<string name="character_counter_content_description">%1$d の %2$d に入力された文字</string>
|
||||
<string name="character_counter_overflowed_content_description">文字制限が %2$d 文字中、 %1$d 文字を超えています</string>
|
||||
<string name="character_counter_pattern">%1$d/%2$d</string>
|
||||
<string name="check_built_in_resource">内蔵アセットの更新を確認</string>
|
||||
<string name="check_resource_from_api">リソースの更新を API から確認</string>
|
||||
<string name="check_update">確認</string>
|
||||
<string name="clear_text_end_icon_content_description">テキストを消去</string>
|
||||
<string name="close_drawer">ナビゲーションメニューを閉じる</string>
|
||||
<string name="close_sheet">シートを閉じる</string>
|
||||
<string name="contributors">貢献者</string>
|
||||
<string name="damping">ダンプ中</string>
|
||||
<string name="debug_settings">デバッグ設定</string>
|
||||
<string name="default_assets_check_api">https://api.github.com/repos/NatsumeLS/Gakumas-Translation-Data-EN/releases/latest</string>
|
||||
<string name="default_error_message">入力が無効です</string>
|
||||
<string name="default_popup_window_title">ポップアップウィンドウ</string>
|
||||
<string name="del_remote_after_update">キャッシュファイルを更新後に削除</string>
|
||||
<string name="delete_plugin_resource">プラグインリソースを削除</string>
|
||||
<string name="download">ダウンロード</string>
|
||||
<string name="downloaded_resource_version">ダウンロードされたバージョン</string>
|
||||
<string name="dropdown_menu">ドロップダウンメニュー</string>
|
||||
<string name="enable_breast_param">胸のパラメーターを有効化</string>
|
||||
<string name="enable_free_camera">フリーカメラを有効化</string>
|
||||
<string name="enable_plugin">プラグイン有効化 (ホットリロードなし)</string>
|
||||
<string name="error_a11y_label">エラー: 無効</string>
|
||||
<string name="error_icon_content_description">エラー</string>
|
||||
<string name="export_text">テキストをエクスポート</string>
|
||||
<string name="exposed_dropdown_menu_content_description">ドロップダウンメニューを表示</string>
|
||||
<string name="fab_transformation_scrim_behavior">com.google.android.material.transformation.FabTransformationScrimBehavior</string>
|
||||
<string name="fab_transformation_sheet_behavior">com.google.android.material.transformation.FabTransformationSheetBehavior</string>
|
||||
<string name="force_export_resource">リソースの更新を強制する</string>
|
||||
<string name="gakumas_localify">Gakumas Localify</string>
|
||||
<string name="game_patch">ゲームパッチ</string>
|
||||
<string name="graphic_settings">グラフィック設定</string>
|
||||
<string name="hide_bottom_view_on_scroll_behavior">com.google.android.material.behavior.HideBottomViewOnScrollBehavior</string>
|
||||
<string name="hign">高</string>
|
||||
<string name="home">ホーム</string>
|
||||
<string name="home_shizuku_warning">一部の機能が使用できません</string>
|
||||
<string name="icon_content_description">ダイアログアイコン</string>
|
||||
<string name="in_progress">実行中</string>
|
||||
<string name="indeterminate">部分的に確認済み</string>
|
||||
<string name="install">インストール</string>
|
||||
<string name="installing">インストール中</string>
|
||||
<string name="invalid_zip_file">無効なファイル</string>
|
||||
<string name="invalid_zip_file_warn">このファイルは有効な ZIP 翻訳リソースパックではありません。</string>
|
||||
<string name="isdirty">IsDirty</string>
|
||||
<string name="item_view_role_description">タブ</string>
|
||||
<string name="lazy_init">高速な初期化 (読み込みを遅延)</string>
|
||||
<string name="liveUseCustomeDress">ライブのキャラクターをカスタム</string>
|
||||
<string name="live_costume_head_id">ライブのカスタムヘッド ID (例: costume_head_hski-cstm-0002)</string>
|
||||
<string name="live_custome_dress_id">ライブ衣装のカスタム ID (例: hski-cstm-0002)</string>
|
||||
<string name="login_as_ios">iOS としてログイン</string>
|
||||
<string name="low">低</string>
|
||||
<string name="m3_exceed_max_badge_text_suffix">%1$s%2$s</string>
|
||||
<string name="m3_ref_typeface_brand_medium">sans-serif-medium</string>
|
||||
<string name="m3_ref_typeface_brand_regular">sans-serif</string>
|
||||
<string name="m3_ref_typeface_plain_medium">sans-serif-medium</string>
|
||||
<string name="m3_ref_typeface_plain_regular">sans-serif</string>
|
||||
<string name="m3_sys_motion_easing_emphasized">path(M 0,0 C 0.05, 0, 0.133333, 0.06, 0.166666, 0.4 C 0.208333, 0.82, 0.25, 1, 1, 1)</string>
|
||||
<string name="m3_sys_motion_easing_emphasized_accelerate">cubic-bezier(0.3, 0, 0.8, 0.2)</string>
|
||||
<string name="m3_sys_motion_easing_emphasized_decelerate">cubic-bezier(0.1, 0.7, 0.1, 1)</string>
|
||||
<string name="m3_sys_motion_easing_emphasized_path_data">M 0,0 C 0.05, 0, 0.133333, 0.06, 0.166666, 0.4 C 0.208333, 0.82, 0.25, 1, 1, 1</string>
|
||||
<string name="m3_sys_motion_easing_legacy">cubic-bezier(0.4, 0, 0.2, 1)</string>
|
||||
<string name="m3_sys_motion_easing_legacy_accelerate">cubic-bezier(0.4, 0, 1, 1)</string>
|
||||
<string name="m3_sys_motion_easing_legacy_decelerate">cubic-bezier(0, 0, 0.2, 1)</string>
|
||||
<string name="m3_sys_motion_easing_linear">cubic-bezier(0, 0, 1, 1)</string>
|
||||
<string name="m3_sys_motion_easing_standard">cubic-bezier(0.2, 0, 0, 1)</string>
|
||||
<string name="m3_sys_motion_easing_standard_accelerate">cubic-bezier(0.3, 0, 1, 1)</string>
|
||||
<string name="m3_sys_motion_easing_standard_decelerate">cubic-bezier(0, 0, 0, 1)</string>
|
||||
<string name="m3c_bottom_sheet_collapse_description">ボトムシートを折りたたみます</string>
|
||||
<string name="m3c_bottom_sheet_dismiss_description">ボトムシートを閉じます</string>
|
||||
<string name="m3c_bottom_sheet_drag_handle_description">ドラッグハンドル</string>
|
||||
<string name="m3c_bottom_sheet_expand_description">ボトムシートを開きます</string>
|
||||
<string name="m3c_bottom_sheet_pane_title">ボトムシート</string>
|
||||
<string name="m3c_date_input_headline">入力された日付</string>
|
||||
<string name="m3c_date_input_headline_description">入力された日付: %1$s</string>
|
||||
<string name="m3c_date_input_invalid_for_pattern">想定パターンと一致しない日付: %1$s</string>
|
||||
<string name="m3c_date_input_invalid_not_allowed">許可されない日付: %1$s</string>
|
||||
<string name="m3c_date_input_invalid_year_range">想定される年の範囲(%1$s~%2$s)から日付が外れています</string>
|
||||
<string name="m3c_date_input_label">日付</string>
|
||||
<string name="m3c_date_input_no_input_description">なし</string>
|
||||
<string name="m3c_date_input_title">日付の選択</string>
|
||||
<string name="m3c_date_picker_headline">選択した日付</string>
|
||||
<string name="m3c_date_picker_headline_description">現在の選択: %1$s</string>
|
||||
<string name="m3c_date_picker_navigate_to_year_description">年に移動 %1$s</string>
|
||||
<string name="m3c_date_picker_no_selection_description">なし</string>
|
||||
<string name="m3c_date_picker_scroll_to_earlier_years">これより前の年を表示するにはスクロールしてください</string>
|
||||
<string name="m3c_date_picker_scroll_to_later_years">これより後の年を表示するにはスクロールしてください</string>
|
||||
<string name="m3c_date_picker_switch_to_calendar_mode">カレンダー入力モードに切り替え</string>
|
||||
<string name="m3c_date_picker_switch_to_day_selection">スワイプして年を選択するか、タップして日付の選択に戻ります</string>
|
||||
<string name="m3c_date_picker_switch_to_input_mode">テキスト入力モードに切り替え</string>
|
||||
<string name="m3c_date_picker_switch_to_next_month">翌月に変更</string>
|
||||
<string name="m3c_date_picker_switch_to_previous_month">前月に変更</string>
|
||||
<string name="m3c_date_picker_switch_to_year_selection">年の選択に切り替え</string>
|
||||
<string name="m3c_date_picker_title">日付の選択</string>
|
||||
<string name="m3c_date_picker_today_description">今日</string>
|
||||
<string name="m3c_date_picker_year_picker_pane_title">年の選択ツールの表示</string>
|
||||
<string name="m3c_date_range_input_invalid_range_input">入力された期間は無効です</string>
|
||||
<string name="m3c_date_range_input_title">日付の入力</string>
|
||||
<string name="m3c_date_range_picker_day_in_range">範囲内</string>
|
||||
<string name="m3c_date_range_picker_end_headline">終了日</string>
|
||||
<string name="m3c_date_range_picker_scroll_to_next_month">翌月を表示するにはスクロールしてください</string>
|
||||
<string name="m3c_date_range_picker_scroll_to_previous_month">前月を表示するにはスクロールしてください</string>
|
||||
<string name="m3c_date_range_picker_start_headline">開始日</string>
|
||||
<string name="m3c_date_range_picker_title">日付の選択</string>
|
||||
<string name="m3c_dialog">ダイアログ</string>
|
||||
<string name="m3c_dropdown_menu_collapsed">閉じています</string>
|
||||
<string name="m3c_dropdown_menu_expanded">開いています</string>
|
||||
<string name="m3c_search_bar_search">検索</string>
|
||||
<string name="m3c_snackbar_dismiss">閉じる</string>
|
||||
<string name="m3c_suggestions_available">検索候補は次のとおりです</string>
|
||||
<string name="m3c_time_picker_am">AM</string>
|
||||
<string name="m3c_time_picker_hour">時間</string>
|
||||
<string name="m3c_time_picker_hour_24h_suffix">%1$d 時間</string>
|
||||
<string name="m3c_time_picker_hour_selection">時刻を選択</string>
|
||||
<string name="m3c_time_picker_hour_suffix">"%1$d 時"</string>
|
||||
<string name="m3c_time_picker_hour_text_field">(時間単位)</string>
|
||||
<string name="m3c_time_picker_minute">分</string>
|
||||
<string name="m3c_time_picker_minute_selection">分を選択</string>
|
||||
<string name="m3c_time_picker_minute_suffix">%1$d 分</string>
|
||||
<string name="m3c_time_picker_minute_text_field">(分単位)</string>
|
||||
<string name="m3c_time_picker_period_toggle_description">午前または午後を選択</string>
|
||||
<string name="m3c_time_picker_pm">PM</string>
|
||||
<string name="m3c_tooltip_long_press_label">ツールチップを表示</string>
|
||||
<string name="m3c_tooltip_pane_description">ツールチップ</string>
|
||||
<string name="material_clock_display_divider">:</string>
|
||||
<string name="material_clock_toggle_content_description">午前または午後を選択</string>
|
||||
<string name="material_hour_24h_suffix">%1$s 時間</string>
|
||||
<string name="material_hour_selection">時刻を選択してください</string>
|
||||
<string name="material_hour_suffix">"%1$s時"</string>
|
||||
<string name="material_minute_selection">分を選択</string>
|
||||
<string name="material_minute_suffix">%1$s分</string>
|
||||
<string name="material_motion_easing_accelerated">cubic-bezier(0.4, 0.0, 1.0, 1.0)</string>
|
||||
<string name="material_motion_easing_decelerated">cubic-bezier(0.0, 0.0, 0.2, 1.0)</string>
|
||||
<string name="material_motion_easing_emphasized">path(M 0,0 C 0.05, 0, 0.133333, 0.06, 0.166666, 0.4 C 0.208333, 0.82, 0.25, 1, 1, 1)</string>
|
||||
<string name="material_motion_easing_linear">cubic-bezier(0.0, 0.0, 1.0, 1.0)</string>
|
||||
<string name="material_motion_easing_standard">cubic-bezier(0.4, 0.0, 0.2, 1.0)</string>
|
||||
<string name="material_slider_range_end">Range end</string>
|
||||
<string name="material_slider_range_start">Range start</string>
|
||||
<string name="material_slider_value">Value</string>
|
||||
<string name="material_timepicker_am">AM</string>
|
||||
<string name="material_timepicker_clock_mode_description">時刻を時計で入力するモードに切り替えます。</string>
|
||||
<string name="material_timepicker_hour">時間</string>
|
||||
<string name="material_timepicker_minute">分</string>
|
||||
<string name="material_timepicker_pm">PM</string>
|
||||
<string name="material_timepicker_select_time">時間を選択</string>
|
||||
<string name="material_timepicker_text_input_mode_description">時刻をテキストで入力するモードに切り替えます。</string>
|
||||
<string name="max_high">ウルトラ</string>
|
||||
<string name="middle">中</string>
|
||||
<string name="mtrl_badge_numberless_content_description">新しい通知</string>
|
||||
<string name="mtrl_checkbox_button_icon_path_checked">M14,18.2 11.4,15.6 10,17 14,21 22,13 20.6,11.6z</string>
|
||||
<string name="mtrl_checkbox_button_icon_path_group_name">icon</string>
|
||||
<string name="mtrl_checkbox_button_icon_path_indeterminate">M13.4,15 11,15 11,17 13.4,17 21,17 21,15z</string>
|
||||
<string name="mtrl_checkbox_button_icon_path_name">icon path</string>
|
||||
<string name="mtrl_checkbox_button_path_checked">M23,7H9C7.9,7,7,7.9,7,9v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V9C25,7.9,24.1,7,23,7z</string>
|
||||
<string name="mtrl_checkbox_button_path_group_name">button</string>
|
||||
<string name="mtrl_checkbox_button_path_name">button path</string>
|
||||
<string name="mtrl_checkbox_button_path_unchecked">M23,7H9C7.9,7,7,7.9,7,9v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V9C25,7.9,24.1,7,23,7z M23,23H9V9h14V23z</string>
|
||||
<string name="mtrl_checkbox_state_description_checked">オン</string>
|
||||
<string name="mtrl_checkbox_state_description_indeterminate">一部オン</string>
|
||||
<string name="mtrl_checkbox_state_description_unchecked">オフ</string>
|
||||
<string name="mtrl_chip_close_icon_content_description">%1$s を削除します</string>
|
||||
<string name="mtrl_exceed_max_badge_number_content_description">%1$d 件以上の新しい通知</string>
|
||||
<string name="mtrl_exceed_max_badge_number_suffix">%1$d%2$s</string>
|
||||
<string name="mtrl_picker_a11y_next_month">翌月に変更</string>
|
||||
<string name="mtrl_picker_a11y_prev_month">前月に変更</string>
|
||||
<string name="mtrl_picker_announce_current_range_selection">開始日の選択: %1$s – 終了日の選択: %2$s</string>
|
||||
<string name="mtrl_picker_announce_current_selection">現在の選択: %1$s</string>
|
||||
<string name="mtrl_picker_announce_current_selection_none">なし</string>
|
||||
<string name="mtrl_picker_cancel">キャンセル</string>
|
||||
<string name="mtrl_picker_confirm">OK</string>
|
||||
<string name="mtrl_picker_date_header_selected">%1$s</string>
|
||||
<string name="mtrl_picker_date_header_title">日付を選択してください</string>
|
||||
<string name="mtrl_picker_date_header_unselected">選択した日付</string>
|
||||
<string name="mtrl_picker_day_of_week_column_header">%1$s</string>
|
||||
<string name="mtrl_picker_end_date_description">終了日 %1$s</string>
|
||||
<string name="mtrl_picker_invalid_format">形式が無効です。</string>
|
||||
<string name="mtrl_picker_invalid_format_example">例: %1$s</string>
|
||||
<string name="mtrl_picker_invalid_format_use">使用: %1$s</string>
|
||||
<string name="mtrl_picker_invalid_range">範囲が無効です。</string>
|
||||
<string name="mtrl_picker_navigate_to_current_year_description">現在の年(%1$d)に移動</string>
|
||||
<string name="mtrl_picker_navigate_to_year_description">%1$d 年に移動</string>
|
||||
<string name="mtrl_picker_out_of_range">範囲外: %1$s</string>
|
||||
<string name="mtrl_picker_range_header_only_end_selected">開始日~%1$s</string>
|
||||
<string name="mtrl_picker_range_header_only_start_selected">%1$s~終了日</string>
|
||||
<string name="mtrl_picker_range_header_selected">%1$s~%2$s</string>
|
||||
<string name="mtrl_picker_range_header_title">期間を選択してください</string>
|
||||
<string name="mtrl_picker_range_header_unselected">開始日~終了日</string>
|
||||
<string name="mtrl_picker_save">保存</string>
|
||||
<string name="mtrl_picker_start_date_description">開始日 %1$s</string>
|
||||
<string name="mtrl_picker_text_input_date_hint">日付</string>
|
||||
<string name="mtrl_picker_text_input_date_range_end_hint">終了日</string>
|
||||
<string name="mtrl_picker_text_input_date_range_start_hint">開始日</string>
|
||||
<string name="mtrl_picker_text_input_day_abbr">d</string>
|
||||
<string name="mtrl_picker_text_input_month_abbr">m</string>
|
||||
<string name="mtrl_picker_text_input_year_abbr">y</string>
|
||||
<string name="mtrl_picker_today_description">今日(%1$s)</string>
|
||||
<string name="mtrl_picker_toggle_to_calendar_input_mode">カレンダー入力モードに切り替え</string>
|
||||
<string name="mtrl_picker_toggle_to_day_selection">タップするとカレンダー表示に切り替わります</string>
|
||||
<string name="mtrl_picker_toggle_to_text_input_mode">テキスト入力モードに切り替え</string>
|
||||
<string name="mtrl_picker_toggle_to_year_selection">タップすると年表示に切り替わります</string>
|
||||
<string name="mtrl_switch_thumb_group_name">circle_group</string>
|
||||
<string name="mtrl_switch_thumb_path_checked">M4,16 A12,12 0 0,1 16,4 H16 A12,12 0 0,1 16,28 H16 A12,12 0 0,1 4,16</string>
|
||||
<string name="mtrl_switch_thumb_path_morphing">M0,16 A11,11 0 0,1 11,5 H21 A11,11 0 0,1 21,27 H11 A11,11 0 0,1 0,16</string>
|
||||
<string name="mtrl_switch_thumb_path_name">circle</string>
|
||||
<string name="mtrl_switch_thumb_path_pressed">M2,16 A14,14 0 0,1 16,2 H16 A14,14 0 0,1 16,30 H16 A14,14 0 0,1 2,16</string>
|
||||
<string name="mtrl_switch_thumb_path_unchecked">M8,16 A8,8 0 0,1 16,8 H16 A8,8 0 0,1 16,24 H16 A8,8 0 0,1 8,16</string>
|
||||
<string name="mtrl_switch_track_decoration_path">M1,16 A15,15 0 0,1 16,1 H36 A15,15 0 0,1 36,31 H16 A15,15 0 0,1 1,16</string>
|
||||
<string name="mtrl_switch_track_path">M0,16 A16,16 0 0,1 16,0 H36 A16,16 0 0,1 36,32 H16 A16,16 0 0,1 0,16</string>
|
||||
<string name="mtrl_timepicker_cancel">キャンセル</string>
|
||||
<string name="mtrl_timepicker_confirm">OK</string>
|
||||
<string name="navigation_menu">ナビゲーションメニュー</string>
|
||||
<string name="not_selected">未選択</string>
|
||||
<string name="off">OFF</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="on">ON</string>
|
||||
<string name="orientation_landscape">横画面</string>
|
||||
<string name="orientation_lock">画面を固定</string>
|
||||
<string name="orientation_orig">オリジナル</string>
|
||||
<string name="orientation_portrait">縦画面</string>
|
||||
<string name="password_toggle_content_description">パスワードを表示</string>
|
||||
<string name="patch_debuggable">デバッグを可能にする</string>
|
||||
<string name="patch_finished">パッチが完了しました。インストールをしますか?</string>
|
||||
<string name="patch_integrated">統合</string>
|
||||
<string name="patch_integrated_desc">"モジュールを埋め込んだ状態なアプリでパッチを当てます。
|
||||
パッチを適用したアプリは LSPatch Manager なしで実行できますが、動的に管理はできません。
|
||||
統合パッチが適用されたアプリは、LSPatch Manager がインストールされていないデバイスでも使用が可能です。"</string>
|
||||
<string name="patch_local">ローカル</string>
|
||||
<string name="patch_local_desc">"モジュールを埋め込まずにアプリにパッチを当てます。
|
||||
Xposed スコープは再パッチなしで動的に変更が可能です。
|
||||
ローカルでのパッチを当てたアプリは、ローカルのデバイスでのみ実行可能です。"</string>
|
||||
<string name="patch_mode">パッチモード</string>
|
||||
<string name="patch_uninstall_confirm">アンインストールをしてもよろしいですか?</string>
|
||||
<string name="patch_uninstall_text">"署名が異なるため、パッチをインストールする前に元となるアプリをアンインストールする必要があります。
|
||||
個人データのバックアップを設定済みであることを確認してください。"</string>
|
||||
<string name="path_password_eye">M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z</string>
|
||||
<string name="path_password_eye_mask_strike_through">M2,4.27 L19.73,22 L22.27,19.46 L4.54,1.73 L4.54,1 L23,1 L23,23 L1,23 L1,4.27 Z</string>
|
||||
<string name="path_password_eye_mask_visible">M2,4.27 L2,4.27 L4.54,1.73 L4.54,1.73 L4.54,1 L23,1 L23,23 L1,23 L1,4.27 Z</string>
|
||||
<string name="path_password_strike_through">M3.27,4.27 L19.74,20.74</string>
|
||||
<string name="pendulum">揺れ</string>
|
||||
<string name="pendulumrange">揺れの範囲</string>
|
||||
<string name="plugin_code">プラグインのコード</string>
|
||||
<string name="project_contribution">プロジェクトの貢献者</string>
|
||||
<string name="range_end">範囲の終了</string>
|
||||
<string name="range_start">範囲の開始</string>
|
||||
<string name="renderscale">RenderScale (0.5/0.59/0.67/0.77/1.0)</string>
|
||||
<string name="replace_font">フォントを置換する</string>
|
||||
<string name="reserve_patched">パッチ済みの APK を予約する</string>
|
||||
<string name="resource_settings">リソース設定</string>
|
||||
<string name="resource_url">リソース URL</string>
|
||||
<string name="rootweight">ルートウエイト</string>
|
||||
<string name="search_menu_title">検索</string>
|
||||
<string name="searchbar_scrolling_view_behavior">com.google.android.material.search.SearchBar$ScrollingViewBehavior</string>
|
||||
<string name="searchview_clear_text_content_description">テキストを消去</string>
|
||||
<string name="searchview_navigation_content_description">戻る</string>
|
||||
<string name="selected">選択済み</string>
|
||||
<string name="setFpsTitle">最大 FPS (0 はオリジナルの設定を使用します)</string>
|
||||
<string name="shizuku_available">Shizuku サービスが有効です</string>
|
||||
<string name="shizuku_unavailable">Shizuku サービスが接続されていません</string>
|
||||
<string name="side_sheet_accessibility_pane_title">サイドシート</string>
|
||||
<string name="side_sheet_behavior">com.google.android.material.sidesheet.SideSheetBehavior</string>
|
||||
<string name="spring">跳ね</string>
|
||||
<string name="start_game">ゲーム開始 / ホットリロードの設定</string>
|
||||
<string name="status_bar_notification_info_overflow">999+</string>
|
||||
<string name="stiffness">剛性</string>
|
||||
<string name="support_file_types">"対応ファイル:
|
||||
単一または複数選択: apk
|
||||
単一選択: apks、xapk、zip"</string>
|
||||
<string name="switch_role">切り替え</string>
|
||||
<string name="tab">タブ</string>
|
||||
<string name="template_percent">%1$d パーセント。</string>
|
||||
<string name="test_mode_live">テストモード - ライブ</string>
|
||||
<string name="text_hook_test_mode">テキストフックテストモード</string>
|
||||
<string name="tooltip_description">ツールチップ</string>
|
||||
<string name="tooltip_label">ツールチップを表示</string>
|
||||
<string name="translation_repository">翻訳のリポジトリ</string>
|
||||
<string name="translation_resource_update">翻訳リソースを更新</string>
|
||||
<string name="unlockAllLive">すべてのライブを開放</string>
|
||||
<string name="unlockAllLiveCostume">すべてのライブ衣装を開放</string>
|
||||
<string name="useCustomeGraphicSettings">カスタムグラフィック設定を使用する</string>
|
||||
<string name="useMasterDBTrans">MasterDB のローカライズを有効化</string>
|
||||
<string name="use_remote_zip_resource">リモート ZIP リソースを使用する</string>
|
||||
<string name="usearmcorrection">Arm コレクションを使用する</string>
|
||||
<string name="uselimit_0_1">リミットレンジの倍率 (0 は無制限)</string>
|
||||
<string name="uselimitmultiplier">乗数制限を使用する</string>
|
||||
<string name="usescale">胸の大きさを使用する</string>
|
||||
<string name="very_high">最高</string>
|
||||
<string name="warning">警告</string>
|
||||
</resources>
|
||||
@@ -8,14 +8,17 @@
|
||||
<string name="start_game">以上述配置启动游戏/重载配置</string>
|
||||
<string name="setFpsTitle">最大 FPS (0 为保持游戏原设置)</string>
|
||||
<string name="unlockAllLive">解锁所有 Live</string>
|
||||
<string name="unlockAllLiveCostume">解锁所有 Live 服装</string>
|
||||
<string name="liveUseCustomeDress">Live 使用自定义角色</string>
|
||||
<string name="live_costume_head_id">Live 自定义头部 ID (例: costume_head_hski-cstm-0002)</string>
|
||||
<string name="live_custome_dress_id">Live 自定义服装 ID (例: hski-cstm-0002)</string>
|
||||
<string name="useCustomeGraphicSettings">使用自定义画质设置</string>
|
||||
<string name="renderscale">RenderScale (0.5/0.59/0.67/0.77/1.0)</string>
|
||||
<string name="text_hook_test_mode">文本 hook 测试模式</string>
|
||||
<string name="useMasterDBTrans">使用 MasterDB 本地化</string>
|
||||
<string name="export_text">导出文本</string>
|
||||
<string name="force_export_resource">启动后强制导出资源</string>
|
||||
<string name="login_as_ios">以 iOS 登陆</string>
|
||||
<string name="max_high">极高</string>
|
||||
<string name="very_high">超高</string>
|
||||
<string name="hign">高</string>
|
||||
|
||||
@@ -8,14 +8,17 @@
|
||||
<string name="start_game">Start Game / Hot Reload Config</string>
|
||||
<string name="setFpsTitle">Max FPS (0 is Use Original Settings)</string>
|
||||
<string name="unlockAllLive">Unlock All Live</string>
|
||||
<string name="unlockAllLiveCostume">Unlock All Live Costume</string>
|
||||
<string name="liveUseCustomeDress">Live Custom Character</string>
|
||||
<string name="live_costume_head_id">Live Custom Head ID (eg. costume_head_hski-cstm-0002)</string>
|
||||
<string name="live_custome_dress_id">Live Custom Dress ID (eg. hski-cstm-0002)</string>
|
||||
<string name="useCustomeGraphicSettings">Use Custom Graphics Settings</string>
|
||||
<string name="renderscale">RenderScale (0.5/0.59/0.67/0.77/1.0)</string>
|
||||
<string name="text_hook_test_mode">Text Hook Test Mode</string>
|
||||
<string name="useMasterDBTrans">Enable MasterDB Localization</string>
|
||||
<string name="export_text">Export Text</string>
|
||||
<string name="force_export_resource">Force Update Resource</string>
|
||||
<string name="login_as_ios">Login as iOS</string>
|
||||
<string name="max_high">Ultra</string>
|
||||
<string name="very_high">Very High</string>
|
||||
<string name="hign">High</string>
|
||||
|
||||
@@ -17,7 +17,7 @@ lifecycle = "2.8.2"
|
||||
material = "1.12.0"
|
||||
navigationCompose = "2.7.7"
|
||||
xdl = "2.1.1"
|
||||
shadowhook = "1.0.9"
|
||||
shadowhook = "1.0.10"
|
||||
serialization="1.7.1"
|
||||
zip4j = "2.9.1"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user