Lombok/Bug fix/DLC(in progress)
This commit is contained in:
parent
ef0639797e
commit
97e62a4c8f
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<annotationProcessing>
|
||||||
|
<profile default="true" name="Default" enabled="true" />
|
||||||
|
</annotationProcessing>
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -47,6 +47,7 @@ dependencies {
|
||||||
implementation 'com.afollestad.material-dialogs:core:0.9.6.0'
|
implementation 'com.afollestad.material-dialogs:core:0.9.6.0'
|
||||||
// https://mvnrepository.com/artifact/com.jakewharton/butterknife
|
// https://mvnrepository.com/artifact/com.jakewharton/butterknife
|
||||||
implementation 'com.jakewharton:butterknife:10.2.1'
|
implementation 'com.jakewharton:butterknife:10.2.1'
|
||||||
|
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||||
annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.1'
|
annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.1'
|
||||||
// https://mvnrepository.com/artifact/com.google.guava/guava
|
// https://mvnrepository.com/artifact/com.google.guava/guava
|
||||||
implementation group: 'com.google.guava', name: 'guava', version: '28.2-android'
|
implementation group: 'com.google.guava', name: 'guava', version: '28.2-android'
|
||||||
|
@ -54,9 +55,15 @@ dependencies {
|
||||||
implementation group: 'org.zeroturnaround', name: 'zt-zip', version: '1.14'
|
implementation group: 'org.zeroturnaround', name: 'zt-zip', version: '1.14'
|
||||||
// https://mvnrepository.com/artifact/com.google.code.gson/gson
|
// https://mvnrepository.com/artifact/com.google.code.gson/gson
|
||||||
implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.6'
|
implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.6'
|
||||||
|
// https://mvnrepository.com/artifact/org.apache.commons/commons-lang3
|
||||||
|
implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.9'
|
||||||
|
implementation 'com.lzy.net:okgo:3.0.4'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||||
implementation 'com.android.support:multidex:1.0.3'
|
implementation 'com.android.support:multidex:1.0.3'
|
||||||
|
|
||||||
|
compileOnly 'org.projectlombok:lombok:1.18.12'
|
||||||
|
annotationProcessor 'org.projectlombok:lombok:1.18.12'
|
||||||
}
|
}
|
||||||
|
|
|
@ -146,6 +146,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
##---------------End: proguard configuration for Gson ----------
|
##---------------End: proguard configuration for Gson ----------
|
||||||
|
#okhttp
|
||||||
|
-dontwarn okhttp3.**
|
||||||
|
-keep class okhttp3.**{*;}
|
||||||
|
|
||||||
|
#okio
|
||||||
|
-dontwarn okio.**
|
||||||
|
-keep class okio.**{*;}
|
||||||
|
|
||||||
-keep class com.zane.** { *; }
|
-keep class com.zane.** { *; }
|
||||||
-keep class pxb.android.** { *; }
|
-keep class pxb.android.** { *; }
|
||||||
|
|
|
@ -2,12 +2,14 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.zane.smapiinstaller">
|
package="com.zane.smapiinstaller">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
|
||||||
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES"/>
|
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES"/>
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".MainApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
[
|
{
|
||||||
|
"minBuildCode": 138,
|
||||||
|
"maxBuildCode": null,
|
||||||
|
"withMonoMod": true,
|
||||||
|
"manifestEntries": [
|
||||||
{
|
{
|
||||||
"targetPath": "classes.dex",
|
"targetPath": "classes.dex",
|
||||||
"assetPath": "apk/classes.dex",
|
"assetPath": "apk/classes.dex",
|
||||||
|
@ -59,4 +63,5 @@
|
||||||
"assetPath": "apk/System.Numerics.dll",
|
"assetPath": "apk/System.Numerics.dll",
|
||||||
"compression": 0
|
"compression": 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"contents": [
|
||||||
|
{
|
||||||
|
"type": "COMPAT",
|
||||||
|
"name": "SMAPI for 1.4.5.135",
|
||||||
|
"description": "SMAPI compat package pack for game 1.4.5.135 - 1.4.5.137",
|
||||||
|
"url": "http://dl.zaneyork.cn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "COMPAT",
|
||||||
|
"name": "SMAPI for 1.4.4.122",
|
||||||
|
"description": "SMAPI compat package pack for game 1.4.4.122 - 1.4.4.128",
|
||||||
|
"url": "http://dl.zaneyork.cn"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
{
|
{
|
||||||
"assetPath":"mods/virtual-keyboard.zip",
|
"assetPath":"mods/virtual-keyboard.zip",
|
||||||
"Name": "VirtualKeyboard",
|
"Name": "VirtualKeyboard",
|
||||||
"UniqueID": "SMAPI.VirtualKeyboard"
|
"UniqueID": "VirtualKeyboard"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"assetPath":"mods/custom-localization.zip",
|
"assetPath":"mods/custom-localization.zip",
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.zane.smapiinstaller;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
|
||||||
|
import com.lzy.okgo.OkGo;
|
||||||
|
|
||||||
|
public class MainApplication extends Application {
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
OkGo.getInstance().init(this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.zane.smapiinstaller.entity;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ApkFilesManifest {
|
||||||
|
private Long minBuildCode;
|
||||||
|
private Long maxBuildCode;
|
||||||
|
private List<ManifestEntry> manifestEntries;
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.zane.smapiinstaller.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class DownloadableContent {
|
||||||
|
private String type;
|
||||||
|
private String name;
|
||||||
|
private String url;
|
||||||
|
private String description;
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.zane.smapiinstaller.entity;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class DownloadableContentList {
|
||||||
|
private int version;
|
||||||
|
List<DownloadableContent> contents;
|
||||||
|
}
|
|
@ -1,40 +1,11 @@
|
||||||
package com.zane.smapiinstaller.entity;
|
package com.zane.smapiinstaller.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
public class ManifestEntry {
|
public class ManifestEntry {
|
||||||
private String targetPath;
|
private String targetPath;
|
||||||
private String assetPath;
|
private String assetPath;
|
||||||
private int compression;
|
private int compression;
|
||||||
private int origin;
|
private int origin;
|
||||||
|
|
||||||
public String getTargetPath() {
|
|
||||||
return targetPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTargetPath(String targetPath) {
|
|
||||||
this.targetPath = targetPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAssetPath() {
|
|
||||||
return assetPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAssetPath(String assetPath) {
|
|
||||||
this.assetPath = assetPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCompression() {
|
|
||||||
return compression;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCompression(int compression) {
|
|
||||||
this.compression = compression;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getOrigin() {
|
|
||||||
return origin;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOrigin(int origin) {
|
|
||||||
this.origin = origin;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +1,12 @@
|
||||||
package com.zane.smapiinstaller.entity;
|
package com.zane.smapiinstaller.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
public class ModManifestEntry {
|
public class ModManifestEntry {
|
||||||
private String assetPath;
|
private String assetPath;
|
||||||
private String Name;
|
private String Name;
|
||||||
private String UniqueID;
|
private String UniqueID;
|
||||||
private String Description;
|
private String Description;
|
||||||
private ModManifestEntry ContentPackFor;
|
private ModManifestEntry ContentPackFor;
|
||||||
|
|
||||||
public String getAssetPath() {
|
|
||||||
return assetPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAssetPath(String assetPath) {
|
|
||||||
this.assetPath = assetPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
Name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUniqueID() {
|
|
||||||
return UniqueID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUniqueID(String uniqueID) {
|
|
||||||
UniqueID = uniqueID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDescription() {
|
|
||||||
return Description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDescription(String description) {
|
|
||||||
Description = description;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,16 +11,20 @@ import android.os.Environment;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.io.Files;
|
import com.google.common.io.Files;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import com.zane.smapiinstaller.BuildConfig;
|
import com.zane.smapiinstaller.BuildConfig;
|
||||||
import com.zane.smapiinstaller.R;
|
import com.zane.smapiinstaller.R;
|
||||||
import com.zane.smapiinstaller.constant.Constants;
|
import com.zane.smapiinstaller.constant.Constants;
|
||||||
|
import com.zane.smapiinstaller.entity.ApkFilesManifest;
|
||||||
import com.zane.smapiinstaller.entity.ManifestEntry;
|
import com.zane.smapiinstaller.entity.ManifestEntry;
|
||||||
|
|
||||||
import net.fornwall.apksigner.KeyStoreFileManager;
|
import net.fornwall.apksigner.KeyStoreFileManager;
|
||||||
import net.fornwall.apksigner.ZipSigner;
|
import net.fornwall.apksigner.ZipSigner;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.zeroturnaround.zip.ByteSource;
|
import org.zeroturnaround.zip.ByteSource;
|
||||||
import org.zeroturnaround.zip.ZipEntrySource;
|
import org.zeroturnaround.zip.ZipEntrySource;
|
||||||
import org.zeroturnaround.zip.ZipUtil;
|
import org.zeroturnaround.zip.ZipUtil;
|
||||||
|
@ -29,7 +33,6 @@ import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -48,6 +51,8 @@ public class ApkPatcher {
|
||||||
|
|
||||||
private static final String TAG = "PATCHER";
|
private static final String TAG = "PATCHER";
|
||||||
|
|
||||||
|
private AtomicReference<String> errorMessage = new AtomicReference<>();
|
||||||
|
|
||||||
public ApkPatcher(Context context) {
|
public ApkPatcher(Context context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
@ -56,8 +61,10 @@ public class ApkPatcher {
|
||||||
PackageManager packageManager = context.getPackageManager();
|
PackageManager packageManager = context.getPackageManager();
|
||||||
List<String> packageNames = CommonLogic.getAssetJson(context, "package_names.json", new TypeToken<List<String>>() {
|
List<String> packageNames = CommonLogic.getAssetJson(context, "package_names.json", new TypeToken<List<String>>() {
|
||||||
}.getType());
|
}.getType());
|
||||||
if (packageNames == null)
|
if (packageNames == null) {
|
||||||
|
errorMessage.set(context.getString(R.string.error_game_not_found));
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
for (String packageName : packageNames) {
|
for (String packageName : packageNames) {
|
||||||
try {
|
try {
|
||||||
PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0);
|
PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0);
|
||||||
|
@ -69,6 +76,7 @@ public class ApkPatcher {
|
||||||
File dest = new File(externalFilesDir.getAbsolutePath() + "/SMAPI Installer/");
|
File dest = new File(externalFilesDir.getAbsolutePath() + "/SMAPI Installer/");
|
||||||
if (!dest.exists()) {
|
if (!dest.exists()) {
|
||||||
if (!dest.mkdir()) {
|
if (!dest.mkdir()) {
|
||||||
|
errorMessage.set(String.format(context.getString(R.string.error_failed_to_create_file), dest.getAbsolutePath()));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,6 +86,7 @@ public class ApkPatcher {
|
||||||
}
|
}
|
||||||
} catch (PackageManager.NameNotFoundException | IOException e) {
|
} catch (PackageManager.NameNotFoundException | IOException e) {
|
||||||
Log.e(TAG, "Extract error", e);
|
Log.e(TAG, "Extract error", e);
|
||||||
|
errorMessage.set(e.getLocalizedMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -90,29 +99,36 @@ public class ApkPatcher {
|
||||||
if (!file.exists())
|
if (!file.exists())
|
||||||
return false;
|
return false;
|
||||||
try {
|
try {
|
||||||
List<ManifestEntry> manifestEntries = CommonLogic.getAssetJson(context, "apk_files_manifest.json", new TypeToken<List<ManifestEntry>>() {
|
ApkFilesManifest apkFilesManifest = CommonLogic.getAssetJson(context, "apk_files_manifest.json", ApkFilesManifest.class);
|
||||||
}.getType());
|
if (apkFilesManifest == null)
|
||||||
if (manifestEntries == null)
|
|
||||||
return false;
|
return false;
|
||||||
|
List<ManifestEntry> manifestEntries = apkFilesManifest.getManifestEntries();
|
||||||
List<ZipEntrySource> zipEntrySourceList = new ArrayList<>();
|
List<ZipEntrySource> zipEntrySourceList = new ArrayList<>();
|
||||||
for (ManifestEntry entry : manifestEntries) {
|
|
||||||
zipEntrySourceList.add(new ByteSource(entry.getTargetPath(), CommonLogic.getAssetBytes(context, entry.getAssetPath()), entry.getCompression()));
|
|
||||||
}
|
|
||||||
byte[] manifest = ZipUtil.unpackEntry(file, "AndroidManifest.xml");
|
byte[] manifest = ZipUtil.unpackEntry(file, "AndroidManifest.xml");
|
||||||
byte[] modifiedManifest = modifyManifest(manifest);
|
List<ApkFilesManifest> apkFilesManifests = Lists.newArrayList(apkFilesManifest);
|
||||||
|
byte[] modifiedManifest = modifyManifest(manifest, apkFilesManifests);
|
||||||
|
if(apkFilesManifests.size() == 0) {
|
||||||
|
errorMessage.set(context.getString(R.string.error_no_supported_game_version));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if(modifiedManifest == null) {
|
if(modifiedManifest == null) {
|
||||||
|
errorMessage.set(context.getString(R.string.failed_to_process_manifest));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
zipEntrySourceList.add(new ByteSource("AndroidManifest.xml", modifiedManifest, Deflater.DEFLATED));
|
zipEntrySourceList.add(new ByteSource("AndroidManifest.xml", modifiedManifest, Deflater.DEFLATED));
|
||||||
|
for (ManifestEntry entry : manifestEntries) {
|
||||||
|
zipEntrySourceList.add(new ByteSource(entry.getTargetPath(), CommonLogic.getAssetBytes(context, entry.getAssetPath()), entry.getCompression()));
|
||||||
|
}
|
||||||
ZipUtil.addOrReplaceEntries(file, zipEntrySourceList.toArray(new ZipEntrySource[0]));
|
ZipUtil.addOrReplaceEntries(file, zipEntrySourceList.toArray(new ZipEntrySource[0]));
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "Patch error", e);
|
Log.e(TAG, "Patch error", e);
|
||||||
|
errorMessage.set(e.getLocalizedMessage());
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] modifyManifest(byte[] bytes) {
|
private byte[] modifyManifest(byte[] bytes, List<ApkFilesManifest> manifests) {
|
||||||
AtomicReference<String> packageName = new AtomicReference<>();
|
AtomicReference<String> packageName = new AtomicReference<>();
|
||||||
Predicate<ManifestTagVisitor.AttrArgs> processLogic = (attr) -> {
|
Predicate<ManifestTagVisitor.AttrArgs> processLogic = (attr) -> {
|
||||||
if (attr.type == NodeVisitor.TYPE_STRING) {
|
if (attr.type == NodeVisitor.TYPE_STRING) {
|
||||||
|
@ -140,9 +156,32 @@ public class ApkPatcher {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if(attr.type == NodeVisitor.TYPE_FIRST_INT) {
|
||||||
|
if(StringUtils.equals(attr.name, "versionCode")){
|
||||||
|
long versionCode = (int) attr.obj;
|
||||||
|
Iterables.removeIf(manifests, manifest -> {
|
||||||
|
if (manifest.getMinBuildCode() != null) {
|
||||||
|
if (versionCode < manifest.getMinBuildCode()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (manifest.getMaxBuildCode() != null) {
|
||||||
|
if (versionCode > manifest.getMinBuildCode()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
try {
|
||||||
return CommonLogic.modifyManifest(bytes, processLogic);
|
return CommonLogic.modifyManifest(bytes, processLogic);
|
||||||
|
}catch (Exception e) {
|
||||||
|
errorMessage.set(e.getLocalizedMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String sign(String apkPath) {
|
public String sign(String apkPath) {
|
||||||
|
@ -156,16 +195,14 @@ public class ApkPatcher {
|
||||||
}
|
}
|
||||||
String alias = ks.aliases().nextElement();
|
String alias = ks.aliases().nextElement();
|
||||||
X509Certificate publicKey = (X509Certificate) ks.getCertificate(alias);
|
X509Certificate publicKey = (X509Certificate) ks.getCertificate(alias);
|
||||||
try {
|
|
||||||
PrivateKey privateKey = (PrivateKey) ks.getKey(alias, "android".toCharArray());
|
PrivateKey privateKey = (PrivateKey) ks.getKey(alias, "android".toCharArray());
|
||||||
ZipSigner.signZip(publicKey, privateKey, "SHA1withRSA", apkPath, signApkPath);
|
ZipSigner.signZip(publicKey, privateKey, "SHA1withRSA", apkPath, signApkPath);
|
||||||
new File(apkPath).delete();
|
new File(apkPath).delete();
|
||||||
return signApkPath;
|
return signApkPath;
|
||||||
} catch (NoSuchAlgorithmException ignored) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "Sign error", e);
|
Log.e(TAG, "Sign error", e);
|
||||||
|
errorMessage.set(e.getLocalizedMessage());
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -179,6 +216,7 @@ public class ApkPatcher {
|
||||||
context.startActivity(intent);
|
context.startActivity(intent);
|
||||||
} catch (ActivityNotFoundException e) {
|
} catch (ActivityNotFoundException e) {
|
||||||
Log.e(TAG, "Install error", e);
|
Log.e(TAG, "Install error", e);
|
||||||
|
errorMessage.set(e.getLocalizedMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,4 +234,7 @@ public class ApkPatcher {
|
||||||
return Uri.fromFile(file);
|
return Uri.fromFile(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AtomicReference<String> getErrorMessage() {
|
||||||
|
return errorMessage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,17 @@ public class CommonLogic {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> T getAssetJson(Context context, String filename, Class<T> tClass) {
|
||||||
|
try {
|
||||||
|
InputStream inputStream = context.getAssets().open(filename);
|
||||||
|
try (InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
|
||||||
|
return new Gson().fromJson(CharStreams.toString(reader), tClass);
|
||||||
|
}
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public static <T> T getAssetJson(Context context, String filename, Type type) {
|
public static <T> T getAssetJson(Context context, String filename, Type type) {
|
||||||
try {
|
try {
|
||||||
InputStream inputStream = context.getAssets().open(filename);
|
InputStream inputStream = context.getAssets().open(filename);
|
||||||
|
@ -177,10 +188,9 @@ public class CommonLogic {
|
||||||
context.startActivity(intent);
|
context.startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] modifyManifest(byte[] bytes, Predicate<ManifestTagVisitor.AttrArgs> processLogic) {
|
public static byte[] modifyManifest(byte[] bytes, Predicate<ManifestTagVisitor.AttrArgs> processLogic) throws IOException {
|
||||||
AxmlReader reader = new AxmlReader(bytes);
|
AxmlReader reader = new AxmlReader(bytes);
|
||||||
AxmlWriter writer = new AxmlWriter();
|
AxmlWriter writer = new AxmlWriter();
|
||||||
try {
|
|
||||||
reader.accept(new AxmlVisitor(writer) {
|
reader.accept(new AxmlVisitor(writer) {
|
||||||
@Override
|
@Override
|
||||||
public NodeVisitor child(String ns, String name) {
|
public NodeVisitor child(String ns, String name) {
|
||||||
|
@ -188,14 +198,6 @@ public class CommonLogic {
|
||||||
return new ManifestTagVisitor(child, processLogic);
|
return new ManifestTagVisitor(child, processLogic);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return writer.toByteArray();
|
return writer.toByteArray();
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,11 @@ public class GameLauncher {
|
||||||
CommonLogic.showAlertDialog(root, R.string.error, R.string.error_failed_to_repair);
|
CommonLogic.showAlertDialog(root, R.string.error, R.string.error_failed_to_repair);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
ModAssetsManager modAssetsManager = new ModAssetsManager(root);
|
||||||
|
if(modAssetsManager.checkModEnvironment()) {
|
||||||
Intent intent = packageManager.getLaunchIntentForPackage(Constants.TARGET_PACKAGE_NAME);
|
Intent intent = packageManager.getLaunchIntentForPackage(Constants.TARGET_PACKAGE_NAME);
|
||||||
context.startActivity(intent);
|
context.startActivity(intent);
|
||||||
|
}
|
||||||
} catch (PackageManager.NameNotFoundException ignored) {
|
} catch (PackageManager.NameNotFoundException ignored) {
|
||||||
CommonLogic.showAlertDialog(root, R.string.error, R.string.error_smapi_not_installed);
|
CommonLogic.showAlertDialog(root, R.string.error, R.string.error_smapi_not_installed);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@ import com.zane.smapiinstaller.R;
|
||||||
import com.zane.smapiinstaller.constant.Constants;
|
import com.zane.smapiinstaller.constant.Constants;
|
||||||
import com.zane.smapiinstaller.entity.ModManifestEntry;
|
import com.zane.smapiinstaller.entity.ModManifestEntry;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.zeroturnaround.zip.NameMapper;
|
||||||
import org.zeroturnaround.zip.ZipUtil;
|
import org.zeroturnaround.zip.ZipUtil;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -23,6 +25,7 @@ import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
public class ModAssetsManager {
|
public class ModAssetsManager {
|
||||||
|
|
||||||
|
@ -43,7 +46,7 @@ public class ModAssetsManager {
|
||||||
if(currentFile != null && currentFile.exists()) {
|
if(currentFile != null && currentFile.exists()) {
|
||||||
boolean foundManifest = false;
|
boolean foundManifest = false;
|
||||||
for(File file : currentFile.listFiles(File::isFile)) {
|
for(File file : currentFile.listFiles(File::isFile)) {
|
||||||
if(file.getName().equalsIgnoreCase("manifest.json")) {
|
if(StringUtils.equalsIgnoreCase(file.getName(), "manifest.json")) {
|
||||||
ModManifestEntry manifest = CommonLogic.getFileJson(file, new TypeToken<ModManifestEntry>(){}.getType());
|
ModManifestEntry manifest = CommonLogic.getFileJson(file, new TypeToken<ModManifestEntry>(){}.getType());
|
||||||
foundManifest = true;
|
foundManifest = true;
|
||||||
if(manifest != null) {
|
if(manifest != null) {
|
||||||
|
@ -70,7 +73,7 @@ public class ModAssetsManager {
|
||||||
File modFolder = new File(Environment.getExternalStorageDirectory(), Constants.MOD_PATH);
|
File modFolder = new File(Environment.getExternalStorageDirectory(), Constants.MOD_PATH);
|
||||||
ImmutableListMultimap<String, ModManifestEntry> installedModMap = Multimaps.index(findAllInstalledMods(), ModManifestEntry::getUniqueID);
|
ImmutableListMultimap<String, ModManifestEntry> installedModMap = Multimaps.index(findAllInstalledMods(), ModManifestEntry::getUniqueID);
|
||||||
for (ModManifestEntry mod : modManifestEntries) {
|
for (ModManifestEntry mod : modManifestEntries) {
|
||||||
if(installedModMap.containsKey(mod.getUniqueID())) {
|
if(installedModMap.containsKey(mod.getUniqueID()) || installedModMap.containsKey(mod.getUniqueID().replace("ZaneYork.CustomLocalization", "SMAPI.CustomLocalization"))) {
|
||||||
ImmutableList<ModManifestEntry> installedMods = installedModMap.get(mod.getUniqueID());
|
ImmutableList<ModManifestEntry> installedMods = installedModMap.get(mod.getUniqueID());
|
||||||
if(installedMods.size() > 1) {
|
if(installedMods.size() > 1) {
|
||||||
CommonLogic.showAlertDialog(root, R.string.error,
|
CommonLogic.showAlertDialog(root, R.string.error,
|
||||||
|
@ -79,7 +82,7 @@ public class ModAssetsManager {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
ZipUtil.unpackEntry(context.getAssets().open(mod.getAssetPath()), mod.getName(), new File(installedMods.get(0).getAssetPath()));
|
ZipUtil.unpack(context.getAssets().open(mod.getAssetPath()), new File(installedMods.get(0).getAssetPath()), (name)-> StringUtils.removeStart(name, mod.getName() + "/"));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, "Install Mod Error", e);
|
Log.e(TAG, "Install Mod Error", e);
|
||||||
}
|
}
|
||||||
|
@ -94,4 +97,8 @@ public class ModAssetsManager {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean checkModEnvironment() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ public class ConfigEditFragment extends Fragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
CommonLogic.showAlertDialog(getView(), R.string.error, e.getMessage());
|
CommonLogic.showAlertDialog(getView(), R.string.error, e.getLocalizedMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@ import com.zane.smapiinstaller.R;
|
||||||
|
|
||||||
public class ConfigFragment extends Fragment {
|
public class ConfigFragment extends Fragment {
|
||||||
|
|
||||||
private ConfigViewModel configViewModel;
|
|
||||||
|
|
||||||
@BindView(R.id.view_mod_list)
|
@BindView(R.id.view_mod_list)
|
||||||
RecyclerView recyclerView;
|
RecyclerView recyclerView;
|
||||||
|
@ -26,13 +25,11 @@ public class ConfigFragment extends Fragment {
|
||||||
ViewGroup container, Bundle savedInstanceState) {
|
ViewGroup container, Bundle savedInstanceState) {
|
||||||
View root = inflater.inflate(R.layout.fragment_config, container, false);
|
View root = inflater.inflate(R.layout.fragment_config, container, false);
|
||||||
ButterKnife.bind(this, root);
|
ButterKnife.bind(this, root);
|
||||||
configViewModel = new ConfigViewModel(root);
|
|
||||||
recyclerView.setLayoutManager(new LinearLayoutManager(this.getContext()));
|
recyclerView.setLayoutManager(new LinearLayoutManager(this.getContext()));
|
||||||
recyclerView.setAdapter(new ModManifestAdapter(configViewModel.getModList().getValue()));
|
ConfigViewModel configViewModel = new ConfigViewModel(root);
|
||||||
recyclerView.addItemDecoration(new DividerItemDecoration(this.getContext(), DividerItemDecoration.VERTICAL));
|
ModManifestAdapter modManifestAdapter = new ModManifestAdapter(configViewModel);
|
||||||
configViewModel.getModList().observe(getViewLifecycleOwner(), modList -> {
|
recyclerView.setAdapter(modManifestAdapter);
|
||||||
recyclerView.getAdapter().notifyDataSetChanged();
|
recyclerView.addItemDecoration(new DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL));
|
||||||
});
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,55 @@
|
||||||
package com.zane.smapiinstaller.ui.config;
|
package com.zane.smapiinstaller.ui.config;
|
||||||
|
|
||||||
|
import android.os.FileObserver;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
|
import com.google.common.collect.Collections2;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
import com.zane.smapiinstaller.entity.ModManifestEntry;
|
import com.zane.smapiinstaller.entity.ModManifestEntry;
|
||||||
import com.zane.smapiinstaller.logic.ModAssetsManager;
|
import com.zane.smapiinstaller.logic.ModAssetsManager;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
import androidx.lifecycle.ViewModel;
|
import androidx.lifecycle.ViewModel;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
class ConfigViewModel extends ViewModel {
|
class ConfigViewModel extends ViewModel {
|
||||||
|
|
||||||
private MutableLiveData<List<ModManifestEntry>> modList;
|
@NonNull
|
||||||
|
private List<ModManifestEntry> modList;
|
||||||
|
|
||||||
public ConfigViewModel(View root) {
|
private RecyclerView view;
|
||||||
|
|
||||||
|
ConfigViewModel(View root) {
|
||||||
ModAssetsManager manager = new ModAssetsManager(root);
|
ModAssetsManager manager = new ModAssetsManager(root);
|
||||||
this.modList = new MutableLiveData<>();
|
this.modList = manager.findAllInstalledMods();
|
||||||
List<ModManifestEntry> entryList = manager.findAllInstalledMods();
|
Collections.sort(this.modList, (a, b)-> a.getName().compareTo(b.getName()));
|
||||||
Collections.sort(entryList, (a, b)-> a.getName().compareTo(b.getName()));
|
|
||||||
this.modList.setValue(entryList);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public MutableLiveData<List<ModManifestEntry>> getModList() {
|
@NonNull
|
||||||
|
public List<ModManifestEntry> getModList() {
|
||||||
return modList;
|
return modList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Integer> removeAll(Predicate<ModManifestEntry> predicate) {
|
||||||
|
List<Integer> deletedId = new ArrayList<>();
|
||||||
|
for (int i = modList.size() - 1; i >=0 ; i--) {
|
||||||
|
if(predicate.apply(modList.get(i))) {
|
||||||
|
modList.remove(i);
|
||||||
|
deletedId.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deletedId;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -7,11 +7,16 @@ import android.widget.Button;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.afollestad.materialdialogs.DialogAction;
|
import com.afollestad.materialdialogs.DialogAction;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
import com.zane.smapiinstaller.R;
|
import com.zane.smapiinstaller.R;
|
||||||
import com.zane.smapiinstaller.entity.ModManifestEntry;
|
import com.zane.smapiinstaller.entity.ModManifestEntry;
|
||||||
import com.zane.smapiinstaller.logic.CommonLogic;
|
import com.zane.smapiinstaller.logic.CommonLogic;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.zeroturnaround.zip.commons.FileUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
@ -23,21 +28,22 @@ import butterknife.ButterKnife;
|
||||||
import butterknife.OnClick;
|
import butterknife.OnClick;
|
||||||
|
|
||||||
public class ModManifestAdapter extends RecyclerView.Adapter<ModManifestAdapter.ViewHolder> {
|
public class ModManifestAdapter extends RecyclerView.Adapter<ModManifestAdapter.ViewHolder> {
|
||||||
private List<ModManifestEntry> modList;
|
private ConfigViewModel model;
|
||||||
|
|
||||||
public ModManifestAdapter(List<ModManifestEntry> modList){
|
public ModManifestAdapter(ConfigViewModel model){
|
||||||
this.modList=modList;
|
this.model=model;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
View view= LayoutInflater.from(parent.getContext()).inflate(R.layout.mod_list_item,null);
|
View view= LayoutInflater.from(parent.getContext()).inflate(R.layout.mod_list_item,parent, false);
|
||||||
return new ViewHolder(view);
|
return new ViewHolder(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||||
ModManifestEntry mod = modList.get(position);
|
ModManifestEntry mod = model.getModList().get(position);
|
||||||
holder.modName.setText(mod.getName());
|
holder.modName.setText(mod.getName());
|
||||||
holder.modDescription.setText(mod.getDescription());
|
holder.modDescription.setText(mod.getDescription());
|
||||||
holder.setModPath(mod.getAssetPath());
|
holder.setModPath(mod.getAssetPath());
|
||||||
|
@ -45,10 +51,10 @@ public class ModManifestAdapter extends RecyclerView.Adapter<ModManifestAdapter.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
return modList.size();
|
return model.getModList().size();
|
||||||
}
|
}
|
||||||
|
|
||||||
static class ViewHolder extends RecyclerView.ViewHolder{
|
class ViewHolder extends RecyclerView.ViewHolder{
|
||||||
private String modPath;
|
private String modPath;
|
||||||
void setModPath(String modPath) {
|
void setModPath(String modPath) {
|
||||||
this.modPath = modPath;
|
this.modPath = modPath;
|
||||||
|
@ -75,7 +81,15 @@ public class ModManifestAdapter extends RecyclerView.Adapter<ModManifestAdapter.
|
||||||
if (which == DialogAction.POSITIVE) {
|
if (which == DialogAction.POSITIVE) {
|
||||||
File file = new File(modPath);
|
File file = new File(modPath);
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
file.delete();
|
try {
|
||||||
|
FileUtils.forceDelete(file);
|
||||||
|
List<Integer> removed = model.removeAll(entry -> StringUtils.equals(entry.getAssetPath(), modPath));
|
||||||
|
for (int idx : removed) {
|
||||||
|
notifyItemRemoved(idx);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
CommonLogic.showAlertDialog(itemView, R.string.error, e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
package com.zane.smapiinstaller.ui.download;
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.OnClick;
|
||||||
|
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.zane.smapiinstaller.R;
|
||||||
|
import com.zane.smapiinstaller.entity.DownloadableContent;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link RecyclerView.Adapter} that can display a {@link DownloadableContent}
|
||||||
|
*/
|
||||||
|
public class DownloadableContentAdapter extends RecyclerView.Adapter<DownloadableContentAdapter.ViewHolder> {
|
||||||
|
|
||||||
|
private final List<DownloadableContent> downloadableContentList;
|
||||||
|
|
||||||
|
public DownloadableContentAdapter(List<DownloadableContent> items) {
|
||||||
|
downloadableContentList = items;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
View view = LayoutInflater.from(parent.getContext())
|
||||||
|
.inflate(R.layout.download_content_item, parent, false);
|
||||||
|
return new ViewHolder(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(final ViewHolder holder, int position) {
|
||||||
|
holder.downloadableContent = downloadableContentList.get(position);
|
||||||
|
holder.typeTextView.setText(holder.downloadableContent.getType());
|
||||||
|
holder.nameTextView.setText(holder.downloadableContent.getName());
|
||||||
|
holder.descriptionTextView.setText(holder.downloadableContent.getDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return downloadableContentList.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
@BindView(R.id.text_item_type)
|
||||||
|
TextView typeTextView;
|
||||||
|
@BindView(R.id.text_item_name)
|
||||||
|
TextView nameTextView;
|
||||||
|
@BindView(R.id.text_item_description)
|
||||||
|
TextView descriptionTextView;
|
||||||
|
public DownloadableContent downloadableContent;
|
||||||
|
|
||||||
|
public ViewHolder(View view) {
|
||||||
|
super(view);
|
||||||
|
ButterKnife.bind(this, itemView);
|
||||||
|
}
|
||||||
|
@OnClick(R.id.button_remove_content) void removeContent() {
|
||||||
|
|
||||||
|
}
|
||||||
|
@OnClick(R.id.button_download_content) void downloadContent() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package com.zane.smapiinstaller.ui.download;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import com.zane.smapiinstaller.R;
|
||||||
|
import com.zane.smapiinstaller.entity.DownloadableContentList;
|
||||||
|
import com.zane.smapiinstaller.logic.CommonLogic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A fragment representing a list of Items.
|
||||||
|
*/
|
||||||
|
public class DownloadableContentFragment extends Fragment {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mandatory empty constructor for the fragment manager to instantiate the
|
||||||
|
* fragment (e.g. upon screen orientation changes).
|
||||||
|
*/
|
||||||
|
public DownloadableContentFragment() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
View view = inflater.inflate(R.layout.fragment_download_content_list, container, false);
|
||||||
|
|
||||||
|
// Set the adapter
|
||||||
|
if (view instanceof RecyclerView) {
|
||||||
|
Context context = view.getContext();
|
||||||
|
RecyclerView recyclerView = (RecyclerView) view;
|
||||||
|
recyclerView.setLayoutManager(new LinearLayoutManager(context));
|
||||||
|
DownloadableContentList contentList = CommonLogic.getAssetJson(context, "downloadable_content_list.json", DownloadableContentList.class);
|
||||||
|
recyclerView.setAdapter(new DownloadableContentAdapter(contentList.getContents()));
|
||||||
|
recyclerView.addItemDecoration(new DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL));
|
||||||
|
}
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,6 +13,8 @@ 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;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
|
@ -52,12 +54,12 @@ public class InstallFragment extends Fragment {
|
||||||
CommonLogic.setProgressDialogState(root, dialog, R.string.extracting_package, 0);
|
CommonLogic.setProgressDialogState(root, dialog, R.string.extracting_package, 0);
|
||||||
String path = patcher.extract();
|
String path = patcher.extract();
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
CommonLogic.showAlertDialog(root, R.string.error, R.string.error_game_not_found);
|
CommonLogic.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.error_game_not_found)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CommonLogic.setProgressDialogState(root, dialog, R.string.unpacking_smapi_files, 10);
|
CommonLogic.setProgressDialogState(root, dialog, R.string.unpacking_smapi_files, 10);
|
||||||
if (!CommonLogic.unpackSmapiFiles(context, path, false)) {
|
if (!CommonLogic.unpackSmapiFiles(context, path, false)) {
|
||||||
CommonLogic.showAlertDialog(root, R.string.error, R.string.failed_to_unpack_smapi_files);
|
CommonLogic.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_unpack_smapi_files)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ModAssetsManager modAssetsManager = new ModAssetsManager(root);
|
ModAssetsManager modAssetsManager = new ModAssetsManager(root);
|
||||||
|
@ -65,13 +67,13 @@ public class InstallFragment extends Fragment {
|
||||||
modAssetsManager.installDefaultMods();
|
modAssetsManager.installDefaultMods();
|
||||||
CommonLogic.setProgressDialogState(root, dialog, R.string.patching_package, 25);
|
CommonLogic.setProgressDialogState(root, dialog, R.string.patching_package, 25);
|
||||||
if (!patcher.patch(path)) {
|
if (!patcher.patch(path)) {
|
||||||
CommonLogic.showAlertDialog(root, R.string.error, R.string.failed_to_patch_game);
|
CommonLogic.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_patch_game)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CommonLogic.setProgressDialogState(root, dialog, R.string.signing_package, 55);
|
CommonLogic.setProgressDialogState(root, dialog, R.string.signing_package, 55);
|
||||||
String signPath = patcher.sign(path);
|
String signPath = patcher.sign(path);
|
||||||
if (signPath == null) {
|
if (signPath == null) {
|
||||||
CommonLogic.showAlertDialog(root, R.string.error, R.string.failed_to_sign_game);
|
CommonLogic.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_sign_game)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CommonLogic.setProgressDialogState(root, dialog, R.string.installing_package, 99);
|
CommonLogic.setProgressDialogState(root, dialog, R.string.installing_package, 99);
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<solid android:color="#eee"/>
|
||||||
|
<size android:width="1dp"/>
|
||||||
|
</shape>
|
|
@ -0,0 +1,59 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_marginEnd="40dp"
|
||||||
|
android:divider="@drawable/horizontal_divider"
|
||||||
|
android:showDividers="middle"
|
||||||
|
android:orientation="vertical">
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:divider="@drawable/vertical_divider"
|
||||||
|
android:showDividers="middle"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_item_type"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:paddingRight="5dp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_item_name"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:paddingLeft="5dp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_item_description"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:orientation="vertical">
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button_remove_content"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:background="@android:drawable/ic_menu_delete" />
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button_download_content"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:background="@android:drawable/ic_menu_add" />
|
||||||
|
</LinearLayout>
|
||||||
|
</RelativeLayout>
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/list"
|
||||||
|
android:name="com.zane.smapiinstaller.ui.download.DownloadContentFragment"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginRight="16dp"
|
||||||
|
app:layoutManager="LinearLayoutManager"
|
||||||
|
tools:context=".ui.download.DownloadableContentFragment"
|
||||||
|
tools:listitem="@layout/download_content_item" />
|
|
@ -3,13 +3,13 @@
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="wrap_content">
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentStart="true"
|
android:layout_alignParentStart="true"
|
||||||
android:layout_marginEnd="80dp"
|
android:layout_marginEnd="40dp"
|
||||||
android:divider="@drawable/divider"
|
android:divider="@drawable/horizontal_divider"
|
||||||
android:showDividers="middle"
|
android:showDividers="middle"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
<TextView
|
<TextView
|
||||||
|
@ -30,7 +30,8 @@
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentEnd="true">
|
android:layout_alignParentEnd="true"
|
||||||
|
android:orientation="vertical">
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/button_remove_mod"
|
android:id="@+id/button_remove_mod"
|
||||||
android:layout_width="32dp"
|
android:layout_width="32dp"
|
||||||
|
|
|
@ -12,6 +12,10 @@
|
||||||
android:id="@+id/nav_config"
|
android:id="@+id/nav_config"
|
||||||
android:icon="@android:drawable/ic_menu_edit"
|
android:icon="@android:drawable/ic_menu_edit"
|
||||||
android:title="@string/menu_config" />
|
android:title="@string/menu_config" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/nav_download"
|
||||||
|
android:icon="@android:drawable/ic_dialog_dialer"
|
||||||
|
android:title="@string/menu_download" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/nav_help"
|
android:id="@+id/nav_help"
|
||||||
android:icon="@android:drawable/ic_menu_help"
|
android:icon="@android:drawable/ic_menu_help"
|
||||||
|
|
|
@ -39,4 +39,9 @@
|
||||||
<argument android:name="editable" android:defaultValue="true" app:argType="boolean"/>
|
<argument android:name="editable" android:defaultValue="true" app:argType="boolean"/>
|
||||||
</action>
|
</action>
|
||||||
</fragment>
|
</fragment>
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/nav_download"
|
||||||
|
android:name="com.zane.smapiinstaller.ui.download.DownloadableContentFragment"
|
||||||
|
android:label="@string/menu_download"
|
||||||
|
tools:layout="@layout/fragment_download_content_list" />
|
||||||
</navigation>
|
</navigation>
|
|
@ -40,4 +40,8 @@
|
||||||
<string name="test_message">暂处于内测阶段, Q群:860453392</string>
|
<string name="test_message">暂处于内测阶段, Q群:860453392</string>
|
||||||
<string name="button_logs">日志</string>
|
<string name="button_logs">日志</string>
|
||||||
<string name="smapi_game_name">SMAPI星露谷物语</string>
|
<string name="smapi_game_name">SMAPI星露谷物语</string>
|
||||||
|
<string name="error_failed_to_create_file">无法创建以下文件: %s</string>
|
||||||
|
<string name="failed_to_process_manifest">无法处理AndroidManifest.xml文件</string>
|
||||||
|
<string name="error_no_supported_game_version">游戏版本不支持,请更新版本或者下载兼容包</string>
|
||||||
|
<string name="menu_download">下载</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -5,4 +5,5 @@
|
||||||
<dimen name="nav_header_vertical_spacing">8dp</dimen>
|
<dimen name="nav_header_vertical_spacing">8dp</dimen>
|
||||||
<dimen name="nav_header_height">176dp</dimen>
|
<dimen name="nav_header_height">176dp</dimen>
|
||||||
<dimen name="fab_margin">16dp</dimen>
|
<dimen name="fab_margin">16dp</dimen>
|
||||||
|
<dimen name="text_margin">16dp</dimen>
|
||||||
</resources>
|
</resources>
|
|
@ -40,4 +40,8 @@
|
||||||
<string name="test_message">Still at test stage now, not release yet.</string>
|
<string name="test_message">Still at test stage now, not release yet.</string>
|
||||||
<string name="button_logs">Logs</string>
|
<string name="button_logs">Logs</string>
|
||||||
<string name="smapi_game_name">SMAPI Stardew Valley</string>
|
<string name="smapi_game_name">SMAPI Stardew Valley</string>
|
||||||
|
<string name="error_failed_to_create_file">Failed to create target file : %s</string>
|
||||||
|
<string name="failed_to_process_manifest">Failed to process AndroidManifest.xml file</string>
|
||||||
|
<string name="error_no_supported_game_version">Game version not supported, upgrade or download compat package first</string>
|
||||||
|
<string name="menu_download">Download</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in New Issue