Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 33 additions & 9 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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")

Expand All @@ -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()
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Comment thread
Try marked this conversation as resolved.
Outdated
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)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this change has no effect

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restored the single install(TARGETS Gothic2Notr Spacer DESTINATION bin) call.

if(NOT ANDROID)
install(TARGETS Spacer DESTINATION bin)
endif()
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ There are ongoing efforts to support parts of it to make at least some popular m
- Ninja

## Build Instructions
### Android

The basic ARM64 APK build and ADB game-file setup are documented in [android/README.md](android/README.md).
Comment thread
Try marked this conversation as resolved.
Outdated

### Linux
Install dependencies:
* Ubuntu 20.04/22.04 and their derived distros
Expand Down
17 changes: 17 additions & 0 deletions android/AndroidManifest.xml
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>
32 changes: 32 additions & 0 deletions android/README.md
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`.
28 changes: 28 additions & 0 deletions android/java/org/opengothic/app/GothicActivity.java
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);
Comment thread
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);
}
}
8 changes: 4 additions & 4 deletions common/utils/crashlog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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__)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, crash-logging wont work by look of this code?
That quite surprising, based that android still have compatibility with unix/linux

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The NDK lacks <execinfo.h> and backtrace. Android now uses _Unwind_Backtrace and dladdr, including module-relative addresses for offline symbolization.

tracebackLinux(std::cout);
#endif
std::cout << std::endl;
Expand All @@ -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();
Expand All @@ -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;
Expand Down
59 changes: 59 additions & 0 deletions game/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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();

Expand Down Expand Up @@ -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();
Expand All @@ -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);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tempest::Log already implements __android_log_print, so application never really need to relay on OS-specific api

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the application-side Android logging call. Tempest::Log handles logcat output; the README now filters its app tag.

#endif
logFile.write(text,std::strlen(text));
logFile.write("\n",1);
if(mode==Tempest::Log::Error)
Expand Down
2 changes: 1 addition & 1 deletion shader/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ file(GLOB_RECURSE GLSL_SOURCES
"${CMAKE_SOURCE_DIR}/shader/*.glsl")

# GLSL to SPIRV compiler
find_program(GLSLANGVALIDATOR glslangValidator "/opt/homebrew/bin")
find_program(GLSLANGVALIDATOR glslangValidator "/opt/homebrew/bin" NO_CMAKE_FIND_ROOT_PATH)
if(NOT GLSLANGVALIDATOR)
message(FATAL_ERROR "glslangValidator required")
endif()
Expand Down
Loading