4 Commits
Author SHA1 Message Date
izzy2lostandGitHub 5ad3f98ee6 Add files via upload 2025-06-12 22:45:10 -04:00
Robert Kirkman d11c66eea9 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
2025-02-19 18:58:13 -06:00
WaterdishandGitHub d6c503f0e9 Update README.md 2024-12-06 21:15:54 -08:00
WaterdishandGitHub e9a5064e59 Update README.md 2024-11-25 21:06:20 -08:00
22 changed files with 378 additions and 198 deletions
+74
View File
@@ -0,0 +1,74 @@
name: generate-android-apk
on:
workflow_dispatch:
jobs:
generate-2ship-otr:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y ninja-build cmake g++ gcc libzip-dev zipcmp zipmerge ziptool
- name: Install latest SDL
run: |
if [ ! -d "SDL2-2.28.5" ]; then
wget https://github.com/libsdl-org/SDL/releases/download/release-2.28.5/SDL2-2.28.5.tar.gz
tar -xzf SDL2-2.28.5.tar.gz
fi
cd SDL2-2.28.5
./configure --enable-hidapi-libusb
make -j 10
sudo make install
sudo cp -av /usr/local/lib/libSDL* /lib/x86_64-linux-gnu/
- name: Install latest tinyxml2
run: |
sudo apt-get remove libtinyxml2-dev
if [ ! -d "tinyxml2-10.0.0" ]; then
wget https://github.com/leethomason/tinyxml2/archive/refs/tags/10.0.0.tar.gz
tar -xzf 10.0.0.tar.gz
fi
cd tinyxml2-10.0.0
mkdir -p build
cd build
cmake ..
make
sudo make install
- name: Generate 2ship.o2r
run: |
cmake --no-warn-unused-cli -H. -Bbuild-cmake -GNinja -DCMAKE_BUILD_TYPE:STRING=Release
cmake --build build-cmake --config Release --target Generate2ShipOtr -j3
- uses: actions/upload-artifact@v4
with:
name: 2ship.o2r
path: 2ship.o2r
retention-days: 1
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 APK
run: |
cd Android/
./gradlew assembleDebug -P elfBuildType=RelWithDebInfo
mv app/build/outputs/apk/debug/app-debug.apk ../2ship.apk
- name: Upload APK artifact
uses: actions/upload-artifact@v4
with:
name: 2ship-apk
path: 2ship.apk
+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
+23 -25
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()
+1 -3
View File
@@ -4,8 +4,6 @@ A port of 2 Ship 2 Harkinian to Android. <br>
Original Repository: https://github.com/HarbourMasters/2ship2harkinian <br>
<br>
NOTE: Controller only. No touch controls yet except for in the enhancements menu. <br>
Supported (probably): Android 7+ (OpenGL ES 3.0+ required) <br>
Tested On: Android 14 <br>
@@ -32,7 +30,7 @@ Q: The GUI scaling is too big/too small. <br>
A: There is no GUI scaling option implemented yet. This will come in a future update. <br><br>
Q: Gyro Aim? <br>
A: It will come once it works in the base 2 Ship 2 Harkinian. <br> <br>
A: It works. You just need to press any controller button when it asks for input. It will default to your phone's gyro if the controller doesn't support it. <br> <br>
Q: My controller is not doing anything. <br>
A: Close the Enhancements Menu. If the Enhancements Menu is not open, open it with the Android back button and check if it is detected in Settings->Controller->Controller Mapping. If it is, press refresh. <br><br>
+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

Some files were not shown because too many files have changed in this diff Show More