Repository navigation
Add basic Android APK integration #1001
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
11c8721
ae55876
01dbdb1
714d4a0
aefda87
8351fde
388461a
e9b320d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,8 +12,12 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) | |
| set(CMAKE_SKIP_RPATH ON) | ||
|
|
||
| add_library(${PROJECT_NAME} STATIC) | ||
| add_executable(Gothic2Notr) | ||
| add_executable(Spacer) | ||
| if(ANDROID) | ||
| add_library(Gothic2Notr SHARED) | ||
| else() | ||
| add_executable(Gothic2Notr) | ||
| add_executable(Spacer) | ||
| endif() | ||
|
|
||
| if(MSVC) | ||
| add_definitions(-D_USE_MATH_DEFINES) | ||
|
|
@@ -77,7 +81,9 @@ add_subdirectory(shader) | |
| target_link_libraries(${PROJECT_NAME} GothicShaders) | ||
|
|
||
| # editor icons and such | ||
| add_subdirectory(spacer/assets) | ||
| if(NOT ANDROID) | ||
| add_subdirectory(spacer/assets) | ||
| endif() | ||
|
|
||
| include_directories("common") | ||
|
|
||
|
|
@@ -92,6 +98,8 @@ endif() | |
|
|
||
| if(WIN32) | ||
| target_link_libraries(${PROJECT_NAME} shlwapi DbgHelp) | ||
| elseif(ANDROID) | ||
| target_link_libraries(${PROJECT_NAME} dl) | ||
| elseif(UNIX) | ||
| target_link_libraries(${PROJECT_NAME} -lpthread -ldl) | ||
| endif() | ||
|
|
@@ -178,7 +186,7 @@ if(WIN32) | |
| endif() | ||
|
|
||
| # in debug mode, enable sanitizers | ||
| if(${CMAKE_BUILD_TYPE} MATCHES "Debug") | ||
| if(${CMAKE_BUILD_TYPE} MATCHES "Debug" AND NOT ANDROID) | ||
| add_compile_options(-fsanitize=address) | ||
| add_compile_options(-fsanitize=leak) | ||
| add_link_options (-fsanitize=address) | ||
|
|
@@ -197,13 +205,29 @@ if(IOS) | |
| XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "iPhone Developer") | ||
| endif() | ||
|
|
||
| target_sources(Gothic2Notr PRIVATE game/main.cpp icon.rc) | ||
| target_sources(Gothic2Notr PRIVATE game/main.cpp) | ||
| if(WIN32) | ||
| target_sources(Gothic2Notr PRIVATE icon.rc) | ||
| endif() | ||
| target_link_libraries(Gothic2Notr PRIVATE OpenGothic) | ||
|
|
||
| # editor | ||
| target_sources(Spacer PRIVATE ${SPACER_SOURCES} spacer/main.cpp) | ||
| target_include_directories(Spacer PRIVATE "spacer") | ||
| target_link_libraries(Spacer PRIVATE OpenGothic SpacerAssets) | ||
| if(NOT ANDROID) | ||
| target_sources(Spacer PRIVATE ${SPACER_SOURCES} spacer/main.cpp) | ||
| target_include_directories(Spacer PRIVATE "spacer") | ||
| target_link_libraries(Spacer PRIVATE OpenGothic SpacerAssets) | ||
| endif() | ||
|
|
||
| if(ANDROID) | ||
| add_android_apk(OpenGothic-apk | ||
| PACKAGE_NAME org.opengothic.app | ||
| CODE Gothic2Notr | ||
| MANIFEST android/AndroidManifest.xml | ||
| JAVA_DIRS android/java) | ||
| endif() | ||
|
|
||
| # installation | ||
| install(TARGETS Gothic2Notr Spacer DESTINATION bin) | ||
| install(TARGETS Gothic2Notr DESTINATION bin) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this change has no effect
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Restored the single |
||
| if(NOT ANDROID) | ||
| install(TARGETS Spacer DESTINATION bin) | ||
| endif() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android"> | ||
| <uses-feature android:name="android.hardware.vulkan.level" android:version="0" android:required="true" /> | ||
| <uses-feature android:name="android.hardware.vulkan.version" android:version="0x00401000" android:required="true" /> | ||
| <application android:label="OpenGothic" android:hasCode="true" android:allowBackup="false" | ||
| android:theme="@android:style/Theme.Material.NoActionBar.Fullscreen"> | ||
| <activity android:name="org.opengothic.app.GothicActivity" android:exported="true" | ||
| android:screenOrientation="landscape" | ||
| android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize|screenLayout|smallestScreenSize|density|uiMode"> | ||
| <meta-data android:name="android.app.lib_name" android:value="Gothic2Notr" /> | ||
| <intent-filter> | ||
| <action android:name="android.intent.action.MAIN" /> | ||
| <category android:name="android.intent.category.LAUNCHER" /> | ||
| </intent-filter> | ||
| </activity> | ||
| </application> | ||
| </manifest> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| # Android | ||
|
|
||
| An ARM64 NativeActivity APK using Tempest's Android backend and generated Gradle project. This is the basic platform integration; touch controls, controller input and game-file importing are not included yet. Android window destruction closes the application, including when backgrounding or locking destroys its surface. | ||
|
|
||
| ## Build | ||
|
|
||
| Install JDK 17, Gradle 8.9, CMake 3.22.1 or newer, Ninja and the Android SDK with SDK 35, build-tools 35.0.0, NDK 27.0.12077973 and SDK CMake 3.22.1. Set `JAVA_HOME`, `ANDROID_HOME` and `VULKAN_SDK`. Put Gradle, Ninja and the host Vulkan SDK's `glslangValidator` on `PATH`. Vulkan headers come from the host SDK; Android links the NDK's Vulkan loader. | ||
|
|
||
| Clone with `git clone --recursive https://github.com/Try/OpenGothic.git`, or run `git submodule update --init --recursive` in an existing checkout. Replace `/path/to/ndk` below with the NDK installation directory. The same commands work in PowerShell and a Linux shell. | ||
|
|
||
| ```sh | ||
| cmake -S . -B build/android -G Ninja -DCMAKE_TOOLCHAIN_FILE=/path/to/ndk/build/cmake/android.toolchain.cmake -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=android-24 -DCMAKE_BUILD_TYPE=Release | ||
| cmake --build build/android --target OpenGothic-apk | ||
| ``` | ||
|
|
||
| APK: `build/android/OpenGothic-apk/build/outputs/apk/release/OpenGothic-apk-release.apk`. Use `-DCMAKE_BUILD_TYPE=Debug` for a debuggable APK under `outputs/apk/debug/OpenGothic-apk-debug.apk`. Desktop builds do not invoke the APK helper or require Android tools. | ||
|
|
||
| ## Install and game files | ||
|
|
||
| Use a legally owned Gothic II: Night of the Raven installation. No game files are included in the APK. Copy the installation root, not only its `Data` directory: `Data`, `_work` and `System` must be directly inside `Gothic2`. | ||
|
|
||
| ```sh | ||
| adb install -r build/android/OpenGothic-apk/build/outputs/apk/release/OpenGothic-apk-release.apk | ||
| adb shell mkdir -p /sdcard/Android/data/org.opengothic.app/files/Gothic2 | ||
| adb push "/path/to/Gothic II/." /sdcard/Android/data/org.opengothic.app/files/Gothic2/ | ||
| adb shell am start -n org.opengothic.app/.GothicActivity | ||
| adb logcat -s OpenGothic AndroidRuntime DEBUG | ||
| ``` | ||
|
|
||
| Game files use app-specific external storage, without storage permissions. Logs, saves and writable settings use the app's internal files directory. Uninstalling removes both directories. Ray tracing and mesh shading are disabled for this initial Android build. | ||
|
|
||
| With a debug APK, read the file log using `adb shell run-as org.opengothic.app cat files/log.txt`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package org.opengothic.app; | ||
|
|
||
| import android.app.NativeActivity; | ||
| import android.os.Bundle; | ||
|
|
||
| import java.io.File; | ||
|
|
||
| public class GothicActivity extends NativeActivity { | ||
| static { | ||
| System.loadLibrary("Gothic2Notr"); | ||
| } | ||
|
|
||
| private static native void prepareStorage(String writablePath, String gamePath); | ||
|
|
||
| @Override | ||
| protected void onCreate(Bundle savedInstanceState) { | ||
| File external = getExternalFilesDir(null); | ||
|
Try marked this conversation as resolved.
Outdated
|
||
| if (external == null) { | ||
| throw new IllegalStateException("External application storage is unavailable"); | ||
| } | ||
| File game = new File(external, "Gothic2"); | ||
| if (!game.isDirectory() && !game.mkdirs()) { | ||
| throw new IllegalStateException("Unable to create the Gothic2 installation directory"); | ||
| } | ||
| prepareStorage(getFilesDir().getAbsolutePath(), game.getAbsolutePath()); | ||
| super.onCreate(savedInstanceState); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,7 +14,7 @@ | |
| #include <stacktrace> | ||
| #endif | ||
|
|
||
| #if defined(__LINUX__) || defined(__APPLE__) | ||
| #if (defined(__LINUX__) && !defined(__ANDROID__)) || defined(__APPLE__) | ||
| #include <execinfo.h> // backtrace | ||
| #include <dlfcn.h> // dladdr | ||
| #include <cxxabi.h> // __cxa_demangle | ||
|
|
@@ -136,7 +136,7 @@ void CrashLog::dumpStack(const char *sig, const char *extGpuLog) { | |
| #elif defined(__WINDOWS__) | ||
| traceback.collect(0); | ||
| traceback.log(db, std::cout); | ||
| #elif defined(__LINUX__) || defined(__APPLE__) | ||
| #elif (defined(__LINUX__) && !defined(__ANDROID__)) || defined(__APPLE__) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, crash-logging wont work by look of this code?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The NDK lacks |
||
| tracebackLinux(std::cout); | ||
| #endif | ||
| std::cout << std::endl; | ||
|
|
@@ -150,7 +150,7 @@ void CrashLog::dumpStack(const char *sig, const char *extGpuLog) { | |
| tracebackStd(fout); | ||
| #elif defined(__WINDOWS__) | ||
| traceback.log(db, fout); | ||
| #elif defined(__LINUX__) || defined(__APPLE__) | ||
| #elif (defined(__LINUX__) && !defined(__ANDROID__)) || defined(__APPLE__) | ||
| tracebackLinux(fout); | ||
| #endif | ||
| fout.flush(); | ||
|
|
@@ -169,7 +169,7 @@ void CrashLog::tracebackStd(std::ostream &out) { | |
| } | ||
|
|
||
| void CrashLog::tracebackLinux(std::ostream &out) { | ||
| #if defined(__LINUX__) || defined(__APPLE__) | ||
| #if (defined(__LINUX__) && !defined(__ANDROID__)) || defined(__APPLE__) | ||
| // inspired by https://gist.github.com/fmela/591333/36faca4c2f68f7483cd0d3a357e8a8dd5f807edf (BSD) | ||
| void *callstack[64] = {}; | ||
| char **symbols = nullptr; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,14 @@ | |
| #include "utils/installdetect.h" | ||
| #endif | ||
|
|
||
| #if defined(__ANDROID__) | ||
| #include <android/log.h> | ||
| #include <jni.h> | ||
| #include <filesystem> | ||
| #include <stdexcept> | ||
| #include <string> | ||
| #endif | ||
|
|
||
| #include "utils/crashlog.h" | ||
| #include "mainwindow.h" | ||
| #include "gothic.h" | ||
|
|
@@ -26,6 +34,48 @@ | |
|
|
||
| #include <dmusic.h> | ||
|
|
||
| #if defined(__ANDROID__) | ||
| static std::string androidGamePath; | ||
|
|
||
| static std::string androidPath(JNIEnv* env, jstring path) { | ||
| if(path==nullptr) | ||
| throw std::invalid_argument("Missing application storage path"); | ||
| const char* text = env->GetStringUTFChars(path,nullptr); | ||
| if(text==nullptr) | ||
| throw std::runtime_error("Unable to read application storage path"); | ||
| try { | ||
| std::string result(text); | ||
| env->ReleaseStringUTFChars(path,text); | ||
| return result; | ||
| } | ||
| catch(...) { | ||
| env->ReleaseStringUTFChars(path,text); | ||
| throw; | ||
| } | ||
| } | ||
|
|
||
| static void throwAndroidError(JNIEnv* env, const char* message) { | ||
| if(env->ExceptionCheck()) | ||
| return; | ||
| jclass type = env->FindClass("java/lang/IllegalStateException"); | ||
| if(type!=nullptr) | ||
| env->ThrowNew(type,message); | ||
| } | ||
|
|
||
| extern "C" JNIEXPORT void JNICALL Java_org_opengothic_app_GothicActivity_prepareStorage(JNIEnv* env, jclass, jstring writablePath, jstring gamePath) { | ||
| try { | ||
| std::filesystem::current_path(androidPath(env,writablePath)); | ||
| androidGamePath = androidPath(env,gamePath); | ||
| } | ||
| catch(const std::exception& e) { | ||
| throwAndroidError(env,e.what()); | ||
| } | ||
| catch(...) { | ||
| throwAndroidError(env,"Unable to initialize application storage"); | ||
| } | ||
| } | ||
| #endif | ||
|
|
||
| std::string_view selectDevice(const Tempest::AbstractGraphicsApi& api) { | ||
| auto d = api.devices(); | ||
|
|
||
|
|
@@ -68,6 +118,11 @@ std::unique_ptr<Tempest::AbstractGraphicsApi> mkApi(const CommandLine& g) { | |
| } | ||
|
|
||
| int main(int argc,const char** argv) { | ||
| #if defined(__ANDROID__) | ||
| const char* androidArgs[] = {"Gothic2Notr", "-g", androidGamePath.c_str(), "-rt", "0", "-ms", "0"}; | ||
| argc = sizeof(androidArgs)/sizeof(androidArgs[0]); | ||
| argv = androidArgs; | ||
| #endif | ||
| #if defined(__IOS__) | ||
| { | ||
| auto appdir = InstallDetect::applicationSupportDirectory(); | ||
|
|
@@ -78,6 +133,10 @@ int main(int argc,const char** argv) { | |
| try { | ||
| static Tempest::WFile logFile("log.txt"); | ||
| Tempest::Log::setOutputCallback([](Tempest::Log::Mode mode, const char* text) { | ||
| #if defined(__ANDROID__) | ||
| const int priority = mode==Tempest::Log::Error ? ANDROID_LOG_ERROR : mode==Tempest::Log::Debug ? ANDROID_LOG_DEBUG : ANDROID_LOG_INFO; | ||
| __android_log_write(priority,"OpenGothic",text); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tempest::Log already implements
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed the application-side Android logging call. |
||
| #endif | ||
| logFile.write(text,std::strlen(text)); | ||
| logFile.write("\n",1); | ||
| if(mode==Tempest::Log::Error) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.