Merge pull request #145 from kichikuou/android-upstream

Android support
This commit is contained in:
Nunuhara Cabbage
2024-03-16 09:10:40 -07:00
committed by GitHub
7 changed files with 386 additions and 10 deletions
+183
View File
@@ -0,0 +1,183 @@
# CMakeLists.txt for Android build
cmake_minimum_required(VERSION 3.14)
project(xsystem4 LANGUAGES C)
set(CMAKE_C_STANDARD 11)
include(FetchContent)
FetchContent_Declare(
cglm
URL https://github.com/recp/cglm/archive/refs/tags/v0.9.2.tar.gz
URL_HASH SHA1=cb8472aa8c2ab67b66378dbaf10c2c7368d4e4c3
)
FetchContent_Declare(
SDL
URL https://github.com/libsdl-org/SDL/releases/download/release-2.30.1/SDL2-2.30.1.tar.gz
URL_HASH SHA1=9d502c495f3aa2d15446376e835a5e561ac32897
)
FetchContent_Declare(
freetype
GIT_REPOSITORY https://github.com/freetype/freetype.git
GIT_TAG VER-2-13-2
)
FetchContent_MakeAvailable(cglm SDL freetype)
add_library(ffi STATIC IMPORTED)
set_target_properties(ffi PROPERTIES
IMPORTED_LOCATION ${CMAKE_STAGING_PREFIX}/lib/libffi.a
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_STAGING_PREFIX}/include)
find_package(SndFile REQUIRED)
add_subdirectory(subprojects/libsys4)
add_library(xsystem4 SHARED)
target_compile_definitions(xsystem4 PRIVATE
_DEFAULT_SOURCE
USE_GLES
)
target_include_directories(xsystem4 PRIVATE include)
execute_process(
COMMAND git rev-parse --short HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE VCS_TAG
OUTPUT_STRIP_TRAILING_WHITESPACE)
configure_file(src/version.h.in version.h)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
target_sources(xsystem4 PRIVATE
src/audio.c
src/audio_meta.c
src/audio_mixer.c
src/asset_manager.c
src/base64.c
src/cJSON.c
src/draw.c
src/effect.c
src/ffi.c
src/font_freetype.c
src/font_fnl.c
src/format.c
src/hacks.c
src/heap.c
src/id_pool.c
src/input.c
src/json.c
src/movie_plmpeg.c
src/msgqueue.c
src/page.c
src/resume.c
src/savedata.c
src/scene.c
src/sprite.c
src/swf.c
src/system4.c
src/text.c
src/util.c
src/video.c
src/vm.c
src/3d/debug.c
src/3d/model.c
src/3d/parser.c
src/3d/particle.c
src/3d/reign.c
src/3d/renderer.c
src/dungeon/dgn.c
src/dungeon/dtx.c
src/dungeon/dungeon.c
src/dungeon/map.c
src/dungeon/polyobj.c
src/dungeon/renderer.c
src/dungeon/skybox.c
src/dungeon/tes.c
src/parts/construction.c
src/parts/debug.c
src/parts/flash.c
src/parts/input.c
src/parts/motion.c
src/parts/parts.c
src/parts/render.c
src/parts/text.c
src/parts/save.c
src/hll/ACXLoader.c
src/hll/ADVSYS.c
src/hll/AliceLogo.c
src/hll/AliceLogo2.c
src/hll/AliceLogo3.c
src/hll/AliceLogo4.c
src/hll/AliceLogo5.c
src/hll/AnteaterADVEngine.c
src/hll/BanMisc.c
src/hll/Bitarray.c
src/hll/CGManager.c
src/hll/CharSpriteManager.c
src/hll/ChipmunkSpriteEngine.c
src/hll/ChrLoader.c
src/hll/CommonSystemData.c
src/hll/Confirm.c
src/hll/Confirm2.c
src/hll/Confirm3.c
src/hll/CrayfishLogViewer.c
src/hll/Cursor.c
src/hll/Data.c
src/hll/DataFile.c
src/hll/DrawDungeon.c
src/hll/DrawGraph.c
src/hll/DrawMovie.c
src/hll/DrawPluginManager.c
src/hll/DrawSimpleText.c
src/hll/File.c
src/hll/File2.c
src/hll/FileOperation.c
src/hll/FillAngle.c
src/hll/GoatGUIEngine.c
src/hll/Gpx2Plus.c
src/hll/GUIEngine.c
src/hll/iarray.c
src/hll/IbisInputEngine.c
src/hll/InputDevice.c
src/hll/InputString.c
src/hll/KiwiSoundEngine.c
src/hll/LoadCG.c
src/hll/MainEXFile.c
src/hll/MainSurface.c
src/hll/MarmotModelEngine.c
src/hll/Math.c
src/hll/MapLoader.c
src/hll/MenuMsg.c
src/hll/MonsterInfo.c
src/hll/MsgLogManager.c
src/hll/MsgLogViewer.c
src/hll/MsgSkip.c
src/hll/OutputLog.c
src/hll/PassRegister.c
src/hll/PartsEngine.c
src/hll/PlayDemo.c
src/hll/PlayMovie.c
src/hll/ReignEngine.c
src/hll/SACT2.c
src/hll/SengokuRanceFont.c
src/hll/SoundFilePlayer.c
src/hll/StoatSpriteEngine.c
src/hll/StretchHelper.c
src/hll/SystemService.c
src/hll/SystemServiceEx.c
src/hll/Timer.c
src/hll/Toushin3Loader.c
src/hll/VSFile.c
)
target_link_libraries(xsystem4 PRIVATE
m z SDL2 freetype ffi GLESv3 cglm SndFile::sndfile sys4)
target_compile_options(xsystem4 PRIVATE -Wno-unused-parameter)
install(TARGETS xsystem4 SDL2 freetype)
+5
View File
@@ -129,8 +129,13 @@ void vm_stack_trace(void);
_Noreturn void _vm_error(const char *fmt, ...);
_Noreturn void vm_exit(int code);
#ifdef __ANDROID__
// Report the error with a message box and exit.
#define VM_ERROR ERROR
#else
#define VM_ERROR(fmt, ...) \
_vm_error("*ERROR*(%s:%s:%d): " fmt "\n", __FILE__, __func__, __LINE__, ##__VA_ARGS__)
#endif
#ifdef VM_PRIVATE
#include "little_endian.h"
+21
View File
@@ -18,6 +18,7 @@
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_BITMAP_H
#include <SDL.h>
#include "system4.h"
#include "system4/hashtable.h"
@@ -188,13 +189,33 @@ static float ft_font_size_char(struct font_size *size, uint32_t code)
struct font *ft_font_load(const char *path)
{
struct font_ft *font = xcalloc(1, sizeof(struct font_ft));
#ifdef __ANDROID__
// On Android, path may be an asset name which FT_New_Face cannot read
// directly, so use SDL_LoadFile to load the content into memory.
size_t size;
void *buf = SDL_LoadFile(path, &size);
if (!buf) {
free(font);
return NULL;
}
if (FT_New_Memory_Face(ft_lib, buf, size, 0, &font->font)) {
free(buf);
free(font);
return NULL;
}
#else
if (FT_New_Face(ft_lib, path, 0, &font->font)) {
free(font);
return NULL;
}
#endif
if (!font->font->charmap) {
WARNING("Font '%s' doesn't contain unicode charmap", path);
#ifdef __ANDROID__
free(buf);
#endif
free(font);
return NULL;
}
+154 -5
View File
@@ -18,6 +18,7 @@
#include <time.h>
#include <SDL.h>
#include "system4.h"
#include "queue.h"
#include "gfx/gfx.h"
#include "gfx/private.h"
#include "input.h"
@@ -145,6 +146,30 @@ bool keyboard_focus = true;
#define MAX_CONTROLLERS 4
static SDL_GameController *controllers[MAX_CONTROLLERS];
#ifdef __ANDROID__
struct deferred_keyevent {
STAILQ_ENTRY(deferred_keyevent) entry;
SDL_Event e;
};
static STAILQ_HEAD(deferred_keyevent_queue, deferred_keyevent) deferred_keyevent_queue =
STAILQ_HEAD_INITIALIZER(deferred_keyevent_queue);
static uint32_t last_keyevent_timestamp;
#define DEFERRED_KEY_DELAY 10
#endif
// Stores a mouse button event synthesized from a touch event (valid if
// .timestamp != 0). We defer such events to prevent games from handling
// button down events before reading the pointer position.
static SDL_MouseButtonEvent deferred_synthetic_mouse_event;
#define SYNTHETIC_MOUSE_EVENT_DELAY 50
static uint32_t long_touch_start_timestamp;
static SDL_FRect long_touch_finger_rect;
#define LONG_TOUCH_DURATION 1000
static float scroll_gesture_y;
#define SCROLL_GESTURE_SENSITIVITY 0.02f // 2% of screen height
static enum sact_keycode sdl_to_sact_button(int button)
{
switch (button) {
@@ -224,6 +249,21 @@ static void mouse_event(SDL_MouseButtonEvent *e)
#endif
}
static void synthetic_mouse_event(SDL_MouseButtonEvent *e)
{
if (e->state == SDL_PRESSED) {
// Touch outside the viewport is treated as right-click.
SDL_Point p = { .x = e->x, .y = e->y };
if (SDL_PointInRect(&p, &sdl.viewport))
key_state[VK_LBUTTON] = true;
else
key_state[VK_RBUTTON] = true;
} else {
key_state[VK_LBUTTON] = false;
key_state[VK_RBUTTON] = false;
}
}
#define JOYAXIS_DEADZONE 13500
enum joyaxis_axis {
@@ -475,8 +515,47 @@ void clear_editing_handler(void)
editing_handler = NULL;
}
static void fire_deferred_events(void)
{
uint32_t now = SDL_GetTicks();
// Flush the deferred mouse button event if it's older than 50ms.
if (deferred_synthetic_mouse_event.timestamp &&
deferred_synthetic_mouse_event.timestamp + SYNTHETIC_MOUSE_EVENT_DELAY < now) {
synthetic_mouse_event(&deferred_synthetic_mouse_event);
deferred_synthetic_mouse_event.timestamp = 0;
}
// Long touch emulates pressing the Ctrl key.
if (long_touch_start_timestamp && long_touch_start_timestamp + LONG_TOUCH_DURATION < now) {
key_state[VK_LBUTTON] = false;
key_state[VK_CONTROL] = true;
}
#ifdef __ANDROID__
// Fire the deferred keyboard and text input events.
while (!STAILQ_EMPTY(&deferred_keyevent_queue) && now >= last_keyevent_timestamp + DEFERRED_KEY_DELAY) {
struct deferred_keyevent *ev = STAILQ_FIRST(&deferred_keyevent_queue);
switch (ev->e.type) {
case SDL_TEXTINPUT:
if (input_handler)
input_handler(ev->e.text.text);
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
last_keyevent_timestamp = now;
key_event(&ev->e.key, ev->e.type == SDL_KEYDOWN);
break;
}
STAILQ_REMOVE_HEAD(&deferred_keyevent_queue, entry);
free(ev);
}
#endif
}
void handle_events(void)
{
fire_deferred_events();
SDL_Event e;
while (SDL_PollEvent(&e)) {
switch (e.type) {
@@ -509,18 +588,78 @@ void handle_events(void)
case SDL_KEYDOWN:
if (e.key.keysym.scancode == SDL_SCANCODE_F9)
vm_stack_trace();
key_event(&e.key, true);
break;
// fallthrough
case SDL_KEYUP:
key_event(&e.key, false);
#ifdef __ANDROID__
if (input_handler && e.key.timestamp < last_keyevent_timestamp + DEFERRED_KEY_DELAY) {
// Input from virtual keyboard is sent as consecutive
// SDL_KEYDOWN and SDL_KEYUP events. To give the game a chance
// to see the previous key event, delay the event.
struct deferred_keyevent *ev = xmalloc(sizeof(struct deferred_keyevent));
ev->e = e;
STAILQ_INSERT_TAIL(&deferred_keyevent_queue, ev, entry);
} else {
last_keyevent_timestamp = e.key.timestamp;
key_event(&e.key, e.type == SDL_KEYDOWN);
}
#else
key_event(&e.key, e.type == SDL_KEYDOWN);
#endif
break;
case SDL_MOUSEBUTTONUP:
case SDL_MOUSEBUTTONDOWN:
mouse_event(&e.button);
if (e.button.which == SDL_TOUCH_MOUSEID) {
if (deferred_synthetic_mouse_event.timestamp)
synthetic_mouse_event(&deferred_synthetic_mouse_event);
deferred_synthetic_mouse_event = e.button;
} else {
mouse_event(&e.button);
}
break;
case SDL_MOUSEWHEEL:
wheel_dir = e.wheel.y;
break;
case SDL_FINGERDOWN:
if (SDL_GetNumTouchFingers(e.tfinger.touchId) >= 2) {
// The user is about to start a multi-touch gesture.
key_state[VK_LBUTTON] = false;
key_state[VK_RBUTTON] = false;
deferred_synthetic_mouse_event.timestamp = 0;
long_touch_start_timestamp = 0;
scroll_gesture_y = -1.0f;
break;
}
long_touch_start_timestamp = e.tfinger.timestamp;
// Movement within this rect (1% of the screen size from the touch
// start position) will be ignored.
long_touch_finger_rect = (SDL_FRect) {
.x = e.tfinger.x - 0.01,
.y = e.tfinger.y - 0.01,
.w = 0.02,
.h = 0.02
};
break;
case SDL_FINGERMOTION:
if (SDL_PointInFRect(&(SDL_FPoint){ e.tfinger.x, e.tfinger.y }, &long_touch_finger_rect))
break;
// Cancel the timer only if Ctrl emulation has not already started.
if (!key_state[VK_CONTROL])
long_touch_start_timestamp = 0;
break;
case SDL_FINGERUP:
long_touch_start_timestamp = 0;
key_state[VK_CONTROL] = false;
break;
case SDL_MULTIGESTURE:
if (e.mgesture.numFingers == 2) {
if (scroll_gesture_y < 0.0f)
scroll_gesture_y = e.mgesture.y;
float dy = scroll_gesture_y - e.mgesture.y;
if (dy * dy > SCROLL_GESTURE_SENSITIVITY * SCROLL_GESTURE_SENSITIVITY) {
wheel_dir = dy < 0 ? 1 : -1; // Swipe up to scroll down.
scroll_gesture_y = e.mgesture.y;
}
}
case SDL_CONTROLLERDEVICEADDED:
if (e.cdevice.which < MAX_CONTROLLERS)
controllers[e.cdevice.which] = SDL_GameControllerOpen(e.cdevice.which);
@@ -533,8 +672,18 @@ void handle_events(void)
controller_button_event(&e.cbutton);
break;
case SDL_TEXTINPUT:
if (input_handler)
if (!input_handler) {
break;
#ifdef __ANDROID__
} else if (!STAILQ_EMPTY(&deferred_keyevent_queue)) {
// The order of key events and text events must be preserved.
struct deferred_keyevent *ev = xmalloc(sizeof(struct deferred_keyevent));
ev->e = e;
STAILQ_INSERT_TAIL(&deferred_keyevent_queue, ev, entry);
#endif
} else {
input_handler(e.text.text);
}
break;
case SDL_TEXTEDITING:
if (editing_handler)
+11
View File
@@ -401,11 +401,22 @@ static void windows_error_handler(const char *msg)
}
#endif
#ifdef __ANDROID__
static void android_error_handler(const char *msg)
{
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "xsystem4", msg, NULL);
}
#endif
int main(int argc, char *argv[])
{
#ifdef _WIN32
sys_error_handler = windows_error_handler;
#endif
#ifdef __ANDROID__
sys_error_handler = android_error_handler;
#endif
initialize_instructions();
char *ainfile;
int err = AIN_SUCCESS;
+11 -4
View File
@@ -68,11 +68,11 @@ struct texture main_surface;
static GLchar *read_shader_file(const char *path)
{
GLchar *source = file_read(path, NULL);
GLchar *source = SDL_LoadFile(path, NULL);
if (!source) {
char full_path[PATH_MAX];
snprintf(full_path, PATH_MAX, XSYS4_DATA_DIR "/%s", path);
source = file_read(full_path, NULL);
source = SDL_LoadFile(full_path, NULL);
if (!source)
ERROR("Failed to load shader file %s", full_path, strerror(errno));
}
@@ -98,7 +98,7 @@ GLuint gfx_load_shader_file(const char *path, GLenum type)
glGetShaderInfoLog(shader, len, NULL, infolog);
ERROR("Failed to compile shader %s: %s", path, infolog);
}
free((char*)source[1]);
SDL_free((char*)source[1]);
return shader;
}
@@ -200,12 +200,19 @@ int gfx_init(void)
#endif
sdl.format = SDL_AllocFormat(SDL_PIXELFORMAT_RGBA32);
uint32_t window_flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN;
#ifdef __ANDROID__
window_flags |= SDL_WINDOW_FULLSCREEN;
SDL_SetHint(SDL_HINT_ORIENTATIONS, "LandscapeLeft LandscapeRight");
#else
window_flags |= SDL_WINDOW_RESIZABLE;
#endif
sdl.window = SDL_CreateWindow("XSystem4",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
config.view_width,
config.view_height,
SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
window_flags);
if (!sdl.window)
ERROR("SDL_CreateWindow failed: %s", SDL_GetError());