Compare commits

..

No commits in common. "main" and "main" have entirely different histories.
main ... main

50 changed files with 1018 additions and 3330 deletions

2
.gitignore vendored
View File

@ -18,5 +18,3 @@ local.properties
/.kotlin /.kotlin
/app/debug /app/debug
/app/release /app/release
app/src/main/assets/gakumas-local

View File

@ -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.

View File

@ -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"):

View File

@ -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

View File

@ -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

View File

@ -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"});
}
}

View File

@ -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

View File

@ -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;
}; };
} }

View File

@ -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;
} }

View File

@ -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
} }

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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:

View File

@ -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

View File

@ -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;

View File

@ -1,6 +1,6 @@
#pragma once #pragma once
#include "../../deps/UnityResolve/UnityResolve.hpp" #include "../deps/UnityResolve/UnityResolve.hpp"
enum LonMoveHState { enum LonMoveHState {
LonMoveLeftAndRight, LonMoveLeftAndRight,

View File

@ -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 {

View File

@ -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 {

View File

@ -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());
}
}
} }

View File

@ -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);
} }

View File

@ -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++

View File

@ -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)
} }

View File

@ -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)?
) { ) {

View File

@ -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")
} }

View File

@ -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) {

View File

@ -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()

View File

@ -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

View File

@ -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())
}
}
}

View File

@ -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,

View File

@ -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 = "",

View File

@ -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()
} }

View File

@ -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)),

View File

@ -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)) }
)*/ )
} }
} }
} }

View File

@ -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))
}
}
}
}
}
} }
} }
} }

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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 AddressGithub Latest Release API</string> <string name="api_addr">API AddressGithub 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>