11 Commits
v1.6 ... v1.6.5

Author SHA1 Message Date
chinosk
06b552a097 Built-in patcher supports Android 15
Source: JingMatrix/LSPatch
2024-12-01 04:51:53 +00:00
chinosk
bd9bcae01d Add login as ios.
Trim version string.
2024-12-01 03:49:58 +00:00
chinosk
8c850ad7db update submodule 2024-11-23 15:19:41 +00:00
chinosk
7bf429336b fix build error, add card name suffixes match 2024-11-22 22:45:52 +00:00
chinosk
c7e3d4f718 Add Japanese UI strings by @reindex-ot
Co-authored-by: Re*Index. (ot_inc) <32851879+reindex-ot@users.noreply.github.com>
2024-09-09 16:49:33 +08:00
chinosk
b74713be78 update version 2024-09-05 20:04:14 +08:00
chinosk
67945c86dd update submodule 2024-09-05 19:28:06 +08:00
chinosk
06a96a450e update README 2024-09-05 19:15:00 +08:00
chinosk
6e512d9380 Fix game crash (#6) 2024-09-05 19:09:47 +08:00
chinosk
f82e73845a delete cache 2024-08-09 20:36:58 +08:00
chinosk
8ddd6f53bc Compatible with Android 10 2024-08-09 20:15:21 +08:00
25 changed files with 462 additions and 19 deletions

View File

@@ -15,7 +15,7 @@
- [x] 卡片信息、TIPS 等部分的文本 hook (`generic`)
- [ ] 更多类型的文件替换
- [ ] LSPatch 集成模式无效
- [x] LSPatch 集成模式无效
... and more

View File

@@ -16,7 +16,7 @@ android {
minSdk 29
targetSdk 34
versionCode 4
versionName "v1.6"
versionName "v1.6.5"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {

Binary file not shown.

Binary file not shown.

3
app/lint.xml Normal file
View File

@@ -0,0 +1,3 @@
<lint>
<issue id="ExtraTranslation" severity="ignore" />
</lint>

View File

@@ -6,6 +6,9 @@
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"

View File

@@ -474,12 +474,41 @@ namespace GakumasLocal::HookMain {
PictureBookLiveThumbnailView_SetData_Orig(self, liveData, isUnlocked, isNew, ct, mtd);
}
void* PictureBookWindowPresenter_instance = nullptr;
std::string PictureBookWindowPresenter_charaId;
DEFINE_HOOK(void*, PictureBookWindowPresenter_GetLiveMusics, (void* self, Il2cppString* charaId, void* mtd)) {
// Log::DebugFmt("GetLiveMusics: %s", charaId->ToString().c_str());
if (Config::unlockAllLive) {
PictureBookWindowPresenter_instance = self;
PictureBookWindowPresenter_charaId = charaId->ToString();
}
return PictureBookWindowPresenter_GetLiveMusics_Orig(self, charaId, mtd);
}
DEFINE_HOOK(void, PictureBookLiveSelectScreenModel_ctor, (void* self, void* transitionParam, void* musics, void* mtd)) {
// Log::DebugFmt("PictureBookLiveSelectScreenModel_ctor");
if (Config::unlockAllLive) {
static auto GetLiveMusics = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.OutGame",
"PictureBookWindowPresenter", "GetLiveMusics");
if (PictureBookWindowPresenter_instance && !PictureBookWindowPresenter_charaId.empty()) {
auto fullMusics = GetLiveMusics->Invoke<void*>(PictureBookWindowPresenter_instance,
Il2cppString::New(PictureBookWindowPresenter_charaId));
return PictureBookLiveSelectScreenModel_ctor_Orig(self, transitionParam, fullMusics, mtd);
}
}
return PictureBookLiveSelectScreenModel_ctor_Orig(self, transitionParam, musics, mtd);
}
bool needRestoreHides = false;
DEFINE_HOOK(void*, PictureBookLiveSelectScreenPresenter_MoveLiveScene, (void* self, void* produceLive,
Il2cppString* characterId, Il2cppString* costumeId, Il2cppString* costumeHeadId)) {
Il2cppString* characterId, Il2cppString* idolCardId, Il2cppString* costumeId, Il2cppString* costumeHeadId, void* mtd)) {
needRestoreHides = false;
Log::InfoFmt("MoveLiveScene: characterId: %s, costumeId: %s, costumeHeadId: %s,",
characterId->ToString().c_str(), costumeId->ToString().c_str(), costumeHeadId->ToString().c_str());
Log::InfoFmt("MoveLiveScene: characterId: %s, idolCardId: %s, costumeId: %s, costumeHeadId: %s,",
characterId->ToString().c_str(), idolCardId->ToString().c_str(), costumeId->ToString().c_str(), costumeHeadId->ToString().c_str());
/*
characterId: hski, costumeId: hski-cstm-0002, costumeHeadId: costume_head_hski-cstm-0002,
@@ -488,12 +517,13 @@ namespace GakumasLocal::HookMain {
if (Config::dbgMode && Config::enableLiveCustomeDress) {
// 修改 LiveFixedData_GetCharacter 可以更改 Loading 角色和演唱者名字,而不变更实际登台人
return PictureBookLiveSelectScreenPresenter_MoveLiveScene_Orig(self, produceLive, characterId,
return PictureBookLiveSelectScreenPresenter_MoveLiveScene_Orig(self, produceLive, characterId, idolCardId,
Config::liveCustomeCostumeId.empty() ? costumeId : Il2cppString::New(Config::liveCustomeCostumeId),
Config::liveCustomeHeadId.empty() ? costumeHeadId : Il2cppString::New(Config::liveCustomeHeadId));
Config::liveCustomeHeadId.empty() ? costumeHeadId : Il2cppString::New(Config::liveCustomeHeadId),
mtd);
}
return PictureBookLiveSelectScreenPresenter_MoveLiveScene_Orig(self, produceLive, characterId, costumeId, costumeHeadId);
return PictureBookLiveSelectScreenPresenter_MoveLiveScene_Orig(self, produceLive, characterId, idolCardId, costumeId, costumeHeadId, mtd);
}
// std::string lastMusicId;
@@ -733,6 +763,32 @@ namespace GakumasLocal::HookMain {
CampusActorController_LateUpdate_Orig(self, mtd);
}
DEFINE_HOOK(bool, PlatformInformation_get_IsAndroid, ()) {
if (Config::loginAsIOS) {
return false;
}
// Log::DebugFmt("PlatformInformation_get_IsAndroid: 0x%x", ret);
return PlatformInformation_get_IsAndroid_Orig();
}
DEFINE_HOOK(bool, PlatformInformation_get_IsIOS, ()) {
if (Config::loginAsIOS) {
return true;
}
// Log::DebugFmt("PlatformInformation_get_IsIOS: 0x%x", ret);
return PlatformInformation_get_IsIOS_Orig();
}
DEFINE_HOOK(Il2cppString*, ApiBase_GetPlatformString, (void* self, void* mtd)) {
if (Config::loginAsIOS) {
return Il2cppString::New("iOS");
}
// auto ret = ApiBase_GetPlatformString_Orig(self, mtd);
// Log::DebugFmt("ApiBase_GetPlatformString: %s", ret->ToString().c_str());
return ApiBase_GetPlatformString_Orig(self, mtd);
}
void UpdateSwingBreastBonesData(void* initializeData) {
if (!Config::enableBreastParam) return;
static auto CampusActorAnimationInitializeData_klass = Il2cppUtils::GetClass("campus-submodule.Runtime.dll", "ActorAnimation",
@@ -883,6 +939,12 @@ namespace GakumasLocal::HookMain {
ADD_HOOK(PictureBookLiveThumbnailView_SetData,
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame.PictureBook",
"PictureBookLiveThumbnailView", "SetDataAsync", {"*", "*", "*", "*"}));
ADD_HOOK(PictureBookWindowPresenter_GetLiveMusics,
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame",
"PictureBookWindowPresenter", "GetLiveMusics"));
ADD_HOOK(PictureBookLiveSelectScreenModel_ctor,
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame",
"PictureBookLiveSelectScreenModel", ".ctor"));
ADD_HOOK(PictureBookLiveSelectScreenPresenter_MoveLiveScene,
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame",
@@ -918,6 +980,22 @@ namespace GakumasLocal::HookMain {
ADD_HOOK(CampusActorController_LateUpdate,
Il2cppUtils::GetMethodPointer("campus-submodule.Runtime.dll", "Campus.Common",
"CampusActorController", "LateUpdate"));
ADD_HOOK(PlatformInformation_get_IsAndroid, Il2cppUtils::GetMethodPointer("Firebase.Platform.dll", "Firebase.Platform",
"PlatformInformation", "get_IsAndroid"));
ADD_HOOK(PlatformInformation_get_IsIOS, Il2cppUtils::GetMethodPointer("Firebase.Platform.dll", "Firebase.Platform",
"PlatformInformation", "get_IsIOS"));
auto api_klass = Il2cppUtils::GetClass("Assembly-CSharp.dll", "Campus.Common.Network", "Api");
if (api_klass) {
// Qua.Network.ApiBase
auto api_parent = UnityResolve::Invoke<Il2cppUtils::Il2CppClassHead*>("il2cpp_class_get_parent", api_klass->address);
if (api_parent) {
// Log::DebugFmt("api_parent at %p, name: %s::%s", api_parent, api_parent->namespaze, api_parent->name);
ADD_HOOK(ApiBase_GetPlatformString, Il2cppUtils::il2cpp_class_get_method_from_name(api_parent, "GetPlatformString", 0)->methodPointer);
}
}
/*
static auto CampusActorController_klass = Il2cppUtils::GetClass("campus-submodule.Runtime.dll",
"Campus.Common", "CampusActorController");

View File

@@ -521,6 +521,17 @@ namespace GakumasLocal::Local {
return false;
}
// 匹配升级卡名
if (auto plusPos = origText.find_last_not_of('+'); plusPos != std::string::npos) {
const auto noPlusText = origText.substr(0, plusPos + 1);
if (const auto iter = genericText.find(noPlusText); iter != genericText.end()) {
size_t plusCount = origText.length() - (plusPos + 1);
*newStr = iter->second + std::string(plusCount, '+');
return true;
}
}
// fmt 文本
auto fmtText = StringParser::ParseItems::parse(origText, false);
if (fmtText.isValid) {

View File

@@ -21,6 +21,8 @@ namespace GakumasLocal::Config {
std::string liveCustomeHeadId = "";
std::string liveCustomeCostumeId = "";
bool loginAsIOS = false;
bool useCustomeGraphicSettings = false;
float renderScale = 0.77f;
int qualitySettingsLevel = 3;
@@ -68,6 +70,7 @@ namespace GakumasLocal::Config {
GetConfigItem(enableLiveCustomeDress);
GetConfigItem(liveCustomeHeadId);
GetConfigItem(liveCustomeCostumeId);
GetConfigItem(loginAsIOS);
GetConfigItem(useCustomeGraphicSettings);
GetConfigItem(renderScale);
GetConfigItem(qualitySettingsLevel);

View File

@@ -19,6 +19,8 @@ namespace GakumasLocal::Config {
extern std::string liveCustomeHeadId;
extern std::string liveCustomeCostumeId;
extern bool loginAsIOS;
extern bool useCustomeGraphicSettings;
extern float renderScale;
extern int qualitySettingsLevel;

View File

@@ -17,6 +17,7 @@ import kotlinx.coroutines.runBlocking
interface ConfigListener {
fun onEnabledChanged(value: Boolean)
fun onForceExportResourceChanged(value: Boolean)
fun onLoginAsIOSChanged(value: Boolean)
fun onTextTestChanged(value: Boolean)
fun onReplaceFontChanged(value: Boolean)
fun onLazyInitChanged(value: Boolean)
@@ -115,6 +116,11 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
pushKeyEvent(KeyEvent(1145, 30))
}
override fun onLoginAsIOSChanged(value: Boolean) {
config.loginAsIOS = value
saveConfig()
}
override fun onReplaceFontChanged(value: Boolean) {
config.replaceFont = value
saveConfig()

View File

@@ -1,19 +1,29 @@
package io.github.chinosk.gakumas.localify
import android.Manifest
import android.content.ContentValues
import android.content.Context
import android.content.pm.PackageInstaller
import android.content.pm.PackageManager
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.provider.MediaStore
import android.provider.OpenableColumns
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.IntentSenderRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import io.github.chinosk.gakumas.localify.mainUtils.IOnShell
import io.github.chinosk.gakumas.localify.mainUtils.LSPatchUtils
@@ -29,11 +39,13 @@ import kotlinx.coroutines.withContext
import org.lsposed.patch.LSPatch
import org.lsposed.patch.util.Logger
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.InputStream
import java.io.OutputStream
import java.nio.file.Files
import java.nio.file.attribute.PosixFilePermissions
import java.util.concurrent.CountDownLatch
interface PatchCallback {
@@ -99,6 +111,137 @@ class PatchActivity : ComponentActivity() {
private var reservePatchFiles: Boolean = false
var patchCallback: PatchCallback? = null
private val writePermissionLauncher = registerForActivityResult(
ActivityResultContracts.StartIntentSenderForResult()
) { result ->
if (result.resultCode != RESULT_OK) {
Toast.makeText(this, "Permission Request Failed.", Toast.LENGTH_SHORT).show()
finish()
}
}
private val writePermissionLauncherQ = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (!isGranted) {
Toast.makeText(this, "Permission Request Failed.", Toast.LENGTH_SHORT).show()
finish()
}
}
private fun checkAndRequestWritePermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
/*
// 针对 API 级别 30 及以上使用 MediaStore.createWriteRequest
val uri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val intentSender = MediaStore.createWriteRequest(contentResolver, listOf(uri)).intentSender
writePermissionLauncher.launch(IntentSenderRequest.Builder(intentSender).build())*/
}
else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
// 请求 WRITE_EXTERNAL_STORAGE 权限
writePermissionLauncherQ.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
}
}
private fun writeFileToDownloadFolder(
sourceFile: File,
targetFolder: String,
targetFileName: String
): Boolean {
val downloadDirectory = Environment.DIRECTORY_DOWNLOADS
val relativePath = "$downloadDirectory/$targetFolder/"
val resolver = contentResolver
// 检查文件是否已经存在
val existingUri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val query = resolver.query(
existingUri,
arrayOf(MediaStore.Files.FileColumns._ID),
"${MediaStore.Files.FileColumns.RELATIVE_PATH}=? AND ${MediaStore.Files.FileColumns.DISPLAY_NAME}=?",
arrayOf(relativePath, targetFileName),
null
)
query?.use {
if (it.moveToFirst()) {
// 如果文件存在,则删除
val id = it.getLong(it.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID))
val deleteUri = MediaStore.Files.getContentUri("external", id)
resolver.delete(deleteUri, null, null)
Log.d(patchTag, "query delete: $deleteUri")
}
}
val contentValues = ContentValues().apply {
put(MediaStore.Downloads.DISPLAY_NAME, targetFileName)
put(MediaStore.Downloads.MIME_TYPE, "application/octet-stream")
put(MediaStore.Downloads.RELATIVE_PATH, relativePath)
}
var uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
Log.d(patchTag, "insert uri: $uri")
if (uri == null) {
val latch = CountDownLatch(1)
val downloadDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val downloadSaveDirectory = File(downloadDirectory, targetFolder)
val downloadSaveFile = File(downloadSaveDirectory, targetFileName)
MediaScannerConnection.scanFile(this, arrayOf(downloadSaveFile.absolutePath),
null
) { _, _ ->
Log.d(patchTag, "scanFile finished.")
latch.countDown()
}
latch.await()
uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
if (uri == null) {
Log.e(patchTag, "uri is still null")
return false
}
}
return try {
resolver.openOutputStream(uri)?.use { outputStream ->
FileInputStream(sourceFile).use { inputStream ->
inputStream.copyTo(outputStream)
}
}
contentValues.clear()
contentValues.put(MediaStore.Downloads.IS_PENDING, 0)
resolver.update(uri, contentValues, null, null)
true
} catch (e: Exception) {
resolver.delete(uri, null, null)
e.printStackTrace()
false
}
}
private fun deleteFileInDownloadFolder(targetFolder: String, targetFileName: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val selection =
"${MediaStore.MediaColumns.RELATIVE_PATH} = ? AND ${MediaStore.MediaColumns.DISPLAY_NAME} = ?"
val selectionArgs =
arrayOf("${Environment.DIRECTORY_DOWNLOADS}/$targetFolder/", targetFileName)
val uri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
contentResolver.delete(uri, selection, selectionArgs)
}
else {
val file = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "$targetFolder/$targetFileName")
if (file.exists()) {
if (file.delete()) {
// Toast.makeText(this, "文件已删除", Toast.LENGTH_SHORT).show()
}
}
}
}
private fun handleSelectedFile(uri: Uri) {
val fileName = uri.path?.substringAfterLast('/')
if (fileName != null) {
@@ -110,6 +253,7 @@ class PatchActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
outputDir = "${filesDir.absolutePath}/output"
// ShizukuApi.init()
checkAndRequestWritePermission()
setContent {
GakumasLocalifyTheme(dynamicColor = false, darkTheme = false) {
@@ -414,7 +558,38 @@ class PatchActivity : ComponentActivity() {
return movedFiles
}
suspend fun installSplitApks(context: Context, apkFiles: List<File>, reservePatchFiles: Boolean,
private fun generateNonce(size: Int): String {
val nonceScope = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
val scopeSize = nonceScope.length
val nonceItem: (Int) -> Char = { nonceScope[(scopeSize * Math.random()).toInt()] }
return Array(size, nonceItem).joinToString("")
}
fun saveFilesToDownload(context: PatchActivity, apkFiles: List<File>, targetFolder: String,
isMove: Boolean): List<String>? {
val ret: MutableList<String> = mutableListOf()
apkFiles.forEach { f ->
val success = context.writeFileToDownloadFolder(f, targetFolder, f.name)
if (success) {
ret.add(f.name)
}
else {
val newName = "${generateNonce(6)}${f.name}"
val success2 = context.writeFileToDownloadFolder(f, targetFolder,
newName)
if (!success2) {
return null
}
ret.add(newName)
}
if (isMove) {
f.delete()
}
}
return ret
}
suspend fun installSplitApks(context: PatchActivity, apkFiles: List<File>, reservePatchFiles: Boolean,
patchCallback: PatchCallback?): Pair<Int, String?> {
Log.i(TAG, "Perform install patched apks")
var status = PackageInstaller.STATUS_FAILURE
@@ -424,13 +599,27 @@ class PatchActivity : ComponentActivity() {
runCatching {
val sdcardPath = Environment.getExternalStorageDirectory().path
val targetDirectory = File(sdcardPath, "Download/gkms_local_patch")
val savedFiles = saveFileTo(apkFiles, targetDirectory, true, false)
patchCallback?.onLog("Patched files: $savedFiles")
// val savedFiles = saveFileTo(apkFiles, targetDirectory, true, false)
val savedFileNames = saveFilesToDownload(context, apkFiles, "gkms_local_patch", true)
if (savedFileNames == null) {
status = PackageInstaller.STATUS_FAILURE
message = "Save files failed."
return@runCatching
}
// patchCallback?.onLog("Patched files: $savedFiles")
patchCallback?.onLog("Patched files: $apkFiles")
if (!ShizukuApi.isPermissionGranted) {
status = PackageInstaller.STATUS_FAILURE
message = "Shizuku Not Ready."
if (!reservePatchFiles) savedFiles.forEach { file -> if (file.exists()) file.delete() }
// if (!reservePatchFiles) savedFiles.forEach { file -> if (file.exists()) file.delete() }
if (!reservePatchFiles) {
savedFileNames.forEach { f ->
context.deleteFileInDownloadFolder("gkms_local_patch", f)
}
}
return@runCatching
}
@@ -455,16 +644,26 @@ class PatchActivity : ComponentActivity() {
val action = if (reservePatchFiles) "cp" else "mv"
val copyFilesCmd: MutableList<String> = mutableListOf()
val movedFiles: MutableList<String> = mutableListOf()
savedFileNames.forEach { file ->
val movedFileName = "$installDS/${file}"
movedFiles.add(movedFileName)
val dlSaveFileName = File(targetDirectory, file)
copyFilesCmd.add("$action ${dlSaveFileName.absolutePath} $movedFileName")
}
/*
savedFiles.forEach { file ->
val movedFileName = "$installDS/${file.name}"
movedFiles.add(movedFileName)
copyFilesCmd.add("$action ${file.absolutePath} $movedFileName")
}
val moveFileCommand = "mkdir $installDS && " +
"chmod 777 $installDS && " +
*/
val createDirCommand = "mkdir $installDS"
val moveFileCommand = "chmod 777 $installDS && " +
copyFilesCmd.joinToString(" && ")
Log.d(TAG, "moveFileCommand: $moveFileCommand")
ShizukuShell(mutableListOf(), createDirCommand, ioShell).exec().destroy()
val cpFileShell = ShizukuShell(mutableListOf(), moveFileCommand, ioShell)
cpFileShell.exec()
cpFileShell.destroy()

View File

@@ -84,7 +84,7 @@ object FilesChecker {
for (i in assets.list(localizationFilesDir)!!) {
if (i.toString() == "version.txt") {
val stream = assets.open("$localizationFilesDir/$i")
return convertToString(stream)
return convertToString(stream).trim()
}
}
return "0.0"
@@ -96,7 +96,7 @@ object FilesChecker {
val versionFile = File(pluginFilesDir, "version.txt")
if (!versionFile.exists()) return "0.0"
return versionFile.readText()
return versionFile.readText().trim()
}
fun convertToString(inputStream: InputStream?): String {

View File

@@ -23,7 +23,7 @@ class ShizukuShell(private var mOutput: MutableList<String>, private var mComman
val isBusy: Boolean
get() = mOutput.size > 0 && mOutput[mOutput.size - 1] != "aShell: Finish"
fun exec() {
fun exec(): ShizukuShell {
try {
Log.i(shellTag, "Execute: $mCommand")
shellCallback?.onShellLine(mCommand)
@@ -66,6 +66,7 @@ class ShizukuShell(private var mOutput: MutableList<String>, private var mComman
mProcess!!.waitFor()
} catch (ignored: Exception) {
}
return this
}
fun destroy() {

View File

@@ -19,6 +19,8 @@ data class GakumasConfig (
var liveCustomeHeadId: String = "",
var liveCustomeCostumeId: String = "",
var loginAsIOS: Boolean = false,
var useCustomeGraphicSettings: Boolean = false,
var renderScale: Float = 0.77f,
var qualitySettingsLevel: Int = 3,

View File

@@ -27,7 +27,7 @@ import java.io.File
@Composable
fun InstallDiag(context: Context?, apkFiles: List<File>, patchCallback: PatchCallback?, reservePatchFiles: Boolean,
fun InstallDiag(context: PatchActivity?, apkFiles: List<File>, patchCallback: PatchCallback?, reservePatchFiles: Boolean,
onFinish: (Int, String?) -> Unit) {
// val scope = rememberCoroutineScope()
// var uninstallFirst by remember { mutableStateOf(ShizukuApi.isPackageInstalledWithoutPatch(patchApp.app.packageName)) }

View File

@@ -86,6 +86,10 @@ fun AdvanceSettingsPage(modifier: Modifier = Modifier,
GakuSwitch(modifier, stringResource(R.string.force_export_resource), checked = config.value.forceExportResource) {
v -> context?.onForceExportResourceChanged(v)
}
GakuSwitch(modifier, stringResource(R.string.login_as_ios), checked = config.value.loginAsIOS) {
v -> context?.onLoginAsIOSChanged(v)
}
}
}

View File

@@ -0,0 +1,129 @@
<resources>
<string name="about">情報</string>
<string name="about_about_p1">このプラグインは完全に無料で提供されます。このプラグインで料金を支払ってしまった場合は、販売者に報告をしてください。</string>
<string name="about_about_p2">プラグインの QQ グループ: 975854705</string>
<string name="about_about_title">このプラグインについて</string>
<string name="about_contributors_asset_file">about_contributors_en.json</string>
<string name="about_warn_p1">このプラグインは学習とコミュニケーションのみを目的としています。</string>
<string name="about_warn_p2">外部プラグインは関連する TOS に違反するため、自己責任でご使用ください。</string>
<string name="about_warn_title">警告</string>
<string name="advanced_settings">高度な設定</string>
<string name="api_addr">APIアドレス (GitHub の最新リリース API)</string>
<string name="app_name">Gakumas Localify</string>
<string name="average">平均</string>
<string name="axisx_x">X 軸.x</string>
<string name="axisx_y">X 軸.y</string>
<string name="axisy_x">Y 軸.x</string>
<string name="axisy_y">Y 軸.y</string>
<string name="axisz_x">Z 軸.x</string>
<string name="axisz_y">Z 軸.y</string>
<string name="basic_settings">基本設定</string>
<string name="breast_param">胸のパラメーター</string>
<string name="breast_scale">胸の大きさ</string>
<string name="camera_settings">カメラ設定</string>
<string name="cancel">キャンセル</string>
<string name="character_counter_content_description">%1$d の %2$d に入力された文字</string>
<string name="character_counter_overflowed_content_description">文字制限が %2$d 文字中、 %1$d 文字を超えています</string>
<string name="character_counter_pattern">%1$d/%2$d</string>
<string name="check_built_in_resource">内蔵アセットのアップデートを確認</string>
<string name="check_resource_from_api">リソースアップデートを API から確認</string>
<string name="check_update">確認</string>
<string name="clear_text_end_icon_content_description">テキストを消去</string>
<string name="close_drawer">ナビゲーションメニューを閉じる</string>
<string name="close_sheet">シートを閉じる</string>
<string name="contributors">貢献者</string>
<string name="damping">ダンプ中</string>
<string name="debug_settings">デバッグ設定</string>
<string name="default_assets_check_api">https://api.github.com/repos/NatsumeLS/Gakumas-Translation-Data-EN/releases/latest</string>
<string name="default_error_message">入力が無効です</string>
<string name="default_popup_window_title">ポップアップウィンドウ</string>
<string name="del_remote_after_update">キャッシュファイルをアップデート後に削除</string>
<string name="delete_plugin_resource">プラグインリソースを削除</string>
<string name="download">ダウンロード</string>
<string name="downloaded_resource_version">ダウンロードされたバージョン</string>
<string name="dropdown_menu">ドロップダウンメニュー</string>
<string name="enable_breast_param">胸のパラメーターを有効化</string>
<string name="enable_free_camera">フリーカメラを有効化</string>
<string name="enable_plugin">プラグイン有効化 (ホットリロードなし)</string>
<string name="error_a11y_label">エラー: 無効</string>
<string name="error_icon_content_description">エラー</string>
<string name="export_text">テキストをエクスポート</string>
<string name="exposed_dropdown_menu_content_description">ドロップダウンメニューを表示</string>
<string name="force_export_resource">リソースのアップデートを強制する</string>
<string name="login_as_ios">iOSとしてログイン</string>
<string name="gakumas_localify">Gakumas Localify</string>
<string name="game_patch">ゲームパッチ</string>
<string name="graphic_settings">グラフィック設定</string>
<string name="hign"></string>
<string name="home">ホーム</string>
<string name="home_shizuku_warning">一部の機能が使用できません</string>
<string name="icon_content_description">ダイアログアイコン</string>
<string name="in_progress">実行中</string>
<string name="indeterminate">部分的にチェック済み</string>
<string name="install">インストール</string>
<string name="installing">インストール中</string>
<string name="invalid_zip_file">無効なファイル</string>
<string name="invalid_zip_file_warn">このファイルは有効な ZIP 翻訳リソースパックではありません。</string>
<string name="isdirty">IsDirty</string>
<string name="item_view_role_description">タブ</string>
<string name="lazy_init">高速な初期化 (読み込みを遅延)</string>
<string name="liveUseCustomeDress">ライブのキャラクターをカスタム</string>
<string name="live_costume_head_id">ライブのカスタムヘッド ID (例: costume_head_hski-cstm-0002)</string>
<string name="live_custome_dress_id">ライブ衣装のカスタム ID (例: hski-cstm-0002)</string>
<string name="low"></string>
<string name="max_high">ウルトラ</string>
<string name="middle"></string>
<string name="off">OFF</string>
<string name="ok">OK</string>
<string name="on">ON</string>
<string name="orientation_landscape">横画面</string>
<string name="orientation_lock">画面を固定</string>
<string name="orientation_orig">オリジナル</string>
<string name="orientation_portrait">縦画面</string>
<string name="password_toggle_content_description">パスワードを表示</string>
<string name="patch_debuggable">デバッグを可能にする</string>
<string name="patch_finished">パッチが完了しました。インストールをしますか?</string>
<string name="patch_integrated">統合</string>
<string name="patch_integrated_desc">"モジュールを埋め込んだ状態なアプリでパッチを当てます。\nパッチを適用したアプリは LSPatch Manager なしで実行できますが、動的に管理はできません。\n統合パッチが適用されたアプリは、LSPatch Manager がインストールされていないデバイスでも使用が可能です。"</string>
<string name="patch_local">ローカル</string>
<string name="patch_local_desc">"モジュールを埋め込まずにアプリにパッチを当てます。\nXposed スコープは再パッチなしで動的に変更が可能です。\nローカルでのパッチを当てたアプリは、ローカルのデバイスでのみ実行可能です。"</string>
<string name="patch_mode">パッチモード</string>
<string name="patch_uninstall_confirm">アンインストールをしてもよろしいですか?</string>
<string name="patch_uninstall_text">"署名が異なるため、パッチをインストールする前に元となるアプリをアンインストールする必要があります。\n個人データのバックアップを設定済みであることを確認してください。"</string>
<string name="pendulum">揺れ</string>
<string name="pendulumrange">揺れの範囲</string>
<string name="plugin_code">プラグインのコード</string>
<string name="project_contribution">プロジェクトの貢献者</string>
<string name="range_end">範囲の終了</string>
<string name="range_start">範囲の開始</string>
<string name="renderscale">RenderScale (0.5/0.59/0.67/0.77/1.0)</string>
<string name="replace_font">フォントを置換する</string>
<string name="reserve_patched">パッチ済みの APK を予約する</string>
<string name="resource_settings">リソース設定</string>
<string name="resource_url">リソース URL</string>
<string name="rootweight">ルートウェイト</string>
<string name="selected">選択済み</string>
<string name="setFpsTitle">最大 FPS (0 はオリジナルの設定を使用します)</string>
<string name="shizuku_available">Shizuku サービスが有効です</string>
<string name="shizuku_unavailable">Shizuku サービスが接続されていません</string>
<string name="spring">跳ね</string>
<string name="start_game">ゲーム開始 / ホットリロードの設定</string>
<string name="stiffness">剛性</string>
<string name="support_file_types">"対応ファイル:\n単一または複数選択: apk\n単一選択: apks、xapk、zip"</string>
<string name="switch_role">切り替え</string>
<string name="tab">タブ</string>
<string name="template_percent">%1$d パーセント。</string>
<string name="test_mode_live">テストモード - ライブ</string>
<string name="text_hook_test_mode">テキストフックテストモード</string>
<string name="translation_repository">翻訳のリポジトリ</string>
<string name="translation_resource_update">翻訳リソースをアップデート</string>
<string name="unlockAllLive">すべてのライブを開放</string>
<string name="useCustomeGraphicSettings">カスタムグラフィック設定を使用する</string>
<string name="use_remote_zip_resource">リモート ZIP リソースを使用する</string>
<string name="usearmcorrection">Arm コレクションを使用する</string>
<string name="uselimit_0_1">リミットレンジの倍率 (0 は無制限)</string>
<string name="uselimitmultiplier">乗数制限を使用する</string>
<string name="usescale">胸の大きさを使用する</string>
<string name="very_high">最高</string>
<string name="warning">警告</string>
</resources>

View File

@@ -16,6 +16,7 @@
<string name="text_hook_test_mode">文本 hook 测试模式</string>
<string name="export_text">导出文本</string>
<string name="force_export_resource">启动后强制导出资源</string>
<string name="login_as_ios">以 iOS 登陆</string>
<string name="max_high">极高</string>
<string name="very_high">超高</string>
<string name="hign"></string>

View File

@@ -16,6 +16,7 @@
<string name="text_hook_test_mode">Text Hook Test Mode</string>
<string name="export_text">Export Text</string>
<string name="force_export_resource">Force Update Resource</string>
<string name="login_as_ios">Login as iOS</string>
<string name="max_high">Ultra</string>
<string name="very_high">Very High</string>
<string name="hign">High</string>