1. Optimize patch logic

2. Fix for PyTK 1.19.6
This commit is contained in:
ZaneYork 2021-02-04 14:40:51 +08:00
parent cf9ceb67fb
commit 3569afb695
50 changed files with 142 additions and 138 deletions

View File

@ -12,8 +12,8 @@ android {
applicationId "com.zane.smapiinstaller"
minSdkVersion 19
targetSdkVersion 30
versionCode 69
versionName "3.7.6.7"
versionCode 70
versionName "3.7.6.8"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true
@ -22,11 +22,11 @@ android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
shrinkResources false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
minifyEnabled false
minifyEnabled true
shrinkResources false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}

View File

@ -119,13 +119,12 @@
}
# For Jackson
-keepattributes Signature
-keepattributes *Annotation*
-keep class sun.misc.Unsafe { *; }
-dontwarn org.codehaus.jackson.**
-keepattributes *Annotation*,EnclosingMethod,Signature
-keepnames class com.fasterxml.jackson.** { *; }
-dontwarn com.fasterxml.jackson.databind.**
-keep class org.codehaus.jackson.** { *;}
-keep class com.fasterxml.jackson.** { *; }
-keep class org.codehaus.** { *; }
-keepclassmembers public final enum org.codehaus.jackson.annotate.JsonAutoDetect$Visibility {
public static final org.codehaus.jackson.annotate.JsonAutoDetect$Visibility *; }
#okhttp
-dontwarn okhttp3.**
@ -139,9 +138,13 @@
-keep class pxb.android.** { *; }
-keep class net.fornwall.apksigner.** { *; }
-keep class com.android.apksig.** { *; }
#Warning:org.bouncycastle.jce.provider.X509LDAPCertStoreSpi: can't find referenced class javax.naming.NamingEnumeration
-dontwarn javax.naming.**
-keep class org.bouncycastle.** {*;}
-keep class org.bouncycastle.jcajce.provider.** { *; }
-keep class org.bouncycastle.jce.provider.** { *; }
#-keep class org.bouncycastle.** {*;}
-keep class org.slf4j.**
-keep class com.hjq.language.** {*;}
-keep class net.jpountz.** {*;}
@ -158,3 +161,28 @@ public static java.lang.String TABLENAME;
-dontwarn net.sqlcipher.database.**
# If you do NOT use RxJava:
-dontwarn rx.**
-keep class com.sun.org.apache.xml.internal.utils.PrefixResolver
-keep class java.rmi.Remote
-keep class java.rmi.server.*
-keep class javax.annotation.processing.AbstractProcessor
-keep class javax.el.*
-keep class javax.servlet.http.*
-keep class javax.servlet.jsp.el.VariableResolver
-keep class javax.servlet.jsp.*
-keep class javax.servlet.jsp.tagext.*
-keep class javax.servlet.*
-keep class javax.swing.JTree
-keep class javax.swing.tree.TreeNode
-keep class lombok.core.configuration.ConfigurationKey
-keep class org.apache.tools.ant.Task
-keep class org.apache.tools.ant.taskdefs.MatchingTask
-keep class org.apache.xml.utils.PrefixResolver
-keep class org.jaxen.dom.*
-keep class org.jaxen.dom4j.Dom4jXPath
-keep class org.jaxen.jdom.JDOMXPath
-keep class org.jaxen.*
-keep class org.jdom.output.XMLOutputter
-keep class org.python.core.PyObject
-keep class org.python.util.PythonInterpreter
-keep class org.zeroturnaround.javarebel.ClassEventListener

Binary file not shown.

Binary file not shown.

View File

@ -101,9 +101,16 @@
"assetPath": "apk/System.Xml.dll",
"origin": 0
},
{
"targetPath": "smapi-internal/BmFont.dll",
"assetPath": "assemblies/BmFont.dll",
"isXALZ": true,
"origin": 1
},
{
"targetPath": "smapi-internal/xTile.dll",
"assetPath": "apk/xTile.dll",
"origin": 0
"assetPath": "assemblies/xTile.dll",
"isXALZ": true,
"origin": 1
}
]

View File

@ -62,18 +62,28 @@ public class Constants {
/**
* 文本文件打开大小阈值
*/
public static int TEXT_FILE_OPEN_SIZE_LIMIT = 16 * 1024 * 1024;
public static final int TEXT_FILE_OPEN_SIZE_LIMIT = 16 * 1024 * 1024;
/**
* SMAPI版本
*/
public static final String SMAPI_VERSION = "3.7.6";
/**
* Mono Android 10 起始版本号
*/
public static final int MONO_10_VERSION_CODE = 148;
/**
* 应用名称
*/
public static String PATCHED_APP_NAME = null;
/**
* Manifest中使用的路径分隔符
*/
public static final String FILE_SEPARATOR = "/";
/**
* 平台
*/

View File

@ -89,12 +89,9 @@ public class ApkPatcher {
/**
* 依次扫描package_names.json文件对应的包名抽取找到的第一个游戏APK到SMAPI Installer路径
*
* @param advancedStage 0: 初始化1: 高级安装-1: 普通安装
* @return 抽取后的APK文件路径如果抽取失败返回null
*/
public String extract() {
return extract(-1);
}
public String extract(int advancedStage) {
emitProgress(0);
PackageManager packageManager = context.getPackageManager();
@ -169,12 +166,9 @@ public class ApkPatcher {
* 将指定APK文件重新打包添加SMAPI修改AndroidManifest.xml同时验证版本是否正确
*
* @param apkPath APK文件路径
* @param isAdvanced 是否高级模式
* @return 是否成功打包
*/
public boolean patch(String apkPath) {
return patch(apkPath, false);
}
public boolean patch(String apkPath, boolean isAdvanced) {
if (apkPath == null) {
return false;
@ -230,15 +224,15 @@ public class ApkPatcher {
if (entry.isAdvanced() && !isAdvanced) {
return null;
}
if (entry.getTargetPath().endsWith("/") && entry.getAssetPath().contains("*")) {
String path = StringUtils.substringBeforeLast(entry.getAssetPath(), "/");
String pattern = StringUtils.substringAfterLast(entry.getAssetPath(), "/");
if (entry.getTargetPath().endsWith(Constants.FILE_SEPARATOR) && entry.getAssetPath().contains("*")) {
String path = StringUtils.substringBeforeLast(entry.getAssetPath(), Constants.FILE_SEPARATOR);
String pattern = StringUtils.substringAfterLast(entry.getAssetPath(), Constants.FILE_SEPARATOR);
try {
if (entry.getOrigin() == 1) {
ArrayList<ZipUtils.ZipEntrySource> list = new ArrayList<>();
ZipUtil.iterate(apkFile, (in, zipEntry) -> {
String entryPath = StringUtils.substringBeforeLast(zipEntry.getName(), "/");
String filename = StringUtils.substringAfterLast(zipEntry.getName(), "/");
String entryPath = StringUtils.substringBeforeLast(zipEntry.getName(), Constants.FILE_SEPARATOR);
String filename = StringUtils.substringAfterLast(zipEntry.getName(), Constants.FILE_SEPARATOR);
if (entryPath.equals(path) && StringUtils.wildCardMatch(filename, pattern)) {
byte[] bytes = ByteStreams.toByteArray(in);
ZipUtils.ZipEntrySource source;
@ -256,7 +250,7 @@ public class ApkPatcher {
.filter(filename -> StringUtils.wildCardMatch(filename, pattern))
.map(filename -> new ZipUtils.ZipEntrySource(entry.getTargetPath() + filename, entry.getCompression(), () -> {
try {
return FileUtils.getLocalAsset(context, path + "/" + filename);
return FileUtils.getLocalAsset(context, path + Constants.FILE_SEPARATOR + filename);
} catch (IOException ignored) {
}
return null;
@ -342,7 +336,7 @@ public class ApkPatcher {
}
case "name":
if (strObj.contains(ManifestPatchConstants.PATTERN_MAIN_ACTIVITY)) {
if (versionCode.get() > 147) {
if (versionCode.get() > Constants.MONO_10_VERSION_CODE) {
attr.obj = strObj.replaceFirst("\\w+\\.MainActivity", "crc648e5438a58262f792.SMainActivity");
} else {
attr.obj = strObj.replaceFirst("\\w+\\.MainActivity", "md5723872fa9a204f7f942686e9ed9d0b7d.SMainActivity");

View File

@ -40,9 +40,7 @@ import org.zeroturnaround.zip.ZipUtil;
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
@ -204,8 +202,8 @@ public class CommonLogic {
* @param context context
* @param apkPath 安装包路径
* @param checkMode 是否为校验模式
* @param packageName
* @param versionCode
* @param packageName 包名
* @param versionCode 版本号
* @return 操作是否成功
*/
public static boolean unpackSmapiFiles(Context context, String apkPath, boolean checkMode, String packageName, long versionCode) {
@ -254,56 +252,10 @@ public class CommonLogic {
File targetFile = new File(basePath, entry.getTargetPath());
switch (entry.getOrigin()) {
case 0:
if(entry.isExternal() && apkFilesManifest != null){
byte[] bytes = FileUtils.getAssetBytes(context, apkFilesManifest.getBasePath() + entry.getAssetPath());
try (FileOutputStream outputStream = new FileOutputStream(targetFile)) {
outputStream.write(bytes);
} catch (IOException ignored) {
}
}
else {
if (entry.getTargetPath().endsWith("/") && entry.getAssetPath().contains("*")) {
String path = StringUtils.substring(entry.getAssetPath(), 0, StringUtils.lastIndexOf(entry.getAssetPath(), "/"));
String pattern = StringUtils.substringAfterLast(entry.getAssetPath(), "/");
try {
Stream.of(context.getAssets().list(path))
.filter(filename -> StringUtils.wildCardMatch(filename, pattern))
.forEach(filename -> {
unpackFile(context, checkMode, path + "/" + filename, new File(basePath, entry.getTargetPath() + filename));
});
} catch (IOException ignored) {
}
} else {
unpackFile(context, checkMode, entry.getAssetPath(), targetFile);
}
}
unpackFromInstaller(context, checkMode, apkFilesManifest, basePath, entry, targetFile);
break;
case 1:
if (!checkMode || !targetFile.exists()) {
if(entry.isXALZ()){
byte[] bytes = ZipUtil.unpackEntry(new File(apkPath), entry.getAssetPath());
if (entry.isXALZ()) {
bytes = ZipUtils.decompressXALZ(bytes);
}
FileOutputStream stream = null;
try {
stream = FileUtils.openOutputStream(targetFile);
stream.write(bytes);
} catch (IOException ignore) {
}
finally {
if(stream != null) {
try {
stream.close();
} catch (IOException ignored) {
}
}
}
}
else {
ZipUtil.unpackEntry(new File(apkPath), entry.getAssetPath(), targetFile);
}
}
unpackFromApk(apkPath, checkMode, entry, targetFile);
break;
default:
break;
@ -312,6 +264,48 @@ public class CommonLogic {
return true;
}
private static void unpackFromApk(String apkPath, boolean checkMode, ManifestEntry entry, File targetFile) {
if (!checkMode || !targetFile.exists()) {
if(entry.isXALZ()){
byte[] bytes = ZipUtil.unpackEntry(new File(apkPath), entry.getAssetPath());
if (entry.isXALZ()) {
bytes = ZipUtils.decompressXALZ(bytes);
}
try (FileOutputStream stream = FileUtils.openOutputStream(targetFile)) {
stream.write(bytes);
} catch (IOException ignore) {
}
}
else {
ZipUtil.unpackEntry(new File(apkPath), entry.getAssetPath(), targetFile);
}
}
}
private static void unpackFromInstaller(Context context, boolean checkMode, ApkFilesManifest apkFilesManifest, File basePath, ManifestEntry entry, File targetFile) {
if(entry.isExternal() && apkFilesManifest != null){
byte[] bytes = FileUtils.getAssetBytes(context, apkFilesManifest.getBasePath() + entry.getAssetPath());
try (FileOutputStream outputStream = new FileOutputStream(targetFile)) {
outputStream.write(bytes);
} catch (IOException ignored) {
}
}
else {
if (entry.getTargetPath().endsWith("/") && entry.getAssetPath().contains("*")) {
String path = StringUtils.substring(entry.getAssetPath(), 0, StringUtils.lastIndexOf(entry.getAssetPath(), "/"));
String pattern = StringUtils.substringAfterLast(entry.getAssetPath(), "/");
try {
Stream.of(context.getAssets().list(path))
.filter(filename -> StringUtils.wildCardMatch(filename, pattern))
.forEach(filename -> unpackFile(context, checkMode, path + "/" + filename, new File(basePath, entry.getTargetPath() + filename)));
} catch (IOException ignored) {
}
} else {
unpackFile(context, checkMode, entry.getAssetPath(), targetFile);
}
}
}
public static void filterManifest(List<ApkFilesManifest> manifests, String packageName, long versionCode){
Iterables.removeIf(manifests, manifest -> {
if (manifest == null) {
@ -325,10 +319,7 @@ public class CommonLogic {
return true;
}
}
if (manifest.getTargetPackageName() != null && packageName != null && !manifest.getTargetPackageName().contains(packageName)) {
return true;
}
return false;
return manifest.getTargetPackageName() != null && packageName != null && !manifest.getTargetPackageName().contains(packageName);
});
}
private static void unpackFile(Context context, boolean checkMode, String assertPath, File targetFile) {
@ -398,7 +389,6 @@ public class CommonLogic {
try {
PackageManager manager = activity.getPackageManager();
PackageInfo info = manager.getPackageInfo(activity.getPackageName(), 0);
String version = info.versionName;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
return info.getLongVersionCode();
}

View File

@ -10,9 +10,7 @@ import android.view.View;
import com.microsoft.appcenter.crashes.Crashes;
import com.zane.smapiinstaller.R;
import com.zane.smapiinstaller.constant.Constants;
import com.zane.smapiinstaller.constant.ManifestPatchConstants;
import com.zane.smapiinstaller.utils.DialogUtils;
import com.zane.smapiinstaller.utils.StringUtils;
/**
* 游戏启动器

View File

@ -4,7 +4,6 @@ import java.util.List;
import java.util.function.Function;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import pxb.android.axml.NodeVisitor;
/**

View File

@ -44,7 +44,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
@ -260,13 +259,7 @@ public class ModAssetsManager {
DialogUtils.showConfirmDialog(root, R.string.error,
root.getContext().getString(R.string.duplicate_mod_found, Joiner.on(";").join(list)),
R.string.continue_text, R.string.abort,
((dialog, which) -> {
if (which == DialogAction.POSITIVE) {
returnCallback.accept(true);
} else {
returnCallback.accept(false);
}
}));
((dialog, which) -> returnCallback.accept(which == DialogAction.POSITIVE)));
} else {
returnCallback.accept(true);
}
@ -466,10 +459,7 @@ public class ModAssetsManager {
if (StringUtils.isBlank(dependency.getMinimumVersion())) {
return false;
}
if (VersionUtil.compareVersion(version, dependency.getMinimumVersion()) < 0) {
return true;
}
return false;
return VersionUtil.compareVersion(version, dependency.getMinimumVersion()) < 0;
}
private Tuple2<String, String> checkContentPackDependencyError(ModManifestEntry mod, ImmutableListMultimap<String, ModManifestEntry> installedModMap) {

View File

@ -17,6 +17,8 @@ import com.zane.smapiinstaller.databinding.FragmentAboutBinding;
import com.zane.smapiinstaller.logic.CommonLogic;
import com.zane.smapiinstaller.utils.DialogUtils;
import org.jetbrains.annotations.NotNull;
import androidx.fragment.app.Fragment;
/**
@ -27,7 +29,7 @@ public class AboutFragment extends Fragment {
private FragmentAboutBinding binding;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
public View onCreateView(@NotNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = FragmentAboutBinding.inflate(inflater, container, false);
binding.buttonRelease.setOnClickListener(v -> release());

View File

@ -77,9 +77,7 @@ public class ConfigEditFragment extends Fragment {
File file = new File(configPath);
if (file.exists() && file.length() < Constants.TEXT_FILE_OPEN_SIZE_LIMIT) {
initAssetWebView();
binding.scrollView.post(() -> {
CommonLogic.doOnNonNull(this.getContext(), (context -> onScrollViewRendered(file, context)));
});
binding.scrollView.post(() -> CommonLogic.doOnNonNull(this.getContext(), (context -> onScrollViewRendered(file, context))));
} else {
DialogUtils.showConfirmDialog(binding.getRoot(), R.string.error, this.getString(R.string.text_too_large), R.string.open_with, R.string.cancel, ((dialog, which) -> {
if (which == DialogAction.POSITIVE) {

View File

@ -32,7 +32,7 @@ import androidx.recyclerview.widget.RecyclerView;
* @author Zane
*/
public class ModManifestAdapter extends RecyclerView.Adapter<ModManifestAdapter.ViewHolder> {
private ConfigViewModel model;
private final ConfigViewModel model;
private List<ModManifestEntry> modList;
public ModManifestAdapter(ConfigViewModel model, List<ModManifestEntry> modList) {
@ -66,7 +66,7 @@ public class ModManifestAdapter extends RecyclerView.Adapter<ModManifestAdapter.
}
class ViewHolder extends RecyclerView.ViewHolder {
private ModListItemBinding binding;
private final ModListItemBinding binding;
private ModManifestEntry modInfo;
private List<String> configList;
@ -181,9 +181,7 @@ public class ModManifestAdapter extends RecyclerView.Adapter<ModManifestAdapter.
if (configList.size() > 0) {
if (configList.size() > 1) {
List<String> selections = configList.stream().map(path -> StringUtils.removeStart(path, modInfo.getAssetPath())).collect(Collectors.toList());
DialogUtils.showListItemsDialog(itemView, R.string.menu_config_edit, selections, (materialDialog, index) -> {
navigateToConfigEditor(configList.get(index));
});
DialogUtils.showListItemsDialog(itemView, R.string.menu_config_edit, selections, (materialDialog, index) -> navigateToConfigEditor(configList.get(index)));
} else {
navigateToConfigEditor(configList.get(0));
}

View File

@ -70,7 +70,7 @@ public class DownloadableContentAdapter extends RecyclerView.Adapter<Downloadabl
}
static class ViewHolder extends RecyclerView.ViewHolder {
private DownloadContentItemBinding binding;
private final DownloadContentItemBinding binding;
private final AtomicBoolean downloading = new AtomicBoolean(false);

View File

@ -50,7 +50,7 @@ public class HelpItemAdapter extends RecyclerView.Adapter<HelpItemAdapter.ViewHo
}
static class ViewHolder extends RecyclerView.ViewHolder {
private HelpListItemBinding binding;
private final HelpListItemBinding binding;
public ViewHolder(View view) {
super(view);

View File

@ -15,7 +15,7 @@ import androidx.fragment.app.Fragment;
import androidx.viewpager2.adapter.FragmentStateAdapter;
class MainTabPagerAdapter extends FragmentStateAdapter {
private Fragment fragment;
private final Fragment fragment;
public MainTabPagerAdapter(Fragment fragment) {
super(fragment);

View File

@ -47,11 +47,7 @@ public class MainTabsFragment extends Fragment {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
if (position >= 3) {
MainActivity.instance.setFloatingBarVisibility(false);
} else {
MainActivity.instance.setFloatingBarVisibility(true);
}
MainActivity.instance.setFloatingBarVisibility(position < 3);
}
});
}

View File

@ -29,7 +29,7 @@ public class ModUpdateAdapter extends RecyclerView.Adapter<ModUpdateAdapter.View
private final ImmutableListMultimap<String, ModManifestEntry> installedModMap;
private List<ModUpdateCheckResponseDto> updateInfoList;
private final List<ModUpdateCheckResponseDto> updateInfoList;
public ModUpdateAdapter(List<ModUpdateCheckResponseDto> items) {
updateInfoList = items;
@ -56,7 +56,7 @@ public class ModUpdateAdapter extends RecyclerView.Adapter<ModUpdateAdapter.View
class ViewHolder extends RecyclerView.ViewHolder {
public ModUpdateCheckResponseDto updateInfo;
private UpdatableModListItemBinding binding;
private final UpdatableModListItemBinding binding;
public void setUpdateInfo(ModUpdateCheckResponseDto updateInfo) {
this.updateInfo = updateInfo;

View File

@ -12,6 +12,8 @@ import com.zane.smapiinstaller.dto.ModUpdateCheckResponseDto;
import com.zane.smapiinstaller.logic.CommonLogic;
import com.zane.smapiinstaller.utils.JsonUtil;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import androidx.fragment.app.Fragment;
@ -35,7 +37,7 @@ public class ModUpdateFragment extends Fragment {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
public View onCreateView(@NotNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = FragmentModUpdateListBinding.inflate(inflater, container, false);
// Set the adapter

View File

@ -24,10 +24,8 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* 文件工具类

View File

@ -7,9 +7,9 @@ import java.math.BigInteger;
*/
public class MathUtils {
private static int INT_MAX_BIT_VALUE = 31;
private static final int INT_MAX_BIT_VALUE = 31;
private static int LONG_MAX_BIT_VALUE = 63;
private static final int LONG_MAX_BIT_VALUE = 63;
/**
* Returns the value of the {@code long} argument;

View File

@ -56,7 +56,7 @@ public class VersionUtil {
* @return 是否为空版本段
*/
private static boolean isZero(List<String> versionSections) {
return !versionSections.stream().anyMatch(version -> {
return versionSections.stream().noneMatch(version -> {
try {
int i = Integer.parseInt(version);
if (i == 0) {

View File

@ -32,8 +32,10 @@ import lombok.EqualsAndHashCode;
*/
public class ZipUtils {
private final static String FILE_HEADER_XALZ = "XALZ";
public static byte[] decompressXALZ(byte[] bytes) {
if ("XALZ".equals(new String(ByteUtils.subArray(bytes, 0, 4), StandardCharsets.ISO_8859_1))) {
if (FILE_HEADER_XALZ.equals(new String(ByteUtils.subArray(bytes, 0, 4), StandardCharsets.ISO_8859_1))) {
byte[] length = ByteUtils.subArray(bytes, 8, 12);
int len = (length[0] & 0xff) | ((length[1] & 0xff) << 8) | ((length[2] & 0xff) << 16) | ((length[3] & 0xff) << 24);
bytes = LZ4Factory.fastestJavaInstance().fastDecompressor().decompress(bytes, 12, len);

View File

@ -7,27 +7,19 @@ import android.text.Editable;
*/
public abstract class TextChangedWatcher implements android.text.TextWatcher {
/**
* Do nothing
* @param s origin string
* @param start modify position
* @param count modify count
* @param after modified string
* {@inheritDoc}
*/
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
/**
* Text changed event
* @param s modified string
* @param start modify position
* @param count modify count
* {@inheritDoc}
*/
@Override
public abstract void onTextChanged(CharSequence s, int start, int before, int count);
/**
* Do nothing
* @param s target view
* {@inheritDoc}
*/
@Override
public void afterTextChanged(Editable s) {}