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'
|
||||
// https://mvnrepository.com/artifact/com.jakewharton/butterknife
|
||||
implementation 'com.jakewharton:butterknife:10.2.1'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||
annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.1'
|
||||
// https://mvnrepository.com/artifact/com.google.guava/guava
|
||||
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'
|
||||
// https://mvnrepository.com/artifact/com.google.code.gson/gson
|
||||
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'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
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 ----------
|
||||
#okhttp
|
||||
-dontwarn okhttp3.**
|
||||
-keep class okhttp3.**{*;}
|
||||
|
||||
#okio
|
||||
-dontwarn okio.**
|
||||
-keep class okio.**{*;}
|
||||
|
||||
-keep class com.zane.** { *; }
|
||||
-keep class pxb.android.** { *; }
|
||||
|
|
|
@ -2,12 +2,14 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
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.REQUEST_INSTALL_PACKAGES"/>
|
||||
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
|
||||
<application
|
||||
android:name=".MainApplication"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
|
|
|
@ -1,62 +1,67 @@
|
|||
[
|
||||
{
|
||||
"targetPath": "classes.dex",
|
||||
"assetPath": "apk/classes.dex",
|
||||
"compression": 8
|
||||
},
|
||||
{
|
||||
"targetPath": "res/mipmap-mdpi-v4/ic_launcher_foreground.png",
|
||||
"assetPath": "apk/ic_launcher_foreground.png",
|
||||
"compression": 8
|
||||
},
|
||||
{
|
||||
"targetPath": "res/mipmap-hdpi-v4/ic_launcher_foreground.png",
|
||||
"assetPath": "apk/ic_launcher_foreground.png",
|
||||
"compression": 8
|
||||
},
|
||||
{
|
||||
"targetPath": "res/mipmap-xhdpi-v4/ic_launcher_foreground.png",
|
||||
"assetPath": "apk/ic_launcher_foreground.png",
|
||||
"compression": 8
|
||||
},
|
||||
{
|
||||
"targetPath": "res/mipmap-xxhdpi-v4/ic_launcher_foreground.png",
|
||||
"assetPath": "apk/ic_launcher_foreground.png",
|
||||
"compression": 8
|
||||
},
|
||||
{
|
||||
"targetPath": "res/mipmap-xxxhdpi-v4/ic_launcher_foreground.png",
|
||||
"assetPath": "apk/ic_launcher_foreground.png",
|
||||
"compression": 8
|
||||
},
|
||||
{
|
||||
"targetPath": "assemblies/Newtonsoft.Json.dll",
|
||||
"assetPath": "apk/Newtonsoft.Json.dll",
|
||||
"compression": 0
|
||||
},
|
||||
{
|
||||
"targetPath": "assemblies/SMAPI.Toolkit.CoreInterfaces.dll",
|
||||
"assetPath": "apk/SMAPI.Toolkit.CoreInterfaces.dll",
|
||||
"compression": 0
|
||||
},
|
||||
{
|
||||
"targetPath": "assemblies/SMAPI.Toolkit.dll",
|
||||
"assetPath": "apk/SMAPI.Toolkit.dll",
|
||||
"compression": 0
|
||||
},
|
||||
{
|
||||
"targetPath": "assemblies/StardewModdingAPI.dll",
|
||||
"assetPath": "apk/StardewModdingAPI.dll",
|
||||
"compression": 0
|
||||
},
|
||||
{
|
||||
"targetPath": "assemblies/System.Data.dll",
|
||||
"assetPath": "apk/System.Data.dll",
|
||||
"compression": 0
|
||||
},
|
||||
{
|
||||
"targetPath": "assemblies/System.Numerics.dll",
|
||||
"assetPath": "apk/System.Numerics.dll",
|
||||
"compression": 0
|
||||
}
|
||||
]
|
||||
{
|
||||
"minBuildCode": 138,
|
||||
"maxBuildCode": null,
|
||||
"withMonoMod": true,
|
||||
"manifestEntries": [
|
||||
{
|
||||
"targetPath": "classes.dex",
|
||||
"assetPath": "apk/classes.dex",
|
||||
"compression": 8
|
||||
},
|
||||
{
|
||||
"targetPath": "res/mipmap-mdpi-v4/ic_launcher_foreground.png",
|
||||
"assetPath": "apk/ic_launcher_foreground.png",
|
||||
"compression": 8
|
||||
},
|
||||
{
|
||||
"targetPath": "res/mipmap-hdpi-v4/ic_launcher_foreground.png",
|
||||
"assetPath": "apk/ic_launcher_foreground.png",
|
||||
"compression": 8
|
||||
},
|
||||
{
|
||||
"targetPath": "res/mipmap-xhdpi-v4/ic_launcher_foreground.png",
|
||||
"assetPath": "apk/ic_launcher_foreground.png",
|
||||
"compression": 8
|
||||
},
|
||||
{
|
||||
"targetPath": "res/mipmap-xxhdpi-v4/ic_launcher_foreground.png",
|
||||
"assetPath": "apk/ic_launcher_foreground.png",
|
||||
"compression": 8
|
||||
},
|
||||
{
|
||||
"targetPath": "res/mipmap-xxxhdpi-v4/ic_launcher_foreground.png",
|
||||
"assetPath": "apk/ic_launcher_foreground.png",
|
||||
"compression": 8
|
||||
},
|
||||
{
|
||||
"targetPath": "assemblies/Newtonsoft.Json.dll",
|
||||
"assetPath": "apk/Newtonsoft.Json.dll",
|
||||
"compression": 0
|
||||
},
|
||||
{
|
||||
"targetPath": "assemblies/SMAPI.Toolkit.CoreInterfaces.dll",
|
||||
"assetPath": "apk/SMAPI.Toolkit.CoreInterfaces.dll",
|
||||
"compression": 0
|
||||
},
|
||||
{
|
||||
"targetPath": "assemblies/SMAPI.Toolkit.dll",
|
||||
"assetPath": "apk/SMAPI.Toolkit.dll",
|
||||
"compression": 0
|
||||
},
|
||||
{
|
||||
"targetPath": "assemblies/StardewModdingAPI.dll",
|
||||
"assetPath": "apk/StardewModdingAPI.dll",
|
||||
"compression": 0
|
||||
},
|
||||
{
|
||||
"targetPath": "assemblies/System.Data.dll",
|
||||
"assetPath": "apk/System.Data.dll",
|
||||
"compression": 0
|
||||
},
|
||||
{
|
||||
"targetPath": "assemblies/System.Numerics.dll",
|
||||
"assetPath": "apk/System.Numerics.dll",
|
||||
"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",
|
||||
"Name": "VirtualKeyboard",
|
||||
"UniqueID": "SMAPI.VirtualKeyboard"
|
||||
"UniqueID": "VirtualKeyboard"
|
||||
},
|
||||
{
|
||||
"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;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ManifestEntry {
|
||||
private String targetPath;
|
||||
private String assetPath;
|
||||
private int compression;
|
||||
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;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ModManifestEntry {
|
||||
private String assetPath;
|
||||
private String Name;
|
||||
private String UniqueID;
|
||||
private String Description;
|
||||
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 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.gson.reflect.TypeToken;
|
||||
import com.zane.smapiinstaller.BuildConfig;
|
||||
import com.zane.smapiinstaller.R;
|
||||
import com.zane.smapiinstaller.constant.Constants;
|
||||
import com.zane.smapiinstaller.entity.ApkFilesManifest;
|
||||
import com.zane.smapiinstaller.entity.ManifestEntry;
|
||||
|
||||
import net.fornwall.apksigner.KeyStoreFileManager;
|
||||
import net.fornwall.apksigner.ZipSigner;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.zeroturnaround.zip.ByteSource;
|
||||
import org.zeroturnaround.zip.ZipEntrySource;
|
||||
import org.zeroturnaround.zip.ZipUtil;
|
||||
|
@ -29,7 +33,6 @@ import java.io.File;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.KeyStore;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
|
@ -48,6 +51,8 @@ public class ApkPatcher {
|
|||
|
||||
private static final String TAG = "PATCHER";
|
||||
|
||||
private AtomicReference<String> errorMessage = new AtomicReference<>();
|
||||
|
||||
public ApkPatcher(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
@ -56,8 +61,10 @@ public class ApkPatcher {
|
|||
PackageManager packageManager = context.getPackageManager();
|
||||
List<String> packageNames = CommonLogic.getAssetJson(context, "package_names.json", new TypeToken<List<String>>() {
|
||||
}.getType());
|
||||
if (packageNames == null)
|
||||
if (packageNames == null) {
|
||||
errorMessage.set(context.getString(R.string.error_game_not_found));
|
||||
return null;
|
||||
}
|
||||
for (String packageName : packageNames) {
|
||||
try {
|
||||
PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0);
|
||||
|
@ -69,6 +76,7 @@ public class ApkPatcher {
|
|||
File dest = new File(externalFilesDir.getAbsolutePath() + "/SMAPI Installer/");
|
||||
if (!dest.exists()) {
|
||||
if (!dest.mkdir()) {
|
||||
errorMessage.set(String.format(context.getString(R.string.error_failed_to_create_file), dest.getAbsolutePath()));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -78,6 +86,7 @@ public class ApkPatcher {
|
|||
}
|
||||
} catch (PackageManager.NameNotFoundException | IOException e) {
|
||||
Log.e(TAG, "Extract error", e);
|
||||
errorMessage.set(e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
@ -90,29 +99,36 @@ public class ApkPatcher {
|
|||
if (!file.exists())
|
||||
return false;
|
||||
try {
|
||||
List<ManifestEntry> manifestEntries = CommonLogic.getAssetJson(context, "apk_files_manifest.json", new TypeToken<List<ManifestEntry>>() {
|
||||
}.getType());
|
||||
if (manifestEntries == null)
|
||||
ApkFilesManifest apkFilesManifest = CommonLogic.getAssetJson(context, "apk_files_manifest.json", ApkFilesManifest.class);
|
||||
if (apkFilesManifest == null)
|
||||
return false;
|
||||
List<ManifestEntry> manifestEntries = apkFilesManifest.getManifestEntries();
|
||||
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[] 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) {
|
||||
errorMessage.set(context.getString(R.string.failed_to_process_manifest));
|
||||
return false;
|
||||
}
|
||||
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]));
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Patch error", e);
|
||||
errorMessage.set(e.getLocalizedMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private byte[] modifyManifest(byte[] bytes) {
|
||||
private byte[] modifyManifest(byte[] bytes, List<ApkFilesManifest> manifests) {
|
||||
AtomicReference<String> packageName = new AtomicReference<>();
|
||||
Predicate<ManifestTagVisitor.AttrArgs> processLogic = (attr) -> {
|
||||
if (attr.type == NodeVisitor.TYPE_STRING) {
|
||||
|
@ -140,9 +156,32 @@ public class ApkPatcher {
|
|||
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 CommonLogic.modifyManifest(bytes, processLogic);
|
||||
try {
|
||||
return CommonLogic.modifyManifest(bytes, processLogic);
|
||||
}catch (Exception e) {
|
||||
errorMessage.set(e.getLocalizedMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String sign(String apkPath) {
|
||||
|
@ -156,16 +195,14 @@ public class ApkPatcher {
|
|||
}
|
||||
String alias = ks.aliases().nextElement();
|
||||
X509Certificate publicKey = (X509Certificate) ks.getCertificate(alias);
|
||||
try {
|
||||
PrivateKey privateKey = (PrivateKey) ks.getKey(alias, "android".toCharArray());
|
||||
ZipSigner.signZip(publicKey, privateKey, "SHA1withRSA", apkPath, signApkPath);
|
||||
new File(apkPath).delete();
|
||||
return signApkPath;
|
||||
} catch (NoSuchAlgorithmException ignored) {
|
||||
}
|
||||
PrivateKey privateKey = (PrivateKey) ks.getKey(alias, "android".toCharArray());
|
||||
ZipSigner.signZip(publicKey, privateKey, "SHA1withRSA", apkPath, signApkPath);
|
||||
new File(apkPath).delete();
|
||||
return signApkPath;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Sign error", e);
|
||||
errorMessage.set(e.getLocalizedMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -179,6 +216,7 @@ public class ApkPatcher {
|
|||
context.startActivity(intent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.e(TAG, "Install error", e);
|
||||
errorMessage.set(e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -196,4 +234,7 @@ public class ApkPatcher {
|
|||
return Uri.fromFile(file);
|
||||
}
|
||||
|
||||
public AtomicReference<String> getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,6 +59,17 @@ public class CommonLogic {
|
|||
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) {
|
||||
try {
|
||||
InputStream inputStream = context.getAssets().open(filename);
|
||||
|
@ -177,25 +188,16 @@ public class CommonLogic {
|
|||
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);
|
||||
AxmlWriter writer = new AxmlWriter();
|
||||
try {
|
||||
reader.accept(new AxmlVisitor(writer) {
|
||||
@Override
|
||||
public NodeVisitor child(String ns, String name) {
|
||||
NodeVisitor child = super.child(ns, name);
|
||||
return new ManifestTagVisitor(child, processLogic);
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
return writer.toByteArray();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
reader.accept(new AxmlVisitor(writer) {
|
||||
@Override
|
||||
public NodeVisitor child(String ns, String name) {
|
||||
NodeVisitor child = super.child(ns, name);
|
||||
return new ManifestTagVisitor(child, processLogic);
|
||||
}
|
||||
});
|
||||
return writer.toByteArray();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,8 +26,11 @@ public class GameLauncher {
|
|||
CommonLogic.showAlertDialog(root, R.string.error, R.string.error_failed_to_repair);
|
||||
return;
|
||||
}
|
||||
Intent intent = packageManager.getLaunchIntentForPackage(Constants.TARGET_PACKAGE_NAME);
|
||||
context.startActivity(intent);
|
||||
ModAssetsManager modAssetsManager = new ModAssetsManager(root);
|
||||
if(modAssetsManager.checkModEnvironment()) {
|
||||
Intent intent = packageManager.getLaunchIntentForPackage(Constants.TARGET_PACKAGE_NAME);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException ignored) {
|
||||
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.entity.ModManifestEntry;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.zeroturnaround.zip.NameMapper;
|
||||
import org.zeroturnaround.zip.ZipUtil;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -23,6 +25,7 @@ import java.io.IOException;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public class ModAssetsManager {
|
||||
|
||||
|
@ -43,7 +46,7 @@ public class ModAssetsManager {
|
|||
if(currentFile != null && currentFile.exists()) {
|
||||
boolean foundManifest = false;
|
||||
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());
|
||||
foundManifest = true;
|
||||
if(manifest != null) {
|
||||
|
@ -70,7 +73,7 @@ public class ModAssetsManager {
|
|||
File modFolder = new File(Environment.getExternalStorageDirectory(), Constants.MOD_PATH);
|
||||
ImmutableListMultimap<String, ModManifestEntry> installedModMap = Multimaps.index(findAllInstalledMods(), ModManifestEntry::getUniqueID);
|
||||
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());
|
||||
if(installedMods.size() > 1) {
|
||||
CommonLogic.showAlertDialog(root, R.string.error,
|
||||
|
@ -79,7 +82,7 @@ public class ModAssetsManager {
|
|||
return false;
|
||||
}
|
||||
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) {
|
||||
Log.e(TAG, "Install Mod Error", e);
|
||||
}
|
||||
|
@ -94,4 +97,8 @@ public class ModAssetsManager {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean checkModEnvironment() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ public class ConfigEditFragment extends Fragment {
|
|||
}
|
||||
}
|
||||
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 {
|
||||
|
||||
private ConfigViewModel configViewModel;
|
||||
|
||||
@BindView(R.id.view_mod_list)
|
||||
RecyclerView recyclerView;
|
||||
|
@ -26,13 +25,11 @@ public class ConfigFragment extends Fragment {
|
|||
ViewGroup container, Bundle savedInstanceState) {
|
||||
View root = inflater.inflate(R.layout.fragment_config, container, false);
|
||||
ButterKnife.bind(this, root);
|
||||
configViewModel = new ConfigViewModel(root);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(this.getContext()));
|
||||
recyclerView.setAdapter(new ModManifestAdapter(configViewModel.getModList().getValue()));
|
||||
recyclerView.addItemDecoration(new DividerItemDecoration(this.getContext(), DividerItemDecoration.VERTICAL));
|
||||
configViewModel.getModList().observe(getViewLifecycleOwner(), modList -> {
|
||||
recyclerView.getAdapter().notifyDataSetChanged();
|
||||
});
|
||||
ConfigViewModel configViewModel = new ConfigViewModel(root);
|
||||
ModManifestAdapter modManifestAdapter = new ModManifestAdapter(configViewModel);
|
||||
recyclerView.setAdapter(modManifestAdapter);
|
||||
recyclerView.addItemDecoration(new DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL));
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +1,55 @@
|
|||
package com.zane.smapiinstaller.ui.config;
|
||||
|
||||
import android.os.FileObserver;
|
||||
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.logic.ModAssetsManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
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);
|
||||
this.modList = new MutableLiveData<>();
|
||||
List<ModManifestEntry> entryList = manager.findAllInstalledMods();
|
||||
Collections.sort(entryList, (a, b)-> a.getName().compareTo(b.getName()));
|
||||
this.modList.setValue(entryList);
|
||||
this.modList = manager.findAllInstalledMods();
|
||||
Collections.sort(this.modList, (a, b)-> a.getName().compareTo(b.getName()));
|
||||
}
|
||||
|
||||
public MutableLiveData<List<ModManifestEntry>> getModList() {
|
||||
@NonNull
|
||||
public List<ModManifestEntry> getModList() {
|
||||
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 com.afollestad.materialdialogs.DialogAction;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.zane.smapiinstaller.R;
|
||||
import com.zane.smapiinstaller.entity.ModManifestEntry;
|
||||
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.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
@ -23,21 +28,22 @@ import butterknife.ButterKnife;
|
|||
import butterknife.OnClick;
|
||||
|
||||
public class ModManifestAdapter extends RecyclerView.Adapter<ModManifestAdapter.ViewHolder> {
|
||||
private List<ModManifestEntry> modList;
|
||||
private ConfigViewModel model;
|
||||
|
||||
public ModManifestAdapter(List<ModManifestEntry> modList){
|
||||
this.modList=modList;
|
||||
public ModManifestAdapter(ConfigViewModel model){
|
||||
this.model=model;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
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.modDescription.setText(mod.getDescription());
|
||||
holder.setModPath(mod.getAssetPath());
|
||||
|
@ -45,10 +51,10 @@ public class ModManifestAdapter extends RecyclerView.Adapter<ModManifestAdapter.
|
|||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return modList.size();
|
||||
return model.getModList().size();
|
||||
}
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder{
|
||||
class ViewHolder extends RecyclerView.ViewHolder{
|
||||
private String modPath;
|
||||
void setModPath(String modPath) {
|
||||
this.modPath = modPath;
|
||||
|
@ -75,7 +81,15 @@ public class ModManifestAdapter extends RecyclerView.Adapter<ModManifestAdapter.
|
|||
if (which == DialogAction.POSITIVE) {
|
||||
File file = new File(modPath);
|
||||
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.ModAssetsManager;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import butterknife.ButterKnife;
|
||||
|
@ -52,12 +54,12 @@ public class InstallFragment extends Fragment {
|
|||
CommonLogic.setProgressDialogState(root, dialog, R.string.extracting_package, 0);
|
||||
String path = patcher.extract();
|
||||
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;
|
||||
}
|
||||
CommonLogic.setProgressDialogState(root, dialog, R.string.unpacking_smapi_files, 10);
|
||||
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;
|
||||
}
|
||||
ModAssetsManager modAssetsManager = new ModAssetsManager(root);
|
||||
|
@ -65,13 +67,13 @@ public class InstallFragment extends Fragment {
|
|||
modAssetsManager.installDefaultMods();
|
||||
CommonLogic.setProgressDialogState(root, dialog, R.string.patching_package, 25);
|
||||
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;
|
||||
}
|
||||
CommonLogic.setProgressDialogState(root, dialog, R.string.signing_package, 55);
|
||||
String signPath = patcher.sign(path);
|
||||
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;
|
||||
}
|
||||
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:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="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="80dp"
|
||||
android:divider="@drawable/divider"
|
||||
android:layout_marginEnd="40dp"
|
||||
android:divider="@drawable/horizontal_divider"
|
||||
android:showDividers="middle"
|
||||
android:orientation="vertical">
|
||||
<TextView
|
||||
|
@ -30,7 +30,8 @@
|
|||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true">
|
||||
android:layout_alignParentEnd="true"
|
||||
android:orientation="vertical">
|
||||
<Button
|
||||
android:id="@+id/button_remove_mod"
|
||||
android:layout_width="32dp"
|
||||
|
|
|
@ -12,6 +12,10 @@
|
|||
android:id="@+id/nav_config"
|
||||
android:icon="@android:drawable/ic_menu_edit"
|
||||
android:title="@string/menu_config" />
|
||||
<item
|
||||
android:id="@+id/nav_download"
|
||||
android:icon="@android:drawable/ic_dialog_dialer"
|
||||
android:title="@string/menu_download" />
|
||||
<item
|
||||
android:id="@+id/nav_help"
|
||||
android:icon="@android:drawable/ic_menu_help"
|
||||
|
|
|
@ -39,4 +39,9 @@
|
|||
<argument android:name="editable" android:defaultValue="true" app:argType="boolean"/>
|
||||
</action>
|
||||
</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>
|
|
@ -40,4 +40,8 @@
|
|||
<string name="test_message">暂处于内测阶段, Q群:860453392</string>
|
||||
<string name="button_logs">日志</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>
|
||||
|
|
|
@ -5,4 +5,5 @@
|
|||
<dimen name="nav_header_vertical_spacing">8dp</dimen>
|
||||
<dimen name="nav_header_height">176dp</dimen>
|
||||
<dimen name="fab_margin">16dp</dimen>
|
||||
<dimen name="text_margin">16dp</dimen>
|
||||
</resources>
|
|
@ -40,4 +40,8 @@
|
|||
<string name="test_message">Still at test stage now, not release yet.</string>
|
||||
<string name="button_logs">Logs</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>
|
||||
|
|
Loading…
Reference in New Issue