1
0
Fork 0

Compare commits

..

1 Commits

Author SHA1 Message Date
chinosk cd82db9422
split generic text 2024-07-02 19:21:36 +08:00
15 changed files with 235 additions and 439 deletions

View File

@ -12,22 +12,21 @@ jobs:
with:
submodules: recursive
- name: set up android development environment
uses: android-actions/setup-android@v2
- name: install dependencies
run: |
sdkmanager --install "cmake;3.22.1"
echo "cmake.dir=/usr/local/lib/android/sdk/cmake/3.22.1" > local.properties
npm install -g pnpm
- name: Setup Java JDK
uses: actions/setup-java@v4.2.1
with:
distribution: 'temurin'
java-version: '21'
- name: Setup Android Development Environment
uses: android-actions/setup-android@v3
- name: install dependencies
run: |
sdkmanager --install "cmake;3.22.1"
echo "cmake.dir=$ANDROID_HOME/cmake/3.22.1" > local.properties
echo "$ANDROID_HOME/build-tools/34.0.0" >> $GITHUB_PATH
npm install -g pnpm
- name: Update Submodules
run: |
git submodule foreach --recursive 'git pull --rebase origin main --allow-unrelated-histories'
@ -59,44 +58,24 @@ jobs:
run: ./gradlew build
- uses: actions/upload-artifact@v4
id: upload_unsigned_v4
with:
name: GakumasLocalify-Unsigned-apk
path: app/build/outputs/apk/debug/app-debug.apk
continue-on-error: true
- name: Upload Unsigned APK with v3 if v4 failed
if: steps.upload_unsigned_v4.outcome == 'failure'
uses: actions/upload-artifact@v3
with:
name: GakumasLocalify-Unsigned-apk
path: app/build/outputs/apk/debug/app-debug.apk
continue-on-error: true
- uses: r0adkll/sign-android-release@v1
- uses: ilharp/sign-android-release@v1
name: Sign app APK
id: sign_app
with:
releaseDirectory: app/build/outputs/apk/debug
signingKeyBase64: ${{ secrets.KEYSTOREB64 }}
alias: ${{ secrets.ANDROID_KEY_ALIAS }}
releaseDir: app/build/outputs/apk/debug
signingKey: ${{ secrets.KEYSTOREB64 }}
keyAlias: ${{ secrets.ANDROID_KEY_ALIAS }}
keyStorePassword: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: "34.0.0"
buildToolsVersion: 33.0.0
continue-on-error: true
- uses: actions/upload-artifact@v4
id: upload_signed_v4
with:
name: GakumasLocalify-Signed-apk
path: ${{steps.sign_app.outputs.signedReleaseFile}}
continue-on-error: true
- name: Upload Signed APK with v3 if v4 failed
if: steps.upload_signed_v4.outcome == 'failure'
uses: actions/upload-artifact@v3
with:
name: GakumasLocalify-Signed-apk
path: ${{steps.sign_app.outputs.signedReleaseFile}}
path: ${{steps.sign_app.outputs.signedFile}}
continue-on-error: true

View File

@ -39,8 +39,7 @@ std::unordered_set<void*> hookedStubs{};
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++
else GakumasLocal::Log::ErrorFmt("Hook failed: %s is NULL", #name, addr)
void UnHookAll() {
for (const auto i: hookedStubs) {
@ -98,7 +97,7 @@ namespace GakumasLocal::HookMain {
UnityResolve::UnityType::Transform* cameraTransformCache = nullptr;
void CheckAndUpdateMainCamera() {
if (!Config::enableFreeCamera) return;
if (IsNativeObjectAlive(mainCameraCache) && IsNativeObjectAlive(cameraTransformCache)) return;
if (IsNativeObjectAlive(mainCameraCache)) return;
mainCameraCache = UnityResolve::UnityType::Camera::GetMain();
cameraTransformCache = mainCameraCache->GetTransform();
@ -828,8 +827,7 @@ namespace GakumasLocal::HookMain {
void StartInjectFunctions() {
const auto hookInstaller = Plugin::GetInstance().GetHookInstaller();
UnityResolve::Init(xdl_open(hookInstaller->m_il2cppLibraryPath.c_str(), RTLD_NOW),
UnityResolve::Mode::Il2Cpp, Config::lazyInit);
UnityResolve::Init(xdl_open(hookInstaller->m_il2cppLibraryPath.c_str(), RTLD_NOW), UnityResolve::Mode::Il2Cpp);
ADD_HOOK(AssetBundle_LoadAssetAsync, Il2cppUtils::il2cpp_resolve_icall(
"UnityEngine.AssetBundle::LoadAssetAsync_Internal(System.String,System.Type)"));
@ -961,30 +959,10 @@ namespace GakumasLocal::HookMain {
Log::Info("Start init plugin...");
if (Config::lazyInit) {
UnityResolveProgress::startInit = true;
UnityResolveProgress::assembliesProgress.total = 2;
UnityResolveProgress::assembliesProgress.current = 1;
UnityResolveProgress::classProgress.total = 36;
UnityResolveProgress::classProgress.current = 0;
}
StartInjectFunctions();
GKCamera::initCameraSettings();
if (Config::lazyInit) {
UnityResolveProgress::assembliesProgress.current = 2;
UnityResolveProgress::classProgress.total = 1;
UnityResolveProgress::classProgress.current = 0;
}
Local::LoadData();
if (Config::lazyInit) {
UnityResolveProgress::classProgress.current = 1;
UnityResolveProgress::startInit = false;
}
Log::Info("Plugin init finished.");
return ret;
}

View File

@ -11,6 +11,9 @@
#include <thread>
#include <regex>
#include <ranges>
#include <string>
#include <cctype>
#include <algorithm>
#include "BaseDefine.h"
@ -19,6 +22,8 @@ namespace GakumasLocal::Local {
std::unordered_map<std::string, std::string> i18nDumpData{};
std::unordered_map<std::string, std::string> genericText{};
std::vector<std::string> genericTextDumpData{};
std::vector<std::string> genericSplittedDumpData{};
std::vector<std::string> genericOrigTextDumpData{};
std::unordered_set<std::string> translatedText{};
int genericDumpFileIndex = 0;
@ -26,6 +31,48 @@ namespace GakumasLocal::Local {
return Plugin::GetInstance().GetHookInstaller()->localizationFilesDir;
}
std::string trim(const std::string& str) {
auto is_not_space = [](char ch) { return !std::isspace(ch); };
auto start = std::ranges::find_if(str, is_not_space);
auto end = std::ranges::find_if(str | std::views::reverse, is_not_space).base();
if (start < end) {
return {start, end};
}
return "";
}
std::string findInMapIgnoreSpace(const std::string& key, const std::unordered_map<std::string, std::string>& searchMap) {
auto is_space = [](char ch) { return std::isspace(ch); };
auto front = std::ranges::find_if_not(key, is_space);
auto back = std::ranges::find_if_not(key | std::views::reverse, is_space).base();
std::string prefix(key.begin(), front);
std::string suffix(back, key.end());
std::string trimmedKey = trim(key);
if ( auto it = searchMap.find(trimmedKey); it != searchMap.end()) {
return prefix + it->second + suffix;
}
else {
return "";
}
}
enum class DumpStrStat {
DEFAULT = 0,
SPLITTABLE_ORIG = 1,
SPLITTED = 2
};
enum class SplitTagsTranslationStat {
NO_TRANS,
PART_TRANS,
FULL_TRANS,
NO_SPLIT,
NO_SPLIT_AND_EMPTY
};
void LoadJsonDataToMap(const std::filesystem::path& filePath, std::unordered_map<std::string, std::string>& dict,
const bool insertToTranslated = false, const bool needClearDict = true) {
if (!exists(filePath)) return;
@ -84,7 +131,7 @@ namespace GakumasLocal::Local {
}
void DumpVectorDataToJson(const std::filesystem::path& dumpBasePath, const std::filesystem::path& fileName,
const std::vector<std::string>& vec) {
const std::vector<std::string>& vec, const std::string& valuePrefix = "") {
const auto dumpFilePath = dumpBasePath / fileName;
try {
if (!is_directory(dumpBasePath)) {
@ -101,7 +148,12 @@ namespace GakumasLocal::Local {
dumpLrcFile.close();
auto fileData = nlohmann::ordered_json::parse(fileContent);
for (const auto& i : vec) {
fileData[i] = i;
if (!valuePrefix.empty()) {
fileData[i] = valuePrefix + i;
}
else {
fileData[i] = i;
}
}
const auto newStr = fileData.dump(4, 32, false);
std::ofstream dumpWriteLrcFile(dumpFilePath, std::ofstream::out);
@ -199,6 +251,87 @@ namespace GakumasLocal::Local {
return ret;
}
SplitTagsTranslationStat GetSplitTagsTranslationFull(const std::string& origTextIn, std::string* newText, std::vector<std::string>& unTransResultRet) {
// static const std::u16string splitFlags = u"0123456789+-%%【】.";
static const std::unordered_set<char16_t> splitFlags = {u'0', u'1', u'2', u'3', u'4', u'5',
u'6', u'7', u'8', u'9', u'+', u'',
u'-', u'', u'%', u'', u'', u'',
u'.', u':', u'', u'×'};
const auto origText = Misc::ToUTF16(origTextIn);
bool isInTag = false;
std::vector<std::string> waitingReplaceTexts{};
std::u16string currentWaitingReplaceText;
#define checkCurrentWaitingReplaceTextAndClear() \
if (!currentWaitingReplaceText.empty()) { \
waitingReplaceTexts.push_back(Misc::ToUTF8(currentWaitingReplaceText)); \
currentWaitingReplaceText.clear(); }
for (char16_t currChar : origText) {
if (currChar == u'<') {
isInTag = true;
}
if (currChar == u'>') {
isInTag = false;
checkCurrentWaitingReplaceTextAndClear()
continue;
}
if (isInTag) {
checkCurrentWaitingReplaceTextAndClear()
continue;
}
if (!splitFlags.contains(currChar)) {
currentWaitingReplaceText.push_back(currChar);
}
else {
checkCurrentWaitingReplaceTextAndClear()
}
}
if (waitingReplaceTexts.empty()) {
if (currentWaitingReplaceText.empty()) {
return SplitTagsTranslationStat::NO_SPLIT_AND_EMPTY;
}
else {
return SplitTagsTranslationStat::NO_SPLIT;
}
}
checkCurrentWaitingReplaceTextAndClear()
*newText = origTextIn;
SplitTagsTranslationStat ret;
bool hasTrans = false;
bool hasNotTrans = false;
if (!waitingReplaceTexts.empty()) {
for (const auto& i : waitingReplaceTexts) {
const auto searchResult = findInMapIgnoreSpace(i, genericText);
if (!searchResult.empty()) {
ReplaceString(newText, i, searchResult);
hasTrans = true;
}
else {
unTransResultRet.emplace_back(trim(i));
hasNotTrans = true;
}
}
if (hasTrans && hasNotTrans) {
ret = SplitTagsTranslationStat::PART_TRANS;
}
else if (hasTrans && !hasNotTrans) {
ret = SplitTagsTranslationStat::FULL_TRANS;
}
else {
ret = SplitTagsTranslationStat::NO_TRANS;
}
}
else {
ret = SplitTagsTranslationStat::NO_TRANS;
}
return ret;
}
void LoadData() {
static auto localizationFile = GetBasePath() / "local-files" / "localization.json";
static auto genericFile = GetBasePath() / "local-files" / "generic.json";
@ -215,7 +348,7 @@ namespace GakumasLocal::Local {
if (std::filesystem::exists(genericDir) || std::filesystem::is_directory(genericDir)) {
for (const auto& entry : std::filesystem::recursive_directory_iterator(genericDir)) {
if (std::filesystem::is_regular_file(entry.path())) {
const auto currFile = entry.path();
const auto& currFile = entry.path();
if (to_lower(currFile.extension().string()) == ".json") {
LoadJsonDataToMap(currFile, genericText, true, false);
}
@ -285,29 +418,47 @@ namespace GakumasLocal::Local {
return false;
}
std::string GetDumpGenericFileName() {
if (genericDumpFileIndex == 0) return "generic.json";
return Log::StringFormat("generic_%d.json", genericDumpFileIndex);
std::string GetDumpGenericFileName(DumpStrStat stat = DumpStrStat::DEFAULT) {
if (stat == DumpStrStat::SPLITTABLE_ORIG) {
if (genericDumpFileIndex == 0) return "generic_orig.json";
return Log::StringFormat("generic_orig_%d.json", genericDumpFileIndex);
}
else {
if (genericDumpFileIndex == 0) return "generic.json";
return Log::StringFormat("generic_%d.json", genericDumpFileIndex);
}
}
bool inDumpGeneric = false;
void DumpGenericText(const std::string& origText) {
void DumpGenericText(const std::string& origText, DumpStrStat stat = DumpStrStat::DEFAULT) {
if (translatedText.contains(origText)) return;
if (std::find(genericTextDumpData.begin(), genericTextDumpData.end(), origText) != genericTextDumpData.end()) {
std::array<std::reference_wrapper<std::vector<std::string>>, 3> targets = {
genericTextDumpData,
genericOrigTextDumpData,
genericSplittedDumpData
};
auto& appendTarget = targets[static_cast<int>(stat)].get();
if (std::find(appendTarget.begin(), appendTarget.end(), origText) != appendTarget.end()) {
return;
}
if (IsPureStringValue(origText)) return;
genericTextDumpData.push_back(origText);
appendTarget.push_back(origText);
static auto dumpBasePath = GetBasePath() / "dump-files";
if (inDumpGeneric) return;
inDumpGeneric = true;
std::thread([](){
std::this_thread::sleep_for(std::chrono::seconds(5));
DumpVectorDataToJson(dumpBasePath, GetDumpGenericFileName(), genericTextDumpData);
DumpVectorDataToJson(dumpBasePath, GetDumpGenericFileName(DumpStrStat::DEFAULT), genericTextDumpData);
DumpVectorDataToJson(dumpBasePath, GetDumpGenericFileName(DumpStrStat::SPLITTABLE_ORIG), genericOrigTextDumpData);
DumpVectorDataToJson(dumpBasePath, GetDumpGenericFileName(DumpStrStat::SPLITTED), genericSplittedDumpData, "[split]");
genericTextDumpData.clear();
genericSplittedDumpData.clear();
genericOrigTextDumpData.clear();
inDumpGeneric = false;
}).detach();
}
@ -318,25 +469,50 @@ namespace GakumasLocal::Local {
return true;
}
auto ret = false;
std::vector<std::string> unTransResultRet;
if (GetSplitTagsTranslation(origText, newStr, unTransResultRet)) {
return true;
const auto splitTransStat = GetSplitTagsTranslationFull(origText, newStr, unTransResultRet);
switch (splitTransStat) {
case SplitTagsTranslationStat::FULL_TRANS: {
return true;
} break;
case SplitTagsTranslationStat::NO_SPLIT_AND_EMPTY: {
return false;
} break;
case SplitTagsTranslationStat::NO_SPLIT: {
ret = false;
} break;
case SplitTagsTranslationStat::NO_TRANS: {
ret = false;
} break;
case SplitTagsTranslationStat::PART_TRANS: {
ret = true;
} break;
}
if (!Config::dumpText) {
return false;
return ret;
}
if (unTransResultRet.empty()) {
if (unTransResultRet.empty() || (splitTransStat == SplitTagsTranslationStat::NO_SPLIT)) {
DumpGenericText(origText);
}
else {
for (const auto& i : unTransResultRet) {
DumpGenericText(i);
DumpGenericText(i, DumpStrStat::SPLITTED);
}
// 若未翻译部分长度为1且未翻译文本等于原文本则不 dump 到原文本文件
//if (unTransResultRet.size() != 1 || unTransResultRet[0] != origText) {
DumpGenericText(origText, DumpStrStat::SPLITTABLE_ORIG);
//}
}
return false;
return ret;
}
std::string ChangeDumpTextIndex(int changeValue) {

View File

@ -7,7 +7,6 @@ namespace GakumasLocal::Config {
bool dbgMode = false;
bool enabled = true;
bool lazyInit = true;
bool replaceFont = true;
bool forceExportResource = true;
bool textTest = false;
@ -56,7 +55,6 @@ namespace GakumasLocal::Config {
GetConfigItem(dbgMode);
GetConfigItem(enabled);
GetConfigItem(lazyInit);
GetConfigItem(replaceFont);
GetConfigItem(forceExportResource);
GetConfigItem(gameOrientation);

View File

@ -5,7 +5,6 @@ namespace GakumasLocal::Config {
extern bool dbgMode;
extern bool enabled;
extern bool lazyInit;
extern bool replaceFont;
extern bool forceExportResource;
extern int gameOrientation;

View File

@ -47,18 +47,6 @@
#include "../../GakumasLocalify/Log.h"
#include "../../GakumasLocalify/Misc.hpp"
class UnityResolveProgress final {
public:
struct Progress {
long current = 0;
long total = 1;
};
static bool startInit;
static Progress assembliesProgress;
static Progress classProgress;
};
class UnityResolve final {
public:
struct Assembly;
@ -81,16 +69,8 @@ public:
[[nodiscard]] auto Get(const std::string& strClass, const std::string& strNamespace = "*", const std::string& strParent = "*") const -> Class* {
if (!this) return nullptr;
/*
if (lazyInit_ && classes.empty()) {
const auto image = Invoke<void*>("il2cpp_assembly_get_image", address);
ForeachClass(const_cast<Assembly *>(this), image);
}*/
for (const auto pClass : classes) if (strClass == pClass->name && (strNamespace == "*" || pClass->namespaze == strNamespace) && (strParent == "*" || pClass->parent == strParent)) return pClass;
if (lazyInit_) {
return FillClass_Il2ccpp(const_cast<Assembly *>(this), strNamespace.c_str(), strClass.c_str());
}
return nullptr;
return nullptr;
}
};
@ -299,17 +279,14 @@ public:
}
}
static auto Init(void* hmodule, const Mode mode = Mode::Mono, const bool lazyInit = false) -> void {
static auto Init(void* hmodule, const Mode mode = Mode::Mono) -> void {
mode_ = mode;
hmodule_ = hmodule;
lazyInit_ = lazyInit;
if (mode_ == Mode::Il2Cpp) {
if (!lazyInit) UnityResolveProgress::startInit = true;
pDomain = Invoke<void*>("il2cpp_domain_get");
Invoke<void*>("il2cpp_thread_attach", pDomain);
ForeachAssembly();
if (!lazyInit) UnityResolveProgress::startInit = false;
}
else {
pDomain = Invoke<void*>("mono_get_root_domain");
@ -584,11 +561,7 @@ private:
if (mode_ == Mode::Il2Cpp) {
size_t nrofassemblies = 0;
const auto assemblies = Invoke<void**>("il2cpp_domain_get_assemblies", pDomain, &nrofassemblies);
if (!lazyInit_) UnityResolveProgress::assembliesProgress.total = nrofassemblies;
for (auto i = 0; i < nrofassemblies; i++) {
if (!lazyInit_) UnityResolveProgress::assembliesProgress.current = i + 1;
const auto ptr = assemblies[i];
if (ptr == nullptr) continue;
auto assembly = new Assembly{ .address = ptr };
@ -596,9 +569,7 @@ private:
assembly->file = Invoke<const char*>("il2cpp_image_get_filename", image);
assembly->name = Invoke<const char*>("il2cpp_image_get_name", image);
UnityResolve::assembly.push_back(assembly);
if (!lazyInit_) {
ForeachClass(assembly, image);
}
ForeachClass(assembly, image);
}
}
else {
@ -619,60 +590,11 @@ private:
}
}
static auto GetPClassFromUnknownNamespace(void* image, const char* klassName) -> void* {
const auto count = Invoke<int>("il2cpp_image_get_class_count", image);
for (auto i = 0; i < count; i++) {
const auto pClass = Invoke<void*>("il2cpp_image_get_class", image, i);
const auto className = Invoke<const char*>("il2cpp_class_get_name", pClass);
if (strcmp(className, klassName) == 0) {
return pClass;
}
}
return nullptr;
}
static auto FillClass_Il2ccpp(Assembly* assembly, const char* namespaze, const char* klassName) -> Class* {
auto image = Invoke<void*>("il2cpp_assembly_get_image", assembly->address);
void* pClass;
if (strcmp(namespaze, "*") == 0) {
pClass = GetPClassFromUnknownNamespace(image, klassName);
}
else {
pClass = Invoke<void*>("il2cpp_class_from_name", image, namespaze, klassName);
}
if (!pClass && (strlen(namespaze) == 0)) {
pClass = GetPClassFromUnknownNamespace(image, klassName);
}
if (pClass == nullptr) return nullptr;
const auto pAClass = new Class();
pAClass->address = pClass;
pAClass->name = Invoke<const char*>("il2cpp_class_get_name", pClass);
if (const auto pPClass = Invoke<void*>("il2cpp_class_get_parent", pClass)) pAClass->parent = Invoke<const char*>("il2cpp_class_get_name", pPClass);
// pAClass->namespaze = Invoke<const char*>("il2cpp_class_get_namespace", pClass);
pAClass->namespaze = namespaze;
assembly->classes.push_back(pAClass);
ForeachFields(pAClass, pClass);
ForeachMethod(pAClass, pClass);
void* i_class{};
void* iter{};
do {
if ((i_class = Invoke<void*>("il2cpp_class_get_interfaces", pClass, &iter))) {
ForeachFields(pAClass, i_class);
ForeachMethod(pAClass, i_class);
}
} while (i_class);
return pAClass;
}
static auto ForeachClass(Assembly* assembly, void* image) -> void {
// 遍历类
if (mode_ == Mode::Il2Cpp) {
const auto count = Invoke<int>("il2cpp_image_get_class_count", image);
if (!lazyInit_) UnityResolveProgress::classProgress.total = count;
for (auto i = 0; i < count; i++) {
if (!lazyInit_) UnityResolveProgress::classProgress.current = i + 1;
const auto pClass = Invoke<void*>("il2cpp_image_get_class", image, i);
if (pClass == nullptr) continue;
const auto pAClass = new Class();
@ -1471,6 +1393,25 @@ public:
}
}
[[nodiscard]] auto ToWString() const -> std::u16string {
#if WINDOWS_MODE
if (IsBadReadPtr(this, sizeof(String))) return {};
if (IsBadReadPtr(m_firstChar, m_stringLength)) return {};
#endif
if (!this) return {};
try {
// using convert_typeX = std::codecvt_utf8<wchar_t>;
// std::wstring_convert<convert_typeX> converterX;
// return converterX.to_bytes(m_firstChar);
return {chars};
}
catch (std::exception& e) {
std::cout << "String Invoke Error\n";
GakumasLocal::Log::ErrorFmt("String Invoke Error: %s", e.what());
return {};
}
}
auto operator=(const std::string& newString) const -> String* { return New(newString); }
auto operator==(const std::wstring& newString) const -> bool { return Equals(newString); }
@ -2664,7 +2605,6 @@ public:
private:
inline static Mode mode_{};
inline static void* hmodule_;
inline static bool lazyInit_;
inline static std::unordered_map<std::string, void*> address_{};
inline static void* pDomain{};
};

View File

@ -15,10 +15,6 @@ JavaVM* g_javaVM = nullptr;
jclass g_gakumasHookMainClass = nullptr;
jmethodID showToastMethodId = nullptr;
bool UnityResolveProgress::startInit = false;
UnityResolveProgress::Progress UnityResolveProgress::assembliesProgress{};
UnityResolveProgress::Progress UnityResolveProgress::classProgress{};
namespace
{
class AndroidHookInstaller : public GakumasLocal::HookInstaller
@ -118,37 +114,8 @@ Java_io_github_chinosk_gakumas_localify_GakumasHookMain_loadConfig(JNIEnv *env,
}
extern "C"
JNIEXPORT jint JNICALL
JNIEXPORT void JNICALL
Java_io_github_chinosk_gakumas_localify_GakumasHookMain_pluginCallbackLooper(JNIEnv *env,
jclass clazz) {
GakumasLocal::Log::ToastLoop(env, clazz);
if (UnityResolveProgress::startInit) {
return 9;
}
return 0;
}
extern "C"
JNIEXPORT void JNICALL
Java_io_github_chinosk_gakumas_localify_models_NativeInitProgress_pluginInitProgressLooper(
JNIEnv *env, jclass clazz, jobject progress) {
// jclass progressClass = env->GetObjectClass(progress);
static jfieldID startInitFieldID = env->GetStaticFieldID(clazz, "startInit", "Z");
static jmethodID setAssembliesProgressDataMethodID = env->GetMethodID(clazz, "setAssembliesProgressData", "(JJ)V");
static jmethodID setClassProgressDataMethodID = env->GetMethodID(clazz, "setClassProgressData", "(JJ)V");
// jboolean startInit = env->GetStaticBooleanField(clazz, startInitFieldID);
env->SetStaticBooleanField(clazz, startInitFieldID, UnityResolveProgress::startInit);
env->CallVoidMethod(progress, setAssembliesProgressDataMethodID,
UnityResolveProgress::assembliesProgress.current, UnityResolveProgress::assembliesProgress.total);
env->CallVoidMethod(progress, setClassProgressDataMethodID,
UnityResolveProgress::classProgress.current, UnityResolveProgress::classProgress.total);
}

View File

@ -17,7 +17,6 @@ interface ConfigListener {
fun onForceExportResourceChanged(value: Boolean)
fun onTextTestChanged(value: Boolean)
fun onReplaceFontChanged(value: Boolean)
fun onLazyInitChanged(value: Boolean)
fun onEnableFreeCameraChanged(value: Boolean)
fun onTargetFpsChanged(s: CharSequence, start: Int, before: Int, count: Int)
fun onUnlockAllLiveChanged(value: Boolean)
@ -112,11 +111,6 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
pushKeyEvent(KeyEvent(1145, 30))
}
override fun onLazyInitChanged(value: Boolean) {
config.lazyInit = value
saveConfig()
}
override fun onTextTestChanged(value: Boolean) {
config.textTest = value
saveConfig()

View File

@ -34,9 +34,7 @@ import java.util.Locale
import kotlin.system.measureTimeMillis
import io.github.chinosk.gakumas.localify.hookUtils.FileHotUpdater
import io.github.chinosk.gakumas.localify.mainUtils.json
import io.github.chinosk.gakumas.localify.models.NativeInitProgress
import io.github.chinosk.gakumas.localify.models.ProgramConfig
import io.github.chinosk.gakumas.localify.ui.game_attach.InitProgressUI
val TAG = "GakumasLocalify"
@ -51,7 +49,6 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
private var getConfigError: Exception? = null
private var externalFilesChecked: Boolean = false
private var gameActivity: Activity? = null
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
// if (lpparam.packageName == "io.github.chinosk.gakumas.localify") {
@ -138,7 +135,6 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
super.beforeHookedMethod(param)
Log.d(TAG, "onStart")
val currActivity = param.thisObject as Activity
gameActivity = currActivity
if (getConfigError != null) {
showGetConfigFailed(currActivity)
}
@ -152,7 +148,6 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
override fun beforeHookedMethod(param: MethodHookParam) {
Log.d(TAG, "onResume")
val currActivity = param.thisObject as Activity
gameActivity = currActivity
if (getConfigError != null) {
showGetConfigFailed(currActivity)
}
@ -211,30 +206,9 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
private fun startLoop() {
GlobalScope.launch {
val interval = 1000L / 30
var lastFrameStartInit = NativeInitProgress.startInit
val initProgressUI = InitProgressUI()
while (isActive) {
val timeTaken = measureTimeMillis {
val returnValue = pluginCallbackLooper() // plugin main thread loop
if (returnValue == 9) {
NativeInitProgress.startInit = true
}
if (NativeInitProgress.startInit) { // if init, update data
NativeInitProgress.pluginInitProgressLooper(NativeInitProgress)
gameActivity?.let { initProgressUI.updateData(it) }
}
if ((gameActivity != null) && (lastFrameStartInit != NativeInitProgress.startInit)) { // change status
if (NativeInitProgress.startInit) {
initProgressUI.createView(gameActivity!!)
}
else {
initProgressUI.finishLoad(gameActivity!!)
}
}
lastFrameStartInit = NativeInitProgress.startInit
pluginCallbackLooper()
}
delay(interval - timeTaken)
}
@ -439,7 +413,7 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
}
@JvmStatic
external fun pluginCallbackLooper(): Int
external fun pluginCallbackLooper()
}
init {

View File

@ -6,7 +6,6 @@ import kotlinx.serialization.Serializable
data class GakumasConfig (
var dbgMode: Boolean = false,
var enabled: Boolean = true,
var lazyInit: Boolean = true,
var replaceFont: Boolean = true,
var textTest: Boolean = false,
var dumpText: Boolean = false,

View File

@ -1,25 +0,0 @@
package io.github.chinosk.gakumas.localify.models
data class ProgressData(
var current: Long = 0,
var total: Long = 1
)
object NativeInitProgress {
var assembliesProgress = ProgressData()
var classProgress = ProgressData()
var startInit: Boolean = false
fun setAssembliesProgressData(current: Long, total: Long) {
assembliesProgress.current = current
assembliesProgress.total = total
}
fun setClassProgressData(current: Long, total: Long) {
classProgress.current = current
classProgress.total = total
}
@JvmStatic
external fun pluginInitProgressLooper(progress: NativeInitProgress)
}

View File

@ -1,176 +0,0 @@
package io.github.chinosk.gakumas.localify.ui.game_attach
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.res.ColorStateList
import android.content.res.Resources
import android.graphics.Color
import android.graphics.Typeface
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.LayerDrawable
import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.RectShape
import android.util.Log
import android.view.Gravity
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.TextView
import io.github.chinosk.gakumas.localify.TAG
import io.github.chinosk.gakumas.localify.models.NativeInitProgress
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class InitProgressUI {
private var uiCreated = false
private lateinit var rootView: ViewGroup
private lateinit var container: LinearLayout
private lateinit var assembliesProgressBar: ProgressBar
private lateinit var classProgressBar: ProgressBar
private lateinit var titleText: TextView
private lateinit var assembliesProgressText: TextView
private lateinit var classProgressText: TextView
@SuppressLint("SetTextI18n")
fun createView(context: Context) {
if (uiCreated) return
uiCreated = true
val activity = context as? Activity ?: return
rootView = activity.findViewById<ViewGroup>(android.R.id.content)
container = LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT
).apply {
gravity = Gravity.TOP or Gravity.END
marginEnd = 20
marginStart = 20
topMargin = 100
}
setBackgroundColor(Color.WHITE)
setPadding(20, 20, 20, 20)
}
// Set up the container layout
assembliesProgressBar = ProgressBar(context, null, android.R.attr.progressBarStyleHorizontal).apply {
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
).apply {
topMargin = 20
}
max = 100
}
// Set up the class progress bar
classProgressBar = ProgressBar(context, null, android.R.attr.progressBarStyleHorizontal).apply {
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
).apply {
topMargin = 20
}
max = 100
}
assembliesProgressBar.progressTintList = ColorStateList.valueOf(Color.parseColor("#FFF89400"))
classProgressBar.progressTintList = ColorStateList.valueOf(Color.parseColor("#FFF89400"))
// Set up the text views
titleText = TextView(context).apply {
layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT
).apply {
topMargin = 20
gravity = Gravity.CENTER_HORIZONTAL
}
setTextColor(Color.BLACK)
text = "Initializing"
textSize = 20f
setTypeface(typeface, Typeface.BOLD)
}
val textLayout = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT
).apply {
topMargin = 20
}
assembliesProgressText = TextView(context).apply {
layoutParams = textLayout
setTextColor(Color.BLACK)
}
classProgressText = TextView(context).apply {
layoutParams = textLayout
setTextColor(Color.BLACK)
}
// Add container to the root view
context.runOnUiThread {
// Add views to the container
container.addView(titleText)
container.addView(assembliesProgressText)
container.addView(assembliesProgressBar)
container.addView(classProgressText)
container.addView(classProgressBar)
rootView.addView(container)
}
}
@SuppressLint("SetTextI18n")
@OptIn(DelicateCoroutinesApi::class)
fun finishLoad(context: Activity) {
if (!uiCreated) return
uiCreated = false
GlobalScope.launch {
context.runOnUiThread {
assembliesProgressBar.progressTintList = ColorStateList.valueOf(Color.parseColor("#FF28B463"))
classProgressBar.progressTintList = ColorStateList.valueOf(Color.parseColor("#FF28B463"))
titleText.text = "Finished"
}
delay(1500L)
context.runOnUiThread {
rootView.removeView(container)
}
}
}
fun removeView(context: Activity) {
if (!uiCreated) return
uiCreated = false
context.runOnUiThread {
rootView.removeView(container)
}
}
@SuppressLint("SetTextI18n")
fun updateData(context: Activity) {
if (!uiCreated) return
//return
context.runOnUiThread {
val assembliesProgress = NativeInitProgress.assembliesProgress
val classProgress = NativeInitProgress.classProgress
assembliesProgressText.text = "${assembliesProgress.current}/${assembliesProgress.total}"
classProgressText.text = "${classProgress.current}/${classProgress.total}"
assembliesProgressBar.setProgress((assembliesProgress.current * 100 / assembliesProgress.total).toInt(), true)
classProgressBar.setProgress((classProgress.current * 100 / classProgress.total).toInt(), true)
}
}
}

View File

@ -141,14 +141,9 @@ fun HomePage(modifier: Modifier = Modifier,
v -> context?.onEnabledChanged(v)
}
GakuSwitch(modifier, stringResource(R.string.lazy_init), checked = config.value.lazyInit) {
v -> context?.onLazyInitChanged(v)
}
GakuSwitch(modifier, stringResource(R.string.replace_font), checked = config.value.replaceFont) {
v -> context?.onReplaceFontChanged(v)
}
}
}
Spacer(Modifier.height(6.dp))

View File

@ -3,7 +3,6 @@
<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>

View File

@ -3,7 +3,6 @@
<string name="gakumas_localify">Gakumas Localify</string>
<string name="enable_plugin">Enable Plugin (Not Hot Reloadable)</string>
<string name="replace_font">Replace Font</string>
<string name="lazy_init">Fast Initialization (Lazy loading)</string>
<string name="enable_free_camera">Enable Free Camera</string>
<string name="start_game">Start Game / Hot Reload Config</string>
<string name="setFpsTitle">Max FPS (0 is Use Original Settings)</string>