# 東方紅魔郷 SDL2 移植版 / th06-sdl2
> ⚠️ **归档通知 / Archived**
>
> 由于作者精力有限,本项目进入归档期。作者不会持续维护这个项目,仅在有兴致时随缘处理。
>
> Due to limited bandwidth, this project is now in archive mode. The author will no longer actively maintain it and will only touch it occasionally when the mood strikes.
**[English](#english)** | **[中文](#中文)**
---
## English
### About
This is a cross-platform port of [Touhou Koumakyou ~ the Embodiment of Scarlet Devil (東方紅魔郷)](https://en.touhouwiki.net/wiki/Embodiment_of_Scarlet_Devil) v1.02h, built on top of the [community-reconstructed source code](https://github.com/happyhavoc/th06). Direct3D 8 and the Win32 window/audio stack have been replaced with **SDL2** and a set of pluggable renderers.
### Rendering backends (runtime-selectable)
Three backends are compiled into the same executable. The active one is chosen by `--backend=...` on startup and clamped to whatever the device actually supports:
| Backend | Source | Default on | Notes |
|---|---|---|---|
| **Desktop OpenGL (fixed-function)** | `src/sdl2_renderer.cpp` | Windows / Linux (x86) | Closest to the original D3D8 behaviour; the `NoGouraud`-style legacy flags only apply here |
| **OpenGL ES 2.0 (shader)** | `src/RendererGLES.cpp` + `src/gles_shaders.h` | Android | Runs on any GLES2-capable GPU; used for the Android APK and for desktops without a working GL 2.1 |
| **Vulkan 1.x** | `src/RendererVulkan.cpp` + `src/vulkan/` | opt-in | Loaded via [volk](https://github.com/zeux/volk), allocator via [VMA](https://gpuopen.com/vulkan-memory-allocator/); enabled at build time with `-DTH06_USE_VULKAN=ON` |
Pick one at runtime:
```
th06.exe --backend=gl # desktop fixed-function GL
th06.exe --backend=gles # GLES 2.0 shader path
th06.exe --backend=vulkan # Vulkan (requires TH06_USE_VULKAN build)
```
The thprac config window (**Extra config → Renderer**) exposes the same choice and persists it to `th06.cfg`. Backends not compiled in or not available on the device are hidden from the radio set.
### Other features
- **SDL2_mixer** for BGM/SE, **SDL2_image** for runtime textures (PNG/BMP)
- **OGG BGM**, legacy WAV tree, and MIDI playback (through `SoundPlayer` / `MidiOutput`)
- **Runtime text-encoding detection** so the same binary can read GBK (Chinese) and Shift-JIS (Japanese) game data without a rebuild
- **thprac practice overlay** — stage select, practice tools, renderer/quality toggles, rendered with Dear ImGui
- **Runtime i18n** (`src/i18n.tpl` + `scripts/gen_i18n.py` → `i18n.hpp`)
- **PBG3 archive reader** in `src/pbg3/` for the original `.dat` files
- **Authoritative-model netplay** in `src/Netplay*` (prediction/rollback path is gated behind `-DTH06_ENABLE_PREDICTION_ROLLBACK=ON`; off by default)
- **Crash handler + watchdog** on both Windows (`CrashHandlerWin` / `WatchdogWin`) and POSIX
- **Android port** with its own Gradle project (see below)
- **CMake** build system with per-backend feature gates
### Requirements
- CMake ≥ 3.20
- C++17 compiler: MSVC (Visual Studio 2022) on Windows, GCC 13+ or Clang on Linux
- Python 3 for the `i18n.hpp` and encoding-table generators
- **Game data files** from `東方紅魔郷.exe` v1.02h — see *Running*
- *For the Vulkan backend:* LunarG Vulkan SDK (`VULKAN_SDK` env var) so CMake can locate headers and `glslangValidator`
- *For Android:* Android SDK + NDK `21.4.7075529` and JDK 17
The game file format and many structs assume `sizeof(void*) == 4`. Non-MSVC builds are therefore forced to `-m32`.
### Building
Windows (x86, default GL + GLES, no Vulkan):
```
cmake -B build_sdl2 -A Win32
cmake --build build_sdl2 --config Release --target th06
```
Windows (x86, GL + GLES + Vulkan):
```
cmake -B build_vk -A Win32 -DTH06_USE_VULKAN=ON
cmake --build build_vk --config Release --target th06
```
Linux (32-bit, GL + GLES):
```
PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig \
cmake -B build_linux_gles -DCMAKE_BUILD_TYPE=Release
cmake --build build_linux_gles --target th06
```
You will need the i386 dev packages: `sudo apt install gcc-multilib g++-multilib libsdl2-dev:i386 libsdl2-image-dev:i386 libsdl2-mixer-dev:i386 libgl-dev:i386`.
Android (Gradle wrapper drives CMake via the NDK):
```
cd android
./gradlew assembleRelease # -> android/app/build/outputs/apk/release/app-release.apk
```
The APK only ships `armeabi-v7a` (for the `sizeof(void*) == 4` reason above). Set `TH06_STORE_PASSWORD` / `TH06_KEY_PASSWORD` env vars before building if you replace the bundled debug keystore.
### Running
The game needs access to the original data files (`th06*_CM.DAT`, `...`_IN.DAT`, `..._MD.DAT`, `..._ST.DAT`, etc.) and optional OGG BGM under `BGM/`. Place them alongside the executable or run the executable from the data directory:
```
cd
/build_sdl2/Release/th06.exe --backend=gl
```
The Android build bundles the DAT files + OGG BGM into the APK (`android/app/src/main/assets/`); the `syncGameAssets` Gradle task copies them from the Windows build directory before packaging.
### Project layout
```
CMakeLists.txt # top-level build (th06 + vk_smoketest + th06_sdl2_test)
3rdparty/
SDL2/ SDL2_image/ SDL2_mixer/ # Windows prebuilts; Linux uses pkg-config
imgui/ # thprac overlay + ImGui GL2/GL3/Vulkan backends
volk/ Vulkan/ # Vulkan loader + headers/VMA
Detours/ rapidjson/ zstd/ munit/
src/
main.cpp # CLI parse, backend selection, restart loop
sdl2_renderer.cpp # Desktop OpenGL backend (IRenderer impl)
RendererGLES.cpp # OpenGL ES 2.0 backend (FBO-based fullscreen scaler)
gles_shaders.h # Embedded GLSL ES shader source
RendererVulkan.cpp # Vulkan backend (IRenderer impl)
vulkan/ # VkContext / VkSwapchain / VkFrameContext /
# VkRenderTarget / VkPipelineCache / VkResources /
# VkTextureManager / VmaUsage
IRenderer.hpp # Backend abstraction + runtime selection glue
BackendAvailability.cpp # Probe / ResolveBackend / PlatformDefaultBackend
GameWindow.cpp # SDL2 window, frame timing, renderer switch
SoundPlayer.cpp # BGM/SE via SDL2_mixer
MidiOutput.cpp # MIDI route
TextHelper.cpp # stb_truetype + runtime GBK/Shift-JIS detection
AnmManager, BulletManager, EnemyManager, Player, Stage,
EclManager, GameManager, Supervisor, ScreenEffect, ...
# game logic ported from the D3D8 reconstruction
Netplay* # authoritative-model netplay + (optional) rollback
thprac_* # practice-mode overlay (ImGui)
pbg3/ # PBG3 archive reader
CrashHandlerWin/Posix, WatchdogWin/Posix
AndroidTouchInput, TouchVirtualButtons, MenuTouchButtons
shaders_vk/ # GLSL sources for the Vulkan backend (glslangValidator)
android/ # Android Gradle project (SDL2 Java glue + JNI CMake)
app/src/main/java/com/th06/game/ # CompatTouchOverlay, GamePerformanceService,
# SessionLogCollector (Android-specific
# quality-of-life work; applicationId is
# tianqi233.th06.game)
scripts/ # gen_i18n.py, gen_encoding_tables.py, Ghidra export,
# decomp/objdiff pipeline helpers
config/ # decomp mapping data (mapping.csv, globals.csv, ...)
tools/ # vk_smoketest (Vulkan gating harness) + relay_service
tests/ # minimal munit-based tests (PBG3)
```
### Platform notes
- **Android — tested on Mali GPUs after #5**: fragment shaders have no default `int` precision in GLSL ES 1.00, so the GLES preamble injects `precision highp float; precision highp int;` from `CompileShader` to keep VS/FS uniform precision matching on strict drivers (Mali-G51 etc.). `gles_shaders.h` is kept source-clean so desktop GL is unaffected.
- **Android — vivo OriginOS "PEM" freezing**: the app declares a `FOREGROUND_SERVICE_SPECIAL_USE` service (`GamePerformanceService`) plus a persistent notification to stop the OS from cgroup-freezing the process while it is supposed to be interactive.
- **Android — touch dispatch on Android 12+**: a `CompatTouchOverlay` sits on top of the SDL surface to bypass BLAST-pipeline input-throttling and recover dropped historical events.
- **Windows x86 Release**: core gameplay translation units are built with `/arch:IA32 /fp:strict /Ob1 /Oy- /Od` to stay close to the original VC7 codegen so stock replays / demos stay bit-for-bit compatible.
### Credits
- [happyhavoc/th06](https://github.com/happyhavoc/th06) — reverse-engineered baseline source
- [Team Shanghai Alice](https://www16.big.or.jp/~zun/) / ZUN — original game
- [SDL2](https://www.libsdl.org/), [Vulkan](https://www.khronos.org/vulkan/), [Dear ImGui](https://github.com/ocornut/imgui), [volk](https://github.com/zeux/volk), [VMA](https://gpuopen.com/vulkan-memory-allocator/), [zstd](https://facebook.github.io/zstd/), [rapidjson](https://rapidjson.org/)
### ⚠️ Developer Disclaimer (A Note on this Project's "Silicon Content")
> **TL;DR:** Yes, this project is 100% **vibe-coded**!
>
> Confession time: A significant portion of the underlying code in this repo was generated with the help of LLMs. If you're scrolling through the commit history and catch a strong whiff of "AI," trust your instincts — your radar is spot on.
>
> My primary goal was straightforward: **achieve cross-platform compatibility** and get the game running smoothly with **full hardware-accelerated rendering on modern machines**, finally breaking free from the ancient shackles of D3D8. To quickly validate this idea and hit that goal, I happily outsourced all the tedious C++ grunt work, API swapping, and low-level duct-taping to my AI assistant.
>
> The core philosophy here is simple: "The code might look a bit abstract, but hey, it actually runs on modern hardware." 🛠️
---
## 中文
### 项目简介
本仓库是 [東方紅魔郷 ~ the Embodiment of Scarlet Devil](https://en.touhouwiki.net/wiki/Embodiment_of_Scarlet_Devil) v1.02h 的跨平台移植,基于社区 [逆向重建的源代码](https://github.com/happyhavoc/th06)。Direct3D 8 和 Win32 的窗口/音频栈被替换成了 **SDL2** 以及一组可切换的渲染后端。
### 渲染后端(运行时可切换)
三种后端都编进同一份可执行文件,启动时用 `--backend=...` 选择,实际使用的后端会被 "device 支持性检测" 再夹紧一次:
| 后端 | 源码 | 默认平台 | 备注 |
|---|---|---|---|
| **Desktop OpenGL(固定管线)** | `src/sdl2_renderer.cpp` | Windows / Linux(x86) | 行为最接近原版 D3D8;`NoGouraud` 等遗留选项仅在该路径生效 |
| **OpenGL ES 2.0(Shader)** | `src/RendererGLES.cpp` + `src/gles_shaders.h` | Android | 覆盖所有 GLES2 设备;Android APK 与桌面端 GL 2.1 不可用时走这一条 |
| **Vulkan 1.x** | `src/RendererVulkan.cpp` + `src/vulkan/` | 可选 | 通过 [volk](https://github.com/zeux/volk) 运行期加载,分配器使用 [VMA](https://gpuopen.com/vulkan-memory-allocator/);需 `-DTH06_USE_VULKAN=ON` |
运行时选择:
```
th06.exe --backend=gl # 桌面固定管线 GL
th06.exe --backend=gles # GLES 2.0 Shader 路径
th06.exe --backend=vulkan # Vulkan(需 TH06_USE_VULKAN 构建)
```
thprac 的扩展配置面板(**扩展配置 → 渲染器**)也能切换,并写回 `th06.cfg`。当前设备/构建不可用的后端会从单选按钮里自动隐藏。
### 其他特性
- **SDL2_mixer** 负责 BGM/SE;**SDL2_image** 负责运行期纹理加载(PNG/BMP)
- 支持 **OGG BGM**、旧版 WAV 目录和 **MIDI**(`SoundPlayer` / `MidiOutput`)
- **运行时编码检测**:同一份二进制可直接读取 GBK(中文版)与 Shift-JIS(日文版)的游戏数据,无需重新编译
- **thprac 练习模式覆盖层**:关卡选择、练习工具、渲染/画质选项,使用 Dear ImGui 渲染
- **运行时 i18n**:`src/i18n.tpl` + `scripts/gen_i18n.py` 生成 `i18n.hpp`
- **PBG3 归档解析器** 位于 `src/pbg3/`,用于读取原版 `.dat`
- **权威模型联机** 位于 `src/Netplay*`,预测回滚路径由 `-DTH06_ENABLE_PREDICTION_ROLLBACK=ON` 开启,默认关闭
- **崩溃处理 + Watchdog**:Windows(`CrashHandlerWin` / `WatchdogWin`)与 POSIX 两套实现
- **Android 移植**:独立 Gradle 工程(详见下文)
- **CMake** 构建系统,按后端分别开关
### 环境要求
- CMake ≥ 3.20
- C++17 编译器:Windows 下 MSVC(Visual Studio 2022),Linux 下 GCC 13+ 或 Clang
- Python 3(用于生成 `i18n.hpp` 与编码表)
- **原版 `東方紅魔郷.exe` v1.02h 的游戏数据文件** — 见下文"运行"
- *启用 Vulkan 后端:* LunarG Vulkan SDK(设置 `VULKAN_SDK` 环境变量),CMake 需要 headers 和 `glslangValidator`
- *Android:* Android SDK + NDK `21.4.7075529` + JDK 17
游戏文件格式与大量结构体假设 `sizeof(void*) == 4`,所以 GCC/Clang 路径会强制 `-m32`。
### 构建
Windows(x86,默认 GL + GLES,不带 Vulkan):
```
cmake -B build_sdl2 -A Win32
cmake --build build_sdl2 --config Release --target th06
```
Windows(x86,GL + GLES + Vulkan):
```
cmake -B build_vk -A Win32 -DTH06_USE_VULKAN=ON
cmake --build build_vk --config Release --target th06
```
Linux(32 位,GL + GLES):
```
PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig \
cmake -B build_linux_gles -DCMAKE_BUILD_TYPE=Release
cmake --build build_linux_gles --target th06
```
需先装好 i386 dev 包:`sudo apt install gcc-multilib g++-multilib libsdl2-dev:i386 libsdl2-image-dev:i386 libsdl2-mixer-dev:i386 libgl-dev:i386`。
Android(Gradle wrapper 会通过 NDK 驱动 CMake):
```
cd android
./gradlew assembleRelease # 产物:android/app/build/outputs/apk/release/app-release.apk
```
APK 仅包含 `armeabi-v7a`(同前面那个 `sizeof(void*) == 4` 的原因)。如果要替换内置的 debug keystore,构建前设置 `TH06_STORE_PASSWORD` / `TH06_KEY_PASSWORD` 环境变量即可。
### 运行
游戏需要访问原版数据文件(`th06*_CM.DAT`、`..._IN.DAT`、`..._MD.DAT`、`..._ST.DAT` 等)以及 `BGM/` 下可选的 OGG 音乐。把它们放在可执行文件旁边,或者直接从游戏数据目录启动:
```
cd <游戏数据路径>
<路径>/build_sdl2/Release/th06.exe --backend=gl
```
Android 构建会把 DAT + OGG BGM 一起打进 APK 的 `android/app/src/main/assets/`;Gradle 里的 `syncGameAssets` 任务会在打包前从 Windows 构建目录同步这些资源。
### 项目结构
```
CMakeLists.txt # 顶层构建(th06 + vk_smoketest + th06_sdl2_test)
3rdparty/
SDL2/ SDL2_image/ SDL2_mixer/ # Windows 内置预编译库;Linux 走 pkg-config
imgui/ # thprac 覆盖层 + ImGui GL2/GL3/Vulkan 后端
volk/ Vulkan/ # Vulkan 加载器 + headers/VMA
Detours/ rapidjson/ zstd/ munit/
src/
main.cpp # CLI 解析、后端选择、重启循环
sdl2_renderer.cpp # Desktop OpenGL 后端(IRenderer 实现)
RendererGLES.cpp # OpenGL ES 2.0 后端(基于 FBO 的全屏缩放)
gles_shaders.h # 内嵌 GLSL ES shader 源
RendererVulkan.cpp # Vulkan 后端(IRenderer 实现)
vulkan/ # VkContext / VkSwapchain / VkFrameContext /
# VkRenderTarget / VkPipelineCache / VkResources /
# VkTextureManager / VmaUsage
IRenderer.hpp # 后端抽象 + 运行时选择
BackendAvailability.cpp # Probe / ResolveBackend / PlatformDefaultBackend
GameWindow.cpp # SDL2 窗口、帧计时、渲染器切换
SoundPlayer.cpp # SDL2_mixer 做 BGM/SE
MidiOutput.cpp # MIDI 路径
TextHelper.cpp # stb_truetype + 运行时 GBK/Shift-JIS 检测
AnmManager, BulletManager, EnemyManager, Player, Stage,
EclManager, GameManager, Supervisor, ScreenEffect, ...
# 从 D3D8 逆向版移植过来的游戏逻辑
Netplay* # 权威模型联机 +(可选)回滚
thprac_* # 练习模式覆盖层(ImGui)
pbg3/ # PBG3 归档解析
CrashHandlerWin/Posix, WatchdogWin/Posix
AndroidTouchInput, TouchVirtualButtons, MenuTouchButtons
shaders_vk/ # Vulkan 后端的 GLSL 源(glslangValidator 编译)
android/ # Android Gradle 工程(SDL2 Java 胶水 + JNI CMake)
app/src/main/java/com/th06/game/ # CompatTouchOverlay、GamePerformanceService、
# SessionLogCollector 等 Android 端稳定性代码
# (applicationId = tianqi233.th06.game)
scripts/ # gen_i18n.py、gen_encoding_tables.py、Ghidra 导出脚本、
# 反编译/objdiff 流水线辅助
config/ # 反编译映射数据(mapping.csv、globals.csv 等)
tools/ # vk_smoketest(Vulkan Gate 用的独立测试)+ relay_service
tests/ # 基于 munit 的最小测试(PBG3)
```
### 平台小贴士
- **Android — 针对 Mali GPU 的 #5 修复**:GLSL ES 1.00 的片段着色器 `int` 没有默认精度,`CompileShader` 里统一在 VS/FS 前注入 `precision highp float; precision highp int;` 前缀,让 Mali-G51 之类严格派驱动认可 uniform 精度匹配。`gles_shaders.h` 保持不改,桌面 GL 路径不受影响。
- **Android — 对抗 vivo OriginOS 的 PEM 冻结**:AndroidManifest 声明了 `FOREGROUND_SERVICE_SPECIAL_USE` 前台服务(`GamePerformanceService`)+ 常驻通知,阻止系统在交互中途把进程放进 `/background` cgroup。
- **Android — Android 12+ 触控**:在 SDL surface 上叠了一层 `CompatTouchOverlay`,用于绕过 BLAST 渲染管线带来的输入节流,并补回被系统吞掉的 historical events。
- **Windows x86 Release**:核心玩法 .cpp 用 `/arch:IA32 /fp:strict /Ob1 /Oy- /Od` 编译,尽量贴近原版 VC7 代码生成,让标准 replay / demo 保持位级兼容。
### 致谢
- [happyhavoc/th06](https://github.com/happyhavoc/th06) — 原始逆向重建源码
- [上海爱丽丝幻乐团](https://www16.big.or.jp/~zun/) / ZUN — 原版游戏
- [SDL2](https://www.libsdl.org/)、[Vulkan](https://www.khronos.org/vulkan/)、[Dear ImGui](https://github.com/ocornut/imgui)、[volk](https://github.com/zeux/volk)、[VMA](https://gpuopen.com/vulkan-memory-allocator/)、[zstd](https://facebook.github.io/zstd/)、[rapidjson](https://rapidjson.org/)
### ⚠️ 开发者叠甲时间(关于本项目含硅量的声明)
> **太长不看版:** 没错,这是一个纯正的 **Vibe-coded(凭感觉编程)** 项目!
>
> 坦白局:本项目含有大量由 LLM 辅助生成的底层代码。如果你在翻看 commit 记录时闻到了一股浓浓的"AI 味",自信点,你的直觉非常准确。
>
> 我的核心目的其实非常纯粹:**实现跨平台运行**,并让游戏在**现代机器上能顺利跑满硬件加速**,彻底摆脱古老的 D3D8 的束缚。为了快速达成这个目标,那些繁琐的 C++ 搬砖活儿、API 替换和底层缝合,就全权委托给 AI 助理了。
>
> 主打一个"代码虽然抽象,但它在现代机器上真能跑"。🛠️