1.Privacy Policy
2.Layout adjust 3.Amazon Store version compat 4.Thai translation 5.Advanced patch mode
This commit is contained in:
parent
263649cb0b
commit
a9af520e0d
|
@ -12,8 +12,8 @@ android {
|
|||
applicationId "com.zane.smapiinstaller"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 28
|
||||
versionCode 39
|
||||
versionName "1.4.8"
|
||||
versionCode 40
|
||||
versionName "1.5.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
multiDexEnabled true
|
||||
|
@ -26,8 +26,8 @@ android {
|
|||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
debug {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
minifyEnabled false
|
||||
shrinkResources false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
@ -103,4 +103,6 @@ dependencies {
|
|||
|
||||
compileOnly 'org.projectlombok:lombok:1.18.12'
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.12'
|
||||
|
||||
api 'com.smart.library.util:bspatch:0.0.2'
|
||||
}
|
||||
|
|
|
@ -135,7 +135,8 @@
|
|||
-dontwarn okio.**
|
||||
-keep class okio.**{*;}
|
||||
|
||||
-keep class com.zane.** { *; }
|
||||
-keep class com.zane.smapiinstaller.entity.** { *; }
|
||||
-keep class com.zane.smapiinstaller.dto.** { *; }
|
||||
-keep class pxb.android.** { *; }
|
||||
-keep class net.fornwall.apksigner.** { *; }
|
||||
-keep class com.android.apksig.** { *; }
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -10,9 +10,10 @@
|
|||
"manifestEntries": [
|
||||
{
|
||||
"targetPath": "classes.dex",
|
||||
"assetPath": "apk/classes.dex",
|
||||
"assetPath": "apk/classes.dex.patch",
|
||||
"compression": 8,
|
||||
"external": false
|
||||
"external": false,
|
||||
"patchCrc": "e918baf8"
|
||||
},
|
||||
{
|
||||
"targetPath": "res/mipmap-mdpi-v4/ic_launcher_foreground.png",
|
||||
|
@ -79,6 +80,14 @@
|
|||
"assetPath": "apk/System.Numerics.dll",
|
||||
"compression": 0,
|
||||
"external": false
|
||||
},
|
||||
{
|
||||
"targetPath": "assemblies/MonoGame.Framework.dll",
|
||||
"assetPath": "apk/MonoGame.Framework.dll.patch",
|
||||
"compression": 0,
|
||||
"external": false,
|
||||
"patchCrc": "c7fec998",
|
||||
"advanced": true
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": 17,
|
||||
"version": 18,
|
||||
"contents": [
|
||||
{
|
||||
"type": "COMPAT",
|
||||
|
@ -14,16 +14,16 @@
|
|||
"name": "SMAPI for Galaxy Store",
|
||||
"assetPath": "compat/samsung_138/",
|
||||
"description": "SMAPI compat package for game 1.4.4.138 - latest, SMAPI 3.5.0",
|
||||
"url": "http://zaneyork.cn/download/compat/smapi_samsung_138_11.zip",
|
||||
"hash": "4dce4537952d868d40ec18dd0f0b312259cd581f6a2ffdaa5a5e3519148f63d9"
|
||||
"url": "http://zaneyork.cn/download/compat/smapi_samsung_138_12.zip",
|
||||
"hash": "5e7d9c52fa5decbc7a319fce8a7f1730aa875867cdeaa339a7c95f22a96d0185"
|
||||
},
|
||||
{
|
||||
"type": "COMPAT",
|
||||
"name": "SMAPI for Amazon Store",
|
||||
"assetPath": "compat/amazon_138/",
|
||||
"description": "SMAPI compat package for game 1.4.4.138 - latest, SMAPI 3.5.0",
|
||||
"url": "http://zaneyork.cn/download/compat/smapi_amazon_138_1.zip",
|
||||
"hash": "8feac89c4b722a38408d40503cc93f8e425e06cdd27b564c03af6aeb07a384e6"
|
||||
"url": "http://zaneyork.cn/download/compat/smapi_amazon_138_2.zip",
|
||||
"hash": "919f4c900dcc2792b25428bc40bebb364cab6fff209d02a2ac03afc265589be8"
|
||||
},
|
||||
{
|
||||
"type": "LOCALE",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": 17,
|
||||
"version": 18,
|
||||
"contents": [
|
||||
{
|
||||
"type": "COMPAT",
|
||||
|
@ -14,16 +14,16 @@
|
|||
"name": "SMAPI for Galaxy Store",
|
||||
"assetPath": "compat/samsung_138/",
|
||||
"description": "SMAPI compat package for game 1.4.4.138 - latest, SMAPI 3.5.0",
|
||||
"url": "http://zaneyork.cn/download/compat/smapi_samsung_138_11.zip",
|
||||
"hash": "4dce4537952d868d40ec18dd0f0b312259cd581f6a2ffdaa5a5e3519148f63d9"
|
||||
"url": "http://zaneyork.cn/download/compat/smapi_samsung_138_12.zip",
|
||||
"hash": "5e7d9c52fa5decbc7a319fce8a7f1730aa875867cdeaa339a7c95f22a96d0185"
|
||||
},
|
||||
{
|
||||
"type": "COMPAT",
|
||||
"name": "SMAPI for Amazon Store",
|
||||
"assetPath": "compat/amazon_138/",
|
||||
"description": "SMAPI compat package for game 1.4.4.138 - latest, SMAPI 3.5.0",
|
||||
"url": "http://zaneyork.cn/download/compat/smapi_amazon_138_1.zip",
|
||||
"hash": "8feac89c4b722a38408d40503cc93f8e425e06cdd27b564c03af6aeb07a384e6"
|
||||
"url": "http://zaneyork.cn/download/compat/smapi_amazon_138_2.zip",
|
||||
"hash": "919f4c900dcc2792b25428bc40bebb364cab6fff209d02a2ac03afc265589be8"
|
||||
},
|
||||
{
|
||||
"type": "LOCALE",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": 17,
|
||||
"version": 18,
|
||||
"contents": [
|
||||
{
|
||||
"type": "COMPAT",
|
||||
|
@ -14,16 +14,16 @@
|
|||
"name": "SMAPI三星商店兼容包",
|
||||
"assetPath": "compat/samsung_138/",
|
||||
"description": "SMAPI三星商店兼容包, 适用版本1.4.4.138至今, SMAPI 3.5.0",
|
||||
"url": "http://zaneyork.cn/download/compat/smapi_samsung_138_11.zip",
|
||||
"hash": "4dce4537952d868d40ec18dd0f0b312259cd581f6a2ffdaa5a5e3519148f63d9"
|
||||
"url": "http://zaneyork.cn/download/compat/smapi_samsung_138_12.zip",
|
||||
"hash": "5e7d9c52fa5decbc7a319fce8a7f1730aa875867cdeaa339a7c95f22a96d0185"
|
||||
},
|
||||
{
|
||||
"type": "COMPAT",
|
||||
"name": "SMAPI亚马逊商店兼容包",
|
||||
"assetPath": "compat/amazon_138/",
|
||||
"description": "SMAPI亚马逊商店兼容包, 适用版本1.4.4.138至今, SMAPI 3.5.0",
|
||||
"url": "http://zaneyork.cn/download/compat/smapi_amazon_138_1.zip",
|
||||
"hash": "8feac89c4b722a38408d40503cc93f8e425e06cdd27b564c03af6aeb07a384e6"
|
||||
"url": "http://zaneyork.cn/download/compat/smapi_amazon_138_2.zip",
|
||||
"hash": "919f4c900dcc2792b25428bc40bebb364cab6fff209d02a2ac03afc265589be8"
|
||||
},
|
||||
{
|
||||
"type": "LOCALE",
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -27,11 +27,6 @@ The default values are mirrored in StardewModdingAPI.Framework.Models.SConfig to
|
|||
*/
|
||||
"DeveloperMode": false,
|
||||
|
||||
/**
|
||||
* Whether to enable load mods with multithreading supports.
|
||||
*/
|
||||
"MultithreadingLoading": false,
|
||||
|
||||
/**
|
||||
* Whether to add a section to the 'mod issues' list for mods which directly use potentially
|
||||
* sensitive .NET APIs like file or shell access. Note that many mods do this legitimately as
|
||||
|
|
|
@ -194,6 +194,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
}
|
||||
menu.findItem(R.id.settings_developer_mode).setChecked(config.isDeveloperMode());
|
||||
menu.findItem(R.id.settings_disable_mono_mod).setChecked(config.isDisableMonoMod());
|
||||
menu.findItem(R.id.settings_advanced_mode).setChecked(Boolean.parseBoolean(ConfigUtils.getConfig((MainApplication) getApplication(), AppConfigKey.ADVANCED_MODE, "false").getValue()));
|
||||
Constants.MOD_PATH = config.getModsPath();
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
|
@ -262,6 +263,14 @@ public class MainActivity extends AppCompatActivity {
|
|||
case R.id.toolbar_update_check:
|
||||
checkModUpdateLogic();
|
||||
return true;
|
||||
case R.id.settings_advanced_mode:
|
||||
AppConfig appConfig = ConfigUtils.getConfig((MainApplication) getApplication(), AppConfigKey.ADVANCED_MODE, "false");
|
||||
appConfig.setValue(String.valueOf(item.isChecked()));
|
||||
ConfigUtils.saveConfig((MainApplication) getApplication(), appConfig);
|
||||
startActivity(new Intent(this, MainActivity.class));
|
||||
overridePendingTransition(R.anim.fragment_fade_enter, R.anim.fragment_fade_exit);
|
||||
finish();
|
||||
break;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
|
|
@ -11,4 +11,6 @@ public class AppConfigKey {
|
|||
public static final String IGNORE_UPDATE_VERSION_CODE = "UpdateIgnoreVersionCode";
|
||||
|
||||
public static final String PRIVACY_POLICY_CONFIRM = "PrivacyPolicyConfirm";
|
||||
|
||||
public static final String ADVANCED_MODE = "AdvancedMode";
|
||||
}
|
||||
|
|
|
@ -2,6 +2,9 @@ package com.zane.smapiinstaller.dto;
|
|||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author Zane
|
||||
*/
|
||||
@Data
|
||||
public class AppUpdateCheckResultDto {
|
||||
/**
|
||||
|
|
|
@ -28,4 +28,14 @@ public class ManifestEntry {
|
|||
* 文件是否不属于兼容包中
|
||||
*/
|
||||
private boolean external;
|
||||
|
||||
/**
|
||||
* 补丁CRC
|
||||
*/
|
||||
private String patchCrc;
|
||||
|
||||
/**
|
||||
* 是否为高级模式补丁
|
||||
*/
|
||||
private boolean advanced;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ 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.hash.Hashing;
|
||||
import com.google.common.io.Files;
|
||||
import com.zane.smapiinstaller.BuildConfig;
|
||||
import com.zane.smapiinstaller.R;
|
||||
|
@ -44,13 +45,12 @@ 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.Objects;
|
||||
import java9.util.function.Consumer;
|
||||
import java9.util.stream.Collectors;
|
||||
import java9.util.stream.StreamSupport;
|
||||
import pxb.android.axml.NodeVisitor;
|
||||
|
||||
|
@ -83,6 +83,10 @@ public class ApkPatcher {
|
|||
* @return 抽取后的APK文件路径,如果抽取失败返回null
|
||||
*/
|
||||
public String extract() {
|
||||
return extract(-1);
|
||||
}
|
||||
|
||||
public String extract(int advancedStage) {
|
||||
emitProgress(0);
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
List<String> packageNames = FileUtils.getAssetJson(context, "package_names.json", new TypeReference<List<String>>() {
|
||||
|
@ -108,12 +112,31 @@ public class ApkPatcher {
|
|||
}
|
||||
}
|
||||
File distFile = new File(dest, apkFile.getName());
|
||||
if (advancedStage == 0) {
|
||||
AtomicInteger count = new AtomicInteger();
|
||||
ZipUtil.unpack(apkFile, new File(externalFilesDir.getAbsolutePath() + "/StardewValley/"), name -> {
|
||||
if (name.startsWith("assets/")) {
|
||||
int progress = count.incrementAndGet();
|
||||
if (progress % 30 == 0) {
|
||||
emitProgress(progress / 30);
|
||||
}
|
||||
return name.replaceFirst("assets/", "");
|
||||
}
|
||||
return null;
|
||||
});
|
||||
} else if (advancedStage == 1) {
|
||||
ZipUtils.removeEntries(sourceDir, "assets/Content", distFile.getAbsolutePath(), (progress) -> emitProgress((int) (progress * 0.05)));
|
||||
} else {
|
||||
Files.copy(apkFile, distFile);
|
||||
}
|
||||
emitProgress(5);
|
||||
return distFile.getAbsolutePath();
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException | IOException e) {
|
||||
} catch (PackageManager.NameNotFoundException ignored) {
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Extract error", e);
|
||||
errorMessage.set(e.getLocalizedMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
errorMessage.set(context.getString(R.string.error_game_not_found));
|
||||
|
@ -127,6 +150,10 @@ public class ApkPatcher {
|
|||
* @return 是否成功打包
|
||||
*/
|
||||
public boolean patch(String apkPath) {
|
||||
return patch(apkPath, false);
|
||||
}
|
||||
|
||||
public boolean patch(String apkPath, boolean advanced) {
|
||||
if (apkPath == null) {
|
||||
return false;
|
||||
}
|
||||
|
@ -150,13 +177,37 @@ public class ApkPatcher {
|
|||
}
|
||||
ApkFilesManifest apkFilesManifest = apkFilesManifests.get(0);
|
||||
List<ManifestEntry> manifestEntries = apkFilesManifest.getManifestEntries();
|
||||
errorMessage.set(null);
|
||||
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());
|
||||
if(entry.isAdvanced() && !advanced) {
|
||||
return null;
|
||||
}
|
||||
byte[] bytes;
|
||||
if (entry.isExternal()) {
|
||||
bytes = FileUtils.getAssetBytes(context, apkFilesManifest.getBasePath() + entry.getAssetPath());
|
||||
} else {
|
||||
bytes = FileUtils.getAssetBytes(context, entry.getAssetPath());
|
||||
}
|
||||
if (StringUtils.isNoneBlank(entry.getPatchCrc())) {
|
||||
byte[] originBytes = ZipUtil.unpackEntry(file, entry.getTargetPath());
|
||||
if (originBytes != null) {
|
||||
String crc = Integer.toHexString(Hashing.crc32().hashBytes(originBytes).hashCode());
|
||||
if (StringUtils.equals(crc, entry.getPatchCrc())) {
|
||||
bytes = FileUtils.patchFile(originBytes, bytes);
|
||||
if (bytes == null) {
|
||||
errorMessage.set("Patch failed");
|
||||
}
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new ZipUtils.ZipEntrySource(entry.getTargetPath(), bytes, entry.getCompression());
|
||||
}).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
if (errorMessage.get() != null) {
|
||||
return false;
|
||||
}
|
||||
}).collect(Collectors.toList());
|
||||
entries.add(new ZipUtils.ZipEntrySource("AndroidManifest.xml", modifiedManifest, Deflater.DEFLATED));
|
||||
emitProgress(10);
|
||||
String patchedFilename = apkPath + ".patched";
|
||||
|
@ -191,7 +242,7 @@ public class ApkPatcher {
|
|||
AtomicReference<String> versionName = new AtomicReference<>();
|
||||
AtomicLong versionCode = new AtomicLong();
|
||||
Predicate<ManifestTagVisitor.AttrArgs> processLogic = (attr) -> {
|
||||
if(attr == null) {
|
||||
if (attr == null) {
|
||||
return true;
|
||||
}
|
||||
if (attr.type == NodeVisitor.TYPE_STRING) {
|
||||
|
@ -236,11 +287,11 @@ public class ApkPatcher {
|
|||
};
|
||||
try {
|
||||
byte[] modifyManifest = CommonLogic.modifyManifest(bytes, processLogic);
|
||||
if(StringUtils.endsWith(versionName.get(), ManifestPatchConstants.PATTERN_VERSION_AMAZON)) {
|
||||
if (StringUtils.endsWith(versionName.get(), ManifestPatchConstants.PATTERN_VERSION_AMAZON)) {
|
||||
packageName.set(ManifestPatchConstants.APP_PACKAGE_NAME + ManifestPatchConstants.PATTERN_VERSION_AMAZON);
|
||||
}
|
||||
Iterables.removeIf(manifests, manifest -> {
|
||||
if(manifest == null) {
|
||||
if (manifest == null) {
|
||||
return true;
|
||||
}
|
||||
if (versionCode.get() < manifest.getMinBuildCode()) {
|
||||
|
@ -314,7 +365,7 @@ public class ApkPatcher {
|
|||
if (thread.isAlive() && !thread.isInterrupted()) {
|
||||
thread.interrupt();
|
||||
}
|
||||
if(result.containsErrors()) {
|
||||
if (result.containsErrors()) {
|
||||
errorMessage.set(StreamSupport.stream(result.getErrors()).map(ApkVerifier.IssueWithParams::toString).collect(Collectors.joining(",")));
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ import com.fasterxml.jackson.core.type.TypeReference;
|
|||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.lmntrx.android.library.livin.missme.ProgressDialog;
|
||||
import com.microsoft.appcenter.crashes.Crashes;
|
||||
import com.zane.smapiinstaller.MainApplication;
|
||||
import com.zane.smapiinstaller.R;
|
||||
import com.zane.smapiinstaller.constant.DialogAction;
|
||||
|
@ -40,6 +42,7 @@ import java.io.InputStream;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import java9.util.function.BiConsumer;
|
||||
import java9.util.function.Consumer;
|
||||
|
@ -305,12 +308,12 @@ public class CommonLogic {
|
|||
CommonLogic.doOnNonNull(activity, (context) -> {
|
||||
try {
|
||||
Intent intent = new Intent("android.intent.action.VIEW");
|
||||
intent.setData(Uri.parse("market://details?id=" + context.getPackageName()));
|
||||
intent.setData(Uri.parse("market://details?id=com.zane.smapiinstaller"));
|
||||
intent.setPackage("com.android.vending");
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
} catch (Exception ex) {
|
||||
CommonLogic.openUrl(activity, "https://play.google.com/store/apps/details?id=" + context.getPackageName());
|
||||
CommonLogic.openUrl(activity, "https://play.google.com/store/apps/details?id=com.zane.smapiinstaller");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -320,4 +323,30 @@ public class CommonLogic {
|
|||
String policy = FileUtils.getLocaledAssetText(context, "privacy_policy.txt");
|
||||
DialogUtils.showConfirmDialog(view, R.string.privacy_policy, policy, R.string.confirm, R.string.cancel, true, callback);
|
||||
}
|
||||
|
||||
public static void showProgressDialog(View root, Context context, Consumer<ProgressDialog> dialogConsumer) {
|
||||
AtomicReference<ProgressDialog> dialogHolder = DialogUtils.showProgressDialog(root, R.string.install_progress_title, context.getString(R.string.extracting_package));
|
||||
ProgressDialog dialog = null;
|
||||
try {
|
||||
do {
|
||||
Thread.sleep(10);
|
||||
dialog = dialogHolder.get();
|
||||
} while (dialog == null);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
ConfigManager configManager = new ConfigManager();
|
||||
if (configManager.getConfig().isInitial()) {
|
||||
configManager.getConfig().setInitial(false);
|
||||
configManager.getConfig().setDisableMonoMod(true);
|
||||
configManager.flushConfig();
|
||||
}
|
||||
}
|
||||
dialogConsumer.accept(dialog);
|
||||
} catch (InterruptedException ignored) {
|
||||
} catch (Exception e) {
|
||||
Crashes.trackError(e);
|
||||
DialogUtils.showAlertDialog(root, R.string.error, e.getLocalizedMessage());
|
||||
} finally {
|
||||
DialogUtils.dismissDialog(root, dialog);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ import java9.util.stream.StreamSupport;
|
|||
|
||||
/**
|
||||
* Mod资源管理器
|
||||
* @author Zane
|
||||
*/
|
||||
public class ModAssetsManager {
|
||||
|
||||
|
|
|
@ -4,20 +4,16 @@ import android.content.Context;
|
|||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.didikee.donate.AlipayDonate;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.microsoft.appcenter.crashes.Crashes;
|
||||
import com.zane.smapiinstaller.R;
|
||||
import com.zane.smapiinstaller.constant.Constants;
|
||||
import com.zane.smapiinstaller.constant.DialogAction;
|
||||
import com.zane.smapiinstaller.logic.CommonLogic;
|
||||
import com.zane.smapiinstaller.utils.DialogUtils;
|
||||
|
||||
|
@ -25,7 +21,6 @@ import androidx.fragment.app.Fragment;
|
|||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
import java9.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* @author Zane
|
||||
|
|
|
@ -8,7 +8,6 @@ import com.hjq.language.LanguagesManager;
|
|||
import com.zane.smapiinstaller.MainApplication;
|
||||
import com.zane.smapiinstaller.constant.AppConfigKey;
|
||||
import com.zane.smapiinstaller.entity.AppConfig;
|
||||
import com.zane.smapiinstaller.entity.AppConfigDao;
|
||||
import com.zane.smapiinstaller.entity.DaoSession;
|
||||
import com.zane.smapiinstaller.entity.ModManifestEntry;
|
||||
import com.zane.smapiinstaller.entity.TranslationResult;
|
||||
|
|
|
@ -93,16 +93,23 @@ public class DownloadableContentAdapter extends RecyclerView.Adapter<Downloadabl
|
|||
typeTextView.setText(downloadableContent.getType());
|
||||
nameTextView.setText(downloadableContent.getName());
|
||||
descriptionTextView.setText(downloadableContent.getDescription());
|
||||
if (StringUtils.isBlank(downloadableContent.getAssetPath())) {
|
||||
buttonRemove.setVisibility(View.INVISIBLE);
|
||||
buttonDownload.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
if (StringUtils.isNoneBlank(downloadableContent.getAssetPath())) {
|
||||
File contentFile = new File(itemView.getContext().getFilesDir(), downloadableContent.getAssetPath());
|
||||
if (contentFile.exists()) {
|
||||
Context context = itemView.getContext();
|
||||
File file = new File(context.getCacheDir(), downloadableContent.getName() + ".zip");
|
||||
if (!file.exists() || !StringUtils.equalsIgnoreCase(FileUtils.getFileHash(file), downloadableContent.getHash())) {
|
||||
buttonRemove.setVisibility(View.VISIBLE);
|
||||
buttonDownload.setVisibility(View.VISIBLE);
|
||||
return;
|
||||
}
|
||||
buttonRemove.setVisibility(View.VISIBLE);
|
||||
buttonDownload.setVisibility(View.INVISIBLE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
buttonRemove.setVisibility(View.INVISIBLE);
|
||||
buttonDownload.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public ViewHolder(View view) {
|
||||
|
@ -119,6 +126,8 @@ public class DownloadableContentAdapter extends RecyclerView.Adapter<Downloadabl
|
|||
if (which == DialogAction.POSITIVE) {
|
||||
try {
|
||||
FileUtils.forceDelete(contentFile);
|
||||
buttonDownload.setVisibility(View.VISIBLE);
|
||||
buttonRemove.setVisibility(View.INVISIBLE);
|
||||
} catch (IOException e) {
|
||||
DialogUtils.showAlertDialog(itemView, R.string.error, e.getLocalizedMessage());
|
||||
}
|
||||
|
@ -203,6 +212,8 @@ public class DownloadableContentAdapter extends RecyclerView.Adapter<Downloadabl
|
|||
ZipUtil.unpack(downloadedFile, new File(context.getFilesDir(), downloadableContent.getAssetPath()));
|
||||
}
|
||||
DialogUtils.showAlertDialog(itemView, R.string.info, R.string.download_unpack_success);
|
||||
buttonDownload.setVisibility(View.INVISIBLE);
|
||||
buttonRemove.setVisibility(View.VISIBLE);
|
||||
} catch (Exception e) {
|
||||
DialogUtils.showAlertDialog(itemView, R.string.error, e.getLocalizedMessage());
|
||||
}
|
||||
|
|
|
@ -8,18 +8,19 @@ import android.view.LayoutInflater;
|
|||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
import com.lmntrx.android.library.livin.missme.ProgressDialog;
|
||||
import com.microsoft.appcenter.crashes.Crashes;
|
||||
import com.zane.smapiinstaller.MainApplication;
|
||||
import com.zane.smapiinstaller.R;
|
||||
import com.zane.smapiinstaller.constant.AppConfigKey;
|
||||
import com.zane.smapiinstaller.constant.Constants;
|
||||
import com.zane.smapiinstaller.constant.DialogAction;
|
||||
import com.zane.smapiinstaller.logic.ApkPatcher;
|
||||
import com.zane.smapiinstaller.logic.CommonLogic;
|
||||
import com.zane.smapiinstaller.logic.ConfigManager;
|
||||
import com.zane.smapiinstaller.logic.ModAssetsManager;
|
||||
import com.zane.smapiinstaller.utils.ConfigUtils;
|
||||
import com.zane.smapiinstaller.utils.DialogUtils;
|
||||
|
||||
import org.apache.commons.lang3.RegExUtils;
|
||||
|
@ -28,7 +29,6 @@ import org.apache.commons.lang3.StringUtils;
|
|||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
@ -55,15 +55,22 @@ public class InstallFragment extends Fragment {
|
|||
@BindView(R.id.text_latest_running)
|
||||
TextView textLatestRunning;
|
||||
|
||||
@BindView(R.id.layout_adv_install)
|
||||
LinearLayout layoutAdvInstall;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
ViewGroup container, Bundle savedInstanceState) {
|
||||
root = inflater.inflate(R.layout.fragment_install, container, false);
|
||||
ButterKnife.bind(this, root);
|
||||
context = this.getActivity();
|
||||
if (Boolean.parseBoolean(ConfigUtils.getConfig((MainApplication) context.getApplication(), AppConfigKey.ADVANCED_MODE, "false").getValue())) {
|
||||
installButton.setVisibility(View.INVISIBLE);
|
||||
layoutAdvInstall.setVisibility(View.VISIBLE);
|
||||
}
|
||||
try {
|
||||
String firstLine = Files.asCharSource(new File(Environment.getExternalStorageDirectory(), Constants.LOG_PATH), StandardCharsets.UTF_8).readFirstLine();
|
||||
if(StringUtils.isNoneBlank(firstLine)) {
|
||||
if (StringUtils.isNoneBlank(firstLine)) {
|
||||
String versionString = RegExUtils.removePattern(firstLine, "\\[.+\\]\\s+");
|
||||
versionString = RegExUtils.removePattern(versionString, "\\s+with.+");
|
||||
textLatestRunning.setText(context.getString(R.string.smapi_version_runing, versionString));
|
||||
|
@ -87,32 +94,89 @@ public class InstallFragment extends Fragment {
|
|||
}
|
||||
}
|
||||
|
||||
@OnClick(R.id.button_adv_initial)
|
||||
void advInitial() {
|
||||
initialLogic();
|
||||
}
|
||||
|
||||
@OnClick(R.id.button_adv_install)
|
||||
void advInstall() {
|
||||
if (task != null) {
|
||||
task.interrupt();
|
||||
}
|
||||
task = new Thread(() -> CommonLogic.showProgressDialog(root, context, (dialog)->{
|
||||
ApkPatcher patcher = new ApkPatcher(context);
|
||||
patcher.registerProgressListener((progress) -> DialogUtils.setProgressDialogState(root, dialog, null, progress));
|
||||
DialogUtils.setProgressDialogState(root, dialog, R.string.extracting_package, null);
|
||||
String path = patcher.extract(1);
|
||||
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, 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, 6);
|
||||
modAssetsManager.installDefaultMods();
|
||||
DialogUtils.setProgressDialogState(root, dialog, R.string.patching_package, 8);
|
||||
if (!patcher.patch(path, true)) {
|
||||
int target = patcher.getSwitchAction().getAndSet(0);
|
||||
if (target == R.string.menu_download) {
|
||||
DialogUtils.showConfirmDialog(root, 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) -> {
|
||||
if (which == DialogAction.POSITIVE) {
|
||||
NavController controller = Navigation.findNavController(installButton);
|
||||
controller.navigate(InstallFragmentDirections.actionNavInstallToNavDownload());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
DialogUtils.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_patch_game)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
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, null);
|
||||
patcher.install(signPath);
|
||||
}));
|
||||
task.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化逻辑
|
||||
*/
|
||||
private void initialLogic() {
|
||||
if (task != null) {
|
||||
task.interrupt();
|
||||
}
|
||||
task = new Thread(() -> CommonLogic.showProgressDialog(root, context, (dialog)->{
|
||||
ApkPatcher patcher = new ApkPatcher(context);
|
||||
patcher.registerProgressListener((progress) -> DialogUtils.setProgressDialogState(root, dialog, null, progress));
|
||||
DialogUtils.setProgressDialogState(root, dialog, R.string.extracting_package, null);
|
||||
String path = patcher.extract(0);
|
||||
if (path == null) {
|
||||
DialogUtils.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.error_game_not_found)));
|
||||
}
|
||||
}));
|
||||
task.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装逻辑
|
||||
*/
|
||||
private void installLogic() {
|
||||
AtomicReference<ProgressDialog> dialogHolder = DialogUtils.showProgressDialog(root, R.string.install_progress_title, context.getString(R.string.extracting_package));
|
||||
if (task != null) {
|
||||
task.interrupt();
|
||||
}
|
||||
task = new Thread(() -> {
|
||||
ProgressDialog dialog = null;
|
||||
try {
|
||||
do {
|
||||
Thread.sleep(10);
|
||||
dialog = dialogHolder.get();
|
||||
} while (dialog == null);
|
||||
task = new Thread(() -> CommonLogic.showProgressDialog(root, context, (dialog)-> {
|
||||
ApkPatcher patcher = new ApkPatcher(context);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
ConfigManager configManager = new ConfigManager();
|
||||
if(configManager.getConfig().isInitial()) {
|
||||
configManager.getConfig().setInitial(false);
|
||||
configManager.getConfig().setDisableMonoMod(true);
|
||||
configManager.flushConfig();
|
||||
}
|
||||
}
|
||||
ProgressDialog finalDialog = dialog;
|
||||
patcher.registerProgressListener((progress) -> DialogUtils.setProgressDialogState(root, finalDialog, null, progress));
|
||||
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) {
|
||||
|
@ -150,14 +214,7 @@ public class InstallFragment extends Fragment {
|
|||
}
|
||||
DialogUtils.setProgressDialogState(root, dialog, R.string.installing_package, null);
|
||||
patcher.install(signPath);
|
||||
} catch (InterruptedException ignored) {
|
||||
} catch (Exception e) {
|
||||
Crashes.trackError(e);
|
||||
DialogUtils.showAlertDialog(root, R.string.error, e.getLocalizedMessage());
|
||||
} finally {
|
||||
DialogUtils.dismissDialog(root, dialog);
|
||||
}
|
||||
});
|
||||
}));
|
||||
task.start();
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ import com.afollestad.materialdialogs.MaterialDialog;
|
|||
import com.afollestad.materialdialogs.input.DialogInputExtKt;
|
||||
import com.afollestad.materialdialogs.list.DialogListExtKt;
|
||||
import com.afollestad.materialdialogs.list.DialogSingleChoiceExtKt;
|
||||
import com.afollestad.materialdialogs.message.DialogMessageSettings;
|
||||
import com.lmntrx.android.library.livin.missme.ProgressDialog;
|
||||
import com.microsoft.appcenter.crashes.Crashes;
|
||||
import com.zane.smapiinstaller.R;
|
||||
|
|
|
@ -8,7 +8,10 @@ import com.fasterxml.jackson.core.type.TypeReference;
|
|||
import com.google.common.hash.Hashing;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.io.CharStreams;
|
||||
import com.google.common.io.Files;
|
||||
import com.hjq.language.LanguagesManager;
|
||||
import com.microsoft.appcenter.crashes.Crashes;
|
||||
import com.smart.library.util.bspatch.BSPatchUtil;
|
||||
|
||||
import org.apache.commons.io.input.BOMInputStream;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -24,11 +27,13 @@ import java.nio.charset.StandardCharsets;
|
|||
|
||||
/**
|
||||
* 文件工具类
|
||||
*
|
||||
* @author Zane
|
||||
*/
|
||||
public class FileUtils extends org.zeroturnaround.zip.commons.FileUtils {
|
||||
/**
|
||||
* 读取文本文件
|
||||
*
|
||||
* @param file 文件
|
||||
* @return 文本
|
||||
*/
|
||||
|
@ -45,6 +50,7 @@ public class FileUtils extends org.zeroturnaround.zip.commons.FileUtils {
|
|||
|
||||
/**
|
||||
* 读取本地资源或Asset资源
|
||||
*
|
||||
* @param context context
|
||||
* @param filename 文件名
|
||||
* @return 输入流
|
||||
|
@ -60,6 +66,7 @@ public class FileUtils extends org.zeroturnaround.zip.commons.FileUtils {
|
|||
|
||||
/**
|
||||
* 尝试获取本地化后的资源文件
|
||||
*
|
||||
* @param context context
|
||||
* @param filename 文件名
|
||||
* @return 输入流
|
||||
|
@ -74,8 +81,7 @@ public class FileUtils extends org.zeroturnaround.zip.commons.FileUtils {
|
|||
return new BOMInputStream(new FileInputStream(file));
|
||||
}
|
||||
return context.getAssets().open(localedFilename);
|
||||
}
|
||||
catch (IOException e) {
|
||||
} catch (IOException e) {
|
||||
Log.d("LOCALE", "No locale asset found", e);
|
||||
}
|
||||
return getLocalAsset(context, filename);
|
||||
|
@ -83,6 +89,7 @@ public class FileUtils extends org.zeroturnaround.zip.commons.FileUtils {
|
|||
|
||||
/**
|
||||
* 读取JSON文件
|
||||
*
|
||||
* @param file 文件
|
||||
* @param type 数据类型
|
||||
* @param <T> 泛型类型
|
||||
|
@ -101,6 +108,7 @@ public class FileUtils extends org.zeroturnaround.zip.commons.FileUtils {
|
|||
|
||||
/**
|
||||
* 读取JSON文件
|
||||
*
|
||||
* @param file 文件
|
||||
* @param tClass 数据类型
|
||||
* @param <T> 泛型类型
|
||||
|
@ -119,6 +127,7 @@ public class FileUtils extends org.zeroturnaround.zip.commons.FileUtils {
|
|||
|
||||
/**
|
||||
* 写入JSON文件到本地
|
||||
*
|
||||
* @param context context
|
||||
* @param filename 文件名
|
||||
* @param content 内容
|
||||
|
@ -132,7 +141,7 @@ public class FileUtils extends org.zeroturnaround.zip.commons.FileUtils {
|
|||
writer.write(JsonUtil.toJson(content));
|
||||
} finally {
|
||||
File distFile = new File(context.getFilesDir(), filename);
|
||||
if(distFile.exists()) {
|
||||
if (distFile.exists()) {
|
||||
org.zeroturnaround.zip.commons.FileUtils.forceDelete(distFile);
|
||||
}
|
||||
org.zeroturnaround.zip.commons.FileUtils.moveFile(file, distFile);
|
||||
|
@ -143,12 +152,13 @@ public class FileUtils extends org.zeroturnaround.zip.commons.FileUtils {
|
|||
|
||||
/**
|
||||
* 写入JSON文件到本地
|
||||
*
|
||||
* @param file 文件
|
||||
* @param content 内容
|
||||
*/
|
||||
public static void writeFileJson(File file, Object content) {
|
||||
try {
|
||||
if(!file.getParentFile().exists()) {
|
||||
if (!file.getParentFile().exists()) {
|
||||
org.zeroturnaround.zip.commons.FileUtils.forceMkdir(file.getParentFile());
|
||||
}
|
||||
String filename = file.getName();
|
||||
|
@ -158,7 +168,7 @@ public class FileUtils extends org.zeroturnaround.zip.commons.FileUtils {
|
|||
try (OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)) {
|
||||
writer.write(JsonUtil.toJson(content));
|
||||
} finally {
|
||||
if(file.exists()) {
|
||||
if (file.exists()) {
|
||||
org.zeroturnaround.zip.commons.FileUtils.forceDelete(file);
|
||||
}
|
||||
org.zeroturnaround.zip.commons.FileUtils.moveFile(fileTmp, file);
|
||||
|
@ -169,6 +179,7 @@ public class FileUtils extends org.zeroturnaround.zip.commons.FileUtils {
|
|||
|
||||
/**
|
||||
* 读取资源文本
|
||||
*
|
||||
* @param context context
|
||||
* @param filename 文件名
|
||||
* @return 文本
|
||||
|
@ -186,6 +197,7 @@ public class FileUtils extends org.zeroturnaround.zip.commons.FileUtils {
|
|||
|
||||
/**
|
||||
* 读取本地化后的资源文本
|
||||
*
|
||||
* @param context context
|
||||
* @param filename 文件名
|
||||
* @return 文本
|
||||
|
@ -203,6 +215,7 @@ public class FileUtils extends org.zeroturnaround.zip.commons.FileUtils {
|
|||
|
||||
/**
|
||||
* 读取JSON资源
|
||||
*
|
||||
* @param context context
|
||||
* @param filename 资源名
|
||||
* @param tClass 数据类型
|
||||
|
@ -211,7 +224,7 @@ public class FileUtils extends org.zeroturnaround.zip.commons.FileUtils {
|
|||
*/
|
||||
public static <T> T getAssetJson(Context context, String filename, Class<T> tClass) {
|
||||
String text = getAssetText(context, filename);
|
||||
if(text != null){
|
||||
if (text != null) {
|
||||
return JsonUtil.fromJson(text, tClass);
|
||||
}
|
||||
return null;
|
||||
|
@ -230,6 +243,7 @@ public class FileUtils extends org.zeroturnaround.zip.commons.FileUtils {
|
|||
|
||||
/**
|
||||
* 读取JSON资源
|
||||
*
|
||||
* @param context context
|
||||
* @param filename 资源名
|
||||
* @param type 数据类型
|
||||
|
@ -238,7 +252,7 @@ public class FileUtils extends org.zeroturnaround.zip.commons.FileUtils {
|
|||
*/
|
||||
public static <T> T getAssetJson(Context context, String filename, TypeReference<T> type) {
|
||||
String text = getAssetText(context, filename);
|
||||
if(text != null){
|
||||
if (text != null) {
|
||||
return JsonUtil.fromJson(text, type);
|
||||
}
|
||||
return null;
|
||||
|
@ -246,6 +260,7 @@ public class FileUtils extends org.zeroturnaround.zip.commons.FileUtils {
|
|||
|
||||
/**
|
||||
* 读取资源为字节数组
|
||||
*
|
||||
* @param context context
|
||||
* @param filename 文件名
|
||||
* @return 字节数组
|
||||
|
@ -262,6 +277,7 @@ public class FileUtils extends org.zeroturnaround.zip.commons.FileUtils {
|
|||
|
||||
/**
|
||||
* 简化路径前缀
|
||||
*
|
||||
* @param path 文件路径
|
||||
* @return 移除前缀后的路径
|
||||
*/
|
||||
|
@ -271,6 +287,7 @@ public class FileUtils extends org.zeroturnaround.zip.commons.FileUtils {
|
|||
|
||||
/**
|
||||
* 计算资源文件SHA3-256
|
||||
*
|
||||
* @param context context
|
||||
* @param filename 资源名
|
||||
* @return SHA3-256值
|
||||
|
@ -285,6 +302,7 @@ public class FileUtils extends org.zeroturnaround.zip.commons.FileUtils {
|
|||
|
||||
/**
|
||||
* 计算文件SHA3-256
|
||||
*
|
||||
* @param file 文件
|
||||
* @return SHA3-256值
|
||||
*/
|
||||
|
@ -295,4 +313,27 @@ public class FileUtils extends org.zeroturnaround.zip.commons.FileUtils {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static byte[] patchFile(byte[] originBytes, byte[] patchBytes) {
|
||||
File patch = null;
|
||||
File origin = null;
|
||||
File patched = null;
|
||||
try {
|
||||
patch = File.createTempFile("patch", null);
|
||||
Files.write(patchBytes, patch);
|
||||
origin = File.createTempFile("origin", null);
|
||||
Files.write(originBytes, origin);
|
||||
patched = File.createTempFile("patched", null);
|
||||
if (BSPatchUtil.bspatch(origin.getAbsolutePath(), patched.getAbsolutePath(), patch.getAbsolutePath()) == 0) {
|
||||
return Files.asByteSource(patched).read();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Crashes.trackError(e);
|
||||
} finally {
|
||||
FileUtils.deleteQuietly(patch);
|
||||
FileUtils.deleteQuietly(origin);
|
||||
FileUtils.deleteQuietly(patched);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,6 +67,31 @@ public class ZipUtils {
|
|||
}
|
||||
}
|
||||
|
||||
public static void removeEntries(String inputZipFilename, String prefix, 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");
|
||||
}
|
||||
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(outFile))) {
|
||||
for (ZioEntry inEntry : input.entries.values()) {
|
||||
if (!inEntry.getName().startsWith(prefix)) {
|
||||
zipOutput.write(inEntry);
|
||||
}
|
||||
index++;
|
||||
if(index % reportInterval == 0) {
|
||||
progressCallback.accept((int) (index * 100.0 / size));
|
||||
}
|
||||
}
|
||||
progressCallback.accept(100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public static class ZipEntrySource {
|
||||
|
|
|
@ -16,6 +16,28 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_adv_install"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:visibility="invisible"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<Button
|
||||
android:id="@+id/button_adv_initial"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/button_initial" />
|
||||
<Button
|
||||
android:id="@+id/button_adv_install"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/button_install" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -55,26 +77,26 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintGuide_percent="0.1" />
|
||||
app:layout_constraintGuide_begin="50dp" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline_h2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintGuide_percent="0.2" />
|
||||
app:layout_constraintGuide_begin="100dp" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline_h3"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintGuide_percent="0.3" />
|
||||
app:layout_constraintGuide_begin="150dp" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline_h4"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintGuide_percent="0.7" />
|
||||
app:layout_constraintGuide_end="100dp" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -29,25 +29,30 @@
|
|||
android:orderInCategory="103"
|
||||
android:title="@string/settings_disable_mono_mod"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/settings_advanced_mode"
|
||||
android:orderInCategory="104"
|
||||
android:title="@string/settings_advanced_mode"
|
||||
app:showAsAction="never" />
|
||||
</group>
|
||||
<item
|
||||
android:id="@+id/settings_set_mod_path"
|
||||
android:orderInCategory="103"
|
||||
android:orderInCategory="105"
|
||||
android:title="@string/settings_set_mod_path"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/settings_set_max_log_size"
|
||||
android:orderInCategory="104"
|
||||
android:orderInCategory="106"
|
||||
android:title="@string/settings_set_max_log_size"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/settings_language"
|
||||
android:orderInCategory="105"
|
||||
android:orderInCategory="107"
|
||||
android:title="@string/settings_set_language"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/settings_translation_service"
|
||||
android:orderInCategory="106"
|
||||
android:orderInCategory="108"
|
||||
android:title="@string/settings_translation_service"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
||||
|
|
|
@ -76,4 +76,6 @@
|
|||
<string name="settings_set_max_log_size">最大日誌檔案大小</string>
|
||||
<string name="button_qq_group_text">QQ群</string>
|
||||
<string name="privacy_policy">隱私權條款</string>
|
||||
<string name="settings_advanced_mode">高級安裝模式</string>
|
||||
<string name="button_initial">初始化</string>
|
||||
</resources>
|
||||
|
|
|
@ -76,4 +76,6 @@
|
|||
<string name="settings_set_max_log_size">最大日志大小</string>
|
||||
<string name="button_qq_group_text">QQ群</string>
|
||||
<string name="privacy_policy">隐私权政策</string>
|
||||
<string name="settings_advanced_mode">高级安装模式</string>
|
||||
<string name="button_initial">初始化</string>
|
||||
</resources>
|
||||
|
|
|
@ -80,4 +80,6 @@
|
|||
<string name="settings_set_max_log_size">Max Log Size</string>
|
||||
<string name="button_qq_group_text">QQ Group</string>
|
||||
<string name="privacy_policy">Privacy Policy</string>
|
||||
<string name="settings_advanced_mode">Advanced Patch Mode</string>
|
||||
<string name="button_initial">Initial</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue