From 7613e7de10e0258f2ff7adfe186f657ee4ed2c74 Mon Sep 17 00:00:00 2001 From: "NkBe(HSSkyBoy)" Date: Thu, 2 Oct 2025 22:36:25 +0800 Subject: [PATCH] feat: add output log to media MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 底層實現了日誌檔案寫入。為管理器新增了 outputLog 選項,可以將日誌輸出到 Android/media 目錄。 --- .github/workflows/build.yml | 3 ++ .gitmodules | 2 +- core | 2 +- .../lsposed/lspatch/ui/page/NewPatchScreen.kt | 8 +++ .../java/org/lsposed/lspatch/ui/util/Utils.kt | 4 +- .../lspatch/ui/viewmodel/NewPatchViewModel.kt | 3 +- .../lspatch/loader/LSPApplication.java | 7 ++- .../lspatch/loader/XposedLogPrinter.java | 49 +++++++++++++++++++ .../main/java/org/lsposed/patch/LSPatch.java | 6 ++- .../lsposed/lspatch/share/PatchConfig.java | 5 +- 10 files changed, 80 insertions(+), 9 deletions(-) create mode 100644 patch-loader/src/main/java/org/lsposed/lspatch/loader/XposedLogPrinter.java diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3d8c0df..daf4c5d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -109,6 +109,9 @@ jobs: echo 'android.native.buildOutput=verbose' } >> gradle.properties + - name: Grant execution permission to gradlew + run: chmod +x gradlew + - name: Build dependencies with Gradle working-directory: libxposed run: | diff --git a/.gitmodules b/.gitmodules index 145f02f..060421c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,5 +4,5 @@ branch = android10-release [submodule "core"] path = core - url = https://github.com/JingMatrix/LSPosed.git + url = https://github.com/HSSkyBoy/LSPosed.git branch = master diff --git a/core b/core index fd894b9..b168c71 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit fd894b94b2c0790f69aed8b75f13d2cc4f5c26b9 +Subproject commit b168c71f7b05b81a05cefc8343ca10bfb5fb1dc9 diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchScreen.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchScreen.kt index 4944f09..11995b1 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchScreen.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchScreen.kt @@ -359,6 +359,14 @@ private fun PatchOptionsBody(modifier: Modifier, onAddEmbed: () -> Unit) { desc = stringResource(R.string.patch_inject_dex_desc) ) + SettingsCheckBox( + modifier = Modifier.clickable { viewModel.outputLog = !viewModel.outputLog }, + checked = viewModel.outputLog, + icon = Icons.Outlined.AddCard, + title = stringResource(R.string.patch_output_log_to_media), + desc = stringResource(R.string.patch_output_log_to_media_desc) + ) + var bypassExpanded by remember { mutableStateOf(false) } AnywhereDropdown( expanded = bypassExpanded, diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/util/Utils.kt b/manager/src/main/java/org/lsposed/lspatch/ui/util/Utils.kt index 8cb4b3e..680a17a 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/util/Utils.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/util/Utils.kt @@ -33,10 +33,10 @@ fun checkIsApkFixedByLSP(context: Context, packageName: String): Boolean { context.packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA) (app.metaData?.containsKey("lspatch") != true) } catch (_: PackageManager.NameNotFoundException) { - Log.e("LSPatch", "Package not found: $packageName") + Log.e("NPatch", "Package not found: $packageName") false } catch (e: Exception) { - Log.e("LSPatch", "Unexpected error in checkIsApkFixedByLSP", e) + Log.e("NPatch", "Unexpected error in checkIsApkFixedByLSP", e) false } } diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/viewmodel/NewPatchViewModel.kt b/manager/src/main/java/org/lsposed/lspatch/ui/viewmodel/NewPatchViewModel.kt index 8d939d1..795536b 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/viewmodel/NewPatchViewModel.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/viewmodel/NewPatchViewModel.kt @@ -43,6 +43,7 @@ class NewPatchViewModel : ViewModel() { var overrideVersionCode by mutableStateOf(false) var sigBypassLevel by mutableStateOf(2) var injectDex by mutableStateOf(false) + var outputLog by mutableStateOf(true) var embeddedModules = emptyList() lateinit var patchApp: AppInfo @@ -96,7 +97,7 @@ class NewPatchViewModel : ViewModel() { if (useManager) embeddedModules = emptyList() patchOptions = Patcher.Options( injectDex = injectDex, - config = PatchConfig(useManager, debuggable, overrideVersionCode, sigBypassLevel, null, null), + config = PatchConfig(useManager, debuggable, overrideVersionCode, sigBypassLevel, null, null, outputLog), apkPaths = listOf(patchApp.app.sourceDir) + (patchApp.app.splitSourceDirs ?: emptyArray()), embeddedModules = embeddedModules.flatMap { listOf(it.app.sourceDir) + (it.app.splitSourceDirs ?: emptyArray()) } ) diff --git a/patch-loader/src/main/java/org/lsposed/lspatch/loader/LSPApplication.java b/patch-loader/src/main/java/org/lsposed/lspatch/loader/LSPApplication.java index 4925fe9..1bd240e 100644 --- a/patch-loader/src/main/java/org/lsposed/lspatch/loader/LSPApplication.java +++ b/patch-loader/src/main/java/org/lsposed/lspatch/loader/LSPApplication.java @@ -112,6 +112,9 @@ public class LSPApplication { // WARN: Since it uses `XResource`, the following class should not be initialized // before forkPostCommon is invoke. Otherwise, you will get failure of XResources + if (config.outputLog){ + XposedBridge.setLogPrinter(new XposedLogPrinter(0,"NPatch")); + } Log.i(TAG, "Load modules"); LSPLoader.initModules(appLoadedApk); Log.i(TAG, "Modules initialized"); @@ -119,7 +122,7 @@ public class LSPApplication { switchAllClassLoader(); SigBypass.doSigBypass(context, config.sigBypassLevel); - Log.i(TAG, "LSPatch bootstrap completed"); + Log.i(TAG, "NPatch bootstrap completed"); } private static Context createLoadedApkWithContext() { @@ -142,7 +145,7 @@ public class LSPApplication { Log.i(TAG, "Use manager: " + config.useManager); Log.i(TAG, "Signature bypass level: " + config.sigBypassLevel); - Path originPath = Paths.get(appInfo.dataDir, "cache/lspatch/origin/"); + Path originPath = Paths.get(appInfo.dataDir, "cache/npatch/origin/"); Path cacheApkPath; try (ZipFile sourceFile = new ZipFile(appInfo.sourceDir)) { cacheApkPath = originPath.resolve(sourceFile.getEntry(ORIGINAL_APK_ASSET_PATH).getCrc() + ".apk"); diff --git a/patch-loader/src/main/java/org/lsposed/lspatch/loader/XposedLogPrinter.java b/patch-loader/src/main/java/org/lsposed/lspatch/loader/XposedLogPrinter.java new file mode 100644 index 0000000..2b818e3 --- /dev/null +++ b/patch-loader/src/main/java/org/lsposed/lspatch/loader/XposedLogPrinter.java @@ -0,0 +1,49 @@ +package org.lsposed.lspatch.loader; + +import android.app.ActivityThread; +import android.os.Environment; +import android.util.Log; +import android.util.LogPrinter; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.text.SimpleDateFormat; +import java.util.Date; + +public class XposedLogPrinter extends LogPrinter { + + /** + * Create a new Printer that sends to the log with the given priority + * and tag. + * + * @param priority The desired log priority: + * {@link Log#VERBOSE Log.VERBOSE}, + * {@link Log#DEBUG Log.DEBUG}, + * {@link Log#INFO Log.INFO}, + * {@link Log#WARN Log.WARN}, or + * {@link Log#ERROR Log.ERROR}. + * @param tag A string tag to associate with each printed log statement. + */ + public XposedLogPrinter(int priority, String tag) { + super(priority, tag); + } + + @Override + public void println(String x) { + writeLine(x); + } + private static SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd"); + private static FileOutputStream out; + private static synchronized void writeLine(String text){ + try { + if (out == null){ + File f = new File(Environment.getExternalStorageDirectory() + "/Android/media/" + ActivityThread.currentPackageName() + "/npatch/log/"); + f.mkdirs(); + out = new FileOutputStream(new File(f,format.format(new Date()) + ".log"),true); + } + out.write(text.getBytes()); + out.write("\n".getBytes()); + }catch (Exception ignored){ } + } +} diff --git a/patch/src/main/java/org/lsposed/patch/LSPatch.java b/patch/src/main/java/org/lsposed/patch/LSPatch.java index 5de90e6..5a58709 100644 --- a/patch/src/main/java/org/lsposed/patch/LSPatch.java +++ b/patch/src/main/java/org/lsposed/patch/LSPatch.java @@ -81,6 +81,9 @@ public class LSPatch { @Parameter(names = {"--injectdex"}, description = "Inject directly the loder dex file into the original application package") private boolean injectDex = false; + @Parameter(names = {"--outputLog"}, description = "Output Log to Media") + private boolean outputLog = true; + @Parameter(names = {"-k", "--keystore"}, arity = 4, description = "Set custom signature keystore. Followed by 4 arguments: keystore path, keystore password, keystore alias, keystore alias password") private List keystoreArgs = Arrays.asList(null, "123456", "key0", "123456"); @@ -96,6 +99,7 @@ public class LSPatch { @Parameter(names = {"-m", "--embed"}, description = "Embed provided modules to apk") private List modules = new ArrayList<>(); + private static final String ANDROID_MANIFEST_XML = "AndroidManifest.xml"; private static final HashSet ARCHES = new HashSet<>(Arrays.asList( "armeabi-v7a", @@ -250,7 +254,7 @@ public class LSPatch { logger.i("Patching apk..."); // modify manifest - final var config = new PatchConfig(useManager, debuggableFlag, overrideVersionCode, sigbypassLevel, originalSignature, appComponentFactory); + final var config = new PatchConfig(useManager, debuggableFlag, overrideVersionCode, sigbypassLevel, originalSignature, appComponentFactory, outputLog); final var configBytes = new Gson().toJson(config).getBytes(StandardCharsets.UTF_8); final var metadata = Base64.getEncoder().encodeToString(configBytes); try (var is = new ByteArrayInputStream(modifyManifestFile(manifestEntry.open(), metadata, minSdkVersion))) { diff --git a/share/java/src/main/java/org/lsposed/lspatch/share/PatchConfig.java b/share/java/src/main/java/org/lsposed/lspatch/share/PatchConfig.java index b69a387..a732291 100644 --- a/share/java/src/main/java/org/lsposed/lspatch/share/PatchConfig.java +++ b/share/java/src/main/java/org/lsposed/lspatch/share/PatchConfig.java @@ -5,6 +5,7 @@ public class PatchConfig { public final boolean useManager; public final boolean debuggable; public final boolean overrideVersionCode; + public final boolean outputLog; public final int sigBypassLevel; public final String originalSignature; public final String appComponentFactory; @@ -17,7 +18,8 @@ public class PatchConfig { boolean overrideVersionCode, int sigBypassLevel, String originalSignature, - String appComponentFactory + String appComponentFactory, + boolean outputLog ) { this.useManager = useManager; this.debuggable = debuggable; @@ -27,5 +29,6 @@ public class PatchConfig { this.appComponentFactory = appComponentFactory; this.lspConfig = LSPConfig.instance; this.managerPackageName = Constants.MANAGER_PACKAGE_NAME; + this.outputLog = outputLog; } }