modify logic
This commit is contained in:
parent
c3014f05de
commit
a6c21a83a8
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.
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.
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.
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.
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,4 +50,9 @@ public class ManifestEntry {
|
||||||
*/
|
*/
|
||||||
@JsonProperty("isXALZ")
|
@JsonProperty("isXALZ")
|
||||||
private boolean isXALZ;
|
private boolean isXALZ;
|
||||||
|
/**
|
||||||
|
* 是否为XABA压缩格式
|
||||||
|
*/
|
||||||
|
@JsonProperty("isXABA")
|
||||||
|
private boolean isXABA;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<>();
|
||||||
|
|
||||||
|
|
|
@ -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,7 +228,17 @@ 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)) {
|
||||||
|
if (entry.isXABA()) {
|
||||||
|
byte[] manifestBytes = ZipUtil.unpackEntry(apkFile, entry.getAssetPath() + ".manifest");
|
||||||
|
byte[] xabaBytes = ZipUtil.unpackEntry(apkFile, entry.getAssetPath() + ".blob");
|
||||||
|
Map<String, byte[]> unpackedAssemblies = ZipUtils.unpackXABA(manifestBytes, xabaBytes);
|
||||||
|
ArrayList<ZipUtils.ZipEntrySource> 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 path = StringUtils.substringBeforeLast(entry.getAssetPath(), Constants.FILE_SEPARATOR);
|
||||||
String pattern = StringUtils.substringAfterLast(entry.getAssetPath(), Constants.FILE_SEPARATOR);
|
String pattern = StringUtils.substringAfterLast(entry.getAssetPath(), Constants.FILE_SEPARATOR);
|
||||||
try {
|
try {
|
||||||
|
@ -260,6 +273,7 @@ public class ApkPatcher {
|
||||||
}
|
}
|
||||||
} catch (IOException ignored) {
|
} catch (IOException ignored) {
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
ZipUtils.ZipEntrySource source;
|
ZipUtils.ZipEntrySource source;
|
||||||
|
@ -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());
|
||||||
|
@ -411,7 +429,7 @@ public class ApkPatcher {
|
||||||
.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);
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue