parent
97e62a4c8f
commit
1fc03d9437
|
@ -9,8 +9,8 @@ android {
|
|||
applicationId "com.zane.smapiinstaller"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 28
|
||||
versionCode 1
|
||||
versionName "1.0.1"
|
||||
versionCode 3
|
||||
versionName "1.1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
multiDexEnabled true
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
|
|
Binary file not shown.
|
@ -1,67 +1,79 @@
|
|||
{
|
||||
"minBuildCode": 138,
|
||||
"maxBuildCode": null,
|
||||
"withMonoMod": true,
|
||||
"basePath": "",
|
||||
"manifestEntries": [
|
||||
{
|
||||
"targetPath": "classes.dex",
|
||||
"assetPath": "apk/classes.dex",
|
||||
"compression": 8
|
||||
"compression": 8,
|
||||
"external": false
|
||||
},
|
||||
{
|
||||
"targetPath": "res/mipmap-mdpi-v4/ic_launcher_foreground.png",
|
||||
"assetPath": "apk/ic_launcher_foreground.png",
|
||||
"compression": 8
|
||||
"compression": 8,
|
||||
"external": false
|
||||
},
|
||||
{
|
||||
"targetPath": "res/mipmap-hdpi-v4/ic_launcher_foreground.png",
|
||||
"assetPath": "apk/ic_launcher_foreground.png",
|
||||
"compression": 8
|
||||
"compression": 8,
|
||||
"external": false
|
||||
},
|
||||
{
|
||||
"targetPath": "res/mipmap-xhdpi-v4/ic_launcher_foreground.png",
|
||||
"assetPath": "apk/ic_launcher_foreground.png",
|
||||
"compression": 8
|
||||
"compression": 8,
|
||||
"external": false
|
||||
},
|
||||
{
|
||||
"targetPath": "res/mipmap-xxhdpi-v4/ic_launcher_foreground.png",
|
||||
"assetPath": "apk/ic_launcher_foreground.png",
|
||||
"compression": 8
|
||||
"compression": 8,
|
||||
"external": false
|
||||
},
|
||||
{
|
||||
"targetPath": "res/mipmap-xxxhdpi-v4/ic_launcher_foreground.png",
|
||||
"assetPath": "apk/ic_launcher_foreground.png",
|
||||
"compression": 8
|
||||
"compression": 8,
|
||||
"external": false
|
||||
},
|
||||
{
|
||||
"targetPath": "assemblies/Newtonsoft.Json.dll",
|
||||
"assetPath": "apk/Newtonsoft.Json.dll",
|
||||
"compression": 0
|
||||
"compression": 0,
|
||||
"external": false
|
||||
},
|
||||
{
|
||||
"targetPath": "assemblies/SMAPI.Toolkit.CoreInterfaces.dll",
|
||||
"assetPath": "apk/SMAPI.Toolkit.CoreInterfaces.dll",
|
||||
"compression": 0
|
||||
"compression": 0,
|
||||
"external": false
|
||||
},
|
||||
{
|
||||
"targetPath": "assemblies/SMAPI.Toolkit.dll",
|
||||
"assetPath": "apk/SMAPI.Toolkit.dll",
|
||||
"compression": 0
|
||||
"compression": 0,
|
||||
"external": false
|
||||
},
|
||||
{
|
||||
"targetPath": "assemblies/StardewModdingAPI.dll",
|
||||
"assetPath": "apk/StardewModdingAPI.dll",
|
||||
"compression": 0
|
||||
"compression": 0,
|
||||
"external": false
|
||||
},
|
||||
{
|
||||
"targetPath": "assemblies/System.Data.dll",
|
||||
"assetPath": "apk/System.Data.dll",
|
||||
"compression": 0
|
||||
"compression": 0,
|
||||
"external": false
|
||||
},
|
||||
{
|
||||
"targetPath": "assemblies/System.Numerics.dll",
|
||||
"assetPath": "apk/System.Numerics.dll",
|
||||
"compression": 0
|
||||
"compression": 0,
|
||||
"external": false
|
||||
}
|
||||
]
|
||||
}
|
|
@ -3,15 +3,18 @@
|
|||
"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"
|
||||
"name": "SMAPI for 1.4.5.137",
|
||||
"assetPath": "compat/137/",
|
||||
"description": "SMAPI compat package for game 1.4.4.128 - 1.4.5.137",
|
||||
"url": "http://dl.zaneyork.cn/compat/smapi_137.zip",
|
||||
"hash": "bd16e8e4cb52d636e24c6a2d2309b66a60e492d2b97c1b8f6a519c04ac42ebdc"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
"type": "LOCALE",
|
||||
"name": "中文汉化v2.4",
|
||||
"description": "简体中文语言包,感谢Wabi-Sabi提供",
|
||||
"url": "http://dl.zaneyork.cn/locale/locale_pack_zh_2.4.zip",
|
||||
"hash": "59be19240160de61046f05619f3e448d180137e0989feb537e392a014588615f"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -5,4 +5,6 @@ public class Constants {
|
|||
public static final String LOG_PATH = "StardewValley/ErrorLogs/SMAPI-latest.txt";
|
||||
|
||||
public static final String TARGET_PACKAGE_NAME = "com.zane.stardewvalley";
|
||||
|
||||
public static final String DLC_LIST_UPDATE_URL = "http://dl.zaneyork.cn/smapi/downloadable_content_list.json";
|
||||
}
|
||||
|
|
|
@ -6,7 +6,8 @@ import lombok.Data;
|
|||
|
||||
@Data
|
||||
public class ApkFilesManifest {
|
||||
private Long minBuildCode;
|
||||
private long minBuildCode;
|
||||
private Long maxBuildCode;
|
||||
private String basePath;
|
||||
private List<ManifestEntry> manifestEntries;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ import lombok.Data;
|
|||
public class DownloadableContent {
|
||||
private String type;
|
||||
private String name;
|
||||
private String assetPath;
|
||||
private String url;
|
||||
private String description;
|
||||
private String hash;
|
||||
}
|
||||
|
|
|
@ -8,4 +8,5 @@ public class ManifestEntry {
|
|||
private String assetPath;
|
||||
private int compression;
|
||||
private int origin;
|
||||
private boolean external;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ 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;
|
||||
|
@ -99,13 +98,9 @@ public class ApkPatcher {
|
|||
if (!file.exists())
|
||||
return false;
|
||||
try {
|
||||
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<>();
|
||||
byte[] manifest = ZipUtil.unpackEntry(file, "AndroidManifest.xml");
|
||||
List<ApkFilesManifest> apkFilesManifests = Lists.newArrayList(apkFilesManifest);
|
||||
List<ApkFilesManifest> apkFilesManifests = CommonLogic.findAllApkFileManifest(context);
|
||||
byte[] modifiedManifest = modifyManifest(manifest, apkFilesManifests);
|
||||
if(apkFilesManifests.size() == 0) {
|
||||
errorMessage.set(context.getString(R.string.error_no_supported_game_version));
|
||||
|
@ -116,8 +111,15 @@ public class ApkPatcher {
|
|||
return false;
|
||||
}
|
||||
zipEntrySourceList.add(new ByteSource("AndroidManifest.xml", modifiedManifest, Deflater.DEFLATED));
|
||||
ApkFilesManifest apkFilesManifest = apkFilesManifests.get(0);
|
||||
List<ManifestEntry> manifestEntries = apkFilesManifest.getManifestEntries();
|
||||
for (ManifestEntry entry : manifestEntries) {
|
||||
zipEntrySourceList.add(new ByteSource(entry.getTargetPath(), CommonLogic.getAssetBytes(context, entry.getAssetPath()), entry.getCompression()));
|
||||
if(entry.isExternal()) {
|
||||
zipEntrySourceList.add(new ByteSource(entry.getTargetPath(), CommonLogic.getAssetBytes(context, apkFilesManifest.getBasePath() + entry.getAssetPath()), entry.getCompression()));
|
||||
}
|
||||
else {
|
||||
zipEntrySourceList.add(new ByteSource(entry.getTargetPath(), CommonLogic.getAssetBytes(context, entry.getAssetPath()), entry.getCompression()));
|
||||
}
|
||||
}
|
||||
ZipUtil.addOrReplaceEntries(file, zipEntrySourceList.toArray(new ZipEntrySource[0]));
|
||||
return true;
|
||||
|
@ -160,13 +162,11 @@ public class ApkPatcher {
|
|||
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 (versionCode < manifest.getMinBuildCode()) {
|
||||
return true;
|
||||
}
|
||||
if (manifest.getMaxBuildCode() != null) {
|
||||
if (versionCode > manifest.getMinBuildCode()) {
|
||||
if (versionCode > manifest.getMaxBuildCode()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,23 +11,32 @@ import android.view.View;
|
|||
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.hash.Hashing;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.io.CharStreams;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.zane.smapiinstaller.R;
|
||||
import com.zane.smapiinstaller.entity.ApkFilesManifest;
|
||||
import com.zane.smapiinstaller.entity.ManifestEntry;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.zeroturnaround.zip.ZipUtil;
|
||||
import org.zeroturnaround.zip.commons.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import pxb.android.axml.AxmlReader;
|
||||
|
@ -48,6 +57,14 @@ public class CommonLogic {
|
|||
return null;
|
||||
}
|
||||
|
||||
public static InputStream getLocalAsset(Context context, String filename) throws IOException {
|
||||
File file = new File(context.getFilesDir(), filename);
|
||||
if(file.exists()){
|
||||
return new FileInputStream(file);
|
||||
}
|
||||
return context.getAssets().open(filename);
|
||||
}
|
||||
|
||||
public static <T> T getFileJson(File file, Type type) {
|
||||
try {
|
||||
InputStream inputStream = new FileInputStream(file);
|
||||
|
@ -59,9 +76,35 @@ public class CommonLogic {
|
|||
return null;
|
||||
}
|
||||
|
||||
public static <T> T getFileJson(File file, Class<T> tClass) {
|
||||
try {
|
||||
InputStream inputStream = new FileInputStream(file);
|
||||
try (InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
|
||||
return new Gson().fromJson(CharStreams.toString(reader), tClass);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void writeAssetJson(Context context, String filename, Object content) {
|
||||
try {
|
||||
String tmpFilename = filename + ".tmp";
|
||||
File file = new File(context.getFilesDir(), tmpFilename);
|
||||
FileOutputStream outputStream = new FileOutputStream(file);
|
||||
try (OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)) {
|
||||
writer.write(new Gson().toJson(content));
|
||||
}
|
||||
finally {
|
||||
FileUtils.moveFile(file, new File(context.getFilesDir(), filename));
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T getAssetJson(Context context, String filename, Class<T> tClass) {
|
||||
try {
|
||||
InputStream inputStream = context.getAssets().open(filename);
|
||||
InputStream inputStream = getLocalAsset(context, filename);
|
||||
try (InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
|
||||
return new Gson().fromJson(CharStreams.toString(reader), tClass);
|
||||
}
|
||||
|
@ -72,7 +115,7 @@ public class CommonLogic {
|
|||
|
||||
public static <T> T getAssetJson(Context context, String filename, Type type) {
|
||||
try {
|
||||
InputStream inputStream = context.getAssets().open(filename);
|
||||
InputStream inputStream = getLocalAsset(context, filename);
|
||||
try (InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
|
||||
return new Gson().fromJson(CharStreams.toString(reader), type);
|
||||
}
|
||||
|
@ -83,7 +126,7 @@ public class CommonLogic {
|
|||
|
||||
public static byte[] getAssetBytes(Context context, String filename) {
|
||||
try {
|
||||
try (InputStream inputStream = context.getAssets().open(filename)) {
|
||||
try (InputStream inputStream = getLocalAsset(context, filename)) {
|
||||
return ByteStreams.toByteArray(inputStream);
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
|
@ -134,6 +177,32 @@ public class CommonLogic {
|
|||
}
|
||||
}
|
||||
|
||||
public static void showConfirmDialog(View view, int title, String message, MaterialDialog.SingleButtonCallback callback) {
|
||||
Activity activity = getActivityFromView(view);
|
||||
if(activity != null && !activity.isFinishing()) {
|
||||
activity.runOnUiThread(()-> new MaterialDialog.Builder(activity).title(title).content(message).positiveText(R.string.confirm).negativeText(R.string.cancel).onAny(callback).show());
|
||||
}
|
||||
}
|
||||
|
||||
public static List<ApkFilesManifest> findAllApkFileManifest(Context context) {
|
||||
ApkFilesManifest apkFilesManifest = CommonLogic.getAssetJson(context, "apk_files_manifest.json", ApkFilesManifest.class);
|
||||
ArrayList<ApkFilesManifest> apkFilesManifests = Lists.newArrayList(apkFilesManifest);
|
||||
File compatFolder = new File(context.getFilesDir(), "compat");
|
||||
if(compatFolder.exists()) {
|
||||
for (File directory : compatFolder.listFiles(File::isDirectory)) {
|
||||
File manifestFile = new File(directory, "apk_files_manifest.json");
|
||||
if(manifestFile.exists()) {
|
||||
ApkFilesManifest manifest = getFileJson(manifestFile, ApkFilesManifest.class);
|
||||
if(manifest != null) {
|
||||
apkFilesManifests.add(manifest);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Collections.sort(apkFilesManifests, (a, b)-> Long.compare(b.getMinBuildCode(), a.getMinBuildCode()));
|
||||
return apkFilesManifests;
|
||||
}
|
||||
|
||||
public static boolean unpackSmapiFiles(Context context, String apkPath, boolean checkMod) {
|
||||
List<ManifestEntry> manifestEntries = CommonLogic.getAssetJson(context, "smapi_files_manifest.json", new TypeToken<List<ManifestEntry>>() {
|
||||
}.getType());
|
||||
|
@ -200,4 +269,25 @@ public class CommonLogic {
|
|||
});
|
||||
return writer.toByteArray();
|
||||
}
|
||||
|
||||
public static String toPrettyPath(String path) {
|
||||
return StringUtils.removeStart(path, Environment.getExternalStorageDirectory().getAbsolutePath());
|
||||
}
|
||||
|
||||
public static String getFileHash(Context context, String filename) {
|
||||
try(InputStream inputStream = getLocalAsset(context, filename)){
|
||||
return Hashing.sha256().hashBytes(ByteStreams.toByteArray(inputStream)).toString();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getFileHash(File file) {
|
||||
try(InputStream inputStream = new FileInputStream(file)){
|
||||
return Hashing.sha256().hashBytes(ByteStreams.toByteArray(inputStream)).toString();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
package com.zane.smapiinstaller.logic;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.lzy.okgo.OkGo;
|
||||
import com.lzy.okgo.callback.StringCallback;
|
||||
import com.lzy.okgo.model.Response;
|
||||
import com.zane.smapiinstaller.constant.Constants;
|
||||
import com.zane.smapiinstaller.entity.DownloadableContentList;
|
||||
|
||||
public class DownloadabeContentManager {
|
||||
|
||||
private final View root;
|
||||
|
||||
private static final String TAG = "DLC_MGR";
|
||||
|
||||
private static boolean updateChecked = false;
|
||||
|
||||
private static DownloadableContentList downloadableContentList = null;
|
||||
|
||||
public DownloadabeContentManager(View root) {
|
||||
this.root = root;
|
||||
downloadableContentList = CommonLogic.getAssetJson(root.getContext(), "downloadable_content_list.json", DownloadableContentList.class);
|
||||
if(!updateChecked) {
|
||||
updateChecked = true;
|
||||
OkGo.<String>get(Constants.DLC_LIST_UPDATE_URL).execute(new StringCallback(){
|
||||
@Override
|
||||
public void onSuccess(Response<String> response) {
|
||||
DownloadableContentList content = new Gson().fromJson(response.body(), DownloadableContentList.class);
|
||||
if(downloadableContentList.getVersion() < content.getVersion()) {
|
||||
CommonLogic.writeAssetJson(root.getContext(), "downloadable_content_list.json", content);
|
||||
downloadableContentList = content;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public DownloadableContentList getDownloadableContentList() {
|
||||
return downloadableContentList;
|
||||
}
|
||||
}
|
|
@ -27,10 +27,12 @@ public class GameLauncher {
|
|||
return;
|
||||
}
|
||||
ModAssetsManager modAssetsManager = new ModAssetsManager(root);
|
||||
if(modAssetsManager.checkModEnvironment()) {
|
||||
Intent intent = packageManager.getLaunchIntentForPackage(Constants.TARGET_PACKAGE_NAME);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
modAssetsManager.checkModEnvironment((isOk) -> {
|
||||
if(isOk) {
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,9 @@ import android.os.Environment;
|
|||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import com.afollestad.materialdialogs.DialogAction;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableListMultimap;
|
||||
import com.google.common.collect.Lists;
|
||||
|
@ -17,7 +19,6 @@ 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;
|
||||
|
@ -25,7 +26,8 @@ import java.io.IOException;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import androidx.core.util.Consumer;
|
||||
|
||||
public class ModAssetsManager {
|
||||
|
||||
|
@ -37,7 +39,34 @@ public class ModAssetsManager {
|
|||
this.root = root;
|
||||
}
|
||||
|
||||
public List<ModManifestEntry> findAllInstalledMods() {
|
||||
public static ModManifestEntry findFirstModIf(Predicate<ModManifestEntry> filter) {
|
||||
ConcurrentLinkedQueue<File> files = Queues.newConcurrentLinkedQueue();
|
||||
files.add(new File(Environment.getExternalStorageDirectory(), Constants.MOD_PATH));
|
||||
do {
|
||||
File currentFile = files.poll();
|
||||
if(currentFile != null && currentFile.exists()) {
|
||||
boolean foundManifest = false;
|
||||
for(File file : currentFile.listFiles(File::isFile)) {
|
||||
if(StringUtils.equalsIgnoreCase(file.getName(), "manifest.json")) {
|
||||
ModManifestEntry manifest = CommonLogic.getFileJson(file, new TypeToken<ModManifestEntry>(){}.getType());
|
||||
foundManifest = true;
|
||||
if(manifest != null) {
|
||||
manifest.setAssetPath(file.getParentFile().getAbsolutePath());
|
||||
if(filter.apply(manifest)) {
|
||||
return manifest;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!foundManifest) {
|
||||
files.addAll(Lists.newArrayList(currentFile.listFiles(File::isDirectory)));
|
||||
}
|
||||
}
|
||||
} while (!files.isEmpty());
|
||||
return null;
|
||||
}
|
||||
public static List<ModManifestEntry> findAllInstalledMods() {
|
||||
ConcurrentLinkedQueue<File> files = Queues.newConcurrentLinkedQueue();
|
||||
files.add(new File(Environment.getExternalStorageDirectory(), Constants.MOD_PATH));
|
||||
List<ModManifestEntry> mods = new ArrayList<>(30);
|
||||
|
@ -78,7 +107,7 @@ public class ModAssetsManager {
|
|||
if(installedMods.size() > 1) {
|
||||
CommonLogic.showAlertDialog(root, R.string.error,
|
||||
String.format(context.getString(R.string.duplicate_mod_found),
|
||||
Joiner.on(",").join(Lists.transform(installedMods, ModManifestEntry::getAssetPath))));
|
||||
Joiner.on(",").join(Lists.transform(installedMods, item -> CommonLogic.toPrettyPath(item.getAssetPath())))));
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
|
@ -98,7 +127,28 @@ public class ModAssetsManager {
|
|||
return true;
|
||||
}
|
||||
|
||||
public boolean checkModEnvironment() {
|
||||
return true;
|
||||
public void checkModEnvironment(Consumer<Boolean> returnCallback) {
|
||||
ImmutableListMultimap<String, ModManifestEntry> installedModMap = Multimaps.index(findAllInstalledMods(), ModManifestEntry::getUniqueID);
|
||||
ArrayList<String> list = Lists.newArrayList();
|
||||
for (String key : installedModMap.keySet()) {
|
||||
ImmutableList<ModManifestEntry> installedMods = installedModMap.get(key);
|
||||
if(installedMods.size() > 1) {
|
||||
list.add(Joiner.on(",").join(Lists.transform(installedMods, item -> CommonLogic.toPrettyPath(item.getAssetPath()))));
|
||||
}
|
||||
}
|
||||
if(list.size() > 0) {
|
||||
CommonLogic.showConfirmDialog(root, R.string.error,
|
||||
String.format(root.getContext().getString(R.string.duplicate_mod_found),
|
||||
Joiner.on(";").join(list)), ((dialog, which) -> {
|
||||
if(which == DialogAction.POSITIVE) {
|
||||
returnCallback.accept(true);
|
||||
}
|
||||
else {
|
||||
returnCallback.accept(false);
|
||||
}
|
||||
}));
|
||||
return;
|
||||
}
|
||||
returnCallback.accept(true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,8 +31,7 @@ class ConfigViewModel extends ViewModel {
|
|||
private RecyclerView view;
|
||||
|
||||
ConfigViewModel(View root) {
|
||||
ModAssetsManager manager = new ModAssetsManager(root);
|
||||
this.modList = manager.findAllInstalledMods();
|
||||
this.modList = ModAssetsManager.findAllInstalledMods();
|
||||
Collections.sort(this.modList, (a, b)-> a.getName().compareTo(b.getName()));
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ 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;
|
||||
|
@ -77,7 +76,7 @@ public class ModManifestAdapter extends RecyclerView.Adapter<ModManifestAdapter.
|
|||
ButterKnife.bind(this, itemView);
|
||||
}
|
||||
@OnClick(R.id.button_remove_mod) void removeMod() {
|
||||
CommonLogic.showConfirmDialog(itemView, R.string.confirm, R.string.confirm_delete_mod, (dialog, which)->{
|
||||
CommonLogic.showConfirmDialog(itemView, R.string.confirm, R.string.confirm_delete_content, (dialog, which)->{
|
||||
if (which == DialogAction.POSITIVE) {
|
||||
File file = new File(modPath);
|
||||
if (file.exists()) {
|
||||
|
|
|
@ -1,18 +1,35 @@
|
|||
package com.zane.smapiinstaller.ui.download;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.afollestad.materialdialogs.DialogAction;
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.lzy.okgo.OkGo;
|
||||
import com.lzy.okgo.callback.FileCallback;
|
||||
import com.lzy.okgo.model.Response;
|
||||
import com.zane.smapiinstaller.R;
|
||||
import com.zane.smapiinstaller.entity.DownloadableContent;
|
||||
import com.zane.smapiinstaller.entity.ModManifestEntry;
|
||||
import com.zane.smapiinstaller.logic.CommonLogic;
|
||||
import com.zane.smapiinstaller.logic.ModAssetsManager;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.zeroturnaround.zip.ZipUtil;
|
||||
import org.zeroturnaround.zip.commons.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
@ -35,10 +52,7 @@ public class DownloadableContentAdapter extends RecyclerView.Adapter<Downloadabl
|
|||
|
||||
@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());
|
||||
holder.setDownloadableContent(downloadableContentList.get(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -53,17 +67,103 @@ public class DownloadableContentAdapter extends RecyclerView.Adapter<Downloadabl
|
|||
TextView nameTextView;
|
||||
@BindView(R.id.text_item_description)
|
||||
TextView descriptionTextView;
|
||||
@BindView(R.id.button_remove_content)
|
||||
Button buttonRemove;
|
||||
@BindView(R.id.button_download_content)
|
||||
Button buttonDownload;
|
||||
|
||||
public DownloadableContent downloadableContent;
|
||||
|
||||
public void setDownloadableContent(DownloadableContent downloadableContent) {
|
||||
this.downloadableContent = downloadableContent;
|
||||
typeTextView.setText(downloadableContent.getType());
|
||||
nameTextView.setText(downloadableContent.getName());
|
||||
descriptionTextView.setText(downloadableContent.getDescription());
|
||||
if (StringUtils.isBlank(downloadableContent.getAssetPath())) {
|
||||
buttonRemove.setVisibility(View.INVISIBLE);
|
||||
buttonDownload.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
File contentFile = new File(itemView.getContext().getFilesDir(), downloadableContent.getAssetPath());
|
||||
if (contentFile.exists()) {
|
||||
buttonRemove.setVisibility(View.VISIBLE);
|
||||
buttonDownload.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ViewHolder(View view) {
|
||||
super(view);
|
||||
ButterKnife.bind(this, itemView);
|
||||
}
|
||||
@OnClick(R.id.button_remove_content) void removeContent() {
|
||||
|
||||
@OnClick(R.id.button_remove_content)
|
||||
void removeContent() {
|
||||
if (StringUtils.isNoneBlank(downloadableContent.getAssetPath())) {
|
||||
File contentFile = new File(itemView.getContext().getFilesDir(), downloadableContent.getAssetPath());
|
||||
if (contentFile.exists()) {
|
||||
CommonLogic.showConfirmDialog(itemView, R.string.confirm, R.string.confirm_delete_content, (dialog, which) -> {
|
||||
if (which == DialogAction.POSITIVE) {
|
||||
try {
|
||||
FileUtils.forceDelete(contentFile);
|
||||
} catch (IOException e) {
|
||||
CommonLogic.showAlertDialog(itemView, R.string.error, e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@OnClick(R.id.button_download_content) void downloadContent() {
|
||||
|
||||
@OnClick(R.id.button_download_content)
|
||||
void downloadContent() {
|
||||
Context context = itemView.getContext();
|
||||
ModManifestEntry modManifestEntry = null;
|
||||
if (StringUtils.equals(downloadableContent.getType(), "LOCALE")) {
|
||||
modManifestEntry = ModAssetsManager.findFirstModIf(mod -> StringUtils.equals(mod.getUniqueID(), "ZaneYork.CustomLocalization") || StringUtils.equals(mod.getUniqueID(), "SMAPI.CustomLocalization"));
|
||||
if (modManifestEntry == null) {
|
||||
CommonLogic.showAlertDialog(itemView, R.string.error, String.format(context.getString(R.string.error_depends_on_mod), context.getString(R.string.locale_pack), "ZaneYork.CustomLocalization"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
File file = new File(context.getCacheDir(), downloadableContent.getName() + ".zip");
|
||||
if (file.exists()) {
|
||||
if (!StringUtils.equalsIgnoreCase(CommonLogic.getFileHash(file), downloadableContent.getHash())) {
|
||||
file.delete();
|
||||
} else {
|
||||
unpackLogic(context, file, modManifestEntry);
|
||||
return;
|
||||
}
|
||||
}
|
||||
ModManifestEntry finalModManifestEntry = modManifestEntry;
|
||||
OkGo.<File>get(downloadableContent.getUrl()).execute(new FileCallback(file.getParentFile().getAbsolutePath(), file.getName()) {
|
||||
@Override
|
||||
public void onError(Response<File> response) {
|
||||
super.onError(response);
|
||||
CommonLogic.showAlertDialog(itemView, R.string.error, R.string.error_failed_to_download);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(Response<File> response) {
|
||||
File downloadedFile = response.body();
|
||||
String hash = CommonLogic.getFileHash(downloadedFile);
|
||||
if (!StringUtils.equalsIgnoreCase(hash, downloadableContent.getHash())) {
|
||||
CommonLogic.showAlertDialog(itemView, R.string.error, R.string.error_failed_to_download);
|
||||
return;
|
||||
}
|
||||
unpackLogic(context, downloadedFile, finalModManifestEntry);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void unpackLogic(Context context, File downloadedFile, ModManifestEntry finalModManifestEntry) {
|
||||
if (StringUtils.equals(downloadableContent.getType(), "LOCALE")) {
|
||||
if (finalModManifestEntry != null) {
|
||||
ZipUtil.unpack(downloadedFile, new File(finalModManifestEntry.getAssetPath()));
|
||||
}
|
||||
} else {
|
||||
ZipUtil.unpack(downloadedFile, new File(context.getFilesDir(), downloadableContent.getAssetPath()));
|
||||
}
|
||||
CommonLogic.showAlertDialog(itemView, R.string.info, R.string.download_unpack_success);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import android.view.ViewGroup;
|
|||
import com.zane.smapiinstaller.R;
|
||||
import com.zane.smapiinstaller.entity.DownloadableContentList;
|
||||
import com.zane.smapiinstaller.logic.CommonLogic;
|
||||
import com.zane.smapiinstaller.logic.DownloadabeContentManager;
|
||||
|
||||
/**
|
||||
* A fragment representing a list of Items.
|
||||
|
@ -38,8 +39,8 @@ public class DownloadableContentFragment extends Fragment {
|
|||
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()));
|
||||
DownloadabeContentManager manager = new DownloadabeContentManager(view);
|
||||
recyclerView.setAdapter(new DownloadableContentAdapter(manager.getDownloadableContentList().getContents()));
|
||||
recyclerView.addItemDecoration(new DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL));
|
||||
}
|
||||
return view;
|
||||
|
|
|
@ -6,6 +6,12 @@ import android.view.LayoutInflater;
|
|||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.zane.smapiinstaller.R;
|
||||
import com.zane.smapiinstaller.constant.Constants;
|
||||
import com.zane.smapiinstaller.logic.CommonLogic;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.navigation.NavController;
|
||||
|
@ -13,13 +19,6 @@ import androidx.navigation.Navigation;
|
|||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
|
||||
import com.afollestad.materialdialogs.DialogAction;
|
||||
import com.zane.smapiinstaller.R;
|
||||
import com.zane.smapiinstaller.constant.Constants;
|
||||
import com.zane.smapiinstaller.logic.CommonLogic;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class HelpFragment extends Fragment {
|
||||
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
|
@ -35,13 +34,7 @@ public class HelpFragment extends Fragment {
|
|||
CommonLogic.openUrl(this.getContext(), "https://www.nexusmods.com/stardewvalley/mods/");
|
||||
}
|
||||
@OnClick(R.id.button_release) void release() {
|
||||
CommonLogic.showConfirmDialog(this.getView(), R.string.confirm, R.string.test_message, (dialog, which)-> {
|
||||
if (which == DialogAction.POSITIVE) {
|
||||
if (this.getString(R.string.test_message).contains("860453392")) {
|
||||
CommonLogic.openUrl(this.getContext(), "mqqopensdkapi://bizAgent/qm/qr?url=http%3A%2F%2Fqm.qq.com%2Fcgi-bin%2Fqm%2Fqr%3Ffrom%3Dapp%26p%3Dandroid%26k%3D" + "AAflCLHiWw1haM1obu_f-CpGsETxXc6b");
|
||||
}
|
||||
}
|
||||
});
|
||||
CommonLogic.openUrl(this.getContext(), "https://github.com/ZaneYork/SMAPI-Android-Installer/releases");
|
||||
}
|
||||
@OnClick({R.id.button_logs}) void showLog() {
|
||||
NavController controller = Navigation.findNavController(this.getView());
|
||||
|
|
|
@ -27,8 +27,8 @@
|
|||
<string name="failed_to_unpack_smapi_files">无法解包SMAPI环境</string>
|
||||
<string name="confirm">确认</string>
|
||||
<string name="cancel">取消</string>
|
||||
<string name="confirm_delete_mod">确定删除该MOD?</string>
|
||||
<string name="text_install_tip1">注意:需要1.4.5.142版本的游戏本体</string>
|
||||
<string name="confirm_delete_content">确定删除该内容?</string>
|
||||
<string name="text_install_tip1">注意:需要不低于1.4.5.138版本的游戏本体</string>
|
||||
<string name="text_install_tip2">更新或安装期间需要安装游戏本体</string>
|
||||
<string name="duplicate_mod_found">发现该MOD存在多份拷贝,请从以下位置删除重复MOD:%s</string>
|
||||
<string name="error_smapi_not_installed">SMAPI尚未安装,请先点击安装按钮</string>
|
||||
|
@ -44,4 +44,9 @@
|
|||
<string name="failed_to_process_manifest">无法处理AndroidManifest.xml文件</string>
|
||||
<string name="error_no_supported_game_version">游戏版本不支持,请更新版本或者下载兼容包</string>
|
||||
<string name="menu_download">下载</string>
|
||||
<string name="error_failed_to_download">无法下载目标资源</string>
|
||||
<string name="error_depends_on_mod">%s依赖%s前置,请先安装它</string>
|
||||
<string name="locale_pack">语言包</string>
|
||||
<string name="info">提示</string>
|
||||
<string name="download_unpack_success">已完成下载安装</string>
|
||||
</resources>
|
||||
|
|
|
@ -27,8 +27,8 @@
|
|||
<string name="failed_to_unpack_smapi_files">Failed to unpack smapi files</string>
|
||||
<string name="confirm">Confirm</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="confirm_delete_mod">Are you sure to delete it?</string>
|
||||
<string name="text_install_tip1">Notes: Game version 1.4.5.142 is required.</string>
|
||||
<string name="confirm_delete_content">Are you sure to delete it?</string>
|
||||
<string name="text_install_tip1">Notes: Game version 1.4.5.138 or later is required.</string>
|
||||
<string name="text_install_tip2">The base game is required when updating/installing.</string>
|
||||
<string name="duplicate_mod_found">Found duplicate copy of mod, please delete duplicate of them: %s</string>
|
||||
<string name="error_smapi_not_installed">SMAPI has not installed yet, press install first</string>
|
||||
|
@ -44,4 +44,9 @@
|
|||
<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>
|
||||
<string name="error_failed_to_download">Failed to download target resources</string>
|
||||
<string name="error_depends_on_mod">The %s is depends on %s, please install it first</string>
|
||||
<string name="locale_pack">Locale Pack</string>
|
||||
<string name="info">Info</string>
|
||||
<string name="download_unpack_success">Download and unpack success</string>
|
||||
</resources>
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<base-config cleartextTrafficPermitted="true" />
|
||||
</network-security-config>
|
Loading…
Reference in New Issue