V0.86 Files, Device fixes, Updated Readme and others!

This commit is contained in:
Chris 2019-06-23 00:29:10 -04:00
parent 42aba8210e
commit 9db4063e85
13 changed files with 252 additions and 123 deletions

View File

@ -1,15 +1,46 @@
# SMAPI-Android-Installer
Installs SMAPI to Android Devices
# WIP!
# For Players
##How to use
Download the latest apk from the releases tab, it contains all the latest files.
After installing the SMAPI Installer app, open it up and click the `Install` button to begin the install process.
The process may take some time depending on the age and specs of the device.
Once it's installed, you're good to go and can uninstall the Installer app if you please to do so.
Add mods in the newly created `StardewValley/Mods` directory.
Lastly, Just click on the `SMAPI Stardew Valley` icon to start the SMAPI version of the game.
##Mod Support
The focus when it comes to mods is try to make the bigger mods compatible first!
Here's a list of some of the bigger mods that are supported:
- [Content Patcher](https://www.nexusmods.com/stardewvalley/mods/1915) by PathosChild.
- [Json Assets](https://www.nexusmods.com/stardewvalley/mods/1720) by spacechase0.
- [PyTK](https://www.nexusmods.com/stardewvalley/mods/1726) by Platonymous (aka Routine).
- [Partial SVE support](https://www.nexusmods.com/stardewvalley/mods/3753) by FlashShifter.
- [Farm Type Manager](https://www.nexusmods.com/stardewvalley/mods/3231) by EscaMMC (aka Esca).
- And plenty more!
##Manual Install
If the Automatic Installer doesn't work I'll always have a manual install option under the releases tab.
Note: It's the `.zip` file.
##How the installer works
1. It pulls the actual games apk from the device.
2. It creates all the needed directories on the the device.
3. Modifies the apk to add SMAPI needed files.
4. Signs the modified apk using JAR signing.
5. Lastly, it installs the new version.
## TODO
- Adding Exceptions so Users can report problems
- Find out why it's crashing on my Samsung Galaxy S6
- Faster modify times
- Clean up the MainActivity
- Uninstall current version
- Install modified version
## APK Signing Details
![alt text](https://github.com/MartyrPher/SMAPI-Android-Installer/blob/master/current_scheme.PNG)

View File

@ -19,7 +19,7 @@ android {
}
dependencies {
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:appcompat-v7:28.0.0-alpha1'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.madgag.spongycastle:core:1.54.0.0'
implementation 'com.madgag.spongycastle:prov:1.54.0.0'

View File

@ -21,6 +21,16 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
</application>
</manifest>

View File

@ -0,0 +1,48 @@
package com.MartyrPher.smapiandroidinstaller;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.support.v4.content.FileProvider;
import java.io.File;
public class ApkInstall {
private static Context context;
public ApkInstall(Context appContext)
{
this.context = appContext;
}
public void installNewStardew()
{
String newApkPath = Environment.getExternalStorageDirectory() + "/SMAPI Installer/base_signed.apk";
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(fromFile(new File(newApkPath)), "application/vnd.android.package-archive");
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
try
{
context.startActivity(intent);
}
catch (ActivityNotFoundException e)
{
DialogFrag.showDialog(context, R.string.install, 1);
}
}
private Uri fromFile(File file)
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
return FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file);
else
return Uri.fromFile(file);
}
}

View File

@ -3,15 +3,25 @@ package com.MartyrPher.smapiandroidinstaller;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Environment;
import android.widget.Toast;
import java.io.File;
public class BackgroundTask extends AsyncTask<Void, Integer, Boolean> {
private static final String TAG = "BackgroundTask";
private static final String DIR_APK_FILES = Environment.getExternalStorageDirectory() + "/SMAPI Installer/ApkFiles/";
private Context contextActivity;
private static final String ASSET_APK_FILES = "SMAPI";
private static final String ASSET_STARDEW_FILES = "Stardew";
private static final String MOD_FILES_VK = "VirtualKeyboard";
private static final String MOD_FILES_VK_ASSET = "VirtualKeyboard/assets";
private static final String DIR_APK_FILES = "/SMAPI Installer/ApkFiles/";
private static final String DIR_STARDEW_FILES = "/StardewValley/smapi-internal/";
private static final String DIR_MODS_VK = "/StardewValley/Mods/VirtualKeyboard/";
private static final String DIR_MODS_VK_ASSET = "/StardewValley/Mods/VirtualKeyboard/assets";
private static final String MOD_DIR = Environment.getExternalStorageDirectory() + "/StardewValley/Mods/";
private final Context contextActivity;
private ApkInstall apkInstall;
public BackgroundTask(Context context)
{
@ -19,32 +29,46 @@ public class BackgroundTask extends AsyncTask<Void, Integer, Boolean> {
}
@Override
protected void onPreExecute() {
protected void onPreExecute()
{
super.onPreExecute();
DialogFrag.showDialog(contextActivity, R.string.modify_apk, 0);
}
@Override
protected Boolean doInBackground(Void... voids) {
protected Boolean doInBackground(Void... voids)
{
CopyAssets copy = new CopyAssets(contextActivity);
WriteApk writeApk = new WriteApk();
SignApk signApk = new SignApk();
apkInstall = new ApkInstall(contextActivity);
try
{
File[] moddingAPI = {new File( DIR_APK_FILES + "StardewModdingAPI.dll"),
new File(DIR_APK_FILES + "StardewModdingAPI.Toolkit.CoreInterfaces.dll"),
new File( DIR_APK_FILES + "StardewModdingAPI.Toolkit.dll"),
new File( DIR_APK_FILES + "Newtonsoft.json.dll"),
new File( DIR_APK_FILES + "System.Data.dll"),
new File( DIR_APK_FILES + "System.Numerics.dll")};
File modDir = new File(MOD_DIR);
if (!modDir.exists())
{
modDir.mkdir();
}
copy.copyAssets(ASSET_APK_FILES, DIR_APK_FILES);
copy.copyAssets(ASSET_STARDEW_FILES, DIR_STARDEW_FILES);
copy.copyAssets(MOD_FILES_VK, DIR_MODS_VK);
copy.copyAssets(MOD_FILES_VK_ASSET, DIR_MODS_VK_ASSET);
File[] moddingAPI = {new File( Environment.getExternalStorageDirectory() + DIR_APK_FILES + "StardewModdingAPI.dll"),
new File(Environment.getExternalStorageDirectory() + DIR_APK_FILES + "StardewModdingAPI.Toolkit.CoreInterfaces.dll"),
new File( Environment.getExternalStorageDirectory() + DIR_APK_FILES + "StardewModdingAPI.Toolkit.dll"),
new File( Environment.getExternalStorageDirectory() + DIR_APK_FILES + "Newtonsoft.json.dll"),
new File( Environment.getExternalStorageDirectory() + DIR_APK_FILES + "System.Data.dll"),
new File( Environment.getExternalStorageDirectory() + DIR_APK_FILES + "System.Numerics.dll")};
publishProgress(1);
writeApk.addFilesToApk(new File(Environment.getExternalStorageDirectory() + "/SMAPI Installer/base.apk"), moddingAPI, "assemblies/", false, 0);
File[] resources = {new File(DIR_APK_FILES + "AndroidManifest.xml"),
new File( DIR_APK_FILES + "classes.dex")};
File[] resources = {new File(Environment.getExternalStorageDirectory() + DIR_APK_FILES + "AndroidManifest.xml"),
new File( Environment.getExternalStorageDirectory() + DIR_APK_FILES + "classes.dex")};
writeApk.addFilesToApk(new File(Environment.getExternalStorageDirectory() + "/SMAPI Installer/base.apk_patched0.apk"), resources, "", true, 1);
signApk.commitSignApk();
File filesToDelete = new File(DIR_APK_FILES);
File filesToDelete = new File(Environment.getExternalStorageDirectory() + DIR_APK_FILES);
File deleteOldApk = new File(Environment.getExternalStorageDirectory() + "/SMAPI Installer/base.apk_patched0.apk_patched1.apk");
deleteOldApk.delete();
if (filesToDelete.isDirectory())
@ -60,9 +84,9 @@ public class BackgroundTask extends AsyncTask<Void, Integer, Boolean> {
}
catch(Exception e)
{
DialogFrag.dismissDialogString(contextActivity, e.getMessage());
return false;
}
}
@Override
@ -75,11 +99,8 @@ public class BackgroundTask extends AsyncTask<Void, Integer, Boolean> {
super.onPostExecute(aBoolean);
if (aBoolean)
{
DialogFrag.dismissDialog(contextActivity, R.string.finished);
}
else
{
Toast.makeText(contextActivity, "Something went wrong!", Toast.LENGTH_LONG).show();
DialogFrag.dismissDialog(contextActivity);
apkInstall.installNewStardew();
}
}
}

View File

@ -0,0 +1,78 @@
package com.MartyrPher.smapiandroidinstaller;
import android.content.Context;
import android.content.res.AssetManager;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class CopyAssets {
private final static String TAG = "CopyAssets";
private final Context context;
public CopyAssets(Context appContext)
{
this.context = appContext;
}
//Copies the needed files from the APK to a local directory so they can be used
public void copyAssets(String asset, String dir) {
AssetManager assetManager = context.getAssets();
String[] files = null;
File dest = new File(Environment.getExternalStorageDirectory() + dir);
if(!dest.exists())
{
dest.mkdir();//directory is created;
}
try {
files = assetManager.list(asset);
} catch (IOException e) {
Log.e(TAG, "Failed to get asset file list.", e);
}
Log.e(TAG, "File Length: " + files.length);
if (files != null) for (String filename : files) {
InputStream in = null;
OutputStream out = null;
try {
in = assetManager.open(asset + "/" + filename);
File outFile = new File(dest, filename);
out = new FileOutputStream(outFile);
copyFile(in, out);
} catch(IOException e) {
Log.e(TAG, "Failed to copy asset file: " + filename, e);
}
finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
// NOOP
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
// NOOP
}
}
}
}
}
private void copyFile(InputStream in, OutputStream out) throws IOException {
byte[] buffer = new byte[1024];
int read;
while((read = in.read(buffer)) != -1){
out.write(buffer, 0, read);
}
}
}

View File

@ -8,6 +8,7 @@ import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import android.view.View;
public class DialogFrag extends DialogFragment {
@ -36,7 +37,31 @@ public class DialogFrag extends DialogFragment {
}
}
public static void dismissDialog(Context context, int message)
public static void dismissDialog(Context context)
{
if (mAlertDialog.isShowing())
mAlertDialog.dismiss();
}
public static void dismissDialogInt(Context context, int message)
{
if(mAlertDialog.isShowing())
{
mAlertDialog.dismiss();
}
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage(message);
builder.setPositiveButton("Awesome", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.show();
}
public static void dismissDialogString(Context context, String message)
{
if(mAlertDialog.isShowing())
{

View File

@ -1,40 +1,20 @@
package com.MartyrPher.smapiandroidinstaller;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.net.Uri;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class MainActivity extends AppCompatActivity {
private static final String ASSET_APK_FILES = "SMAPI/";
private static final String ASSET_STARDEW_FILES = "Stardew/";
private static final String MOD_FILES_VK = "VirtualKeyboard/";
private static final String MOD_FILES_VK_ASSET = "VirtualKeyboard/assets/";
private static final String DIR_APK_FILES = "/SMAPI Installer/ApkFiles/";
private static final String DIR_STARDEW_FILES = "/StardewValley/smapi-internal/";
private static final String DIR_MODS_VK = "/StardewValley/Mods/VirtualKeyboard";
private static final String DIR_MODS_VK_ASSET = "/StardewValley/Mods/VirtualKeyboard/assets";
private static final String TAG = "MainActivity";
private static final int UNINSTALL_REQUEST_CODE = 0;
private static final int MY_PERMISSION_REQUEST_STORAGE = 2;
private boolean hasPermissions = false;
@ -59,18 +39,10 @@ public class MainActivity extends AppCompatActivity {
foundGame = apkExtractor.extractAPK(getApplicationContext());
if(foundGame)
{
copyAssets(ASSET_APK_FILES, DIR_APK_FILES);
copyAssets(ASSET_STARDEW_FILES, DIR_STARDEW_FILES);
copyAssets(MOD_FILES_VK, DIR_MODS_VK);
copyAssets(MOD_FILES_VK_ASSET, DIR_MODS_VK_ASSET);
backgroundTask.execute();
}
else
{
DialogFrag.showDialog(MainActivity.this, R.string.cant_find, 1);
}
}
else
{
@ -107,67 +79,4 @@ public class MainActivity extends AppCompatActivity {
break;
}
}
//Prompts the user to Uninstall the current version of the game from the device
public void uninstallStardew()
{
String app_pkg_name = "com.chucklefish.stardewvalley";
Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
intent.setData(Uri.parse("package:" + app_pkg_name));
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
startActivityForResult(intent, UNINSTALL_REQUEST_CODE);
}
//Copies the needed files from the APK to a local directory so they can be used
private void copyAssets(String asset, String dir) {
AssetManager assetManager = getAssets();
String[] files = null;
File dest = new File(Environment.getExternalStorageDirectory() + dir);
if(!dest.exists())
{
dest.mkdir();//directory is created;
}
try {
files = assetManager.list(asset);
} catch (IOException e) {
Log.e(TAG, "Failed to get asset file list.", e);
}
if (files != null) for (String filename : files) {
InputStream in = null;
OutputStream out = null;
try {
in = assetManager.open(asset + filename);
File outFile = new File(dest, filename);
out = new FileOutputStream(outFile);
copyFile(in, out);
} catch(IOException e) {
Log.e(TAG, "Failed to copy asset file: " + filename, e);
}
finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
// NOOP
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
// NOOP
}
}
}
}
}
private void copyFile(InputStream in, OutputStream out) throws IOException {
byte[] buffer = new byte[1024];
int read;
while((read = in.read(buffer)) != -1){
out.write(buffer, 0, read);
}
}
}

View File

@ -2,5 +2,6 @@
<string name="app_name">SMAPI Android Installer</string>
<string name="cant_find">Could not find the Stardew Valley Apk. Is the game installed?</string>
<string name="modify_apk">Modifying the Apk…this might take some time. Please keep the screen on.</string>
<string name="finished">Finished! The modified apk can be found here: /SMAPI Installer/base_signed.apk. You have to Uninstall the original game first.</string>
<string name="finished">Finished!</string>
<string name="install">Error in opening file!</string>
</resources>

View File

@ -1,7 +1,7 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<style name="AppTheme" parent="Base.Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external_files"
path="." />
</paths>