1.Downloadable Content support

2.Patch logic upgrade
This commit is contained in:
ZaneYork 2020-03-06 22:08:56 +08:00
parent 97e62a4c8f
commit 1fc03d9437
22 changed files with 391 additions and 78 deletions

View File

@ -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

View File

@ -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"

View File

@ -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
}
]
}

View File

@ -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"
}
]
}

View File

@ -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";
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -8,4 +8,5 @@ public class ManifestEntry {
private String assetPath;
private int compression;
private int origin;
private boolean external;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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()));
}

View File

@ -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()) {

View File

@ -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);
}
}
}

View File

@ -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;

View File

@ -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());

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>