Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,12 @@ compile_commands.json
*~
*.log
memcards/

# Android/Gradle
.gradle/
local.properties
**/.cxx/
*.apk
*.aar
captures/
*.keystore
64 changes: 36 additions & 28 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
cmake_minimum_required(VERSION 3.20)
project(CTR-Native C)
project(CTR-Native C CXX)
include(CheckCCompilerFlag)

set(CTR_NATIVE_VERSION "0.1.0-beta.7.1")

if(NOT CMAKE_SIZEOF_VOID_P EQUAL 4)
message(FATAL_ERROR
"CTR Native currently requires a 32-bit target. "
"Select Win32 for MSVC, an i686 MinGW compiler on Windows, or pass -m32 "
"in the initial compiler and linker flags on Linux."
)
endif()

set(CTR_NATIVE_MSVC_FRONTEND OFF)
if(CMAKE_C_COMPILER_ID STREQUAL "MSVC" OR CMAKE_C_SIMULATE_ID STREQUAL "MSVC")
set(CTR_NATIVE_MSVC_FRONTEND ON)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
if(NOT ANDROID)
if(NOT CMAKE_C_COMPILER_TARGET MATCHES "i.86" AND NOT CMAKE_SYSTEM_PROCESSOR MATCHES "i.86")
set(CMAKE_C_FLAGS "-m32 ${CMAKE_C_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "-m32 ${CMAKE_EXE_LINKER_FLAGS}")
endif()
endif()

set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE NEVER)
Expand Down Expand Up @@ -45,6 +38,11 @@ else()
set(CTR_NATIVE_BUILD_ID "${CTR_NATIVE_GIT_HASH}-dirty")
endif()

if(NOT ANDROID)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msse")
endif()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-error -g")

function(ctr_native_add_label_warning_if_supported target warning flag_check)
set(saved_required_flags "${CMAKE_REQUIRED_FLAGS}")
if(CMAKE_C_COMPILER_ID MATCHES "Clang")
Expand All @@ -60,21 +58,25 @@ function(ctr_native_add_label_warning_if_supported target warning flag_check)
endfunction()

# SDL3
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
set(SDL_STATIC ON CACHE BOOL "" FORCE)
set(SDL_SHARED ON CACHE BOOL "" FORCE)
set(SDL_STATIC OFF CACHE BOOL "" FORCE)
set(SDL_TEST_LIBRARY OFF CACHE BOOL "" FORCE)
set(SDL_TESTS OFF CACHE BOOL "" FORCE)
set(SDL_EXAMPLES OFF CACHE BOOL "" FORCE)
set(SDL_INSTALL OFF CACHE BOOL "" FORCE)
set(SDL_GPU OFF CACHE BOOL "" FORCE)
set(SDL_RENDER OFF CACHE BOOL "" FORCE)
set(SDL_RENDER ON CACHE BOOL "" FORCE)
set(SDL_CAMERA OFF CACHE BOOL "" FORCE)
set(SDL_SENSOR OFF CACHE BOOL "" FORCE)
set(SDL_SENSOR ON CACHE BOOL "" FORCE)
set(SDL_DIALOG OFF CACHE BOOL "" FORCE)
set(SDL_TRAY OFF CACHE BOOL "" FORCE)
set(SDL_POWER OFF CACHE BOOL "" FORCE)
set(SDL_POWER ON CACHE BOOL "" FORCE)
set(SDL_VULKAN OFF CACHE BOOL "" FORCE)
set(SDL_OPENGLES OFF CACHE BOOL "" FORCE)
if(ANDROID)
set(SDL_OPENGLES ON CACHE BOOL "" FORCE)
else()
set(SDL_OPENGLES OFF CACHE BOOL "" FORCE)
endif()
set(SDL_OFFSCREEN OFF CACHE BOOL "" FORCE)
set(SDL_SNDIO OFF CACHE BOOL "" FORCE)
set(SDL_SNDIO_SHARED OFF CACHE BOOL "" FORCE)
Expand All @@ -83,13 +85,12 @@ set(SDL_X11_XTEST OFF CACHE BOOL "" FORCE)
add_subdirectory(externals/SDL)

# CTR Native
add_executable(ctr_native main.c)
set_target_properties(ctr_native PROPERTIES
C_STANDARD 17
C_STANDARD_REQUIRED ON
C_EXTENSIONS OFF
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
)
if(ANDROID)
add_library(ctr_native SHARED main.c)
else()
add_executable(ctr_native main.c)
endif()
set_target_properties(ctr_native PROPERTIES LINKER_LANGUAGE CXX)
target_include_directories(ctr_native PRIVATE ${CMAKE_SOURCE_DIR}/include)
target_compile_definitions(ctr_native PRIVATE
CTR_NATIVE
Expand All @@ -110,8 +111,10 @@ if(CTR_NATIVE_MSVC_FRONTEND)
/wd5287
)
else()
if(NOT ANDROID)
target_compile_options(ctr_native PRIVATE -msse)
endif()
target_compile_options(ctr_native PRIVATE
-msse
-Wenum-conversion
-Wenum-compare
-Wswitch-enum
Expand All @@ -132,7 +135,12 @@ target_link_libraries(ctr_native SDL3::SDL3)
if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
target_link_options(ctr_native PRIVATE -static-libgcc)
endif()
if(MINGW)
target_link_libraries(ctr_native SDL3::SDL3)
if(ANDROID)
target_link_libraries(ctr_native log GLESv2 android)
endif()
target_link_options(ctr_native PRIVATE -static-libgcc)
if(WIN32)
target_link_options(ctr_native PRIVATE -static)
endif()

Expand Down
29 changes: 20 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
# CTR Native

A native PC port of Crash Team Racing (PS1, 1999), built on top of the [CTR-ModSDK](https://github.com/CTR-tools/CTR-ModSDK) decompilation project.

## Philosophy

- **No byte budget.** Game source lives in `game/` as our own copies. Edit freely.
- **No PSX toolchain.** Targets Windows and Linux with SDL3. No MIPS compiler needed.
- **Clean platform layer.** `main.c` owns process startup; host details stay in `platform/native_*`.
- **No build system nonsense.** Just `build.bat` / `build.sh`.
- **Fully static build.** Single executable, zero dependencies. SDL3 is compiled from vendored source and linked statically.
A native port of Crash Team Racing (PS1, 1999), built on top of the [CTR-ModSDK](https://github.com/CTR-tools/CTR-ModSDK) decompilation project.

## Directory Layout

Expand All @@ -19,8 +11,10 @@ ctr_native/
build-msvc.bat Windows build (MSVC x86)
build.bat Windows build (MinGW i686)
build.sh Linux build
android/ Android Gradle project and launcher
CMakePresets.json Shared CLion/command-line CMake configurations
README.md This file
README_ANDROID.md Android build and setup guide
game/ Our copies of all decompiled game source (943 files)
game_unity.h Ordered unity include chain for all game source files
include/ Project headers (structs, globals, declarations, platform facade)
Expand Down Expand Up @@ -60,6 +54,11 @@ sudo apt install gcc-multilib
sudo apt install libx11-dev libxext-dev libgl1-mesa-dev libasound2-dev libudev-dev libdbus-1-dev
```

### Android

See [README_ANDROID.md](README_ANDROID.md). Android currently builds 32-bit
`armeabi-v7a` and `x86` APKs and requires an OpenGL ES 3 capable device.

## Building

```
Expand All @@ -69,6 +68,13 @@ chmod +x build.sh
./build.sh # Linux
```

For Android:

```
cd android
./gradlew assembleDebug
```

The shared CMake presets can also be used directly or selected as CLion CMake profiles:

```
Expand All @@ -84,6 +90,7 @@ Output:
- MSVC: `build-msvc-x86/Release/ctr_native.exe`
- MinGW: `build/ctr_native.exe`
- Linux: `build/ctr_native`
- Android: `android/app/build/outputs/apk/debug/app-debug.apk`

### Clean build

Expand All @@ -100,6 +107,9 @@ rm -rf build/ # Linux: delete cached libraries

## Running

Android users select their own raw NTSC-U BIN through the in-app setup screen;
the app copies it into app-owned storage. See [README_ANDROID.md](README_ANDROID.md).

### Normal Setup

If you downloaded a release build, you only need two things for normal play:
Expand Down Expand Up @@ -197,4 +207,5 @@ main.c (entrypoint)
- [CTR-ModSDK](https://github.com/CTR-tools/CTR-ModSDK) — the decompilation project this is built on
- [PsyCross](https://github.com/OpenDriver2/PsyCross) — original PS1 compatibility code from which parts of CTR Native's owned platform layer and PsyQ facade headers are derived
- [SDL3](https://github.com/libsdl-org/SDL) — cross-platform multimedia
- [Simon Butt](https://github.com/Simon358) — initial Android port contribution
- Crash Team Racing is a trademark of Sony Computer Entertainment / Naughty Dog
40 changes: 40 additions & 0 deletions README_ANDROID.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# CTR Native Android Port

This project has been ported to Android using SDL3.

## Building

1. **Clone with submodules**:
`git clone --recursive https://github.com/Simon358/ctr-native-android`
2. **Open in Android Studio**: Open the `android` folder.
3. **Install NDK**: Go to `Settings > SDK Tools` and install `NDK (Side by side)` and `CMake`.
4. **Sync Gradle**: Click the "Elephant" icon.
5. **Build & Run**: The project is configured for 32-bit (`armeabi-v7a`) to maintain compatibility with the game's memory model.

## Assets Setup (Modern Android)

The game requires retail NTSC-U assets to run.

### Recommended: File Picker (Automatic Copy)
On first launch, the app will ask for **"All Files Access"** permission. After granting this:
1. In the "Missing Assets" dialog, click **"Select File"**.
2. Navigate to and select your `ctr-u.bin` or `BIGFILE.BIG` file.
3. The app will **automatically copy** the asset to its internal storage (`/data/data/com.ctrnative/files/assets/`).
4. **Restart the app**. Once copied, you no longer need the original file on your storage.

### Manual Setup (PC/Root)
Place your `ctr-u.bin` or extracted assets folder into:
`/storage/emulated/0/Android/data/com.ctrnative/files/assets/`

## Troubleshooting

- **Black Screen/Crash**: Ensure you have the correct NTSC-U retail assets.
- **Viewing Logs**: Run the following command to see detailed output and crash reports:
`adb logcat -s CTR-Native:V CTRNativeInput:V SDL:V AndroidRuntime:E`
- **OpenGL Errors**: This port requires **OpenGL ES 3.0**.

## Controls

- **Gamepads**: Supports standard Android gamepads (Backbone One, Xbox, DualShock) via SDL3.
- **Touch Controls**: On-screen buttons are automatically enabled if no gamepad is detected.
- **Vibration**: Supports native gamepad rumble via SDL3.
1 change: 1 addition & 0 deletions VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.1.0-beta.7.1
55 changes: 55 additions & 0 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
apply plugin: 'com.android.application'

android {
namespace 'com.ctrnative'
compileSdk 34

defaultConfig {
applicationId "com.ctrnative"
minSdk 21
targetSdk 34
versionCode 1
versionName "1.0"

ndk {
abiFilters 'armeabi-v7a', 'x86'
}

externalNativeBuild {
cmake {
arguments "-DANDROID_STL=c++_static", "-DCTR_NATIVE=ON"
abiFilters 'armeabi-v7a', 'x86'
}
}
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}

externalNativeBuild {
cmake {
path "../../CMakeLists.txt"
version "3.22.1"
}
}

sourceSets {
main {
java.srcDirs = [
'src/main/java',
'../../externals/SDL/android-project/app/src/main/java'
]
res.srcDirs = [
'src/main/res'
]
}
}
}

dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
}
19 changes: 19 additions & 0 deletions android/app/lint.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<lint>
<!-- SDL's activity sources perform the API and permission guards at runtime. -->
<issue id="MissingPermission">
<ignore regexp=".*externals/SDL/android-project/app/src/main/.*" />
</issue>
<issue id="NewApi">
<ignore regexp=".*externals/SDL/android-project/app/src/main/.*" />
</issue>
<issue id="StaticFieldLeak">
<ignore regexp=".*externals/SDL/android-project/app/src/main/.*" />
</issue>
<issue id="UnspecifiedRegisterReceiverFlag">
<ignore regexp=".*externals/SDL/android-project/app/src/main/.*" />
</issue>
<issue id="UnusedResources">
<ignore regexp=".*externals/SDL/android-project/app/src/main/.*" />
</issue>
</lint>
30 changes: 30 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-feature android:glEsVersion="0x00030000" android:required="true" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="CTR Native"
android:requestLegacyExternalStorage="true"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen">

<activity
android:name=".CTRNativeActivity"
android:configChanges="orientation|keyboardHidden|screenSize|screenLayout"
android:label="CTR Native"
android:screenOrientation="landscape"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
Loading