Compare commits

..

No commits in common. "c28e05e271a14c8c739dd4a38f071f3c502ccfee" and "9fede70bec3e8b5e52f3bbdde0efbdfb4bf04780" have entirely different histories.

52 changed files with 1774 additions and 1697 deletions

View File

@ -72,10 +72,8 @@ jobs:
keyStorePassword: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} keyStorePassword: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }} keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }}
buildToolsVersion: 33.0.0 buildToolsVersion: 33.0.0
continue-on-error: true
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
with: with:
name: GakumasLocalify-Signed-apk name: GakumasLocalify-Signed-apk
path: ${{steps.sign_app.outputs.signedFile}} path: ${{steps.sign_app.outputs.signedFile}}
continue-on-error: true

4
.gitignore vendored
View File

@ -15,6 +15,4 @@
local.properties local.properties
/.idea /.idea
/.vs /.vs
/.kotlin /app
/app/debug
/app/release

View File

@ -1,9 +1,8 @@
plugins { plugins {
alias(libs.plugins.androidApplication) id 'com.android.application'
alias(libs.plugins.kotlinAndroid) id 'org.jetbrains.kotlin.android'
alias(libs.plugins.compose.compiler)
alias(libs.plugins.serialization)
} }
android.buildFeatures.buildConfig true
android { android {
namespace 'io.github.chinosk.gakumas.localify' namespace 'io.github.chinosk.gakumas.localify'
@ -15,7 +14,7 @@ android {
minSdk 29 minSdk 29
targetSdk 34 targetSdk 34
versionCode 2 versionCode 2
versionName "v1.2" versionName "v1.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {
@ -36,38 +35,40 @@ android {
minifyEnabled false minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
buildConfigField "boolean", "ENABLE_LOG", "true" buildConfigField "boolean", "ENABLE_LOG", "true"
signingConfig signingConfigs.debug
} }
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_11 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_1_8
} }
kotlinOptions { kotlinOptions {
jvmTarget = '11' jvmTarget = '1.8'
} }
buildFeatures { buildFeatures {
buildConfig true
compose true compose true
prefab true prefab true
} }
composeOptions {
kotlinCompilerExtensionVersion '1.5.1'
}
packaging {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
externalNativeBuild { externalNativeBuild {
cmake { cmake {
path file('src/main/cpp/CMakeLists.txt') path file('src/main/cpp/CMakeLists.txt')
version '3.22.1' version '3.22.1'
} }
} }
packagingOptions {
packaging { pickFirst '**/libxdl.so'
jniLibs { pickFirst '**/libshadowhook.so'
pickFirsts += "**/libxdl.so" exclude 'gakumas-local/gakuen-adapted-translation-data/**'
pickFirsts += "**/libshadowhook.so"
}
resources {
excludes += "**/META-INF/{AL2.0,LGPL2.1}"
excludes += "kotlin/**"
excludes += "**.bin"
} }
dataBinding {
enable true
} }
applicationVariants.configureEach { variant -> applicationVariants.configureEach { variant ->
@ -82,40 +83,38 @@ android {
} }
dependencies { dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.material3)
implementation(libs.material)
implementation(libs.androidx.activity.compose) implementation 'androidx.core:core-ktx:1.13.1'
implementation(libs.androidx.appcompat) implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.2'
implementation(libs.androidx.navigation.compose) implementation 'androidx.compose.material3:material3'
implementation 'com.google.android.material:material:1.12.0'
def composeBom = platform(libs.androidx.compose.bom) implementation "androidx.activity:activity-compose:1.9.0"
implementation "androidx.appcompat:appcompat:1.7.0"
implementation 'androidx.navigation:navigation-compose:2.7.7'
def composeBom = platform('androidx.compose:compose-bom:2024.06.00')
implementation(composeBom) implementation(composeBom)
androidTestImplementation(composeBom) androidTestImplementation(composeBom)
implementation(libs.androidx.runtime) implementation "androidx.compose.runtime:runtime"
implementation(libs.androidx.material) implementation "androidx.compose.material:material"
implementation(libs.androidx.foundation) implementation "androidx.compose.foundation:foundation"
implementation(libs.androidx.foundation.layout) implementation "androidx.compose.foundation:foundation-layout"
implementation(libs.androidx.animation) implementation "androidx.compose.animation:animation"
implementation(libs.androidx.ui.tooling.preview) implementation "androidx.compose.ui:ui-tooling-preview"
androidTestImplementation(libs.androidx.ui.test.junit4) androidTestImplementation "androidx.compose.ui:ui-test-junit4"
debugImplementation(libs.androidx.ui.tooling) debugImplementation "androidx.compose.ui:ui-tooling"
debugImplementation(libs.androidx.ui.test.manifest) debugImplementation "androidx.compose.ui:ui-test-manifest"
implementation(libs.accompanist.pager) implementation "com.google.accompanist:accompanist-pager:0.30.0"
implementation(libs.accompanist.pager.indicators) implementation "com.google.accompanist:accompanist-pager-indicators:0.30.0"
implementation(libs.androidx.lifecycle.viewmodel.compose) implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.8.2"
implementation(libs.coil.compose) implementation "io.coil-kt:coil-compose:2.6.0"
implementation(libs.coil.svg) implementation "io.coil-kt:coil-svg:2.6.0"
implementation(platform(libs.okhttp.bom)) implementation 'io.github.hexhacking:xdl:2.1.1'
implementation(libs.okhttp) implementation 'com.bytedance.android:shadowhook:1.0.9'
implementation(libs.logging.interceptor) compileOnly 'de.robv.android.xposed:api:82'
implementation "org.jetbrains.kotlin:kotlin-reflect:1.9.22"
implementation(libs.xdl) implementation 'com.google.code.gson:gson:2.11.0'
implementation(libs.shadowhook)
compileOnly(libs.xposed.api)
implementation(libs.kotlinx.serialization.json)
} }

View File

@ -19,8 +19,3 @@
# If you keep the line number information, uncomment this to # If you keep the line number information, uncomment this to
# hide the original source file name. # hide the original source file name.
#-renamesourcefileattribute SourceFile #-renamesourcefileattribute SourceFile
-keep class io.github.chinosk.gakumas.localify.GakumasHookMain {
<init>();
native <methods>;
}

View File

@ -12,7 +12,6 @@
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.GakumasLocalify" android:theme="@style/Theme.GakumasLocalify"
android:usesCleartextTraffic="true"
tools:targetApi="31"> tools:targetApi="31">
<meta-data <meta-data
@ -35,6 +34,7 @@
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.GakumasLocalify"> android:theme="@style/Theme.GakumasLocalify">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -42,23 +42,6 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".TranslucentActivity"
android:exported="true"
android:theme="@style/Theme.GakumasLocalify.NoDisplay">
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application> </application>
</manifest> </manifest>

@ -1 +1 @@
Subproject commit a60a171b40b22b04d567ab39a8fd7f571c7921f5 Subproject commit cdd0ad064cf6d3f13107e19b5d08c582d8d0664e

View File

@ -299,11 +299,6 @@ namespace GakumasLocal::HookMain {
void* fontCache = nullptr; void* fontCache = nullptr;
void* GetReplaceFont() { void* GetReplaceFont() {
static std::string fontName = Local::GetBasePath() / "local-files" / "gkamsZHFontMIX.otf";
if (!std::filesystem::exists(fontName)) {
return nullptr;
}
static auto CreateFontFromPath = reinterpret_cast<void (*)(void* self, Il2cppString* path)>( static auto CreateFontFromPath = reinterpret_cast<void (*)(void* self, Il2cppString* path)>(
Il2cppUtils::il2cpp_resolve_icall("UnityEngine.Font::Internal_CreateFontFromPath(UnityEngine.Font,System.String)") Il2cppUtils::il2cpp_resolve_icall("UnityEngine.Font::Internal_CreateFontFromPath(UnityEngine.Font,System.String)")
); );
@ -320,6 +315,7 @@ namespace GakumasLocal::HookMain {
const auto newFont = Font_klass->New<void*>(); const auto newFont = Font_klass->New<void*>();
Font_ctor->Invoke<void>(newFont); Font_ctor->Invoke<void>(newFont);
static std::string fontName = Local::GetBasePath() / "local-files" / "gkamsZHFontMIX.otf";
CreateFontFromPath(newFont, Il2cppString::New(fontName)); CreateFontFromPath(newFont, Il2cppString::New(fontName));
fontCache = newFont; fontCache = newFont;
return newFont; return newFont;
@ -338,10 +334,9 @@ namespace GakumasLocal::HookMain {
static auto UpdateFontAssetData = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll", "TMPro", static auto UpdateFontAssetData = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll", "TMPro",
"TMP_FontAsset", "UpdateFontAssetData"); "TMP_FontAsset", "UpdateFontAssetData");
auto newFont = GetReplaceFont();
if (!newFont) return;
auto fontAsset = get_font->Invoke<void*>(TMP_Textself); auto fontAsset = get_font->Invoke<void*>(TMP_Textself);
if (fontAsset) { auto newFont = GetReplaceFont();
if (fontAsset && newFont) {
set_sourceFontFile->Invoke<void>(fontAsset, newFont); set_sourceFontFile->Invoke<void>(fontAsset, newFont);
if (!updatedFontPtrs.contains(fontAsset)) { if (!updatedFontPtrs.contains(fontAsset)) {
updatedFontPtrs.emplace(fontAsset); updatedFontPtrs.emplace(fontAsset);

View File

@ -4,7 +4,6 @@
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <thread> #include <thread>
#include <queue>
extern JavaVM* g_javaVM; extern JavaVM* g_javaVM;
extern jclass g_gakumasHookMainClass; extern jclass g_gakumasHookMainClass;
@ -25,13 +24,9 @@ extern jmethodID showToastMethodId;
namespace GakumasLocal::Log { namespace GakumasLocal::Log {
namespace {
std::queue<std::string> showingToasts{};
}
std::string StringFormat(const char* fmt, ...) { std::string StringFormat(const char* fmt, ...) {
GetParamStringResult(result); GetParamStringResult(result);
return result; return result.c_str();
} }
void Log(int prio, const char* msg) { void Log(int prio, const char* msg) {
@ -75,7 +70,38 @@ 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 ShowToast(const std::string& text) {
DebugFmt("Toast: %s", text.c_str());
std::thread([text](){
auto env = Misc::GetJNIEnv();
if (!env) {
return;
}
jclass& kotlinClass = g_gakumasHookMainClass;
if (!kotlinClass) {
g_javaVM->DetachCurrentThread();
return;
}
jmethodID& methodId = showToastMethodId;
if (!methodId) {
g_javaVM->DetachCurrentThread();
return;
}
jstring param = env->NewStringUTF(text.c_str());
env->CallStaticVoidMethod(kotlinClass, methodId, param);
g_javaVM->DetachCurrentThread();
}).detach();
}
void ShowToastFmt(const char* fmt, ...) {
GetParamStringResult(result);
ShowToast(result);
}
void ShowToast(const char* text) {
DebugFmt("Toast: %s", text); DebugFmt("Toast: %s", text);
std::thread([text](){ std::thread([text](){
@ -100,44 +126,4 @@ namespace GakumasLocal::Log {
g_javaVM->DetachCurrentThread(); g_javaVM->DetachCurrentThread();
}).detach(); }).detach();
} }
void ShowToast(const std::string& text) {
showingToasts.push(text);
}
void ShowToast(const char* text) {
DebugFmt("Toast: %s", text);
return ShowToast(std::string(text));
}
void ShowToastFmt(const char* fmt, ...) {
GetParamStringResult(result);
ShowToast(result);
}
std::string GetQueuedToast() {
if (showingToasts.empty()) {
return "";
}
const auto ret = showingToasts.front();
showingToasts.pop();
return ret;
}
void ToastLoop(JNIEnv *env, jclass clazz) {
const auto toastString = GetQueuedToast();
if (toastString.empty()) return;
static auto _showToastMethodId = env->GetStaticMethodID(clazz, "showToast", "(Ljava/lang/String;)V");
if (env && clazz && _showToastMethodId) {
jstring param = env->NewStringUTF(toastString.c_str());
env->CallStaticVoidMethod(clazz, _showToastMethodId, param);
env->DeleteLocalRef(param);
}
else {
_showToastMethodId = env->GetStaticMethodID(clazz, "showToast", "(Ljava/lang/String;)V");
}
}
} }

View File

@ -2,7 +2,6 @@
#define GAKUMAS_LOCALIFY_LOG_H #define GAKUMAS_LOCALIFY_LOG_H
#include <string> #include <string>
#include <jni.h>
namespace GakumasLocal::Log { namespace GakumasLocal::Log {
std::string StringFormat(const char* fmt, ...); std::string StringFormat(const char* fmt, ...);
@ -17,8 +16,6 @@ namespace GakumasLocal::Log {
void ShowToast(const char* text); void ShowToast(const char* text);
void ShowToastFmt(const char* fmt, ...); void ShowToastFmt(const char* fmt, ...);
void ToastLoop(JNIEnv *env, jclass clazz);
} }
#endif //GAKUMAS_LOCALIFY_LOG_H #endif //GAKUMAS_LOCALIFY_LOG_H

View File

@ -112,10 +112,3 @@ Java_io_github_chinosk_gakumas_localify_GakumasHookMain_loadConfig(JNIEnv *env,
const std::string configJson = configJsonStrChars; const std::string configJson = configJsonStrChars;
GakumasLocal::Config::LoadConfig(configJson); GakumasLocal::Config::LoadConfig(configJson);
} }
extern "C"
JNIEXPORT void JNICALL
Java_io_github_chinosk_gakumas_localify_GakumasHookMain_pluginCallbackLooper(JNIEnv *env,
jclass clazz) {
GakumasLocal::Log::ToastLoop(env, clazz);
}

View File

@ -1,105 +0,0 @@
package io.github.chinosk.gakumas.localify
import android.app.Activity
import android.content.Intent
import android.widget.Toast
import androidx.core.content.FileProvider
import io.github.chinosk.gakumas.localify.mainUtils.json
import io.github.chinosk.gakumas.localify.models.GakumasConfig
import io.github.chinosk.gakumas.localify.models.ProgramConfig
import io.github.chinosk.gakumas.localify.models.ProgramConfigSerializer
import kotlinx.serialization.SerializationException
import java.io.File
interface IHasConfigItems {
var config: GakumasConfig
var programConfig: ProgramConfig
fun saveConfig() {} // do nothing
}
interface IConfigurableActivity<T : Activity> : IHasConfigItems
fun <T> T.getConfigContent(): String where T : Activity {
val configFile = File(filesDir, "gkms-config.json")
return if (configFile.exists()) {
configFile.readText()
} else {
Toast.makeText(this, "检测到第一次启动,初始化配置文件...", Toast.LENGTH_SHORT).show()
configFile.writeText("{}")
"{}"
}
}
fun <T> T.getProgramConfigContent(
excludes: List<String> = emptyList(),
origProgramConfig: ProgramConfig? = null
): String where T : Activity {
val configFile = File(filesDir, "localify-config.json")
if (excludes.isEmpty()) {
return if (configFile.exists()) {
configFile.readText()
} else {
"{}"
}
} else {
return if (origProgramConfig == null) {
if (configFile.exists()) {
val parsedConfig = json.decodeFromString<ProgramConfig>(configFile.readText())
json.encodeToString(ProgramConfigSerializer(excludes), parsedConfig)
} else {
"{}"
}
} else {
json.encodeToString(ProgramConfigSerializer(excludes), origProgramConfig)
}
}
}
fun <T> T.loadConfig() where T : Activity, T : IHasConfigItems {
val configStr = getConfigContent()
config = try {
json.decodeFromString<GakumasConfig>(configStr)
} catch (e: SerializationException) {
Toast.makeText(this, "配置文件异常: $e", Toast.LENGTH_SHORT).show()
GakumasConfig()
}
saveConfig()
val programConfigStr = getProgramConfigContent()
programConfig = try {
json.decodeFromString<ProgramConfig>(programConfigStr)
} catch (e: SerializationException) {
ProgramConfig()
}
}
fun <T> T.onClickStartGame() where T : Activity, T : IHasConfigItems {
val intent = Intent().apply {
setClassName(
"com.bandainamcoent.idolmaster_gakuen",
"com.google.firebase.MessagingUnityPlayerActivity"
)
putExtra("gkmsData", getConfigContent())
putExtra(
"localData",
getProgramConfigContent(listOf("transRemoteZipUrl", "p"), programConfig)
)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
val updateFile = File(filesDir, "update_trans.zip")
if (updateFile.exists()) {
val dirUri = FileProvider.getUriForFile(
this,
"io.github.chinosk.gakumas.localify.fileprovider",
File(updateFile.absolutePath)
)
intent.setDataAndType(dirUri, "resource/file")
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
startActivity(intent)
}

View File

@ -1,18 +1,20 @@
package io.github.chinosk.gakumas.localify package io.github.chinosk.gakumas.localify
import android.view.KeyEvent import android.view.KeyEvent
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import io.github.chinosk.gakumas.localify.databinding.ActivityMainBinding
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.ProgramConfigViewModel
import io.github.chinosk.gakumas.localify.models.ProgramConfigViewModelFactory
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
interface ConfigListener { interface ConfigListener {
fun onClickStartGame()
fun onEnabledChanged(value: Boolean) fun onEnabledChanged(value: Boolean)
fun onForceExportResourceChanged(value: Boolean) fun onForceExportResourceChanged(value: Boolean)
fun onTextTestChanged(value: Boolean) fun onTextTestChanged(value: Boolean)
@ -53,15 +55,6 @@ interface ConfigListener {
fun onBUseArmCorrectionChanged(value: Boolean) fun onBUseArmCorrectionChanged(value: Boolean)
fun onBUseScaleChanged(value: Boolean) fun onBUseScaleChanged(value: Boolean)
fun onBClickPresetChanged(index: Int) fun onBClickPresetChanged(index: Int)
fun onPCheckBuiltInAssetsChanged(value: Boolean)
fun onPUseRemoteAssetsChanged(value: Boolean)
fun onPCleanLocalAssetsChanged(value: Boolean)
fun onPDelRemoteAfterUpdateChanged(value: Boolean)
fun onPTransRemoteZipUrlChanged(s: CharSequence, start: Int, before: Int, count: Int)
fun mainPageAssetsViewDataUpdate(downloadAbleState: Boolean? = null,
downloadProgressState: Float? = null,
localResourceVersionState: String? = null,
errorString: String? = null)
} }
class UserConfigViewModelFactory(private val initialValue: GakumasConfig) : ViewModelProvider.Factory { class UserConfigViewModelFactory(private val initialValue: GakumasConfig) : ViewModelProvider.Factory {
@ -80,54 +73,52 @@ class UserConfigViewModel(initValue: GakumasConfig) : ViewModel() {
} }
interface ConfigUpdateListener: ConfigListener, IHasConfigItems { interface ConfigUpdateListener: ConfigListener {
var binding: ActivityMainBinding
var factory: UserConfigViewModelFactory var factory: UserConfigViewModelFactory
var viewModel: UserConfigViewModel var viewModel: UserConfigViewModel
var programConfigFactory: ProgramConfigViewModelFactory
var programConfigViewModel: ProgramConfigViewModel
fun pushKeyEvent(event: KeyEvent): Boolean fun pushKeyEvent(event: KeyEvent): Boolean
fun checkConfigAndUpdateView() {} // do nothing fun getConfigContent(): String
// fun saveConfig() fun checkConfigAndUpdateView()
fun saveProgramConfig() fun saveConfig()
override fun onEnabledChanged(value: Boolean) { override fun onEnabledChanged(value: Boolean) {
config.enabled = value binding.config!!.enabled = value
saveConfig() saveConfig()
pushKeyEvent(KeyEvent(1145, 29)) pushKeyEvent(KeyEvent(1145, 29))
} }
override fun onForceExportResourceChanged(value: Boolean) { override fun onForceExportResourceChanged(value: Boolean) {
config.forceExportResource = value binding.config!!.forceExportResource = value
saveConfig() saveConfig()
pushKeyEvent(KeyEvent(1145, 30)) pushKeyEvent(KeyEvent(1145, 30))
} }
override fun onReplaceFontChanged(value: Boolean) { override fun onReplaceFontChanged(value: Boolean) {
config.replaceFont = value binding.config!!.replaceFont = value
saveConfig() saveConfig()
pushKeyEvent(KeyEvent(1145, 30)) pushKeyEvent(KeyEvent(1145, 30))
} }
override fun onTextTestChanged(value: Boolean) { override fun onTextTestChanged(value: Boolean) {
config.textTest = value binding.config!!.textTest = value
saveConfig() saveConfig()
} }
override fun onDumpTextChanged(value: Boolean) { override fun onDumpTextChanged(value: Boolean) {
config.dumpText = value binding.config!!.dumpText = value
saveConfig() saveConfig()
} }
override fun onEnableFreeCameraChanged(value: Boolean) { override fun onEnableFreeCameraChanged(value: Boolean) {
config.enableFreeCamera = value binding.config!!.enableFreeCamera = value
saveConfig() saveConfig()
} }
override fun onUnlockAllLiveChanged(value: Boolean) { override fun onUnlockAllLiveChanged(value: Boolean) {
config.unlockAllLive = value binding.config!!.unlockAllLive = value
saveConfig() saveConfig()
} }
@ -140,7 +131,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
} else { } else {
valueStr.toInt() valueStr.toInt()
} }
config.targetFrameRate = value binding.config!!.targetFrameRate = value
saveConfig() saveConfig()
} }
catch (e: Exception) { catch (e: Exception) {
@ -149,22 +140,22 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
} }
override fun onLiveCustomeDressChanged(value: Boolean) { override fun onLiveCustomeDressChanged(value: Boolean) {
config.enableLiveCustomeDress = value binding.config!!.enableLiveCustomeDress = value
saveConfig() saveConfig()
} }
override fun onLiveCustomeCostumeIdChanged(s: CharSequence, start: Int, before: Int, count: Int) { override fun onLiveCustomeCostumeIdChanged(s: CharSequence, start: Int, before: Int, count: Int) {
config.liveCustomeCostumeId = s.toString() binding.config!!.liveCustomeCostumeId = s.toString()
saveConfig() saveConfig()
} }
override fun onUseCustomeGraphicSettingsChanged(value: Boolean) { override fun onUseCustomeGraphicSettingsChanged(value: Boolean) {
config.useCustomeGraphicSettings = value binding.config!!.useCustomeGraphicSettings = value
saveConfig() saveConfig()
} }
override fun onRenderScaleChanged(s: CharSequence, start: Int, before: Int, count: Int) { override fun onRenderScaleChanged(s: CharSequence, start: Int, before: Int, count: Int) {
config.renderScale = try { binding.config!!.renderScale = try {
s.toString().toFloat() s.toString().toFloat()
} }
catch (e: Exception) { catch (e: Exception) {
@ -174,7 +165,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
} }
override fun onQualitySettingsLevelChanged(s: CharSequence, start: Int, before: Int, count: Int) { override fun onQualitySettingsLevelChanged(s: CharSequence, start: Int, before: Int, count: Int) {
config.qualitySettingsLevel = try { binding.config!!.qualitySettingsLevel = try {
s.toString().toInt() s.toString().toInt()
} }
catch (e: Exception) { catch (e: Exception) {
@ -184,7 +175,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
} }
override fun onVolumeIndexChanged(s: CharSequence, start: Int, before: Int, count: Int) { override fun onVolumeIndexChanged(s: CharSequence, start: Int, before: Int, count: Int) {
config.volumeIndex = try { binding.config!!.volumeIndex = try {
s.toString().toInt() s.toString().toInt()
} }
catch (e: Exception) { catch (e: Exception) {
@ -194,7 +185,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
} }
override fun onMaxBufferPixelChanged(s: CharSequence, start: Int, before: Int, count: Int) { override fun onMaxBufferPixelChanged(s: CharSequence, start: Int, before: Int, count: Int) {
config.maxBufferPixel = try { binding.config!!.maxBufferPixel = try {
s.toString().toInt() s.toString().toInt()
} }
catch (e: Exception) { catch (e: Exception) {
@ -204,12 +195,12 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
} }
override fun onLiveCustomeHeadIdChanged(s: CharSequence, start: Int, before: Int, count: Int) { override fun onLiveCustomeHeadIdChanged(s: CharSequence, start: Int, before: Int, count: Int) {
config.liveCustomeHeadId = s.toString() binding.config!!.liveCustomeHeadId = s.toString()
saveConfig() saveConfig()
} }
override fun onReflectionQualityLevelChanged(s: CharSequence, start: Int, before: Int, count: Int) { override fun onReflectionQualityLevelChanged(s: CharSequence, start: Int, before: Int, count: Int) {
config.reflectionQualityLevel = try { binding.config!!.reflectionQualityLevel = try {
val value = s.toString().toInt() val value = s.toString().toInt()
if (value > 5) 5 else value if (value > 5) 5 else value
} }
@ -220,7 +211,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
} }
override fun onLodQualityLevelChanged(s: CharSequence, start: Int, before: Int, count: Int) { override fun onLodQualityLevelChanged(s: CharSequence, start: Int, before: Int, count: Int) {
config.lodQualityLevel = try { binding.config!!.lodQualityLevel = try {
val value = s.toString().toInt() val value = s.toString().toInt()
if (value > 5) 5 else value if (value > 5) 5 else value
} }
@ -233,44 +224,44 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
override fun onChangePresetQuality(level: Int) { override fun onChangePresetQuality(level: Int) {
when (level) { when (level) {
0 -> { 0 -> {
config.renderScale = 0.5f binding.config!!.renderScale = 0.5f
config.qualitySettingsLevel = 1 binding.config!!.qualitySettingsLevel = 1
config.volumeIndex = 0 binding.config!!.volumeIndex = 0
config.maxBufferPixel = 1024 binding.config!!.maxBufferPixel = 1024
config.lodQualityLevel = 1 binding.config!!.lodQualityLevel = 1
config.reflectionQualityLevel = 1 binding.config!!.reflectionQualityLevel = 1
} }
1 -> { 1 -> {
config.renderScale = 0.59f binding.config!!.renderScale = 0.59f
config.qualitySettingsLevel = 1 binding.config!!.qualitySettingsLevel = 1
config.volumeIndex = 1 binding.config!!.volumeIndex = 1
config.maxBufferPixel = 1440 binding.config!!.maxBufferPixel = 1440
config.lodQualityLevel = 2 binding.config!!.lodQualityLevel = 2
config.reflectionQualityLevel = 2 binding.config!!.reflectionQualityLevel = 2
} }
2 -> { 2 -> {
config.renderScale = 0.67f binding.config!!.renderScale = 0.67f
config.qualitySettingsLevel = 2 binding.config!!.qualitySettingsLevel = 2
config.volumeIndex = 2 binding.config!!.volumeIndex = 2
config.maxBufferPixel = 2538 binding.config!!.maxBufferPixel = 2538
config.lodQualityLevel = 3 binding.config!!.lodQualityLevel = 3
config.reflectionQualityLevel = 3 binding.config!!.reflectionQualityLevel = 3
} }
3 -> { 3 -> {
config.renderScale = 0.77f binding.config!!.renderScale = 0.77f
config.qualitySettingsLevel = 3 binding.config!!.qualitySettingsLevel = 3
config.volumeIndex = 3 binding.config!!.volumeIndex = 3
config.maxBufferPixel = 3384 binding.config!!.maxBufferPixel = 3384
config.lodQualityLevel = 4 binding.config!!.lodQualityLevel = 4
config.reflectionQualityLevel = 4 binding.config!!.reflectionQualityLevel = 4
} }
4 -> { 4 -> {
config.renderScale = 1.0f binding.config!!.renderScale = 1.0f
config.qualitySettingsLevel = 5 binding.config!!.qualitySettingsLevel = 5
config.volumeIndex = 4 binding.config!!.volumeIndex = 4
config.maxBufferPixel = 8190 binding.config!!.maxBufferPixel = 8190
config.lodQualityLevel = 5 binding.config!!.lodQualityLevel = 5
config.reflectionQualityLevel = 5 binding.config!!.reflectionQualityLevel = 5
} }
} }
checkConfigAndUpdateView() checkConfigAndUpdateView()
@ -278,31 +269,38 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
} }
override fun onGameOrientationChanged(checkedId: Int) { override fun onGameOrientationChanged(checkedId: Int) {
if (checkedId in listOf(0, 1, 2)) { when (checkedId) {
config.gameOrientation = checkedId R.id.radioButtonGameDefault -> binding.config!!.gameOrientation = 0
R.id.radioButtonGamePortrait -> binding.config!!.gameOrientation = 1
R.id.radioButtonGameLandscape -> binding.config!!.gameOrientation = 2
else -> {
if (listOf(0, 1, 2).contains(checkedId)) {
binding.config!!.gameOrientation = checkedId
}
}
} }
saveConfig() saveConfig()
} }
override fun onEnableBreastParamChanged(value: Boolean) { override fun onEnableBreastParamChanged(value: Boolean) {
config.enableBreastParam = value binding.config!!.enableBreastParam = value
saveConfig() saveConfig()
checkConfigAndUpdateView() checkConfigAndUpdateView()
} }
override fun onBUseArmCorrectionChanged(value: Boolean) { override fun onBUseArmCorrectionChanged(value: Boolean) {
config.bUseArmCorrection = value binding.config!!.bUseArmCorrection = value
saveConfig() saveConfig()
} }
override fun onBUseScaleChanged(value: Boolean) { override fun onBUseScaleChanged(value: Boolean) {
config.bUseScale = value binding.config!!.bUseScale = value
saveConfig() saveConfig()
checkConfigAndUpdateView() checkConfigAndUpdateView()
} }
override fun onBDampingChanged(s: CharSequence, start: Int, before: Int, count: Int) { override fun onBDampingChanged(s: CharSequence, start: Int, before: Int, count: Int) {
config.bDamping = try { binding.config!!.bDamping = try {
s.toString().toFloat() s.toString().toFloat()
} }
catch (e: Exception) { catch (e: Exception) {
@ -312,7 +310,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
} }
override fun onBStiffnessChanged(s: CharSequence, start: Int, before: Int, count: Int){ override fun onBStiffnessChanged(s: CharSequence, start: Int, before: Int, count: Int){
config.bStiffness = try { binding.config!!.bStiffness = try {
s.toString().toFloat() s.toString().toFloat()
} }
catch (e: Exception) { catch (e: Exception) {
@ -322,7 +320,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
} }
override fun onBSpringChanged(s: CharSequence, start: Int, before: Int, count: Int){ override fun onBSpringChanged(s: CharSequence, start: Int, before: Int, count: Int){
config.bSpring = try { binding.config!!.bSpring = try {
s.toString().toFloat() s.toString().toFloat()
} }
catch (e: Exception) { catch (e: Exception) {
@ -332,7 +330,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
} }
override fun onBPendulumChanged(s: CharSequence, start: Int, before: Int, count: Int){ override fun onBPendulumChanged(s: CharSequence, start: Int, before: Int, count: Int){
config.bPendulum = try { binding.config!!.bPendulum = try {
s.toString().toFloat() s.toString().toFloat()
} }
catch (e: Exception) { catch (e: Exception) {
@ -342,7 +340,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
} }
override fun onBPendulumRangeChanged(s: CharSequence, start: Int, before: Int, count: Int){ override fun onBPendulumRangeChanged(s: CharSequence, start: Int, before: Int, count: Int){
config.bPendulumRange = try { binding.config!!.bPendulumRange = try {
s.toString().toFloat() s.toString().toFloat()
} }
catch (e: Exception) { catch (e: Exception) {
@ -352,7 +350,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
} }
override fun onBAverageChanged(s: CharSequence, start: Int, before: Int, count: Int){ override fun onBAverageChanged(s: CharSequence, start: Int, before: Int, count: Int){
config.bAverage = try { binding.config!!.bAverage = try {
s.toString().toFloat() s.toString().toFloat()
} }
catch (e: Exception) { catch (e: Exception) {
@ -362,7 +360,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
} }
override fun onBRootWeightChanged(s: CharSequence, start: Int, before: Int, count: Int){ override fun onBRootWeightChanged(s: CharSequence, start: Int, before: Int, count: Int){
config.bRootWeight = try { binding.config!!.bRootWeight = try {
s.toString().toFloat() s.toString().toFloat()
} }
catch (e: Exception) { catch (e: Exception) {
@ -372,13 +370,13 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
} }
override fun onBUseLimitChanged(value: Boolean){ override fun onBUseLimitChanged(value: Boolean){
config.bUseLimit = value binding.config!!.bUseLimit = value
saveConfig() saveConfig()
checkConfigAndUpdateView() checkConfigAndUpdateView()
} }
override fun onBLimitXxChanged(s: CharSequence, start: Int, before: Int, count: Int) { override fun onBLimitXxChanged(s: CharSequence, start: Int, before: Int, count: Int) {
config.bLimitXx = try { binding.config!!.bLimitXx = try {
s.toString().toFloat() s.toString().toFloat()
} }
catch (e: Exception) { catch (e: Exception) {
@ -388,7 +386,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
} }
override fun onBLimitXyChanged(s: CharSequence, start: Int, before: Int, count: Int) { override fun onBLimitXyChanged(s: CharSequence, start: Int, before: Int, count: Int) {
config.bLimitXy = try { binding.config!!.bLimitXy = try {
s.toString().toFloat() s.toString().toFloat()
} }
catch (e: Exception) { catch (e: Exception) {
@ -398,7 +396,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
} }
override fun onBLimitYxChanged(s: CharSequence, start: Int, before: Int, count: Int) { override fun onBLimitYxChanged(s: CharSequence, start: Int, before: Int, count: Int) {
config.bLimitYx = try { binding.config!!.bLimitYx = try {
s.toString().toFloat() s.toString().toFloat()
} }
catch (e: Exception) { catch (e: Exception) {
@ -408,7 +406,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
} }
override fun onBLimitYyChanged(s: CharSequence, start: Int, before: Int, count: Int) { override fun onBLimitYyChanged(s: CharSequence, start: Int, before: Int, count: Int) {
config.bLimitYy = try { binding.config!!.bLimitYy = try {
s.toString().toFloat() s.toString().toFloat()
} }
catch (e: Exception) { catch (e: Exception) {
@ -418,7 +416,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
} }
override fun onBLimitZxChanged(s: CharSequence, start: Int, before: Int, count: Int) { override fun onBLimitZxChanged(s: CharSequence, start: Int, before: Int, count: Int) {
config.bLimitZx = try { binding.config!!.bLimitZx = try {
s.toString().toFloat() s.toString().toFloat()
} }
catch (e: Exception) { catch (e: Exception) {
@ -428,7 +426,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
} }
override fun onBLimitZyChanged(s: CharSequence, start: Int, before: Int, count: Int) { override fun onBLimitZyChanged(s: CharSequence, start: Int, before: Int, count: Int) {
config.bLimitZy = try { binding.config!!.bLimitZy = try {
s.toString().toFloat() s.toString().toFloat()
} }
catch (e: Exception) { catch (e: Exception) {
@ -439,7 +437,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
override fun onBScaleChanged(s: CharSequence, start: Int, before: Int, count: Int) { override fun onBScaleChanged(s: CharSequence, start: Int, before: Int, count: Int) {
config.bScale = try { binding.config!!.bScale = try {
s.toString().toFloat() s.toString().toFloat()
} }
catch (e: Exception) { catch (e: Exception) {
@ -469,62 +467,30 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
1f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f) 1f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f)
} }
config.bDamping = setData[0] binding.config!!.bDamping = setData[0]
config.bStiffness = setData[1] binding.config!!.bStiffness = setData[1]
config.bSpring = setData[2] binding.config!!.bSpring = setData[2]
config.bPendulum = setData[3] binding.config!!.bPendulum = setData[3]
config.bPendulumRange = setData[4] binding.config!!.bPendulumRange = setData[4]
config.bAverage = setData[5] binding.config!!.bAverage = setData[5]
config.bRootWeight = setData[6] binding.config!!.bRootWeight = setData[6]
config.bUseLimit = if (setData[7] == 0f) { binding.config!!.bUseLimit = if (setData[7] == 0f) {
false false
} }
else { else {
config.bLimitXx = setData[8] binding.config!!.bLimitXx = setData[8]
config.bLimitXy = setData[9] binding.config!!.bLimitXy = setData[9]
config.bLimitYx = setData[10] binding.config!!.bLimitYx = setData[10]
config.bLimitYy = setData[11] binding.config!!.bLimitYy = setData[11]
config.bLimitZx = setData[12] binding.config!!.bLimitZx = setData[12]
config.bLimitZy = setData[13] binding.config!!.bLimitZy = setData[13]
true true
} }
config.bUseArmCorrection = true binding.config!!.bUseArmCorrection = true
checkConfigAndUpdateView() checkConfigAndUpdateView()
saveConfig() saveConfig()
} }
override fun onPCheckBuiltInAssetsChanged(value: Boolean) {
programConfig.checkBuiltInAssets = value
saveProgramConfig()
}
override fun onPUseRemoteAssetsChanged(value: Boolean) {
programConfig.useRemoteAssets = value
saveProgramConfig()
}
override fun onPCleanLocalAssetsChanged(value: Boolean) {
programConfig.cleanLocalAssets = value
saveProgramConfig()
}
override fun onPDelRemoteAfterUpdateChanged(value: Boolean) {
programConfig.delRemoteAfterUpdate = value
saveProgramConfig()
}
override fun onPTransRemoteZipUrlChanged(s: CharSequence, start: Int, before: Int, count: Int) {
programConfig.transRemoteZipUrl = s.toString()
saveProgramConfig()
}
override fun mainPageAssetsViewDataUpdate(downloadAbleState: Boolean?, downloadProgressState: Float?,
localResourceVersionState: String?, errorString: String?) {
downloadAbleState?.let { programConfigViewModel.downloadAbleState.value = downloadAbleState }
downloadProgressState?.let{ programConfigViewModel.downloadProgressState.value = downloadProgressState }
localResourceVersionState?.let{ programConfigViewModel.localResourceVersionState.value = localResourceVersionState }
errorString?.let{ programConfigViewModel.errorStringState.value = errorString }
}
} }

View File

@ -11,30 +11,22 @@ import android.net.Uri
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log import android.util.Log
import android.view.KeyEvent
import android.view.MotionEvent
import android.widget.Toast
import com.bytedance.shadowhook.ShadowHook import com.bytedance.shadowhook.ShadowHook
import com.bytedance.shadowhook.ShadowHook.ConfigBuilder import com.bytedance.shadowhook.ShadowHook.ConfigBuilder
import de.robv.android.xposed.IXposedHookLoadPackage import de.robv.android.xposed.IXposedHookLoadPackage
import de.robv.android.xposed.IXposedHookZygoteInit import de.robv.android.xposed.IXposedHookZygoteInit
import de.robv.android.xposed.XC_MethodHook import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.XposedHelpers import de.robv.android.xposed.XposedHelpers
import de.robv.android.xposed.callbacks.XC_LoadPackage import de.robv.android.xposed.callbacks.XC_LoadPackage
import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker
import android.view.KeyEvent
import android.view.MotionEvent
import android.widget.Toast
import com.google.gson.Gson
import de.robv.android.xposed.XposedBridge
import io.github.chinosk.gakumas.localify.models.GakumasConfig import io.github.chinosk.gakumas.localify.models.GakumasConfig
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import java.io.File import java.io.File
import java.util.Locale 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.ProgramConfig
val TAG = "GakumasLocalify" val TAG = "GakumasLocalify"
@ -48,23 +40,8 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
private var gkmsDataInited = false private var gkmsDataInited = false
private var getConfigError: Exception? = null private var getConfigError: Exception? = null
private var externalFilesChecked: Boolean = false
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
// if (lpparam.packageName == "io.github.chinosk.gakumas.localify") {
// XposedHelpers.findAndHookMethod(
// "io.github.chinosk.gakumas.localify.MainActivity",
// lpparam.classLoader,
// "showToast",
// String::class.java,
// object : XC_MethodHook() {
// override fun beforeHookedMethod(param: MethodHookParam) {
// Log.d(TAG, "beforeHookedMethod hooked: ${param.args}")
// }
// }
// )
// }
if (lpparam.packageName != targetPackageName) { if (lpparam.packageName != targetPackageName) {
return return
} }
@ -186,7 +163,7 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
requestConfig(app.applicationContext) requestConfig(app.applicationContext)
} }
FilesChecker.initDir(app.filesDir, modulePath) FilesChecker.initAndCheck(app.filesDir, modulePath)
initHook( initHook(
"${app.applicationInfo.nativeLibraryDir}/libil2cpp.so", "${app.applicationInfo.nativeLibraryDir}/libil2cpp.so",
File( File(
@ -198,74 +175,23 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
alreadyInitialized = true alreadyInitialized = true
} }
}) })
startLoop()
}
@OptIn(DelicateCoroutinesApi::class)
private fun startLoop() {
GlobalScope.launch {
val interval = 1000L / 30
while (isActive) {
val timeTaken = measureTimeMillis {
pluginCallbackLooper()
}
delay(interval - timeTaken)
}
}
} }
fun initGkmsConfig(activity: Activity) { fun initGkmsConfig(activity: Activity) {
val intent = activity.intent val intent = activity.intent
val gkmsData = intent.getStringExtra("gkmsData") val gkmsData = intent.getStringExtra("gkmsData")
val programData = intent.getStringExtra("localData")
if (gkmsData != null) { if (gkmsData != null) {
gkmsDataInited = true gkmsDataInited = true
val initConfig = try { val initConfig = try {
json.decodeFromString<GakumasConfig>(gkmsData) Gson().fromJson(gkmsData, GakumasConfig::class.java)
} }
catch (e: Exception) { catch (e: Exception) {
null null
} }
val programConfig = try {
if (programData == null) {
ProgramConfig()
} else {
json.decodeFromString<ProgramConfig>(programData)
}
}
catch (e: Exception) {
null
}
// 清理本地文件
if (programConfig?.cleanLocalAssets == true) {
FilesChecker.cleanAssets()
}
// 检查 files 版本和 assets 版本并更新
if (programConfig?.checkBuiltInAssets == true) {
FilesChecker.initAndCheck(activity.filesDir, modulePath)
}
// 强制导出 assets 文件
if (initConfig?.forceExportResource == true) { if (initConfig?.forceExportResource == true) {
FilesChecker.updateFiles() FilesChecker.updateFiles()
} }
// 使用热更新文件
if (programConfig?.useRemoteAssets == true) {
val dataUri = intent.data
if (dataUri != null) {
if (!externalFilesChecked) {
externalFilesChecked = true
// Log.d(TAG, "dataUri: $dataUri")
FileHotUpdater.updateFilesFromZip(activity, dataUri, activity.filesDir,
programConfig.delRemoteAfterUpdate)
}
}
}
loadConfig(gkmsData) loadConfig(gkmsData)
Log.d(TAG, "gkmsData: $gkmsData") Log.d(TAG, "gkmsData: $gkmsData")
} }
@ -347,7 +273,7 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
fun requestConfig(activity: Context) { fun requestConfig(activity: Context) {
try { try {
val intent = Intent().apply { val intent = Intent().apply {
setClassName("io.github.chinosk.gakumas.localify", "io.github.chinosk.gakumas.localify.TranslucentActivity") setClassName("io.github.chinosk.gakumas.localify", "io.github.chinosk.gakumas.localify.MainActivity")
putExtra("gkmsData", "requestConfig") putExtra("gkmsData", "requestConfig")
flags = FLAG_ACTIVITY_NEW_TASK flags = FLAG_ACTIVITY_NEW_TASK
} }
@ -411,9 +337,6 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
Log.e(TAG, "showToast: $message failed: applicationContext is null") Log.e(TAG, "showToast: $message failed: applicationContext is null")
} }
} }
@JvmStatic
external fun pluginCallbackLooper()
} }
init { init {

View File

@ -1,5 +1,6 @@
package io.github.chinosk.gakumas.localify package io.github.chinosk.gakumas.localify
import SplashScreen
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
@ -7,65 +8,69 @@ import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.KeyEvent import android.view.KeyEvent
import android.widget.Toast import android.widget.Toast
import androidx.compose.runtime.Composable
import androidx.databinding.DataBindingUtil
import com.google.gson.Gson
import com.google.gson.JsonSyntaxException
import io.github.chinosk.gakumas.localify.databinding.ActivityMainBinding
import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker
import io.github.chinosk.gakumas.localify.hookUtils.MainKeyEventDispatcher
import io.github.chinosk.gakumas.localify.models.GakumasConfig
import io.github.chinosk.gakumas.localify.ui.theme.GakumasLocalifyTheme
import java.io.File
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.runtime.Composable import kotlinx.coroutines.flow.MutableStateFlow
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import io.github.chinosk.gakumas.localify.hookUtils.FileHotUpdater import androidx.navigation.compose.NavHost
import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker import androidx.navigation.compose.composable
import io.github.chinosk.gakumas.localify.hookUtils.MainKeyEventDispatcher import androidx.navigation.compose.rememberNavController
import io.github.chinosk.gakumas.localify.mainUtils.json
import io.github.chinosk.gakumas.localify.models.GakumasConfig
import io.github.chinosk.gakumas.localify.models.ProgramConfig
import io.github.chinosk.gakumas.localify.models.ProgramConfigViewModel
import io.github.chinosk.gakumas.localify.models.ProgramConfigViewModelFactory
import io.github.chinosk.gakumas.localify.ui.pages.MainUI import io.github.chinosk.gakumas.localify.ui.pages.MainUI
import io.github.chinosk.gakumas.localify.ui.theme.GakumasLocalifyTheme
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.serialization.encodeToString
import java.io.File
class MainActivity : ComponentActivity(), ConfigUpdateListener, IConfigurableActivity<MainActivity> { class MainActivity : ComponentActivity(), ConfigUpdateListener {
override lateinit var config: GakumasConfig override lateinit var binding: ActivityMainBinding
override lateinit var programConfig: ProgramConfig
override lateinit var factory: UserConfigViewModelFactory override lateinit var factory: UserConfigViewModelFactory
override lateinit var viewModel: UserConfigViewModel override lateinit var viewModel: UserConfigViewModel
override lateinit var programConfigFactory: ProgramConfigViewModelFactory override fun onClickStartGame() {
override lateinit var programConfigViewModel: ProgramConfigViewModel val intent = Intent().apply {
setClassName("com.bandainamcoent.idolmaster_gakuen", "com.google.firebase.MessagingUnityPlayerActivity")
putExtra("gkmsData", getConfigContent())
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
startActivity(intent)
}
private fun showToast(message: String) { private fun showToast(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show() Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
} }
override fun getConfigContent(): String {
val configFile = File(filesDir, "gkms-config.json")
return if (configFile.exists()) {
configFile.readText()
}
else {
showToast("检测到第一次启动,初始化配置文件...")
"{}"
}
}
override fun saveConfig() { override fun saveConfig() {
try { try {
config.pf = false binding.config!!.pf = false
viewModel.configState.value = config.copy( pf = true ) // 更新 UI viewModel.configState.value = binding.config!!.copy( pf = true ) // 更新 UI
} }
catch (e: RuntimeException) { catch (e: RuntimeException) {
Log.d(TAG, e.toString()) Log.d(TAG, e.toString())
} }
val configFile = File(filesDir, "gkms-config.json") val configFile = File(filesDir, "gkms-config.json")
configFile.writeText(json.encodeToString(config)) configFile.writeText(Gson().toJson(binding.config!!))
}
override fun saveProgramConfig() {
try {
programConfig.p = false
programConfigViewModel.configState.value = programConfig.copy( p = true ) // 更新 UI
}
catch (e: RuntimeException) {
Log.d(TAG, e.toString())
}
val configFile = File(filesDir, "localify-config.json")
configFile.writeText(json.encodeToString(programConfig))
} }
fun getVersion(): List<String> { fun getVersion(): List<String> {
@ -92,6 +97,23 @@ class MainActivity : ComponentActivity(), ConfigUpdateListener, IConfigurableAct
startActivity(intent) startActivity(intent)
} }
private fun loadConfig() {
val configStr = getConfigContent()
binding.config = try {
Gson().fromJson(configStr, GakumasConfig::class.java)
}
catch (e: JsonSyntaxException) {
showToast("配置文件异常,已重置: $e")
Gson().fromJson("{}", GakumasConfig::class.java)
}
saveConfig()
}
override fun checkConfigAndUpdateView() {
binding.config = binding.config
binding.notifyChange()
}
override fun pushKeyEvent(event: KeyEvent): Boolean { override fun pushKeyEvent(event: KeyEvent): Boolean {
return dispatchKeyEvent(event) return dispatchKeyEvent(event)
} }
@ -100,32 +122,50 @@ class MainActivity : ComponentActivity(), ConfigUpdateListener, IConfigurableAct
override fun dispatchKeyEvent(event: KeyEvent): Boolean { override fun dispatchKeyEvent(event: KeyEvent): Boolean {
// Log.d(TAG, "${event.keyCode}, ${event.action}") // Log.d(TAG, "${event.keyCode}, ${event.action}")
if (MainKeyEventDispatcher.checkDbgKey(event.keyCode, event.action)) { if (MainKeyEventDispatcher.checkDbgKey(event.keyCode, event.action)) {
val origDbg = config.dbgMode val origDbg = binding.config?.dbgMode
config.dbgMode = !origDbg if (origDbg != null) {
binding.config!!.dbgMode = !origDbg
checkConfigAndUpdateView() checkConfigAndUpdateView()
saveConfig() saveConfig()
showToast("TestMode: ${!origDbg}") showToast("TestMode: ${!origDbg}")
} }
}
return if (event.action == 1145) true else super.dispatchKeyEvent(event) return if (event.action == 1145) true else super.dispatchKeyEvent(event)
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
loadConfig() loadConfig()
binding.listener = this
factory = UserConfigViewModelFactory(config) val requestData = intent.getStringExtra("gkmsData")
if (requestData != null) {
if (requestData == "requestConfig") {
onClickStartGame()
finish()
}
}
factory = UserConfigViewModelFactory(binding.config!!)
viewModel = ViewModelProvider(this, factory)[UserConfigViewModel::class.java] viewModel = ViewModelProvider(this, factory)[UserConfigViewModel::class.java]
programConfigFactory = ProgramConfigViewModelFactory(programConfig,
FileHotUpdater.getZipResourceVersion(File(filesDir, "update_trans.zip").absolutePath).toString()
)
programConfigViewModel = ViewModelProvider(this, programConfigFactory)[ProgramConfigViewModel::class.java]
setContent { setContent {
GakumasLocalifyTheme(dynamicColor = false, darkTheme = false) { GakumasLocalifyTheme(dynamicColor = false) {
MainUI(context = this) MainUI(context = this)
/*
val navController = rememberNavController()
NavHost(navController, startDestination = "splash") {
composable("splash") {
SplashScreen(navController)
} }
composable("main") {
MainUI(context = this@MainActivity)
}
}*/
}
} }
} }
} }
@ -142,57 +182,149 @@ fun getConfigState(context: MainActivity?, previewData: GakumasConfig?): State<G
} }
} }
@Composable /*
fun getProgramConfigState(context: MainActivity?, previewData: ProgramConfig? = null): State<ProgramConfig> { class OldActivity : AppCompatActivity(), ConfigUpdateListener {
return if (context != null) { override lateinit var binding: ActivityMainBinding
context.programConfigViewModel.config.collectAsState() private val TAG = "GakumasLocalify"
override lateinit var factory: UserConfigViewModelFactory // No usage
override lateinit var viewModel: UserConfigViewModel // No usage
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
loadConfig()
binding.listener = this
val requestData = intent.getStringExtra("gkmsData")
if (requestData != null) {
if (requestData == "requestConfig") {
onClickStartGame()
finish()
} }
else { }
val configMSF = MutableStateFlow(previewData ?: ProgramConfig()) showVersion()
configMSF.asStateFlow().collectAsState()
val scrollView: ScrollView = findViewById(R.id.scrollView)
scrollView.viewTreeObserver.addOnScrollChangedListener { onScrollChanged() }
onScrollChanged()
val coordinatorLayout = findViewById<View>(R.id.coordinatorLayout)
coordinatorLayout.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
onScrollChanged()
coordinatorLayout.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
})
}
override fun onClickStartGame() {
val intent = Intent().apply {
setClassName("com.bandainamcoent.idolmaster_gakuen", "com.google.firebase.MessagingUnityPlayerActivity")
putExtra("gkmsData", getConfigContent())
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
startActivity(intent)
}
private fun onScrollChanged() {
val fab: FloatingActionButton = findViewById(R.id.fabStartGame)
val startGameButton: MaterialButton = findViewById(R.id.StartGameButton)
val scrollView: ScrollView = findViewById(R.id.scrollView)
val location = IntArray(2)
startGameButton.getLocationOnScreen(location)
val buttonTop = location[1]
val buttonBottom = buttonTop + startGameButton.height
val scrollViewLocation = IntArray(2)
scrollView.getLocationOnScreen(scrollViewLocation)
val scrollViewTop = scrollViewLocation[1]
val scrollViewBottom = scrollViewTop + scrollView.height
val isButtonVisible = buttonTop >= scrollViewTop && buttonBottom <= scrollViewBottom
if (isButtonVisible) {
fab.hide()
} else {
fab.show()
} }
} }
@Composable private fun showToast(message: String) {
fun getProgramDownloadState(context: MainActivity?): State<Float> { Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
return if (context != null) { }
context.programConfigViewModel.downloadProgress.collectAsState()
override fun getConfigContent(): String {
val configFile = File(filesDir, "gkms-config.json")
return if (configFile.exists()) {
configFile.readText()
} }
else { else {
val configMSF = MutableStateFlow(0f) showToast("检测到第一次启动,初始化配置文件...")
configMSF.asStateFlow().collectAsState() "{}"
} }
} }
@Composable override fun saveConfig() {
fun getProgramDownloadAbleState(context: MainActivity?): State<Boolean> { val configFile = File(filesDir, "gkms-config.json")
return if (context != null) { configFile.writeText(Gson().toJson(binding.config!!))
context.programConfigViewModel.downloadAble.collectAsState()
}
else {
val configMSF = MutableStateFlow(true)
configMSF.asStateFlow().collectAsState()
}
} }
@Composable @SuppressLint("SetTextI18n")
fun getProgramLocalResourceVersionState(context: MainActivity?): State<String> { private fun showVersion() {
return if (context != null) { val titleLabel = findViewById<TextView>(R.id.textViewTitle)
context.programConfigViewModel.localResourceVersion.collectAsState() val versionLabel = findViewById<TextView>(R.id.textViewResVersion)
} var versionText = "unknown"
else {
val configMSF = MutableStateFlow("null") try {
configMSF.asStateFlow().collectAsState() val stream = assets.open("${FilesChecker.localizationFilesDir}/version.txt")
versionText = FilesChecker.convertToString(stream)
val packInfo = packageManager.getPackageInfo(packageName, 0)
val version = packInfo.versionName
val versionCode = packInfo.longVersionCode
titleLabel.text = "${titleLabel.text} $version ($versionCode)"
} }
catch (_: Exception) {}
versionLabel.text = "Assets Version: $versionText"
} }
@Composable private fun loadConfig() {
fun getProgramDownloadErrorStringState(context: MainActivity?): State<String> { val configStr = getConfigContent()
return if (context != null) { binding.config = try {
context.programConfigViewModel.errorString.collectAsState() Gson().fromJson(configStr, GakumasConfig::class.java)
} }
else { catch (e: JsonSyntaxException) {
val configMSF = MutableStateFlow("") showToast("配置文件异常,已重置: $e")
configMSF.asStateFlow().collectAsState() Gson().fromJson("{}", GakumasConfig::class.java)
}
saveConfig()
}
override fun checkConfigAndUpdateView() {
binding.config = binding.config
binding.notifyChange()
}
override fun pushKeyEvent(event: KeyEvent): Boolean {
return dispatchKeyEvent(event)
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
// Log.d(TAG, "${event.keyCode}, ${event.action}")
if (MainKeyEventDispatcher.checkDbgKey(event.keyCode, event.action)) {
val origDbg = binding.config?.dbgMode
if (origDbg != null) {
binding.config!!.dbgMode = !origDbg
checkConfigAndUpdateView()
saveConfig()
showToast("TestMode: ${!origDbg}")
} }
} }
return if (event.action == 1145) true else super.dispatchKeyEvent(event)
}
}
*/

View File

@ -1,24 +0,0 @@
package io.github.chinosk.gakumas.localify
import android.os.Bundle
import androidx.activity.ComponentActivity
import io.github.chinosk.gakumas.localify.models.GakumasConfig
import io.github.chinosk.gakumas.localify.models.ProgramConfig
class TranslucentActivity : ComponentActivity(), IConfigurableActivity<TranslucentActivity> {
override lateinit var config: GakumasConfig
override lateinit var programConfig: ProgramConfig
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loadConfig()
val requestData = intent.getStringExtra("gkmsData")
if (requestData != null) {
if (requestData == "requestConfig") {
onClickStartGame()
finish()
}
}
}
}

View File

@ -1,181 +0,0 @@
package io.github.chinosk.gakumas.localify.hookUtils
import android.app.Activity
import android.net.Uri
import android.util.Log
import io.github.chinosk.gakumas.localify.GakumasHookMain
import io.github.chinosk.gakumas.localify.TAG
import java.io.BufferedReader
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.InputStream
import java.io.InputStreamReader
import java.util.zip.ZipInputStream
object FileHotUpdater {
private fun unzip(zipFile: InputStream, destDir: String, matchNamePrefix: String = "",
replaceMatchNamePrefix: String? = null) {
val buffer = ByteArray(1024)
try {
val folder = File(destDir)
if (!folder.exists()) {
folder.mkdir()
}
val zipIn = ZipInputStream(zipFile)
var entry = zipIn.nextEntry
while (entry != null) {
var writeEntryName = entry.name
if (matchNamePrefix.isNotEmpty()) {
if (!entry.name.startsWith(matchNamePrefix)) {
zipIn.closeEntry()
entry = zipIn.nextEntry
continue
}
replaceMatchNamePrefix?.let {
writeEntryName = replaceMatchNamePrefix + writeEntryName.substring(
matchNamePrefix.length, writeEntryName.length
)
}
}
val filePath = destDir + File.separator + writeEntryName
if (!entry.isDirectory) {
extractFile(zipIn, filePath, buffer)
} else {
val dir = File(filePath)
dir.mkdirs()
}
zipIn.closeEntry()
entry = zipIn.nextEntry
}
zipIn.close()
} catch (e: Exception) {
Log.e(TAG, "unzip error: $e")
}
}
private fun unzip(zipFile: String, destDir: String, matchNamePrefix: String = "") {
return unzip(FileInputStream(zipFile), destDir, matchNamePrefix)
}
private fun extractFile(zipIn: ZipInputStream, filePath: String, buffer: ByteArray) {
val fout = FileOutputStream(filePath)
var length: Int
while (zipIn.read(buffer).also { length = it } > 0) {
fout.write(buffer, 0, length)
}
fout.close()
}
private fun getZipResourcePath(zipFile: InputStream): String? {
try {
val zipIn = ZipInputStream(zipFile)
var entry = zipIn.nextEntry
while (entry != null) {
if (entry.isDirectory) {
if (entry.name.endsWith("local-files/")) {
zipIn.close()
var retPath = File(entry.name, "..").canonicalPath
if (retPath.startsWith("/")) retPath = retPath.substring(1)
return retPath
}
}
zipIn.closeEntry()
entry = zipIn.nextEntry
}
zipIn.close()
}
catch (e: Exception) {
Log.e(TAG, "getZipResourcePath error: $e")
}
return null
}
private fun getZipResourceVersion(zipFile: InputStream, basePath: String): String? {
try {
val targetVersionFilePath = File(basePath, "version.txt").canonicalPath
val zipIn = ZipInputStream(zipFile)
var entry = zipIn.nextEntry
while (entry != null) {
if (!entry.isDirectory) {
if ("/${entry.name}" == targetVersionFilePath) {
Log.d(TAG, "targetVersionFilePath: $targetVersionFilePath")
val reader = BufferedReader(InputStreamReader(zipIn))
val versionContent = reader.use { it.readText() }
Log.d(TAG, "versionContent: $versionContent")
zipIn.close()
return versionContent
}
}
zipIn.closeEntry()
entry = zipIn.nextEntry
}
zipIn.close()
}
catch (e: Exception) {
Log.e(TAG, "getZipResourceVersion error: $e")
}
return null
}
private fun getZipResourceVersion(zipFile: String, basePath: String): String? {
return getZipResourceVersion(FileInputStream(zipFile), basePath)
}
fun getZipResourceVersion(zipFile: String): String? {
return try {
val basePath = getZipResourcePath(FileInputStream(zipFile))
basePath?.let { getZipResourceVersion(zipFile, it) }
}
catch (_: Exception) {
null
}
}
fun updateFilesFromZip(activity: Activity, zipFileUri: Uri, filesDir: File, deleteAfterUpdate: Boolean) {
try {
GakumasHookMain.showToast("Updating files from zip...")
var basePath: String?
activity.contentResolver.openInputStream(zipFileUri).use {
basePath = it?.let { getZipResourcePath(it) }
if (basePath == null) {
Log.e(TAG, "getZipResourcePath failed.")
return@updateFilesFromZip
}
}
/*
var resourceVersion: String?
activity.contentResolver.openInputStream(zipFileUri).use {
resourceVersion = it?.let { getZipResourceVersion(it, basePath!!) }
Log.d(TAG, "resourceVersion: $resourceVersion ($basePath)")
}*/
activity.contentResolver.openInputStream(zipFileUri).use {
it?.let {
unzip(it, File(filesDir, FilesChecker.localizationFilesDir).absolutePath,
basePath!!, "../gakumas-local/")
if (deleteAfterUpdate) {
activity.contentResolver.delete(zipFileUri, null, null)
}
GakumasHookMain.showToast("Update success.")
}
}
}
catch (e: java.io.FileNotFoundException) {
Log.i(TAG, "updateFilesFromZip - file not found: $e")
GakumasHookMain.showToast("Update file not found.")
}
catch (e: Exception) {
Log.e(TAG, "updateFilesFromZip failed: $e")
GakumasHookMain.showToast("Updating files failed: $e")
}
}
}

View File

@ -16,14 +16,10 @@ object FilesChecker {
var filesUpdated = false var filesUpdated = false
fun initAndCheck(fileDir: File, modulePath: String) { fun initAndCheck(fileDir: File, modulePath: String) {
initDir(fileDir, modulePath)
checkFiles()
}
fun initDir(fileDir: File, modulePath: String) {
this.filesDir = fileDir this.filesDir = fileDir
this.modulePath = modulePath this.modulePath = modulePath
checkFiles()
} }
fun checkFiles() { fun checkFiles() {
@ -122,45 +118,4 @@ object FilesChecker {
return stringBuilder.toString() return stringBuilder.toString()
} }
private fun deleteRecursively(file: File): Boolean {
if (file.isDirectory) {
val children = file.listFiles()
if (children != null) {
for (child in children) {
val success = deleteRecursively(child)
if (!success) {
return false
}
}
}
}
return file.delete()
}
fun cleanAssets() {
val pluginBasePath = File(filesDir, localizationFilesDir)
val localFilesDir = File(pluginBasePath, "local-files")
val fontFile = File(localFilesDir, "gkamsZHFontMIX.otf")
val resourceDir = File(localFilesDir, "resource")
val genericTransDir = File(localFilesDir, "genericTrans")
val genericTransFile = File(localFilesDir, "generic.json")
val i18nFile = File(localFilesDir, "localization.json")
if (fontFile.exists()) {
fontFile.delete()
}
if (deleteRecursively(resourceDir)) {
resourceDir.mkdirs()
}
if (deleteRecursively(genericTransDir)) {
genericTransDir.mkdirs()
}
if (genericTransFile.exists()) {
genericTransFile.writeText("{}")
}
if (i18nFile.exists()) {
i18nFile.writeText("{}")
}
}
} }

View File

@ -1,10 +1,11 @@
package io.github.chinosk.gakumas.localify.hookUtils package io.github.chinosk.gakumas.localify.hookUtils
import android.util.Log
import android.view.KeyEvent import android.view.KeyEvent
object MainKeyEventDispatcher { object MainKeyEventDispatcher {
private val targetDbgKeyList: IntArray = intArrayOf(19, 19, 20, 20, 21, 22, 21, 22, 30, 29) private val targetDbgKeyList: IntArray = intArrayOf(19, 19, 20, 20, 21, 22, 21, 22, 30, 29)
private var currentIndex = 0 private var currentIndex = 0;
fun checkDbgKey(code: Int, action: Int): Boolean { fun checkDbgKey(code: Int, action: Int): Boolean {
if (action == KeyEvent.ACTION_UP) return false if (action == KeyEvent.ACTION_UP) return false

View File

@ -1,137 +0,0 @@
package io.github.chinosk.gakumas.localify.mainUtils
import okhttp3.*
import java.io.IOException
import java.io.ByteArrayOutputStream
import java.util.concurrent.TimeUnit
object FileDownloader {
private val client = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(0, TimeUnit.SECONDS)
.readTimeout(0, TimeUnit.SECONDS)
.build()
private var call: Call? = null
fun downloadFile(
url: String,
onDownload: (Float, downloaded: Long, size: Long) -> Unit,
onSuccess: (ByteArray) -> Unit,
onFailed: (Int, String) -> Unit,
checkContentTypes: List<String>? = null
) {
try {
if (call != null) {
onFailed(-1, "Another file is downloading.")
return
}
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
val outputStream = ByteArrayOutputStream()
try {
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)
}
onSuccess(outputStream.toByteArray())
} catch (e: IOException) {
if (call.isCanceled()) {
onFailed(-1, "Download canceled")
} else {
onFailed(-1, e.message ?: "Error reading stream")
}
} finally {
this@FileDownloader.call = null
inputStream.close()
outputStream.close()
}
} ?: run {
this@FileDownloader.call = null
onFailed(-1, "Response body is null")
}
}
})
}
catch (e: Exception) {
onFailed(-1, e.toString())
call = null
}
}
fun cancel() {
call?.cancel()
this@FileDownloader.call = null
}
/**
* return: Status, newString
* Status: 0 - not change, 1 - need check, 2 - modified, 3 - checked
**/
fun checkAndChangeDownloadURL(url: String, forceEdit: Boolean = false): Pair<Int, String> {
if (!url.startsWith("https://github.com/")) { // check github only
return Pair(0, url)
}
if (url.endsWith(".zip")) {
return Pair(0, url)
}
// https://github.com/chinosk6/GakumasTranslationData
// https://github.com/chinosk6/GakumasTranslationData.git
// https://github.com/chinosk6/GakumasTranslationData/archive/refs/heads/main.zip
if (url.endsWith(".git")) {
return Pair(2, "${url.substring(0, url.length - 4)}/archive/refs/heads/main.zip")
}
if (forceEdit) {
return Pair(3, "$url/archive/refs/heads/main.zip")
}
return Pair(1, url)
}
}

View File

@ -1,7 +0,0 @@
package io.github.chinosk.gakumas.localify.mainUtils
import kotlinx.serialization.json.Json
val json = Json {
encodeDefaults = true
}

View File

@ -1,31 +1,24 @@
package io.github.chinosk.gakumas.localify.models package io.github.chinosk.gakumas.localify.models
import kotlinx.serialization.Serializable
@Serializable
data class AboutPageConfig ( data class AboutPageConfig (
val plugin_repo: String = "https://github.com/chinosk6/gakuen-imas-localify", var plugin_repo: String = "https://github.com/chinosk6/gakuen-imas-localify",
val main_contributors: List<MainContributors> = listOf(), var main_contributors: List<MainContributors> = listOf(),
val contrib_img: ContribImg = ContribImg( var contrib_img: ContribImg = ContribImg(
"https://contrib.rocks/image?repo=chinosk6/gakuen-imas-localify", "https://contrib.rocks/image?repo=chinosk6/gakuen-imas-localify",
"https://contrib.rocks/image?repo=chinosk6/GakumasTranslationData" "https://contrib.rocks/image?repo=chinosk6/GakumasTranslationData")
)
) )
@Serializable
data class MainContributors ( data class MainContributors (
val name: String, var name: String,
val links: List<Links> var links: List<Links>
) )
@Serializable
data class ContribImg ( data class ContribImg (
val plugin: String, var plugin: String,
val translation: String var translation: String
) )
@Serializable
data class Links ( data class Links (
val name: String, var name: String,
val link: String var link: String
) )

View File

@ -1,8 +1,6 @@
package io.github.chinosk.gakumas.localify.models package io.github.chinosk.gakumas.localify.models
import kotlinx.serialization.Serializable
@Serializable
data class GakumasConfig ( data class GakumasConfig (
var dbgMode: Boolean = false, var dbgMode: Boolean = false,
var enabled: Boolean = true, var enabled: Boolean = true,

View File

@ -1,41 +0,0 @@
package io.github.chinosk.gakumas.localify.models
import io.github.chinosk.gakumas.localify.mainUtils.json
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.encoding.encodeStructure
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.encodeToJsonElement
import kotlinx.serialization.json.jsonObject
@Serializable
data class ProgramConfig(
var checkBuiltInAssets: Boolean = true,
var transRemoteZipUrl: String = "",
var useRemoteAssets: Boolean = false,
var delRemoteAfterUpdate: Boolean = true,
var cleanLocalAssets: Boolean = false,
var p: Boolean = false
)
class ProgramConfigSerializer(
private val excludes: List<String> = emptyList(),
) : KSerializer<ProgramConfig> {
override val descriptor: SerialDescriptor = ProgramConfig.serializer().descriptor
override fun serialize(encoder: Encoder, value: ProgramConfig) {
val jsonObject = json.encodeToJsonElement(value).jsonObject
encoder.encodeStructure(descriptor) {
jsonObject.keys.forEachIndexed { index, k ->
if (k in excludes) return@forEachIndexed
encodeSerializableElement(descriptor, index, JsonElement.serializer(), jsonObject[k]!!)
}
}
}
override fun deserialize(decoder: Decoder): ProgramConfig {
return ProgramConfig.serializer().deserialize(decoder)
}
}

View File

@ -5,67 +5,18 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
open class CollapsibleBoxViewModel(initiallyBreastExpanded: Boolean = false) : ViewModel() {
open var expanded by mutableStateOf(initiallyBreastExpanded) class CollapsibleBoxViewModel(initiallyExpanded: Boolean = false) : ViewModel() {
var expanded by mutableStateOf(initiallyExpanded)
} }
class BreastCollapsibleBoxViewModel(initiallyBreastExpanded: Boolean = false) : CollapsibleBoxViewModel(initiallyBreastExpanded) { class CollapsibleBoxViewModelFactory(private val initiallyExpanded: Boolean) : ViewModelProvider.Factory {
override var expanded by mutableStateOf(initiallyBreastExpanded)
}
class ResourceCollapsibleBoxViewModel(initiallyBreastExpanded: Boolean = false) : CollapsibleBoxViewModel(initiallyBreastExpanded) {
override var expanded by mutableStateOf(initiallyBreastExpanded)
}
class BreastCollapsibleBoxViewModelFactory(private val initiallyExpanded: Boolean) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T { override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(BreastCollapsibleBoxViewModel::class.java)) { if (modelClass.isAssignableFrom(CollapsibleBoxViewModel::class.java)) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
return BreastCollapsibleBoxViewModel(initiallyExpanded) as T return CollapsibleBoxViewModel(initiallyExpanded) as T
} }
throw IllegalArgumentException("Unknown ViewModel class") throw IllegalArgumentException("Unknown ViewModel class")
} }
} }
class ResourceCollapsibleBoxViewModelFactory(private val initiallyExpanded: Boolean) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(ResourceCollapsibleBoxViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return ResourceCollapsibleBoxViewModel(initiallyExpanded) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
class ProgramConfigViewModelFactory(private val initialValue: ProgramConfig,
private val localResourceVersion: String) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(ProgramConfigViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return ProgramConfigViewModel(initialValue, localResourceVersion) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
class ProgramConfigViewModel(initValue: ProgramConfig, initLocalResourceVersion: String) : ViewModel() {
val configState = MutableStateFlow(initValue)
val config: StateFlow<ProgramConfig> = configState.asStateFlow()
val downloadProgressState = MutableStateFlow(-1f)
val downloadProgress: StateFlow<Float> = downloadProgressState.asStateFlow()
val downloadAbleState = MutableStateFlow(true)
val downloadAble: StateFlow<Boolean> = downloadAbleState.asStateFlow()
val localResourceVersionState = MutableStateFlow(initLocalResourceVersion)
val localResourceVersion: StateFlow<String> = localResourceVersionState.asStateFlow()
val errorStringState = MutableStateFlow("")
val errorString: StateFlow<String> = errorStringState.asStateFlow()
}

View File

@ -34,15 +34,13 @@ fun GakuButton(
shape: Shape = RoundedCornerShape(50.dp), // 用于实现左右两边的半圆角 shape: Shape = RoundedCornerShape(50.dp), // 用于实现左右两边的半圆角
shadowElevation: Dp = 8.dp, // 阴影的高度 shadowElevation: Dp = 8.dp, // 阴影的高度
borderWidth: Dp = 1.dp, // 描边的宽度 borderWidth: Dp = 1.dp, // 描边的宽度
borderColor: Color = Color.Transparent, // 描边的颜色 borderColor: Color = Color.Transparent // 描边的颜色
enabled: Boolean = true
) { ) {
var buttonSize by remember { mutableStateOf(IntSize.Zero) } var buttonSize by remember { mutableStateOf(IntSize.Zero) }
val gradient = remember(buttonSize) { val gradient = remember(buttonSize) {
Brush.linearGradient( Brush.linearGradient(
colors = if (enabled) listOf(Color(0xFFFF5F19), Color(0xFFFFA028)) else colors = listOf(Color(0xFFFF5F19), Color(0xFFFFA028)),
listOf(Color(0xFFF9F9F9), Color(0xFFF0F0F0)),
start = Offset(0f, 0f), start = Offset(0f, 0f),
end = Offset(buttonSize.width.toFloat(), buttonSize.height.toFloat()) // 动态终点 end = Offset(buttonSize.width.toFloat(), buttonSize.height.toFloat()) // 动态终点
) )
@ -50,7 +48,6 @@ fun GakuButton(
Button( Button(
onClick = onClick, onClick = onClick,
enabled = enabled,
colors = ButtonDefaults.buttonColors( colors = ButtonDefaults.buttonColors(
containerColor = Color.Transparent containerColor = Color.Transparent
), ),
@ -64,7 +61,7 @@ fun GakuButton(
.border(borderWidth, borderColor, shape), .border(borderWidth, borderColor, shape),
contentPadding = PaddingValues(0.dp) contentPadding = PaddingValues(0.dp)
) { ) {
Text(text = text, color = if (enabled) Color.White else Color(0xFF111111)) Text(text = text)
} }
} }
@ -72,6 +69,5 @@ fun GakuButton(
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO) @Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
@Composable @Composable
fun GakuButtonPreview() { fun GakuButtonPreview() {
GakuButton(modifier = Modifier.width(80.dp).height(40.dp), text = "Button", onClick = {}, GakuButton(modifier = Modifier.width(80.dp).height(40.dp), text = "Button", onClick = {})
enabled = true)
} }

View File

@ -1,5 +1,3 @@
package io.github.chinosk.gakumas.localify.ui.components
import android.content.res.Configuration.UI_MODE_NIGHT_NO import android.content.res.Configuration.UI_MODE_NIGHT_NO
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*

View File

@ -1,55 +0,0 @@
package io.github.chinosk.gakumas.localify.ui.components
import android.content.res.Configuration.UI_MODE_NIGHT_NO
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@Composable
fun GakuProgressBar(modifier: Modifier = Modifier, progress: Float, isError: Boolean = false) {
val animatedProgress by animateFloatAsState(targetValue = progress, label = "progressAnime")
Row(
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically,
modifier = modifier
) {
if (progress <= 0f) {
LinearProgressIndicator(
modifier = Modifier
.weight(1f)
.clip(RoundedCornerShape(4.dp))
.height(8.dp),
color = if (isError) Color(0xFFE2041B) else Color(0xFFF9C114),
)
}
else {
LinearProgressIndicator(
progress = { animatedProgress },
modifier = Modifier
.weight(1f)
.clip(RoundedCornerShape(4.dp))
.height(8.dp),
color = if (isError) Color(0xFFE2041B) else Color(0xFFF9C114),
)
}
Spacer(modifier = Modifier.width(8.dp))
Text(if (progress > 0f) "${(progress * 100).toInt()}%" else if (isError) "Failed" else "Downloading")
}
}
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
@Composable
fun GakuProgressBarPreview() {
GakuProgressBar(progress = 0.25f)
}

View File

@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.Text
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
import androidx.compose.material3.SwitchDefaults import androidx.compose.material3.SwitchDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -33,7 +34,7 @@ fun GakuSwitch(modifier: Modifier = Modifier,
modifier = Modifier, modifier = Modifier,
colors = SwitchDefaults.colors( colors = SwitchDefaults.colors(
checkedThumbColor = Color(0xFFFFFFFF), checkedThumbColor = Color(0xFFFFFFFF),
checkedTrackColor = Color(0xFFF9C114), checkedTrackColor = Color(0xFFF89400),
uncheckedThumbColor = Color(0xFFFFFFFF), uncheckedThumbColor = Color(0xFFFFFFFF),
uncheckedTrackColor = Color(0xFFCFD8DC), uncheckedTrackColor = Color(0xFFCFD8DC),

View File

@ -2,6 +2,7 @@ package io.github.chinosk.gakumas.localify.ui.components
import android.content.res.Configuration.UI_MODE_NIGHT_NO import android.content.res.Configuration.UI_MODE_NIGHT_NO
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column

View File

@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.* import androidx.compose.runtime.*
@ -30,8 +29,6 @@ fun CollapsibleBox(
viewModel: CollapsibleBoxViewModel = viewModel(), viewModel: CollapsibleBoxViewModel = viewModel(),
showExpand: Boolean = true, showExpand: Boolean = true,
expandState: Boolean? = null, expandState: Boolean? = null,
innerPaddingTopBottom: Dp = 0.dp,
innerPaddingLeftRight: Dp = 0.dp,
content: @Composable () -> Unit content: @Composable () -> Unit
) { ) {
val expanded by viewModel::expanded val expanded by viewModel::expanded
@ -68,8 +65,6 @@ fun CollapsibleBox(
modifier = Modifier modifier = Modifier
.height(animatedHeight) .height(animatedHeight)
.fillMaxWidth() .fillMaxWidth()
.padding(start = innerPaddingLeftRight, end = innerPaddingLeftRight,
top = innerPaddingTopBottom, bottom = innerPaddingTopBottom)
// .fillMaxSize() // .fillMaxSize()
.clickable { .clickable {
if (!expanded && showExpand) { if (!expanded && showExpand) {

View File

@ -24,7 +24,6 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.github.chinosk.gakumas.localify.MainActivity import io.github.chinosk.gakumas.localify.MainActivity
import io.github.chinosk.gakumas.localify.models.GakumasConfig import io.github.chinosk.gakumas.localify.models.GakumasConfig
import io.github.chinosk.gakumas.localify.onClickStartGame
import io.github.chinosk.gakumas.localify.ui.components.GakuTabRow import io.github.chinosk.gakumas.localify.ui.components.GakuTabRow
import io.github.chinosk.gakumas.localify.ui.pages.subPages.AboutPage import io.github.chinosk.gakumas.localify.ui.pages.subPages.AboutPage
import io.github.chinosk.gakumas.localify.ui.pages.subPages.AdvanceSettingsPage import io.github.chinosk.gakumas.localify.ui.pages.subPages.AdvanceSettingsPage

View File

@ -28,10 +28,10 @@ import coil.compose.rememberAsyncImagePainter
import coil.decode.SvgDecoder import coil.decode.SvgDecoder
import coil.request.ImageRequest import coil.request.ImageRequest
import coil.size.Size import coil.size.Size
import com.google.gson.Gson
import io.github.chinosk.gakumas.localify.MainActivity import io.github.chinosk.gakumas.localify.MainActivity
import io.github.chinosk.gakumas.localify.R import io.github.chinosk.gakumas.localify.R
import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker.convertToString import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker.convertToString
import io.github.chinosk.gakumas.localify.mainUtils.json
import io.github.chinosk.gakumas.localify.models.AboutPageConfig import io.github.chinosk.gakumas.localify.models.AboutPageConfig
import io.github.chinosk.gakumas.localify.models.GakumasConfig import io.github.chinosk.gakumas.localify.models.GakumasConfig
import io.github.chinosk.gakumas.localify.ui.components.GakuButton import io.github.chinosk.gakumas.localify.ui.components.GakuButton
@ -48,7 +48,7 @@ fun AboutPage(modifier: Modifier = Modifier,
val dataJsonString = context?.getString(R.string.about_contributors_asset_file)?.let { val dataJsonString = context?.getString(R.string.about_contributors_asset_file)?.let {
convertToString(context.assets?.open(it)) convertToString(context.assets?.open(it))
} }
dataJsonString?.let { json.decodeFromString<AboutPageConfig>(it) } Gson().fromJson(dataJsonString, AboutPageConfig::class.java)
?: AboutPageConfig() ?: AboutPageConfig()
} }

View File

@ -1,6 +1,6 @@
package io.github.chinosk.gakumas.localify.ui.pages.subPages package io.github.chinosk.gakumas.localify.ui.pages.subPages
import io.github.chinosk.gakumas.localify.ui.components.GakuGroupBox import GakuGroupBox
import android.content.res.Configuration.UI_MODE_NIGHT_NO import android.content.res.Configuration.UI_MODE_NIGHT_NO
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -29,8 +29,8 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import io.github.chinosk.gakumas.localify.MainActivity import io.github.chinosk.gakumas.localify.MainActivity
import io.github.chinosk.gakumas.localify.R import io.github.chinosk.gakumas.localify.R
import io.github.chinosk.gakumas.localify.getConfigState import io.github.chinosk.gakumas.localify.getConfigState
import io.github.chinosk.gakumas.localify.models.BreastCollapsibleBoxViewModel import io.github.chinosk.gakumas.localify.models.CollapsibleBoxViewModel
import io.github.chinosk.gakumas.localify.models.BreastCollapsibleBoxViewModelFactory import io.github.chinosk.gakumas.localify.models.CollapsibleBoxViewModelFactory
import io.github.chinosk.gakumas.localify.models.GakumasConfig import io.github.chinosk.gakumas.localify.models.GakumasConfig
import io.github.chinosk.gakumas.localify.ui.components.base.CollapsibleBox import io.github.chinosk.gakumas.localify.ui.components.base.CollapsibleBox
import io.github.chinosk.gakumas.localify.ui.components.GakuButton import io.github.chinosk.gakumas.localify.ui.components.GakuButton
@ -47,8 +47,8 @@ fun AdvanceSettingsPage(modifier: Modifier = Modifier,
val config = getConfigState(context, previewData) val config = getConfigState(context, previewData)
// val scrollState = rememberScrollState() // val scrollState = rememberScrollState()
val breastParamViewModel: BreastCollapsibleBoxViewModel = val breastParamViewModel: CollapsibleBoxViewModel =
viewModel(factory = BreastCollapsibleBoxViewModelFactory(initiallyExpanded = false)) viewModel(factory = CollapsibleBoxViewModelFactory(initiallyExpanded = false))
val keyBoardOptionsDecimal = remember { val keyBoardOptionsDecimal = remember {
KeyboardOptions(keyboardType = KeyboardType.Decimal) KeyboardOptions(keyboardType = KeyboardType.Decimal)
} }

View File

@ -1,8 +1,7 @@
package io.github.chinosk.gakumas.localify.ui.pages.subPages package io.github.chinosk.gakumas.localify.ui.pages.subPages
import io.github.chinosk.gakumas.localify.ui.components.GakuGroupBox import GakuGroupBox
import android.content.res.Configuration.UI_MODE_NIGHT_NO import android.content.res.Configuration.UI_MODE_NIGHT_NO
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@ -17,39 +16,24 @@ import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import io.github.chinosk.gakumas.localify.MainActivity import io.github.chinosk.gakumas.localify.MainActivity
import io.github.chinosk.gakumas.localify.R import io.github.chinosk.gakumas.localify.R
import io.github.chinosk.gakumas.localify.getConfigState import io.github.chinosk.gakumas.localify.getConfigState
import io.github.chinosk.gakumas.localify.getProgramConfigState
import io.github.chinosk.gakumas.localify.getProgramDownloadAbleState
import io.github.chinosk.gakumas.localify.getProgramDownloadErrorStringState
import io.github.chinosk.gakumas.localify.getProgramDownloadState
import io.github.chinosk.gakumas.localify.getProgramLocalResourceVersionState
import io.github.chinosk.gakumas.localify.hookUtils.FileHotUpdater
import io.github.chinosk.gakumas.localify.mainUtils.FileDownloader
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.ResourceCollapsibleBoxViewModelFactory
import io.github.chinosk.gakumas.localify.ui.components.base.CollapsibleBox import io.github.chinosk.gakumas.localify.ui.components.base.CollapsibleBox
import io.github.chinosk.gakumas.localify.ui.components.GakuButton import io.github.chinosk.gakumas.localify.ui.components.GakuButton
import io.github.chinosk.gakumas.localify.ui.components.GakuProgressBar
import io.github.chinosk.gakumas.localify.ui.components.GakuRadio import io.github.chinosk.gakumas.localify.ui.components.GakuRadio
import io.github.chinosk.gakumas.localify.ui.components.GakuSwitch import io.github.chinosk.gakumas.localify.ui.components.GakuSwitch
import io.github.chinosk.gakumas.localify.ui.components.GakuTextInput import io.github.chinosk.gakumas.localify.ui.components.GakuTextInput
import java.io.File
@Composable @Composable
@ -59,13 +43,6 @@ fun HomePage(modifier: Modifier = Modifier,
bottomSpacerHeight: Dp = 120.dp, bottomSpacerHeight: Dp = 120.dp,
screenH: Dp = 1080.dp) { screenH: Dp = 1080.dp) {
val config = getConfigState(context, previewData) val config = getConfigState(context, previewData)
val programConfig = getProgramConfigState(context)
val downloadProgress by getProgramDownloadState(context)
val downloadAble by getProgramDownloadAbleState(context)
val localResourceVersion by getProgramLocalResourceVersionState(context)
val downloadErrorString by getProgramDownloadErrorStringState(context)
// val scrollState = rememberScrollState() // val scrollState = rememberScrollState()
val keyboardOptionsNumber = remember { val keyboardOptionsNumber = remember {
KeyboardOptions(keyboardType = KeyboardType.Number) KeyboardOptions(keyboardType = KeyboardType.Number)
@ -74,57 +51,6 @@ fun HomePage(modifier: Modifier = Modifier,
KeyboardOptions(keyboardType = KeyboardType.Decimal) KeyboardOptions(keyboardType = KeyboardType.Decimal)
} }
val resourceSettingsViewModel: ResourceCollapsibleBoxViewModel =
viewModel(factory = ResourceCollapsibleBoxViewModelFactory(initiallyExpanded = false))
fun onClickDownload() {
context?.mainPageAssetsViewDataUpdate(
downloadAbleState = false,
errorString = "",
downloadProgressState = -1f
)
val (_, newUrl) = FileDownloader.checkAndChangeDownloadURL(programConfig.value.transRemoteZipUrl)
context?.onPTransRemoteZipUrlChanged(newUrl, 0, 0, 0)
FileDownloader.downloadFile(
newUrl,
checkContentTypes = listOf("application/zip", "application/octet-stream"),
onDownload = { progress, _, _ ->
context?.mainPageAssetsViewDataUpdate(downloadProgressState = progress)
},
onSuccess = { byteArray ->
context?.mainPageAssetsViewDataUpdate(
downloadAbleState = true,
errorString = "",
downloadProgressState = -1f
)
val file = File(context?.filesDir, "update_trans.zip")
file.writeBytes(byteArray)
val newFileVersion = FileHotUpdater.getZipResourceVersion(file.absolutePath)
if (newFileVersion != null) {
context?.mainPageAssetsViewDataUpdate(
localResourceVersionState = newFileVersion
)
}
else {
context?.mainPageAssetsViewDataUpdate(
localResourceVersionState = context.getString(
R.string.invalid_zip_file
),
errorString = context.getString(R.string.invalid_zip_file_warn)
)
}
},
onFailed = { code, reason ->
context?.mainPageAssetsViewDataUpdate(
downloadAbleState = true,
errorString = reason,
)
})
}
LazyColumn(modifier = modifier LazyColumn(modifier = modifier
.sizeIn(maxHeight = screenH) .sizeIn(maxHeight = screenH)
@ -149,138 +75,6 @@ fun HomePage(modifier: Modifier = Modifier,
Spacer(Modifier.height(6.dp)) Spacer(Modifier.height(6.dp))
} }
item {
GakuGroupBox(modifier, stringResource(R.string.resource_settings),
contentPadding = 0.dp,
onHeadClick = {
resourceSettingsViewModel.expanded = !resourceSettingsViewModel.expanded
}) {
CollapsibleBox(modifier = modifier,
viewModel = resourceSettingsViewModel
) {
LazyColumn(modifier = modifier
// .padding(8.dp)
.sizeIn(maxHeight = screenH),
// verticalArrangement = Arrangement.spacedBy(12.dp)
) {
item {
GakuSwitch(modifier = modifier.padding(start = 8.dp, end = 8.dp, top = 8.dp),
checked = programConfig.value.checkBuiltInAssets,
text = stringResource(id = R.string.check_built_in_resource)
) { v -> context?.onPCheckBuiltInAssetsChanged(v) }
}
item {
GakuSwitch(modifier = modifier.padding(start = 8.dp, end = 8.dp),
checked = programConfig.value.cleanLocalAssets,
text = stringResource(id = R.string.delete_plugin_resource)
) { v -> context?.onPCleanLocalAssetsChanged(v) }
}
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.useRemoteAssets,
text = stringResource(id = R.string.use_remote_zip_resource)
) { v -> context?.onPUseRemoteAssetsChanged(v) }
CollapsibleBox(modifier = modifier.graphicsLayer(clip = false),
expandState = programConfig.value.useRemoteAssets,
collapsedHeight = 0.dp,
innerPaddingLeftRight = 8.dp,
showExpand = false
) {
GakuSwitch(modifier = modifier,
checked = programConfig.value.delRemoteAfterUpdate,
text = stringResource(id = R.string.del_remote_after_update)
) { v -> context?.onPDelRemoteAfterUpdateChanged(v) }
LazyColumn(modifier = modifier
// .padding(8.dp)
.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.transRemoteZipUrl,
onValueChange = { c -> context?.onPTransRemoteZipUrlChanged(c, 0, 0, 0)},
label = { Text(stringResource(id = R.string.resource_url)) },
keyboardOptions = keyboardOptionsNumber)
if (downloadAble) {
GakuButton(modifier = modifier
.height(40.dp)
.sizeIn(minWidth = 80.dp),
text = stringResource(id = R.string.download),
onClick = { onClickDownload() })
}
else {
GakuButton(modifier = modifier
.height(40.dp)
.sizeIn(minWidth = 80.dp),
text = stringResource(id = R.string.cancel), onClick = {
FileDownloader.cancel()
})
}
}
}
if (downloadProgress >= 0) {
item {
GakuProgressBar(progress = downloadProgress, isError = downloadErrorString.isNotEmpty())
}
}
if (downloadErrorString.isNotEmpty()) {
item {
Text(text = downloadErrorString, color = Color(0xFFE2041B))
}
}
item {
Text(modifier = Modifier
.fillMaxWidth()
.clickable {
val file =
File(context?.filesDir, "update_trans.zip")
context?.mainPageAssetsViewDataUpdate(
localResourceVersionState = FileHotUpdater
.getZipResourceVersion(file.absolutePath)
.toString()
)
}, text = "${stringResource(R.string.downloaded_resource_version)}: $localResourceVersion")
}
item {
Spacer(Modifier.height(0.dp))
}
}
}
}
}
}
}
Spacer(Modifier.height(6.dp))
}
item { item {
GakuGroupBox(modifier = modifier, contentPadding = 0.dp, title = stringResource(R.string.graphic_settings)) { GakuGroupBox(modifier = modifier, contentPadding = 0.dp, title = stringResource(R.string.graphic_settings)) {
LazyColumn(modifier = Modifier LazyColumn(modifier = Modifier
@ -469,7 +263,7 @@ fun HomePage(modifier: Modifier = Modifier,
} }
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO) @Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO, widthDp = 880)
@Composable @Composable
fun HomePagePreview(modifier: Modifier = Modifier, data: GakumasConfig = GakumasConfig()) { fun HomePagePreview(modifier: Modifier = Modifier, data: GakumasConfig = GakumasConfig()) {
HomePage(modifier, previewData = data) HomePage(modifier, previewData = data)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 650 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/white" />
</shape>
</item>
<item>
<bitmap android:src="@drawable/splash"
android:gravity="center"
android:tileMode="disabled"/>
</item>
</layer-list>

File diff suppressed because it is too large Load Diff

View File

@ -63,17 +63,6 @@
<string name="plugin_code">插件本体</string> <string name="plugin_code">插件本体</string>
<string name="contributors">贡献者列表</string> <string name="contributors">贡献者列表</string>
<string name="translation_repository">译文仓库</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="downloaded_resource_version">已下载资源版本</string>
<string name="del_remote_after_update">替换文件后删除下载缓存</string>
<string name="about_contributors_asset_file">about_contributors_zh_cn.json</string> <string name="about_contributors_asset_file">about_contributors_zh_cn.json</string>
</resources> </resources>

View File

@ -63,17 +63,6 @@
<string name="plugin_code">Plugin Code</string> <string name="plugin_code">Plugin Code</string>
<string name="contributors">Contributors</string> <string name="contributors">Contributors</string>
<string name="translation_repository">Translation Repository</string> <string name="translation_repository">Translation Repository</string>
<string name="resource_settings">Resource Settings</string>
<string name="check_built_in_resource">Check Built-in Assets Update</string>
<string name="delete_plugin_resource">Delete Plugin Resource</string>
<string name="use_remote_zip_resource">Use Remote ZIP Resource</string>
<string name="resource_url">Resource URL</string>
<string name="download">Download</string>
<string name="invalid_zip_file">Invalid file</string>
<string name="invalid_zip_file_warn">This file is not a valid ZIP translation resource pack.</string>
<string name="cancel">Cancel</string>
<string name="downloaded_resource_version">Downloaded Version</string>
<string name="del_remote_after_update">Delete Cache File After Update</string>
<string name="about_contributors_asset_file">about_contributors_en.json</string> <string name="about_contributors_asset_file">about_contributors_en.json</string>
</resources> </resources>

View File

@ -1,30 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="Theme.GakumasLocalify" parent="@style/Theme.MaterialComponents.Light.NoActionBar" > <style name="Theme.GakumasLocalify" parent="@style/Theme.MaterialComponents.Light.NoActionBar" />
<item name="android:windowBackground">@drawable/splash_style</item>
</style>
<style name="Theme.GakumasLocalify.NoDisplay" parent="@style/Theme.MaterialComponents.Light.NoActionBar" >
<item name="android:windowFrame">@null</item>
<!-- 设置是否可滑动 -->
<item name="android:windowIsFloating">true</item>
<!-- 设置是否透明 -->
<item name="android:windowIsTranslucent">true</item>
<!-- 无标题 -->
<item name="android:windowNoTitle">true</item>
<!-- 背景 -->
<item name="android:background">@null</item>
<!-- 窗口背景 -->
<item name="android:windowBackground">@android:color/transparent</item>
<!-- 是否变暗 -->
<item name="android:backgroundDimEnabled">false</item>
<!-- 点击空白部分activity不消失 -->
<item name="android:windowCloseOnTouchOutside">true</item>
<!-- 无标题 有的手机设置这行代码-->
<item name="windowNoTitle">true</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowAnimationStyle">@null</item>
<item name="android:windowDisablePreview">true</item>
<item name="android:windowNoDisplay">true</item>
</style>
</resources> </resources>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path name="files" path="." />
</paths>

View File

@ -1,4 +1,5 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins { plugins {
alias(libs.plugins.androidApplication) apply false id 'com.android.application' version '8.2.0' apply false
alias(libs.plugins.kotlinAndroid) apply false id 'org.jetbrains.kotlin.android' version '1.9.0' apply false
} }

View File

@ -1,54 +0,0 @@
[versions]
accompanistPager = "0.30.0"
activityCompose = "1.9.0"
okhttpBom = "4.12.0"
xposedApi = "82"
appcompat = "1.7.0"
coil = "2.6.0"
composeBom = "2024.06.00"
agp = "8.5.0"
coreKtx = "1.13.1"
kotlin = "2.0.0"
lifecycle = "2.8.2"
material = "1.12.0"
navigationCompose = "2.7.7"
xdl = "2.1.1"
shadowhook = "1.0.9"
serialization="1.7.1"
[libraries]
accompanist-pager = { module = "com.google.accompanist:accompanist-pager", version.ref = "accompanistPager" }
accompanist-pager-indicators = { module = "com.google.accompanist:accompanist-pager-indicators", version.ref = "accompanistPager" }
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
androidx-animation = { module = "androidx.compose.animation:animation" }
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" }
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
androidx-foundation = { module = "androidx.compose.foundation:foundation" }
androidx-foundation-layout = { module = "androidx.compose.foundation:foundation-layout" }
androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" }
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle" }
androidx-material = { module = "androidx.compose.material:material" }
androidx-material3 = { module = "androidx.compose.material3:material3" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
androidx-runtime = { module = "androidx.compose.runtime:runtime" }
androidx-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4" }
androidx-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" }
androidx-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
androidx-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor" }
okhttp = { module = "com.squareup.okhttp3:okhttp" }
okhttp-bom = { module = "com.squareup.okhttp3:okhttp-bom", version.ref = "okhttpBom" }
xposed-api = { module = "de.robv.android.xposed:api", version.ref = "xposedApi" }
coil-svg = { module = "io.coil-kt:coil-svg", version.ref = "coil" }
coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" }
material = { module = "com.google.android.material:material", version.ref = "material" }
shadowhook = { module = "com.bytedance.android:shadowhook", version.ref = "shadowhook" }
xdl = { module = "io.github.hexhacking:xdl", version.ref = "xdl" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" }
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }

Binary file not shown.

View File

@ -1,7 +1,6 @@
#Fri May 17 13:19:52 CST 2024
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

282
gradlew vendored Executable file → Normal file
View File

@ -1,7 +1,7 @@
#!/bin/sh #!/usr/bin/env sh
# #
# Copyright © 2015-2021 the original authors. # Copyright 2015 the original author or authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -17,99 +17,67 @@
# #
############################################################################## ##############################################################################
# ##
# Gradle start up script for POSIX generated by Gradle. ## Gradle start up script for UN*X
# ##
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
############################################################################## ##############################################################################
# Attempt to set APP_HOME # Attempt to set APP_HOME
# Resolve links: $0 may be a link # Resolve links: $0 may be a link
app_path=$0 PRG="$0"
# Need this for relative symlinks.
# Need this for daisy-chained symlinks. while [ -h "$PRG" ] ; do
while ls=`ls -ld "$PRG"`
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path link=`expr "$ls" : '.*-> \(.*\)$'`
[ -h "$app_path" ] if expr "$link" : '/.*' > /dev/null; then
do PRG="$link"
ls=$( ls -ld "$app_path" ) else
link=${ls#*' -> '} PRG=`dirname "$PRG"`"/$link"
case $link in #( fi
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
# This is normally unused APP_NAME="Gradle"
# shellcheck disable=SC2034 APP_BASE_NAME=`basename "$0"`
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD="maximum"
warn () { warn () {
echo "$*" echo "$*"
} >&2 }
die () { die () {
echo echo
echo "$*" echo "$*"
echo echo
exit 1 exit 1
} >&2 }
# OS specific support (must be 'true' or 'false'). # OS specific support (must be 'true' or 'false').
cygwin=false cygwin=false
msys=false msys=false
darwin=false darwin=false
nonstop=false nonstop=false
case "$( uname )" in #( case "`uname`" in
CYGWIN* ) cygwin=true ;; #( CYGWIN* )
Darwin* ) darwin=true ;; #( cygwin=true
MSYS* | MINGW* ) msys=true ;; #( ;;
NONSTOP* ) nonstop=true ;; Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@ -119,9 +87,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables # IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java JAVACMD="$JAVA_HOME/jre/sh/java"
else else
JAVACMD=$JAVA_HOME/bin/java JAVACMD="$JAVA_HOME/bin/java"
fi fi
if [ ! -x "$JAVACMD" ] ; then if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@ -130,120 +98,88 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi fi
else else
JAVACMD=java JAVACMD="java"
if ! command -v java >/dev/null 2>&1 which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi fi
fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
case $MAX_FD in #( MAX_FD_LIMIT=`ulimit -H -n`
max*) if [ $? -eq 0 ] ; then
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
# shellcheck disable=SC2039,SC3045 MAX_FD="$MAX_FD_LIMIT"
MAX_FD=$( ulimit -H -n ) || fi
warn "Could not query maximum file descriptor limit" ulimit -n $MAX_FD
esac if [ $? -ne 0 ] ; then
case $MAX_FD in #( warn "Could not set maximum file descriptor limit: $MAX_FD"
'' | soft) :;; #( fi
*) else
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
# shellcheck disable=SC2039,SC3045 fi
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi fi
# Collect all arguments for the java command, stacking in reverse order: # For Darwin, add options to specify how the application appears in the dock
# * args from the command line if $darwin; then
# * the main class name GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
# * -classpath fi
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java # For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=$( cygpath --unix "$JAVACMD" ) JAVACMD=`cygpath --unix "$JAVACMD"`
# Now convert the arguments - kludge to limit ourselves to /bin/sh # We build the pattern for arguments to be converted via cygpath
for arg do ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
if SEP=""
case $arg in #( for dir in $ROOTDIRSRAW ; do
-*) false ;; # don't mess with options #( ROOTDIRS="$ROOTDIRS$SEP$dir"
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath SEP="|"
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # Collect all arguments for the java command, following the shell quoting and substitution rules
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@" exec "$JAVACMD" "$@"

31
gradlew.bat vendored
View File

@ -26,7 +26,6 @@ if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=. if "%DIRNAME%" == "" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@ -41,13 +40,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute if "%ERRORLEVEL%" == "0" goto execute
echo. 1>&2 echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo. 1>&2 echo.
echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation. 1>&2 echo location of your Java installation.
goto fail goto fail
@ -57,11 +56,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. 1>&2 echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo. 1>&2 echo.
echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation. 1>&2 echo location of your Java installation.
goto fail goto fail
@ -76,15 +75,13 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd if "%ERRORLEVEL%"=="0" goto mainEnd
:fail :fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code! rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL% if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
if %EXIT_CODE% equ 0 set EXIT_CODE=1 exit /b 1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd :mainEnd
if "%OS%"=="Windows_NT" endlocal if "%OS%"=="Windows_NT" endlocal

View File

@ -1,17 +1,10 @@
pluginManagement { pluginManagement {
repositories { repositories {
google { google()
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral() mavenCentral()
gradlePluginPortal() gradlePluginPortal()
} }
} }
dependencyResolutionManagement { dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories { repositories {