special external storage permission

- bump SDL to 2.32.0
    - bump NDK to r27c
    - bump AGP to 8.8.0
    - bump Gradle to 8.10.2
    - bump CMake to 3.31.5
    - bump targetSdkVersion to 34
    - add GitHub Actions Release support (Debug)
    - add Special External Storage Permission support
    - fix "assets not found" error on first launch
    - add Android 5 & 6 support
This commit is contained in:
Robert Kirkman
2025-02-19 18:58:13 -06:00
parent d6c503f0e9
commit d11c66eea9
20 changed files with 303 additions and 195 deletions
+29 -2
View File
@@ -1,7 +1,8 @@
name: generate-builds
on:
push:
pull_request:
# push:
# pull_request:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
@@ -380,3 +381,29 @@ jobs:
with:
name: 2ship-windows
path: 2ship-windows
build-android:
needs: generate-2ship-otr
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Install dependencies
run: sudo apt-get install -y ninja-build
- name: Download 2ship.o2r
uses: actions/download-artifact@v4
with:
name: 2ship.o2r
path: Android/app/src/main/assets
- name: Build 2Ship
run: |
cd Android/
./gradlew assembleDebug -P elfBuildType=RelWithDebInfo
mv app/build/outputs/apk/debug/app-debug.apk ../2ship.apk
- name: Create release
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref }}
file: 2ship.apk
+3
View File
@@ -67,3 +67,6 @@ _packages/
/mm/src/boot/build.c
/mm/windows/properties.h
/clang-format.exe
# IntelliJ / Android Studio
.idea
+1 -1
View File
@@ -1,6 +1,6 @@
[submodule "libultraship"]
path = libultraship
url = https://github.com/Waterdish/libultraship.git
url = https://github.com/robertkirkman/libultraship.git
[submodule "OTRExporter"]
path = OTRExporter
url = https://github.com/Waterdish/OTRExporter.git
+24 -26
View File
@@ -1,4 +1,4 @@
def buildAsLibrary = project.hasProperty('BUILD_AS_LIBRARY');
def buildAsLibrary = project.hasProperty('BUILD_AS_LIBRARY')
def buildAsApplication = !buildAsLibrary
if (buildAsApplication) {
apply plugin: 'com.android.application'
@@ -8,25 +8,21 @@ else {
}
android {
ndkPath "/home/waterdish/Android/Sdk/ndk/26.0.10792818" // Point to your own NDK
compileSdkVersion 31
ndkVersion '27.2.12479018'
compileSdkVersion 34
defaultConfig {
if (buildAsApplication) {
applicationId "com.dishii.mm"
}
minSdkVersion 24
targetSdkVersion 31
minSdkVersion 21
//noinspection OldTargetApi
targetSdkVersion 34
versionCode 4
versionName "1.1.1"
externalNativeBuild {
//ndkBuild {
// arguments "APP_PLATFORM=android-23"
// abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
//}
cmake {
arguments "-DSDL_SHARED=ON", "-DANDROID_STL=c++_static", "-DHAVE_LD_VERSION_SCRIPT=OFF",'-DUSE_OPENGLES=ON'
arguments "-DANDROID_APPNAME=${applicationId}", "-DANDROID_APP_PLATFORM=android-21", "-DANDROID_STL=c++_static", "-DHAVE_LD_VERSION_SCRIPT=OFF", "-DUSE_OPENGLES=ON", "-DCMAKE_BUILD_TYPE=$elfBuildType"
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
//abiFilters 'arm64-v8a'
}
}
}
@@ -34,9 +30,17 @@ android {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.debug
}
}
applicationVariants.all { variant ->
buildFeatures {
buildConfig = true
}
namespace 'com.dishii.mm'
lint {
abortOnError false
}
applicationVariants.configureEach { variant ->
tasks["merge${variant.name.capitalize()}Assets"]
.dependsOn("externalNativeBuild${variant.name.capitalize()}")
}
@@ -45,26 +49,20 @@ android {
jniLibs.srcDir 'libs'
}
externalNativeBuild {
//ndkBuild {
// path 'jni/Android.mk'
//}
cmake {
path '../../CMakeLists.txt'
version "3.25.1"
version "3.31.5"
}
}
}
lintOptions {
abortOnError false
}
if (buildAsLibrary) {
libraryVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith(".aar")) {
def fileName = "org.libsdl.app.aar";
def fileName = "com.dishii.mm.aar"
output.outputFile = new File(outputFile.parent, fileName);
}
}
@@ -74,13 +72,13 @@ android {
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'androidx.core:core:1.7.0' // Use the latest version
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.core:core:1.13.1'
implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
}
task wrapper(type: Wrapper) {
gradleVersion = '7.0.3'
tasks.register('wrapper', Wrapper) {
gradleVersion = '8.10.2'
}
task prepareKotlinBuildScriptModel {
tasks.register('prepareKotlinBuildScriptModel') {
}
Binary file not shown.
-5
View File
@@ -1,5 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+3 -4
View File
@@ -3,7 +3,6 @@
com.gamemaker.game
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.dishii.mm"
android:versionCode="4"
android:versionName="1.1.1"
android:installLocation="auto">
@@ -54,8 +53,8 @@
<!-- Allow access to the vibrator -->
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<!-- if you want to capture audio, uncomment this. -->
<!-- <uses-permission android:name="android.permission.RECORD_AUDIO" /> -->
@@ -73,7 +72,8 @@
android:allowBackup="true"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:hardwareAccelerated="true"
android:appCategory="game" >
android:appCategory="game"
android:requestLegacyExternalStorage="true">
<!-- Example of setting SDL hints from AndroidManifest.xml:
@@ -84,7 +84,6 @@
<activity android:name="MainActivity"
android:label="@string/app_name"
android:alwaysRetainTaskState="true"
android:launchMode="singleInstance"
android:configChanges="layoutDirection|locale|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
+1
View File
@@ -1,2 +1,3 @@
# Extractor Assets Copied by CMake during buildtime
assets/
mods/
@@ -2,6 +2,8 @@ package com.dishii.mm;
import android.content.Context;
import android.content.res.AssetManager;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -9,53 +11,70 @@ import java.io.InputStream;
import java.io.OutputStream;
public class AssetCopyUtil {
// based on https://stackoverflow.com/a/8366081/11708026
// This is desirable because it bulk copies all assets indiscriminately, simplifying the code in MainActivity
// the side effect is that, some people will see strange, unidentifiable files appearing in their destination folder.
// those extra files do not come from the app itself. They originate from something inside the Android ROM
// of the device they are using, and can be observed to vary between different models of devices.
public static void copyAssetsToExternal(Context context, String externalFolderPath) {
externalFolderPath = externalFolderPath + "/";
copyFileOrDir(context, "", externalFolderPath); // copy all files in assets folder to the destination
}
public static void copyAssetsToExternal(Context context, String assetsFolderPath, String externalFolderPath) throws IOException {
private static void copyFileOrDir(Context context, String srcpath, String destpath) {
AssetManager assetManager = context.getAssets();
String[] assetFiles = assetManager.list(assetsFolderPath);
for (String assetFile : assetFiles) {
String assetPath = assetsFolderPath + File.separator + assetFile;
String externalPath = externalFolderPath + File.separator + assetFile;
if (assetManager.list(assetPath).length > 0) {
// It's a directory
// Check if the directory exists in the external storage
File externalDir = new File(externalPath);
if (!externalDir.exists()) {
externalDir.mkdirs(); // Create the directory if it doesn't exist
}
// Recursively copy contents of the directory
copyAssetsToExternal(context, assetPath, externalPath);
String assets[] = null;
String tag = "AssetCopyUtil";
try {
Log.i(tag, "copyFileOrDir() " + srcpath);
assets = assetManager.list(srcpath);
if (assets.length == 0) {
copyFile(context, srcpath, destpath);
} else {
// It's a file
File externalFile = new File(externalPath);
if (!externalFile.exists()) {
// Check if the file exists in the external storage
InputStream in = null;
OutputStream out = null;
try {
in = assetManager.open(assetPath);
out = new FileOutputStream(externalPath);
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
String fullPath = destpath + srcpath;
Log.i(tag, "path=" + fullPath);
File dir = new File(fullPath);
if (!dir.exists())
if (!dir.mkdirs())
Log.i(tag, "could not create dir " + fullPath);
for (int i = 0; i < assets.length; ++i) {
String p;
if (srcpath.isEmpty())
p = "";
else
p = srcpath + "/";
copyFileOrDir(context,p + assets[i], destpath);
}
}
} catch (IOException ex) {
Log.e(tag, "I/O Exception", ex);
}
}
private static void copyFile(Context context, String filename, String destpath) {
AssetManager assetManager = context.getAssets();
InputStream in = null;
OutputStream out = null;
String newFileName = null;
String tag = "AssetCopyUtil";
try {
Log.i(tag, "copyFile() " + filename);
in = assetManager.open(filename);
newFileName = destpath + filename;
out = new FileOutputStream(newFileName);
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
in.close();
in = null;
out.flush();
out.close();
out = null;
} catch (Exception e) {
Log.e(tag, "Exception in copyFile() of " + newFileName);
Log.e(tag, "Exception in copyFile() " + e.toString());
}
}
}
@@ -6,20 +6,12 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.Settings;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileOutputStream;
import android.Manifest;
import android.content.pm.PackageManager;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.util.Log;
import android.provider.Settings;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.FrameLayout;
@@ -28,28 +20,48 @@ import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
//This class is the main SDLActivity and just sets up a bunch of default files and the input overlay
public class MainActivity extends SDLActivity{
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
private static final int STORAGE_PERMISSION_REQUEST_CODE = 1;
//This class is the main SDLActivity and just sets up a bunch of default files and the input overlay
public class MainActivity extends SDLActivity {
private static final int FILE_HANDLER_REQUEST_CODE = 0;
private static final int SPECIAL_STORAGE_PERMISSION_REQUEST_CODE = 1;
SharedPreferences preferences;
private boolean hasSpecialExternalStoragePermission = false;
private boolean permissionPopupIsOpen = false;
// this is a case where I feel like it is actually clearer to have a double negative boolean,
// than to have one named "permissionPopupChargeIsAvailable = true", but you can let me know
// if you would prefer to reorganize this.
private boolean permissionPopupWasDeclined = false;
private boolean hasInstalledExternalAssetFiles = false;
@Override
protected String[] getLibraries() {
return new String[] {
"SDL2",
"2ship"
};
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
preferences = getSharedPreferences("com.dishii.mm.prefs",Context.MODE_PRIVATE);
preferences = getSharedPreferences("com.dishii.mm.prefs", Context.MODE_PRIVATE);
doVersionCheck();
setupControllerOverlay();
// Check if storage permissions are granted
if (hasStoragePermission()) {
doVersionCheck();
setupFiles();
} else {
requestStoragePermission();
}
attachController();
}
@@ -58,97 +70,108 @@ public class MainActivity extends SDLActivity{
int storedVersion = preferences.getInt("appVersion", 1);
if (currentVersion > storedVersion) {
deleteOutdatedAssets();
// I tend to just copy all assets on every app launch, overwriting the old ones,
// so that I don't have to change appVersion every time I need to make sure that the newest assets
// are guaranteed to always be present. Also, when /storage/emulated/0/com.dishii.mm is used, the assets
// could be from a whole different version of the app and that wouldn't be detected (at least by this
// particular SharedPreferences), so my way always overwrites those.
// My way is also very slow to load at every startup, which is ok for
// apps that have only a few external assets, but for apps like this one that have
// a lot of external assets, the slowness is pretty severe. Let me know if that is not desirable
// and you would prefer it to work differently.
//deleteOutdatedAssets();
preferences.edit().putInt("appVersion", currentVersion).apply();
}
}
private void deleteOutdatedAssets(){
File externalSohFile = new File(getExternalFilesDir(null), "2ship.o2r");
externalSohFile.delete();
File externalOotFile = new File(getExternalFilesDir(null), "mm.o2r");
externalOotFile.delete();
File externalAssetsFolder = new File(getExternalFilesDir(null), "assets");
deleteRecursive(externalAssetsFolder);
// called from native code through JNI where necessary
public String getExternalAssetsPath() {
// the original location, /storage/emulated/0/Android/data/com.dishii.mm/files,
// can be the fallback if the user denies the permission
String externalAssetsPath = getExternalFilesDir(null).getAbsolutePath();
}
if (!permissionPopupWasDeclined) {
requestSpecialExternalStoragePermission();
}
private void deleteRecursive(File fileOrDirectory) {
if (fileOrDirectory.isDirectory()) {
for (File child : fileOrDirectory.listFiles()) {
deleteRecursive(child);
while(permissionPopupIsOpen) {
// Do nothing until a permission is chosen
try {
Thread.sleep(250);
} catch (InterruptedException e) {
// do nothing
}
}
fileOrDirectory.delete();
if (hasSpecialExternalStoragePermission) {
// /storage/emulated/0/com.dishii.mm, also mounted at /sdcard/com.dishii.mm
externalAssetsPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + getApplicationContext().getPackageName();
}
if (!hasInstalledExternalAssetFiles) {
setupFiles(externalAssetsPath);
}
return externalAssetsPath;
}
// Request the special external storage permission
private void requestSpecialExternalStoragePermission() {
// Android 5 or older
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) {
hasSpecialExternalStoragePermission = true;
return;
}
// Android 10 or older
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
if (checkSelfPermission("android.permission.WRITE_EXTERNAL_STORAGE") == PackageManager.PERMISSION_GRANTED) {
hasSpecialExternalStoragePermission = true;
return;
}
// Check if storage permission is granted
private boolean hasStoragePermission() {
return ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED;
}
requestPermissions(new String[]{"android.permission.WRITE_EXTERNAL_STORAGE"}, SPECIAL_STORAGE_PERMISSION_REQUEST_CODE);
permissionPopupIsOpen = true;
return;
}
// Request storage permission
private void requestStoragePermission() {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE},
STORAGE_PERMISSION_REQUEST_CODE);
// Android 11 or newer
if (Environment.isExternalStorageManager()) {
hasSpecialExternalStoragePermission = true;
return;
}
try {
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.addCategory("android.intent.category.DEFAULT");
intent.setData(Uri.parse(String.format("package:%s", getApplicationContext().getPackageName())));
startActivityForResult(intent, SPECIAL_STORAGE_PERMISSION_REQUEST_CODE);
} catch (Exception e) {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
startActivityForResult(intent, SPECIAL_STORAGE_PERMISSION_REQUEST_CODE);
}
permissionPopupIsOpen = true;
}
// Handle permission request result
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == STORAGE_PERMISSION_REQUEST_CODE) {
if (requestCode == SPECIAL_STORAGE_PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
setupFiles();
hasSpecialExternalStoragePermission = true;
} else {
// Permission denied, handle accordingly (e.g., show a message)
permissionPopupWasDeclined = true;
}
permissionPopupIsOpen = false;
}
}
private void setupFiles(){
//Copy assets folder for rom extraction
File externalAssetsDir = new File(getExternalFilesDir(null), "assets");
if (!externalAssetsDir.exists()) {
try {
externalAssetsDir.mkdirs();
AssetCopyUtil.copyAssetsToExternal(this, "assets", externalAssetsDir.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}
}
//Create empty mods folder
File externalModsDir = new File(getExternalFilesDir(null), "mods");
externalModsDir.mkdirs();
//Copy 2ship.o2r
File externalSohOtrFile = new File(getExternalFilesDir(null), "2ship.o2r");
if (!externalSohOtrFile.exists()) {
try {
InputStream in = getAssets().open("2ship.o2r");
OutputStream out = new FileOutputStream(externalSohOtrFile);
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
in.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private void setupFiles(String externalAssetsPath) {
AssetCopyUtil.copyAssetsToExternal(this, externalAssetsPath);
hasInstalledExternalAssetFiles = true;
}
private native void nativeHandleSelectedFile(String filePath);
@@ -156,10 +179,11 @@ public class MainActivity extends SDLActivity{
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 0 && resultCode == RESULT_OK) {
if (requestCode == FILE_HANDLER_REQUEST_CODE && resultCode == RESULT_OK) {
Uri selectedFileUri = data.getData();
String fileName = "MM.z64";
File destinationDirectory = getExternalFilesDir(null); // The second argument can specify a subdirectory, or you can pass null to use the root directory.
String destinationDirectory = getExternalAssetsPath();
File destinationFile = new File(destinationDirectory, fileName);
if (destinationDirectory != null) {
@@ -181,8 +205,20 @@ public class MainActivity extends SDLActivity{
}
nativeHandleSelectedFile(destinationFile.getPath());
}
if (requestCode == SPECIAL_STORAGE_PERMISSION_REQUEST_CODE) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R /* Android 11 or newer */) {
if (Environment.isExternalStorageManager()) {
hasSpecialExternalStoragePermission = true;
} else {
permissionPopupWasDeclined = true;
}
}
permissionPopupIsOpen = false;
}
}
// called from native code through JNI where necessary
public void openFilePicker() {
// Create an Intent to open the file picker dialog
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
@@ -192,13 +228,6 @@ public class MainActivity extends SDLActivity{
startActivityForResult(intent, 0);
}
// Check if external storage is available and writable
private boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state);
}
public native void attachController();
public native void detachController();
// Native method for setting button state
@@ -60,8 +60,8 @@ import java.util.Locale;
public class SDLActivity extends Activity implements View.OnSystemUiVisibilityChangeListener {
private static final String TAG = "SDL";
private static final int SDL_MAJOR_VERSION = 2;
private static final int SDL_MINOR_VERSION = 30;
private static final int SDL_MICRO_VERSION = 5;
private static final int SDL_MINOR_VERSION = 32;
private static final int SDL_MICRO_VERSION = 0;
/*
// Display InputType.SOURCE/CLASS of events and devices
//
@@ -89,7 +89,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
| InputDevice.SOURCE_CLASS_POSITION
| InputDevice.SOURCE_CLASS_TRACKBALL);
if (s2 != 0) cls += "Some_Unkown";
if (s2 != 0) cls += "Some_Unknown";
s2 = s_copy & InputDevice.SOURCE_ANY; // keep source only, no class;
@@ -163,7 +163,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
if (s == FLAG_TAINTED) src += " FLAG_TAINTED";
s2 &= ~FLAG_TAINTED;
if (s2 != 0) src += " Some_Unkown";
if (s2 != 0) src += " Some_Unknown";
Log.v(TAG, prefix + "int=" + s_copy + " CLASS={" + cls + " } source(s):" + src);
}
@@ -274,7 +274,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
// "SDL2_mixer",
// "SDL2_net",
// "SDL2_ttf",
"2ship"
"main"
};
}
@@ -790,6 +790,9 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
SDLActivity.mFullscreenModeActive = false;
}
if (Build.VERSION.SDK_INT >= 28 /* Android 9 (Pie) */) {
window.getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
}
}
} else {
Log.e(TAG, "error handling message, getContext() returned no Activity");
+2 -2
View File
@@ -6,7 +6,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.3'
classpath 'com.android.tools.build:gradle:8.8.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -20,6 +20,6 @@ allprojects {
}
}
task clean(type: Delete) {
tasks.register('clean', Delete) {
delete rootProject.buildDir
}
+5
View File
@@ -18,3 +18,8 @@ org.gradle.jvmargs=-Xmx1536m
# org.gradle.parallel=true
android.useAndroidX=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false
# set to Release or RelWithDebInfo to avoid slow mm.o2r generation
elfBuildType=Debug
+1 -1
View File
@@ -1,6 +1,6 @@
#Thu Nov 11 18:20:34 PST 2021
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
+2
View File
@@ -148,6 +148,8 @@ file(COPY "${CMAKE_SOURCE_DIR}/OTRExporter/CFG/filelists/" DESTINATION "${CMAKE_
file(COPY "${CMAKE_SOURCE_DIR}/OTRExporter/CFG/ActorList_MM.txt" DESTINATION "${CMAKE_SOURCE_DIR}/Android/app/src/main/assets/assets/extractor/symbols")
file(COPY "${CMAKE_SOURCE_DIR}/OTRExporter/CFG/ObjectList_MM.txt" DESTINATION "${CMAKE_SOURCE_DIR}/Android/app/src/main/assets/assets/extractor/symbols")
file(COPY "${CMAKE_SOURCE_DIR}/OTRExporter/CFG/SymbolMap_MM.txt" DESTINATION "${CMAKE_SOURCE_DIR}/Android/app/src/main/assets/assets/extractor/symbols")
file(MAKE_DIRECTORY "${CMAKE_SOURCE_DIR}/Android/app/src/main/assets/mods")
file(TOUCH "${CMAKE_SOURCE_DIR}/Android/app/src/main/assets/mods/custom_mod_files_go_here.txt")
endif()
+3 -9
View File
@@ -79,7 +79,7 @@ enum class ButtonId : int {
#ifdef __ANDROID__
const char* javaRomPath = NULL;
static char javaRomPath[4096] = { 0 };
bool fileDialogOpen = false;
//function to be called from C
@@ -92,7 +92,7 @@ void openFilePickerFromC(JNIEnv* env, jobject javaObject) {
// Define the native method to handle the selected file path
extern "C" void JNICALL Java_com_dishii_mm_MainActivity_nativeHandleSelectedFile(JNIEnv* env, jobject obj, jstring filePath) {
const char* filePathStr = env->GetStringUTFChars(filePath, 0);
javaRomPath = strdup(filePathStr); // save filepath to string
snprintf(javaRomPath, sizeof(javaRomPath), "%s", filePathStr);
fileDialogOpen = false;
env->ReleaseStringUTFChars(filePath, filePathStr);
}
@@ -322,7 +322,7 @@ bool Extractor::GetRomPathFromBox() {
//Do nothing until it's chosen
SDL_Delay(250);
}
SDL_Log("%s",javaRomPath);
SDL_Log("javaRomPath: %s", javaRomPath);
selection.push_back(javaRomPath);
#endif
if (selection.empty()) {
@@ -332,12 +332,6 @@ bool Extractor::GetRomPathFromBox() {
mCurrentRomPath = selection[0];
#endif
mCurRomSize = GetCurRomSize();
#ifdef __ANDROID__
if (javaRomPath) {
free((void*)javaRomPath);
javaRomPath = NULL;
}
#endif
return true;
}
+14
View File
@@ -37,7 +37,21 @@ typedef enum FlashSlotFile {
((GET_NEWF(save, 0) == 'Z') && (GET_NEWF(save, 1) == 'E') && (GET_NEWF(save, 2) == 'L') && \
(GET_NEWF(save, 3) == 'D') && (GET_NEWF(save, 4) == 'A') && (GET_NEWF(save, 5) == '3'))
#if !defined(__ANDROID__)
const std::filesystem::path savesFolderPath(Ship::Context::GetPathRelativeToAppDirectory("saves", appShortName));
#else
// in Android, when targeting SurfaceFlinger (ANativeWindow, ART, calling JNI), to avoid problems, one should usually
// try to avoid using C++ features that run port-sensitive code in early initialization BEFORE main() (SDL_main())
// actually runs, like __attribute__((constructor)) and method-initialized global variables,
// because it is necessary to wait until after the Java Activity initializes JNI before calling certain methods.
// the Java Activity will call SDL_main() when it is ready to execute native code, and does not expect native application
// code to be running before that point.
std::filesystem::path savesFolderPath;
// call this function early in main() before any other functions that use savesFolderPath are called.
void SaveManager_Init(void) {
savesFolderPath = Ship::Context::GetPathRelativeToAppDirectory("saves", appShortName);
}
#endif
// Migrations
// The idea here is that we can read in any version of the save as generic JSON, then apply migrations
+11
View File
@@ -15,4 +15,15 @@ void SaveManager_SysFlashrom_WriteData(u8* addr, u32 pageNum, u32 pageCount);
s32 SaveManager_SysFlashrom_ReadData(void* addr, u32 pageNum, u32 pageCount);
#endif
#if defined(__ANDROID__)
#ifdef __cplusplus
extern "C"
{
#endif
void SaveManager_Init();
#ifdef __cplusplus
}
#endif
#endif
#endif // SAVE_MANAGER_H
+8
View File
@@ -15,6 +15,10 @@
#include "stackcheck.h"
#include "BenPort.h"
#if defined(__ANDROID__)
#include "SaveManager/SaveManager.h"
#endif
// Variables are put before most headers as a hacky way to bypass bss reordering
OSMesgQueue sSerialEventQueue;
OSMesg sSerialMsgBuf[1];
@@ -75,6 +79,10 @@ void SDL_main(int argc, char** argv /* void* arg*/) {
setlocale(LC_ALL, ".UTF8");
#endif // _WIN32
#if defined(__ANDROID__)
SaveManager_Init();
#endif
InitOTR();
Heaps_Alloc();