Compare commits
No commits in common. "main" and "main" have entirely different histories.
|
|
@ -18,5 +18,3 @@ local.properties
|
||||||
/.kotlin
|
/.kotlin
|
||||||
/app/debug
|
/app/debug
|
||||||
/app/release
|
/app/release
|
||||||
|
|
||||||
app/src/main/assets/gakumas-local
|
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@ android {
|
||||||
applicationId "io.github.chinosk.gakumas.localify"
|
applicationId "io.github.chinosk.gakumas.localify"
|
||||||
minSdk 29
|
minSdk 29
|
||||||
targetSdk 34
|
targetSdk 34
|
||||||
versionCode 12
|
versionCode 11
|
||||||
versionName "v3.3.1"
|
versionName "v2.1.0"
|
||||||
buildConfigField "String", "VERSION_NAME", "\"${versionName}\""
|
buildConfigField "String", "VERSION_NAME", "\"${versionName}\""
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -2,33 +2,33 @@ import os
|
||||||
|
|
||||||
|
|
||||||
logs = """
|
logs = """
|
||||||
Duplicate class com.google.common.util.concurrent.ListenableFuture found in modules listenablefuture-1.0.jar -> listenablefuture-1.0 (com.google.guava:listenablefuture:1.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
|
Duplicate class com.google.common.util.concurrent.ListenableFuture found in modules listenablefuture-1.0.jar -> listenablefuture-1.0 (com.google.guava:listenablefuture:1.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||||
Duplicate class com.google.errorprone.annotations.CanIgnoreReturnValue found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
|
Duplicate class com.google.errorprone.annotations.CanIgnoreReturnValue found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||||
Duplicate class com.google.errorprone.annotations.CheckReturnValue found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
|
Duplicate class com.google.errorprone.annotations.CheckReturnValue found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||||
Duplicate class com.google.errorprone.annotations.CompatibleWith found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
|
Duplicate class com.google.errorprone.annotations.CompatibleWith found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||||
Duplicate class com.google.errorprone.annotations.CompileTimeConstant found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
|
Duplicate class com.google.errorprone.annotations.CompileTimeConstant found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||||
Duplicate class com.google.errorprone.annotations.DoNotCall found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
|
Duplicate class com.google.errorprone.annotations.DoNotCall found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||||
Duplicate class com.google.errorprone.annotations.DoNotMock found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
|
Duplicate class com.google.errorprone.annotations.DoNotMock found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||||
Duplicate class com.google.errorprone.annotations.ForOverride found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
|
Duplicate class com.google.errorprone.annotations.ForOverride found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||||
Duplicate class com.google.errorprone.annotations.FormatMethod found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
|
Duplicate class com.google.errorprone.annotations.FormatMethod found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||||
Duplicate class com.google.errorprone.annotations.FormatString found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
|
Duplicate class com.google.errorprone.annotations.FormatString found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||||
Duplicate class com.google.errorprone.annotations.Immutable found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
|
Duplicate class com.google.errorprone.annotations.Immutable found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||||
Duplicate class com.google.errorprone.annotations.IncompatibleModifiers found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
|
Duplicate class com.google.errorprone.annotations.IncompatibleModifiers found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||||
Duplicate class com.google.errorprone.annotations.InlineMe found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
|
Duplicate class com.google.errorprone.annotations.InlineMe found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||||
Duplicate class com.google.errorprone.annotations.InlineMeValidationDisabled found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
|
Duplicate class com.google.errorprone.annotations.InlineMeValidationDisabled found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||||
Duplicate class com.google.errorprone.annotations.Keep found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
|
Duplicate class com.google.errorprone.annotations.Keep found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||||
Duplicate class com.google.errorprone.annotations.Modifier found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
|
Duplicate class com.google.errorprone.annotations.Modifier found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||||
Duplicate class com.google.errorprone.annotations.MustBeClosed found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
|
Duplicate class com.google.errorprone.annotations.MustBeClosed found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||||
Duplicate class com.google.errorprone.annotations.NoAllocation found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
|
Duplicate class com.google.errorprone.annotations.NoAllocation found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||||
Duplicate class com.google.errorprone.annotations.OverridingMethodsMustInvokeSuper found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
|
Duplicate class com.google.errorprone.annotations.OverridingMethodsMustInvokeSuper found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||||
Duplicate class com.google.errorprone.annotations.RequiredModifiers found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
|
Duplicate class com.google.errorprone.annotations.RequiredModifiers found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||||
Duplicate class com.google.errorprone.annotations.RestrictedApi found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
|
Duplicate class com.google.errorprone.annotations.RestrictedApi found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||||
Duplicate class com.google.errorprone.annotations.SuppressPackageLocation found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
|
Duplicate class com.google.errorprone.annotations.SuppressPackageLocation found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||||
Duplicate class com.google.errorprone.annotations.Var found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
|
Duplicate class com.google.errorprone.annotations.Var found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||||
Duplicate class com.google.errorprone.annotations.concurrent.GuardedBy found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
|
Duplicate class com.google.errorprone.annotations.concurrent.GuardedBy found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||||
Duplicate class com.google.errorprone.annotations.concurrent.LazyInit found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
|
Duplicate class com.google.errorprone.annotations.concurrent.LazyInit found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||||
Duplicate class com.google.errorprone.annotations.concurrent.LockMethod found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
|
Duplicate class com.google.errorprone.annotations.concurrent.LockMethod found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||||
Duplicate class com.google.errorprone.annotations.concurrent.UnlockMethod found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
|
Duplicate class com.google.errorprone.annotations.concurrent.UnlockMethod found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for i in logs.split("\n"):
|
for i in logs.split("\n"):
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -39,7 +39,6 @@ add_library(${CMAKE_PROJECT_NAME} SHARED
|
||||||
libMarryKotone.cpp
|
libMarryKotone.cpp
|
||||||
GakumasLocalify/Plugin.cpp
|
GakumasLocalify/Plugin.cpp
|
||||||
GakumasLocalify/Hook.cpp
|
GakumasLocalify/Hook.cpp
|
||||||
GakumasLocalify/HookTexture.cpp
|
|
||||||
GakumasLocalify/Log.cpp
|
GakumasLocalify/Log.cpp
|
||||||
GakumasLocalify/Misc.cpp
|
GakumasLocalify/Misc.cpp
|
||||||
GakumasLocalify/Local.cpp
|
GakumasLocalify/Local.cpp
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
#include "../platformDefine.hpp"
|
|
||||||
|
|
||||||
#ifndef GKMS_WINDOWS
|
|
||||||
#define KEY_W 51
|
#define KEY_W 51
|
||||||
#define KEY_S 47
|
#define KEY_S 47
|
||||||
#define KEY_A 29
|
#define KEY_A 29
|
||||||
|
|
@ -27,32 +24,6 @@
|
||||||
|
|
||||||
#define WM_KEYDOWN 0
|
#define WM_KEYDOWN 0
|
||||||
#define WM_KEYUP 1
|
#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_A 96
|
||||||
#define BTN_B 97
|
#define BTN_B 97
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,797 +0,0 @@
|
||||||
#include "HookTexture.h"
|
|
||||||
|
|
||||||
#include "Log.h"
|
|
||||||
#include "Il2cppUtils.hpp"
|
|
||||||
#include "Local.h"
|
|
||||||
#include "config/Config.hpp"
|
|
||||||
#include "../deps/UnityResolve/UnityResolve.hpp"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cctype>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <exception>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <string_view>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <unordered_set>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace GakumasLocal::HookMain
|
|
||||||
{
|
|
||||||
using Il2cppString = UnityResolve::UnityType::String;
|
|
||||||
|
|
||||||
extern void* (*Sprite_get_texture_Orig)(void* self);
|
|
||||||
|
|
||||||
bool IsNativeObjectAlive(void* obj);
|
|
||||||
|
|
||||||
Il2cppUtils::Il2CppClassHead* Texture2DClass = nullptr;
|
|
||||||
Il2cppUtils::Il2CppClassHead* SpriteClass = nullptr;
|
|
||||||
std::unordered_map<std::string, uint32_t> LoadedLocalTextureHandles{};
|
|
||||||
std::unordered_set<std::string> AppliedLocalTextureKeys{};
|
|
||||||
|
|
||||||
Il2cppUtils::Il2CppClassHead* GetTexture2DClass() {
|
|
||||||
if (!Texture2DClass) {
|
|
||||||
const auto textureClass = Il2cppUtils::GetClass("UnityEngine.CoreModule.dll", "UnityEngine", "Texture2D");
|
|
||||||
if (textureClass) {
|
|
||||||
Texture2DClass = static_cast<Il2cppUtils::Il2CppClassHead*>(textureClass->address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Texture2DClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
Il2cppUtils::Il2CppClassHead* GetSpriteClass() {
|
|
||||||
if (!SpriteClass) {
|
|
||||||
const auto spriteClass = Il2cppUtils::GetClass("UnityEngine.CoreModule.dll", "UnityEngine", "Sprite");
|
|
||||||
if (spriteClass) {
|
|
||||||
SpriteClass = static_cast<Il2cppUtils::Il2CppClassHead*>(spriteClass->address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return SpriteClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsTexture2D(void* obj) {
|
|
||||||
const auto textureClass = GetTexture2DClass();
|
|
||||||
if (!obj || !textureClass) return false;
|
|
||||||
|
|
||||||
const auto objClass = Il2cppUtils::get_class_from_instance(obj);
|
|
||||||
if (objClass == textureClass) return true;
|
|
||||||
|
|
||||||
return UnityResolve::Invoke<bool>("il2cpp_class_is_assignable_from", textureClass, objClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsSprite(void* obj) {
|
|
||||||
const auto spriteClass = GetSpriteClass();
|
|
||||||
if (!obj || !spriteClass) return false;
|
|
||||||
|
|
||||||
const auto objClass = Il2cppUtils::get_class_from_instance(obj);
|
|
||||||
if (objClass == spriteClass) return true;
|
|
||||||
|
|
||||||
return UnityResolve::Invoke<bool>("il2cpp_class_is_assignable_from", spriteClass, objClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
Il2cppString* GetObjectName(void* obj) {
|
|
||||||
if (!obj) return nullptr;
|
|
||||||
|
|
||||||
static auto Object_GetName = reinterpret_cast<Il2cppString * (*)(void*)>(
|
|
||||||
Il2cppUtils::il2cpp_resolve_icall("UnityEngine.Object::GetName(UnityEngine.Object)"));
|
|
||||||
return Object_GetName ? Object_GetName(obj) : nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetDontUnloadUnusedAsset(void* obj) {
|
|
||||||
if (!obj) return;
|
|
||||||
|
|
||||||
static auto Object_set_hideFlags = reinterpret_cast<void (*)(void*, int)>(
|
|
||||||
Il2cppUtils::GetMethodPointer("UnityEngine.CoreModule.dll", "UnityEngine", "Object", "set_hideFlags"));
|
|
||||||
if (Object_set_hideFlags) {
|
|
||||||
Object_set_hideFlags(obj, 32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddTexturePathCandidate(std::vector<std::filesystem::path>& candidates, const std::filesystem::path& path) {
|
|
||||||
if (path.empty()) return;
|
|
||||||
if (std::find(candidates.begin(), candidates.end(), path) == candidates.end()) {
|
|
||||||
candidates.emplace_back(path);
|
|
||||||
}
|
|
||||||
if (!path.has_extension()) {
|
|
||||||
auto pngPath = path;
|
|
||||||
pngPath += ".png";
|
|
||||||
if (std::find(candidates.begin(), candidates.end(), pngPath) == candidates.end()) {
|
|
||||||
candidates.emplace_back(std::move(pngPath));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class TextureCategory {
|
|
||||||
Image,
|
|
||||||
Atlas,
|
|
||||||
Others,
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string ToLowerAscii(std::string value) {
|
|
||||||
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char ch) {
|
|
||||||
return static_cast<char>(std::tolower(ch));
|
|
||||||
});
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
TextureCategory GetTextureCategory(const std::string& textureName) {
|
|
||||||
const auto lowerName = ToLowerAscii(std::filesystem::path(textureName).filename().generic_string());
|
|
||||||
if (lowerName.rfind("img", 0) == 0) {
|
|
||||||
return TextureCategory::Image;
|
|
||||||
}
|
|
||||||
if (lowerName.rfind("sactx", 0) == 0) {
|
|
||||||
return TextureCategory::Atlas;
|
|
||||||
}
|
|
||||||
return TextureCategory::Others;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::filesystem::path GetTextureCategoryDirName(TextureCategory category) {
|
|
||||||
switch (category) {
|
|
||||||
case TextureCategory::Image:
|
|
||||||
return "image";
|
|
||||||
case TextureCategory::Atlas:
|
|
||||||
return "atlas";
|
|
||||||
default:
|
|
||||||
return "others";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::filesystem::path GetTextureReplaceRoot() {
|
|
||||||
return Local::GetBasePath() / "texture2d";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::filesystem::path GetTextureDumpRoot() {
|
|
||||||
return Local::GetBasePath() / "dump-files" / "texture2d";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::filesystem::path GetTextureReplaceBase(const std::string& textureName) {
|
|
||||||
return GetTextureReplaceRoot() / GetTextureCategoryDirName(GetTextureCategory(textureName));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::filesystem::path GetTextureDumpBase(const std::string& textureName) {
|
|
||||||
return GetTextureDumpRoot() / GetTextureCategoryDirName(GetTextureCategory(textureName));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::string> SplitString(const std::string& value, char delimiter) {
|
|
||||||
std::vector<std::string> parts;
|
|
||||||
size_t start = 0;
|
|
||||||
while (start <= value.size()) {
|
|
||||||
const auto end = value.find(delimiter, start);
|
|
||||||
parts.emplace_back(value.substr(start, end == std::string::npos ? std::string::npos : end - start));
|
|
||||||
if (end == std::string::npos) break;
|
|
||||||
start = end + 1;
|
|
||||||
}
|
|
||||||
return parts;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppendTextureCandidates(std::vector<std::filesystem::path>& target, std::vector<std::filesystem::path>&& source);
|
|
||||||
std::string NormalizeLocalAssetKey(const std::filesystem::path& path);
|
|
||||||
|
|
||||||
bool IsHexHashPart(const std::string& value) {
|
|
||||||
return value.size() == 8 && std::all_of(value.begin(), value.end(), [](unsigned char ch) {
|
|
||||||
return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string GetPortableSactxTextureName(const std::string& objectName) {
|
|
||||||
auto fileName = std::filesystem::path(objectName).filename().generic_string();
|
|
||||||
if (fileName.ends_with(".png")) {
|
|
||||||
fileName.resize(fileName.size() - 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto parts = SplitString(fileName, '-');
|
|
||||||
if (parts.size() < 5 || parts[0] != "sactx" || parts[2].find('x') == std::string::npos) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto atlasEnd = IsHexHashPart(parts.back()) ? parts.size() - 1 : parts.size();
|
|
||||||
if (atlasEnd <= 4) return {};
|
|
||||||
|
|
||||||
std::string portableName = parts[0] + "-" + parts[1] + "-" + parts[2];
|
|
||||||
for (size_t i = 4; i < atlasEnd; ++i) {
|
|
||||||
portableName += "-" + parts[i];
|
|
||||||
}
|
|
||||||
return portableName;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unordered_map<std::string, std::unordered_map<std::string, std::vector<std::filesystem::path>>> RecursiveTexturePathIndex{};
|
|
||||||
|
|
||||||
std::vector<std::filesystem::path> GetRecursiveTextureCandidates(const std::filesystem::path& basePath,
|
|
||||||
const std::string& lookupName) {
|
|
||||||
std::vector<std::filesystem::path> candidates;
|
|
||||||
if (lookupName.empty() || !std::filesystem::exists(basePath)) return candidates;
|
|
||||||
|
|
||||||
const auto baseKey = NormalizeLocalAssetKey(basePath);
|
|
||||||
auto& index = RecursiveTexturePathIndex[baseKey];
|
|
||||||
if (index.empty()) {
|
|
||||||
for (const auto& entry : std::filesystem::recursive_directory_iterator(basePath)) {
|
|
||||||
if (!entry.is_regular_file()) continue;
|
|
||||||
|
|
||||||
const auto& path = entry.path();
|
|
||||||
if (ToLowerAscii(path.extension().generic_string()) != ".png") continue;
|
|
||||||
|
|
||||||
const auto fileName = path.filename().generic_string();
|
|
||||||
const auto stemName = path.stem().generic_string();
|
|
||||||
index[fileName].emplace_back(path);
|
|
||||||
if (stemName != fileName) {
|
|
||||||
index[stemName].emplace_back(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (const auto iter = index.find(lookupName); iter != index.end()) {
|
|
||||||
candidates.insert(candidates.end(), iter->second.begin(), iter->second.end());
|
|
||||||
}
|
|
||||||
return candidates;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::filesystem::path> GetNamedTextureCandidates(const std::filesystem::path& assetName) {
|
|
||||||
std::vector<std::filesystem::path> candidates;
|
|
||||||
if (assetName.empty()) return candidates;
|
|
||||||
|
|
||||||
const auto basePath = GetTextureReplaceBase(assetName.filename().generic_string());
|
|
||||||
AddTexturePathCandidate(candidates, basePath / assetName);
|
|
||||||
if (assetName.has_parent_path()) {
|
|
||||||
AddTexturePathCandidate(candidates, basePath / assetName.filename());
|
|
||||||
}
|
|
||||||
AppendTextureCandidates(candidates, GetRecursiveTextureCandidates(basePath, assetName.filename().generic_string()));
|
|
||||||
|
|
||||||
const auto portableAssetName = GetPortableSactxTextureName(assetName.filename().generic_string());
|
|
||||||
if (!portableAssetName.empty()) {
|
|
||||||
AddTexturePathCandidate(candidates, basePath / portableAssetName);
|
|
||||||
AppendTextureCandidates(candidates, GetRecursiveTextureCandidates(basePath, portableAssetName));
|
|
||||||
}
|
|
||||||
return candidates;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::filesystem::path> GetSpriteTextureCandidates(const std::string& objectName) {
|
|
||||||
std::vector<std::filesystem::path> candidates;
|
|
||||||
if (objectName.empty()) return candidates;
|
|
||||||
|
|
||||||
auto safeObjectName = objectName;
|
|
||||||
std::replace(safeObjectName.begin(), safeObjectName.end(), '|', '_');
|
|
||||||
|
|
||||||
const auto basePath = GetTextureReplaceBase(safeObjectName);
|
|
||||||
AddTexturePathCandidate(candidates, basePath / objectName);
|
|
||||||
if (safeObjectName != objectName) {
|
|
||||||
AddTexturePathCandidate(candidates, basePath / safeObjectName);
|
|
||||||
}
|
|
||||||
AppendTextureCandidates(candidates, GetRecursiveTextureCandidates(basePath, safeObjectName));
|
|
||||||
const auto portableObjectName = GetPortableSactxTextureName(safeObjectName);
|
|
||||||
if (!portableObjectName.empty() && portableObjectName != objectName && portableObjectName != safeObjectName) {
|
|
||||||
AddTexturePathCandidate(candidates, basePath / portableObjectName);
|
|
||||||
AppendTextureCandidates(candidates, GetRecursiveTextureCandidates(basePath, portableObjectName));
|
|
||||||
}
|
|
||||||
return candidates;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppendTextureCandidates(std::vector<std::filesystem::path>& target, std::vector<std::filesystem::path>&& source) {
|
|
||||||
target.insert(target.end(),
|
|
||||||
std::make_move_iterator(source.begin()),
|
|
||||||
std::make_move_iterator(source.end()));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::filesystem::path> GetSpriteAssetTextureCandidates(void* sprite, const std::string& assetName) {
|
|
||||||
std::vector<std::filesystem::path> candidates;
|
|
||||||
|
|
||||||
const auto assetPath = std::filesystem::path(assetName);
|
|
||||||
if (!assetName.empty()) {
|
|
||||||
AppendTextureCandidates(candidates, GetSpriteTextureCandidates(assetPath.filename().generic_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sprite && Sprite_get_texture_Orig) {
|
|
||||||
if (const auto texture = Sprite_get_texture_Orig(sprite)) {
|
|
||||||
if (const auto textureName = GetObjectName(texture)) {
|
|
||||||
AppendTextureCandidates(candidates, GetSpriteTextureCandidates(textureName->ToString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return candidates;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string NormalizeLocalAssetKey(const std::filesystem::path& path) {
|
|
||||||
auto key = path.lexically_normal().generic_string();
|
|
||||||
std::replace(key.begin(), key.end(), '\\', '/');
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string SanitizeDumpPathPart(std::string part) {
|
|
||||||
constexpr std::string_view invalidChars = "<>:\"/\\|?*";
|
|
||||||
if (part.empty() || part == "." || part == "..") return "_";
|
|
||||||
|
|
||||||
for (auto& ch : part) {
|
|
||||||
if (static_cast<unsigned char>(ch) < 32 || invalidChars.find(ch) != std::string_view::npos) {
|
|
||||||
ch = '_';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!part.empty() && (part.back() == '.' || part.back() == ' ')) {
|
|
||||||
part.back() = '_';
|
|
||||||
}
|
|
||||||
return part.empty() ? "_" : part;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::filesystem::path SanitizeDumpSubPath(const std::filesystem::path& dumpSubDir) {
|
|
||||||
std::filesystem::path safePath;
|
|
||||||
for (const auto& part : dumpSubDir) {
|
|
||||||
const auto partString = part.generic_string();
|
|
||||||
if (partString.empty() || partString == "." || partString == ".."
|
|
||||||
|| part == part.root_name() || part == part.root_directory()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
safePath /= SanitizeDumpPathPart(partString);
|
|
||||||
}
|
|
||||||
return safePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DumpTexture2D(void* texture2D) {
|
|
||||||
if (!IsTexture2D(texture2D)) return false;
|
|
||||||
|
|
||||||
const auto objectName = GetObjectName(texture2D);
|
|
||||||
const auto textureName = objectName ? objectName->ToString() : std::string("texture");
|
|
||||||
const auto dumpDir = GetTextureDumpBase(textureName);
|
|
||||||
const auto dumpPath = dumpDir / (SanitizeDumpPathPart(textureName) + ".png");
|
|
||||||
|
|
||||||
if (std::filesystem::exists(dumpPath)) return true;
|
|
||||||
|
|
||||||
static auto Texture2D_get_width = [] {
|
|
||||||
const auto textureClass = GetTexture2DClass();
|
|
||||||
const auto method = textureClass ? Il2cppUtils::il2cpp_class_get_method_from_name(textureClass, "get_width", 0) : nullptr;
|
|
||||||
return method ? reinterpret_cast<int (*)(void*)>(method->methodPointer) : nullptr;
|
|
||||||
}();
|
|
||||||
static auto Texture2D_get_height = [] {
|
|
||||||
const auto textureClass = GetTexture2DClass();
|
|
||||||
const auto method = textureClass ? Il2cppUtils::il2cpp_class_get_method_from_name(textureClass, "get_height", 0) : nullptr;
|
|
||||||
return method ? reinterpret_cast<int (*)(void*)>(method->methodPointer) : nullptr;
|
|
||||||
}();
|
|
||||||
static auto Texture2D_ctor = [] {
|
|
||||||
const auto textureClass = GetTexture2DClass();
|
|
||||||
const auto ctor = textureClass ? Il2cppUtils::il2cpp_class_get_method_from_name(textureClass, ".ctor", 2) : nullptr;
|
|
||||||
return ctor ? reinterpret_cast<void (*)(void*, int, int)>(ctor->methodPointer) : nullptr;
|
|
||||||
}();
|
|
||||||
static auto Texture2D_ReadPixels = [] {
|
|
||||||
const auto textureClass = GetTexture2DClass();
|
|
||||||
const auto method = textureClass ? Il2cppUtils::il2cpp_class_get_method_from_name(textureClass, "ReadPixels", 3) : nullptr;
|
|
||||||
return method ? reinterpret_cast<void (*)(void*, UnityResolve::UnityType::Rect, int, int)>(method->methodPointer) : nullptr;
|
|
||||||
}();
|
|
||||||
static auto Texture2D_Apply = [] {
|
|
||||||
const auto textureClass = GetTexture2DClass();
|
|
||||||
const auto method = textureClass ? Il2cppUtils::il2cpp_class_get_method_from_name(textureClass, "Apply", 0) : nullptr;
|
|
||||||
return method ? reinterpret_cast<void (*)(void*)>(method->methodPointer) : nullptr;
|
|
||||||
}();
|
|
||||||
static auto RenderTexture_GetTemporary = [] {
|
|
||||||
const auto renderTextureClass = Il2cppUtils::GetClass("UnityEngine.CoreModule.dll", "UnityEngine", "RenderTexture");
|
|
||||||
const auto method = renderTextureClass ? Il2cppUtils::il2cpp_class_get_method_from_name(renderTextureClass->address, "GetTemporary", 3) : nullptr;
|
|
||||||
return method ? reinterpret_cast<void* (*)(int, int, int)>(method->methodPointer) : nullptr;
|
|
||||||
}();
|
|
||||||
static auto RenderTexture_ReleaseTemporary = [] {
|
|
||||||
const auto renderTextureClass = Il2cppUtils::GetClass("UnityEngine.CoreModule.dll", "UnityEngine", "RenderTexture");
|
|
||||||
const auto method = renderTextureClass ? Il2cppUtils::il2cpp_class_get_method_from_name(renderTextureClass->address, "ReleaseTemporary", 1) : nullptr;
|
|
||||||
return method ? reinterpret_cast<void (*)(void*)>(method->methodPointer) : nullptr;
|
|
||||||
}();
|
|
||||||
static auto RenderTexture_get_active = [] {
|
|
||||||
const auto renderTextureClass = Il2cppUtils::GetClass("UnityEngine.CoreModule.dll", "UnityEngine", "RenderTexture");
|
|
||||||
const auto method = renderTextureClass ? Il2cppUtils::il2cpp_class_get_method_from_name(renderTextureClass->address, "get_active", 0) : nullptr;
|
|
||||||
return method ? reinterpret_cast<void* (*)()>(method->methodPointer) : nullptr;
|
|
||||||
}();
|
|
||||||
static auto RenderTexture_set_active = [] {
|
|
||||||
const auto renderTextureClass = Il2cppUtils::GetClass("UnityEngine.CoreModule.dll", "UnityEngine", "RenderTexture");
|
|
||||||
const auto method = renderTextureClass ? Il2cppUtils::il2cpp_class_get_method_from_name(renderTextureClass->address, "set_active", 1) : nullptr;
|
|
||||||
return method ? reinterpret_cast<void (*)(void*)>(method->methodPointer) : nullptr;
|
|
||||||
}();
|
|
||||||
static auto Graphics_Blit = [] {
|
|
||||||
const auto graphicsClass = Il2cppUtils::GetClass("UnityEngine.CoreModule.dll", "UnityEngine", "Graphics");
|
|
||||||
const auto method = graphicsClass ? Il2cppUtils::il2cpp_class_get_method_from_name(graphicsClass->address, "Blit", 2) : nullptr;
|
|
||||||
return method ? reinterpret_cast<void (*)(void*, void*)>(method->methodPointer) : nullptr;
|
|
||||||
}();
|
|
||||||
static auto ImageConversion_EncodeToPNG = [] {
|
|
||||||
using EncodeToPNGFn = void* (*)(void*);
|
|
||||||
if (const auto icall = Il2cppUtils::il2cpp_resolve_icall("UnityEngine.ImageConversion::EncodeToPNG(UnityEngine.Texture2D)")) {
|
|
||||||
return reinterpret_cast<EncodeToPNGFn>(icall);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& assemblyName : {"UnityEngine.ImageConversionModule.dll", "UnityEngine.CoreModule.dll"}) {
|
|
||||||
const auto assembly = UnityResolve::Get(assemblyName);
|
|
||||||
const auto imageConversionClass = assembly ? assembly->Get("ImageConversion", "UnityEngine") : nullptr;
|
|
||||||
const auto method = imageConversionClass
|
|
||||||
? Il2cppUtils::il2cpp_class_get_method_from_name(imageConversionClass->address, "EncodeToPNG", 1)
|
|
||||||
: nullptr;
|
|
||||||
if (method) {
|
|
||||||
return reinterpret_cast<EncodeToPNGFn>(method->methodPointer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return static_cast<EncodeToPNGFn>(nullptr);
|
|
||||||
}();
|
|
||||||
static auto File_WriteAllBytes = [] {
|
|
||||||
const auto fileClass = Il2cppUtils::GetClass("mscorlib.dll", "System.IO", "File");
|
|
||||||
const auto method = fileClass ? Il2cppUtils::il2cpp_class_get_method_from_name(fileClass->address, "WriteAllBytes", 2) : nullptr;
|
|
||||||
return method ? reinterpret_cast<void (*)(Il2cppString*, void*)>(method->methodPointer) : nullptr;
|
|
||||||
}();
|
|
||||||
|
|
||||||
if (!Texture2D_get_width || !Texture2D_get_height || !Texture2D_ctor || !Texture2D_ReadPixels
|
|
||||||
|| !Texture2D_Apply || !RenderTexture_GetTemporary || !RenderTexture_ReleaseTemporary
|
|
||||||
|| !RenderTexture_get_active || !RenderTexture_set_active || !Graphics_Blit
|
|
||||||
|| !ImageConversion_EncodeToPNG || !File_WriteAllBytes) {
|
|
||||||
Log::Error("DumpTexture2D failed: Unity texture dump API not found.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto width = Texture2D_get_width(texture2D);
|
|
||||||
const auto height = Texture2D_get_height(texture2D);
|
|
||||||
if (width <= 0 || height <= 0) return false;
|
|
||||||
|
|
||||||
void* renderTexture = nullptr;
|
|
||||||
void* readableTexture = nullptr;
|
|
||||||
void* previousActive = nullptr;
|
|
||||||
const auto cleanup = [&] {
|
|
||||||
if (RenderTexture_get_active && RenderTexture_set_active
|
|
||||||
&& (previousActive || RenderTexture_get_active() == renderTexture)) {
|
|
||||||
RenderTexture_set_active(previousActive);
|
|
||||||
}
|
|
||||||
if (renderTexture && RenderTexture_ReleaseTemporary) {
|
|
||||||
RenderTexture_ReleaseTemporary(renderTexture);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
std::filesystem::create_directories(dumpDir);
|
|
||||||
|
|
||||||
renderTexture = RenderTexture_GetTemporary(width, height, 0);
|
|
||||||
if (!renderTexture) {
|
|
||||||
cleanup();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Graphics_Blit(texture2D, renderTexture);
|
|
||||||
previousActive = RenderTexture_get_active();
|
|
||||||
RenderTexture_set_active(renderTexture);
|
|
||||||
|
|
||||||
readableTexture = UnityResolve::Invoke<void*>("il2cpp_object_new", GetTexture2DClass());
|
|
||||||
if (!readableTexture) {
|
|
||||||
cleanup();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Texture2D_ctor(readableTexture, width, height);
|
|
||||||
Texture2D_ReadPixels(readableTexture, UnityResolve::UnityType::Rect(0, 0, static_cast<float>(width), static_cast<float>(height)), 0, 0);
|
|
||||||
Texture2D_Apply(readableTexture);
|
|
||||||
|
|
||||||
const auto pngBytes = ImageConversion_EncodeToPNG(readableTexture);
|
|
||||||
if (!pngBytes) {
|
|
||||||
cleanup();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
File_WriteAllBytes(Il2cppString::New(dumpPath.string()), pngBytes);
|
|
||||||
Log::InfoFmt("Texture dumped: %s", dumpPath.string().c_str());
|
|
||||||
cleanup();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (const std::exception& ex) {
|
|
||||||
cleanup();
|
|
||||||
Log::ErrorFmt("DumpTexture2D failed: %s", ex.what());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
cleanup();
|
|
||||||
Log::Error("DumpTexture2D failed: unknown error.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DumpTextureOrSpriteAsset(void* result) {
|
|
||||||
if (!result) return;
|
|
||||||
|
|
||||||
if (IsTexture2D(result)) {
|
|
||||||
DumpTexture2D(result);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (IsSprite(result) && Sprite_get_texture_Orig) {
|
|
||||||
if (const auto texture = Sprite_get_texture_Orig(result)) {
|
|
||||||
DumpTexture2D(texture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void* LoadLocalTexture2D(const std::filesystem::path& path) {
|
|
||||||
if (!std::filesystem::is_regular_file(path)) return nullptr;
|
|
||||||
|
|
||||||
const auto cacheKey = NormalizeLocalAssetKey(path);
|
|
||||||
if (const auto iter = LoadedLocalTextureHandles.find(cacheKey); iter != LoadedLocalTextureHandles.end()) {
|
|
||||||
const auto cachedTexture = UnityResolve::Invoke<void*>("il2cpp_gchandle_get_target", iter->second);
|
|
||||||
if (cachedTexture && IsNativeObjectAlive(cachedTexture)) {
|
|
||||||
return cachedTexture;
|
|
||||||
}
|
|
||||||
|
|
||||||
UnityResolve::Invoke<void>("il2cpp_gchandle_free", iter->second);
|
|
||||||
LoadedLocalTextureHandles.erase(iter);
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto textureClass = GetTexture2DClass();
|
|
||||||
if (!textureClass) return nullptr;
|
|
||||||
|
|
||||||
static auto Texture2D_ctor = [] {
|
|
||||||
const auto textureClass = GetTexture2DClass();
|
|
||||||
const auto ctor = textureClass ? Il2cppUtils::il2cpp_class_get_method_from_name(textureClass, ".ctor", 2) : nullptr;
|
|
||||||
return ctor ? reinterpret_cast<void (*)(void*, int, int)>(ctor->methodPointer) : nullptr;
|
|
||||||
}();
|
|
||||||
static auto ImageConversion_LoadImage = [] {
|
|
||||||
using LoadImageFn = bool (*)(void*, void*, bool);
|
|
||||||
if (const auto icall = Il2cppUtils::il2cpp_resolve_icall(
|
|
||||||
"UnityEngine.ImageConversion::LoadImage(UnityEngine.Texture2D,System.Byte[],System.Boolean)")) {
|
|
||||||
return reinterpret_cast<LoadImageFn>(icall);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& assemblyName : {"UnityEngine.ImageConversionModule.dll", "UnityEngine.CoreModule.dll"}) {
|
|
||||||
const auto assembly = UnityResolve::Get(assemblyName);
|
|
||||||
const auto imageConversionClass = assembly ? assembly->Get("ImageConversion", "UnityEngine") : nullptr;
|
|
||||||
const auto method = imageConversionClass
|
|
||||||
? Il2cppUtils::il2cpp_class_get_method_from_name(imageConversionClass->address, "LoadImage", 3)
|
|
||||||
: nullptr;
|
|
||||||
if (method) {
|
|
||||||
return reinterpret_cast<LoadImageFn>(method->methodPointer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return static_cast<LoadImageFn>(nullptr);
|
|
||||||
}();
|
|
||||||
static auto File_ReadAllBytes = [] {
|
|
||||||
const auto fileClass = Il2cppUtils::GetClass("mscorlib.dll", "System.IO", "File");
|
|
||||||
const auto method = fileClass ? Il2cppUtils::il2cpp_class_get_method_from_name(fileClass->address, "ReadAllBytes", 1) : nullptr;
|
|
||||||
return method ? reinterpret_cast<void* (*)(Il2cppString*)>(method->methodPointer) : nullptr;
|
|
||||||
}();
|
|
||||||
|
|
||||||
if (!Texture2D_ctor || !ImageConversion_LoadImage || !File_ReadAllBytes) {
|
|
||||||
Log::Error("LoadLocalTexture2D failed: Unity Texture2D/ImageConversion/File API not found.");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto fileBytes = File_ReadAllBytes(Il2cppString::New(path.string()));
|
|
||||||
if (!fileBytes) return nullptr;
|
|
||||||
|
|
||||||
const auto texture = UnityResolve::Invoke<void*>("il2cpp_object_new", textureClass);
|
|
||||||
Texture2D_ctor(texture, 2, 2);
|
|
||||||
if (!ImageConversion_LoadImage(texture, fileBytes, false)) {
|
|
||||||
Log::ErrorFmt("LoadLocalTexture2D failed: %s", path.string().c_str());
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
SetDontUnloadUnusedAsset(texture);
|
|
||||||
LoadedLocalTextureHandles.emplace(cacheKey, UnityResolve::Invoke<uint32_t>("il2cpp_gchandle_new", texture, false));
|
|
||||||
Log::InfoFmt("Texture replaced from local file: %s", path.string().c_str());
|
|
||||||
return texture;
|
|
||||||
}
|
|
||||||
|
|
||||||
void* LoadLocalTexture2DFromCandidates(const std::vector<std::filesystem::path>& candidates) {
|
|
||||||
for (const auto& candidate : candidates) {
|
|
||||||
if (auto texture = LoadLocalTexture2D(candidate)) {
|
|
||||||
return texture;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ApplyLocalImageToTexture2D(void* texture2D, const std::filesystem::path& path) {
|
|
||||||
if (!IsTexture2D(texture2D) || !std::filesystem::is_regular_file(path)) return false;
|
|
||||||
|
|
||||||
auto cacheKey = NormalizeLocalAssetKey(path)
|
|
||||||
+ "|" + std::to_string(reinterpret_cast<std::uintptr_t>(texture2D));
|
|
||||||
if (AppliedLocalTextureKeys.contains(cacheKey)) return true;
|
|
||||||
|
|
||||||
static auto ImageConversion_LoadImage = [] {
|
|
||||||
using LoadImageFn = bool (*)(void*, void*, bool);
|
|
||||||
if (const auto icall = Il2cppUtils::il2cpp_resolve_icall(
|
|
||||||
"UnityEngine.ImageConversion::LoadImage(UnityEngine.Texture2D,System.Byte[],System.Boolean)")) {
|
|
||||||
return reinterpret_cast<LoadImageFn>(icall);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& assemblyName : {"UnityEngine.ImageConversionModule.dll", "UnityEngine.CoreModule.dll"}) {
|
|
||||||
const auto assembly = UnityResolve::Get(assemblyName);
|
|
||||||
const auto imageConversionClass = assembly ? assembly->Get("ImageConversion", "UnityEngine") : nullptr;
|
|
||||||
const auto method = imageConversionClass
|
|
||||||
? Il2cppUtils::il2cpp_class_get_method_from_name(imageConversionClass->address, "LoadImage", 3)
|
|
||||||
: nullptr;
|
|
||||||
if (method) {
|
|
||||||
return reinterpret_cast<LoadImageFn>(method->methodPointer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return static_cast<LoadImageFn>(nullptr);
|
|
||||||
}();
|
|
||||||
static auto File_ReadAllBytes = [] {
|
|
||||||
const auto fileClass = Il2cppUtils::GetClass("mscorlib.dll", "System.IO", "File");
|
|
||||||
const auto method = fileClass ? Il2cppUtils::il2cpp_class_get_method_from_name(fileClass->address, "ReadAllBytes", 1) : nullptr;
|
|
||||||
return method ? reinterpret_cast<void* (*)(Il2cppString*)>(method->methodPointer) : nullptr;
|
|
||||||
}();
|
|
||||||
|
|
||||||
if (!ImageConversion_LoadImage || !File_ReadAllBytes) {
|
|
||||||
Log::Error("ApplyLocalImageToTexture2D failed: Unity ImageConversion/File API not found.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto fileBytes = File_ReadAllBytes(Il2cppString::New(path.string()));
|
|
||||||
if (!fileBytes) return false;
|
|
||||||
|
|
||||||
if (!ImageConversion_LoadImage(texture2D, fileBytes, false)) {
|
|
||||||
Log::ErrorFmt("ApplyLocalImageToTexture2D failed: %s", path.string().c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SetDontUnloadUnusedAsset(texture2D);
|
|
||||||
AppliedLocalTextureKeys.emplace(std::move(cacheKey));
|
|
||||||
Log::InfoFmt("Texture replaced in-place from local file: %s", path.string().c_str());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ApplyLocalImageToTexture2DFromCandidates(void* texture2D, const std::vector<std::filesystem::path>& candidates) {
|
|
||||||
for (const auto& candidate : candidates) {
|
|
||||||
if (ApplyLocalImageToTexture2D(texture2D, candidate)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ReplaceSpriteTextureInPlace(void* sprite, const std::vector<std::filesystem::path>& candidates) {
|
|
||||||
if (!sprite || !Sprite_get_texture_Orig) return false;
|
|
||||||
|
|
||||||
const auto texture = Sprite_get_texture_Orig(sprite);
|
|
||||||
if (!IsTexture2D(texture)) return false;
|
|
||||||
|
|
||||||
return ApplyLocalImageToTexture2DFromCandidates(texture, candidates);
|
|
||||||
}
|
|
||||||
|
|
||||||
void* ReplaceTextureOrSpriteAsset(void* result, const std::string& assetName) {
|
|
||||||
if (!Config::replaceTexture && !Config::dumpRuntimeTexture) return result;
|
|
||||||
|
|
||||||
if (Config::dumpRuntimeTexture) {
|
|
||||||
DumpTextureOrSpriteAsset(result);
|
|
||||||
}
|
|
||||||
if (!Config::replaceTexture) return result;
|
|
||||||
|
|
||||||
if (IsSprite(result)) {
|
|
||||||
if (ReplaceSpriteTextureInPlace(result, GetSpriteAssetTextureCandidates(result, assetName))) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result && !IsTexture2D(result)) return result;
|
|
||||||
|
|
||||||
if (auto localTexture = LoadLocalTexture2DFromCandidates(GetNamedTextureCandidates(std::filesystem::path(assetName)))) {
|
|
||||||
return localTexture;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void* ReplaceTextureOrSpriteByObjectName(void* result) {
|
|
||||||
if ((!Config::replaceTexture && !Config::dumpRuntimeTexture) || !result) return result;
|
|
||||||
|
|
||||||
const auto objectName = GetObjectName(result);
|
|
||||||
if (!objectName) return result;
|
|
||||||
|
|
||||||
const auto assetPath = std::filesystem::path(objectName->ToString());
|
|
||||||
if (Config::dumpRuntimeTexture) {
|
|
||||||
DumpTextureOrSpriteAsset(result);
|
|
||||||
}
|
|
||||||
if (!Config::replaceTexture) return result;
|
|
||||||
|
|
||||||
if (IsSprite(result)) {
|
|
||||||
std::vector<std::filesystem::path> candidates;
|
|
||||||
AppendTextureCandidates(candidates, GetSpriteTextureCandidates(objectName->ToString()));
|
|
||||||
if (ReplaceSpriteTextureInPlace(result, candidates)) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsTexture2D(result)) {
|
|
||||||
if (auto localTexture = LoadLocalTexture2DFromCandidates(GetNamedTextureCandidates(assetPath))) {
|
|
||||||
return localTexture;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReplaceAllAssetTextures(void* allAssets) {
|
|
||||||
if ((!Config::replaceTexture && !Config::dumpRuntimeTexture) || !allAssets) return;
|
|
||||||
|
|
||||||
auto assets = reinterpret_cast<UnityResolve::UnityType::Array<void*>*>(allAssets);
|
|
||||||
for (std::uintptr_t i = 0; i < assets->max_length; ++i) {
|
|
||||||
auto asset = assets->At(static_cast<unsigned int>(i));
|
|
||||||
auto replacedAsset = ReplaceTextureOrSpriteByObjectName(asset);
|
|
||||||
if (replacedAsset != asset) {
|
|
||||||
assets->At(static_cast<unsigned int>(i)) = replacedAsset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void* ReplaceSpriteAssetByTextureName(void* sprite) {
|
|
||||||
if (!Config::replaceTexture || !sprite) return sprite;
|
|
||||||
|
|
||||||
if (!IsSprite(sprite)) {
|
|
||||||
return sprite;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ReplaceSpriteTextureInPlace(sprite, GetSpriteAssetTextureCandidates(sprite, ""))) {
|
|
||||||
return sprite;
|
|
||||||
}
|
|
||||||
return sprite;
|
|
||||||
}
|
|
||||||
|
|
||||||
void* ReplaceSpriteTexture(void* texture2D) {
|
|
||||||
if ((!Config::replaceTexture && !Config::dumpRuntimeTexture) || !IsTexture2D(texture2D)) return texture2D;
|
|
||||||
|
|
||||||
const auto objectName = GetObjectName(texture2D);
|
|
||||||
if (!objectName) return texture2D;
|
|
||||||
|
|
||||||
if (Config::dumpRuntimeTexture) {
|
|
||||||
DumpTexture2D(texture2D);
|
|
||||||
}
|
|
||||||
if (!Config::replaceTexture) return texture2D;
|
|
||||||
|
|
||||||
if (ApplyLocalImageToTexture2DFromCandidates(texture2D, GetSpriteTextureCandidates(objectName->ToString()))) {
|
|
||||||
return texture2D;
|
|
||||||
}
|
|
||||||
return texture2D;
|
|
||||||
}
|
|
||||||
|
|
||||||
void* ResolveSpriteGetTextureHookAddress() {
|
|
||||||
if (const auto addr = Il2cppUtils::il2cpp_resolve_icall("UnityEngine.Sprite::get_texture(UnityEngine.Sprite)")) {
|
|
||||||
return addr;
|
|
||||||
}
|
|
||||||
if (const auto addr = Il2cppUtils::il2cpp_resolve_icall("UnityEngine.Sprite::get_texture()")) {
|
|
||||||
return addr;
|
|
||||||
}
|
|
||||||
return Il2cppUtils::GetMethodPointer("UnityEngine.CoreModule.dll", "UnityEngine", "Sprite", "get_texture");
|
|
||||||
}
|
|
||||||
|
|
||||||
void* ResolveAssetBundleLoadAssetHookAddress() {
|
|
||||||
if (const auto addr = Il2cppUtils::il2cpp_resolve_icall(
|
|
||||||
"UnityEngine.AssetBundle::LoadAsset_Internal(System.String,System.Type)")) {
|
|
||||||
return addr;
|
|
||||||
}
|
|
||||||
return Il2cppUtils::GetMethodPointer("UnityEngine.AssetBundleModule.dll", "UnityEngine", "AssetBundle",
|
|
||||||
"LoadAsset_Internal", {"System.String", "System.Type"});
|
|
||||||
}
|
|
||||||
|
|
||||||
void* ResolveAssetBundleLoadAssetAsyncHookAddress() {
|
|
||||||
if (const auto addr = Il2cppUtils::il2cpp_resolve_icall(
|
|
||||||
"UnityEngine.AssetBundle::LoadAssetAsync_Internal(System.String,System.Type)")) {
|
|
||||||
return addr;
|
|
||||||
}
|
|
||||||
return Il2cppUtils::GetMethodPointer("UnityEngine.AssetBundleModule.dll", "UnityEngine", "AssetBundle",
|
|
||||||
"LoadAssetAsync_Internal", {"System.String", "System.Type"});
|
|
||||||
}
|
|
||||||
|
|
||||||
void* ResolveAssetBundleRequestResultHookAddress() {
|
|
||||||
if (const auto addr = Il2cppUtils::il2cpp_resolve_icall("UnityEngine.AssetBundleRequest::GetResult()")) {
|
|
||||||
return addr;
|
|
||||||
}
|
|
||||||
return Il2cppUtils::GetMethodPointer("UnityEngine.AssetBundleModule.dll", "UnityEngine", "AssetBundleRequest", "GetResult");
|
|
||||||
}
|
|
||||||
|
|
||||||
void* ResolveAssetBundleRequestAssetHookAddress() {
|
|
||||||
if (const auto addr = Il2cppUtils::il2cpp_resolve_icall("UnityEngine.AssetBundleRequest::get_asset()")) {
|
|
||||||
return addr;
|
|
||||||
}
|
|
||||||
return Il2cppUtils::GetMethodPointer("UnityEngine.AssetBundleModule.dll", "UnityEngine", "AssetBundleRequest", "get_asset");
|
|
||||||
}
|
|
||||||
|
|
||||||
void* ResolveAssetBundleRequestAllAssetsHookAddress() {
|
|
||||||
if (const auto addr = Il2cppUtils::il2cpp_resolve_icall("UnityEngine.AssetBundleRequest::get_allAssets()")) {
|
|
||||||
return addr;
|
|
||||||
}
|
|
||||||
return Il2cppUtils::GetMethodPointer("UnityEngine.AssetBundleModule.dll", "UnityEngine", "AssetBundleRequest", "get_allAssets");
|
|
||||||
}
|
|
||||||
|
|
||||||
void* ResolveResourcesLoadHookAddress() {
|
|
||||||
if (const auto addr = Il2cppUtils::il2cpp_resolve_icall(
|
|
||||||
"UnityEngine.ResourcesAPIInternal::Load(System.String,System.Type)")) {
|
|
||||||
return addr;
|
|
||||||
}
|
|
||||||
return Il2cppUtils::GetMethodPointer("UnityEngine.CoreModule.dll", "UnityEngine", "ResourcesAPIInternal",
|
|
||||||
"Load", {"System.String", "System.Type"});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
#ifndef GAKUMAS_LOCALIFY_HOOK_TEXTURE_H
|
|
||||||
#define GAKUMAS_LOCALIFY_HOOK_TEXTURE_H
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace GakumasLocal::HookMain
|
|
||||||
{
|
|
||||||
void* ReplaceTextureOrSpriteAsset(void* result, const std::string& assetName);
|
|
||||||
void* ReplaceTextureOrSpriteByObjectName(void* result);
|
|
||||||
void ReplaceAllAssetTextures(void* allAssets);
|
|
||||||
void* ReplaceSpriteAssetByTextureName(void* sprite);
|
|
||||||
void* ReplaceSpriteTexture(void* texture2D);
|
|
||||||
|
|
||||||
void* ResolveSpriteGetTextureHookAddress();
|
|
||||||
void* ResolveAssetBundleLoadAssetHookAddress();
|
|
||||||
void* ResolveAssetBundleLoadAssetAsyncHookAddress();
|
|
||||||
void* ResolveAssetBundleRequestResultHookAddress();
|
|
||||||
void* ResolveAssetBundleRequestAssetHookAddress();
|
|
||||||
void* ResolveAssetBundleRequestAllAssetsHookAddress();
|
|
||||||
void* ResolveResourcesLoadHookAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // GAKUMAS_LOCALIFY_HOOK_TEXTURE_H
|
|
||||||
|
|
@ -308,22 +308,6 @@ namespace Il2cppUtils {
|
||||||
return 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 {
|
namespace Tools {
|
||||||
|
|
||||||
template <typename T = void*>
|
template <typename T = void*>
|
||||||
|
|
@ -337,23 +321,17 @@ namespace Il2cppUtils {
|
||||||
lst_get_Item_method = il2cpp_class_get_method_from_name(list_klass, "get_Item", 1);
|
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_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_Add_method = il2cpp_class_get_method_from_name(list_klass, "Add", 1);
|
||||||
lst_Contains_method = il2cpp_class_get_method_from_name(list_klass, "Contains", 1);
|
|
||||||
|
|
||||||
lst_get_Count = reinterpret_cast<lst_get_Count_t>(lst_get_Count_method->methodPointer);
|
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_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_set_Item = reinterpret_cast<lst_set_Item_t>(lst_set_Item_method->methodPointer);
|
||||||
lst_Add = reinterpret_cast<lst_Add_t>(lst_Add_method->methodPointer);
|
lst_Add = reinterpret_cast<lst_Add_t>(lst_Add_method->methodPointer);
|
||||||
lst_Contains = reinterpret_cast<lst_Contains_t>(lst_Contains_method->methodPointer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Add(T value) {
|
void Add(T value) {
|
||||||
lst_Add(lst, value, lst_Add_method);
|
lst_Add(lst, value, lst_Add_method);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Contains(T value) {
|
|
||||||
return lst_Contains(lst, value, lst_Contains_method);
|
|
||||||
}
|
|
||||||
|
|
||||||
T get_Item(int index) {
|
T get_Item(int index) {
|
||||||
return lst_get_Item(lst, index, lst_get_Item_method);
|
return lst_get_Item(lst, index, lst_get_Item_method);
|
||||||
}
|
}
|
||||||
|
|
@ -407,86 +385,16 @@ namespace Il2cppUtils {
|
||||||
typedef void(*lst_Add_t)(void*, T, void* mtd);
|
typedef void(*lst_Add_t)(void*, T, void* mtd);
|
||||||
typedef void(*lst_set_Item_t)(void*, int, T, void* mtd);
|
typedef void(*lst_set_Item_t)(void*, int, T, void* mtd);
|
||||||
typedef int(*lst_get_Count_t)(void*, void* mtd);
|
typedef int(*lst_get_Count_t)(void*, void* mtd);
|
||||||
typedef bool(*lst_Contains_t)(void*, T, void* mtd);
|
|
||||||
|
|
||||||
MethodInfo* lst_get_Item_method;
|
MethodInfo* lst_get_Item_method;
|
||||||
MethodInfo* lst_Add_method;
|
MethodInfo* lst_Add_method;
|
||||||
MethodInfo* lst_get_Count_method;
|
MethodInfo* lst_get_Count_method;
|
||||||
MethodInfo* lst_set_Item_method;
|
MethodInfo* lst_set_Item_method;
|
||||||
MethodInfo* lst_Contains_method;
|
|
||||||
|
|
||||||
lst_get_Item_t lst_get_Item;
|
lst_get_Item_t lst_get_Item;
|
||||||
lst_set_Item_t lst_set_Item;
|
lst_set_Item_t lst_set_Item;
|
||||||
lst_Add_t lst_Add;
|
lst_Add_t lst_Add;
|
||||||
lst_get_Count_t lst_get_Count;
|
lst_get_Count_t lst_get_Count;
|
||||||
lst_Contains_t lst_Contains;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
template <typename KT = void*, typename VT = void*>
|
|
||||||
class CSDictEditor {
|
|
||||||
public:
|
|
||||||
// @param dict: Dictionary instance.
|
|
||||||
// @param dictTypeStr: Reflection type. eg: "System.Collections.Generic.Dictionary`2[System.Int32, System.Int32]"
|
|
||||||
CSDictEditor(void* dict, const char* dictTypeStr) {
|
|
||||||
dic_klass = Il2cppUtils::get_system_class_from_reflection_type_str(dictTypeStr);
|
|
||||||
initDict(dict);
|
|
||||||
}
|
|
||||||
|
|
||||||
CSDictEditor(void* dict) {
|
|
||||||
dic_klass = get_class_from_instance(dict);
|
|
||||||
initDict(dict);
|
|
||||||
}
|
|
||||||
|
|
||||||
CSDictEditor(void* dict, void* dicClass) {
|
|
||||||
dic_klass = dicClass;
|
|
||||||
initDict(dict);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Add(KT key, VT value) {
|
|
||||||
dic_Add(dict, key, value, Add_method);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ContainsKey(KT key) {
|
|
||||||
return dic_containsKey(dict, key, ContainsKey_method);
|
|
||||||
}
|
|
||||||
|
|
||||||
VT get_Item(KT key) {
|
|
||||||
return dic_get_Item(dict, key, get_Item_method);
|
|
||||||
}
|
|
||||||
|
|
||||||
VT operator[] (KT key) {
|
|
||||||
return get_Item(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
void* dict;
|
|
||||||
void* dic_klass;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void initDict(void* dict) {
|
|
||||||
// dic_klass = dicClass;
|
|
||||||
this->dict = dict;
|
|
||||||
|
|
||||||
get_Item_method = il2cpp_class_get_method_from_name(dic_klass, "get_Item", 1);
|
|
||||||
Add_method = il2cpp_class_get_method_from_name(dic_klass, "Add", 2);
|
|
||||||
ContainsKey_method = il2cpp_class_get_method_from_name(dic_klass, "ContainsKey", 1);
|
|
||||||
|
|
||||||
dic_get_Item = (dic_get_Item_t)get_Item_method->methodPointer;
|
|
||||||
dic_Add = (dic_Add_t)Add_method->methodPointer;
|
|
||||||
dic_containsKey = (dic_containsKey_t)ContainsKey_method->methodPointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef VT(*dic_get_Item_t)(void*, KT, void* mtd);
|
|
||||||
typedef VT(*dic_Add_t)(void*, KT, VT, void* mtd);
|
|
||||||
typedef VT(*dic_containsKey_t)(void*, KT, void* mtd);
|
|
||||||
|
|
||||||
CSDictEditor();
|
|
||||||
MethodInfo* get_Item_method;
|
|
||||||
MethodInfo* Add_method;
|
|
||||||
MethodInfo* ContainsKey_method;
|
|
||||||
dic_get_Item_t dic_get_Item;
|
|
||||||
dic_Add_t dic_Add;
|
|
||||||
dic_containsKey_t dic_containsKey;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,6 @@
|
||||||
#include "BaseDefine.h"
|
#include "BaseDefine.h"
|
||||||
#include "string_parser/StringParser.hpp"
|
#include "string_parser/StringParser.hpp"
|
||||||
|
|
||||||
// #include "cpprest/details/http_helpers.h"
|
|
||||||
|
|
||||||
|
|
||||||
namespace GakumasLocal::Local {
|
namespace GakumasLocal::Local {
|
||||||
std::unordered_map<std::string, std::string> i18nData{};
|
std::unordered_map<std::string, std::string> i18nData{};
|
||||||
|
|
@ -39,12 +37,6 @@ namespace GakumasLocal::Local {
|
||||||
return Plugin::GetInstance().GetHookInstaller()->localizationFilesDir;
|
return Plugin::GetInstance().GetHookInstaller()->localizationFilesDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isAllSpace(const std::string& str) {
|
|
||||||
return std::all_of(str.begin(), str.end(), [](unsigned char c) {
|
|
||||||
return std::isspace(c);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string trim(const std::string& str) {
|
std::string trim(const std::string& str) {
|
||||||
auto is_not_space = [](char ch) { return !std::isspace(ch); };
|
auto is_not_space = [](char ch) { return !std::isspace(ch); };
|
||||||
auto start = std::ranges::find_if(str, is_not_space);
|
auto start = std::ranges::find_if(str, is_not_space);
|
||||||
|
|
@ -98,7 +90,7 @@ namespace GakumasLocal::Local {
|
||||||
}
|
}
|
||||||
std::ifstream file(filePath);
|
std::ifstream file(filePath);
|
||||||
if (!file.is_open()) {
|
if (!file.is_open()) {
|
||||||
Log::ErrorFmt("Load %s failed.\n", filePath.string().c_str());
|
Log::ErrorFmt("Load %s failed.\n", filePath.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::string fileContent((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
std::string fileContent((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||||
|
|
@ -120,7 +112,7 @@ namespace GakumasLocal::Local {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (std::exception& e) {
|
catch (std::exception& e) {
|
||||||
Log::ErrorFmt("Load %s failed: %s\n", filePath.string().c_str(), e.what());
|
Log::ErrorFmt("Load %s failed: %s\n", filePath.c_str(), e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -257,7 +249,7 @@ namespace GakumasLocal::Local {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GetSplitTagsTranslation(const std::string& origText, std::string* newText, std::vector<std::string>& unTransResultRet) {
|
bool GetSplitTagsTranslation(const std::string& origText, std::string* newText, std::vector<std::string>& unTransResultRet) {
|
||||||
if (!origText.contains('<')) return false;
|
if (!origText.contains(L'<')) return false;
|
||||||
const auto splitResult = SplitByTags(origText);
|
const auto splitResult = SplitByTags(origText);
|
||||||
if (splitResult.empty()) return false;
|
if (splitResult.empty()) return false;
|
||||||
|
|
||||||
|
|
@ -297,18 +289,10 @@ namespace GakumasLocal::Local {
|
||||||
|
|
||||||
std::u16string currentWaitingReplaceText;
|
std::u16string currentWaitingReplaceText;
|
||||||
|
|
||||||
#ifdef GKMS_WINDOWS
|
|
||||||
#define checkCurrentWaitingReplaceTextAndClear() \
|
|
||||||
if (!currentWaitingReplaceText.empty()) { \
|
|
||||||
auto trimmed = trim(Misc::ToUTF8(currentWaitingReplaceText)); \
|
|
||||||
waitingReplaceTexts.push_back(trimmed); \
|
|
||||||
currentWaitingReplaceText.clear(); }
|
|
||||||
#else
|
|
||||||
#define checkCurrentWaitingReplaceTextAndClear() \
|
#define checkCurrentWaitingReplaceTextAndClear() \
|
||||||
if (!currentWaitingReplaceText.empty()) { \
|
if (!currentWaitingReplaceText.empty()) { \
|
||||||
waitingReplaceTexts.push_back(Misc::ToUTF8(currentWaitingReplaceText)); \
|
waitingReplaceTexts.push_back(Misc::ToUTF8(currentWaitingReplaceText)); \
|
||||||
currentWaitingReplaceText.clear(); }
|
currentWaitingReplaceText.clear(); }
|
||||||
#endif
|
|
||||||
|
|
||||||
for (char16_t currChar : origText) {
|
for (char16_t currChar : origText) {
|
||||||
if (currChar == u'<') {
|
if (currChar == u'<') {
|
||||||
|
|
@ -349,7 +333,6 @@ namespace GakumasLocal::Local {
|
||||||
bool hasNotTrans = false;
|
bool hasNotTrans = false;
|
||||||
if (!waitingReplaceTexts.empty()) {
|
if (!waitingReplaceTexts.empty()) {
|
||||||
for (const auto& i : waitingReplaceTexts) {
|
for (const auto& i : waitingReplaceTexts) {
|
||||||
if (isAllSpace(i)) continue;
|
|
||||||
std::string searchResult = findInMapIgnoreSpace(i, genericSplitText);
|
std::string searchResult = findInMapIgnoreSpace(i, genericSplitText);
|
||||||
if (!searchResult.empty()) {
|
if (!searchResult.empty()) {
|
||||||
ReplaceNumberComma(&searchResult);
|
ReplaceNumberComma(&searchResult);
|
||||||
|
|
@ -464,7 +447,7 @@ namespace GakumasLocal::Local {
|
||||||
const auto targetFilePath = basePath / "local-files" / "resource" / name;
|
const auto targetFilePath = basePath / "local-files" / "resource" / name;
|
||||||
// Log::DebugFmt("GetResourceText: %s", targetFilePath.c_str());
|
// Log::DebugFmt("GetResourceText: %s", targetFilePath.c_str());
|
||||||
if (exists(targetFilePath)) {
|
if (exists(targetFilePath)) {
|
||||||
auto readStr = readFileToString(targetFilePath.string());
|
auto readStr = readFileToString(targetFilePath);
|
||||||
*ret = readStr;
|
*ret = readStr;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,14 @@
|
||||||
#include "Log.h"
|
#include "Log.h"
|
||||||
#include "Misc.hpp"
|
#include <android/log.h>
|
||||||
|
#include <Misc.hpp>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <cstdarg>
|
|
||||||
|
|
||||||
#ifndef GKMS_WINDOWS
|
|
||||||
#include <android/log.h>
|
|
||||||
|
|
||||||
extern JavaVM* g_javaVM;
|
extern JavaVM* g_javaVM;
|
||||||
extern jclass g_gakumasHookMainClass;
|
extern jclass g_gakumasHookMainClass;
|
||||||
extern jmethodID showToastMethodId;
|
extern jmethodID showToastMethodId;
|
||||||
#endif // GKMS_WINDOWS
|
|
||||||
|
|
||||||
|
|
||||||
#define GetParamStringResult(name)\
|
#define GetParamStringResult(name)\
|
||||||
va_list args;\
|
va_list args;\
|
||||||
|
|
@ -80,7 +75,6 @@ namespace GakumasLocal::Log {
|
||||||
__android_log_write(prio, "GakumasLog", result.c_str());
|
__android_log_write(prio, "GakumasLog", result.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
void ShowToastJNI(const char* text) {
|
void ShowToastJNI(const char* text) {
|
||||||
DebugFmt("Toast: %s", text);
|
DebugFmt("Toast: %s", text);
|
||||||
|
|
||||||
|
|
@ -105,19 +99,15 @@ namespace GakumasLocal::Log {
|
||||||
|
|
||||||
g_javaVM->DetachCurrentThread();
|
g_javaVM->DetachCurrentThread();
|
||||||
}).detach();
|
}).detach();
|
||||||
}*/
|
}
|
||||||
|
|
||||||
|
|
||||||
void ShowToast(const std::string& text) {
|
void ShowToast(const std::string& text) {
|
||||||
#ifndef GKMS_WINDOWS
|
|
||||||
showingToasts.push(text);
|
showingToasts.push(text);
|
||||||
#else
|
|
||||||
InfoFmt("Toast: %s", text.c_str());
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShowToast(const char* text) {
|
void ShowToast(const char* text) {
|
||||||
// DebugFmt("Toast: %s", text);
|
DebugFmt("Toast: %s", text);
|
||||||
return ShowToast(std::string(text));
|
return ShowToast(std::string(text));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,7 +125,6 @@ namespace GakumasLocal::Log {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef GKMS_WINDOWS
|
|
||||||
void ToastLoop(JNIEnv *env, jclass clazz) {
|
void ToastLoop(JNIEnv *env, jclass clazz) {
|
||||||
const auto toastString = GetQueuedToast();
|
const auto toastString = GetQueuedToast();
|
||||||
if (toastString.empty()) return;
|
if (toastString.empty()) return;
|
||||||
|
|
@ -151,6 +140,4 @@ namespace GakumasLocal::Log {
|
||||||
_showToastMethodId = env->GetStaticMethodID(clazz, "showToast", "(Ljava/lang/String;)V");
|
_showToastMethodId = env->GetStaticMethodID(clazz, "showToast", "(Ljava/lang/String;)V");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,8 @@
|
||||||
#ifndef GAKUMAS_LOCALIFY_LOG_H
|
#ifndef GAKUMAS_LOCALIFY_LOG_H
|
||||||
#define GAKUMAS_LOCALIFY_LOG_H
|
#define GAKUMAS_LOCALIFY_LOG_H
|
||||||
|
|
||||||
#include "../platformDefine.hpp"
|
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#ifndef GKMS_WINDOWS
|
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
namespace GakumasLocal::Log {
|
namespace GakumasLocal::Log {
|
||||||
std::string StringFormat(const char* fmt, ...);
|
std::string StringFormat(const char* fmt, ...);
|
||||||
|
|
@ -24,9 +18,7 @@ namespace GakumasLocal::Log {
|
||||||
void ShowToast(const char* text);
|
void ShowToast(const char* text);
|
||||||
void ShowToastFmt(const char* fmt, ...);
|
void ShowToastFmt(const char* fmt, ...);
|
||||||
|
|
||||||
#ifndef GKMS_WINDOWS
|
|
||||||
void ToastLoop(JNIEnv *env, jclass clazz);
|
void ToastLoop(JNIEnv *env, jclass clazz);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif //GAKUMAS_LOCALIFY_LOG_H
|
#endif //GAKUMAS_LOCALIFY_LOG_H
|
||||||
|
|
|
||||||
|
|
@ -542,7 +542,6 @@ namespace GakumasLocal::MasterLocal {
|
||||||
}
|
}
|
||||||
if (!isFailed) break;
|
if (!isFailed) break;
|
||||||
NextLoop:
|
NextLoop:
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFailed) return false;
|
if (isFailed) return false;
|
||||||
|
|
@ -694,7 +693,6 @@ namespace GakumasLocal::MasterLocal {
|
||||||
} break;
|
} break;
|
||||||
case JsonValueType::JVT_String: {
|
case JsonValueType::JVT_String: {
|
||||||
auto readValue = fc.ReadStringField(mainPk);
|
auto readValue = fc.ReadStringField(mainPk);
|
||||||
if (!readValue) return;
|
|
||||||
baseDataKey.append(readValue->ToString());
|
baseDataKey.append(readValue->ToString());
|
||||||
baseDataKey.push_back('|');
|
baseDataKey.push_back('|');
|
||||||
} break;
|
} break;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,786 @@
|
||||||
|
#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;
|
||||||
|
|
||||||
|
enum class JsonValueType {
|
||||||
|
JVT_String,
|
||||||
|
JVT_Int,
|
||||||
|
JVT_Object,
|
||||||
|
JVT_ArrayObject,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PKItem {
|
||||||
|
std::string topLevel;
|
||||||
|
std::string subField;
|
||||||
|
JsonValueType topLevelType;
|
||||||
|
JsonValueType subFieldType;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TableInfo {
|
||||||
|
std::vector<PKItem> pkItems;
|
||||||
|
std::unordered_map<std::string, nlohmann::json> dataMap;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::unordered_map<std::string, TableInfo> g_loadedData;
|
||||||
|
static std::unordered_map<std::string, Il2cppUtils::MethodInfo*> fieldSetCache;
|
||||||
|
static std::unordered_map<std::string, Il2cppUtils::MethodInfo*> fieldGetCache;
|
||||||
|
|
||||||
|
class FieldController {
|
||||||
|
void* self;
|
||||||
|
std::string self_klass_name;
|
||||||
|
|
||||||
|
static std::string capitalizeFirstLetter(const std::string& input) {
|
||||||
|
if (input.empty()) return input;
|
||||||
|
std::string result = input;
|
||||||
|
result[0] = static_cast<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) {
|
||||||
|
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) {
|
||||||
|
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) {
|
||||||
|
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) {
|
||||||
|
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) {
|
||||||
|
auto newString = Il2cppString::New(value);
|
||||||
|
SetField(fieldName, newString);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetStringListField(const std::string& fieldName, const std::vector<std::string>& data) {
|
||||||
|
static auto List_String_klass = Il2cppUtils::get_system_class_from_reflection_type_str(
|
||||||
|
"System.Collections.Generic.List`1[System.String]"
|
||||||
|
);
|
||||||
|
static auto List_String_ctor_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(
|
||||||
|
List_String_klass, ".ctor", 0
|
||||||
|
);
|
||||||
|
static auto List_String_ctor = reinterpret_cast<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) {
|
||||||
|
return ReadField<void*>(fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* ReadObjectListField(const std::string& fieldName) {
|
||||||
|
return ReadField<void*>(fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
static FieldController CreateSubFieldController(void* subObj) {
|
||||||
|
return FieldController(subObj);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================
|
||||||
|
// 帮助函数:判断 JSON 字段类型
|
||||||
|
//==============================================================
|
||||||
|
JsonValueType checkJsonValueType(const nlohmann::json& j) {
|
||||||
|
if (j.is_string()) return JsonValueType::JVT_String;
|
||||||
|
if (j.is_number_integer()) return JsonValueType::JVT_Int;
|
||||||
|
if (j.is_object()) return JsonValueType::JVT_Object;
|
||||||
|
if (j.is_array()) {
|
||||||
|
if (!j.empty() && j.begin()->is_object()) {
|
||||||
|
return JsonValueType::JVT_ArrayObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return JsonValueType::JVT_String;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================
|
||||||
|
// 解析 pkName => PKItem
|
||||||
|
//==============================================================
|
||||||
|
PKItem parsePK(const nlohmann::json& row, const std::string& pkStr) {
|
||||||
|
auto pos = pkStr.find('.');
|
||||||
|
PKItem item;
|
||||||
|
if (pos == std::string::npos) {
|
||||||
|
item.topLevel = pkStr;
|
||||||
|
item.subField = "";
|
||||||
|
if (!row.contains(pkStr)) {
|
||||||
|
item.topLevelType = JsonValueType::JVT_String;
|
||||||
|
} else {
|
||||||
|
item.topLevelType = checkJsonValueType(row[pkStr]);
|
||||||
|
}
|
||||||
|
item.subFieldType = JsonValueType::JVT_String;
|
||||||
|
} else {
|
||||||
|
item.topLevel = pkStr.substr(0, pos);
|
||||||
|
item.subField = pkStr.substr(pos + 1);
|
||||||
|
if (!row.contains(item.topLevel)) {
|
||||||
|
item.topLevelType = JsonValueType::JVT_Object;
|
||||||
|
} else {
|
||||||
|
auto& jTop = row[item.topLevel];
|
||||||
|
auto t = checkJsonValueType(jTop);
|
||||||
|
if (t == JsonValueType::JVT_Object) {
|
||||||
|
item.topLevelType = JsonValueType::JVT_Object;
|
||||||
|
} else if (t == JsonValueType::JVT_ArrayObject) {
|
||||||
|
item.topLevelType = JsonValueType::JVT_ArrayObject;
|
||||||
|
} else {
|
||||||
|
item.topLevelType = JsonValueType::JVT_Object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item.subFieldType = JsonValueType::JVT_String;
|
||||||
|
if (row.contains(item.topLevel)) {
|
||||||
|
auto& jTop = row[item.topLevel];
|
||||||
|
if (jTop.is_object()) {
|
||||||
|
if (jTop.contains(item.subField)) {
|
||||||
|
item.subFieldType = checkJsonValueType(jTop[item.subField]);
|
||||||
|
}
|
||||||
|
} else if (jTop.is_array() && !jTop.empty()) {
|
||||||
|
auto& firstElem = *jTop.begin();
|
||||||
|
if (firstElem.is_object() && firstElem.contains(item.subField)) {
|
||||||
|
item.subFieldType = checkJsonValueType(firstElem[item.subField]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<PKItem> parseAllPKItems(const nlohmann::json& row, const std::vector<std::string>& pkNames) {
|
||||||
|
std::vector<PKItem> result;
|
||||||
|
result.reserve(pkNames.size());
|
||||||
|
for (auto& pk : pkNames) {
|
||||||
|
auto item = parsePK(row, pk);
|
||||||
|
result.push_back(item);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================
|
||||||
|
// 将 jval 拼接到 uniqueKey
|
||||||
|
//==============================================================
|
||||||
|
inline void appendPKValue(std::string& uniqueKey, const nlohmann::json& jval, bool& isFirst) {
|
||||||
|
if (!isFirst) uniqueKey += "|";
|
||||||
|
if (jval.is_string()) {
|
||||||
|
uniqueKey += jval.get<std::string>();
|
||||||
|
} else if (jval.is_number_integer()) {
|
||||||
|
uniqueKey += std::to_string(jval.get<int>());
|
||||||
|
}
|
||||||
|
isFirst = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================
|
||||||
|
// 读取文件 => 解析 => 加载 dataMap
|
||||||
|
//==============================================================
|
||||||
|
std::string ReadFileToString(const std::filesystem::path& path) {
|
||||||
|
std::ifstream ifs(path, std::ios::binary);
|
||||||
|
if (!ifs) return {};
|
||||||
|
std::stringstream buffer;
|
||||||
|
buffer << ifs.rdbuf();
|
||||||
|
return buffer.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断 row 里,与 pkNames/主键相关的字段(若是数组)是否为空
|
||||||
|
bool hasEmptyArrayForPk(const nlohmann::json& row, const std::vector<std::string>& pkNames) {
|
||||||
|
// 如果行为空,直接返回 false(或 true,看你需求)
|
||||||
|
if (row.is_null() || !row.is_object()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& pk : pkNames) {
|
||||||
|
// 先看该行是否包含此顶层字段
|
||||||
|
auto dotPos = pk.find('.');
|
||||||
|
std::string topLevel = (dotPos == std::string::npos) ? pk : pk.substr(0, dotPos);
|
||||||
|
|
||||||
|
if (!row.contains(topLevel)) {
|
||||||
|
// 没有这个字段就略过
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果 pk 中含 '.', 说明可能是 array<object> 类型
|
||||||
|
// 这里仅检查 "顶层字段是否是空数组"
|
||||||
|
// 若需要更深层的判断,需扩展
|
||||||
|
const auto& jTop = row[topLevel];
|
||||||
|
if (jTop.is_array()) {
|
||||||
|
// 一旦发现是空数组,就返回 true
|
||||||
|
if (jTop.empty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据 pkItems 构造一个 skipSet,里面包含 "topLevel" 和 "topLevel.subField"
|
||||||
|
// 或者只包含 subField, 看你具体需求
|
||||||
|
static std::unordered_set<std::string> buildSkipFields(const std::vector<PKItem>& pkItems) {
|
||||||
|
std::unordered_set<std::string> skipSet;
|
||||||
|
for (auto& pk : pkItems) {
|
||||||
|
if (pk.subField.empty()) {
|
||||||
|
// e.g. "id"
|
||||||
|
skipSet.insert(pk.topLevel);
|
||||||
|
} else {
|
||||||
|
// e.g. "descriptions.type" => 既要跳过 "type" 又要跳过 "descriptions"?
|
||||||
|
// 具体看你业务需要:
|
||||||
|
// skipSet.insert(pk.topLevel); // 可能不需要
|
||||||
|
skipSet.insert(pk.subField); // "type"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return skipSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归枚举 JSON 值里的字符串并插入到 localSet
|
||||||
|
void collectLocalizableStrings_impl(
|
||||||
|
const nlohmann::json& node,
|
||||||
|
const std::unordered_set<std::string>& skipSet,
|
||||||
|
std::unordered_set<std::string>& localSet
|
||||||
|
) {
|
||||||
|
if (node.is_string()) {
|
||||||
|
// node本身就是string => 这时无法知道key名,但一般情况下我们是key->value对?
|
||||||
|
// 这里仅当外层调用传入一个object时可取到key
|
||||||
|
// 先写成仅object字段时处理
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (node.is_object()) {
|
||||||
|
// 枚举键值
|
||||||
|
for (auto it = node.begin(); it != node.end(); ++it) {
|
||||||
|
auto& key = it.key();
|
||||||
|
auto& val = it.value();
|
||||||
|
// 如果key在skipSet里,则跳过
|
||||||
|
if (skipSet.count(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 否则看val的类型
|
||||||
|
if (val.is_string()) {
|
||||||
|
// 收集
|
||||||
|
localSet.insert(val.get<std::string>());
|
||||||
|
// Log::DebugFmt("localSet.insert: %s", val.get<std::string>().c_str());
|
||||||
|
} else if (val.is_object() || val.is_array()) {
|
||||||
|
// 递归下去
|
||||||
|
collectLocalizableStrings_impl(val, skipSet, localSet);
|
||||||
|
}
|
||||||
|
// 其他类型 (int/bool/float) 不做本地化
|
||||||
|
}
|
||||||
|
} else if (node.is_array()) {
|
||||||
|
// 枚举数组元素
|
||||||
|
for (auto& element : node) {
|
||||||
|
if (element.is_string()) {
|
||||||
|
localSet.insert(element.get<std::string>());
|
||||||
|
// Log::DebugFmt("localSet.insert: %s", element.get<std::string>().c_str());
|
||||||
|
} else if (element.is_object() || element.is_array()) {
|
||||||
|
collectLocalizableStrings_impl(element, skipSet, localSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对外接口:根据 row + pkItems,把所有非主键字段的字符串插到 localSet
|
||||||
|
void collectLocalizableStrings(const nlohmann::json& row, const std::vector<PKItem>& pkItems, std::unordered_set<std::string>& localSet) {
|
||||||
|
// 先构建一个 skipSet,表示"主键字段"要跳过
|
||||||
|
auto skipSet = buildSkipFields(pkItems);
|
||||||
|
// 然后递归遍历
|
||||||
|
collectLocalizableStrings_impl(row, skipSet, localSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LoadData() {
|
||||||
|
g_loadedData.clear();
|
||||||
|
static auto masterDir = Local::GetBasePath() / "local-files" / "masterTrans";
|
||||||
|
if (!std::filesystem::is_directory(masterDir)) {
|
||||||
|
Log::ErrorFmt("LoadData: not found: %s", masterDir.string().c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isFirstIteration = true;
|
||||||
|
for (auto& p : std::filesystem::directory_iterator(masterDir)) {
|
||||||
|
if (isFirstIteration) {
|
||||||
|
auto totalFileCount = std::distance(
|
||||||
|
std::filesystem::directory_iterator(masterDir),
|
||||||
|
std::filesystem::directory_iterator{}
|
||||||
|
);
|
||||||
|
UnityResolveProgress::classProgress.total = totalFileCount <= 0 ? 1 : totalFileCount;
|
||||||
|
isFirstIteration = false;
|
||||||
|
}
|
||||||
|
UnityResolveProgress::classProgress.current++;
|
||||||
|
|
||||||
|
if (!p.is_regular_file()) continue;
|
||||||
|
const auto& path = p.path();
|
||||||
|
if (path.extension() != ".json") continue;
|
||||||
|
|
||||||
|
std::string tableName = path.stem().string();
|
||||||
|
auto fileContent = ReadFileToString(path);
|
||||||
|
if (fileContent.empty()) continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto j = nlohmann::json::parse(fileContent);
|
||||||
|
if (!j.contains("rules") || !j["rules"].contains("primaryKeys")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::vector<std::string> pkNames;
|
||||||
|
for (auto& x : j["rules"]["primaryKeys"]) {
|
||||||
|
pkNames.push_back(x.get<std::string>());
|
||||||
|
}
|
||||||
|
if (!j.contains("data") || !j["data"].is_array()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
TableInfo tableInfo;
|
||||||
|
if (!j["data"].empty()) {
|
||||||
|
for (auto & currRow : j["data"]) {
|
||||||
|
if (!hasEmptyArrayForPk(currRow, pkNames)) {
|
||||||
|
tableInfo.pkItems = parseAllPKItems(currRow, pkNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// auto& firstRow = j["data"][0];
|
||||||
|
// tableInfo.pkItems = parseAllPKItems(firstRow, pkNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================
|
||||||
|
// 构建 dataMap, 支持 array + index
|
||||||
|
//==============================================================
|
||||||
|
for (auto& row : j["data"]) {
|
||||||
|
std::string uniqueKey;
|
||||||
|
bool firstKey = true;
|
||||||
|
bool failed = false;
|
||||||
|
|
||||||
|
for (auto& pkItem : tableInfo.pkItems) {
|
||||||
|
if (!row.contains(pkItem.topLevel)) {
|
||||||
|
failed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
auto& jTop = row[pkItem.topLevel];
|
||||||
|
|
||||||
|
// 无子字段 => 直接处理
|
||||||
|
if (pkItem.subField.empty()) {
|
||||||
|
if (jTop.is_string() || jTop.is_number_integer()) {
|
||||||
|
appendPKValue(uniqueKey, jTop, firstKey);
|
||||||
|
} else {
|
||||||
|
failed = true; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 若是 array<object> + subField,就遍历数组每个下标 + subField => 并将 index + value 拼进 uniqueKey
|
||||||
|
if (pkItem.topLevelType == JsonValueType::JVT_ArrayObject) {
|
||||||
|
if (!jTop.is_array()) { failed = true; break; }
|
||||||
|
// 遍历数组所有元素
|
||||||
|
for (int i = 0; i < (int)jTop.size(); i++) {
|
||||||
|
auto& elem = jTop[i];
|
||||||
|
if (!elem.is_object()) { failed = true; break; }
|
||||||
|
if (!elem.contains(pkItem.subField)) { failed = true; break; }
|
||||||
|
auto& subVal = elem[pkItem.subField];
|
||||||
|
// 只支持 string/int
|
||||||
|
if (!subVal.is_string() && !subVal.is_number_integer()) {
|
||||||
|
failed = true; break;
|
||||||
|
}
|
||||||
|
// 拼上索引 + 值
|
||||||
|
// e.g. "|0:xxx|1:yyy"...
|
||||||
|
if (!firstKey) uniqueKey += "|";
|
||||||
|
uniqueKey += std::to_string(i);
|
||||||
|
uniqueKey += ":";
|
||||||
|
if (subVal.is_string()) {
|
||||||
|
uniqueKey += subVal.get<std::string>();
|
||||||
|
} else {
|
||||||
|
uniqueKey += std::to_string(subVal.get<int>());
|
||||||
|
}
|
||||||
|
firstKey = false;
|
||||||
|
}
|
||||||
|
if (failed) break;
|
||||||
|
}
|
||||||
|
else if (pkItem.topLevelType == JsonValueType::JVT_Object) {
|
||||||
|
if (!jTop.is_object()) {
|
||||||
|
failed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!jTop.contains(pkItem.subField)) { failed = true; break; }
|
||||||
|
auto& subVal = jTop[pkItem.subField];
|
||||||
|
if (subVal.is_string() || subVal.is_number_integer()) {
|
||||||
|
appendPKValue(uniqueKey, subVal, firstKey);
|
||||||
|
} else {
|
||||||
|
failed = true; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
failed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (failed) break;
|
||||||
|
}
|
||||||
|
if (!failed && !uniqueKey.empty()) {
|
||||||
|
tableInfo.dataMap[uniqueKey] = row;
|
||||||
|
collectLocalizableStrings(row, tableInfo.pkItems, Local::translatedText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log::DebugFmt("Load table: %s, %d, %d", tableName.c_str(), tableInfo.pkItems.size(), tableInfo.dataMap.size());
|
||||||
|
g_loadedData[tableName] = std::move(tableInfo);
|
||||||
|
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
Log::ErrorFmt("MasterLocal::LoadData: parse error in '%s': %s",
|
||||||
|
path.string().c_str(), e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================
|
||||||
|
// 在 C# 对象里,根据 pkItems 构造 uniqueKey
|
||||||
|
// 同样要支持 array<object> + index
|
||||||
|
//==============================================================
|
||||||
|
bool buildUniqueKeyFromCSharp(FieldController& fc, const TableInfo& tableInfo, std::string& outKey) {
|
||||||
|
outKey.clear();
|
||||||
|
bool firstKey = true;
|
||||||
|
|
||||||
|
for (auto& pk : tableInfo.pkItems) {
|
||||||
|
if (pk.subField.empty()) {
|
||||||
|
// 顶层无子字段
|
||||||
|
if (pk.topLevelType == JsonValueType::JVT_String) {
|
||||||
|
auto sptr = fc.ReadStringField(pk.topLevel);
|
||||||
|
if (!sptr) return false;
|
||||||
|
if (!firstKey) outKey += "|";
|
||||||
|
outKey += sptr->ToString();
|
||||||
|
firstKey = false;
|
||||||
|
} else if (pk.topLevelType == JsonValueType::JVT_Int) {
|
||||||
|
int ival = fc.ReadIntField(pk.topLevel);
|
||||||
|
if (!firstKey) outKey += "|";
|
||||||
|
outKey += std::to_string(ival);
|
||||||
|
firstKey = false;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// subField
|
||||||
|
if (pk.topLevelType == JsonValueType::JVT_ArrayObject) {
|
||||||
|
// => c# 里 readObjectListField
|
||||||
|
void* listPtr = fc.ReadObjectListField(pk.topLevel);
|
||||||
|
if (!listPtr) return false;
|
||||||
|
Il2cppUtils::Tools::CSListEditor<void*> listEdit(listPtr);
|
||||||
|
int arrCount = listEdit.get_Count();
|
||||||
|
|
||||||
|
// 遍历每个 index
|
||||||
|
for (int i = 0; i < arrCount; i++) {
|
||||||
|
auto elemPtr = listEdit.get_Item(i);
|
||||||
|
if (!elemPtr) return false;
|
||||||
|
FieldController subFC = FieldController::CreateSubFieldController(elemPtr);
|
||||||
|
|
||||||
|
// 只支持 string/int
|
||||||
|
if (pk.subFieldType == JsonValueType::JVT_String) {
|
||||||
|
auto sptr = subFC.ReadStringField(pk.subField);
|
||||||
|
if (!sptr) return false;
|
||||||
|
if (!firstKey) outKey += "|";
|
||||||
|
// "|i:xxx"
|
||||||
|
outKey += std::to_string(i);
|
||||||
|
outKey += ":";
|
||||||
|
outKey += sptr->ToString();
|
||||||
|
firstKey = false;
|
||||||
|
}
|
||||||
|
else if (pk.subFieldType == JsonValueType::JVT_Int) {
|
||||||
|
int ival = subFC.ReadIntField(pk.subField);
|
||||||
|
if (!firstKey) outKey += "|";
|
||||||
|
outKey += std::to_string(i);
|
||||||
|
outKey += ":";
|
||||||
|
outKey += std::to_string(ival);
|
||||||
|
firstKey = false;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (pk.topLevelType == JsonValueType::JVT_Object) {
|
||||||
|
void* subObj = fc.ReadObjectField(pk.topLevel);
|
||||||
|
if (!subObj) return false;
|
||||||
|
FieldController subFC = FieldController::CreateSubFieldController(subObj);
|
||||||
|
|
||||||
|
if (pk.subFieldType == JsonValueType::JVT_String) {
|
||||||
|
auto sptr = subFC.ReadStringField(pk.subField);
|
||||||
|
if (!sptr) return false;
|
||||||
|
if (!firstKey) outKey += "|";
|
||||||
|
outKey += sptr->ToString();
|
||||||
|
firstKey = false;
|
||||||
|
}
|
||||||
|
else if (pk.subFieldType == JsonValueType::JVT_Int) {
|
||||||
|
int ival = subFC.ReadIntField(pk.subField);
|
||||||
|
if (!firstKey) outKey += "|";
|
||||||
|
outKey += std::to_string(ival);
|
||||||
|
firstKey = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return !outKey.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 声明
|
||||||
|
void localizeJsonToCsharp(FieldController& fc, const nlohmann::json& jdata, const std::unordered_set<std::string>& skipKeySet);
|
||||||
|
void localizeArrayOfObject(FieldController& fc, const std::string& fieldName, const nlohmann::json& arrVal, const std::unordered_set<std::string>& skipKeySet);
|
||||||
|
void localizeObject(FieldController& fc, const std::string& fieldName, const nlohmann::json& objVal, const std::unordered_set<std::string>& skipKeySet);
|
||||||
|
|
||||||
|
//====================================================================
|
||||||
|
// 对 array<object> 做一层递归 —— 需要带着 skipKeySet
|
||||||
|
//====================================================================
|
||||||
|
void localizeArrayOfObject(FieldController& fc, const std::string& fieldName, const nlohmann::json& arrVal, const std::unordered_set<std::string>& skipKeySet) {
|
||||||
|
void* listPtr = fc.ReadObjectListField(fieldName);
|
||||||
|
if (!listPtr) return;
|
||||||
|
Il2cppUtils::Tools::CSListEditor<void*> listEdit(listPtr);
|
||||||
|
int cmin = std::min<int>(listEdit.get_Count(), (int)arrVal.size());
|
||||||
|
for (int i = 0; i < cmin; i++) {
|
||||||
|
auto elemPtr = listEdit.get_Item(i);
|
||||||
|
if (!elemPtr) continue;
|
||||||
|
FieldController subFC = FieldController::CreateSubFieldController(elemPtr);
|
||||||
|
localizeJsonToCsharp(subFC, arrVal[i], skipKeySet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//====================================================================
|
||||||
|
// 对单个 object 做一层递归 —— 需要带着 skipKeySet
|
||||||
|
//====================================================================
|
||||||
|
void localizeObject(FieldController& fc, const std::string& fieldName, const nlohmann::json& objVal, const std::unordered_set<std::string>& skipKeySet) {
|
||||||
|
void* subObj = fc.ReadObjectField(fieldName);
|
||||||
|
if (!subObj) return;
|
||||||
|
FieldController subFC = FieldController::CreateSubFieldController(subObj);
|
||||||
|
localizeJsonToCsharp(subFC, objVal, skipKeySet);
|
||||||
|
}
|
||||||
|
|
||||||
|
//====================================================================
|
||||||
|
// 仅一层本地化: string, string[], object, object[],带 skipKeySet
|
||||||
|
//====================================================================
|
||||||
|
void localizeJsonToCsharp(FieldController& fc, const nlohmann::json& jdata, const std::unordered_set<std::string>& skipKeySet) {
|
||||||
|
if (!jdata.is_object()) return;
|
||||||
|
for (auto it = jdata.begin(); it != jdata.end(); ++it) {
|
||||||
|
const std::string& key = it.key();
|
||||||
|
// 如果 key 在 skipKeySet 里,则跳过本地化
|
||||||
|
if (skipKeySet.count(key)) {
|
||||||
|
// Debug输出可以留意一下
|
||||||
|
// Log::DebugFmt("skip field: %s", key.c_str());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& val = it.value();
|
||||||
|
if (val.is_string()) {
|
||||||
|
// 打印一下做验证
|
||||||
|
auto origStr = fc.ReadStringField(key);
|
||||||
|
auto newStr = val.get<std::string>();
|
||||||
|
if (origStr) {
|
||||||
|
std::string oldVal = origStr->ToString();
|
||||||
|
// Log::DebugFmt("SetStringField key: %s, oldVal: %s -> newVal: %s", key.c_str(), oldVal.c_str(), newStr.c_str());
|
||||||
|
if (((oldVal == "\n") || (oldVal == "\r\n")) && newStr.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fc.SetStringField(key, val.get<std::string>());
|
||||||
|
}
|
||||||
|
else if (val.is_array()) {
|
||||||
|
if (!val.empty() && val.begin()->is_string()) {
|
||||||
|
bool allStr = true;
|
||||||
|
std::vector<std::string> strArray;
|
||||||
|
for (auto& x : val) {
|
||||||
|
if (!x.is_string()) { allStr = false; break; }
|
||||||
|
strArray.push_back(x.get<std::string>());
|
||||||
|
}
|
||||||
|
if (allStr) {
|
||||||
|
// Log::DebugFmt("SetStringListField in %s, key: %s", fc.self_klass->name, key.c_str());
|
||||||
|
fc.SetStringListField(key, strArray);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// array<object>
|
||||||
|
if (!val.empty() && val.begin()->is_object()) {
|
||||||
|
localizeArrayOfObject(fc, key, val, skipKeySet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (val.is_object()) {
|
||||||
|
localizeObject(fc, key, val, skipKeySet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//====================================================================
|
||||||
|
// 真正处理单个C#对象
|
||||||
|
//====================================================================
|
||||||
|
void LocalizeMasterItem(FieldController& fc, const std::string& tableName) {
|
||||||
|
auto it = g_loadedData.find(tableName);
|
||||||
|
if (it == g_loadedData.end()) return;
|
||||||
|
// Log::DebugFmt("LocalizeMasterItem: %s", tableName.c_str());
|
||||||
|
auto& tableInfo = it->second;
|
||||||
|
if (tableInfo.dataMap.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string uniqueKey;
|
||||||
|
if (!buildUniqueKeyFromCSharp(fc, tableInfo, uniqueKey)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto itRow = tableInfo.dataMap.find(uniqueKey);
|
||||||
|
if (itRow == tableInfo.dataMap.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto& rowData = itRow->second;
|
||||||
|
|
||||||
|
//=====================================================
|
||||||
|
// 把「有子字段」的 pkItem 也加入 skipKeySet,但用它的 `subField` 部分
|
||||||
|
//=====================================================
|
||||||
|
std::unordered_set<std::string> skipKeySet;
|
||||||
|
for (auto& pk : tableInfo.pkItems) {
|
||||||
|
if (pk.subField.empty()) {
|
||||||
|
// 若没有子字段,说明 topLevel 本身是主键
|
||||||
|
skipKeySet.insert(pk.topLevel);
|
||||||
|
} else {
|
||||||
|
// 如果有子字段,说明这个子字段才是 PK
|
||||||
|
// e.g. produceDescriptions.examEffectType => skipKeySet.insert("examEffectType");
|
||||||
|
skipKeySet.insert(pk.subField);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 然后带着 skipKeySet 去做本地化
|
||||||
|
localizeJsonToCsharp(fc, rowData, skipKeySet);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalizeMasterTables(const std::string& tableName, UnityResolve::UnityType::List<void*>* result) {
|
||||||
|
if (!result) return;
|
||||||
|
Il2cppUtils::Tools::CSListEditor resultList(result);
|
||||||
|
if (resultList.get_Count() <= 0) return;
|
||||||
|
|
||||||
|
for (auto i : resultList) {
|
||||||
|
if (!i) continue;
|
||||||
|
FieldController fc(i);
|
||||||
|
LocalizeMasterItem(fc, tableName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalizeMaster(const std::string& sql, UnityResolve::UnityType::List<void*>* result) {
|
||||||
|
static const std::regex tableNameRegex(R"(\bFROM\s+(?:`([^`]+)`|(\S+)))");
|
||||||
|
std::smatch match;
|
||||||
|
if (std::regex_search(sql, match, tableNameRegex)) {
|
||||||
|
std::string tableName = match[1].matched ? match[1].str() : match[2].str();
|
||||||
|
LocalizeMasterTables(tableName, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalizeMaster(const std::string& sql, void* result) {
|
||||||
|
if (!Config::useMasterTrans) return;
|
||||||
|
LocalizeMaster(sql, reinterpret_cast<UnityResolve::UnityType::List<void*>*>(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalizeMaster(void* result, const std::string& tableName) {
|
||||||
|
if (!Config::useMasterTrans) return;
|
||||||
|
LocalizeMasterTables(tableName, reinterpret_cast<UnityResolve::UnityType::List<void*>*>(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalizeMasterItem(void* item, const std::string& tableName) {
|
||||||
|
if (!Config::useMasterTrans) return;
|
||||||
|
FieldController fc(item);
|
||||||
|
LocalizeMasterItem(fc, tableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace GakumasLocal::MasterLocal
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
#ifndef GAKUMAS_LOCALIFY_MASTERLOCAL_H
|
||||||
|
#define GAKUMAS_LOCALIFY_MASTERLOCAL_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace GakumasLocal::MasterLocal {
|
||||||
|
void LoadData();
|
||||||
|
void LocalizeMaster(const std::string& sql, void* result);
|
||||||
|
void LocalizeMaster(void* result, const std::string& tableName);
|
||||||
|
void LocalizeMasterItem(void* item, const std::string& tableName);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#endif //GAKUMAS_LOCALIFY_MASTERLOCAL_H
|
||||||
|
|
@ -2,36 +2,14 @@
|
||||||
|
|
||||||
#include <codecvt>
|
#include <codecvt>
|
||||||
#include <locale>
|
#include <locale>
|
||||||
|
#include <jni.h>
|
||||||
#include "fmt/core.h"
|
#include "fmt/core.h"
|
||||||
|
|
||||||
#ifndef GKMS_WINDOWS
|
|
||||||
#include <jni.h>
|
|
||||||
|
|
||||||
extern JavaVM* g_javaVM;
|
extern JavaVM* g_javaVM;
|
||||||
#else
|
|
||||||
#include "cpprest/details/http_helpers.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
namespace GakumasLocal::Misc {
|
namespace GakumasLocal::Misc {
|
||||||
|
|
||||||
#ifdef GKMS_WINDOWS
|
|
||||||
std::string ToUTF8(const std::wstring_view& str) {
|
|
||||||
return utility::conversions::to_utf8string(str.data());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::u16string ToUTF16(const std::string_view& str) {
|
|
||||||
std::string input(str);
|
|
||||||
std::wstring wstr = utility::conversions::utf8_to_utf16(input);
|
|
||||||
return std::u16string(wstr.begin(), wstr.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ToUTF8(const std::u16string_view& str) {
|
|
||||||
std::u16string u16(str);
|
|
||||||
std::wstring wstr(u16.begin(), u16.end());
|
|
||||||
return utility::conversions::utf16_to_utf8(wstr);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
std::u16string ToUTF16(const std::string_view& str) {
|
std::u16string ToUTF16(const std::string_view& str) {
|
||||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> utf16conv;
|
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> utf16conv;
|
||||||
return utf16conv.from_bytes(str.data(), str.data() + str.size());
|
return utf16conv.from_bytes(str.data(), str.data() + str.size());
|
||||||
|
|
@ -41,9 +19,7 @@ namespace GakumasLocal::Misc {
|
||||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> utf16conv;
|
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> utf16conv;
|
||||||
return utf16conv.to_bytes(str.data(), str.data() + str.size());
|
return utf16conv.to_bytes(str.data(), str.data() + str.size());
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef GKMS_WINDOWS
|
|
||||||
JNIEnv* GetJNIEnv() {
|
JNIEnv* GetJNIEnv() {
|
||||||
if (!g_javaVM) return nullptr;
|
if (!g_javaVM) return nullptr;
|
||||||
JNIEnv* env = nullptr;
|
JNIEnv* env = nullptr;
|
||||||
|
|
@ -55,7 +31,6 @@ namespace GakumasLocal::Misc {
|
||||||
}
|
}
|
||||||
return env;
|
return env;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
CSEnum::CSEnum(const std::string& name, const int value) {
|
CSEnum::CSEnum(const std::string& name, const int value) {
|
||||||
this->Add(name, value);
|
this->Add(name, value);
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,11 @@
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <jni.h>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "../platformDefine.hpp"
|
|
||||||
|
|
||||||
#ifndef GKMS_WINDOWS
|
|
||||||
#include <jni.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
namespace GakumasLocal {
|
namespace GakumasLocal {
|
||||||
using OpaqueFunctionPointer = void (*)();
|
using OpaqueFunctionPointer = void (*)();
|
||||||
|
|
@ -19,13 +14,7 @@ namespace GakumasLocal {
|
||||||
namespace Misc {
|
namespace Misc {
|
||||||
std::u16string ToUTF16(const std::string_view& str);
|
std::u16string ToUTF16(const std::string_view& str);
|
||||||
std::string ToUTF8(const std::u16string_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();
|
JNIEnv* GetJNIEnv();
|
||||||
#endif
|
|
||||||
|
|
||||||
class CSEnum {
|
class CSEnum {
|
||||||
public:
|
public:
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,7 @@
|
||||||
#include "Misc.hpp"
|
#include "Misc.hpp"
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "../platformDefine.hpp"
|
|
||||||
|
|
||||||
#ifndef GKMS_WINDOWS
|
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
#endif // !GKMS_WINDOWS
|
|
||||||
|
|
||||||
|
|
||||||
namespace GakumasLocal {
|
namespace GakumasLocal {
|
||||||
struct HookInstaller
|
struct HookInstaller
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,6 @@
|
||||||
#include "baseCamera.hpp"
|
#include "baseCamera.hpp"
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#include "../../platformDefine.hpp"
|
|
||||||
|
|
||||||
#ifdef GKMS_WINDOWS
|
|
||||||
#include <corecrt_math_defines.h>
|
|
||||||
#endif // GKMS_WINDOWS
|
|
||||||
|
|
||||||
|
|
||||||
namespace BaseCamera {
|
namespace BaseCamera {
|
||||||
using Vector3_t = UnityResolve::UnityType::Vector3;
|
using Vector3_t = UnityResolve::UnityType::Vector3;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "../../deps/UnityResolve/UnityResolve.hpp"
|
#include "../deps/UnityResolve/UnityResolve.hpp"
|
||||||
|
|
||||||
enum LonMoveHState {
|
enum LonMoveHState {
|
||||||
LonMoveLeftAndRight,
|
LonMoveLeftAndRight,
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,8 @@
|
||||||
#include "baseCamera.hpp"
|
#include "baseCamera.hpp"
|
||||||
#include "camera.hpp"
|
#include "camera.hpp"
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include "../Misc.hpp"
|
#include "Misc.hpp"
|
||||||
#include "../BaseDefine.h"
|
#include "../BaseDefine.h"
|
||||||
#include "../../platformDefine.hpp"
|
|
||||||
|
|
||||||
#ifdef GKMS_WINDOWS
|
|
||||||
#include <corecrt_math_defines.h>
|
|
||||||
#endif // GKMS_WINDOWS
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
namespace GKCamera {
|
namespace GKCamera {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "baseCamera.hpp"
|
#include "baseCamera.hpp"
|
||||||
#include "../../deps/Joystick/JoystickEvent.h"
|
#include "Joystick/JoystickEvent.h"
|
||||||
|
|
||||||
namespace GKCamera {
|
namespace GKCamera {
|
||||||
enum class CameraMode {
|
enum class CameraMode {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "nlohmann/json.hpp"
|
#include "nlohmann/json.hpp"
|
||||||
#include "../Log.h"
|
#include "../Log.h"
|
||||||
#include <thread>
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
namespace GakumasLocal::Config {
|
namespace GakumasLocal::Config {
|
||||||
bool isConfigInit = false;
|
bool isConfigInit = false;
|
||||||
|
|
@ -11,13 +9,11 @@ namespace GakumasLocal::Config {
|
||||||
bool enabled = true;
|
bool enabled = true;
|
||||||
bool lazyInit = true;
|
bool lazyInit = true;
|
||||||
bool replaceFont = true;
|
bool replaceFont = true;
|
||||||
bool replaceTexture = true;
|
|
||||||
bool forceExportResource = true;
|
bool forceExportResource = true;
|
||||||
bool textTest = false;
|
bool textTest = false;
|
||||||
bool useMasterTrans = true;
|
bool useMasterTrans = true;
|
||||||
int gameOrientation = 0;
|
int gameOrientation = 0;
|
||||||
bool dumpText = false;
|
bool dumpText = false;
|
||||||
bool dumpRuntimeTexture = false;
|
|
||||||
bool enableFreeCamera = false;
|
bool enableFreeCamera = false;
|
||||||
int targetFrameRate = 0;
|
int targetFrameRate = 0;
|
||||||
bool unlockAllLive = false;
|
bool unlockAllLive = false;
|
||||||
|
|
@ -56,8 +52,6 @@ namespace GakumasLocal::Config {
|
||||||
float bLimitZx = 1.0f;
|
float bLimitZx = 1.0f;
|
||||||
float bLimitZy = 1.0f;
|
float bLimitZy = 1.0f;
|
||||||
|
|
||||||
bool dmmUnlockSize = false;
|
|
||||||
|
|
||||||
void LoadConfig(const std::string& configStr) {
|
void LoadConfig(const std::string& configStr) {
|
||||||
try {
|
try {
|
||||||
const auto config = nlohmann::json::parse(configStr);
|
const auto config = nlohmann::json::parse(configStr);
|
||||||
|
|
@ -68,13 +62,11 @@ namespace GakumasLocal::Config {
|
||||||
GetConfigItem(enabled);
|
GetConfigItem(enabled);
|
||||||
GetConfigItem(lazyInit);
|
GetConfigItem(lazyInit);
|
||||||
GetConfigItem(replaceFont);
|
GetConfigItem(replaceFont);
|
||||||
GetConfigItem(replaceTexture);
|
|
||||||
GetConfigItem(forceExportResource);
|
GetConfigItem(forceExportResource);
|
||||||
GetConfigItem(gameOrientation);
|
GetConfigItem(gameOrientation);
|
||||||
GetConfigItem(textTest);
|
GetConfigItem(textTest);
|
||||||
GetConfigItem(useMasterTrans);
|
GetConfigItem(useMasterTrans);
|
||||||
GetConfigItem(dumpText);
|
GetConfigItem(dumpText);
|
||||||
GetConfigItem(dumpRuntimeTexture);
|
|
||||||
GetConfigItem(targetFrameRate);
|
GetConfigItem(targetFrameRate);
|
||||||
GetConfigItem(enableFreeCamera);
|
GetConfigItem(enableFreeCamera);
|
||||||
GetConfigItem(unlockAllLive);
|
GetConfigItem(unlockAllLive);
|
||||||
|
|
@ -108,76 +100,11 @@ namespace GakumasLocal::Config {
|
||||||
GetConfigItem(bLimitYy);
|
GetConfigItem(bLimitYy);
|
||||||
GetConfigItem(bLimitZx);
|
GetConfigItem(bLimitZx);
|
||||||
GetConfigItem(bLimitZy);
|
GetConfigItem(bLimitZy);
|
||||||
GetConfigItem(dmmUnlockSize);
|
|
||||||
}
|
}
|
||||||
catch (std::exception& e) {
|
catch (std::exception& e) {
|
||||||
Log::ErrorFmt("LoadConfig error: %s", e.what());
|
Log::ErrorFmt("LoadConfig error: %s", e.what());
|
||||||
}
|
}
|
||||||
isConfigInit = true;
|
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(replaceTexture);
|
|
||||||
SetConfigItem(forceExportResource);
|
|
||||||
SetConfigItem(gameOrientation);
|
|
||||||
SetConfigItem(textTest);
|
|
||||||
SetConfigItem(useMasterTrans);
|
|
||||||
SetConfigItem(dumpText);
|
|
||||||
SetConfigItem(dumpRuntimeTexture);
|
|
||||||
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);
|
|
||||||
SetConfigItem(dmmUnlockSize);
|
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,11 @@ namespace GakumasLocal::Config {
|
||||||
extern bool enabled;
|
extern bool enabled;
|
||||||
extern bool lazyInit;
|
extern bool lazyInit;
|
||||||
extern bool replaceFont;
|
extern bool replaceFont;
|
||||||
extern bool replaceTexture;
|
|
||||||
extern bool forceExportResource;
|
extern bool forceExportResource;
|
||||||
extern int gameOrientation;
|
extern int gameOrientation;
|
||||||
extern bool textTest;
|
extern bool textTest;
|
||||||
extern bool useMasterTrans;
|
extern bool useMasterTrans;
|
||||||
extern bool dumpText;
|
extern bool dumpText;
|
||||||
extern bool dumpRuntimeTexture;
|
|
||||||
extern bool enableFreeCamera;
|
extern bool enableFreeCamera;
|
||||||
extern int targetFrameRate;
|
extern int targetFrameRate;
|
||||||
extern bool unlockAllLive;
|
extern bool unlockAllLive;
|
||||||
|
|
@ -53,8 +51,5 @@ namespace GakumasLocal::Config {
|
||||||
extern float bLimitZx;
|
extern float bLimitZx;
|
||||||
extern float bLimitZy;
|
extern float bLimitZy;
|
||||||
|
|
||||||
extern bool dmmUnlockSize;
|
|
||||||
|
|
||||||
void LoadConfig(const std::string& configStr);
|
void LoadConfig(const std::string& configStr);
|
||||||
void SaveConfig(const std::string& configPath);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
#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++
|
|
||||||
|
|
@ -2,10 +2,8 @@ package io.github.chinosk.gakumas.localify
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.util.Log
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import io.github.chinosk.gakumas.localify.mainUtils.TextureResourceUpdater
|
|
||||||
import io.github.chinosk.gakumas.localify.mainUtils.json
|
import io.github.chinosk.gakumas.localify.mainUtils.json
|
||||||
import io.github.chinosk.gakumas.localify.models.GakumasConfig
|
import io.github.chinosk.gakumas.localify.models.GakumasConfig
|
||||||
import io.github.chinosk.gakumas.localify.models.ProgramConfig
|
import io.github.chinosk.gakumas.localify.models.ProgramConfig
|
||||||
|
|
@ -79,9 +77,6 @@ fun <T> T.loadConfig() where T : Activity, T : IHasConfigItems {
|
||||||
if (programConfig.useAPIAssetsURL.isEmpty()) {
|
if (programConfig.useAPIAssetsURL.isEmpty()) {
|
||||||
programConfig.useAPIAssetsURL = getString(R.string.default_assets_check_api)
|
programConfig.useAPIAssetsURL = getString(R.string.default_assets_check_api)
|
||||||
}
|
}
|
||||||
if (programConfig.useAPITextureAssetsURL.isEmpty()) {
|
|
||||||
programConfig.useAPITextureAssetsURL = getString(R.string.default_texture_assets_check_api)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T> T.onClickStartGame() where T : Activity, T : IHasConfigItems {
|
fun <T> T.onClickStartGame() where T : Activity, T : IHasConfigItems {
|
||||||
|
|
@ -110,7 +105,7 @@ fun <T> T.onClickStartGame() where T : Activity, T : IHasConfigItems {
|
||||||
putExtra(
|
putExtra(
|
||||||
"localData",
|
"localData",
|
||||||
getProgramConfigContent(listOf("transRemoteZipUrl", "useAPIAssetsURL",
|
getProgramConfigContent(listOf("transRemoteZipUrl", "useAPIAssetsURL",
|
||||||
"useAPITextureAssetsURL", "localAPIAssetsVersion", "p"), programConfig)
|
"localAPIAssetsVersion", "p"), programConfig)
|
||||||
)
|
)
|
||||||
putExtra("lVerName", version)
|
putExtra("lVerName", version)
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
|
@ -146,30 +141,5 @@ fun <T> T.onClickStartGame() where T : Activity, T : IHasConfigItems {
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
}
|
}
|
||||||
|
|
||||||
val textureUpdateFile = TextureResourceUpdater.getCachedZipFile(this)
|
|
||||||
Log.i(TAG, "Texture cache before launch: replaceTexture=${config.replaceTexture}, " +
|
|
||||||
"useAPITextureAssets=${programConfig.useAPITextureAssets}, " +
|
|
||||||
"exists=${textureUpdateFile.exists()}, size=${if (textureUpdateFile.exists()) textureUpdateFile.length() else 0}, " +
|
|
||||||
"path=${textureUpdateFile.absolutePath}")
|
|
||||||
if (config.replaceTexture && textureUpdateFile.exists()) {
|
|
||||||
val textureUri = FileProvider.getUriForFile(
|
|
||||||
this,
|
|
||||||
"io.github.chinosk.gakumas.localify.fileprovider",
|
|
||||||
textureUpdateFile
|
|
||||||
)
|
|
||||||
|
|
||||||
grantUriPermission(
|
|
||||||
"com.bandainamcoent.idolmaster_gakuen",
|
|
||||||
textureUri,
|
|
||||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
|
||||||
)
|
|
||||||
intent.putExtra("texture_resource_file", textureUri)
|
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
|
||||||
Log.i(TAG, "Texture resource uri attached: $textureUri")
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Log.i(TAG, "Texture resource uri not attached.")
|
|
||||||
}
|
|
||||||
|
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ interface ConfigListener {
|
||||||
fun onTextTestChanged(value: Boolean)
|
fun onTextTestChanged(value: Boolean)
|
||||||
fun onUseMasterTransChanged(value: Boolean)
|
fun onUseMasterTransChanged(value: Boolean)
|
||||||
fun onReplaceFontChanged(value: Boolean)
|
fun onReplaceFontChanged(value: Boolean)
|
||||||
fun onReplaceTextureChanged(value: Boolean)
|
|
||||||
fun onLazyInitChanged(value: Boolean)
|
fun onLazyInitChanged(value: Boolean)
|
||||||
fun onEnableFreeCameraChanged(value: Boolean)
|
fun onEnableFreeCameraChanged(value: Boolean)
|
||||||
fun onTargetFpsChanged(s: CharSequence, start: Int, before: Int, count: Int)
|
fun onTargetFpsChanged(s: CharSequence, start: Int, before: Int, count: Int)
|
||||||
|
|
@ -40,7 +39,6 @@ interface ConfigListener {
|
||||||
fun onLodQualityLevelChanged(s: CharSequence, start: Int, before: Int, count: Int)
|
fun onLodQualityLevelChanged(s: CharSequence, start: Int, before: Int, count: Int)
|
||||||
fun onGameOrientationChanged(checkedId: Int)
|
fun onGameOrientationChanged(checkedId: Int)
|
||||||
fun onDumpTextChanged(value: Boolean)
|
fun onDumpTextChanged(value: Boolean)
|
||||||
fun onDumpRuntimeTextureChanged(value: Boolean)
|
|
||||||
|
|
||||||
fun onEnableBreastParamChanged(value: Boolean)
|
fun onEnableBreastParamChanged(value: Boolean)
|
||||||
fun onBDampingChanged(s: CharSequence, start: Int, before: Int, count: Int)
|
fun onBDampingChanged(s: CharSequence, start: Int, before: Int, count: Int)
|
||||||
|
|
@ -71,15 +69,8 @@ interface ConfigListener {
|
||||||
localResourceVersionState: String? = null,
|
localResourceVersionState: String? = null,
|
||||||
errorString: String? = null,
|
errorString: String? = null,
|
||||||
localAPIResourceVersion: String? = null)
|
localAPIResourceVersion: String? = null)
|
||||||
fun mainPageTextureAssetsViewDataUpdate(downloadAbleState: Boolean? = null,
|
|
||||||
downloadProgressState: Float? = null,
|
|
||||||
localTextureResourceVersion: String? = null,
|
|
||||||
errorString: String? = null)
|
|
||||||
fun onPUseAPIAssetsChanged(value: Boolean)
|
fun onPUseAPIAssetsChanged(value: Boolean)
|
||||||
fun onPUseAPIAssetsURLChanged(s: CharSequence, start: Int, before: Int, count: Int)
|
fun onPUseAPIAssetsURLChanged(s: CharSequence, start: Int, before: Int, count: Int)
|
||||||
fun onPUseAPITextureAssetsChanged(value: Boolean)
|
|
||||||
fun onPUseAPITextureAssetsURLChanged(s: CharSequence, start: Int, before: Int, count: Int)
|
|
||||||
fun onPDelTextureRemoteAfterUpdateChanged(value: Boolean)
|
|
||||||
fun mainUIConfirmStatUpdate(isShow: Boolean? = null, title: String? = null,
|
fun mainUIConfirmStatUpdate(isShow: Boolean? = null, title: String? = null,
|
||||||
content: String? = null,
|
content: String? = null,
|
||||||
onConfirm: (() -> Unit)? = { mainUIConfirmStatUpdate(isShow = false) },
|
onConfirm: (() -> Unit)? = { mainUIConfirmStatUpdate(isShow = false) },
|
||||||
|
|
@ -138,12 +129,6 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
|
||||||
pushKeyEvent(KeyEvent(1145, 30))
|
pushKeyEvent(KeyEvent(1145, 30))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onReplaceTextureChanged(value: Boolean) {
|
|
||||||
config.replaceTexture = value
|
|
||||||
saveConfig()
|
|
||||||
pushKeyEvent(KeyEvent(1145, 30))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLazyInitChanged(value: Boolean) {
|
override fun onLazyInitChanged(value: Boolean) {
|
||||||
config.lazyInit = value
|
config.lazyInit = value
|
||||||
saveConfig()
|
saveConfig()
|
||||||
|
|
@ -164,11 +149,6 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
|
||||||
saveConfig()
|
saveConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDumpRuntimeTextureChanged(value: Boolean) {
|
|
||||||
config.dumpRuntimeTexture = value
|
|
||||||
saveConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onEnableFreeCameraChanged(value: Boolean) {
|
override fun onEnableFreeCameraChanged(value: Boolean) {
|
||||||
config.enableFreeCamera = value
|
config.enableFreeCamera = value
|
||||||
saveConfig()
|
saveConfig()
|
||||||
|
|
@ -596,14 +576,6 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
|
||||||
localAPIResourceVersion?.let{ programConfigViewModel.localAPIResourceVersionState.value = it }
|
localAPIResourceVersion?.let{ programConfigViewModel.localAPIResourceVersionState.value = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mainPageTextureAssetsViewDataUpdate(downloadAbleState: Boolean?, downloadProgressState: Float?,
|
|
||||||
localTextureResourceVersion: String?, errorString: String?) {
|
|
||||||
downloadAbleState?.let { programConfigViewModel.textureDownloadAbleState.value = it }
|
|
||||||
downloadProgressState?.let{ programConfigViewModel.textureDownloadProgressState.value = it }
|
|
||||||
localTextureResourceVersion?.let{ programConfigViewModel.localTextureResourceVersionState.value = it }
|
|
||||||
errorString?.let{ programConfigViewModel.textureErrorStringState.value = it }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPUseAPIAssetsChanged(value: Boolean) {
|
override fun onPUseAPIAssetsChanged(value: Boolean) {
|
||||||
programConfig.useAPIAssets = value
|
programConfig.useAPIAssets = value
|
||||||
if (value) {
|
if (value) {
|
||||||
|
|
@ -619,21 +591,6 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
|
||||||
saveProgramConfig()
|
saveProgramConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPUseAPITextureAssetsChanged(value: Boolean) {
|
|
||||||
programConfig.useAPITextureAssets = value
|
|
||||||
saveProgramConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPUseAPITextureAssetsURLChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
|
||||||
programConfig.useAPITextureAssetsURL = s.toString()
|
|
||||||
saveProgramConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPDelTextureRemoteAfterUpdateChanged(value: Boolean) {
|
|
||||||
programConfig.delTextureRemoteAfterUpdate = value
|
|
||||||
saveProgramConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun mainUIConfirmStatUpdate(isShow: Boolean?, title: String?, content: String?,
|
override fun mainUIConfirmStatUpdate(isShow: Boolean?, title: String?, content: String?,
|
||||||
onConfirm: (() -> Unit)?, onCancel: (() -> Unit)?
|
onConfirm: (() -> Unit)?, onCancel: (() -> Unit)?
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@ import java.util.Locale
|
||||||
import kotlin.system.measureTimeMillis
|
import kotlin.system.measureTimeMillis
|
||||||
import io.github.chinosk.gakumas.localify.hookUtils.FileHotUpdater
|
import io.github.chinosk.gakumas.localify.hookUtils.FileHotUpdater
|
||||||
import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker.localizationFilesDir
|
import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker.localizationFilesDir
|
||||||
import io.github.chinosk.gakumas.localify.mainUtils.TextureResourceUpdater
|
|
||||||
import io.github.chinosk.gakumas.localify.mainUtils.json
|
import io.github.chinosk.gakumas.localify.mainUtils.json
|
||||||
import io.github.chinosk.gakumas.localify.models.NativeInitProgress
|
import io.github.chinosk.gakumas.localify.models.NativeInitProgress
|
||||||
import io.github.chinosk.gakumas.localify.models.ProgramConfig
|
import io.github.chinosk.gakumas.localify.models.ProgramConfig
|
||||||
|
|
@ -54,7 +53,6 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
|
||||||
|
|
||||||
private var getConfigError: Exception? = null
|
private var getConfigError: Exception? = null
|
||||||
private var externalFilesChecked: Boolean = false
|
private var externalFilesChecked: Boolean = false
|
||||||
private var textureFilesChecked: Boolean = false
|
|
||||||
private var gameActivity: Activity? = null
|
private var gameActivity: Activity? = null
|
||||||
|
|
||||||
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
|
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
|
||||||
|
|
@ -313,25 +311,6 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (initConfig?.replaceTexture == true && !textureFilesChecked) {
|
|
||||||
val textureDataUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
||||||
intent.getParcelableExtra("texture_resource_file", Uri::class.java)
|
|
||||||
} else {
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
intent.getParcelableExtra<Uri>("texture_resource_file")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (textureDataUri != null) {
|
|
||||||
Log.i(TAG, "Texture resource uri received: $textureDataUri")
|
|
||||||
textureFilesChecked = true
|
|
||||||
TextureResourceUpdater.updateTextureFilesFromZip(activity, textureDataUri,
|
|
||||||
activity.filesDir, programConfig?.delTextureRemoteAfterUpdate ?: true)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Log.i(TAG, "Texture resource uri missing.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadConfig(gkmsData)
|
loadConfig(gkmsData)
|
||||||
Log.d(TAG, "gkmsData: $gkmsData")
|
Log.d(TAG, "gkmsData: $gkmsData")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker
|
||||||
import io.github.chinosk.gakumas.localify.hookUtils.MainKeyEventDispatcher
|
import io.github.chinosk.gakumas.localify.hookUtils.MainKeyEventDispatcher
|
||||||
import io.github.chinosk.gakumas.localify.mainUtils.RemoteAPIFilesChecker
|
import io.github.chinosk.gakumas.localify.mainUtils.RemoteAPIFilesChecker
|
||||||
import io.github.chinosk.gakumas.localify.mainUtils.ShizukuApi
|
import io.github.chinosk.gakumas.localify.mainUtils.ShizukuApi
|
||||||
import io.github.chinosk.gakumas.localify.mainUtils.TextureResourceUpdater
|
|
||||||
import io.github.chinosk.gakumas.localify.mainUtils.json
|
import io.github.chinosk.gakumas.localify.mainUtils.json
|
||||||
import io.github.chinosk.gakumas.localify.models.ConfirmStateModel
|
import io.github.chinosk.gakumas.localify.models.ConfirmStateModel
|
||||||
import io.github.chinosk.gakumas.localify.models.GakumasConfig
|
import io.github.chinosk.gakumas.localify.models.GakumasConfig
|
||||||
|
|
@ -80,7 +79,6 @@ class MainActivity : ComponentActivity(), ConfigUpdateListener, IConfigurableAct
|
||||||
fun getVersion(): List<String> {
|
fun getVersion(): List<String> {
|
||||||
var versionText = ""
|
var versionText = ""
|
||||||
var resVersionText = "unknown"
|
var resVersionText = "unknown"
|
||||||
var textureVersionText = "unknown"
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val stream = assets.open("${FilesChecker.localizationFilesDir}/version.txt")
|
val stream = assets.open("${FilesChecker.localizationFilesDir}/version.txt")
|
||||||
|
|
@ -89,7 +87,6 @@ class MainActivity : ComponentActivity(), ConfigUpdateListener, IConfigurableAct
|
||||||
if (programConfig.useAPIAssets) {
|
if (programConfig.useAPIAssets) {
|
||||||
RemoteAPIFilesChecker.getLocalVersion(this)?.let { resVersionText = it }
|
RemoteAPIFilesChecker.getLocalVersion(this)?.let { resVersionText = it }
|
||||||
}
|
}
|
||||||
TextureResourceUpdater.getLocalVersion(this)?.let { textureVersionText = it }
|
|
||||||
|
|
||||||
val packInfo = packageManager.getPackageInfo(packageName, 0)
|
val packInfo = packageManager.getPackageInfo(packageName, 0)
|
||||||
val version = packInfo.versionName
|
val version = packInfo.versionName
|
||||||
|
|
@ -98,7 +95,7 @@ class MainActivity : ComponentActivity(), ConfigUpdateListener, IConfigurableAct
|
||||||
}
|
}
|
||||||
catch (_: Exception) {}
|
catch (_: Exception) {}
|
||||||
|
|
||||||
return listOf(versionText, resVersionText, textureVersionText)
|
return listOf(versionText, resVersionText)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openUrl(url: String) {
|
fun openUrl(url: String) {
|
||||||
|
|
@ -133,8 +130,7 @@ class MainActivity : ComponentActivity(), ConfigUpdateListener, IConfigurableAct
|
||||||
viewModel = ViewModelProvider(this, factory)[UserConfigViewModel::class.java]
|
viewModel = ViewModelProvider(this, factory)[UserConfigViewModel::class.java]
|
||||||
|
|
||||||
programConfigFactory = ProgramConfigViewModelFactory(programConfig,
|
programConfigFactory = ProgramConfigViewModelFactory(programConfig,
|
||||||
FileHotUpdater.getZipResourceVersion(File(filesDir, "update_trans.zip").absolutePath).toString(),
|
FileHotUpdater.getZipResourceVersion(File(filesDir, "update_trans.zip").absolutePath).toString()
|
||||||
TextureResourceUpdater.getLocalVersion(this).toString()
|
|
||||||
)
|
)
|
||||||
programConfigViewModel = ViewModelProvider(this, programConfigFactory)[ProgramConfigViewModel::class.java]
|
programConfigViewModel = ViewModelProvider(this, programConfigFactory)[ProgramConfigViewModel::class.java]
|
||||||
|
|
||||||
|
|
@ -226,50 +222,6 @@ fun getProgramDownloadErrorStringState(context: MainActivity?): State<String> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun getProgramTextureDownloadState(context: MainActivity?): State<Float> {
|
|
||||||
return if (context != null) {
|
|
||||||
context.programConfigViewModel.textureDownloadProgress.collectAsState()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
val configMSF = MutableStateFlow(0f)
|
|
||||||
configMSF.asStateFlow().collectAsState()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun getProgramTextureDownloadAbleState(context: MainActivity?): State<Boolean> {
|
|
||||||
return if (context != null) {
|
|
||||||
context.programConfigViewModel.textureDownloadAble.collectAsState()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
val configMSF = MutableStateFlow(true)
|
|
||||||
configMSF.asStateFlow().collectAsState()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun getProgramLocalTextureResourceVersionState(context: MainActivity?): State<String> {
|
|
||||||
return if (context != null) {
|
|
||||||
context.programConfigViewModel.localTextureResourceVersion.collectAsState()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
val configMSF = MutableStateFlow("null")
|
|
||||||
configMSF.asStateFlow().collectAsState()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun getProgramTextureDownloadErrorStringState(context: MainActivity?): State<String> {
|
|
||||||
return if (context != null) {
|
|
||||||
context.programConfigViewModel.textureErrorString.collectAsState()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
val configMSF = MutableStateFlow("")
|
|
||||||
configMSF.asStateFlow().collectAsState()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun getMainUIConfirmState(context: MainActivity?, previewData: ConfirmStateModel? = null): State<ConfirmStateModel> {
|
fun getMainUIConfirmState(context: MainActivity?, previewData: ConfirmStateModel? = null): State<ConfirmStateModel> {
|
||||||
return if (context != null) {
|
return if (context != null) {
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,6 @@ object FilesChecker {
|
||||||
if (!pluginBasePath.exists()) {
|
if (!pluginBasePath.exists()) {
|
||||||
pluginBasePath.mkdirs()
|
pluginBasePath.mkdirs()
|
||||||
}
|
}
|
||||||
val skipBuiltInTexture2d = File(filesDir, "$localizationFilesDir/texture2d").exists()
|
|
||||||
|
|
||||||
val assets = XModuleResources.createInstance(modulePath, null).assets
|
val assets = XModuleResources.createInstance(modulePath, null).assets
|
||||||
fun forAllAssetFiles(
|
fun forAllAssetFiles(
|
||||||
|
|
@ -66,12 +65,6 @@ object FilesChecker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
forAllAssetFiles(localizationFilesDir) { path, file ->
|
forAllAssetFiles(localizationFilesDir) { path, file ->
|
||||||
if ((path == "$localizationFilesDir/texture2d" ||
|
|
||||||
path.startsWith("$localizationFilesDir/texture2d/")) &&
|
|
||||||
skipBuiltInTexture2d) {
|
|
||||||
return@forAllAssetFiles
|
|
||||||
}
|
|
||||||
|
|
||||||
val outFile = File(filesDir, path)
|
val outFile = File(filesDir, path)
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
outFile.mkdirs()
|
outFile.mkdirs()
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@ package io.github.chinosk.gakumas.localify.mainUtils
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
object FileDownloader {
|
object FileDownloader {
|
||||||
|
|
@ -113,99 +111,6 @@ object FileDownloader {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun downloadFileToPath(
|
|
||||||
url: String,
|
|
||||||
outputFile: File,
|
|
||||||
onDownload: (Float, downloaded: Long, size: Long) -> Unit,
|
|
||||||
onSuccess: (File) -> Unit,
|
|
||||||
onFailed: (Int, String) -> Unit,
|
|
||||||
checkContentTypes: List<String>? = null
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
if (call != null) {
|
|
||||||
onFailed(-1, "Another file is downloading.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
outputFile.parentFile?.mkdirs()
|
|
||||||
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url(url)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
call = client.newCall(request)
|
|
||||||
call?.enqueue(object : Callback {
|
|
||||||
override fun onFailure(call: Call, e: IOException) {
|
|
||||||
this@FileDownloader.call = null
|
|
||||||
if (call.isCanceled()) {
|
|
||||||
onFailed(-1, "Download canceled")
|
|
||||||
} else {
|
|
||||||
onFailed(-1, e.message ?: "Unknown error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResponse(call: Call, response: Response) {
|
|
||||||
if (!response.isSuccessful) {
|
|
||||||
this@FileDownloader.call = null
|
|
||||||
onFailed(response.code, response.message)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (checkContentTypes != null) {
|
|
||||||
val contentType = response.header("Content-Type")
|
|
||||||
if (!checkContentTypes.contains(contentType)) {
|
|
||||||
onFailed(-1, "Unexpected content type: $contentType")
|
|
||||||
this@FileDownloader.call = null
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
response.body?.let { responseBody ->
|
|
||||||
val contentLength = responseBody.contentLength()
|
|
||||||
val inputStream = responseBody.byteStream()
|
|
||||||
val buffer = ByteArray(8 * 1024)
|
|
||||||
var downloadedBytes = 0L
|
|
||||||
var read: Int
|
|
||||||
|
|
||||||
try {
|
|
||||||
FileOutputStream(outputFile).use { outputStream ->
|
|
||||||
while (inputStream.read(buffer).also { read = it } != -1) {
|
|
||||||
outputStream.write(buffer, 0, read)
|
|
||||||
downloadedBytes += read
|
|
||||||
val progress = if (contentLength < 0) {
|
|
||||||
0f
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
downloadedBytes.toFloat() / contentLength
|
|
||||||
}
|
|
||||||
onDownload(progress, downloadedBytes, contentLength)
|
|
||||||
}
|
|
||||||
outputStream.flush()
|
|
||||||
}
|
|
||||||
onSuccess(outputFile)
|
|
||||||
} catch (e: IOException) {
|
|
||||||
outputFile.delete()
|
|
||||||
if (call.isCanceled()) {
|
|
||||||
onFailed(-1, "Download canceled")
|
|
||||||
} else {
|
|
||||||
onFailed(-1, e.message ?: "Error reading stream")
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
this@FileDownloader.call = null
|
|
||||||
inputStream.close()
|
|
||||||
}
|
|
||||||
} ?: run {
|
|
||||||
this@FileDownloader.call = null
|
|
||||||
onFailed(-1, "Response body is null")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
catch (e: Exception) {
|
|
||||||
onFailed(-1, e.toString())
|
|
||||||
call = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun cancel() {
|
fun cancel() {
|
||||||
call?.cancel()
|
call?.cancel()
|
||||||
this@FileDownloader.call = null
|
this@FileDownloader.call = null
|
||||||
|
|
|
||||||
|
|
@ -1,322 +0,0 @@
|
||||||
package io.github.chinosk.gakumas.localify.mainUtils
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
|
||||||
import android.net.Uri
|
|
||||||
import android.util.Log
|
|
||||||
import io.github.chinosk.gakumas.localify.GakumasHookMain
|
|
||||||
import io.github.chinosk.gakumas.localify.TAG
|
|
||||||
import io.github.chinosk.gakumas.localify.models.Asset
|
|
||||||
import io.github.chinosk.gakumas.localify.models.GithubReleaseModel
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import okhttp3.Call
|
|
||||||
import okhttp3.Callback
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.Response
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.io.IOException
|
|
||||||
import java.util.zip.ZipFile
|
|
||||||
|
|
||||||
object TextureResourceUpdater {
|
|
||||||
private const val CACHE_DIR = "remote_texture_files"
|
|
||||||
private const val CACHE_ZIP_NAME = "texture.zip"
|
|
||||||
private const val CACHE_VERSION_NAME = "texture_version.txt"
|
|
||||||
private const val TEXTURE_DIR = "gakumas-local/texture2d"
|
|
||||||
private const val VERSION_FILE_NAME = "texture_version.txt"
|
|
||||||
|
|
||||||
private data class TextureZipRoot(val prefix: String, val containsTexture2dDir: Boolean)
|
|
||||||
|
|
||||||
private fun textureDir(context: Context): File {
|
|
||||||
return File(context.filesDir, TEXTURE_DIR)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getCachedZipFile(context: Context): File {
|
|
||||||
return File(context.filesDir, "$CACHE_DIR/$CACHE_ZIP_NAME")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getLocalVersion(context: Context): String? {
|
|
||||||
val versionFile = File(context.filesDir, "$CACHE_DIR/$CACHE_VERSION_NAME")
|
|
||||||
if (versionFile.exists()) {
|
|
||||||
versionFile.readText().trim().takeIf { it.isNotEmpty() }?.let { return it }
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getInstalledVersion(context: Context): String? {
|
|
||||||
val versionFile = File(textureDir(context), VERSION_FILE_NAME)
|
|
||||||
if (!versionFile.exists()) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return versionFile.readText().trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ensureCacheDir(context: Context): File {
|
|
||||||
val basePath = File(context.filesDir, CACHE_DIR)
|
|
||||||
if (!basePath.exists()) {
|
|
||||||
basePath.mkdirs()
|
|
||||||
}
|
|
||||||
return basePath
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun saveCachedVersion(context: Context, version: String) {
|
|
||||||
val versionFile = File(ensureCacheDir(context), CACHE_VERSION_NAME)
|
|
||||||
versionFile.writeText(version)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findTextureZipRoot(zipFile: ZipFile): TextureZipRoot? {
|
|
||||||
val entries = zipFile.entries()
|
|
||||||
while (entries.hasMoreElements()) {
|
|
||||||
val entry = entries.nextElement()
|
|
||||||
if (entry.isDirectory) continue
|
|
||||||
|
|
||||||
val name = entry.name.replace('\\', '/').trimStart('/')
|
|
||||||
val textureVersionMarker = "texture2d/$VERSION_FILE_NAME"
|
|
||||||
if (name.endsWith(textureVersionMarker)) {
|
|
||||||
return TextureZipRoot(name.substring(0, name.length - textureVersionMarker.length), true)
|
|
||||||
}
|
|
||||||
if (name == VERSION_FILE_NAME || name.endsWith("/$VERSION_FILE_NAME")) {
|
|
||||||
val prefix = name.substring(0, name.length - VERSION_FILE_NAME.length)
|
|
||||||
return TextureZipRoot(prefix, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun readTextureVersion(zipFile: ZipFile, root: TextureZipRoot): String? {
|
|
||||||
val versionPath = if (root.containsTexture2dDir) {
|
|
||||||
"${root.prefix}texture2d/$VERSION_FILE_NAME"
|
|
||||||
} else {
|
|
||||||
"${root.prefix}$VERSION_FILE_NAME"
|
|
||||||
}
|
|
||||||
val entry = zipFile.getEntry(versionPath) ?: return null
|
|
||||||
return zipFile.getInputStream(entry).bufferedReader().use { it.readText().trim() }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun validateTextureZip(zipFile: File): String {
|
|
||||||
ZipFile(zipFile).use { zip ->
|
|
||||||
val root = findTextureZipRoot(zip) ?: throw IOException("texture_version.txt not found in texture zip")
|
|
||||||
return readTextureVersion(zip, root) ?: throw IOException("texture_version.txt is empty")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findTextureZipAsset(releaseData: GithubReleaseModel): Asset? {
|
|
||||||
val zipAssets = releaseData.assets.filter { it.name.endsWith(".zip", ignoreCase = true) }
|
|
||||||
return zipAssets.firstOrNull { it.name.equals("texture2d.zip", ignoreCase = true) }
|
|
||||||
?: zipAssets.firstOrNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun safeOutputFile(baseDir: File, relativePath: String): File? {
|
|
||||||
val targetFile = File(baseDir, relativePath)
|
|
||||||
val baseCanonical = baseDir.canonicalFile
|
|
||||||
val targetCanonical = targetFile.canonicalFile
|
|
||||||
val basePath = baseCanonical.path + File.separator
|
|
||||||
if (targetCanonical.path != baseCanonical.path && !targetCanonical.path.startsWith(basePath)) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return targetFile
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun installTextureZip(context: Context, zipFile: File, version: String? = null) {
|
|
||||||
ZipFile(zipFile).use { zip ->
|
|
||||||
val root = findTextureZipRoot(zip) ?: throw IOException("texture_version.txt not found in texture zip")
|
|
||||||
val installedVersion = version ?: readTextureVersion(zip, root)
|
|
||||||
?: throw IOException("texture_version.txt is empty")
|
|
||||||
val targetDir = textureDir(context)
|
|
||||||
val tempDir = File(context.filesDir, "$TEXTURE_DIR.tmp")
|
|
||||||
|
|
||||||
if (tempDir.exists()) {
|
|
||||||
tempDir.deleteRecursively()
|
|
||||||
}
|
|
||||||
tempDir.mkdirs()
|
|
||||||
|
|
||||||
val entries = zip.entries()
|
|
||||||
while (entries.hasMoreElements()) {
|
|
||||||
val entry = entries.nextElement()
|
|
||||||
val name = entry.name.replace('\\', '/').trimStart('/')
|
|
||||||
|
|
||||||
val relativePath = if (root.containsTexture2dDir) {
|
|
||||||
val rootPath = "${root.prefix}texture2d/"
|
|
||||||
if (!name.startsWith(rootPath)) continue
|
|
||||||
name.substring(rootPath.length)
|
|
||||||
} else {
|
|
||||||
if (!name.startsWith(root.prefix)) continue
|
|
||||||
name.substring(root.prefix.length)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (relativePath.isEmpty()) continue
|
|
||||||
val targetFile = safeOutputFile(tempDir, relativePath) ?: continue
|
|
||||||
|
|
||||||
if (entry.isDirectory) {
|
|
||||||
targetFile.mkdirs()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
targetFile.parentFile?.mkdirs()
|
|
||||||
zip.getInputStream(entry).use { input ->
|
|
||||||
FileOutputStream(targetFile).use { output ->
|
|
||||||
input.copyTo(output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
File(tempDir, VERSION_FILE_NAME).writeText(installedVersion)
|
|
||||||
|
|
||||||
if (targetDir.exists()) {
|
|
||||||
targetDir.deleteRecursively()
|
|
||||||
}
|
|
||||||
if (!tempDir.renameTo(targetDir)) {
|
|
||||||
tempDir.copyRecursively(targetDir, overwrite = true)
|
|
||||||
tempDir.deleteRecursively()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateTextureFilesFromZip(activity: Activity, zipFileUri: Uri, filesDir: File,
|
|
||||||
deleteAfterUpdate: Boolean) {
|
|
||||||
try {
|
|
||||||
GakumasHookMain.showToast("Updating texture files from zip...")
|
|
||||||
|
|
||||||
val tempZipFile = File(filesDir, "texture_update.zip")
|
|
||||||
activity.contentResolver.openInputStream(zipFileUri).use { input ->
|
|
||||||
if (input == null) {
|
|
||||||
Log.e(TAG, "texture zip openInputStream failed.")
|
|
||||||
GakumasHookMain.showToast("Texture update file not found.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tempZipFile.outputStream().use { output ->
|
|
||||||
input.copyTo(output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
installTextureZip(activity, tempZipFile)
|
|
||||||
Log.i(TAG, "Texture zip installed into ${File(filesDir, TEXTURE_DIR).absolutePath}, " +
|
|
||||||
"version=${getInstalledVersion(activity)}")
|
|
||||||
tempZipFile.delete()
|
|
||||||
|
|
||||||
if (deleteAfterUpdate) {
|
|
||||||
activity.contentResolver.delete(zipFileUri, null, null)
|
|
||||||
}
|
|
||||||
GakumasHookMain.showToast("Texture update success.")
|
|
||||||
}
|
|
||||||
catch (e: java.io.FileNotFoundException) {
|
|
||||||
Log.i(TAG, "updateTextureFilesFromZip - file not found: $e")
|
|
||||||
GakumasHookMain.showToast("Texture update file not found.")
|
|
||||||
}
|
|
||||||
catch (e: Exception) {
|
|
||||||
Log.e(TAG, "updateTextureFilesFromZip failed: $e")
|
|
||||||
GakumasHookMain.showToast("Texture update failed: $e")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun checkUpdateTextureAssets(context: Context, apiURL: String,
|
|
||||||
onFailed: (Int, String) -> Unit,
|
|
||||||
onResult: (data: GithubReleaseModel, localVersion: String?) -> Unit) {
|
|
||||||
runCatching {
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url(apiURL)
|
|
||||||
.build()
|
|
||||||
FileDownloader.requestGet(request, object : Callback {
|
|
||||||
override fun onFailure(call: Call, e: IOException) {
|
|
||||||
onFailed(-1, e.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResponse(call: Call, response: Response) {
|
|
||||||
runCatching {
|
|
||||||
response.use {
|
|
||||||
if (!response.isSuccessful) throw IOException("Unexpected code $response")
|
|
||||||
|
|
||||||
val responseBody = response.body?.string()
|
|
||||||
if (responseBody != null) {
|
|
||||||
val json = Json { ignoreUnknownKeys = true }
|
|
||||||
val releaseData = json.decodeFromString<GithubReleaseModel>(responseBody)
|
|
||||||
onResult(releaseData, getLocalVersion(context))
|
|
||||||
} else {
|
|
||||||
onFailed(-1, "Response body is null")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.onFailure { e ->
|
|
||||||
Log.e(TAG, "checkUpdateTextureAssets failed", e)
|
|
||||||
onFailed(-1, e.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}.onFailure { e ->
|
|
||||||
Log.e(TAG, "checkUpdateTextureAssets failed", e)
|
|
||||||
onFailed(-1, e.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateTextureAssets(context: Context, apiURL: String,
|
|
||||||
deleteAfterUpdate: Boolean,
|
|
||||||
onDownload: (Float, downloaded: Long, size: Long) -> Unit,
|
|
||||||
onFailed: (Int, String) -> Unit,
|
|
||||||
onSuccess: (version: String, changed: Boolean) -> Unit) {
|
|
||||||
runCatching {
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url(apiURL)
|
|
||||||
.build()
|
|
||||||
FileDownloader.requestGet(request, object : Callback {
|
|
||||||
override fun onFailure(call: Call, e: IOException) {
|
|
||||||
onFailed(-1, e.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResponse(call: Call, response: Response) {
|
|
||||||
runCatching {
|
|
||||||
response.use {
|
|
||||||
if (!response.isSuccessful) throw IOException("Unexpected code $response")
|
|
||||||
|
|
||||||
val responseBody = response.body?.string()
|
|
||||||
if (responseBody != null) {
|
|
||||||
val json = Json { ignoreUnknownKeys = true }
|
|
||||||
val releaseData = json.decodeFromString<GithubReleaseModel>(responseBody)
|
|
||||||
val releaseVersion = releaseData.tag_name
|
|
||||||
val localVersion = getLocalVersion(context)
|
|
||||||
if (releaseVersion == localVersion) {
|
|
||||||
onSuccess(releaseVersion, false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val zipAsset = findTextureZipAsset(releaseData)
|
|
||||||
if (zipAsset == null) {
|
|
||||||
onFailed(-1, "No zip asset found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val cacheFile = getCachedZipFile(context)
|
|
||||||
FileDownloader.downloadFileToPath(zipAsset.browser_download_url,
|
|
||||||
cacheFile,
|
|
||||||
onDownload, {
|
|
||||||
runCatching {
|
|
||||||
saveCachedVersion(context, releaseVersion)
|
|
||||||
val zipVersion = validateTextureZip(cacheFile)
|
|
||||||
if (zipVersion != releaseVersion) {
|
|
||||||
cacheFile.delete()
|
|
||||||
File(context.filesDir, "$CACHE_DIR/$CACHE_VERSION_NAME").delete()
|
|
||||||
throw IOException("texture_version.txt ($zipVersion) differs from release tag ($releaseVersion)")
|
|
||||||
}
|
|
||||||
Log.i(TAG, "Texture zip cached: ${cacheFile.absolutePath}, " +
|
|
||||||
"size=${cacheFile.length()}, version=$releaseVersion")
|
|
||||||
onSuccess(releaseVersion, true)
|
|
||||||
}.onFailure { e ->
|
|
||||||
Log.e(TAG, "save texture zip failed", e)
|
|
||||||
onFailed(-1, e.toString())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onFailed)
|
|
||||||
} else {
|
|
||||||
onFailed(-1, "Response body is null")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.onFailure { e ->
|
|
||||||
Log.e(TAG, "updateTextureAssets failed", e)
|
|
||||||
onFailed(-1, e.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}.onFailure { e ->
|
|
||||||
Log.e(TAG, "updateTextureAssets failed", e)
|
|
||||||
onFailed(-1, e.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -8,11 +8,9 @@ data class GakumasConfig (
|
||||||
var enabled: Boolean = true,
|
var enabled: Boolean = true,
|
||||||
var lazyInit: Boolean = true,
|
var lazyInit: Boolean = true,
|
||||||
var replaceFont: Boolean = true,
|
var replaceFont: Boolean = true,
|
||||||
var replaceTexture: Boolean = true,
|
|
||||||
var textTest: Boolean = false,
|
var textTest: Boolean = false,
|
||||||
var useMasterTrans: Boolean = true,
|
var useMasterTrans: Boolean = true,
|
||||||
var dumpText: Boolean = false,
|
var dumpText: Boolean = false,
|
||||||
var dumpRuntimeTexture: Boolean = false,
|
|
||||||
var gameOrientation: Int = 0,
|
var gameOrientation: Int = 0,
|
||||||
var forceExportResource: Boolean = false,
|
var forceExportResource: Boolean = false,
|
||||||
var enableFreeCamera: Boolean = false,
|
var enableFreeCamera: Boolean = false,
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,6 @@ data class ProgramConfig(
|
||||||
var useAPIAssets: Boolean = false,
|
var useAPIAssets: Boolean = false,
|
||||||
var useAPIAssetsURL: String = "",
|
var useAPIAssetsURL: String = "",
|
||||||
var delRemoteAfterUpdate: Boolean = true,
|
var delRemoteAfterUpdate: Boolean = true,
|
||||||
var useAPITextureAssets: Boolean = false,
|
|
||||||
var useAPITextureAssetsURL: String = "",
|
|
||||||
var delTextureRemoteAfterUpdate: Boolean = true,
|
|
||||||
var cleanLocalAssets: Boolean = false,
|
var cleanLocalAssets: Boolean = false,
|
||||||
|
|
||||||
// var localAPIAssetsVersion: String = "",
|
// var localAPIAssetsVersion: String = "",
|
||||||
|
|
|
||||||
|
|
@ -43,12 +43,11 @@ class ResourceCollapsibleBoxViewModelFactory(private val initiallyExpanded: Bool
|
||||||
|
|
||||||
|
|
||||||
class ProgramConfigViewModelFactory(private val initialValue: ProgramConfig,
|
class ProgramConfigViewModelFactory(private val initialValue: ProgramConfig,
|
||||||
private val localResourceVersion: String,
|
private val localResourceVersion: String) : ViewModelProvider.Factory {
|
||||||
private val localTextureResourceVersion: String) : ViewModelProvider.Factory {
|
|
||||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
if (modelClass.isAssignableFrom(ProgramConfigViewModel::class.java)) {
|
if (modelClass.isAssignableFrom(ProgramConfigViewModel::class.java)) {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
return ProgramConfigViewModel(initialValue, localResourceVersion, localTextureResourceVersion) as T
|
return ProgramConfigViewModel(initialValue, localResourceVersion) as T
|
||||||
}
|
}
|
||||||
throw IllegalArgumentException("Unknown ViewModel class")
|
throw IllegalArgumentException("Unknown ViewModel class")
|
||||||
}
|
}
|
||||||
|
|
@ -63,8 +62,7 @@ data class ConfirmStateModel(
|
||||||
var p: Boolean = false
|
var p: Boolean = false
|
||||||
)
|
)
|
||||||
|
|
||||||
class ProgramConfigViewModel(initValue: ProgramConfig, initLocalResourceVersion: String,
|
class ProgramConfigViewModel(initValue: ProgramConfig, initLocalResourceVersion: String) : ViewModel() {
|
||||||
initLocalTextureResourceVersion: String) : ViewModel() {
|
|
||||||
val configState = MutableStateFlow(initValue)
|
val configState = MutableStateFlow(initValue)
|
||||||
val config: StateFlow<ProgramConfig> = configState.asStateFlow()
|
val config: StateFlow<ProgramConfig> = configState.asStateFlow()
|
||||||
|
|
||||||
|
|
@ -83,18 +81,6 @@ class ProgramConfigViewModel(initValue: ProgramConfig, initLocalResourceVersion:
|
||||||
val errorStringState = MutableStateFlow("")
|
val errorStringState = MutableStateFlow("")
|
||||||
val errorString: StateFlow<String> = errorStringState.asStateFlow()
|
val errorString: StateFlow<String> = errorStringState.asStateFlow()
|
||||||
|
|
||||||
val textureDownloadProgressState = MutableStateFlow(-1f)
|
|
||||||
val textureDownloadProgress: StateFlow<Float> = textureDownloadProgressState.asStateFlow()
|
|
||||||
|
|
||||||
val textureDownloadAbleState = MutableStateFlow(true)
|
|
||||||
val textureDownloadAble: StateFlow<Boolean> = textureDownloadAbleState.asStateFlow()
|
|
||||||
|
|
||||||
val localTextureResourceVersionState = MutableStateFlow(initLocalTextureResourceVersion)
|
|
||||||
val localTextureResourceVersion: StateFlow<String> = localTextureResourceVersionState.asStateFlow()
|
|
||||||
|
|
||||||
val textureErrorStringState = MutableStateFlow("")
|
|
||||||
val textureErrorString: StateFlow<String> = textureErrorStringState.asStateFlow()
|
|
||||||
|
|
||||||
val mainUIConfirmState = MutableStateFlow(ConfirmStateModel())
|
val mainUIConfirmState = MutableStateFlow(ConfirmStateModel())
|
||||||
val mainUIConfirm: StateFlow<ConfirmStateModel> = mainUIConfirmState.asStateFlow()
|
val mainUIConfirm: StateFlow<ConfirmStateModel> = mainUIConfirmState.asStateFlow()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,14 +49,14 @@ fun MainUI(modifier: Modifier = Modifier, context: MainActivity? = null,
|
||||||
previewData: GakumasConfig? = null) {
|
previewData: GakumasConfig? = null) {
|
||||||
val imagePainter = painterResource(R.drawable.bg_pattern)
|
val imagePainter = painterResource(R.drawable.bg_pattern)
|
||||||
var versionInfo by remember {
|
var versionInfo by remember {
|
||||||
mutableStateOf(context?.getVersion() ?: listOf("", "Unknown", "Unknown"))
|
mutableStateOf(context?.getVersion() ?: listOf("", "Unknown"))
|
||||||
}
|
}
|
||||||
// val config = getConfigState(context, previewData)
|
// val config = getConfigState(context, previewData)
|
||||||
val confirmState by getMainUIConfirmState(context, null)
|
val confirmState by getMainUIConfirmState(context, null)
|
||||||
val programConfig by getProgramConfigState(context)
|
val programConfig by getProgramConfigState(context)
|
||||||
|
|
||||||
LaunchedEffect(programConfig) {
|
LaunchedEffect(programConfig) {
|
||||||
versionInfo = context?.getVersion() ?: listOf("", "Unknown", "Unknown")
|
versionInfo = context?.getVersion() ?: listOf("", "Unknown")
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
|
|
@ -79,7 +79,6 @@ fun MainUI(modifier: Modifier = Modifier, context: MainActivity? = null,
|
||||||
) {
|
) {
|
||||||
Text(text = "Gakumas Localify ${versionInfo[0]}", fontSize = 18.sp)
|
Text(text = "Gakumas Localify ${versionInfo[0]}", fontSize = 18.sp)
|
||||||
Text(text = "Assets version: ${versionInfo[1]}", fontSize = 13.sp)
|
Text(text = "Assets version: ${versionInfo[1]}", fontSize = 13.sp)
|
||||||
Text(text = "Texture version: ${versionInfo[2]}", fontSize = 13.sp)
|
|
||||||
|
|
||||||
SettingsTabs(modifier, listOf(stringResource(R.string.about), stringResource(R.string.home),
|
SettingsTabs(modifier, listOf(stringResource(R.string.about), stringResource(R.string.home),
|
||||||
stringResource(R.string.advanced_settings)),
|
stringResource(R.string.advanced_settings)),
|
||||||
|
|
|
||||||
|
|
@ -87,10 +87,6 @@ fun AdvanceSettingsPage(modifier: Modifier = Modifier,
|
||||||
v -> context?.onDumpTextChanged(v)
|
v -> context?.onDumpTextChanged(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
GakuSwitch(modifier, stringResource(R.string.dump_runtime_texture), checked = config.value.dumpRuntimeTexture) {
|
|
||||||
v -> context?.onDumpRuntimeTextureChanged(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
GakuSwitch(modifier, stringResource(R.string.force_export_resource), checked = config.value.forceExportResource) {
|
GakuSwitch(modifier, stringResource(R.string.force_export_resource), checked = config.value.forceExportResource) {
|
||||||
v -> context?.onForceExportResourceChanged(v)
|
v -> context?.onForceExportResourceChanged(v)
|
||||||
}
|
}
|
||||||
|
|
@ -369,8 +365,6 @@ fun AdvanceSettingsPage(modifier: Modifier = Modifier,
|
||||||
checked = config.value.unlockAllLiveCostume) {
|
checked = config.value.unlockAllLiveCostume) {
|
||||||
v -> context?.onUnlockAllLiveCostumeChanged(v)
|
v -> context?.onUnlockAllLiveCostumeChanged(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
HorizontalDivider(
|
HorizontalDivider(
|
||||||
thickness = 1.dp,
|
thickness = 1.dp,
|
||||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
|
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
|
||||||
|
|
@ -395,7 +389,7 @@ fun AdvanceSettingsPage(modifier: Modifier = Modifier,
|
||||||
value = config.value.liveCustomeCostumeId,
|
value = config.value.liveCustomeCostumeId,
|
||||||
onValueChange = { c -> context?.onLiveCustomeCostumeIdChanged(c, 0, 0, 0)},
|
onValueChange = { c -> context?.onLiveCustomeCostumeIdChanged(c, 0, 0, 0)},
|
||||||
label = { Text(stringResource(R.string.live_custome_dress_id)) }
|
label = { Text(stringResource(R.string.live_custome_dress_id)) }
|
||||||
)*/
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,16 +43,11 @@ import io.github.chinosk.gakumas.localify.getProgramConfigState
|
||||||
import io.github.chinosk.gakumas.localify.getProgramDownloadAbleState
|
import io.github.chinosk.gakumas.localify.getProgramDownloadAbleState
|
||||||
import io.github.chinosk.gakumas.localify.getProgramDownloadErrorStringState
|
import io.github.chinosk.gakumas.localify.getProgramDownloadErrorStringState
|
||||||
import io.github.chinosk.gakumas.localify.getProgramDownloadState
|
import io.github.chinosk.gakumas.localify.getProgramDownloadState
|
||||||
import io.github.chinosk.gakumas.localify.getProgramLocalTextureResourceVersionState
|
|
||||||
import io.github.chinosk.gakumas.localify.getProgramLocalResourceVersionState
|
import io.github.chinosk.gakumas.localify.getProgramLocalResourceVersionState
|
||||||
import io.github.chinosk.gakumas.localify.getProgramLocalAPIResourceVersionState
|
import io.github.chinosk.gakumas.localify.getProgramLocalAPIResourceVersionState
|
||||||
import io.github.chinosk.gakumas.localify.getProgramTextureDownloadAbleState
|
|
||||||
import io.github.chinosk.gakumas.localify.getProgramTextureDownloadErrorStringState
|
|
||||||
import io.github.chinosk.gakumas.localify.getProgramTextureDownloadState
|
|
||||||
import io.github.chinosk.gakumas.localify.hookUtils.FileHotUpdater
|
import io.github.chinosk.gakumas.localify.hookUtils.FileHotUpdater
|
||||||
import io.github.chinosk.gakumas.localify.mainUtils.FileDownloader
|
import io.github.chinosk.gakumas.localify.mainUtils.FileDownloader
|
||||||
import io.github.chinosk.gakumas.localify.mainUtils.RemoteAPIFilesChecker
|
import io.github.chinosk.gakumas.localify.mainUtils.RemoteAPIFilesChecker
|
||||||
import io.github.chinosk.gakumas.localify.mainUtils.TextureResourceUpdater
|
|
||||||
import io.github.chinosk.gakumas.localify.mainUtils.TimeUtils
|
import io.github.chinosk.gakumas.localify.mainUtils.TimeUtils
|
||||||
import io.github.chinosk.gakumas.localify.models.GakumasConfig
|
import io.github.chinosk.gakumas.localify.models.GakumasConfig
|
||||||
import io.github.chinosk.gakumas.localify.models.ResourceCollapsibleBoxViewModel
|
import io.github.chinosk.gakumas.localify.models.ResourceCollapsibleBoxViewModel
|
||||||
|
|
@ -80,10 +75,6 @@ fun HomePage(modifier: Modifier = Modifier,
|
||||||
val localResourceVersion by getProgramLocalResourceVersionState(context)
|
val localResourceVersion by getProgramLocalResourceVersionState(context)
|
||||||
val localAPIResourceVersion by getProgramLocalAPIResourceVersionState(context)
|
val localAPIResourceVersion by getProgramLocalAPIResourceVersionState(context)
|
||||||
val downloadErrorString by getProgramDownloadErrorStringState(context)
|
val downloadErrorString by getProgramDownloadErrorStringState(context)
|
||||||
val textureDownloadProgress by getProgramTextureDownloadState(context)
|
|
||||||
val textureDownloadAble by getProgramTextureDownloadAbleState(context)
|
|
||||||
val localTextureResourceVersion by getProgramLocalTextureResourceVersionState(context)
|
|
||||||
val textureDownloadErrorString by getProgramTextureDownloadErrorStringState(context)
|
|
||||||
var isFirstTimeInThisPage by rememberSaveable { mutableStateOf(true) }
|
var isFirstTimeInThisPage by rememberSaveable { mutableStateOf(true) }
|
||||||
|
|
||||||
// val scrollState = rememberScrollState()
|
// val scrollState = rememberScrollState()
|
||||||
|
|
@ -140,8 +131,7 @@ fun HomePage(modifier: Modifier = Modifier,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onClickDownload(isZipResource: Boolean, isHumanClick: Boolean = true,
|
fun onClickDownload(isZipResource: Boolean, isHumanClick: Boolean = true) {
|
||||||
onFinished: (() -> Unit)? = null) {
|
|
||||||
context?.mainPageAssetsViewDataUpdate(
|
context?.mainPageAssetsViewDataUpdate(
|
||||||
downloadAbleState = false,
|
downloadAbleState = false,
|
||||||
errorString = "",
|
errorString = "",
|
||||||
|
|
@ -149,7 +139,6 @@ fun HomePage(modifier: Modifier = Modifier,
|
||||||
)
|
)
|
||||||
if (isZipResource) {
|
if (isZipResource) {
|
||||||
zipResourceDownload()
|
zipResourceDownload()
|
||||||
onFinished?.invoke()
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
RemoteAPIFilesChecker.checkUpdateLocalAssets(context!!,
|
RemoteAPIFilesChecker.checkUpdateLocalAssets(context!!,
|
||||||
|
|
@ -161,7 +150,6 @@ fun HomePage(modifier: Modifier = Modifier,
|
||||||
downloadProgressState = -1f
|
downloadProgressState = -1f
|
||||||
)
|
)
|
||||||
context.mainUIConfirmStatUpdate(true, "Error", reason)
|
context.mainUIConfirmStatUpdate(true, "Error", reason)
|
||||||
onFinished?.invoke()
|
|
||||||
},
|
},
|
||||||
onResult = { data, localVersion ->
|
onResult = { data, localVersion ->
|
||||||
if (!isHumanClick) {
|
if (!isHumanClick) {
|
||||||
|
|
@ -171,7 +159,6 @@ fun HomePage(modifier: Modifier = Modifier,
|
||||||
errorString = "",
|
errorString = "",
|
||||||
downloadProgressState = -1f
|
downloadProgressState = -1f
|
||||||
)
|
)
|
||||||
onFinished?.invoke()
|
|
||||||
return@checkUpdateLocalAssets
|
return@checkUpdateLocalAssets
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -183,13 +170,10 @@ fun HomePage(modifier: Modifier = Modifier,
|
||||||
onDownload = { progress, _, _ ->
|
onDownload = { progress, _, _ ->
|
||||||
context.mainPageAssetsViewDataUpdate(downloadProgressState = progress)
|
context.mainPageAssetsViewDataUpdate(downloadProgressState = progress)
|
||||||
},
|
},
|
||||||
onFailed = { _, reason ->
|
onFailed = { _, reason -> context.mainPageAssetsViewDataUpdate(
|
||||||
context.mainPageAssetsViewDataUpdate(
|
|
||||||
downloadAbleState = true,
|
downloadAbleState = true,
|
||||||
errorString = reason,
|
errorString = reason,
|
||||||
)
|
)},
|
||||||
onFinished?.invoke()
|
|
||||||
},
|
|
||||||
onSuccess = { saveFile, releaseVersion ->
|
onSuccess = { saveFile, releaseVersion ->
|
||||||
context.mainPageAssetsViewDataUpdate(
|
context.mainPageAssetsViewDataUpdate(
|
||||||
downloadAbleState = true,
|
downloadAbleState = true,
|
||||||
|
|
@ -201,7 +185,6 @@ fun HomePage(modifier: Modifier = Modifier,
|
||||||
)
|
)
|
||||||
context.saveProgramConfig()
|
context.saveProgramConfig()
|
||||||
Log.d(TAG, "saved: $releaseVersion $saveFile")
|
Log.d(TAG, "saved: $releaseVersion $saveFile")
|
||||||
onFinished?.invoke()
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onCancel = {
|
onCancel = {
|
||||||
|
|
@ -210,92 +193,12 @@ fun HomePage(modifier: Modifier = Modifier,
|
||||||
errorString = "",
|
errorString = "",
|
||||||
downloadProgressState = -1f
|
downloadProgressState = -1f
|
||||||
)
|
)
|
||||||
onFinished?.invoke()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startTextureResourceUpdate() {
|
|
||||||
context?.mainPageTextureAssetsViewDataUpdate(
|
|
||||||
downloadAbleState = false,
|
|
||||||
errorString = "",
|
|
||||||
downloadProgressState = -1f
|
|
||||||
)
|
|
||||||
TextureResourceUpdater.updateTextureAssets(context!!,
|
|
||||||
programConfig.value.useAPITextureAssetsURL,
|
|
||||||
programConfig.value.delTextureRemoteAfterUpdate,
|
|
||||||
onDownload = { progress, _, _ ->
|
|
||||||
context.mainPageTextureAssetsViewDataUpdate(downloadProgressState = progress)
|
|
||||||
},
|
|
||||||
onFailed = { _, reason ->
|
|
||||||
context.mainPageTextureAssetsViewDataUpdate(
|
|
||||||
downloadAbleState = true,
|
|
||||||
errorString = reason,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onSuccess = { releaseVersion, changed ->
|
|
||||||
context.mainPageTextureAssetsViewDataUpdate(
|
|
||||||
downloadAbleState = true,
|
|
||||||
errorString = "",
|
|
||||||
downloadProgressState = -1f,
|
|
||||||
localTextureResourceVersion = TextureResourceUpdater.getLocalVersion(context)
|
|
||||||
?: releaseVersion
|
|
||||||
)
|
|
||||||
context.saveProgramConfig()
|
|
||||||
Log.d(TAG, "texture resource update finished: $releaseVersion changed=$changed")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onClickTextureDownload(isHumanClick: Boolean = true) {
|
|
||||||
context?.mainPageTextureAssetsViewDataUpdate(
|
|
||||||
downloadAbleState = false,
|
|
||||||
errorString = "",
|
|
||||||
downloadProgressState = -1f
|
|
||||||
)
|
|
||||||
TextureResourceUpdater.checkUpdateTextureAssets(context!!,
|
|
||||||
programConfig.value.useAPITextureAssetsURL,
|
|
||||||
onFailed = { _, reason ->
|
|
||||||
context.mainPageTextureAssetsViewDataUpdate(
|
|
||||||
downloadAbleState = true,
|
|
||||||
errorString = reason,
|
|
||||||
downloadProgressState = -1f
|
|
||||||
)
|
|
||||||
if (isHumanClick) {
|
|
||||||
context.mainUIConfirmStatUpdate(true, "Error", reason)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onResult = { data, localVersion ->
|
|
||||||
if (!isHumanClick) {
|
|
||||||
if (data.tag_name == localVersion) {
|
|
||||||
context.mainPageTextureAssetsViewDataUpdate(
|
|
||||||
downloadAbleState = true,
|
|
||||||
errorString = "",
|
|
||||||
downloadProgressState = -1f,
|
|
||||||
localTextureResourceVersion = localVersion
|
|
||||||
)
|
|
||||||
return@checkUpdateTextureAssets
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context.mainUIConfirmStatUpdate(true, context.getString(R.string.texture_resource_update),
|
|
||||||
"${data.name}\n$localVersion -> ${data.tag_name}\n${data.body}\n\n${TimeUtils.convertIsoToLocalTime(data.published_at)}",
|
|
||||||
onConfirm = {
|
|
||||||
resourceSettingsViewModel.expanded = true
|
|
||||||
startTextureResourceUpdate()
|
|
||||||
},
|
|
||||||
onCancel = {
|
|
||||||
context.mainPageTextureAssetsViewDataUpdate(
|
|
||||||
downloadAbleState = true,
|
|
||||||
errorString = "",
|
|
||||||
downloadProgressState = -1f
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
try {
|
try {
|
||||||
if (context == null) return@LaunchedEffect
|
if (context == null) return@LaunchedEffect
|
||||||
|
|
@ -303,25 +206,9 @@ fun HomePage(modifier: Modifier = Modifier,
|
||||||
context.mainPageAssetsViewDataUpdate(
|
context.mainPageAssetsViewDataUpdate(
|
||||||
localAPIResourceVersion = localAPIResVer
|
localAPIResourceVersion = localAPIResVer
|
||||||
)
|
)
|
||||||
context.mainPageTextureAssetsViewDataUpdate(
|
|
||||||
localTextureResourceVersion = TextureResourceUpdater.getLocalVersion(context)
|
|
||||||
)
|
|
||||||
if (isFirstTimeInThisPage) {
|
if (isFirstTimeInThisPage) {
|
||||||
val shouldCheckResource =
|
if (programConfig.value.useAPIAssets && programConfig.value.useAPIAssetsURL.isNotEmpty()) {
|
||||||
programConfig.value.useAPIAssets && programConfig.value.useAPIAssetsURL.isNotEmpty()
|
onClickDownload(false, false)
|
||||||
val shouldCheckTexture = config.value.replaceTexture &&
|
|
||||||
programConfig.value.useAPITextureAssets &&
|
|
||||||
programConfig.value.useAPITextureAssetsURL.isNotEmpty()
|
|
||||||
|
|
||||||
if (shouldCheckResource) {
|
|
||||||
onClickDownload(false, false) {
|
|
||||||
if (shouldCheckTexture) {
|
|
||||||
onClickTextureDownload(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (shouldCheckTexture) {
|
|
||||||
onClickTextureDownload(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -353,10 +240,6 @@ fun HomePage(modifier: Modifier = Modifier,
|
||||||
v -> context?.onReplaceFontChanged(v)
|
v -> context?.onReplaceFontChanged(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
GakuSwitch(modifier, stringResource(R.string.replace_texture), checked = config.value.replaceTexture) {
|
|
||||||
v -> context?.onReplaceTextureChanged(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(Modifier.height(6.dp))
|
Spacer(Modifier.height(6.dp))
|
||||||
|
|
@ -431,8 +314,8 @@ fun HomePage(modifier: Modifier = Modifier,
|
||||||
fontSize = 14f,
|
fontSize = 14f,
|
||||||
value = programConfig.value.useAPIAssetsURL,
|
value = programConfig.value.useAPIAssetsURL,
|
||||||
onValueChange = { c -> context?.onPUseAPIAssetsURLChanged(c, 0, 0, 0)},
|
onValueChange = { c -> context?.onPUseAPIAssetsURLChanged(c, 0, 0, 0)},
|
||||||
label = { Text(stringResource(R.string.api_addr)) }
|
label = { Text(stringResource(R.string.api_addr)) },
|
||||||
)
|
keyboardOptions = keyboardOptionsNumber)
|
||||||
|
|
||||||
if (downloadAble) {
|
if (downloadAble) {
|
||||||
GakuButton(modifier = modifier
|
GakuButton(modifier = modifier
|
||||||
|
|
@ -528,8 +411,8 @@ fun HomePage(modifier: Modifier = Modifier,
|
||||||
fontSize = 14f,
|
fontSize = 14f,
|
||||||
value = programConfig.value.transRemoteZipUrl,
|
value = programConfig.value.transRemoteZipUrl,
|
||||||
onValueChange = { c -> context?.onPTransRemoteZipUrlChanged(c, 0, 0, 0)},
|
onValueChange = { c -> context?.onPTransRemoteZipUrlChanged(c, 0, 0, 0)},
|
||||||
label = { Text(stringResource(id = R.string.resource_url)) }
|
label = { Text(stringResource(id = R.string.resource_url)) },
|
||||||
)
|
keyboardOptions = keyboardOptionsNumber)
|
||||||
|
|
||||||
if (downloadAble) {
|
if (downloadAble) {
|
||||||
GakuButton(modifier = modifier
|
GakuButton(modifier = modifier
|
||||||
|
|
@ -584,104 +467,6 @@ fun HomePage(modifier: Modifier = Modifier,
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.value.replaceTexture) {
|
|
||||||
item {
|
|
||||||
HorizontalDivider(
|
|
||||||
thickness = 1.dp,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
item {
|
|
||||||
GakuSwitch(modifier = modifier.padding(start = 8.dp, end = 8.dp),
|
|
||||||
checked = programConfig.value.useAPITextureAssets,
|
|
||||||
text = stringResource(R.string.check_texture_resource_from_api)
|
|
||||||
) { v -> context?.onPUseAPITextureAssetsChanged(v) }
|
|
||||||
|
|
||||||
CollapsibleBox(modifier = modifier.graphicsLayer(clip = false),
|
|
||||||
expandState = programConfig.value.useAPITextureAssets,
|
|
||||||
collapsedHeight = 0.dp,
|
|
||||||
innerPaddingLeftRight = 8.dp,
|
|
||||||
showExpand = false
|
|
||||||
) {
|
|
||||||
GakuSwitch(modifier = modifier,
|
|
||||||
checked = programConfig.value.delTextureRemoteAfterUpdate,
|
|
||||||
text = stringResource(id = R.string.del_remote_after_update)
|
|
||||||
) { v -> context?.onPDelTextureRemoteAfterUpdateChanged(v) }
|
|
||||||
|
|
||||||
LazyColumn(modifier = modifier
|
|
||||||
.sizeIn(maxHeight = screenH),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
|
||||||
) {
|
|
||||||
item {
|
|
||||||
Row(modifier = modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically) {
|
|
||||||
|
|
||||||
GakuTextInput(modifier = modifier
|
|
||||||
.height(45.dp)
|
|
||||||
.padding(end = 8.dp)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.weight(1f),
|
|
||||||
fontSize = 14f,
|
|
||||||
value = programConfig.value.useAPITextureAssetsURL,
|
|
||||||
onValueChange = { c -> context?.onPUseAPITextureAssetsURLChanged(c, 0, 0, 0)},
|
|
||||||
label = { Text(stringResource(R.string.texture_api_addr)) }
|
|
||||||
)
|
|
||||||
|
|
||||||
if (textureDownloadAble) {
|
|
||||||
GakuButton(modifier = modifier
|
|
||||||
.height(40.dp)
|
|
||||||
.sizeIn(minWidth = 80.dp),
|
|
||||||
text = stringResource(R.string.check_update),
|
|
||||||
onClick = { onClickTextureDownload(true) })
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
GakuButton(modifier = modifier
|
|
||||||
.height(40.dp)
|
|
||||||
.sizeIn(minWidth = 80.dp),
|
|
||||||
text = stringResource(id = R.string.cancel), onClick = {
|
|
||||||
FileDownloader.cancel()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (textureDownloadProgress >= 0) {
|
|
||||||
item {
|
|
||||||
GakuProgressBar(progress = textureDownloadProgress,
|
|
||||||
isError = textureDownloadErrorString.isNotEmpty())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (textureDownloadErrorString.isNotEmpty()) {
|
|
||||||
item {
|
|
||||||
Text(text = textureDownloadErrorString, color = Color(0xFFE2041B))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
item {
|
|
||||||
Text(modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clickable {
|
|
||||||
context?.let {
|
|
||||||
it.mainPageTextureAssetsViewDataUpdate(
|
|
||||||
localTextureResourceVersion = TextureResourceUpdater
|
|
||||||
.getLocalVersion(it)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}, text = "${stringResource(R.string.downloaded_texture_resource_version)}: $localTextureResourceVersion")
|
|
||||||
}
|
|
||||||
|
|
||||||
item {
|
|
||||||
Spacer(Modifier.height(0.dp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
<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">情報</string>
|
||||||
<string name="about_about_p1">このプラグインは完全に無料で提供されます。このプラグインで料金を支払ってしまった場合は、販売者に報告をしてください。</string>
|
<string name="about_about_p1">このプラグインは完全に無料で提供されます。このプラグインで料金を支払ってしまった場合は、販売者に報告をしてください。</string>
|
||||||
<string name="about_about_p2">プラグインの QQ グループ: 975854705</string>
|
<string name="about_about_p2">プラグインの QQ グループ: 975854705</string>
|
||||||
|
|
@ -36,10 +8,8 @@
|
||||||
<string name="about_warn_p2">外部プラグインは関連する TOS に違反するため、自己責任でご使用ください。</string>
|
<string name="about_warn_p2">外部プラグインは関連する TOS に違反するため、自己責任でご使用ください。</string>
|
||||||
<string name="about_warn_title">警告</string>
|
<string name="about_warn_title">警告</string>
|
||||||
<string name="advanced_settings">高度な設定</string>
|
<string name="advanced_settings">高度な設定</string>
|
||||||
<string name="androidx_startup">androidx.startup</string>
|
<string name="api_addr">APIアドレス (GitHub の最新リリース API)</string>
|
||||||
<string name="api_addr">API アドレス (GitHub 最新リリース API)</string>
|
|
||||||
<string name="app_name">Gakumas Localify</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="average">平均</string>
|
||||||
<string name="axisx_x">X 軸.x</string>
|
<string name="axisx_x">X 軸.x</string>
|
||||||
<string name="axisx_y">X 軸.y</string>
|
<string name="axisx_y">X 軸.y</string>
|
||||||
|
|
@ -48,28 +18,15 @@
|
||||||
<string name="axisz_x">Z 軸.x</string>
|
<string name="axisz_x">Z 軸.x</string>
|
||||||
<string name="axisz_y">Z 軸.y</string>
|
<string name="axisz_y">Z 軸.y</string>
|
||||||
<string name="basic_settings">基本設定</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_param">胸のパラメーター</string>
|
||||||
<string name="breast_scale">胸の大きさ</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="camera_settings">カメラ設定</string>
|
||||||
<string name="cancel">キャンセル</string>
|
<string name="cancel">キャンセル</string>
|
||||||
<string name="character_counter_content_description">%1$d の %2$d に入力された文字</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_overflowed_content_description">文字制限が %2$d 文字中、 %1$d 文字を超えています</string>
|
||||||
<string name="character_counter_pattern">%1$d/%2$d</string>
|
<string name="character_counter_pattern">%1$d/%2$d</string>
|
||||||
<string name="check_built_in_resource">内蔵アセットの更新を確認</string>
|
<string name="check_built_in_resource">内蔵アセットのアップデートを確認</string>
|
||||||
<string name="check_resource_from_api">リソースの更新を API から確認</string>
|
<string name="check_resource_from_api">リソースアップデートを API から確認</string>
|
||||||
<string name="check_update">確認</string>
|
<string name="check_update">確認</string>
|
||||||
<string name="clear_text_end_icon_content_description">テキストを消去</string>
|
<string name="clear_text_end_icon_content_description">テキストを消去</string>
|
||||||
<string name="close_drawer">ナビゲーションメニューを閉じる</string>
|
<string name="close_drawer">ナビゲーションメニューを閉じる</string>
|
||||||
|
|
@ -80,7 +37,7 @@
|
||||||
<string name="default_assets_check_api">https://api.github.com/repos/NatsumeLS/Gakumas-Translation-Data-EN/releases/latest</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_error_message">入力が無効です</string>
|
||||||
<string name="default_popup_window_title">ポップアップウィンドウ</string>
|
<string name="default_popup_window_title">ポップアップウィンドウ</string>
|
||||||
<string name="del_remote_after_update">キャッシュファイルを更新後に削除</string>
|
<string name="del_remote_after_update">キャッシュファイルをアップデート後に削除</string>
|
||||||
<string name="delete_plugin_resource">プラグインリソースを削除</string>
|
<string name="delete_plugin_resource">プラグインリソースを削除</string>
|
||||||
<string name="download">ダウンロード</string>
|
<string name="download">ダウンロード</string>
|
||||||
<string name="downloaded_resource_version">ダウンロードされたバージョン</string>
|
<string name="downloaded_resource_version">ダウンロードされたバージョン</string>
|
||||||
|
|
@ -91,21 +48,18 @@
|
||||||
<string name="error_a11y_label">エラー: 無効</string>
|
<string name="error_a11y_label">エラー: 無効</string>
|
||||||
<string name="error_icon_content_description">エラー</string>
|
<string name="error_icon_content_description">エラー</string>
|
||||||
<string name="export_text">テキストをエクスポート</string>
|
<string name="export_text">テキストをエクスポート</string>
|
||||||
<string name="dump_runtime_texture">実行時テクスチャをダンプ</string>
|
|
||||||
<string name="exposed_dropdown_menu_content_description">ドロップダウンメニューを表示</string>
|
<string name="exposed_dropdown_menu_content_description">ドロップダウンメニューを表示</string>
|
||||||
<string name="fab_transformation_scrim_behavior">com.google.android.material.transformation.FabTransformationScrimBehavior</string>
|
<string name="force_export_resource">リソースのアップデートを強制する</string>
|
||||||
<string name="fab_transformation_sheet_behavior">com.google.android.material.transformation.FabTransformationSheetBehavior</string>
|
<string name="login_as_ios">iOSとしてログイン</string>
|
||||||
<string name="force_export_resource">リソースの更新を強制する</string>
|
|
||||||
<string name="gakumas_localify">Gakumas Localify</string>
|
<string name="gakumas_localify">Gakumas Localify</string>
|
||||||
<string name="game_patch">ゲームパッチ</string>
|
<string name="game_patch">ゲームパッチ</string>
|
||||||
<string name="graphic_settings">グラフィック設定</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="hign">高</string>
|
||||||
<string name="home">ホーム</string>
|
<string name="home">ホーム</string>
|
||||||
<string name="home_shizuku_warning">一部の機能が使用できません</string>
|
<string name="home_shizuku_warning">一部の機能が使用できません</string>
|
||||||
<string name="icon_content_description">ダイアログアイコン</string>
|
<string name="icon_content_description">ダイアログアイコン</string>
|
||||||
<string name="in_progress">実行中</string>
|
<string name="in_progress">実行中</string>
|
||||||
<string name="indeterminate">部分的に確認済み</string>
|
<string name="indeterminate">部分的にチェック済み</string>
|
||||||
<string name="install">インストール</string>
|
<string name="install">インストール</string>
|
||||||
<string name="installing">インストール中</string>
|
<string name="installing">インストール中</string>
|
||||||
<string name="invalid_zip_file">無効なファイル</string>
|
<string name="invalid_zip_file">無効なファイル</string>
|
||||||
|
|
@ -116,168 +70,9 @@
|
||||||
<string name="liveUseCustomeDress">ライブのキャラクターをカスタム</string>
|
<string name="liveUseCustomeDress">ライブのキャラクターをカスタム</string>
|
||||||
<string name="live_costume_head_id">ライブのカスタムヘッド ID (例: costume_head_hski-cstm-0002)</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="live_custome_dress_id">ライブ衣装のカスタム ID (例: hski-cstm-0002)</string>
|
||||||
<string name="login_as_ios">iOS としてログイン</string>
|
|
||||||
<string name="low">低</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="max_high">ウルトラ</string>
|
||||||
<string name="middle">中</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="off">OFF</string>
|
||||||
<string name="ok">OK</string>
|
<string name="ok">OK</string>
|
||||||
<string name="on">ON</string>
|
<string name="on">ON</string>
|
||||||
|
|
@ -289,21 +84,12 @@
|
||||||
<string name="patch_debuggable">デバッグを可能にする</string>
|
<string name="patch_debuggable">デバッグを可能にする</string>
|
||||||
<string name="patch_finished">パッチが完了しました。インストールをしますか?</string>
|
<string name="patch_finished">パッチが完了しました。インストールをしますか?</string>
|
||||||
<string name="patch_integrated">統合</string>
|
<string name="patch_integrated">統合</string>
|
||||||
<string name="patch_integrated_desc">"モジュールを埋め込んだ状態なアプリでパッチを当てます。
|
<string name="patch_integrated_desc">"モジュールを埋め込んだ状態なアプリでパッチを当てます。\nパッチを適用したアプリは LSPatch Manager なしで実行できますが、動的に管理はできません。\n統合パッチが適用されたアプリは、LSPatch Manager がインストールされていないデバイスでも使用が可能です。"</string>
|
||||||
パッチを適用したアプリは LSPatch Manager なしで実行できますが、動的に管理はできません。
|
|
||||||
統合パッチが適用されたアプリは、LSPatch Manager がインストールされていないデバイスでも使用が可能です。"</string>
|
|
||||||
<string name="patch_local">ローカル</string>
|
<string name="patch_local">ローカル</string>
|
||||||
<string name="patch_local_desc">"モジュールを埋め込まずにアプリにパッチを当てます。
|
<string name="patch_local_desc">"モジュールを埋め込まずにアプリにパッチを当てます。\nXposed スコープは再パッチなしで動的に変更が可能です。\nローカルでのパッチを当てたアプリは、ローカルのデバイスでのみ実行可能です。"</string>
|
||||||
Xposed スコープは再パッチなしで動的に変更が可能です。
|
|
||||||
ローカルでのパッチを当てたアプリは、ローカルのデバイスでのみ実行可能です。"</string>
|
|
||||||
<string name="patch_mode">パッチモード</string>
|
<string name="patch_mode">パッチモード</string>
|
||||||
<string name="patch_uninstall_confirm">アンインストールをしてもよろしいですか?</string>
|
<string name="patch_uninstall_confirm">アンインストールをしてもよろしいですか?</string>
|
||||||
<string name="patch_uninstall_text">"署名が異なるため、パッチをインストールする前に元となるアプリをアンインストールする必要があります。
|
<string name="patch_uninstall_text">"署名が異なるため、パッチをインストールする前に元となるアプリをアンインストールする必要があります。\n個人データのバックアップを設定済みであることを確認してください。"</string>
|
||||||
個人データのバックアップを設定済みであることを確認してください。"</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="pendulum">揺れ</string>
|
||||||
<string name="pendulumrange">揺れの範囲</string>
|
<string name="pendulumrange">揺れの範囲</string>
|
||||||
<string name="plugin_code">プラグインのコード</string>
|
<string name="plugin_code">プラグインのコード</string>
|
||||||
|
|
@ -312,41 +98,29 @@ Xposed スコープは再パッチなしで動的に変更が可能です。
|
||||||
<string name="range_start">範囲の開始</string>
|
<string name="range_start">範囲の開始</string>
|
||||||
<string name="renderscale">RenderScale (0.5/0.59/0.67/0.77/1.0)</string>
|
<string name="renderscale">RenderScale (0.5/0.59/0.67/0.77/1.0)</string>
|
||||||
<string name="replace_font">フォントを置換する</string>
|
<string name="replace_font">フォントを置換する</string>
|
||||||
<string name="replace_texture">テクスチャを置換する</string>
|
|
||||||
<string name="reserve_patched">パッチ済みの APK を予約する</string>
|
<string name="reserve_patched">パッチ済みの APK を予約する</string>
|
||||||
<string name="resource_settings">リソース設定</string>
|
<string name="resource_settings">リソース設定</string>
|
||||||
<string name="resource_url">リソース URL</string>
|
<string name="resource_url">リソース URL</string>
|
||||||
<string name="rootweight">ルートウエイト</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="selected">選択済み</string>
|
||||||
<string name="setFpsTitle">最大 FPS (0 はオリジナルの設定を使用します)</string>
|
<string name="setFpsTitle">最大 FPS (0 はオリジナルの設定を使用します)</string>
|
||||||
<string name="shizuku_available">Shizuku サービスが有効です</string>
|
<string name="shizuku_available">Shizuku サービスが有効です</string>
|
||||||
<string name="shizuku_unavailable">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="spring">跳ね</string>
|
||||||
<string name="start_game">ゲーム開始 / ホットリロードの設定</string>
|
<string name="start_game">ゲーム開始 / ホットリロードの設定</string>
|
||||||
<string name="status_bar_notification_info_overflow">999+</string>
|
|
||||||
<string name="stiffness">剛性</string>
|
<string name="stiffness">剛性</string>
|
||||||
<string name="support_file_types">"対応ファイル:
|
<string name="support_file_types">"対応ファイル:\n単一または複数選択: apk\n単一選択: apks、xapk、zip"</string>
|
||||||
単一または複数選択: apk
|
|
||||||
単一選択: apks、xapk、zip"</string>
|
|
||||||
<string name="switch_role">切り替え</string>
|
<string name="switch_role">切り替え</string>
|
||||||
<string name="tab">タブ</string>
|
<string name="tab">タブ</string>
|
||||||
<string name="template_percent">%1$d パーセント。</string>
|
<string name="template_percent">%1$d パーセント。</string>
|
||||||
<string name="test_mode_live">テストモード - ライブ</string>
|
<string name="test_mode_live">テストモード - ライブ</string>
|
||||||
<string name="text_hook_test_mode">テキストフックテストモード</string>
|
<string name="text_hook_test_mode">テキストフックテストモード</string>
|
||||||
<string name="tooltip_description">ツールチップ</string>
|
<string name="useMasterDBTrans">MasterDB をローカライズする</string>
|
||||||
<string name="tooltip_label">ツールチップを表示</string>
|
|
||||||
<string name="translation_repository">翻訳のリポジトリ</string>
|
<string name="translation_repository">翻訳のリポジトリ</string>
|
||||||
<string name="translation_resource_update">翻訳リソースを更新</string>
|
<string name="translation_resource_update">翻訳リソースをアップデート</string>
|
||||||
<string name="unlockAllLive">すべてのライブを開放</string>
|
<string name="unlockAllLive">すべてのライブを開放</string>
|
||||||
<string name="unlockAllLiveCostume">すべてのライブ衣装を開放</string>
|
<string name="unlockAllLiveCostume">すべてのライブ衣装を開放</string>
|
||||||
<string name="useCustomeGraphicSettings">カスタムグラフィック設定を使用する</string>
|
<string name="useCustomeGraphicSettings">カスタムグラフィック設定を使用する</string>
|
||||||
<string name="useMasterDBTrans">MasterDB のローカライズを有効化</string>
|
|
||||||
<string name="use_remote_zip_resource">リモート ZIP リソースを使用する</string>
|
<string name="use_remote_zip_resource">リモート ZIP リソースを使用する</string>
|
||||||
<string name="usearmcorrection">Arm コレクションを使用する</string>
|
<string name="usearmcorrection">Arm コレクションを使用する</string>
|
||||||
<string name="uselimit_0_1">リミットレンジの倍率 (0 は無制限)</string>
|
<string name="uselimit_0_1">リミットレンジの倍率 (0 は無制限)</string>
|
||||||
|
|
@ -354,9 +128,4 @@ Xposed スコープは再パッチなしで動的に変更が可能です。
|
||||||
<string name="usescale">胸の大きさを使用する</string>
|
<string name="usescale">胸の大きさを使用する</string>
|
||||||
<string name="very_high">最高</string>
|
<string name="very_high">最高</string>
|
||||||
<string name="warning">警告</string>
|
<string name="warning">警告</string>
|
||||||
<string name="check_texture_resource_from_api">API からテクスチャリソースの更新を確認</string>
|
|
||||||
<string name="texture_api_addr">テクスチャ API アドレス (GitHub 最新リリース API)</string>
|
|
||||||
<string name="texture_resource_update">テクスチャリソースを更新</string>
|
|
||||||
<string name="downloaded_texture_resource_version">ダウンロード済みテクスチャバージョン</string>
|
|
||||||
<string name="default_texture_assets_check_api">https://texture.gakumas.cn/api/gkms_texture_data</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
<string name="gakumas_localify">Gakumas Localify</string>
|
<string name="gakumas_localify">Gakumas Localify</string>
|
||||||
<string name="enable_plugin">启用插件 (不可热重载)</string>
|
<string name="enable_plugin">启用插件 (不可热重载)</string>
|
||||||
<string name="replace_font">替换字体</string>
|
<string name="replace_font">替换字体</string>
|
||||||
<string name="replace_texture">替换贴图</string>
|
|
||||||
<string name="lazy_init">快速初始化(懒加载配置)</string>
|
<string name="lazy_init">快速初始化(懒加载配置)</string>
|
||||||
<string name="enable_free_camera">启用自由视角(可热重载; 需使用实体键盘)</string>
|
<string name="enable_free_camera">启用自由视角(可热重载; 需使用实体键盘)</string>
|
||||||
<string name="start_game">以上述配置启动游戏/重载配置</string>
|
<string name="start_game">以上述配置启动游戏/重载配置</string>
|
||||||
|
|
@ -18,7 +17,6 @@
|
||||||
<string name="text_hook_test_mode">文本 hook 测试模式</string>
|
<string name="text_hook_test_mode">文本 hook 测试模式</string>
|
||||||
<string name="useMasterDBTrans">使用 MasterDB 本地化</string>
|
<string name="useMasterDBTrans">使用 MasterDB 本地化</string>
|
||||||
<string name="export_text">导出文本</string>
|
<string name="export_text">导出文本</string>
|
||||||
<string name="dump_runtime_texture">导出运行时贴图</string>
|
|
||||||
<string name="force_export_resource">启动后强制导出资源</string>
|
<string name="force_export_resource">启动后强制导出资源</string>
|
||||||
<string name="login_as_ios">以 iOS 登陆</string>
|
<string name="login_as_ios">以 iOS 登陆</string>
|
||||||
<string name="max_high">极高</string>
|
<string name="max_high">极高</string>
|
||||||
|
|
@ -88,10 +86,6 @@
|
||||||
<string name="api_addr">API 地址(Github Latest Release API)</string>
|
<string name="api_addr">API 地址(Github Latest Release API)</string>
|
||||||
<string name="check_update">检查更新</string>
|
<string name="check_update">检查更新</string>
|
||||||
<string name="translation_resource_update">翻译资源更新</string>
|
<string name="translation_resource_update">翻译资源更新</string>
|
||||||
<string name="check_texture_resource_from_api">从服务器检查贴图资源更新</string>
|
|
||||||
<string name="texture_api_addr">贴图 API 地址(Github Latest Release API)</string>
|
|
||||||
<string name="texture_resource_update">贴图资源更新</string>
|
|
||||||
<string name="downloaded_texture_resource_version">已下载贴图资源版本</string>
|
|
||||||
|
|
||||||
<string name="game_patch">游戏修补</string>
|
<string name="game_patch">游戏修补</string>
|
||||||
<string name="patch_mode">修补模式</string>
|
<string name="patch_mode">修补模式</string>
|
||||||
|
|
@ -111,5 +105,4 @@
|
||||||
|
|
||||||
<string name="about_contributors_asset_file">about_contributors_zh_cn.json</string>
|
<string name="about_contributors_asset_file">about_contributors_zh_cn.json</string>
|
||||||
<string name="default_assets_check_api">https://uma.chinosk6.cn/api/gkms_trans_data</string>
|
<string name="default_assets_check_api">https://uma.chinosk6.cn/api/gkms_trans_data</string>
|
||||||
<string name="default_texture_assets_check_api">https://texture.gakumas.cn/api/gkms_texture_data</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -1,113 +0,0 @@
|
||||||
<resources>
|
|
||||||
<string name="app_name">Gakumas Localify</string>
|
|
||||||
<string name="gakumas_localify">Gakumas Localify</string>
|
|
||||||
<string name="enable_plugin">啟用插件 (不可熱重載)</string>
|
|
||||||
<string name="replace_font">替換字體</string>
|
|
||||||
<string name="lazy_init">快速初始化(懶人設定)</string>
|
|
||||||
<string name="enable_free_camera">啟用自由視角(可熱重載; 需使用實體鍵盤)</string>
|
|
||||||
<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="dump_runtime_texture">導出運行時貼圖</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>
|
|
||||||
<string name="middle">中</string>
|
|
||||||
<string name="low">低</string>
|
|
||||||
<string name="orientation_orig">原版</string>
|
|
||||||
<string name="orientation_portrait">豎屏</string>
|
|
||||||
<string name="orientation_landscape">橫屏</string>
|
|
||||||
<string name="orientation_lock">方向鎖定</string>
|
|
||||||
<string name="enable_breast_param">啟用胸部參數</string>
|
|
||||||
<string name="damping">阻尼 (Damping)</string>
|
|
||||||
<string name="stiffness">剛度 (Stiffness)</string>
|
|
||||||
<string name="spring">彈簧係數 (Spring)</string>
|
|
||||||
<string name="pendulum">鐘擺係數 (Pendulum)</string>
|
|
||||||
<string name="pendulumrange">鐘擺範圍 (PendulumRange)</string>
|
|
||||||
<string name="average">Average</string>
|
|
||||||
<string name="rootweight">RootWeight</string>
|
|
||||||
<string name="uselimit_0_1">範圍限制倍率 (0 為不限制, 1 為原版)</string>
|
|
||||||
<string name="usearmcorrection">使用手臂矯正</string>
|
|
||||||
<string name="isdirty">IsDirty</string>
|
|
||||||
<string name="usescale">應用縮放</string>
|
|
||||||
<string name="breast_scale">胸部縮放倍率</string>
|
|
||||||
<string name="uselimitmultiplier">啟用範圍限制倍率</string>
|
|
||||||
<string name="axisx_x">axisX.x</string>
|
|
||||||
<string name="axisy_x">axisY.x</string>
|
|
||||||
<string name="axisz_x">axisZ.x</string>
|
|
||||||
<string name="axisx_y">axisX.y</string>
|
|
||||||
<string name="axisy_y">axisY.y</string>
|
|
||||||
<string name="axisz_y">axisZ.y</string>
|
|
||||||
<string name="basic_settings">基本設定</string>
|
|
||||||
<string name="graphic_settings">畫面設定</string>
|
|
||||||
<string name="camera_settings">攝影機設定</string>
|
|
||||||
<string name="test_mode_live">測試模式 - LIVE</string>
|
|
||||||
<string name="debug_settings">調試設定</string>
|
|
||||||
<string name="breast_param">胸部參數</string>
|
|
||||||
<string name="about">關於</string>
|
|
||||||
<string name="home">主頁</string>
|
|
||||||
<string name="advanced_settings">進階設定</string>
|
|
||||||
<string name="about_warn_title">使用前警告</string>
|
|
||||||
<string name="about_warn_p1">本插件僅供學習和交流使用。</string>
|
|
||||||
<string name="about_warn_p2">使用外部插件屬於違反遊戲條款的行為。若使用插件後帳號被封禁,造成的後果由用户自行承擔。</string>
|
|
||||||
<string name="about_about_title">關於本插件</string>
|
|
||||||
<string name="about_about_p1">本插件完全免費。若您付費購買了本插件,請檢舉店家。</string>
|
|
||||||
<string name="about_about_p2">插件交流QQ群: 991990192</string>
|
|
||||||
<string name="project_contribution">項目貢獻</string>
|
|
||||||
<string name="plugin_code">插件本體</string>
|
|
||||||
<string name="contributors">貢獻者列表</string>
|
|
||||||
<string name="translation_repository">譯文倉庫</string>
|
|
||||||
<string name="resource_settings">資源設定</string>
|
|
||||||
<string name="check_built_in_resource">檢查內置翻譯資源更新</string>
|
|
||||||
<string name="delete_plugin_resource">清除遊戲目錄內的插件翻譯資源</string>
|
|
||||||
<string name="use_remote_zip_resource">使用雲端 ZIP 翻譯資源</string>
|
|
||||||
<string name="resource_url">資源地址</string>
|
|
||||||
<string name="download">下載</string>
|
|
||||||
<string name="invalid_zip_file">文件解析失敗</string>
|
|
||||||
<string name="invalid_zip_file_warn">此 ZIP 文件不是一個有效的翻譯資源包</string>
|
|
||||||
<string name="cancel">取消</string>
|
|
||||||
<string name="ok">確定</string>
|
|
||||||
<string name="downloaded_resource_version">已下載資源版本</string>
|
|
||||||
<string name="del_remote_after_update">替換文件後刪除下載緩存</string>
|
|
||||||
<string name="warning">注意</string>
|
|
||||||
<string name="install">安裝</string>
|
|
||||||
<string name="installing">安裝中</string>
|
|
||||||
<string name="check_resource_from_api">从伺服器檢查更新資源</string>
|
|
||||||
<string name="api_addr">API 地址(Github Latest Release API)</string>
|
|
||||||
<string name="check_update">檢查更新</string>
|
|
||||||
<string name="translation_resource_update">翻譯資源更新</string>
|
|
||||||
<string name="check_texture_resource_from_api">从伺服器檢查貼圖資源更新</string>
|
|
||||||
<string name="texture_api_addr">貼圖 API 地址(Github Latest Release API)</string>
|
|
||||||
<string name="texture_resource_update">貼圖資源更新</string>
|
|
||||||
<string name="downloaded_texture_resource_version">已下載貼圖資源版本</string>
|
|
||||||
<string name="game_patch">遊戲修補</string>
|
|
||||||
<string name="patch_mode">修補模式</string>
|
|
||||||
<string name="patch_local">本地模式</string>
|
|
||||||
<string name="patch_local_desc">為未嵌入模塊的遊戲程式打補丁。\nXposed 範圍可動態更改,無需重新打補丁。\n以本地模式修補的遊戲程式只能在本地設備上執行。</string>
|
|
||||||
<string name="patch_integrated">集成模式</string>
|
|
||||||
<string name="patch_integrated_desc">修補遊戲程式並內置模塊。\n經集成模式修補的遊戲可以在沒有插件管理器的情况下執行,但不能動態管理設定。\n以集成模式修補的遊戲可在未安裝 LSPatch 管理器的設備上執行。</string>
|
|
||||||
<string name="shizuku_available">Shizuku 服務可用</string>
|
|
||||||
<string name="shizuku_unavailable">Shizuku 服務未連接</string>
|
|
||||||
<string name="home_shizuku_warning">部分功能不可用</string>
|
|
||||||
<string name="patch_debuggable">可調試</string>
|
|
||||||
<string name="reserve_patched">安裝時保留修補包</string>
|
|
||||||
<string name="support_file_types">支援文件類型:\n單/多選 apk\n單選 apks, xapk, zip</string>
|
|
||||||
<string name="patch_uninstall_text">由於程式簽名不同,安裝修補版的遊戲前需要先刪除原版。\n請確保您已備份好個人資料。</string>
|
|
||||||
<string name="patch_uninstall_confirm">您確定要刪除吗</string>
|
|
||||||
<string name="patch_finished">修補完成,是否開始安裝?</string>
|
|
||||||
|
|
||||||
<string name="about_contributors_asset_file">about_contributors_zh_cn.json</string>
|
|
||||||
<string name="default_assets_check_api">https://uma.chinosk6.cn/api/gkms_trans_data</string>
|
|
||||||
<string name="default_texture_assets_check_api">https://texture.gakumas.cn/api/gkms_texture_data</string>
|
|
||||||
</resources>
|
|
||||||
|
|
@ -1,113 +0,0 @@
|
||||||
<resources>
|
|
||||||
<string name="app_name">Gakumas Localify</string>
|
|
||||||
<string name="gakumas_localify">Gakumas Localify</string>
|
|
||||||
<string name="enable_plugin">啟用插件 (不可熱重載)</string>
|
|
||||||
<string name="replace_font">替換字體</string>
|
|
||||||
<string name="lazy_init">快速初始化(懶人設定)</string>
|
|
||||||
<string name="enable_free_camera">啟用自由視角(可熱重載; 需使用實體鍵盤)</string>
|
|
||||||
<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="dump_runtime_texture">導出運行時貼圖</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>
|
|
||||||
<string name="middle">中</string>
|
|
||||||
<string name="low">低</string>
|
|
||||||
<string name="orientation_orig">原版</string>
|
|
||||||
<string name="orientation_portrait">豎屏</string>
|
|
||||||
<string name="orientation_landscape">橫屏</string>
|
|
||||||
<string name="orientation_lock">方向鎖定</string>
|
|
||||||
<string name="enable_breast_param">啟用胸部參數</string>
|
|
||||||
<string name="damping">阻尼 (Damping)</string>
|
|
||||||
<string name="stiffness">剛度 (Stiffness)</string>
|
|
||||||
<string name="spring">彈簧係數 (Spring)</string>
|
|
||||||
<string name="pendulum">鐘擺係數 (Pendulum)</string>
|
|
||||||
<string name="pendulumrange">鐘擺範圍 (PendulumRange)</string>
|
|
||||||
<string name="average">Average</string>
|
|
||||||
<string name="rootweight">RootWeight</string>
|
|
||||||
<string name="uselimit_0_1">範圍限制倍率 (0 為不限制, 1 為原版)</string>
|
|
||||||
<string name="usearmcorrection">使用手臂矯正</string>
|
|
||||||
<string name="isdirty">IsDirty</string>
|
|
||||||
<string name="usescale">應用縮放</string>
|
|
||||||
<string name="breast_scale">胸部縮放倍率</string>
|
|
||||||
<string name="uselimitmultiplier">啟用範圍限制倍率</string>
|
|
||||||
<string name="axisx_x">axisX.x</string>
|
|
||||||
<string name="axisy_x">axisY.x</string>
|
|
||||||
<string name="axisz_x">axisZ.x</string>
|
|
||||||
<string name="axisx_y">axisX.y</string>
|
|
||||||
<string name="axisy_y">axisY.y</string>
|
|
||||||
<string name="axisz_y">axisZ.y</string>
|
|
||||||
<string name="basic_settings">基本設定</string>
|
|
||||||
<string name="graphic_settings">畫面設定</string>
|
|
||||||
<string name="camera_settings">攝影機設定</string>
|
|
||||||
<string name="test_mode_live">測試模式 - LIVE</string>
|
|
||||||
<string name="debug_settings">調試設定</string>
|
|
||||||
<string name="breast_param">胸部參數</string>
|
|
||||||
<string name="about">關於</string>
|
|
||||||
<string name="home">主頁</string>
|
|
||||||
<string name="advanced_settings">進階設定</string>
|
|
||||||
<string name="about_warn_title">使用前警告</string>
|
|
||||||
<string name="about_warn_p1">本插件僅供學習和交流使用。</string>
|
|
||||||
<string name="about_warn_p2">使用外部插件屬於違反遊戲條款的行為。若使用插件後帳號被封禁,造成的後果由用户自行承擔。</string>
|
|
||||||
<string name="about_about_title">關於本插件</string>
|
|
||||||
<string name="about_about_p1">本插件完全免費。若您付費購買了本插件,請檢舉店家。</string>
|
|
||||||
<string name="about_about_p2">插件交流QQ群: 991990192</string>
|
|
||||||
<string name="project_contribution">項目貢獻</string>
|
|
||||||
<string name="plugin_code">插件本體</string>
|
|
||||||
<string name="contributors">貢獻者列表</string>
|
|
||||||
<string name="translation_repository">譯文倉庫</string>
|
|
||||||
<string name="resource_settings">資源設定</string>
|
|
||||||
<string name="check_built_in_resource">檢查內置翻譯資源更新</string>
|
|
||||||
<string name="delete_plugin_resource">清除遊戲目錄內的插件翻譯資源</string>
|
|
||||||
<string name="use_remote_zip_resource">使用雲端 ZIP 翻譯資源</string>
|
|
||||||
<string name="resource_url">資源地址</string>
|
|
||||||
<string name="download">下載</string>
|
|
||||||
<string name="invalid_zip_file">文件解析失敗</string>
|
|
||||||
<string name="invalid_zip_file_warn">此 ZIP 文件不是一個有效的翻譯資源包</string>
|
|
||||||
<string name="cancel">取消</string>
|
|
||||||
<string name="ok">確定</string>
|
|
||||||
<string name="downloaded_resource_version">已下載資源版本</string>
|
|
||||||
<string name="del_remote_after_update">替換文件後刪除下載緩存</string>
|
|
||||||
<string name="warning">注意</string>
|
|
||||||
<string name="install">安裝</string>
|
|
||||||
<string name="installing">安裝中</string>
|
|
||||||
<string name="check_resource_from_api">从伺服器檢查更新資源</string>
|
|
||||||
<string name="api_addr">API 地址(Github Latest Release API)</string>
|
|
||||||
<string name="check_update">檢查更新</string>
|
|
||||||
<string name="translation_resource_update">翻譯資源更新</string>
|
|
||||||
<string name="check_texture_resource_from_api">从伺服器檢查貼圖資源更新</string>
|
|
||||||
<string name="texture_api_addr">貼圖 API 地址(Github Latest Release API)</string>
|
|
||||||
<string name="texture_resource_update">貼圖資源更新</string>
|
|
||||||
<string name="downloaded_texture_resource_version">已下載貼圖資源版本</string>
|
|
||||||
<string name="game_patch">遊戲修補</string>
|
|
||||||
<string name="patch_mode">修補模式</string>
|
|
||||||
<string name="patch_local">本地模式</string>
|
|
||||||
<string name="patch_local_desc">為未嵌入模塊的遊戲程式打補丁。\nXposed 範圍可動態更改,無需重新打補丁。\n以本地模式修補的遊戲程式只能在本地設備上執行。</string>
|
|
||||||
<string name="patch_integrated">集成模式</string>
|
|
||||||
<string name="patch_integrated_desc">修補遊戲程式並內置模塊。\n經集成模式修補的遊戲可以在沒有插件管理器的情况下執行,但不能動態管理設定。\n以集成模式修補的遊戲可在未安裝 LSPatch 管理器的設備上執行。</string>
|
|
||||||
<string name="shizuku_available">Shizuku 服務可用</string>
|
|
||||||
<string name="shizuku_unavailable">Shizuku 服務未連接</string>
|
|
||||||
<string name="home_shizuku_warning">部分功能不可用</string>
|
|
||||||
<string name="patch_debuggable">可調試</string>
|
|
||||||
<string name="reserve_patched">安裝時保留修補包</string>
|
|
||||||
<string name="support_file_types">支援文件類型:\n單/多選 apk\n單選 apks, xapk, zip</string>
|
|
||||||
<string name="patch_uninstall_text">由於程式簽名不同,安裝修補版的遊戲前需要先刪除原版。\n請確保您已備份好個人資料。</string>
|
|
||||||
<string name="patch_uninstall_confirm">您確定要刪除吗</string>
|
|
||||||
<string name="patch_finished">修補完成,是否開始安裝?</string>
|
|
||||||
|
|
||||||
<string name="about_contributors_asset_file">about_contributors_zh_cn.json</string>
|
|
||||||
<string name="default_assets_check_api">https://uma.chinosk6.cn/api/gkms_trans_data</string>
|
|
||||||
<string name="default_texture_assets_check_api">https://texture.gakumas.cn/api/gkms_texture_data</string>
|
|
||||||
</resources>
|
|
||||||
|
|
@ -1,113 +0,0 @@
|
||||||
<resources>
|
|
||||||
<string name="app_name">Gakumas Localify</string>
|
|
||||||
<string name="gakumas_localify">Gakumas Localify</string>
|
|
||||||
<string name="enable_plugin">啟用插件 (不可熱重載)</string>
|
|
||||||
<string name="replace_font">替換字體</string>
|
|
||||||
<string name="lazy_init">快速初始化(懶人設定)</string>
|
|
||||||
<string name="enable_free_camera">啟用自由視角(可熱重載; 需使用實體鍵盤)</string>
|
|
||||||
<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="dump_runtime_texture">導出運行時貼圖</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>
|
|
||||||
<string name="middle">中</string>
|
|
||||||
<string name="low">低</string>
|
|
||||||
<string name="orientation_orig">原版</string>
|
|
||||||
<string name="orientation_portrait">豎屏</string>
|
|
||||||
<string name="orientation_landscape">橫屏</string>
|
|
||||||
<string name="orientation_lock">方向鎖定</string>
|
|
||||||
<string name="enable_breast_param">啟用胸部參數</string>
|
|
||||||
<string name="damping">阻尼 (Damping)</string>
|
|
||||||
<string name="stiffness">剛度 (Stiffness)</string>
|
|
||||||
<string name="spring">彈簧係數 (Spring)</string>
|
|
||||||
<string name="pendulum">鐘擺係數 (Pendulum)</string>
|
|
||||||
<string name="pendulumrange">鐘擺範圍 (PendulumRange)</string>
|
|
||||||
<string name="average">Average</string>
|
|
||||||
<string name="rootweight">RootWeight</string>
|
|
||||||
<string name="uselimit_0_1">範圍限制倍率 (0 為不限制, 1 為原版)</string>
|
|
||||||
<string name="usearmcorrection">使用手臂矯正</string>
|
|
||||||
<string name="isdirty">IsDirty</string>
|
|
||||||
<string name="usescale">應用縮放</string>
|
|
||||||
<string name="breast_scale">胸部縮放倍率</string>
|
|
||||||
<string name="uselimitmultiplier">啟用範圍限制倍率</string>
|
|
||||||
<string name="axisx_x">axisX.x</string>
|
|
||||||
<string name="axisy_x">axisY.x</string>
|
|
||||||
<string name="axisz_x">axisZ.x</string>
|
|
||||||
<string name="axisx_y">axisX.y</string>
|
|
||||||
<string name="axisy_y">axisY.y</string>
|
|
||||||
<string name="axisz_y">axisZ.y</string>
|
|
||||||
<string name="basic_settings">基本設定</string>
|
|
||||||
<string name="graphic_settings">畫面設定</string>
|
|
||||||
<string name="camera_settings">攝影機設定</string>
|
|
||||||
<string name="test_mode_live">測試模式 - LIVE</string>
|
|
||||||
<string name="debug_settings">調試設定</string>
|
|
||||||
<string name="breast_param">胸部參數</string>
|
|
||||||
<string name="about">關於</string>
|
|
||||||
<string name="home">主頁</string>
|
|
||||||
<string name="advanced_settings">進階設定</string>
|
|
||||||
<string name="about_warn_title">使用前警告</string>
|
|
||||||
<string name="about_warn_p1">本插件僅供學習和交流使用。</string>
|
|
||||||
<string name="about_warn_p2">使用外部插件屬於違反遊戲條款的行為。若使用插件後帳號被封禁,造成的後果由用户自行承擔。</string>
|
|
||||||
<string name="about_about_title">關於本插件</string>
|
|
||||||
<string name="about_about_p1">本插件完全免費。若您付費購買了本插件,請檢舉店家。</string>
|
|
||||||
<string name="about_about_p2">插件交流QQ群: 991990192</string>
|
|
||||||
<string name="project_contribution">項目貢獻</string>
|
|
||||||
<string name="plugin_code">插件本體</string>
|
|
||||||
<string name="contributors">貢獻者列表</string>
|
|
||||||
<string name="translation_repository">譯文倉庫</string>
|
|
||||||
<string name="resource_settings">資源設定</string>
|
|
||||||
<string name="check_built_in_resource">檢查內置翻譯資源更新</string>
|
|
||||||
<string name="delete_plugin_resource">清除遊戲目錄內的插件翻譯資源</string>
|
|
||||||
<string name="use_remote_zip_resource">使用雲端 ZIP 翻譯資源</string>
|
|
||||||
<string name="resource_url">資源地址</string>
|
|
||||||
<string name="download">下載</string>
|
|
||||||
<string name="invalid_zip_file">文件解析失敗</string>
|
|
||||||
<string name="invalid_zip_file_warn">此 ZIP 文件不是一個有效的翻譯資源包</string>
|
|
||||||
<string name="cancel">取消</string>
|
|
||||||
<string name="ok">確定</string>
|
|
||||||
<string name="downloaded_resource_version">已下載資源版本</string>
|
|
||||||
<string name="del_remote_after_update">替換文件後刪除下載緩存</string>
|
|
||||||
<string name="warning">注意</string>
|
|
||||||
<string name="install">安裝</string>
|
|
||||||
<string name="installing">安裝中</string>
|
|
||||||
<string name="check_resource_from_api">从伺服器檢查更新資源</string>
|
|
||||||
<string name="api_addr">API 地址(Github Latest Release API)</string>
|
|
||||||
<string name="check_update">檢查更新</string>
|
|
||||||
<string name="translation_resource_update">翻譯資源更新</string>
|
|
||||||
<string name="check_texture_resource_from_api">从伺服器檢查貼圖資源更新</string>
|
|
||||||
<string name="texture_api_addr">貼圖 API 地址(Github Latest Release API)</string>
|
|
||||||
<string name="texture_resource_update">貼圖資源更新</string>
|
|
||||||
<string name="downloaded_texture_resource_version">已下載貼圖資源版本</string>
|
|
||||||
<string name="game_patch">遊戲修補</string>
|
|
||||||
<string name="patch_mode">修補模式</string>
|
|
||||||
<string name="patch_local">本地模式</string>
|
|
||||||
<string name="patch_local_desc">為未嵌入模塊的遊戲程式打補丁。\nXposed 範圍可動態更改,無需重新打補丁。\n以本地模式修補的遊戲程式只能在本地設備上執行。</string>
|
|
||||||
<string name="patch_integrated">集成模式</string>
|
|
||||||
<string name="patch_integrated_desc">修補遊戲程式並內置模塊。\n經集成模式修補的遊戲可以在沒有插件管理器的情况下執行,但不能動態管理設定。\n以集成模式修補的遊戲可在未安裝 LSPatch 管理器的設備上執行。</string>
|
|
||||||
<string name="shizuku_available">Shizuku 服務可用</string>
|
|
||||||
<string name="shizuku_unavailable">Shizuku 服務未連接</string>
|
|
||||||
<string name="home_shizuku_warning">部分功能不可用</string>
|
|
||||||
<string name="patch_debuggable">可調試</string>
|
|
||||||
<string name="reserve_patched">安裝時保留修補包</string>
|
|
||||||
<string name="support_file_types">支援文件類型:\n單/多選 apk\n單選 apks, xapk, zip</string>
|
|
||||||
<string name="patch_uninstall_text">由於程式簽名不同,安裝修補版的遊戲前需要先刪除原版。\n請確保您已備份好個人資料。</string>
|
|
||||||
<string name="patch_uninstall_confirm">您確定要刪除吗</string>
|
|
||||||
<string name="patch_finished">修補完成,是否開始安裝?</string>
|
|
||||||
|
|
||||||
<string name="about_contributors_asset_file">about_contributors_zh_cn.json</string>
|
|
||||||
<string name="default_assets_check_api">https://uma.chinosk6.cn/api/gkms_trans_data</string>
|
|
||||||
<string name="default_texture_assets_check_api">https://texture.gakumas.cn/api/gkms_texture_data</string>
|
|
||||||
</resources>
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
<string name="gakumas_localify">Gakumas Localify</string>
|
<string name="gakumas_localify">Gakumas Localify</string>
|
||||||
<string name="enable_plugin">Enable Plugin (Not Hot Reloadable)</string>
|
<string name="enable_plugin">Enable Plugin (Not Hot Reloadable)</string>
|
||||||
<string name="replace_font">Replace Font</string>
|
<string name="replace_font">Replace Font</string>
|
||||||
<string name="replace_texture">Replace Texture</string>
|
|
||||||
<string name="lazy_init">Fast Initialization (Lazy loading)</string>
|
<string name="lazy_init">Fast Initialization (Lazy loading)</string>
|
||||||
<string name="enable_free_camera">Enable Free Camera</string>
|
<string name="enable_free_camera">Enable Free Camera</string>
|
||||||
<string name="start_game">Start Game / Hot Reload Config</string>
|
<string name="start_game">Start Game / Hot Reload Config</string>
|
||||||
|
|
@ -18,7 +17,6 @@
|
||||||
<string name="text_hook_test_mode">Text Hook Test Mode</string>
|
<string name="text_hook_test_mode">Text Hook Test Mode</string>
|
||||||
<string name="useMasterDBTrans">Enable MasterDB Localization</string>
|
<string name="useMasterDBTrans">Enable MasterDB Localization</string>
|
||||||
<string name="export_text">Export Text</string>
|
<string name="export_text">Export Text</string>
|
||||||
<string name="dump_runtime_texture">Dump Runtime Texture</string>
|
|
||||||
<string name="force_export_resource">Force Update Resource</string>
|
<string name="force_export_resource">Force Update Resource</string>
|
||||||
<string name="login_as_ios">Login as iOS</string>
|
<string name="login_as_ios">Login as iOS</string>
|
||||||
<string name="max_high">Ultra</string>
|
<string name="max_high">Ultra</string>
|
||||||
|
|
@ -50,7 +48,7 @@
|
||||||
<string name="axisx_y">axisX.y</string>
|
<string name="axisx_y">axisX.y</string>
|
||||||
<string name="axisy_y">axisY.y</string>
|
<string name="axisy_y">axisY.y</string>
|
||||||
<string name="axisz_y">axisZ.y</string>
|
<string name="axisz_y">axisZ.y</string>
|
||||||
<string name="basic_settings">Basic Settings</string>
|
<string name="basic_settings">Basic Ssettings</string>
|
||||||
<string name="graphic_settings">Graphic Settings</string>
|
<string name="graphic_settings">Graphic Settings</string>
|
||||||
<string name="camera_settings">Camera Settings</string>
|
<string name="camera_settings">Camera Settings</string>
|
||||||
<string name="test_mode_live">Test Mode - LIVE</string>
|
<string name="test_mode_live">Test Mode - LIVE</string>
|
||||||
|
|
@ -88,10 +86,6 @@
|
||||||
<string name="api_addr">API Address(Github Latest Release API)</string>
|
<string name="api_addr">API Address(Github Latest Release API)</string>
|
||||||
<string name="check_update">Check</string>
|
<string name="check_update">Check</string>
|
||||||
<string name="translation_resource_update">Translation Resource Update</string>
|
<string name="translation_resource_update">Translation Resource Update</string>
|
||||||
<string name="check_texture_resource_from_api">Check Texture Resource Update From API</string>
|
|
||||||
<string name="texture_api_addr">Texture API Address (Github Latest Release API)</string>
|
|
||||||
<string name="texture_resource_update">Texture Resource Update</string>
|
|
||||||
<string name="downloaded_texture_resource_version">Downloaded Texture Version</string>
|
|
||||||
|
|
||||||
<string name="game_patch">Game Patch</string>
|
<string name="game_patch">Game Patch</string>
|
||||||
<string name="patch_mode">Patch Mode</string>
|
<string name="patch_mode">Patch Mode</string>
|
||||||
|
|
@ -111,5 +105,4 @@
|
||||||
|
|
||||||
<string name="about_contributors_asset_file">about_contributors_en.json</string>
|
<string name="about_contributors_asset_file">about_contributors_en.json</string>
|
||||||
<string name="default_assets_check_api">https://api.github.com/repos/NatsumeLS/Gakumas-Translation-Data-EN/releases/latest</string>
|
<string name="default_assets_check_api">https://api.github.com/repos/NatsumeLS/Gakumas-Translation-Data-EN/releases/latest</string>
|
||||||
<string name="default_texture_assets_check_api">https://texture.gakumas.cn/api/gkms_texture_data</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
Loading…
Reference in New Issue