diff --git a/.gitignore b/.gitignore
index c55a27cea..5d64770cc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,21 @@
+# Android / Gradle
+.gradle/
+android/launcher/app/build/
+android/launcher/app/jniLibs/
+android/launcher/build/
+android/launcher/.cxx/
+android/launcher/local.properties
+*.apk
+*.aab
+*.jks
+
+# IDE
+.idea/
+*.iml
+.vscode/
+
+# CLAUDE.md is intentionally tracked
+
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 000000000..002ab90eb
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,149 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Project
+
+reVC is a fully reverse-engineered Grand Theft Auto: Vice City (`miami` branch). It builds and runs on Windows, Linux, macOS, FreeBSD, and Android on x86, amd64, arm, and arm64.
+
+## Build Commands
+
+### Linux (Conan + CMake — recommended)
+```
+conan export vendor/librw librw/master@
+mkdir build && cd build
+conan install .. reVC/miami@ -if build -o reVC:audio=openal -o librw:platform=gl3 -o librw:gl3_gfxlib=glfw --build missing -s reVC:build_type=RelWithDebInfo -s librw:build_type=RelWithDebInfo
+conan build .. -if build -bf build -pf package
+```
+
+### Linux (Premake)
+```sh
+./premake5Linux --with-librw gmake2
+cd build && make config=release_x86_64
+```
+
+### Windows (Premake + Visual Studio)
+```bat
+premake-vs2022.cmd
+:: Open build/reVC.sln in Visual Studio, build reVC project
+```
+
+### macOS (Premake)
+```sh
+./premake5Linux --with-librw --os=macosx gmake2
+cd build && make config=release_x86_64
+```
+
+### Build options
+- `--with-librw` — build librw alongside reVC (vs. using system/external)
+- `--with-lto` — enable Link Time Optimization
+- `--with-asan` — enable Address Sanitizer
+- `--with-opus` — enable Opus audio codec support
+- `--no-git-hash` / `--no-full-paths` — reproducible build options
+
+### CMake options
+```
+-DREVC_AUDIO=OAL|MSS # audio backend (default OAL)
+-DREVC_WITH_OPUS=ON|OFF # Opus support
+-DREVC_WITH_SANITIZERS=ON # UBSan
+-DREVC_WITH_ASAN=ON # ASan
+```
+
+## Architecture
+
+reVC is a single monolithic executable. All game code lives under `src/`, organized by subsystem:
+
+| Directory | Purpose |
+|-----------|---------|
+| `src/core/` | Core loop, world manager, streaming, pools, frontend, zones, config |
+| `src/renderer/` | Rendering: HUD, coronas, particles, shadows, weather, timecycle |
+| `src/collision/` | Collision detection and response |
+| `src/entities/` | Entity base classes and managers |
+| `src/peds/` | Pedestrian AI, animation blending, player control |
+| `src/vehicles/` | Vehicle physics, heli, boat, car control |
+| `src/weapons/` | Weapon types, bullet traces, explosions |
+| `src/objects/` | Dynamic world objects |
+| `src/buildings/` | Static building rendering and LOD |
+| `src/animation/` | Animation system (AnimBlend) |
+| `src/audio/` | Audio system with backends in `audio/oal/` (OpenAL), `audio/eax/` |
+| `src/control/` | Gamepad, controller config |
+| `src/math/` | Vector, matrix, quaternion math |
+| `src/modelinfo/` | Model type information registry |
+| `src/save/` | Save/load game state |
+| `src/text/` | GXT text system |
+| `src/rw/` | RenderWare helper/utility code |
+
+### Platform abstraction (`src/skel/`)
+
+The platform layer follows the RenderWare skeleton pattern. Entry point is `main.cpp` → `WinMain`/`main` → `Game::InitialiseOnceAfterRW()`.
+
+- **`skeleton.h`** — public API (events, input, timer, camera). All platform backends implement this interface.
+- **`platform.h`** — platform-specific function declarations (`psInitialize`, `psTimer`, etc.).
+- **`glfw/glfw.cpp`** — primary cross-platform backend (Windows OpenGL, Linux, macOS, BSD)
+- **`sdl2/sdl2.cpp`** — SDL2 backend used on Android (`LIBRW_SDL2` define)
+- **`android/`** — Android-specific JNI bridge (`AndroidMain.cpp`, `JavaWrapper`)
+- **`win/`** — Windows-specific D3D8/D3D9 backend (`RWLIBS` or `USE_D3D9`)
+- **`crossplatform.cpp/.h`** — filesystem path utilities, locale/language detection, `casepath`
+
+### RenderWare abstraction (`src/fakerw/`)
+
+When building with librw (`LIBRW` define), stub headers in `fakerw/` redirect RenderWare API calls through librw. When building with original RW (`RWLIBS`), the real RenderWare SDK headers are used directly.
+
+### Rendering backends (preprocessor-controlled)
+
+- `RWLIBS` — original RenderWare D3D8 (Windows only)
+- `LIBRW` + `RW_D3D9` — librw with Direct3D 9 (Windows only)
+- `LIBRW` + `RW_GL3` — librw with OpenGL 3.x via GLFW or SDL2 (all platforms)
+
+### Audio backends
+
+- `AUDIO_MSS` — Miles Sound System (Windows, requires original MSS DLLs)
+- `AUDIO_OAL` — OpenAL (cross-platform, default)
+
+### Key configuration
+
+`src/core/config.h` defines all pool sizes, entity limits, and compile-time options. Feature toggles and bugfix guards (e.g., `FIX_BUGS`) are also here. Runtime settings live in `reVC.ini` (not the original `gta_vc.set`).
+
+## Coding Conventions
+
+- **C++ standard**: C++11 maximum — do not use C++14 or later features
+- **Types**: Always use project typedefs (`int8`, `uint8`, `int16`, `uint16`, `int32`, `uint32`, `bool`). Never use `unsigned` bare, `char` for non-characters, or Win32 types (`BYTE`, `DWORD`) outside platform-specific code.
+- **Pointers**: `int *ptr;` style (asterisk attached to variable, not type)
+- **Indentation**: TABS only
+- **Brace style**: K&R-ish — brace on next line for functions/structs, same line for control flow. No braces for single-statement bodies. `else` on same line with closing brace.
+- **Variable naming**: Hungarian-notation influenced — `f` prefix for float, `i`/`n` for integer, `b` for boolean, `m_` for private members, `ms_` for private static members.
+- **Magic numbers**: Avoid. Use enums even when the exact meaning is unknown (`FOOBAR_TYPE_4` over `4`).
+
+## PR Guidelines
+
+Accepted contributions:
+- Features that existed in at least one original GTA version
+- Bug fixes (behind `FIX_BUGS` preprocessor guard if fixing original behavior)
+- Un-reversed platform-specific or unused code
+- Making reversed code more accurate (matching original assembly)
+- Cross-platform skeleton/compatibility layer improvements
+- Translation fixes for original languages
+
+No custom/gameplay features unless guarded by preprocessor conditions, and no mod-like additions.
+
+## Android Port (`android-port` branch)
+
+### Build
+```sh
+cd android/launcher && ./gradlew assembleDebug
+```
+Requires NDK 30+, SDK 35+. Prebuilt `.so` files (SDL2, OpenAL, mpg123) are in `vendor/` — run `android/setup_libs.sh` to copy to `jniLibs/`.
+
+### First-launch flow
+1. SAF folder picker → user selects GTA VC directory
+2. Files validated (`models/gta3.img` must exist)
+3. Background copy to `getExternalFilesDir()` via `DocumentsContract` (app-owned files)
+4. Game loads from app-private storage — no permissions needed
+5. Subsequent launches skip picker
+
+### Key changes from upstream
+- `FileMgr.cpp` — `\` → `/` conversion (Windows paths)
+- `sdl2.cpp` — SDL hints, `STORAGE_ROOT` via `SDL_AndroidGetExternalStoragePath`
+- `crossplatform.cpp` — `casepath` does `opendir`/`readdir`; works because FUSE is case-insensitive
+- `AndroidManifest.xml` — `extractNativeLibs=true` (SDL2 needs filesystem .so), landscape, gamepad-only
+- `CdStream_posix.cpp` — Android uses `statfs` not `statvfs`, pthread mutex instead of semaphore
diff --git a/android/launcher/app/CMakeLists.txt b/android/launcher/app/CMakeLists.txt
index 2d3ee95bb..d1595cdbe 100644
--- a/android/launcher/app/CMakeLists.txt
+++ b/android/launcher/app/CMakeLists.txt
@@ -3,18 +3,17 @@
#
# Description:
# This file defines the root CMake configuration for the reVC – Mobile Edition,
-# targeting the Android platform via the Android NDK. It initializes the build system
-# for the mobile game, integrates shared cross-platform sources, configures compiler settings,
-# and handles dependency resolution.
+# targeting the Android platform via the Android NDK. It sets up prebuilt libraries
+# (SDL2, OpenAL, mpg123) and then includes the main game CMake build.
# ==================================================================================================
cmake_minimum_required(VERSION 3.22.1 FATAL_ERROR)
message(" ")
-message("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
+message("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
message(" * 🌐 reVC – Build Configuration Initialized")
message(" * 🔧 Preparing Android platform modules and environment configuration...")
-message("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
+message("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
message(" ")
# ────────────────────────────────────────────────────────────────────────────────
@@ -38,17 +37,54 @@ get_filename_component(REVC_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}" ABSOLUTE)
set(REVC_GAME_DIR "${REVC_ROOT_DIR}/../../../")
message(" * 📂 Game source directory set to: ${REVC_GAME_DIR}")
+# ────────────────────────────────────────────────────────────────────────────────
+# 📦 Prebuilt Native Libraries for Android
+# ────────────────────────────────────────────────────────────────────────────────
+
+# SDL2
+add_library(SDL2::SDL2 SHARED IMPORTED)
+set_target_properties(SDL2::SDL2 PROPERTIES
+ IMPORTED_LOCATION "${REVC_GAME_DIR}/vendor/sdl2/libs/Android/${ANDROID_ABI}/libSDL2.so"
+ INTERFACE_INCLUDE_DIRECTORIES "${REVC_GAME_DIR}/vendor/sdl2/include"
+)
+message(" * 📦 SDL2 prebuilt: ${ANDROID_ABI}")
+
+# OpenAL
+add_library(OpenAL::OpenAL SHARED IMPORTED)
+set_target_properties(OpenAL::OpenAL PROPERTIES
+ IMPORTED_LOCATION "${REVC_GAME_DIR}/vendor/openal-soft/libs/Android/${ANDROID_ABI}/libopenal.so"
+ INTERFACE_INCLUDE_DIRECTORIES "${REVC_GAME_DIR}/vendor/openal-soft/include"
+)
+message(" * 📦 OpenAL prebuilt: ${ANDROID_ABI}")
+
+# mpg123
+add_library(MPG123::libmpg123 SHARED IMPORTED)
+set_target_properties(MPG123::libmpg123 PROPERTIES
+ IMPORTED_LOCATION "${REVC_GAME_DIR}/vendor/mpg123/lib/Android/${ANDROID_ABI}/libmpg123.so"
+ INTERFACE_INCLUDE_DIRECTORIES "${REVC_GAME_DIR}/vendor/mpg123/include"
+)
+message(" * 📦 mpg123 prebuilt: ${ANDROID_ABI}")
+
# ────────────────────────────────────────────────────────────────────────────────
# ➕ Add Game Subdirectory If Available
# ────────────────────────────────────────────────────────────────────────────────
if(IS_DIRECTORY "${REVC_GAME_DIR}" AND EXISTS "${REVC_GAME_DIR}/CMakeLists.txt")
message(" * ➕ Including Game module from: ${REVC_GAME_DIR}")
message(" ")
- message("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
+ message("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
message(" * 🚀 Initiating Game module build configuration...")
- message("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
+ message("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
message(" ")
- #add_subdirectory(${REVC_GAME_DIR} ${CMAKE_BINARY_DIR}/game_build)
+
+ set(REVC_AUDIO "OAL" CACHE STRING "Audio" FORCE)
+ set(REVC_VENDORED_LIBRW ON CACHE BOOL "Use vendored librw" FORCE)
+ set(REVC_INSTALL OFF CACHE BOOL "Install" FORCE)
+
+ add_subdirectory(${REVC_GAME_DIR} ${CMAKE_BINARY_DIR}/game_build)
+
+ # Force lowercase output name — System.loadLibrary("revc") looks for librevc.so
+ set_target_properties(reVC PROPERTIES LIBRARY_OUTPUT_NAME revc)
+
else()
message(" * ⚠️ Game source directory or CMakeLists.txt not found at: ${REVC_GAME_DIR}")
message(FATAL_ERROR " * ❗ Please ensure the game module sources are available for the build.")
diff --git a/android/launcher/app/build.gradle b/android/launcher/app/build.gradle
index e4a404468..1dc2ac28a 100644
--- a/android/launcher/app/build.gradle
+++ b/android/launcher/app/build.gradle
@@ -9,17 +9,17 @@ android {
defaultConfig {
applicationId "com.revc.game"
- minSdk 24
+ minSdk 26
targetSdk 35
versionCode 1
versionName "reVC-0.0.1"
- ndkVersion '27.2.12479018'
+ ndkVersion '30.0.15729638'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
ndk {
- abiFilters "armeabi-v7a"/*, "arm64-v8a"*/
+ abiFilters "armeabi-v7a", "arm64-v8a"
}
}
@@ -37,7 +37,6 @@ android {
minifyEnabled true
shrinkResources true
jniDebuggable false
- renderscriptDebuggable false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
@@ -54,7 +53,6 @@ android {
debuggable true
minifyEnabled false
jniDebuggable true
- renderscriptDebuggable true
}
}
diff --git a/android/launcher/app/src/main/AndroidManifest.xml b/android/launcher/app/src/main/AndroidManifest.xml
index 54669c7da..6fdf00de3 100644
--- a/android/launcher/app/src/main/AndroidManifest.xml
+++ b/android/launcher/app/src/main/AndroidManifest.xml
@@ -36,17 +36,22 @@
android:glEsVersion="0x00020000"
android:required="true"/>
-
+
-
+
+
+
-
-
-
-
-
-
-
-
+
diff --git a/android/launcher/app/src/main/java/com/revc/game/MainActivity.java b/android/launcher/app/src/main/java/com/revc/game/MainActivity.java
deleted file mode 100644
index 1b8558ee0..000000000
--- a/android/launcher/app/src/main/java/com/revc/game/MainActivity.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.revc.game;
-
-import androidx.appcompat.app.AppCompatActivity;
-
-import android.os.Bundle;
-import android.widget.TextView;
-
-import com.revc.game.databinding.ActivityMainBinding;
-
-public class MainActivity extends AppCompatActivity {
-
- // Used to load the 'game' library on application startup.
- static {
- //System.loadLibrary("revc");
- }
-
- private ActivityMainBinding binding;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- binding = ActivityMainBinding.inflate(getLayoutInflater());
- setContentView(binding.getRoot());
-
- // Example of a call to a native method
- TextView tv = binding.sampleText;
- // tv.setText(stringFromJNI());
- }
-
- /**
- * A native method that is implemented by the 'game' native library,
- * which is packaged with this application.
- */
- //public native String stringFromJNI();
-}
\ No newline at end of file
diff --git a/android/launcher/app/src/main/java/org/libsdl/app/SDLActivity.java b/android/launcher/app/src/main/java/org/libsdl/app/SDLActivity.java
index e6622294b..0ee13377e 100644
--- a/android/launcher/app/src/main/java/org/libsdl/app/SDLActivity.java
+++ b/android/launcher/app/src/main/java/org/libsdl/app/SDLActivity.java
@@ -4,6 +4,7 @@
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.UiModeManager;
+import android.content.ActivityNotFoundException;
import android.content.ClipboardManager;
import android.content.ClipData;
import android.content.Context;
@@ -50,6 +51,16 @@
import android.widget.TextView;
import android.widget.Toast;
+import android.app.ProgressDialog;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.provider.DocumentsContract;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
import java.util.Hashtable;
import java.util.Locale;
@@ -62,6 +73,8 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
private static final int SDL_MAJOR_VERSION = 2;
private static final int SDL_MINOR_VERSION = 32;
private static final int SDL_MICRO_VERSION = 8;
+ private static final int REQUEST_GAME_DIR = 1000;
+ private static final String PREF_GAME_READY = "game_ready";
/*
// Display InputType.SOURCE/CLASS of events and devices
//
@@ -325,6 +338,23 @@ protected void onCreate(Bundle savedInstanceState) {
Log.v(TAG, "modify thread properties failed " + e.toString());
}
+ // First launch: SAF picker → copy to app-private
+ SharedPreferences prefs = getSharedPreferences(PREF_GAME_READY, MODE_PRIVATE);
+ if (!prefs.getBoolean("ready", false)) {
+ Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
+ try {
+ startActivityForResult(intent, REQUEST_GAME_DIR);
+ } catch (ActivityNotFoundException e) {
+ // Google TV ships no SAF document picker. Material Files (or any
+ // file manager exposing a DocumentsProvider) supplies one.
+ Toast.makeText(this,
+ "No file picker found. Install a file manager (e.g. Material Files) to select the game folder.",
+ Toast.LENGTH_LONG).show();
+ finish();
+ }
+ return;
+ }
+
// Load shared libraries
String errorMsgBrokenLib = "";
try {
@@ -409,7 +439,7 @@ public void onClick(DialogInterface dialog,int id) {
setContentView(mLayout);
- setWindowStyle(false);
+ setWindowStyle(true);
getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(this);
@@ -446,6 +476,77 @@ protected void resumeNativeThread() {
SDLActivity.handleNativeState();
}
+ // SAF picker result + copy
+ @Override
+ protected void onActivityResult(int req, int res, Intent data) {
+ super.onActivityResult(req, res, data);
+ if (req != REQUEST_GAME_DIR || res != RESULT_OK || data == null) { finish(); return; }
+ Uri treeUri = data.getData();
+ if (treeUri == null) { finish(); return; }
+ getContentResolver().takePersistableUriPermission(treeUri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ new CopyGameTask().execute(treeUri);
+ }
+
+ private class CopyGameTask extends AsyncTask {
+ private ProgressDialog dialog;
+ protected void onPreExecute() {
+ dialog = new ProgressDialog(SDLActivity.this);
+ dialog.setTitle("Copying game files");
+ dialog.setMessage("Please wait...");
+ dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+ dialog.setCancelable(false);
+ dialog.show();
+ }
+ protected Boolean doInBackground(Uri... uris) {
+ File dst = getExternalFilesDir(null);
+ if (dst == null) return false;
+ dst.mkdirs();
+ try { copyTree(uris[0], "", dst); return true; }
+ catch (IOException e) { Log.e(TAG, "Copy failed: " + e); return false; }
+ }
+ protected void onPostExecute(Boolean ok) {
+ dialog.dismiss();
+ if (ok) {
+ getSharedPreferences(PREF_GAME_READY, MODE_PRIVATE)
+ .edit().putBoolean("ready", true).apply();
+ recreate();
+ } else {
+ new AlertDialog.Builder(SDLActivity.this)
+ .setTitle("Error").setMessage("Failed to copy game files.")
+ .setPositiveButton("Exit", (d, w) -> finish()).show();
+ }
+ }
+ private void copyTree(Uri treeUri, String path, File dstDir) throws IOException {
+ String docId = DocumentsContract.getTreeDocumentId(treeUri);
+ String childId = path.isEmpty() ? docId : docId + "/" + path;
+ Uri dirUri = DocumentsContract.buildChildDocumentsUriUsingTree(treeUri, childId);
+ try (Cursor c = getContentResolver().query(dirUri,
+ new String[]{DocumentsContract.Document.COLUMN_DOCUMENT_ID,
+ DocumentsContract.Document.COLUMN_DISPLAY_NAME,
+ DocumentsContract.Document.COLUMN_MIME_TYPE},
+ null, null, null)) {
+ if (c == null) return;
+ while (c.moveToNext()) {
+ String name = c.getString(1);
+ String mime = c.getString(2);
+ String childDocId = c.getString(0);
+ if (DocumentsContract.Document.MIME_TYPE_DIR.equals(mime)) {
+ File sub = new File(dstDir, name); sub.mkdirs();
+ copyTree(treeUri, (path.isEmpty() ? "" : path + "/") + name, sub);
+ } else {
+ Uri fileUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, childDocId);
+ try (InputStream in = getContentResolver().openInputStream(fileUri);
+ FileOutputStream out = new FileOutputStream(new File(dstDir, name))) {
+ byte[] buf = new byte[65536]; int n;
+ while ((n = in.read(buf)) > 0) out.write(buf, 0, n);
+ }
+ }
+ }
+ }
+ }
+ }
+
// Events
@Override
protected void onPause() {
@@ -606,6 +707,9 @@ protected void onDestroy() {
SDLActivity.nativeQuit();
super.onDestroy();
+
+ // Ensure clean process state for next launch
+ android.os.Process.killProcess(android.os.Process.myPid());
}
@Override
diff --git a/android/launcher/app/src/main/res/drawable-xhdpi/banner.png b/android/launcher/app/src/main/res/drawable-xhdpi/banner.png
new file mode 100644
index 000000000..9dccf05c0
Binary files /dev/null and b/android/launcher/app/src/main/res/drawable-xhdpi/banner.png differ
diff --git a/android/launcher/app/src/main/res/drawable/ic_launcher_background.xml b/android/launcher/app/src/main/res/drawable/ic_launcher_background.xml
deleted file mode 100644
index 07d5da9cb..000000000
--- a/android/launcher/app/src/main/res/drawable/ic_launcher_background.xml
+++ /dev/null
@@ -1,170 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/android/launcher/app/src/main/res/drawable/ic_launcher_foreground.xml b/android/launcher/app/src/main/res/drawable/ic_launcher_foreground.xml
deleted file mode 100644
index 2b068d114..000000000
--- a/android/launcher/app/src/main/res/drawable/ic_launcher_foreground.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/android/launcher/app/src/main/res/layout/activity_main.xml b/android/launcher/app/src/main/res/layout/activity_main.xml
deleted file mode 100644
index 0fdf98529..000000000
--- a/android/launcher/app/src/main/res/layout/activity_main.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/android/launcher/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/launcher/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
index 6f3b755bf..345888d26 100644
--- a/android/launcher/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/android/launcher/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -1,6 +1,6 @@
-
-
-
+
+
+
\ No newline at end of file
diff --git a/android/launcher/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/launcher/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
index 6f3b755bf..345888d26 100644
--- a/android/launcher/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ b/android/launcher/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -1,6 +1,6 @@
-
-
-
+
+
+
\ No newline at end of file
diff --git a/android/launcher/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/launcher/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..19fe524ef
Binary files /dev/null and b/android/launcher/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/android/launcher/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/android/launcher/app/src/main/res/mipmap-hdpi/ic_launcher.webp
deleted file mode 100644
index c209e78ec..000000000
Binary files a/android/launcher/app/src/main/res/mipmap-hdpi/ic_launcher.webp and /dev/null differ
diff --git a/android/launcher/app/src/main/res/mipmap-hdpi/ic_launcher_background.png b/android/launcher/app/src/main/res/mipmap-hdpi/ic_launcher_background.png
new file mode 100644
index 000000000..58bfe2670
Binary files /dev/null and b/android/launcher/app/src/main/res/mipmap-hdpi/ic_launcher_background.png differ
diff --git a/android/launcher/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/android/launcher/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 000000000..fc692ae5e
Binary files /dev/null and b/android/launcher/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/android/launcher/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png b/android/launcher/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png
new file mode 100644
index 000000000..fc692ae5e
Binary files /dev/null and b/android/launcher/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png differ
diff --git a/android/launcher/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/android/launcher/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
deleted file mode 100644
index b2dfe3d1b..000000000
Binary files a/android/launcher/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/android/launcher/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/launcher/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..ecad188ea
Binary files /dev/null and b/android/launcher/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/android/launcher/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/android/launcher/app/src/main/res/mipmap-mdpi/ic_launcher.webp
deleted file mode 100644
index 4f0f1d64e..000000000
Binary files a/android/launcher/app/src/main/res/mipmap-mdpi/ic_launcher.webp and /dev/null differ
diff --git a/android/launcher/app/src/main/res/mipmap-mdpi/ic_launcher_background.png b/android/launcher/app/src/main/res/mipmap-mdpi/ic_launcher_background.png
new file mode 100644
index 000000000..93fb1ef98
Binary files /dev/null and b/android/launcher/app/src/main/res/mipmap-mdpi/ic_launcher_background.png differ
diff --git a/android/launcher/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/android/launcher/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 000000000..50c9ae3b4
Binary files /dev/null and b/android/launcher/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/android/launcher/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png b/android/launcher/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png
new file mode 100644
index 000000000..50c9ae3b4
Binary files /dev/null and b/android/launcher/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png differ
diff --git a/android/launcher/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/android/launcher/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
deleted file mode 100644
index 62b611da0..000000000
Binary files a/android/launcher/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/android/launcher/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/launcher/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..70a85f72d
Binary files /dev/null and b/android/launcher/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/android/launcher/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/android/launcher/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
deleted file mode 100644
index 948a3070f..000000000
Binary files a/android/launcher/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and /dev/null differ
diff --git a/android/launcher/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/android/launcher/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png
new file mode 100644
index 000000000..b8aa7bb22
Binary files /dev/null and b/android/launcher/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png differ
diff --git a/android/launcher/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/android/launcher/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 000000000..690e5d902
Binary files /dev/null and b/android/launcher/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/android/launcher/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png b/android/launcher/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png
new file mode 100644
index 000000000..690e5d902
Binary files /dev/null and b/android/launcher/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png differ
diff --git a/android/launcher/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/android/launcher/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
deleted file mode 100644
index 1b9a6956b..000000000
Binary files a/android/launcher/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/android/launcher/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/launcher/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..272101480
Binary files /dev/null and b/android/launcher/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/android/launcher/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/android/launcher/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
deleted file mode 100644
index 28d4b77f9..000000000
Binary files a/android/launcher/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and /dev/null differ
diff --git a/android/launcher/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/android/launcher/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png
new file mode 100644
index 000000000..d21b0b8e9
Binary files /dev/null and b/android/launcher/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png differ
diff --git a/android/launcher/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/android/launcher/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 000000000..08a3839c5
Binary files /dev/null and b/android/launcher/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/android/launcher/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png b/android/launcher/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png
new file mode 100644
index 000000000..08a3839c5
Binary files /dev/null and b/android/launcher/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png differ
diff --git a/android/launcher/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/android/launcher/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
deleted file mode 100644
index 9287f5083..000000000
Binary files a/android/launcher/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/android/launcher/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/launcher/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..afcf0f92d
Binary files /dev/null and b/android/launcher/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/android/launcher/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/android/launcher/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
deleted file mode 100644
index aa7d6427e..000000000
Binary files a/android/launcher/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and /dev/null differ
diff --git a/android/launcher/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/android/launcher/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png
new file mode 100644
index 000000000..6e53e59e2
Binary files /dev/null and b/android/launcher/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png differ
diff --git a/android/launcher/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/android/launcher/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 000000000..9f7078488
Binary files /dev/null and b/android/launcher/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/android/launcher/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png b/android/launcher/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png
new file mode 100644
index 000000000..9f7078488
Binary files /dev/null and b/android/launcher/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png differ
diff --git a/android/launcher/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/android/launcher/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
deleted file mode 100644
index 9126ae37c..000000000
Binary files a/android/launcher/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/android/launcher/app/src/main/res/values/themes.xml b/android/launcher/app/src/main/res/values/themes.xml
index 606a05210..a29a9e328 100644
--- a/android/launcher/app/src/main/res/values/themes.xml
+++ b/android/launcher/app/src/main/res/values/themes.xml
@@ -1,16 +1,16 @@
-
-
\ No newline at end of file
diff --git a/android/setup_libs.sh b/android/setup_libs.sh
new file mode 100644
index 000000000..c9a1f4654
--- /dev/null
+++ b/android/setup_libs.sh
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+# Setup prebuilt native libraries for Android build.
+# Copies .so files from vendor/ to jniLibs/ for Gradle packaging.
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+APP_DIR="$SCRIPT_DIR/launcher/app"
+VENDOR_DIR="$SCRIPT_DIR/../vendor"
+
+abis=("arm64-v8a" "armeabi-v7a")
+
+for abi in "${abis[@]}"; do
+ mkdir -p "$APP_DIR/jniLibs/$abi"
+
+ cp "$VENDOR_DIR/sdl2/libs/Android/$abi/libSDL2.so" "$APP_DIR/jniLibs/$abi/"
+ cp "$VENDOR_DIR/openal-soft/libs/Android/$abi/libopenal.so" "$APP_DIR/jniLibs/$abi/"
+ cp "$VENDOR_DIR/mpg123/lib/Android/$abi/libmpg123.so" "$APP_DIR/jniLibs/$abi/"
+
+ echo "✔ $abi: libSDL2.so, libopenal.so, libmpg123.so"
+done
+
+echo "Done. Prebuilt libs copied to jniLibs/"
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 91d3b8f69..d44325f26 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -84,7 +84,9 @@ else()
endif()
if(${PROJECT}_AUDIO STREQUAL "OAL")
- find_package(OpenAL REQUIRED)
+ if(NOT TARGET OpenAL::OpenAL)
+ find_package(OpenAL REQUIRED)
+ endif()
if(TARGET OpenAL::OpenAL)
target_link_libraries(${EXECUTABLE} PRIVATE OpenAL::OpenAL)
else()
@@ -99,7 +101,9 @@ elseif(${PROJECT}_AUDIO STREQUAL "MSS")
target_link_libraries(${EXECUTABLE} PRIVATE MilesSDK::MilesSDK)
endif()
-find_package(mpg123 REQUIRED)
+if(NOT TARGET MPG123::libmpg123)
+ find_package(mpg123 REQUIRED)
+endif()
target_link_libraries(${EXECUTABLE} PRIVATE
MPG123::libmpg123
)
@@ -161,6 +165,9 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang
-Wno-unused-command-line-argument
-faligned-new
)
+ target_link_options(${EXECUTABLE} PRIVATE
+ -Wl,-z,max-page-size=16384
+ )
endif()
target_compile_options(${EXECUTABLE}
PRIVATE
diff --git a/src/core/CdStream_posix.cpp b/src/core/CdStream_posix.cpp
index 0287adc5d..ef9fd7ef6 100644
--- a/src/core/CdStream_posix.cpp
+++ b/src/core/CdStream_posix.cpp
@@ -229,9 +229,10 @@ CdStreamInit(int32 numChannels)
char pwd[128];
getcwd(pwd, 128);
setenv("STORAGE_ROOT", pwd, 1);
- debug("%s\n", pwd);
+ StorageRootBuffer = getenv("STORAGE_ROOT");
+ debug("%s\n", pwd);
}
-
+
debug("FILES %s\n", StorageRootBuffer);
strcpy(imgPath, StorageRootBuffer);
strcat(imgPath, "/models/gta3.img");
@@ -242,8 +243,9 @@ CdStreamInit(int32 numChannels)
if((statvfs("models/gta3.img", &fsInfo)) < 0)
#endif
{
- CDTRACE("can't get filesystem info");
- ASSERT(0);
+ CDTRACE("can't get filesystem info for %s", imgPath);
+ debug("ERROR: Cannot find game assets at %s\n", imgPath);
+ debug("Please copy GTA VC files to this directory\n");
return;
}
#if defined ANDROID
diff --git a/src/core/FileMgr.cpp b/src/core/FileMgr.cpp
index 124423529..69c2f97ad 100644
--- a/src/core/FileMgr.cpp
+++ b/src/core/FileMgr.cpp
@@ -294,7 +294,15 @@ int
CFileMgr::OpenFile(const char *file, const char *mode)
{
debug("CFileMgr::OpenFile: %s", file);
+#if defined(ANDROID)
+ char cleanFile[MAX_PATH];
+ strcpy(cleanFile, file);
+ for (char *p = cleanFile; *p; p++)
+ if (*p == '\\') *p = '/';
+ return myfopen(cleanFile, mode);
+#else
return myfopen(file, mode);
+#endif
}
int
diff --git a/src/core/main.cpp b/src/core/main.cpp
index 7cfb29cf5..42ebcd3b4 100644
--- a/src/core/main.cpp
+++ b/src/core/main.cpp
@@ -531,11 +531,13 @@ Initialise3D(void *param)
{
POP_MEMID();
+
#ifdef DEBUGMENU
DebugMenuInit();
DebugMenuPopulate();
#endif // !DEBUGMENU
- return CGame::InitialiseRenderWare();
+ bool rwResult = CGame::InitialiseRenderWare();
+ return rwResult;
}
POP_MEMID();
diff --git a/src/skel/android/AndroidMain.cpp b/src/skel/android/AndroidMain.cpp
index ba2da0237..9cb3d7e6a 100644
--- a/src/skel/android/AndroidMain.cpp
+++ b/src/skel/android/AndroidMain.cpp
@@ -51,13 +51,6 @@ bool AndWrapper::InitLibraries() {
return true;
}
-void AndWrapper::TimeInitialize() {
- struct timeval v0;
-
- gettimeofday(&v0, NULL);
- // base_time = (double)v0.tv_usec / 1000000.0 + (double)v0.tv_sec;
-}
-
void* AndWrapper::GetJNI() {
return CJavaWrapper::GetEnv();
}
@@ -66,352 +59,6 @@ void* AndWrapper::GetJNIFunc() {
return CJavaWrapper::GetEnv();
}
-void* AndWrapper::GetObj() {
- //return s_event_globalThiz;
-}
-
-const char* AndWrapper::GetAppId()
-{
- /*JNIEnv* CurrentJNIEnv; // x19
- jobject v1; // x20
- const char *v2; // x21
- jboolean v4[4]; // [xsp+Ch] [xbp-14h] BYREF
-
- if (!staticAppId[0]) {
- CurrentJNIEnv = CJavaWrapper::GetEnv();
-
- v1 = _JNIEnv::CallObjectMethod(CurrentJNIEnv, s_event_globalThiz, s_GetAppId);
- v2 = CurrentJNIEnv->functions->GetStringUTFChars(CurrentJNIEnv, (jstring)v1, v4);
- strcpy(staticAppId, v2);
- CurrentJNIEnv->functions->ReleaseStringUTFChars(CurrentJNIEnv, (jstring)v1, v2);
- CurrentJNIEnv->functions->DeleteLocalRef(CurrentJNIEnv, v1);
- }
- return staticAppId;*/
-}
-
-const char* AndWrapper::GetDeviceID()
-{
- /*JNIEnv *CurrentJNIEnv; // x19
- jobject v1; // x20
- const char *v2; // x21
- jboolean v4[4]; // [xsp+Ch] [xbp-24h] BYREF
-
- CurrentJNIEnv = NVThreadGetCurrentJNIEnv();
- v1 = JNIEnv::CallObjectMethod(CurrentJNIEnv, s_event_globalThiz, s_GetDeviceID);
- v2 = CurrentJNIEnv->functions->GetStringUTFChars(CurrentJNIEnv, (jstring)v1, v4);
- strncpy(staticDeviceID, v2, 0x80uLL);
- CurrentJNIEnv->functions->ReleaseStringUTFChars(CurrentJNIEnv, (jstring)v1, v2);
- CurrentJNIEnv->functions->DeleteLocalRef(CurrentJNIEnv, v1);
- return staticDeviceID;*/
-}
-
-int AndWrapper::GetDeviceInfo(int index)
-{
- //JNIEnv *CurrentJNIEnv; // x0
-
- //CurrentJNIEnv = NVThreadGetCurrentJNIEnv();
- //return _JNIEnv::CallIntMethod(CurrentJNIEnv, s_event_globalThiz, s_GetDeviceInfo, (unsigned int)index);
-}
-
-bool AndWrapper::IsAppInstalled(const char* app)
-{
- /*_JNIEnv *CurrentJNIEnv; // x20
- __int64 v3; // x19
- bool v4; // w21
-
- CurrentJNIEnv = NVThreadGetCurrentJNIEnv();
- v3 = (__int64)CurrentJNIEnv->functions->NewStringUTF(CurrentJNIEnv, app);
- v4 = _JNIEnv::CallBooleanMethod(CurrentJNIEnv, s_event_globalThiz, s_IsAppInstalled, v3) != 0;
- CurrentJNIEnv->functions->DeleteLocalRef(CurrentJNIEnv, (jobject)v3);
- return v4;*/
-}
-
-void AndWrapper::OpenLink(const char* link)
-{
- /*_JNIEnv *CurrentJNIEnv; // x20
- __int64 v3; // x19
-
- CurrentJNIEnv = NVThreadGetCurrentJNIEnv();
- v3 = (__int64)CurrentJNIEnv->functions->NewStringUTF(CurrentJNIEnv, link);
- _JNIEnv::CallVoidMethod(CurrentJNIEnv, s_event_globalThiz, s_OpenLink, v3);
- CurrentJNIEnv->functions->DeleteLocalRef(CurrentJNIEnv, (jobject)v3);*/
-}
-
-bool AndWrapper::DeviceIsTV()
-{
- //_JNIEnv *CurrentJNIEnv; // x0
-
- //CurrentJNIEnv = NVThreadGetCurrentJNIEnv();
- //return _JNIEnv::CallBooleanMethod(CurrentJNIEnv, s_event_globalThiz, s_IsTV) != 0;
-}
-
-int AndWrapper::DeviceLocale()
-{
- // _JNIEnv *CurrentJNIEnv; // x0
-
- // CurrentJNIEnv = NVThreadGetCurrentJNIEnv();
- // return _JNIEnv::CallIntMethod(CurrentJNIEnv, s_event_globalThiz, s_GetDeviceLocale);
-}
-
-int AndWrapper::DeviceType()
-{
- //_JNIEnv *CurrentJNIEnv; // x0
-
- //CurrentJNIEnv = NVThreadGetCurrentJNIEnv();
- //return _JNIEnv::CallIntMethod(CurrentJNIEnv, s_event_globalThiz, s_GetDeviceType);
-}
-
-void AndWrapper::SystemInitialize()
-{
-
-}
-/*
-int32_t __fastcall NVEventAppMain(int32_t argc, char **argv)
-{
- _QWORD *v4; // x8
- pthread_key_t v5; // w0
- __int64 v6; // x21
- _BOOL4 v7; // w21
- _JNIEnv *CurrentJNIEnv; // x0
- _JNIEnv *v9; // x0
- jint v10; // w0
- __int64 v11; // x22
- __int64 v12; // x22
- _JNIEnv *v13; // x0
- int v14; // w8
- _JNIEnv *v15; // x0
- _JNIEnv *v16; // x0
- double v17; // d8
- bool v18; // w19
- double v19; // d1
- double v20; // d0
- double v21; // d12
- double v22; // d8
- float v23; // s0
- char v24; // w20
- pthread_mutexattr_t *v25; // x19
- pthread_mutexattr_t *v26; // x19
- pthread_mutexattr_t *v27; // x19
- struct timeval v29; // [xsp+0h] [xbp-90h] BYREF
- int data; // [xsp+1Ch] [xbp-74h] BYREF
-
- OS_ApplicationPreinit();
- AND_KeyboardInitialize();
- memset(lastGamepadAxis, 0, sizeof(lastGamepadAxis));
- gettimeofday(&v29, 0LL);
- base_time = (double)v29.tv_usec / 1000000.0 + (double)v29.tv_sec;
- if ( !ANDThread_Initted )
- {
- pthread_key_create(&ANDThreadStorageKey, ANDThreadData::Destroy);
- v4 = malloc(0x18uLL);
- v5 = ANDThreadStorageKey;
- v4[1] = 0LL;
- v4[2] = 0LL;
- *v4 = 0LL;
- pthread_setspecific(v5, v4);
- ANDThread_Initted = 1;
- }
- v6 = operator new(0x30uLL);
- pthread_mutexattr_init((pthread_mutexattr_t *)(v6 + 40));
- pthread_mutexattr_settype((pthread_mutexattr_t *)(v6 + 40), 1);
- pthread_mutex_init((pthread_mutex_t *)v6, (const pthread_mutexattr_t *)(v6 + 40));
- fileMutex = (OSMutex)v6;
- if ( DoInitGraphics )
- initGraphics();
- v7 = 0;
- if ( IsInitGraphics )
- goto LABEL_8;
- while ( IsAndroidPaused || !v7 )
- {
- while ( 1 )
- {
- ++NVEventAppMain(int,char **)::iter;
- v7 = ProcessEvents(0);
- if ( !IsInitGraphics )
- break;
- LABEL_8:
- if ( !IsAndroidPaused )
- goto LABEL_12;
- }
- }
- LABEL_12:
- pthread_mutex_lock((pthread_mutex_t *)AndroidEGLContext);
- CurrentJNIEnv = NVThreadGetCurrentJNIEnv();
- if ( CurrentJNIEnv && s_event_globalThiz )
- {
- if ( !_JNIEnv::CallBooleanMethod(CurrentJNIEnv, s_event_globalThiz, s_makeCurrent) )
- __android_log_print(3, "NVEvent", "Error: MakeCurrent failed");
- }
- else
- {
- __android_log_print(3, "NVEvent", "Error: No valid JNI env in MakeCurrent");
- }
- AND_WRAPPER::SystemInitialize();
- v9 = NVThreadGetCurrentJNIEnv();
- v10 = _JNIEnv::CallIntMethod(v9, s_event_globalThiz, s_GetDeviceType);
- isLowMemoryDevice = (((unsigned int)v10 >> 1) & 1) == 0 || v10 >> 6 < 250;
- v11 = operator new(0x30uLL);
- pthread_mutexattr_init((pthread_mutexattr_t *)(v11 + 40));
- pthread_mutexattr_settype((pthread_mutexattr_t *)(v11 + 40), 1);
- pthread_mutex_init((pthread_mutex_t *)v11, (const pthread_mutexattr_t *)(v11 + 40));
- billingMutex = (OSMutex)v11;
- v12 = operator new(0x30uLL);
- pthread_mutexattr_init((pthread_mutexattr_t *)(v12 + 40));
- pthread_mutexattr_settype((pthread_mutexattr_t *)(v12 + 40), 1);
- pthread_mutex_init((pthread_mutex_t *)v12, (const pthread_mutexattr_t *)(v12 + 40));
- gameServiceMutex = (OSMutex)v12;
- s_conflictHandler = 0LL;
- if ( OS_ApplicationInitialize(argc, (const char **)argv) )
- {
- data = 0;
- AND_AppInitialized = 1;
- v13 = NVThreadGetCurrentJNIEnv();
- if ( _JNIEnv::CallBooleanMethod(v13, s_event_globalThiz, s_IsWifiAvailable) )
- {
- v14 = 2;
- }
- else
- {
- v15 = NVThreadGetCurrentJNIEnv();
- if ( !_JNIEnv::CallBooleanMethod(v15, s_event_globalThiz, s_IsNetworkAvailable) )
- goto LABEL_23;
- v14 = 1;
- }
- data = v14;
- LABEL_23:
- OS_ApplicationEvent(OSET_NetworkChanged, &data);
- v16 = NVThreadGetCurrentJNIEnv();
- if ( v16 && s_event_globalThiz )
- {
- if ( !_JNIEnv::CallBooleanMethod(v16, s_event_globalThiz, s_unMakeCurrent) )
- __android_log_print(3, "NVEvent", "Error: UnMakeCurrent failed");
- }
- else
- {
- __android_log_print(3, "NVEvent", "Error: No valid JNI env in UnMakeCurrent");
- }
- pthread_mutex_unlock((pthread_mutex_t *)AndroidEGLContext);
- if ( OS_ApplicationStartup(windowSize[0], windowSize[1], argc, (const char **)argv) )
- {
- AND_AppStarted = 1;
- gettimeofday(&v29, 0LL);
- if ( !v7 )
- {
- v17 = (double)v29.tv_usec / 1000000.0 + (double)v29.tv_sec;
- do
- {
- v18 = ProcessEvents(0);
- while ( !v18 )
- {
- if ( !IsAndroidPaused )
- break;
- if ( IsAndroidInMultiplayer )
- break;
- v18 = ProcessEvents(0);
- usleep(0x61A8u);
- }
- gettimeofday(&v29, 0LL);
- if ( v29.tv_usec > 1000000 || v29.tv_usec < 0 )
- v19 = OS_TimeAccurate(void)::last_current_time
- - (double)(unsigned int)OS_TimeAccurate(void)::last_current_time
- + 0.00033;
- else
- v19 = (double)v29.tv_usec / 1000000.0;
- v20 = v19 + (double)v29.tv_sec;
- OS_TimeAccurate(void)::last_current_time = v20;
- if ( v20 - OS_TimeAccurate(void)::lastPrint > 5.0 )
- OS_TimeAccurate(void)::lastPrint = v19 + (double)v29.tv_sec;
- v21 = v20 - base_time;
- v22 = v20 - base_time - v17;
- v23 = v22;
- v24 = OS_ApplicationTick(v23);
- AND_GamepadUpdate();
- AND_FileUpdate(v22);
- AND_BillingUpdate(0);
- v17 = v21;
- }
- while ( !v18 && (v24 & 1) != 0 );
- }
- OS_ApplicationEvent(OSET_RequestExit, 0LL);
- pthread_key_delete(ANDThreadStorageKey);
- if ( items )
- {
- free(items);
- items = 0LL;
- numItems = 0;
- }
- v25 = (pthread_mutexattr_t *)billingMutex;
- if ( billingMutex )
- {
- pthread_mutex_destroy((pthread_mutex_t *)billingMutex);
- pthread_mutexattr_destroy(v25 + 5);
- operator delete(v25);
- }
- billingMutex = 0LL;
- AND_ClearAchievementData(1);
- v26 = (pthread_mutexattr_t *)billingMutex;
- if ( billingMutex )
- {
- pthread_mutex_destroy((pthread_mutex_t *)billingMutex);
- pthread_mutexattr_destroy(v26 + 5);
- operator delete(v26);
- }
- v27 = (pthread_mutexattr_t *)fileMutex;
- if ( fileMutex )
- {
- pthread_mutex_destroy((pthread_mutex_t *)fileMutex);
- pthread_mutexattr_destroy(v27 + 5);
- operator delete(v27);
- }
- fileMutex = 0LL;
- }
- else
- {
- OS_ApplicationEvent(OSET_RequestExit, 0LL);
- }
- }
- return 0;
-}*/
-/*
-void __fastcall OS_ApplicationEvent(OSEventType type, void *data)
-{
- switch ( type )
- {
- case OSET_RequestExit:
- RsGlobal.quit = TRUE;
- //OS_ThreadWait(mainThread);
- break;
-
- case OSET_Pause:
- SaveGameForPause(eSaveTypes::eExitSave, 0LL);
- CTimer::StartUserPause();
- if ( !CPad::GetPad(0)->DisablePlayerControls
- && !gMobileMenu.screenStack.numEntries
- && !CCutsceneMgr::IntroTextIsActiveHack
- && !CCutsceneMgr::ms_running
- && !gMobileMenu.pendingScreen
- && !CTouchInterface::AnyWidgetsUsingAltBack() )
- {
- if (FindPlayerPed())
- bPendingPause = 1;
- }
- CAEAudioHardware::PauseOpenAL((CAEAudioHardware *)&AEAudioHardware, 1);
- break;
- case OSET_Resume:
- OS_ThreadUnmakeCurrent();
- CTimer::Update();
- if ( !gMobileMenu.screenStack.numEntries && !gMobileMenu.pendingScreen )
- CTimer::EndUserPause();
- CAEAudioHardware::PauseOpenAL((CAEAudioHardware *)&AEAudioHardware, 0);
- break;
- case OSET_LowMemory:
- //DoLowMemoryCleanup = 1;
- break;
- default:
- return;
- }
-}*/
-
JNI_WRAPPER int InitializeGame() {
debug("Initialize Game");
@@ -421,7 +68,6 @@ JNI_WRAPPER int InitializeGame() {
char* argv[1] = { nullptr };
SDL_SetHint(SDL_HINT_ORIENTATIONS, "LandscapeLeft LandscapeRight");
- // SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
CrashHandler::SetupSignalHandlers();
diff --git a/src/skel/android/AndroidMain.h b/src/skel/android/AndroidMain.h
index dc851f3f1..6e7c1929b 100644
--- a/src/skel/android/AndroidMain.h
+++ b/src/skel/android/AndroidMain.h
@@ -20,19 +20,8 @@ namespace AndWrapper {
extern bool AppStarted;
bool InitLibraries();
- void SystemInitialize();
- void TimeInitialize();
void* GetJNI();
void* GetJNIFunc();
- void* GetObj();
- const char* GetAppId();
- const char* GetDeviceID();
- int GetDeviceInfo(int index);
- bool IsAppInstalled(const char* app);
- void OpenLink(const char* link);
- bool DeviceIsTV();
- int DeviceLocale();
- int DeviceType();
}
#endif
diff --git a/src/skel/android/signalhandler/SignalHandler.cpp b/src/skel/android/signalhandler/SignalHandler.cpp
index fcc23abc3..46e23a711 100644
--- a/src/skel/android/signalhandler/SignalHandler.cpp
+++ b/src/skel/android/signalhandler/SignalHandler.cpp
@@ -15,13 +15,7 @@
extern int16 g_usLastProcessedModelIndexAutomobile;
extern int g_iLastProcessedModelIndexAutoEnt;
-
-extern int g_iLastProcessedSkinCollision;
-extern int g_iLastProcessedEntityCollision;
-extern char lastFile[123];
extern int g_iLastRenderedObject;
-extern int lastNvEvent;
-extern CVector lastPos;
char g_iLastBlock[123];
char streamimgState[255];
@@ -34,7 +28,11 @@ namespace CrashHandler {
tm* timeInfo = localtime(¤tTime);
Logger::CrashLog("Crash time: %d:%d:%d %d:%d:%d", timeInfo->tm_mday, timeInfo->tm_mon, timeInfo->tm_year, timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec);
- Logger::CrashLog("Build times: %s %s. ABI: %s", __TIME__, __DATE__, (ANDROID_x32 ? "armeabi-v7a" : "arm64-v8a"));
+#ifdef __arm__
+ Logger::CrashLog("Build times: %s %s. ABI: %s", __TIME__, __DATE__, "armeabi-v7a");
+#else
+ Logger::CrashLog("Build times: %s %s. ABI: %s", __TIME__, __DATE__, "arm64-v8a");
+#endif
Logger::CrashLog("Last processed auto and entity: %d %d", g_usLastProcessedModelIndexAutomobile, g_iLastProcessedModelIndexAutoEnt);
Logger::CrashLog("Last rendered object: %d", g_iLastRenderedObject);
}
diff --git a/src/skel/android/signalhandler/StackTrace.h b/src/skel/android/signalhandler/StackTrace.h
index 47d6c8970..2a58b1431 100644
--- a/src/skel/android/signalhandler/StackTrace.h
+++ b/src/skel/android/signalhandler/StackTrace.h
@@ -13,7 +13,7 @@
extern uintptr_t g_libREVC;
-#if ANDROID_x32
+#ifdef __arm__
#define PRINT_CRASH_STATES(context) \
Logger::CrashLog("register states:"); \
Logger::CrashLog("r0: 0x%X, r1: 0x%X, r2: 0x%X, r3: 0x%X", (context)->uc_mcontext.arm_r0, (context)->uc_mcontext.arm_r1, (context)->uc_mcontext.arm_r2, (context)->uc_mcontext.arm_r3); \
diff --git a/src/skel/crossplatform.cpp b/src/skel/crossplatform.cpp
index 99ec599d2..9873302d3 100644
--- a/src/skel/crossplatform.cpp
+++ b/src/skel/crossplatform.cpp
@@ -217,7 +217,7 @@ char* casepath(char const* path, bool checkPathFirst)
}
else
#endif
-#if defined(ANDROID) // TODO: Android is fuck!!!
+#if defined(ANDROID) // Android FUSE resolves relative paths differently — strip the root dir prefix and open CWD
char cwd[MAX_PATH];
getcwd(cwd, sizeof(cwd));
diff --git a/src/skel/sdl2/sdl2.cpp b/src/skel/sdl2/sdl2.cpp
index 47b943f39..71f96b375 100644
--- a/src/skel/sdl2/sdl2.cpp
+++ b/src/skel/sdl2/sdl2.cpp
@@ -83,7 +83,14 @@ void _psCreateFolder(const char *path)
#if defined(ANDROID)
const char* pathroot = StorageRootBuffer;
char dbPath[1024];
- snprintf(dbPath, sizeof(dbPath), "%s%s", pathroot, path);
+ if (path[0] == '/' && pathroot) {
+ // Path is already absolute, use as-is
+ snprintf(dbPath, sizeof(dbPath), "%s", path);
+ } else if (pathroot) {
+ snprintf(dbPath, sizeof(dbPath), "%s/%s", pathroot, path);
+ } else {
+ snprintf(dbPath, sizeof(dbPath), "%s", path);
+ }
mkdir(dbPath, 0755);
debug("Creating Folder Path: %s", dbPath);
#else
@@ -107,8 +114,18 @@ void _psCreateFolder(const char *path)
const char *_psGetUserFilesFolder()
{
static char szUserFiles[256];
+#if defined(ANDROID)
+ const char *root = getenv("STORAGE_ROOT");
+ if (root) {
+ snprintf(szUserFiles, sizeof(szUserFiles), "%s/userfiles", root);
+ _psCreateFolder(szUserFiles);
+ } else {
+ strcpy(szUserFiles, "userfiles");
+ }
+#else
strcpy(szUserFiles, "userfiles");
_psCreateFolder(szUserFiles);
+#endif
return szUserFiles;
}
@@ -473,6 +490,7 @@ psSelectDevice()
RwBool modeFound = FALSE;
+
if (!useDefault)
{
GnumSubSystems = RwEngineGetNumSubSystems();
@@ -592,8 +610,10 @@ psSelectDevice()
}
if(bestFsMode < 0){
- printf("WARNING: Cannot find desired video mode, selecting device cancelled\n");
- return FALSE;
+ // Fallback to windowed mode — happens on Android/GLES where
+ // there are no SDL fullscreen display modes
+ bestFsMode = bestWndMode >= 0 ? bestWndMode : 0;
+ FrontEndMenuManager.m_nPrefsWindowed = 1;
}
GcurSelVM = bestFsMode;
@@ -701,7 +721,7 @@ void _InputInitialiseJoys()
#if defined ANDROID
const char* pathRoot = getenv("STORAGE_ROOT");
char SDL_GAMEPAD_DB_PATH[MAX_PATH];
- snprintf(SDL_GAMEPAD_DB_PATH, sizeof(SDL_GAMEPAD_DB_PATH), "%s%s", pathRoot, "gamecontrollerdb.txt");
+ snprintf(SDL_GAMEPAD_DB_PATH, sizeof(SDL_GAMEPAD_DB_PATH), "%s/%s", pathRoot, "gamecontrollerdb.txt");
#else
const char* SDL_GAMEPAD_DB_PATH = "gamecontrollerdb.txt";
#endif
@@ -712,8 +732,9 @@ void _InputInitialiseJoys()
}
}
- // TODO SDL2 the part below seems unnecessary SDL2 (at least on Linux), remove in the future
- /*for (int i = 0; i <= SDL_NumJoysticks(); i++) {
+ // Detect already-connected joysticks during init.
+ // Needed on Android where gamepad may already be connected before SDL init.
+ for (int i = 0; i < SDL_NumJoysticks(); i++) {
if (!IsThisJoystickBlacklisted(i)) {
if (PSGLOBAL(joy1id) == -1)
PSGLOBAL(joy1id) = i;
@@ -732,7 +753,7 @@ void _InputInitialiseJoys()
strncpy(gSelectedJoystickName, SDL_JoystickNameForIndex(PSGLOBAL(joy1id)), sizeof(gSelectedJoystickName));
#endif
ControlsManager.InitDefaultControlConfigJoyPad(count);
- }*/
+ }
}
#if 0
@@ -1320,6 +1341,20 @@ main(int argc, char *argv[])
InitMemoryMgr();
#endif
+#if defined(ANDROID)
+ // Gamepad-only: disable touch-to-mouse emulation
+ SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
+ // Force landscape orientation regardless of auto-rotate
+ SDL_SetHint(SDL_HINT_ORIENTATIONS, "LandscapeLeft LandscapeRight");
+
+ // Game assets are copied to app-private storage by Java SAF picker.
+ if (!getenv("STORAGE_ROOT")) {
+ const char *extPath = SDL_AndroidGetExternalStoragePath();
+ if (extPath) setenv("STORAGE_ROOT", extPath, 1);
+ }
+ StorageRootBuffer = getenv("STORAGE_ROOT");
+#endif
+
struct sigaction act;
act.sa_sigaction = terminateHandler;
act.sa_flags = SA_SIGINFO;
@@ -1372,7 +1407,6 @@ main(int argc, char *argv[])
if( rsEVENTERROR == RsEventHandler(rsRWINITIALIZE, &openParams) )
{
RsEventHandler(rsTERMINATE, nil);
-
return 0;
}
diff --git a/src/skel/skeleton.cpp b/src/skel/skeleton.cpp
index 2bb234605..f4ee2e4b1 100644
--- a/src/skel/skeleton.cpp
+++ b/src/skel/skeleton.cpp
@@ -318,7 +318,7 @@ RsRwInitialize(void *displayID)
* Install any platform specific file systems...
*/
psInstallFileSystem();
-
+
/*
* Initialize debug message handling...
*/
@@ -339,7 +339,7 @@ RsRwInitialize(void *displayID)
{
return (FALSE);
}
-
+
openParams.displayID = displayID;
if (!RwEngineOpen(&openParams))
@@ -347,14 +347,14 @@ RsRwInitialize(void *displayID)
RwEngineTerm();
return (FALSE);
}
-
+
if (RsEventHandler(rsSELECTDEVICE, displayID) == rsEVENTERROR)
{
RwEngineClose();
RwEngineTerm();
return (FALSE);
}
-
+
if (!RwEngineStart())
{
RwEngineClose();
diff --git a/src/weapons/WeaponInfo.cpp b/src/weapons/WeaponInfo.cpp
index 1f78b7d45..6c2219331 100644
--- a/src/weapons/WeaponInfo.cpp
+++ b/src/weapons/WeaponInfo.cpp
@@ -30,7 +30,7 @@ uint16 CWeaponInfo::ms_aReloadSampleTime[WEAPONTYPE_TOTALWEAPONS] =
0, // ROCKET
250, // COLT45
250, // PYTHON
- 650, // SHOTGUN
+ 150, // SHOTGUN
650, // SPAS12 SHOTGUN
650, // STUBBY SHOTGUN
400, // TEC9
diff --git a/vendor/mpg123/lib/Android/arm64-v8a/libmpg123.so b/vendor/mpg123/lib/Android/arm64-v8a/libmpg123.so
index 36dcbe5db..c7f465738 100644
Binary files a/vendor/mpg123/lib/Android/arm64-v8a/libmpg123.so and b/vendor/mpg123/lib/Android/arm64-v8a/libmpg123.so differ
diff --git a/vendor/mpg123/lib/Android/armeabi-v7a/libmpg123.so b/vendor/mpg123/lib/Android/armeabi-v7a/libmpg123.so
index 2b2e32b94..a3b075712 100644
Binary files a/vendor/mpg123/lib/Android/armeabi-v7a/libmpg123.so and b/vendor/mpg123/lib/Android/armeabi-v7a/libmpg123.so differ
diff --git a/vendor/sdl2/libs/Android/arm64-v8a/libSDL2.so b/vendor/sdl2/libs/Android/arm64-v8a/libSDL2.so
index fc7f72cc9..2a986d5bf 100644
Binary files a/vendor/sdl2/libs/Android/arm64-v8a/libSDL2.so and b/vendor/sdl2/libs/Android/arm64-v8a/libSDL2.so differ
diff --git a/vendor/sdl2/libs/Android/armeabi-v7a/libSDL2.so b/vendor/sdl2/libs/Android/armeabi-v7a/libSDL2.so
index 9013ffc04..6a5966d8c 100644
Binary files a/vendor/sdl2/libs/Android/armeabi-v7a/libSDL2.so and b/vendor/sdl2/libs/Android/armeabi-v7a/libSDL2.so differ