diff --git a/app/src/main/assets/apk/MonoGame.Framework.dll b/app/src/main/assets/apk/MonoGame.Framework.dll deleted file mode 100644 index 7df2a7f..0000000 Binary files a/app/src/main/assets/apk/MonoGame.Framework.dll and /dev/null differ diff --git a/app/src/main/assets/apk/StardewModdingAPI.dll b/app/src/main/assets/apk/StardewModdingAPI.dll index 0d67b4e..74a7edc 100644 Binary files a/app/src/main/assets/apk/StardewModdingAPI.dll and b/app/src/main/assets/apk/StardewModdingAPI.dll differ diff --git a/app/src/main/assets/apk/classes.dex b/app/src/main/assets/apk/classes.dex index 71462d2..1d3eb18 100644 Binary files a/app/src/main/assets/apk/classes.dex and b/app/src/main/assets/apk/classes.dex differ diff --git a/app/src/main/assets/apk/lib/arm64-v8a/libSkiaSharp.so b/app/src/main/assets/apk/lib/arm64-v8a/libSkiaSharp.so new file mode 100644 index 0000000..d5f558c Binary files /dev/null and b/app/src/main/assets/apk/lib/arm64-v8a/libSkiaSharp.so differ diff --git a/app/src/main/assets/apk/lib/arm64-v8a/libmono-btls-shared.so b/app/src/main/assets/apk/lib/arm64-v8a/libmono-btls-shared.so new file mode 100644 index 0000000..5e60a3a Binary files /dev/null and b/app/src/main/assets/apk/lib/arm64-v8a/libmono-btls-shared.so differ diff --git a/app/src/main/assets/apk/lib/arm64-v8a/libmono-native.so b/app/src/main/assets/apk/lib/arm64-v8a/libmono-native.so new file mode 100644 index 0000000..f790896 Binary files /dev/null and b/app/src/main/assets/apk/lib/arm64-v8a/libmono-native.so differ diff --git a/app/src/main/assets/apk/lib/arm64-v8a/libmono-profiler-log.so b/app/src/main/assets/apk/lib/arm64-v8a/libmono-profiler-log.so new file mode 100644 index 0000000..fae6e5e Binary files /dev/null and b/app/src/main/assets/apk/lib/arm64-v8a/libmono-profiler-log.so differ diff --git a/app/src/main/assets/apk/lib/arm64-v8a/libmonodroid.so b/app/src/main/assets/apk/lib/arm64-v8a/libmonodroid.so index 3cb8630..a33dda1 100644 Binary files a/app/src/main/assets/apk/lib/arm64-v8a/libmonodroid.so and b/app/src/main/assets/apk/lib/arm64-v8a/libmonodroid.so differ diff --git a/app/src/main/assets/apk/lib/arm64-v8a/libmonosgen-2.0.so b/app/src/main/assets/apk/lib/arm64-v8a/libmonosgen-2.0.so new file mode 100644 index 0000000..cf2eba3 Binary files /dev/null and b/app/src/main/assets/apk/lib/arm64-v8a/libmonosgen-2.0.so differ diff --git a/app/src/main/assets/apk/lib/arm64-v8a/libxa-internal-api.so b/app/src/main/assets/apk/lib/arm64-v8a/libxa-internal-api.so new file mode 100644 index 0000000..533ef65 Binary files /dev/null and b/app/src/main/assets/apk/lib/arm64-v8a/libxa-internal-api.so differ diff --git a/app/src/main/assets/apk/lib/arm64-v8a/libxamarin-app.so b/app/src/main/assets/apk/lib/arm64-v8a/libxamarin-app.so index 806939c..2d3c597 100644 Binary files a/app/src/main/assets/apk/lib/arm64-v8a/libxamarin-app.so and b/app/src/main/assets/apk/lib/arm64-v8a/libxamarin-app.so differ diff --git a/app/src/main/assets/apk/lib/arm64-v8a/libxamarin-debug-app-helper.so b/app/src/main/assets/apk/lib/arm64-v8a/libxamarin-debug-app-helper.so new file mode 100644 index 0000000..ed70da9 Binary files /dev/null and b/app/src/main/assets/apk/lib/arm64-v8a/libxamarin-debug-app-helper.so differ diff --git a/app/src/main/assets/apk/lib/armeabi-v7a/libSkiaSharp.so b/app/src/main/assets/apk/lib/armeabi-v7a/libSkiaSharp.so new file mode 100644 index 0000000..a5426a3 Binary files /dev/null and b/app/src/main/assets/apk/lib/armeabi-v7a/libSkiaSharp.so differ diff --git a/app/src/main/assets/apk/lib/armeabi-v7a/libmono-btls-shared.so b/app/src/main/assets/apk/lib/armeabi-v7a/libmono-btls-shared.so new file mode 100644 index 0000000..8eac3d6 Binary files /dev/null and b/app/src/main/assets/apk/lib/armeabi-v7a/libmono-btls-shared.so differ diff --git a/app/src/main/assets/apk/lib/armeabi-v7a/libmono-native.so b/app/src/main/assets/apk/lib/armeabi-v7a/libmono-native.so new file mode 100644 index 0000000..0f6814d Binary files /dev/null and b/app/src/main/assets/apk/lib/armeabi-v7a/libmono-native.so differ diff --git a/app/src/main/assets/apk/lib/armeabi-v7a/libmono-profiler-log.so b/app/src/main/assets/apk/lib/armeabi-v7a/libmono-profiler-log.so new file mode 100644 index 0000000..1d7364b Binary files /dev/null and b/app/src/main/assets/apk/lib/armeabi-v7a/libmono-profiler-log.so differ diff --git a/app/src/main/assets/apk/lib/armeabi-v7a/libmonodroid.so b/app/src/main/assets/apk/lib/armeabi-v7a/libmonodroid.so index 4dd9cd4..7c27485 100644 Binary files a/app/src/main/assets/apk/lib/armeabi-v7a/libmonodroid.so and b/app/src/main/assets/apk/lib/armeabi-v7a/libmonodroid.so differ diff --git a/app/src/main/assets/apk/lib/armeabi-v7a/libmonosgen-2.0.so b/app/src/main/assets/apk/lib/armeabi-v7a/libmonosgen-2.0.so new file mode 100644 index 0000000..3c48363 Binary files /dev/null and b/app/src/main/assets/apk/lib/armeabi-v7a/libmonosgen-2.0.so differ diff --git a/app/src/main/assets/apk/lib/armeabi-v7a/libxa-internal-api.so b/app/src/main/assets/apk/lib/armeabi-v7a/libxa-internal-api.so new file mode 100644 index 0000000..7264726 Binary files /dev/null and b/app/src/main/assets/apk/lib/armeabi-v7a/libxa-internal-api.so differ diff --git a/app/src/main/assets/apk/lib/armeabi-v7a/libxamarin-app.so b/app/src/main/assets/apk/lib/armeabi-v7a/libxamarin-app.so index ddd2429..bbdca91 100644 Binary files a/app/src/main/assets/apk/lib/armeabi-v7a/libxamarin-app.so and b/app/src/main/assets/apk/lib/armeabi-v7a/libxamarin-app.so differ diff --git a/app/src/main/assets/apk/lib/armeabi-v7a/libxamarin-debug-app-helper.so b/app/src/main/assets/apk/lib/armeabi-v7a/libxamarin-debug-app-helper.so new file mode 100644 index 0000000..b9ec821 Binary files /dev/null and b/app/src/main/assets/apk/lib/armeabi-v7a/libxamarin-debug-app-helper.so differ diff --git a/app/src/main/assets/apk/lib/x86/libSkiaSharp.so b/app/src/main/assets/apk/lib/x86/libSkiaSharp.so new file mode 100644 index 0000000..e8a6eaf Binary files /dev/null and b/app/src/main/assets/apk/lib/x86/libSkiaSharp.so differ diff --git a/app/src/main/assets/apk/lib/x86/libmono-btls-shared.so b/app/src/main/assets/apk/lib/x86/libmono-btls-shared.so new file mode 100644 index 0000000..cd280b3 Binary files /dev/null and b/app/src/main/assets/apk/lib/x86/libmono-btls-shared.so differ diff --git a/app/src/main/assets/apk/lib/x86/libmono-native.so b/app/src/main/assets/apk/lib/x86/libmono-native.so new file mode 100644 index 0000000..e553097 Binary files /dev/null and b/app/src/main/assets/apk/lib/x86/libmono-native.so differ diff --git a/app/src/main/assets/apk/lib/x86/libmono-profiler-log.so b/app/src/main/assets/apk/lib/x86/libmono-profiler-log.so new file mode 100644 index 0000000..8cb232f Binary files /dev/null and b/app/src/main/assets/apk/lib/x86/libmono-profiler-log.so differ diff --git a/app/src/main/assets/apk/lib/x86/libmonodroid.so b/app/src/main/assets/apk/lib/x86/libmonodroid.so index 0e8c03a..2d954ba 100644 Binary files a/app/src/main/assets/apk/lib/x86/libmonodroid.so and b/app/src/main/assets/apk/lib/x86/libmonodroid.so differ diff --git a/app/src/main/assets/apk/lib/x86/libmonosgen-2.0.so b/app/src/main/assets/apk/lib/x86/libmonosgen-2.0.so new file mode 100644 index 0000000..3bc1695 Binary files /dev/null and b/app/src/main/assets/apk/lib/x86/libmonosgen-2.0.so differ diff --git a/app/src/main/assets/apk/lib/x86/libxa-internal-api.so b/app/src/main/assets/apk/lib/x86/libxa-internal-api.so new file mode 100644 index 0000000..484a97d Binary files /dev/null and b/app/src/main/assets/apk/lib/x86/libxa-internal-api.so differ diff --git a/app/src/main/assets/apk/lib/x86/libxamarin-app.so b/app/src/main/assets/apk/lib/x86/libxamarin-app.so index c2dfe5b..dfbce43 100644 Binary files a/app/src/main/assets/apk/lib/x86/libxamarin-app.so and b/app/src/main/assets/apk/lib/x86/libxamarin-app.so differ diff --git a/app/src/main/assets/apk/lib/x86/libxamarin-debug-app-helper.so b/app/src/main/assets/apk/lib/x86/libxamarin-debug-app-helper.so new file mode 100644 index 0000000..acf1ca4 Binary files /dev/null and b/app/src/main/assets/apk/lib/x86/libxamarin-debug-app-helper.so differ diff --git a/app/src/main/assets/apk/lib/x86_64/libSkiaSharp.so b/app/src/main/assets/apk/lib/x86_64/libSkiaSharp.so new file mode 100644 index 0000000..20cd56e Binary files /dev/null and b/app/src/main/assets/apk/lib/x86_64/libSkiaSharp.so differ diff --git a/app/src/main/assets/apk/lib/x86_64/libmono-btls-shared.so b/app/src/main/assets/apk/lib/x86_64/libmono-btls-shared.so new file mode 100644 index 0000000..a4e44df Binary files /dev/null and b/app/src/main/assets/apk/lib/x86_64/libmono-btls-shared.so differ diff --git a/app/src/main/assets/apk/lib/x86_64/libmono-native.so b/app/src/main/assets/apk/lib/x86_64/libmono-native.so new file mode 100644 index 0000000..bc07e24 Binary files /dev/null and b/app/src/main/assets/apk/lib/x86_64/libmono-native.so differ diff --git a/app/src/main/assets/apk/lib/x86_64/libmono-profiler-log.so b/app/src/main/assets/apk/lib/x86_64/libmono-profiler-log.so new file mode 100644 index 0000000..66659be Binary files /dev/null and b/app/src/main/assets/apk/lib/x86_64/libmono-profiler-log.so differ diff --git a/app/src/main/assets/apk/lib/x86_64/libmonodroid.so b/app/src/main/assets/apk/lib/x86_64/libmonodroid.so index 0312497..a4d6f6b 100644 Binary files a/app/src/main/assets/apk/lib/x86_64/libmonodroid.so and b/app/src/main/assets/apk/lib/x86_64/libmonodroid.so differ diff --git a/app/src/main/assets/apk/lib/x86_64/libmonosgen-2.0.so b/app/src/main/assets/apk/lib/x86_64/libmonosgen-2.0.so new file mode 100644 index 0000000..0de5190 Binary files /dev/null and b/app/src/main/assets/apk/lib/x86_64/libmonosgen-2.0.so differ diff --git a/app/src/main/assets/apk/lib/x86_64/libxa-internal-api.so b/app/src/main/assets/apk/lib/x86_64/libxa-internal-api.so new file mode 100644 index 0000000..204d04b Binary files /dev/null and b/app/src/main/assets/apk/lib/x86_64/libxa-internal-api.so differ diff --git a/app/src/main/assets/apk/lib/x86_64/libxamarin-app.so b/app/src/main/assets/apk/lib/x86_64/libxamarin-app.so index a565875..7bba460 100644 Binary files a/app/src/main/assets/apk/lib/x86_64/libxamarin-app.so and b/app/src/main/assets/apk/lib/x86_64/libxamarin-app.so differ diff --git a/app/src/main/assets/apk/lib/x86_64/libxamarin-debug-app-helper.so b/app/src/main/assets/apk/lib/x86_64/libxamarin-debug-app-helper.so new file mode 100644 index 0000000..f2fecc6 Binary files /dev/null and b/app/src/main/assets/apk/lib/x86_64/libxamarin-debug-app-helper.so differ diff --git a/app/src/main/assets/apk_files_manifest.json b/app/src/main/assets/apk_files_manifest.json index 0f816b8..fd6ebba 100644 --- a/app/src/main/assets/apk_files_manifest.json +++ b/app/src/main/assets/apk_files_manifest.json @@ -72,9 +72,9 @@ }, { "targetPath": "assemblies/", - "assetPath": "assemblies/assemblies.blob", + "assetPath": "assemblies/assemblies", "origin": 1, - "isXABB": true, + "isXABA": true, "compression": 0, "external": false } diff --git a/app/src/main/java/com/zane/smapiinstaller/constant/ManifestPatchConstants.java b/app/src/main/java/com/zane/smapiinstaller/constant/ManifestPatchConstants.java index aca49d0..f1077eb 100644 --- a/app/src/main/java/com/zane/smapiinstaller/constant/ManifestPatchConstants.java +++ b/app/src/main/java/com/zane/smapiinstaller/constant/ManifestPatchConstants.java @@ -15,4 +15,6 @@ public class ManifestPatchConstants { public static final CharSequence APP_PACKAGE_NAME = "com.chucklefish.stardewvalley"; public static final String PATTERN_VERSION_AMAZON = "amazon"; + + public static final String PATTERN_EXTRACT_NATIVE_LIBS = "extractNativeLibs"; } diff --git a/app/src/main/java/com/zane/smapiinstaller/entity/ManifestEntry.java b/app/src/main/java/com/zane/smapiinstaller/entity/ManifestEntry.java index d271937..8494c84 100644 --- a/app/src/main/java/com/zane/smapiinstaller/entity/ManifestEntry.java +++ b/app/src/main/java/com/zane/smapiinstaller/entity/ManifestEntry.java @@ -50,4 +50,9 @@ public class ManifestEntry { */ @JsonProperty("isXALZ") private boolean isXALZ; + /** + * 是否为XABA压缩格式 + */ + @JsonProperty("isXABA") + private boolean isXABA; } diff --git a/app/src/main/java/com/zane/smapiinstaller/logic/ActivityResultHandler.java b/app/src/main/java/com/zane/smapiinstaller/logic/ActivityResultHandler.java index 773ff44..2804823 100644 --- a/app/src/main/java/com/zane/smapiinstaller/logic/ActivityResultHandler.java +++ b/app/src/main/java/com/zane/smapiinstaller/logic/ActivityResultHandler.java @@ -13,6 +13,7 @@ public class ActivityResultHandler { public static final int REQUEST_CODE_APP_INSTALL = 1001; public static final int REQUEST_CODE_ALL_FILES_ACCESS_PERMISSION = 1002; public static final int REQUEST_CODE_OBB_FILES_ACCESS_PERMISSION = 1003; + public static final int REQUEST_CODE_DATA_FILES_ACCESS_PERMISSION = 1004; public static ConcurrentHashMap> listenerMap = new ConcurrentHashMap<>(); diff --git a/app/src/main/java/com/zane/smapiinstaller/logic/ApkPatcher.java b/app/src/main/java/com/zane/smapiinstaller/logic/ApkPatcher.java index a5ef7a5..c062977 100644 --- a/app/src/main/java/com/zane/smapiinstaller/logic/ApkPatcher.java +++ b/app/src/main/java/com/zane/smapiinstaller/logic/ApkPatcher.java @@ -36,6 +36,7 @@ import com.zane.smapiinstaller.utils.ZipUtils; import net.fornwall.apksigner.KeyStoreFileManager; +import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.NotImplementedException; import org.zeroturnaround.zip.ZipUtil; @@ -50,6 +51,7 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -64,6 +66,7 @@ import java.util.zip.Deflater; import androidx.annotation.Nullable; import androidx.core.content.FileProvider; + import pxb.android.axml.NodeVisitor; /** @@ -109,7 +112,7 @@ public class ApkPatcher { * @param advancedStage 0: 初始化,1: 高级安装,-1: 普通安装 * @return 抽取后的APK文件路径,如果抽取失败返回null */ - public String extract(int advancedStage) { + public Tuple2 extract(int advancedStage) { emitProgress(0); PackageManager packageManager = context.getPackageManager(); List packageNames = FileUtils.getAssetJson(context, "package_names.json", new TypeReference>() { @@ -143,7 +146,7 @@ public class ApkPatcher { } return null; }); - return apkFile.getAbsolutePath(); + return new Tuple2<>(apkFile.getAbsolutePath(), null); } else if (advancedStage == 1) { File contentFolder = new File(stadewValleyBasePath + "/StardewValley/Content"); if (contentFolder.exists()) { @@ -154,10 +157,10 @@ public class ApkPatcher { } else { extract(0); } - return apkFile.getAbsolutePath(); + return new Tuple2<>(apkFile.getAbsolutePath(), null); } emitProgress(5); - return apkFile.getAbsolutePath(); + return new Tuple2<>(apkFile.getAbsolutePath(), packageInfo.applicationInfo.splitSourceDirs); } catch (PackageManager.NameNotFoundException ignored) { return null; } @@ -174,7 +177,7 @@ public class ApkPatcher { * @param isAdvanced 是否高级模式 * @return 是否成功打包 */ - public boolean patch(String apkPath, File targetFile, boolean isAdvanced) { + public boolean patch(String apkPath, File targetFile, boolean isAdvanced, boolean isResourcePack) { if (apkPath == null) { return false; } @@ -199,7 +202,7 @@ public class ApkPatcher { ApkFilesManifest apkFilesManifest = apkFilesManifests.get(0); List manifestEntries = apkFilesManifest.getManifestEntries(); errorMessage.set(null); - List entries = manifestEntries.stream() + List entries = isResourcePack ? new ArrayList<>() : manifestEntries.stream() .map(entry -> processFileEntry(file, apkFilesManifest, entry, isAdvanced)) .filter(Objects::nonNull).flatMap(Stream::of).distinct().collect(Collectors.toList()); entries.add(new ZipUtils.ZipEntrySource("AndroidManifest.xml", modifiedManifest, Deflater.DEFLATED)); @@ -208,7 +211,7 @@ public class ApkPatcher { stopwatch.reset(); stopwatch.start(); originSignInfo = ZipUtils.addOrReplaceEntries(apkPath, entries, targetFile.getAbsolutePath(), - isAdvanced ? (entryName) -> entryName.startsWith("assets/Content") : null, + (entryName) -> entryName.startsWith("assemblies/assemblies.") || (isAdvanced && entryName.startsWith("assets/Content")), (progress) -> emitProgress((int) (baseProgress + (progress / 100.0) * 35))); stopwatch.stop(); emitProgress(46); @@ -225,40 +228,51 @@ public class ApkPatcher { if (entry.isAdvanced() && !isAdvanced) { return null; } - if (entry.getTargetPath().endsWith(Constants.FILE_SEPARATOR) && entry.getAssetPath().contains("*")) { - String path = StringUtils.substringBeforeLast(entry.getAssetPath(), Constants.FILE_SEPARATOR); - String pattern = StringUtils.substringAfterLast(entry.getAssetPath(), Constants.FILE_SEPARATOR); - try { - if (entry.getOrigin() == 1) { - ArrayList list = new ArrayList<>(); - ZipUtil.iterate(apkFile, (in, zipEntry) -> { - String entryPath = StringUtils.substringBeforeLast(zipEntry.getName(), Constants.FILE_SEPARATOR); - String filename = StringUtils.substringAfterLast(zipEntry.getName(), Constants.FILE_SEPARATOR); - if (entryPath.equals(path) && StringUtils.wildCardMatch(filename, pattern)) { - byte[] bytes = ByteStreams.toByteArray(in); - ZipUtils.ZipEntrySource source; - if (entry.isXALZ()) { - source = new ZipUtils.ZipEntrySource(entry.getTargetPath() + filename, entry.getCompression(), () -> new ByteArrayInputStream(ZipUtils.decompressXALZ(bytes))); - } else { - source = new ZipUtils.ZipEntrySource(entry.getTargetPath() + filename, bytes, entry.getCompression()); - } - list.add(source); - } - }); - return list.toArray(new ZipUtils.ZipEntrySource[0]); - } else { - return Stream.of(context.getAssets().list(path)) - .filter(filename -> StringUtils.wildCardMatch(filename, pattern)) - .map(filename -> new ZipUtils.ZipEntrySource(entry.getTargetPath() + filename, entry.getCompression(), () -> { - try { - return FileUtils.getLocalAsset(context, path + Constants.FILE_SEPARATOR + filename); - } catch (IOException ignored) { + if (entry.getTargetPath().endsWith(Constants.FILE_SEPARATOR)) { + if (entry.isXABA()) { + byte[] manifestBytes = ZipUtil.unpackEntry(apkFile, entry.getAssetPath() + ".manifest"); + byte[] xabaBytes = ZipUtil.unpackEntry(apkFile, entry.getAssetPath() + ".blob"); + Map unpackedAssemblies = ZipUtils.unpackXABA(manifestBytes, xabaBytes); + ArrayList list = new ArrayList<>(); + unpackedAssemblies.forEach((filename, bytes) -> { + list.add(new ZipUtils.ZipEntrySource(entry.getTargetPath() + filename, bytes, entry.getCompression())); + }); + return list.toArray(new ZipUtils.ZipEntrySource[0]); + } else if (entry.getAssetPath().contains("*")) { + String path = StringUtils.substringBeforeLast(entry.getAssetPath(), Constants.FILE_SEPARATOR); + String pattern = StringUtils.substringAfterLast(entry.getAssetPath(), Constants.FILE_SEPARATOR); + try { + if (entry.getOrigin() == 1) { + ArrayList list = new ArrayList<>(); + ZipUtil.iterate(apkFile, (in, zipEntry) -> { + String entryPath = StringUtils.substringBeforeLast(zipEntry.getName(), Constants.FILE_SEPARATOR); + String filename = StringUtils.substringAfterLast(zipEntry.getName(), Constants.FILE_SEPARATOR); + if (entryPath.equals(path) && StringUtils.wildCardMatch(filename, pattern)) { + byte[] bytes = ByteStreams.toByteArray(in); + ZipUtils.ZipEntrySource source; + if (entry.isXALZ()) { + source = new ZipUtils.ZipEntrySource(entry.getTargetPath() + filename, entry.getCompression(), () -> new ByteArrayInputStream(ZipUtils.decompressXALZ(bytes))); + } else { + source = new ZipUtils.ZipEntrySource(entry.getTargetPath() + filename, bytes, entry.getCompression()); } - return null; - })) - .toArray(ZipUtils.ZipEntrySource[]::new); + list.add(source); + } + }); + return list.toArray(new ZipUtils.ZipEntrySource[0]); + } else { + return Stream.of(context.getAssets().list(path)) + .filter(filename -> StringUtils.wildCardMatch(filename, pattern)) + .map(filename -> new ZipUtils.ZipEntrySource(entry.getTargetPath() + filename, entry.getCompression(), () -> { + try { + return FileUtils.getLocalAsset(context, path + Constants.FILE_SEPARATOR + filename); + } catch (IOException ignored) { + } + return null; + })) + .toArray(ZipUtils.ZipEntrySource[]::new); + } + } catch (IOException ignored) { } - } catch (IOException ignored) { } return null; } @@ -351,6 +365,10 @@ public class ApkPatcher { if (StringUtils.equals(attr.name, ManifestPatchConstants.PATTERN_VERSION_CODE)) { versionCode.set((int) attr.obj); } + } else if (attr.type == NodeVisitor.TYPE_INT_BOOLEAN) { + if (StringUtils.equals(attr.name, ManifestPatchConstants.PATTERN_EXTRACT_NATIVE_LIBS)) { + attr.obj = true; + } } return null; }; @@ -390,7 +408,7 @@ public class ApkPatcher { try { String stadewValleyBasePath = FileUtils.getStadewValleyBasePath(); emitProgress(47); - String signApkPath = stadewValleyBasePath + "/SMAPI Installer/base_signed.apk"; + String signApkPath = stadewValleyBasePath + "/SMAPI Installer/" + FilenameUtils.getBaseName(apkPath) + "_signed.apk"; KeyStore ks = new KeyStoreFileManager.JksKeyStore(); try (InputStream fis = context.getAssets().open("debug.keystore.dat")) { ks.load(fis, PASSWORD.toCharArray()); @@ -407,11 +425,11 @@ public class ApkPatcher { Collections.singletonList(publicKey)) .build()); DefaultApkSignerEngine signerEngine = new DefaultApkSignerEngine.Builder(engineSignerConfigs, 19) - .setV1SigningEnabled(true) - .setV2SigningEnabled(true) - .setV3SigningEnabled(false) - .build(); - if(originSignInfo != null && originSignInfo.getFirst() != null) { + .setV1SigningEnabled(true) + .setV2SigningEnabled(true) + .setV3SigningEnabled(false) + .build(); + if (originSignInfo != null && originSignInfo.getFirst() != null) { signerEngine.initWith(originSignInfo.getFirst(), originSignInfo.getSecond()); } long zipOpElapsed = stopwatch.elapsed(TimeUnit.MILLISECONDS); @@ -432,7 +450,7 @@ public class ApkPatcher { } }); thread.start(); - try(RandomAccessFile inputApkFile = new RandomAccessFile(apkPath, "r")) { + try (RandomAccessFile inputApkFile = new RandomAccessFile(apkPath, "r")) { ApkSigner signer = new ApkSigner.Builder(signerEngine) .setInputApk(DataSources.asDataSource(inputApkFile, 0, inputApkFile.length())) .setOutputApk(outputFile) @@ -527,7 +545,7 @@ public class ApkPatcher { } private void emitProgress(int progress) { - if(lastProgress < progress) { + if (lastProgress < progress) { lastProgress = progress; for (Consumer consumer : progressListener) { consumer.accept(progress); diff --git a/app/src/main/java/com/zane/smapiinstaller/logic/CommonLogic.java b/app/src/main/java/com/zane/smapiinstaller/logic/CommonLogic.java index 8e84e7b..51a44bc 100644 --- a/app/src/main/java/com/zane/smapiinstaller/logic/CommonLogic.java +++ b/app/src/main/java/com/zane/smapiinstaller/logic/CommonLogic.java @@ -4,6 +4,7 @@ import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.ClipData; import android.content.ClipboardManager; +import android.content.ContentResolver; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; @@ -59,6 +60,7 @@ import java.util.stream.Stream; import androidx.annotation.RequiresApi; import androidx.documentfile.provider.DocumentFile; + import pxb.android.axml.AxmlReader; import pxb.android.axml.AxmlVisitor; import pxb.android.axml.AxmlWriter; @@ -214,9 +216,6 @@ public class CommonLogic { * @return 操作是否成功 */ public static boolean unpackSmapiFiles(Context context, String apkPath, boolean checkMode, String packageName, long versionCode) { - if(!checkMusic(context)){ - return false; - } List apkFilesManifests = CommonLogic.findAllApkFileManifest(context); filterManifest(apkFilesManifests, packageName, versionCode); List manifestEntries = null; @@ -279,20 +278,8 @@ public class CommonLogic { File pathFrom = new File(FileUtils.getStadewValleyBasePath(), "Android/obb/" + Constants.ORIGIN_PACKAGE_NAME_GOOGLE); File pathTo = new File(FileUtils.getStadewValleyBasePath(), "StardewValley"); if (pathFrom.exists() && pathFrom.isDirectory()) { - if (!pathFrom.canRead()) { - ActivityResultHandler.registerListener(ActivityResultHandler.REQUEST_CODE_ALL_FILES_ACCESS_PERMISSION, (resultCode, data) -> { - if (resultCode == Activity.RESULT_OK) { - if (data == null) { - return; - } - Uri uri = data.getData(); - if (uri == null) { - return; - } - context.getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); - } - }); - openObbRootPermission((Activity) context, ActivityResultHandler.REQUEST_CODE_OBB_FILES_ACCESS_PERMISSION); + if(!checkObbRootPermission((Activity) context, ActivityResultHandler.REQUEST_CODE_OBB_FILES_ACCESS_PERMISSION, (success) -> checkMusic(context))) { + return false; } if (!pathTo.exists()) { pathTo.mkdirs(); @@ -475,14 +462,13 @@ public class CommonLogic { intent.setData(Uri.parse("package:" + activity.getPackageName())); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); activity.startActivityForResult(intent, ActivityResultHandler.REQUEST_CODE_ALL_FILES_ACCESS_PERMISSION); - } catch (ActivityNotFoundException ignored){ + } catch (ActivityNotFoundException ignored) { Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(Uri.parse("package:" + activity.getPackageName())); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); try { activity.startActivityForResult(intent, ActivityResultHandler.REQUEST_CODE_ALL_FILES_ACCESS_PERMISSION); - } - catch (ActivityNotFoundException ignored2) { + } catch (ActivityNotFoundException ignored2) { intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); activity.startActivityForResult(intent, ActivityResultHandler.REQUEST_CODE_ALL_FILES_ACCESS_PERMISSION); @@ -490,9 +476,15 @@ public class CommonLogic { } } - public static void openObbRootPermission(Activity context, int REQUEST_CODE_FOR_DIR) { - Uri uri1 = Uri.parse("content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fobb"); - DocumentFile documentFile = DocumentFile.fromTreeUri(context, uri1); + public static boolean checkDataRootPermission(Activity context, int REQUEST_CODE_FOR_DIR, Consumer callback) { + Uri targetDirUri = pathToUri("Android/data/" + Constants.ORIGIN_PACKAGE_NAME_GOOGLE); + if(checkPathPermission(context, targetDirUri)) { + return true; + } + ActivityResultHandler.registerListener(ActivityResultHandler.REQUEST_CODE_DATA_FILES_ACCESS_PERMISSION, (resultCode, data) -> { + takePermission(resultCode, data, context.getContentResolver(), callback); + }); + DocumentFile documentFile = DocumentFile.fromTreeUri(context, targetDirUri); Intent intent1 = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); intent1.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION @@ -500,6 +492,57 @@ public class CommonLogic { | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); intent1.putExtra(DocumentsContract.EXTRA_INITIAL_URI, documentFile.getUri()); context.startActivityForResult(intent1, REQUEST_CODE_FOR_DIR); + return false; + } + + private static void takePermission(Integer resultCode, Intent data, ContentResolver context, Consumer callback) { + if (resultCode == Activity.RESULT_OK) { + if (data == null) { + return; + } + Uri uri = data.getData(); + if (uri == null) { + return; + } + context.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + callback.accept(true); + } else { + callback.accept(false); + } + } + + public static Uri pathToUri(String path) { + return Uri.parse("content://com.android.externalstorage.documents/tree/primary%3A" + path.replace("/", "%3A")); + } + + public static boolean checkPathPermission(Context context, Uri targetDirUri) { + if(DocumentFile.fromTreeUri(context, targetDirUri).canWrite()) { + return true; + } + return false; + } + + public static boolean checkPathPermission(Context context, String path) { + return checkPathPermission(context, path); + } + + public static boolean checkObbRootPermission(Activity context, int REQUEST_CODE_FOR_DIR, Consumer callback) { + Uri targetDirUri = pathToUri("Android/obb"); + if(checkPathPermission(context, targetDirUri)) { + return true; + } + ActivityResultHandler.registerListener(ActivityResultHandler.REQUEST_CODE_OBB_FILES_ACCESS_PERMISSION, (resultCode, data) -> { + takePermission(resultCode, data, context.getContentResolver(), callback); + }); + DocumentFile documentFile = DocumentFile.fromTreeUri(context, targetDirUri); + Intent intent1 = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); + intent1.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION + | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION + | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); + intent1.putExtra(DocumentsContract.EXTRA_INITIAL_URI, documentFile.getUri()); + context.startActivityForResult(intent1, REQUEST_CODE_FOR_DIR); + return false; } public static void showPrivacyPolicy(View view, BiConsumer callback) { diff --git a/app/src/main/java/com/zane/smapiinstaller/ui/install/InstallFragment.java b/app/src/main/java/com/zane/smapiinstaller/ui/install/InstallFragment.java index c5e5b45..b17cea3 100644 --- a/app/src/main/java/com/zane/smapiinstaller/ui/install/InstallFragment.java +++ b/app/src/main/java/com/zane/smapiinstaller/ui/install/InstallFragment.java @@ -7,6 +7,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import com.google.common.collect.Lists; import com.google.common.io.Files; import com.zane.smapiinstaller.MainApplication; import com.zane.smapiinstaller.R; @@ -14,6 +15,8 @@ import com.zane.smapiinstaller.constant.AppConfigKeyConstants; import com.zane.smapiinstaller.constant.Constants; import com.zane.smapiinstaller.constant.DialogAction; import com.zane.smapiinstaller.databinding.FragmentInstallBinding; +import com.zane.smapiinstaller.dto.Tuple2; +import com.zane.smapiinstaller.logic.ActivityResultHandler; import com.zane.smapiinstaller.logic.ApkPatcher; import com.zane.smapiinstaller.logic.CommonLogic; import com.zane.smapiinstaller.logic.ModAssetsManager; @@ -22,12 +25,16 @@ import com.zane.smapiinstaller.utils.ConfigUtils; import com.zane.smapiinstaller.utils.DialogUtils; import com.zane.smapiinstaller.utils.FileUtils; +import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.RegExUtils; import org.apache.commons.lang3.StringUtils; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; @@ -95,12 +102,12 @@ public class InstallFragment extends Fragment { if (task != null) { task.interrupt(); } - task = new Thread(() -> CommonLogic.showProgressDialog(binding.getRoot(), context, (dialog)->{ + task = new Thread(() -> CommonLogic.showProgressDialog(binding.getRoot(), context, (dialog) -> { ApkPatcher patcher = new ApkPatcher(context); patcher.registerProgressListener((progress) -> DialogUtils.setProgressDialogState(binding.getRoot(), dialog, null, progress)); DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.extracting_package, null); - String path = patcher.extract(0); - if (path == null) { + Tuple2 paths = patcher.extract(0); + if (paths == null) { DialogUtils.showAlertDialog(binding.getRoot(), R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.error_game_not_found))); } })); @@ -111,17 +118,20 @@ public class InstallFragment extends Fragment { * 安装逻辑 */ private void installLogic(boolean isAdv) { +// if (!CommonLogic.checkDataRootPermission(context, ActivityResultHandler.REQUEST_CODE_DATA_FILES_ACCESS_PERMISSION, (success) -> installLogic(isAdv))) { +// return; +// } if (task != null) { task.interrupt(); } - task = new Thread(() -> CommonLogic.showProgressDialog(binding.getRoot(), context, (dialog)-> { + task = new Thread(() -> CommonLogic.showProgressDialog(binding.getRoot(), context, (dialog) -> { ApkPatcher patcher = new ApkPatcher(context); patcher.registerProgressListener((progress) -> DialogUtils.setProgressDialogState(binding.getRoot(), dialog, null, progress)); DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.extracting_package, null); - String path = patcher.extract(isAdv ? 1 : -1); + Tuple2 paths = patcher.extract(isAdv ? 1 : -1); String stadewValleyBasePath = FileUtils.getStadewValleyBasePath(); File dest = new File(stadewValleyBasePath + "/SMAPI Installer/"); - boolean failed = path == null; + boolean failed = paths == null; if (!dest.exists()) { if (!dest.mkdir()) { failed = true; @@ -132,16 +142,16 @@ public class InstallFragment extends Fragment { return; } DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.unpacking_smapi_files, null); - if (!CommonLogic.unpackSmapiFiles(context, path, false, patcher.getGamePackageName(), patcher.getGameVersionCode())) { - DialogUtils.showAlertDialog(binding.getRoot(), R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_unpack_smapi_files))); - return; - } - ModAssetsManager modAssetsManager = new ModAssetsManager(binding.getRoot()); - DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.unpacking_smapi_files, 6); - modAssetsManager.installDefaultMods(); +// if (!CommonLogic.unpackSmapiFiles(context, apkPath, false, patcher.getGamePackageName(), patcher.getGameVersionCode())) { +// DialogUtils.showAlertDialog(binding.getRoot(), R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_unpack_smapi_files))); +// return; +// } +// ModAssetsManager modAssetsManager = new ModAssetsManager(binding.getRoot()); +// DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.unpacking_smapi_files, 6); +// modAssetsManager.installDefaultMods(); DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.patching_package, 8); File targetApk = new File(dest, "base.apk"); - if (!patcher.patch(path, targetApk, isAdv)) { + if (!patcher.patch(paths.getFirst(), targetApk, isAdv, false)) { int target = patcher.getSwitchAction().getAndSet(0); if (target == R.string.menu_download) { DialogUtils.showConfirmDialog(binding.getRoot(), R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_patch_game)), R.string.menu_download, R.string.cancel, (d, which) -> { @@ -155,12 +165,21 @@ public class InstallFragment extends Fragment { } return; } + List resourcePacks = new ArrayList<>(); + if (paths.getSecond() != null) { + for (String resourcePack : paths.getSecond()) { + File targetResourcePack = new File(dest, FilenameUtils.getName(resourcePack)); + patcher.patch(resourcePack, targetResourcePack, false, true); + resourcePacks.add(targetResourcePack.getAbsolutePath()); + } + } DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.signing_package, null); String signPath = patcher.sign(targetApk.getAbsolutePath()); if (signPath == null) { DialogUtils.showAlertDialog(binding.getRoot(), R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_sign_game))); return; } + List signedResourcePacks = resourcePacks.stream().map(patcher::sign).collect(Collectors.toList()); DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.installing_package, null); patcher.install(signPath); })); diff --git a/app/src/main/java/com/zane/smapiinstaller/utils/ZipUtils.java b/app/src/main/java/com/zane/smapiinstaller/utils/ZipUtils.java index 36f86c0..aa82cf0 100644 --- a/app/src/main/java/com/zane/smapiinstaller/utils/ZipUtils.java +++ b/app/src/main/java/com/zane/smapiinstaller/utils/ZipUtils.java @@ -68,7 +68,7 @@ public class ZipUtils { public static Map unpackXABA(byte[] manifestBytes, byte[] xabaBytes) { List> manifest = Splitter.on('\n').omitEmptyStrings().splitToList(new String(manifestBytes, StandardCharsets.UTF_8)) .stream().skip(1).map(line -> Splitter.on(CharMatcher.whitespace()).omitEmptyStrings().splitToList(line)).collect(Collectors.toList()); - ByteSource source = ByteSource.wrap(manifestBytes); + ByteSource source = ByteSource.wrap(xabaBytes); Map result = new HashMap<>(); try { int offset = 0;