feat: add output log to media

底層實現了日誌檔案寫入。為管理器新增了 outputLog 選項,可以將日誌輸出到 Android/media 目錄。
This commit is contained in:
NkBe(HSSkyBoy) 2025-10-02 22:36:25 +08:00
parent 02c9d34f5a
commit 7613e7de10
No known key found for this signature in database
GPG Key ID: 525137026FF031DF
10 changed files with 80 additions and 9 deletions

View File

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

2
.gitmodules vendored
View File

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

2
core

@ -1 +1 @@
Subproject commit fd894b94b2c0790f69aed8b75f13d2cc4f5c26b9
Subproject commit b168c71f7b05b81a05cefc8343ca10bfb5fb1dc9

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<String> 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<String> modules = new ArrayList<>();
private static final String ANDROID_MANIFEST_XML = "AndroidManifest.xml";
private static final HashSet<String> 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))) {

View File

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