1.Improve patch processing speed
2.Make process bar more accurately
This commit is contained in:
parent
ebb36e9388
commit
27d7ba816a
|
@ -23,11 +23,11 @@ android {
|
|||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
debug {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
// debug {
|
||||
// minifyEnabled true
|
||||
// shrinkResources true
|
||||
// proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
// }
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
|
@ -57,8 +57,8 @@ dependencies {
|
|||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'com.google.android.material:material:1.1.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'androidx.navigation:navigation-fragment:2.3.0-alpha03'
|
||||
implementation 'androidx.navigation:navigation-ui:2.3.0-alpha03'
|
||||
implementation 'androidx.navigation:navigation-fragment:2.3.0-alpha04'
|
||||
implementation 'androidx.navigation:navigation-ui:2.3.0-alpha04'
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
||||
implementation 'com.madgag.spongycastle:core:1.54.0.0'
|
||||
implementation 'com.madgag.spongycastle:prov:1.54.0.0'
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.zane.smapiinstaller.entity;
|
||||
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import lombok.Data;
|
||||
|
@ -9,6 +10,7 @@ import lombok.Data;
|
|||
* Mod信息
|
||||
*/
|
||||
@Data
|
||||
@JsonAutoDetect(fieldVisibility=JsonAutoDetect.Visibility.ANY, getterVisibility= JsonAutoDetect.Visibility.NONE)
|
||||
public class ModManifestEntry {
|
||||
/**
|
||||
* 存放位置
|
||||
|
|
|
@ -13,6 +13,7 @@ import android.util.Log;
|
|||
import com.android.apksig.ApkSigner;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Stopwatch;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.io.Files;
|
||||
import com.zane.smapiinstaller.BuildConfig;
|
||||
|
@ -22,13 +23,11 @@ import com.zane.smapiinstaller.constant.ManifestPatchConstants;
|
|||
import com.zane.smapiinstaller.entity.ApkFilesManifest;
|
||||
import com.zane.smapiinstaller.entity.ManifestEntry;
|
||||
import com.zane.smapiinstaller.utils.FileUtils;
|
||||
import com.zane.smapiinstaller.utils.ZipUtils;
|
||||
|
||||
import net.fornwall.apksigner.KeyStoreFileManager;
|
||||
import net.fornwall.apksigner.ZipAligner;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.zeroturnaround.zip.ByteSource;
|
||||
import org.zeroturnaround.zip.ZipEntrySource;
|
||||
import org.zeroturnaround.zip.ZipUtil;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -40,12 +39,18 @@ import java.security.cert.X509Certificate;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import java9.util.function.Consumer;
|
||||
import java9.util.stream.Collectors;
|
||||
|
||||
import java.util.zip.Deflater;
|
||||
|
||||
import androidx.core.content.FileProvider;
|
||||
import java9.util.stream.StreamSupport;
|
||||
import pxb.android.axml.NodeVisitor;
|
||||
|
||||
public class ApkPatcher {
|
||||
|
@ -60,21 +65,29 @@ public class ApkPatcher {
|
|||
|
||||
private AtomicInteger switchAction = new AtomicInteger();
|
||||
|
||||
private List<Consumer<Integer>> progressListener = new ArrayList<>();
|
||||
|
||||
private Stopwatch stopwatch = Stopwatch.createUnstarted();
|
||||
|
||||
public ApkPatcher(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* 依次扫描package_names.json文件对应的包名,抽取找到的第一个游戏APK到SMAPI Installer路径
|
||||
*
|
||||
* @return 抽取后的APK文件路径,如果抽取失败返回null
|
||||
*/
|
||||
public String extract() {
|
||||
emitProgress(0);
|
||||
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>>() {
|
||||
});
|
||||
if (packageNames == null) {
|
||||
errorMessage.set(context.getString(R.string.error_game_not_found));
|
||||
return null;
|
||||
}
|
||||
emitProgress(1);
|
||||
for (String packageName : packageNames) {
|
||||
try {
|
||||
PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0);
|
||||
|
@ -92,6 +105,7 @@ public class ApkPatcher {
|
|||
}
|
||||
File distFile = new File(dest, apkFile.getName());
|
||||
Files.copy(apkFile, distFile);
|
||||
emitProgress(5);
|
||||
return distFile.getAbsolutePath();
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException | IOException e) {
|
||||
|
@ -104,6 +118,7 @@ public class ApkPatcher {
|
|||
|
||||
/**
|
||||
* 将指定APK文件重新打包,添加SMAPI,修改AndroidManifest.xml,同时验证版本是否正确
|
||||
*
|
||||
* @param apkPath APK文件路径
|
||||
* @return 是否成功打包
|
||||
*/
|
||||
|
@ -116,31 +131,43 @@ public class ApkPatcher {
|
|||
return false;
|
||||
}
|
||||
try {
|
||||
List<ZipEntrySource> zipEntrySourceList = new ArrayList<>();
|
||||
byte[] manifest = ZipUtil.unpackEntry(file, "AndroidManifest.xml");
|
||||
emitProgress(9);
|
||||
List<ApkFilesManifest> apkFilesManifests = CommonLogic.findAllApkFileManifest(context);
|
||||
byte[] modifiedManifest = modifyManifest(manifest, apkFilesManifests);
|
||||
if(apkFilesManifests.size() == 0) {
|
||||
if (apkFilesManifests.size() == 0) {
|
||||
errorMessage.set(context.getString(R.string.error_no_supported_game_version));
|
||||
switchAction.set(R.string.menu_download);
|
||||
return false;
|
||||
}
|
||||
if(modifiedManifest == null) {
|
||||
if (modifiedManifest == null) {
|
||||
errorMessage.set(context.getString(R.string.failed_to_process_manifest));
|
||||
return false;
|
||||
}
|
||||
zipEntrySourceList.add(new ByteSource("AndroidManifest.xml", modifiedManifest, Deflater.DEFLATED));
|
||||
ApkFilesManifest apkFilesManifest = apkFilesManifests.get(0);
|
||||
List<ManifestEntry> manifestEntries = apkFilesManifest.getManifestEntries();
|
||||
for (ManifestEntry entry : manifestEntries) {
|
||||
if(entry.isExternal()) {
|
||||
zipEntrySourceList.add(new ByteSource(entry.getTargetPath(), FileUtils.getAssetBytes(context, apkFilesManifest.getBasePath() + entry.getAssetPath()), entry.getCompression()));
|
||||
List<ZipUtils.ZipEntrySource> entries = StreamSupport.stream(manifestEntries).map(entry -> {
|
||||
if (entry.isExternal()) {
|
||||
return new ZipUtils.ZipEntrySource(entry.getTargetPath(), FileUtils.getAssetBytes(context, apkFilesManifest.getBasePath() + entry.getAssetPath()), entry.getCompression());
|
||||
} else {
|
||||
return new ZipUtils.ZipEntrySource(entry.getTargetPath(), FileUtils.getAssetBytes(context, entry.getAssetPath()), entry.getCompression());
|
||||
}
|
||||
else {
|
||||
zipEntrySourceList.add(new ByteSource(entry.getTargetPath(), FileUtils.getAssetBytes(context, entry.getAssetPath()), entry.getCompression()));
|
||||
}
|
||||
}
|
||||
ZipUtil.addOrReplaceEntries(file, zipEntrySourceList.toArray(new ZipEntrySource[0]));
|
||||
}).collect(Collectors.toList());
|
||||
entries.add(new ZipUtils.ZipEntrySource("AndroidManifest.xml", modifiedManifest, Deflater.DEFLATED));
|
||||
emitProgress(10);
|
||||
String patchedFilename = apkPath + ".patched";
|
||||
File patchedFile = new File(patchedFilename);
|
||||
int baseProgress = 10;
|
||||
stopwatch.reset();
|
||||
stopwatch.start();
|
||||
ZipUtils.addOrReplaceEntries(apkPath, entries, patchedFilename, (progress)->{
|
||||
emitProgress((int) (baseProgress + (progress / 100.0) * 35));
|
||||
});
|
||||
stopwatch.stop();
|
||||
emitProgress(45);
|
||||
FileUtils.forceDelete(file);
|
||||
FileUtils.moveFile(patchedFile, file);
|
||||
emitProgress(46);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Patch error", e);
|
||||
|
@ -151,7 +178,8 @@ public class ApkPatcher {
|
|||
|
||||
/**
|
||||
* 扫描全部兼容包,寻找匹配的版本,修改AndroidManifest.xml文件
|
||||
* @param bytes AndroidManifest.xml的字节数组
|
||||
*
|
||||
* @param bytes AndroidManifest.xml的字节数组
|
||||
* @param manifests 兼容包列表
|
||||
* @return 修改后的AndroidManifest.xml的字节数组
|
||||
*/
|
||||
|
@ -176,8 +204,7 @@ public class ApkPatcher {
|
|||
case "authorities":
|
||||
if (strObj.contains(packageName.get())) {
|
||||
attr.obj = strObj.replace(packageName.get(), Constants.TARGET_PACKAGE_NAME);
|
||||
}
|
||||
else if(strObj.contains(ManifestPatchConstants.APP_PACKAGE_NAME)){
|
||||
} else if (strObj.contains(ManifestPatchConstants.APP_PACKAGE_NAME)) {
|
||||
attr.obj = strObj.replace(ManifestPatchConstants.APP_PACKAGE_NAME, Constants.TARGET_PACKAGE_NAME);
|
||||
}
|
||||
case "name":
|
||||
|
@ -188,9 +215,8 @@ public class ApkPatcher {
|
|||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if(attr.type == NodeVisitor.TYPE_FIRST_INT) {
|
||||
if(StringUtils.equals(attr.name, ManifestPatchConstants.PATTERN_VERSION_CODE)){
|
||||
} else if (attr.type == NodeVisitor.TYPE_FIRST_INT) {
|
||||
if (StringUtils.equals(attr.name, ManifestPatchConstants.PATTERN_VERSION_CODE)) {
|
||||
versionCode.set((int) attr.obj);
|
||||
}
|
||||
}
|
||||
|
@ -207,13 +233,13 @@ public class ApkPatcher {
|
|||
return true;
|
||||
}
|
||||
}
|
||||
if(manifest.getTargetPackageName() != null && packageName.get() != null && !manifest.getTargetPackageName().contains(packageName.get())) {
|
||||
if (manifest.getTargetPackageName() != null && packageName.get() != null && !manifest.getTargetPackageName().contains(packageName.get())) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return modifyManifest;
|
||||
}catch (Exception e) {
|
||||
} catch (Exception e) {
|
||||
errorMessage.set(e.getLocalizedMessage());
|
||||
return null;
|
||||
}
|
||||
|
@ -221,12 +247,14 @@ public class ApkPatcher {
|
|||
|
||||
/**
|
||||
* 重新签名安装包
|
||||
*
|
||||
* @param apkPath APK文件路径
|
||||
* @return 签名后的安装包路径
|
||||
*/
|
||||
public String sign(String apkPath) {
|
||||
try {
|
||||
File externalFilesDir = Environment.getExternalStorageDirectory();
|
||||
emitProgress(47);
|
||||
if (externalFilesDir != null) {
|
||||
String signApkPath = externalFilesDir.getAbsolutePath() + "/SMAPI Installer/base_signed.apk";
|
||||
KeyStore ks = new KeyStoreFileManager.JksKeyStore();
|
||||
|
@ -237,16 +265,36 @@ public class ApkPatcher {
|
|||
X509Certificate publicKey = (X509Certificate) ks.getCertificate(alias);
|
||||
PrivateKey privateKey = (PrivateKey) ks.getKey(alias, "android".toCharArray());
|
||||
ApkSigner.SignerConfig signerConfig = new ApkSigner.SignerConfig.Builder("debug", privateKey, Collections.singletonList(publicKey)).build();
|
||||
ZipAligner.alignZip(apkPath, signApkPath);
|
||||
new File(apkPath).delete();
|
||||
FileUtils.moveFile(new File(signApkPath), new File(apkPath));
|
||||
emitProgress(49);
|
||||
ApkSigner signer = new ApkSigner.Builder(Collections.singletonList(signerConfig))
|
||||
.setInputApk(new File(apkPath))
|
||||
.setOutputApk(new File(signApkPath))
|
||||
.setV1SigningEnabled(true)
|
||||
.setV2SigningEnabled(true).build();
|
||||
long zipOpElapsed = stopwatch.elapsed(TimeUnit.MILLISECONDS);
|
||||
stopwatch.reset();
|
||||
Thread thread = new Thread(() -> {
|
||||
stopwatch.start();
|
||||
while (true){
|
||||
try {
|
||||
Thread.sleep(20);
|
||||
long elapsed = stopwatch.elapsed(TimeUnit.MILLISECONDS);
|
||||
double progress = elapsed * 0.98 / zipOpElapsed;
|
||||
if(progress < 1.0) {
|
||||
emitProgress((int) (49 + 40 * progress));
|
||||
}
|
||||
} catch (InterruptedException ignored) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
signer.sign();
|
||||
new File(apkPath).delete();
|
||||
if(thread.isAlive() && !thread.isInterrupted()) {
|
||||
thread.interrupt();
|
||||
}
|
||||
FileUtils.forceDelete(new File(apkPath));
|
||||
emitProgress(90);
|
||||
return signApkPath;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
@ -258,6 +306,7 @@ public class ApkPatcher {
|
|||
|
||||
/**
|
||||
* 对指定安装包发起安装
|
||||
*
|
||||
* @param apkPath 安装包路径
|
||||
*/
|
||||
public void install(String apkPath) {
|
||||
|
@ -290,6 +339,7 @@ public class ApkPatcher {
|
|||
|
||||
/**
|
||||
* 获取报错内容
|
||||
*
|
||||
* @return 报错内容
|
||||
*/
|
||||
public AtomicReference<String> getErrorMessage() {
|
||||
|
@ -299,4 +349,14 @@ public class ApkPatcher {
|
|||
public AtomicInteger getSwitchAction() {
|
||||
return switchAction;
|
||||
}
|
||||
|
||||
private void emitProgress(int progress) {
|
||||
for (Consumer<Integer> consumer : progressListener) {
|
||||
consumer.accept(progress);
|
||||
}
|
||||
}
|
||||
|
||||
public void registerProgressListener(Consumer<Integer> listener) {
|
||||
progressListener.add(listener);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ import com.zane.smapiinstaller.utils.DialogUtils;
|
|||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.navigation.NavController;
|
||||
|
@ -74,21 +76,24 @@ public class InstallFragment extends Fragment {
|
|||
task = new Thread(() -> {
|
||||
try {
|
||||
ApkPatcher patcher = new ApkPatcher(context);
|
||||
DialogUtils.setProgressDialogState(root, dialog, R.string.extracting_package, 0);
|
||||
patcher.registerProgressListener((progress)->{
|
||||
DialogUtils.setProgressDialogState(root, dialog, null, progress);
|
||||
});
|
||||
DialogUtils.setProgressDialogState(root, dialog, R.string.extracting_package, null);
|
||||
String path = patcher.extract();
|
||||
if (path == null) {
|
||||
DialogUtils.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.error_game_not_found)));
|
||||
return;
|
||||
}
|
||||
DialogUtils.setProgressDialogState(root, dialog, R.string.unpacking_smapi_files, 10);
|
||||
DialogUtils.setProgressDialogState(root, dialog, R.string.unpacking_smapi_files, null);
|
||||
if (!CommonLogic.unpackSmapiFiles(context, path, false)) {
|
||||
DialogUtils.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_unpack_smapi_files)));
|
||||
return;
|
||||
}
|
||||
ModAssetsManager modAssetsManager = new ModAssetsManager(root);
|
||||
DialogUtils.setProgressDialogState(root, dialog, R.string.unpacking_smapi_files, 15);
|
||||
DialogUtils.setProgressDialogState(root, dialog, R.string.unpacking_smapi_files, 6);
|
||||
modAssetsManager.installDefaultMods();
|
||||
DialogUtils.setProgressDialogState(root, dialog, R.string.patching_package, 25);
|
||||
DialogUtils.setProgressDialogState(root, dialog, R.string.patching_package, 8);
|
||||
if (!patcher.patch(path)) {
|
||||
int target = patcher.getSwitchAction().getAndSet(0);
|
||||
if(target == R.string.menu_download) {
|
||||
|
@ -104,15 +109,14 @@ public class InstallFragment extends Fragment {
|
|||
}
|
||||
return;
|
||||
}
|
||||
DialogUtils.setProgressDialogState(root, dialog, R.string.signing_package, 55);
|
||||
DialogUtils.setProgressDialogState(root, dialog, R.string.signing_package, null);
|
||||
String signPath = patcher.sign(path);
|
||||
if (signPath == null) {
|
||||
DialogUtils.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_sign_game)));
|
||||
return;
|
||||
}
|
||||
DialogUtils.setProgressDialogState(root, dialog, R.string.installing_package, 99);
|
||||
DialogUtils.setProgressDialogState(root, dialog, R.string.installing_package, null);
|
||||
patcher.install(signPath);
|
||||
dialog.incrementProgress(1);
|
||||
}
|
||||
catch (Exception e) {
|
||||
Crashes.trackError(e);
|
||||
|
|
|
@ -28,12 +28,16 @@ public class DialogUtils {
|
|||
* @param message 消息
|
||||
* @param progress 进度
|
||||
*/
|
||||
public static void setProgressDialogState(View view, MaterialDialog dialog, int message, int progress) {
|
||||
public static void setProgressDialogState(View view, MaterialDialog dialog, Integer message, Integer progress) {
|
||||
Activity activity = CommonLogic.getActivityFromView(view);
|
||||
if (activity != null && !activity.isFinishing() && !dialog.isCancelled()) {
|
||||
activity.runOnUiThread(() -> {
|
||||
dialog.setProgress(progress);
|
||||
dialog.setContent(message);
|
||||
if(progress != null) {
|
||||
dialog.setProgress(progress);
|
||||
}
|
||||
if(message != null) {
|
||||
dialog.setContent(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
package com.zane.smapiinstaller.utils;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import net.fornwall.apksigner.zipio.ZioEntry;
|
||||
import net.fornwall.apksigner.zipio.ZipInput;
|
||||
import net.fornwall.apksigner.zipio.ZipOutput;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java9.util.function.Consumer;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
public class ZipUtils {
|
||||
|
||||
public static void addOrReplaceEntries(String inputZipFilename, List<ZipEntrySource> entrySources, String outputZipFilename, Consumer<Integer> progressCallback) throws IOException {
|
||||
File inFile = new File(inputZipFilename).getCanonicalFile();
|
||||
File outFile = new File(outputZipFilename).getCanonicalFile();
|
||||
if (inFile.equals(outFile)) {
|
||||
throw new IllegalArgumentException("Input and output files are the same");
|
||||
}
|
||||
ImmutableMap<String, ZipEntrySource> entryMap = Maps.uniqueIndex(entrySources, ZipEntrySource::getPath);
|
||||
try (ZipInput input = new ZipInput(inputZipFilename)) {
|
||||
int size = input.entries.values().size();
|
||||
int index = 0;
|
||||
int reportInterval = size / 100;
|
||||
try (ZipOutput zipOutput = new ZipOutput(new FileOutputStream(outputZipFilename))) {
|
||||
HashSet<String> replacedFileSet = new HashSet<>(entryMap.size());
|
||||
for (ZioEntry inEntry : input.entries.values()) {
|
||||
if (entryMap.containsKey(inEntry.getName())) {
|
||||
ZipEntrySource source = entryMap.get(inEntry.getName());
|
||||
ZioEntry zioEntry = new ZioEntry(inEntry.getName());
|
||||
zioEntry.setCompression(source.getCompressionMethod());
|
||||
zioEntry.getOutputStream().write(source.getData());
|
||||
zipOutput.write(zioEntry);
|
||||
replacedFileSet.add(inEntry.getName());
|
||||
} else {
|
||||
zipOutput.write(inEntry);
|
||||
}
|
||||
index++;
|
||||
if(index % reportInterval == 0) {
|
||||
progressCallback.accept((int) (index * 95.0 / size));
|
||||
}
|
||||
}
|
||||
Sets.SetView<String> difference = Sets.difference(entryMap.keySet(), replacedFileSet);
|
||||
index = 0;
|
||||
for (String name : difference) {
|
||||
ZipEntrySource source = entryMap.get(name);
|
||||
ZioEntry zioEntry = new ZioEntry(name);
|
||||
zioEntry.setCompression(source.getCompressionMethod());
|
||||
zioEntry.getOutputStream().write(source.getData());
|
||||
zipOutput.write(zioEntry);
|
||||
progressCallback.accept(95 + (int)(index * 5.0 / difference.size()));
|
||||
}
|
||||
progressCallback.accept(100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public static class ZipEntrySource {
|
||||
private String path;
|
||||
private byte[] data;
|
||||
private int compressionMethod;
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
package net.fornwall.apksigner;
|
||||
|
||||
import net.fornwall.apksigner.zipio.ZioEntry;
|
||||
import net.fornwall.apksigner.zipio.ZipInput;
|
||||
import net.fornwall.apksigner.zipio.ZipOutput;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
public class ZipAligner {
|
||||
public static void alignZip(String inputZipFilename, String outputZipFilename) throws IOException, GeneralSecurityException {
|
||||
File inFile = new File(inputZipFilename).getCanonicalFile();
|
||||
File outFile = new File(outputZipFilename).getCanonicalFile();
|
||||
if (inFile.equals(outFile)) {
|
||||
throw new IllegalArgumentException("Input and output files are the same");
|
||||
}
|
||||
|
||||
try (ZipInput input = new ZipInput(inputZipFilename)) {
|
||||
try (ZipOutput zipOutput = new ZipOutput(new FileOutputStream(outputZipFilename))) {
|
||||
for (ZioEntry inEntry : input.entries.values()) {
|
||||
zipOutput.write(inEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue