modify logic

This commit is contained in:
zhiyang7 2023-02-09 17:59:43 +08:00
parent c3014f05de
commit a6c21a83a8
47 changed files with 175 additions and 87 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -72,9 +72,9 @@
}, },
{ {
"targetPath": "assemblies/", "targetPath": "assemblies/",
"assetPath": "assemblies/assemblies.blob", "assetPath": "assemblies/assemblies",
"origin": 1, "origin": 1,
"isXABB": true, "isXABA": true,
"compression": 0, "compression": 0,
"external": false "external": false
} }

View File

@ -15,4 +15,6 @@ public class ManifestPatchConstants {
public static final CharSequence APP_PACKAGE_NAME = "com.chucklefish.stardewvalley"; public static final CharSequence APP_PACKAGE_NAME = "com.chucklefish.stardewvalley";
public static final String PATTERN_VERSION_AMAZON = "amazon"; public static final String PATTERN_VERSION_AMAZON = "amazon";
public static final String PATTERN_EXTRACT_NATIVE_LIBS = "extractNativeLibs";
} }

View File

@ -50,4 +50,9 @@ public class ManifestEntry {
*/ */
@JsonProperty("isXALZ") @JsonProperty("isXALZ")
private boolean isXALZ; private boolean isXALZ;
/**
* 是否为XABA压缩格式
*/
@JsonProperty("isXABA")
private boolean isXABA;
} }

View File

@ -13,6 +13,7 @@ public class ActivityResultHandler {
public static final int REQUEST_CODE_APP_INSTALL = 1001; 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_ALL_FILES_ACCESS_PERMISSION = 1002;
public static final int REQUEST_CODE_OBB_FILES_ACCESS_PERMISSION = 1003; 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<Integer, BiConsumer<Integer, Intent>> listenerMap = new ConcurrentHashMap<>(); public static ConcurrentHashMap<Integer, BiConsumer<Integer, Intent>> listenerMap = new ConcurrentHashMap<>();

View File

@ -36,6 +36,7 @@ import com.zane.smapiinstaller.utils.ZipUtils;
import net.fornwall.apksigner.KeyStoreFileManager; import net.fornwall.apksigner.KeyStoreFileManager;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.NotImplementedException;
import org.zeroturnaround.zip.ZipUtil; import org.zeroturnaround.zip.ZipUtil;
@ -50,6 +51,7 @@ import java.security.cert.X509Certificate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -64,6 +66,7 @@ import java.util.zip.Deflater;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.FileProvider; import androidx.core.content.FileProvider;
import pxb.android.axml.NodeVisitor; import pxb.android.axml.NodeVisitor;
/** /**
@ -109,7 +112,7 @@ public class ApkPatcher {
* @param advancedStage 0: 初始化1: 高级安装-1: 普通安装 * @param advancedStage 0: 初始化1: 高级安装-1: 普通安装
* @return 抽取后的APK文件路径如果抽取失败返回null * @return 抽取后的APK文件路径如果抽取失败返回null
*/ */
public String extract(int advancedStage) { public Tuple2<String, String[]> extract(int advancedStage) {
emitProgress(0); emitProgress(0);
PackageManager packageManager = context.getPackageManager(); PackageManager packageManager = context.getPackageManager();
List<String> packageNames = FileUtils.getAssetJson(context, "package_names.json", new TypeReference<List<String>>() { List<String> packageNames = FileUtils.getAssetJson(context, "package_names.json", new TypeReference<List<String>>() {
@ -143,7 +146,7 @@ public class ApkPatcher {
} }
return null; return null;
}); });
return apkFile.getAbsolutePath(); return new Tuple2<>(apkFile.getAbsolutePath(), null);
} else if (advancedStage == 1) { } else if (advancedStage == 1) {
File contentFolder = new File(stadewValleyBasePath + "/StardewValley/Content"); File contentFolder = new File(stadewValleyBasePath + "/StardewValley/Content");
if (contentFolder.exists()) { if (contentFolder.exists()) {
@ -154,10 +157,10 @@ public class ApkPatcher {
} else { } else {
extract(0); extract(0);
} }
return apkFile.getAbsolutePath(); return new Tuple2<>(apkFile.getAbsolutePath(), null);
} }
emitProgress(5); emitProgress(5);
return apkFile.getAbsolutePath(); return new Tuple2<>(apkFile.getAbsolutePath(), packageInfo.applicationInfo.splitSourceDirs);
} catch (PackageManager.NameNotFoundException ignored) { } catch (PackageManager.NameNotFoundException ignored) {
return null; return null;
} }
@ -174,7 +177,7 @@ public class ApkPatcher {
* @param isAdvanced 是否高级模式 * @param isAdvanced 是否高级模式
* @return 是否成功打包 * @return 是否成功打包
*/ */
public boolean patch(String apkPath, File targetFile, boolean isAdvanced) { public boolean patch(String apkPath, File targetFile, boolean isAdvanced, boolean isResourcePack) {
if (apkPath == null) { if (apkPath == null) {
return false; return false;
} }
@ -199,7 +202,7 @@ public class ApkPatcher {
ApkFilesManifest apkFilesManifest = apkFilesManifests.get(0); ApkFilesManifest apkFilesManifest = apkFilesManifests.get(0);
List<ManifestEntry> manifestEntries = apkFilesManifest.getManifestEntries(); List<ManifestEntry> manifestEntries = apkFilesManifest.getManifestEntries();
errorMessage.set(null); errorMessage.set(null);
List<ZipUtils.ZipEntrySource> entries = manifestEntries.stream() List<ZipUtils.ZipEntrySource> entries = isResourcePack ? new ArrayList<>() : manifestEntries.stream()
.map(entry -> processFileEntry(file, apkFilesManifest, entry, isAdvanced)) .map(entry -> processFileEntry(file, apkFilesManifest, entry, isAdvanced))
.filter(Objects::nonNull).flatMap(Stream::of).distinct().collect(Collectors.toList()); .filter(Objects::nonNull).flatMap(Stream::of).distinct().collect(Collectors.toList());
entries.add(new ZipUtils.ZipEntrySource("AndroidManifest.xml", modifiedManifest, Deflater.DEFLATED)); entries.add(new ZipUtils.ZipEntrySource("AndroidManifest.xml", modifiedManifest, Deflater.DEFLATED));
@ -208,7 +211,7 @@ public class ApkPatcher {
stopwatch.reset(); stopwatch.reset();
stopwatch.start(); stopwatch.start();
originSignInfo = ZipUtils.addOrReplaceEntries(apkPath, entries, targetFile.getAbsolutePath(), 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))); (progress) -> emitProgress((int) (baseProgress + (progress / 100.0) * 35)));
stopwatch.stop(); stopwatch.stop();
emitProgress(46); emitProgress(46);
@ -225,40 +228,51 @@ public class ApkPatcher {
if (entry.isAdvanced() && !isAdvanced) { if (entry.isAdvanced() && !isAdvanced) {
return null; return null;
} }
if (entry.getTargetPath().endsWith(Constants.FILE_SEPARATOR) && entry.getAssetPath().contains("*")) { if (entry.getTargetPath().endsWith(Constants.FILE_SEPARATOR)) {
String path = StringUtils.substringBeforeLast(entry.getAssetPath(), Constants.FILE_SEPARATOR); if (entry.isXABA()) {
String pattern = StringUtils.substringAfterLast(entry.getAssetPath(), Constants.FILE_SEPARATOR); byte[] manifestBytes = ZipUtil.unpackEntry(apkFile, entry.getAssetPath() + ".manifest");
try { byte[] xabaBytes = ZipUtil.unpackEntry(apkFile, entry.getAssetPath() + ".blob");
if (entry.getOrigin() == 1) { Map<String, byte[]> unpackedAssemblies = ZipUtils.unpackXABA(manifestBytes, xabaBytes);
ArrayList<ZipUtils.ZipEntrySource> list = new ArrayList<>(); ArrayList<ZipUtils.ZipEntrySource> list = new ArrayList<>();
ZipUtil.iterate(apkFile, (in, zipEntry) -> { unpackedAssemblies.forEach((filename, bytes) -> {
String entryPath = StringUtils.substringBeforeLast(zipEntry.getName(), Constants.FILE_SEPARATOR); list.add(new ZipUtils.ZipEntrySource(entry.getTargetPath() + filename, bytes, entry.getCompression()));
String filename = StringUtils.substringAfterLast(zipEntry.getName(), Constants.FILE_SEPARATOR); });
if (entryPath.equals(path) && StringUtils.wildCardMatch(filename, pattern)) { return list.toArray(new ZipUtils.ZipEntrySource[0]);
byte[] bytes = ByteStreams.toByteArray(in); } else if (entry.getAssetPath().contains("*")) {
ZipUtils.ZipEntrySource source; String path = StringUtils.substringBeforeLast(entry.getAssetPath(), Constants.FILE_SEPARATOR);
if (entry.isXALZ()) { String pattern = StringUtils.substringAfterLast(entry.getAssetPath(), Constants.FILE_SEPARATOR);
source = new ZipUtils.ZipEntrySource(entry.getTargetPath() + filename, entry.getCompression(), () -> new ByteArrayInputStream(ZipUtils.decompressXALZ(bytes))); try {
} else { if (entry.getOrigin() == 1) {
source = new ZipUtils.ZipEntrySource(entry.getTargetPath() + filename, bytes, entry.getCompression()); ArrayList<ZipUtils.ZipEntrySource> list = new ArrayList<>();
} ZipUtil.iterate(apkFile, (in, zipEntry) -> {
list.add(source); 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)) {
return list.toArray(new ZipUtils.ZipEntrySource[0]); byte[] bytes = ByteStreams.toByteArray(in);
} else { ZipUtils.ZipEntrySource source;
return Stream.of(context.getAssets().list(path)) if (entry.isXALZ()) {
.filter(filename -> StringUtils.wildCardMatch(filename, pattern)) source = new ZipUtils.ZipEntrySource(entry.getTargetPath() + filename, entry.getCompression(), () -> new ByteArrayInputStream(ZipUtils.decompressXALZ(bytes)));
.map(filename -> new ZipUtils.ZipEntrySource(entry.getTargetPath() + filename, entry.getCompression(), () -> { } else {
try { source = new ZipUtils.ZipEntrySource(entry.getTargetPath() + filename, bytes, entry.getCompression());
return FileUtils.getLocalAsset(context, path + Constants.FILE_SEPARATOR + filename);
} catch (IOException ignored) {
} }
return null; list.add(source);
})) }
.toArray(ZipUtils.ZipEntrySource[]::new); });
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; return null;
} }
@ -351,6 +365,10 @@ public class ApkPatcher {
if (StringUtils.equals(attr.name, ManifestPatchConstants.PATTERN_VERSION_CODE)) { if (StringUtils.equals(attr.name, ManifestPatchConstants.PATTERN_VERSION_CODE)) {
versionCode.set((int) attr.obj); 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; return null;
}; };
@ -390,7 +408,7 @@ public class ApkPatcher {
try { try {
String stadewValleyBasePath = FileUtils.getStadewValleyBasePath(); String stadewValleyBasePath = FileUtils.getStadewValleyBasePath();
emitProgress(47); 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(); KeyStore ks = new KeyStoreFileManager.JksKeyStore();
try (InputStream fis = context.getAssets().open("debug.keystore.dat")) { try (InputStream fis = context.getAssets().open("debug.keystore.dat")) {
ks.load(fis, PASSWORD.toCharArray()); ks.load(fis, PASSWORD.toCharArray());
@ -407,11 +425,11 @@ public class ApkPatcher {
Collections.singletonList(publicKey)) Collections.singletonList(publicKey))
.build()); .build());
DefaultApkSignerEngine signerEngine = new DefaultApkSignerEngine.Builder(engineSignerConfigs, 19) DefaultApkSignerEngine signerEngine = new DefaultApkSignerEngine.Builder(engineSignerConfigs, 19)
.setV1SigningEnabled(true) .setV1SigningEnabled(true)
.setV2SigningEnabled(true) .setV2SigningEnabled(true)
.setV3SigningEnabled(false) .setV3SigningEnabled(false)
.build(); .build();
if(originSignInfo != null && originSignInfo.getFirst() != null) { if (originSignInfo != null && originSignInfo.getFirst() != null) {
signerEngine.initWith(originSignInfo.getFirst(), originSignInfo.getSecond()); signerEngine.initWith(originSignInfo.getFirst(), originSignInfo.getSecond());
} }
long zipOpElapsed = stopwatch.elapsed(TimeUnit.MILLISECONDS); long zipOpElapsed = stopwatch.elapsed(TimeUnit.MILLISECONDS);
@ -432,7 +450,7 @@ public class ApkPatcher {
} }
}); });
thread.start(); thread.start();
try(RandomAccessFile inputApkFile = new RandomAccessFile(apkPath, "r")) { try (RandomAccessFile inputApkFile = new RandomAccessFile(apkPath, "r")) {
ApkSigner signer = new ApkSigner.Builder(signerEngine) ApkSigner signer = new ApkSigner.Builder(signerEngine)
.setInputApk(DataSources.asDataSource(inputApkFile, 0, inputApkFile.length())) .setInputApk(DataSources.asDataSource(inputApkFile, 0, inputApkFile.length()))
.setOutputApk(outputFile) .setOutputApk(outputFile)
@ -527,7 +545,7 @@ public class ApkPatcher {
} }
private void emitProgress(int progress) { private void emitProgress(int progress) {
if(lastProgress < progress) { if (lastProgress < progress) {
lastProgress = progress; lastProgress = progress;
for (Consumer<Integer> consumer : progressListener) { for (Consumer<Integer> consumer : progressListener) {
consumer.accept(progress); consumer.accept(progress);

View File

@ -4,6 +4,7 @@ import android.app.Activity;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.ClipData; import android.content.ClipData;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.ContextWrapper; import android.content.ContextWrapper;
import android.content.Intent; import android.content.Intent;
@ -59,6 +60,7 @@ import java.util.stream.Stream;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.documentfile.provider.DocumentFile; import androidx.documentfile.provider.DocumentFile;
import pxb.android.axml.AxmlReader; import pxb.android.axml.AxmlReader;
import pxb.android.axml.AxmlVisitor; import pxb.android.axml.AxmlVisitor;
import pxb.android.axml.AxmlWriter; import pxb.android.axml.AxmlWriter;
@ -214,9 +216,6 @@ public class CommonLogic {
* @return 操作是否成功 * @return 操作是否成功
*/ */
public static boolean unpackSmapiFiles(Context context, String apkPath, boolean checkMode, String packageName, long versionCode) { public static boolean unpackSmapiFiles(Context context, String apkPath, boolean checkMode, String packageName, long versionCode) {
if(!checkMusic(context)){
return false;
}
List<ApkFilesManifest> apkFilesManifests = CommonLogic.findAllApkFileManifest(context); List<ApkFilesManifest> apkFilesManifests = CommonLogic.findAllApkFileManifest(context);
filterManifest(apkFilesManifests, packageName, versionCode); filterManifest(apkFilesManifests, packageName, versionCode);
List<ManifestEntry> manifestEntries = null; List<ManifestEntry> manifestEntries = null;
@ -279,20 +278,8 @@ public class CommonLogic {
File pathFrom = new File(FileUtils.getStadewValleyBasePath(), "Android/obb/" + Constants.ORIGIN_PACKAGE_NAME_GOOGLE); File pathFrom = new File(FileUtils.getStadewValleyBasePath(), "Android/obb/" + Constants.ORIGIN_PACKAGE_NAME_GOOGLE);
File pathTo = new File(FileUtils.getStadewValleyBasePath(), "StardewValley"); File pathTo = new File(FileUtils.getStadewValleyBasePath(), "StardewValley");
if (pathFrom.exists() && pathFrom.isDirectory()) { if (pathFrom.exists() && pathFrom.isDirectory()) {
if (!pathFrom.canRead()) { if(!checkObbRootPermission((Activity) context, ActivityResultHandler.REQUEST_CODE_OBB_FILES_ACCESS_PERMISSION, (success) -> checkMusic(context))) {
ActivityResultHandler.registerListener(ActivityResultHandler.REQUEST_CODE_ALL_FILES_ACCESS_PERMISSION, (resultCode, data) -> { return false;
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 (!pathTo.exists()) { if (!pathTo.exists()) {
pathTo.mkdirs(); pathTo.mkdirs();
@ -475,14 +462,13 @@ public class CommonLogic {
intent.setData(Uri.parse("package:" + activity.getPackageName())); intent.setData(Uri.parse("package:" + activity.getPackageName()));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
activity.startActivityForResult(intent, ActivityResultHandler.REQUEST_CODE_ALL_FILES_ACCESS_PERMISSION); 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 intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + activity.getPackageName())); intent.setData(Uri.parse("package:" + activity.getPackageName()));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try { try {
activity.startActivityForResult(intent, ActivityResultHandler.REQUEST_CODE_ALL_FILES_ACCESS_PERMISSION); 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 = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
activity.startActivityForResult(intent, ActivityResultHandler.REQUEST_CODE_ALL_FILES_ACCESS_PERMISSION); 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) { public static boolean checkDataRootPermission(Activity context, int REQUEST_CODE_FOR_DIR, Consumer<Boolean> callback) {
Uri uri1 = Uri.parse("content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fobb"); Uri targetDirUri = pathToUri("Android/data/" + Constants.ORIGIN_PACKAGE_NAME_GOOGLE);
DocumentFile documentFile = DocumentFile.fromTreeUri(context, uri1); 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); Intent intent1 = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent1.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION intent1.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
@ -500,6 +492,57 @@ public class CommonLogic {
| Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
intent1.putExtra(DocumentsContract.EXTRA_INITIAL_URI, documentFile.getUri()); intent1.putExtra(DocumentsContract.EXTRA_INITIAL_URI, documentFile.getUri());
context.startActivityForResult(intent1, REQUEST_CODE_FOR_DIR); context.startActivityForResult(intent1, REQUEST_CODE_FOR_DIR);
return false;
}
private static void takePermission(Integer resultCode, Intent data, ContentResolver context, Consumer<Boolean> 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<Boolean> 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<MaterialDialog, DialogAction> callback) { public static void showPrivacyPolicy(View view, BiConsumer<MaterialDialog, DialogAction> callback) {

View File

@ -7,6 +7,7 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.google.common.collect.Lists;
import com.google.common.io.Files; import com.google.common.io.Files;
import com.zane.smapiinstaller.MainApplication; import com.zane.smapiinstaller.MainApplication;
import com.zane.smapiinstaller.R; 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.Constants;
import com.zane.smapiinstaller.constant.DialogAction; import com.zane.smapiinstaller.constant.DialogAction;
import com.zane.smapiinstaller.databinding.FragmentInstallBinding; 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.ApkPatcher;
import com.zane.smapiinstaller.logic.CommonLogic; import com.zane.smapiinstaller.logic.CommonLogic;
import com.zane.smapiinstaller.logic.ModAssetsManager; 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.DialogUtils;
import com.zane.smapiinstaller.utils.FileUtils; import com.zane.smapiinstaller.utils.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.RegExUtils; import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
@ -95,12 +102,12 @@ public class InstallFragment extends Fragment {
if (task != null) { if (task != null) {
task.interrupt(); 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); ApkPatcher patcher = new ApkPatcher(context);
patcher.registerProgressListener((progress) -> DialogUtils.setProgressDialogState(binding.getRoot(), dialog, null, progress)); patcher.registerProgressListener((progress) -> DialogUtils.setProgressDialogState(binding.getRoot(), dialog, null, progress));
DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.extracting_package, null); DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.extracting_package, null);
String path = patcher.extract(0); Tuple2<String, String[]> paths = patcher.extract(0);
if (path == null) { if (paths == null) {
DialogUtils.showAlertDialog(binding.getRoot(), R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.error_game_not_found))); 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) { private void installLogic(boolean isAdv) {
// if (!CommonLogic.checkDataRootPermission(context, ActivityResultHandler.REQUEST_CODE_DATA_FILES_ACCESS_PERMISSION, (success) -> installLogic(isAdv))) {
// return;
// }
if (task != null) { if (task != null) {
task.interrupt(); 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); ApkPatcher patcher = new ApkPatcher(context);
patcher.registerProgressListener((progress) -> DialogUtils.setProgressDialogState(binding.getRoot(), dialog, null, progress)); patcher.registerProgressListener((progress) -> DialogUtils.setProgressDialogState(binding.getRoot(), dialog, null, progress));
DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.extracting_package, null); DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.extracting_package, null);
String path = patcher.extract(isAdv ? 1 : -1); Tuple2<String, String[]> paths = patcher.extract(isAdv ? 1 : -1);
String stadewValleyBasePath = FileUtils.getStadewValleyBasePath(); String stadewValleyBasePath = FileUtils.getStadewValleyBasePath();
File dest = new File(stadewValleyBasePath + "/SMAPI Installer/"); File dest = new File(stadewValleyBasePath + "/SMAPI Installer/");
boolean failed = path == null; boolean failed = paths == null;
if (!dest.exists()) { if (!dest.exists()) {
if (!dest.mkdir()) { if (!dest.mkdir()) {
failed = true; failed = true;
@ -132,16 +142,16 @@ public class InstallFragment extends Fragment {
return; return;
} }
DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.unpacking_smapi_files, null); DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.unpacking_smapi_files, null);
if (!CommonLogic.unpackSmapiFiles(context, path, false, patcher.getGamePackageName(), patcher.getGameVersionCode())) { // 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))); // DialogUtils.showAlertDialog(binding.getRoot(), R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_unpack_smapi_files)));
return; // return;
} // }
ModAssetsManager modAssetsManager = new ModAssetsManager(binding.getRoot()); // ModAssetsManager modAssetsManager = new ModAssetsManager(binding.getRoot());
DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.unpacking_smapi_files, 6); // DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.unpacking_smapi_files, 6);
modAssetsManager.installDefaultMods(); // modAssetsManager.installDefaultMods();
DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.patching_package, 8); DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.patching_package, 8);
File targetApk = new File(dest, "base.apk"); 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); int target = patcher.getSwitchAction().getAndSet(0);
if (target == R.string.menu_download) { 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) -> { 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; return;
} }
List<String> 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); DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.signing_package, null);
String signPath = patcher.sign(targetApk.getAbsolutePath()); String signPath = patcher.sign(targetApk.getAbsolutePath());
if (signPath == null) { if (signPath == null) {
DialogUtils.showAlertDialog(binding.getRoot(), R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_sign_game))); DialogUtils.showAlertDialog(binding.getRoot(), R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_sign_game)));
return; return;
} }
List<String> signedResourcePacks = resourcePacks.stream().map(patcher::sign).collect(Collectors.toList());
DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.installing_package, null); DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.installing_package, null);
patcher.install(signPath); patcher.install(signPath);
})); }));

View File

@ -68,7 +68,7 @@ public class ZipUtils {
public static Map<String, byte[]> unpackXABA(byte[] manifestBytes, byte[] xabaBytes) { public static Map<String, byte[]> unpackXABA(byte[] manifestBytes, byte[] xabaBytes) {
List<List<String>> manifest = Splitter.on('\n').omitEmptyStrings().splitToList(new String(manifestBytes, StandardCharsets.UTF_8)) List<List<String>> 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()); .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<String, byte[]> result = new HashMap<>(); Map<String, byte[]> result = new HashMap<>();
try { try {
int offset = 0; int offset = 0;