Touhou 7 (Switch)
Touhou 7: Perfect Cherry Blossom
About the project
Touhou Youyoumu ~ Perfect Cherry Blossom es un videojuego danmaku de desplazamiento vertical y el séptimo videojuego oficial de la serie Touhou Project hecha por Team Shanghai Alice, y el segundo en ser lanzado para Microsoft Windows después de Embodiment of Scarlet Devil. El juego completo fue lanzado por primera vez el 17 de agosto de 2003, en la Comiket 64.
2 versions of Touhou 7: Perfect Cherry Blossom
You're viewing: Touhou 7 (Switch)
Official project information
Touhou 7: Perfect Cherry Blossom — Nintendo Switch Port
(東方妖々夢 〜 Perfect Cherry Blossom)
A native homebrew port of ZUN's 2003 bullet hell danmaku classic Touhou 7: Perfect Cherry Blossom for the Nintendo Switch (Horizon OS).
Built on the some100/th07 decompilation (reallyportable branch), this port runs the game through SDL3 on an OpenGL ES 3 context over Nouveau/Mesa, talks to the console's audio hardware directly via libnx AUDOUT, and paces itself to a locked 60 FPS on Horizon — no Linux, Box64 or Wine involved.
Companion to the Touhou Switch ports, with matching controls.
🆕 What's New (September 2026 update)
- 📁 One folder for all Touhou ports: the game can now live in
sd:/switch/touhou/touhou7/(recommended), next to the other ports. The NRO's own folder is always checked first, so your current setup keeps working. - 🏷️ The NRO is now
touhou7.nro, matching the other ports (touhou6.nro,touhou8.nro…). Delete the oldth07.nrowhen updating so hbmenu doesn't list the game twice. - 🎮 Remappable controls, like the other Touhou ports: the in-game Key Config now works with the Switch buttons, and ZL/ZR act as L/R. The default layout is the same as every other port (B shoot, A bomb, L/ZL focus, R/ZR skip, + pause); the D-Pad and sticks only move.
- Gameplay itself is unchanged.
✨ Key Features
- 🚀 Locked 60 FPS: Horizon's EGL implementation does not block on swap, so the renderer would otherwise free-run at ~120 FPS while the logic ticked at 60. The port paces presentation against an absolute deadline, giving stable frames and noticeably less battery drain.
- 🌸 OLED-Friendly Pillarboxing: The original 640×480 playfield is centred inside the Switch's 1280×720 display with pure black (
#000000) bars. - 🔊 Direct AUDOUT Audio: Rather than layering the miniaudio engine on top of SDL's audio backend, the port drives libnx AUDOUT itself with four page-aligned buffers cycled in strict release order at the hardware's native 48 kHz — no resampling anywhere in the path. BGM is preloaded into RAM so SD card reads never stall playback.
- 🎮 Sane, Remappable Controls: Joy-Con (handheld, grip, detached) and Pro Controller with the same default layout as every other Touhou Switch port (B shoot, A bomb, L/ZL focus, R/ZR skip, + pause); the in-game Key Config can rebind them.
- 🌏 Language-Aware Title: hbmenu shows the original Japanese title on consoles set to 日本語 and the romanised one everywhere else, filled across all 16 NACP language slots.
- 📁 Flexible Data Location: The game data can sit in
sd:/switch/touhou/touhou7/(recommended),sd:/switch/th07/,sd:/th07/,sd:/touhou7/,sd:/games/th07/and more — folder names are matched case-insensitively against real directory entries, so FAT32 capitalisation quirks do not matter. - 💾 Saves Next to the Data:
th07.cfg,score.dat, replays and snapshots are written into the same SD folder the game loaded from.
📥 Installation Guide
⚠️ Disclaimer: In compliance with ZUN's guidelines and copyright law, this repository contains ONLY the homebrew engine code. No game assets are distributed. You must legally own a copy of Touhou 7: Perfect Cherry Blossom v1.00b.
1. SD Card File Structure
- Ensure your Nintendo Switch is running custom firmware (Atmosphère CFW).
- Download the latest
touhou7.nrofrom the Releases tab (or build from source). Older releases were calledth07.nro/touhou07.nro; delete the old file when updating so hbmenu doesn't list the game twice. - Create the folder
sd:/switch/touhou/touhou7/and copy the following into it:
sd:/switch/touhou/touhou7/
├── touhou7.nro # Nintendo Switch homebrew executable
├── th07.dat # Main game archive
├── thbgm.dat # Background music archive
└── msgothic.ttc # Japanese font (ships with the Windows release)
Recommended place: sd:/switch/touhou/touhou7/. Keeping every Touhou port in one sd:/switch/touhou/ folder (touhou6, touhou7, touhou8 …) is much tidier than a separate folder per game. Other places still work: the port first looks in its own folder (wherever the NRO is), then for a th07 / touhou7 (or touhou 7, pcb, perfect cherry blossom) folder (any capitalisation) directly on the SD card, in switch/, touhou/, switch/touhou/, games/ or roms/ — so older layouts keep working.
2. Music
Unlike Touhou 6, no extra soundtrack download is needed: PCB ships its BGM inside thbgm.dat, and the port plays it directly. MIDI mode is unavailable (Horizon has no system synthesizer) and is not needed.
3. Launching
Run touhou7.nro from the Homebrew Menu (hbmenu), Sphaira launcher, or a home screen forwarder.
🕹 Controls
| Nintendo Switch Button | Action |
|---|---|
| Left Stick / D-Pad | Character Movement |
| B | Shoot / Confirm |
| A | Bomb / Cancel |
| L / ZL | Focus (Precision Slow-Motion Movement) |
| R / ZR | Skip Dialogue (hold) |
| + (Plus) | Pause / In-Game Menu |
The same default layout in every Touhou Switch port: B shoots, A bombs, L/ZL focuses, R/ZR skips dialogue, + pauses. These are only defaults — the Switch buttons act as the game's own gamepad, so the in-game Key Config can rebind them, and Default there brings this layout back. The D-Pad and sticks only move — they can never be picked as a button. Key Config numbers: 0 A, 1 B, 2 X, 3 Y, 4 L/ZL, 5 R/ZR, 7 +.
ZUN's "shot slow" auto-focus (holding shot also engages focus) defaults to off here, but can be turned back on in Option and will be respected.
🛠 Building from Source
Automated Build (GitHub Actions)
This repository includes a CI pipeline (.github/workflows/build-switch.yml). Push or fork the repository and the workflow compiles touhou7.nro inside the official devkitpro/devkita64 container, uploading it as a downloadable artifact.
Local Build (Linux / macOS / WSL)
-
Install devkitPro with
devkitA64andlibnx. -
Install the required Switch portlibs:
sudo dkp-pacman -Syu sudo dkp-pacman -S switch-dev switch-mesa switch-libdrm_nouveau switch-freetype switch-libpng switch-libjpeg-turbo switch-zlib -
Build:
export DEVKITPRO=/opt/devkitpro ./scripts/build_switch.sh # SDL3 stack + game ./scripts/build_switch.sh game # rebuild the game onlyThe result is
touhou7.nroin the repository root.
The script cross-compiles an SDL3 stack into ext/prefix first, because devkitPro only ships SDL2 for Switch and the official SDL3 Switch port is NDA-gated:
- SDL3
release-3.4.2+platform/switch/sdl3-switch.patch— the libnx video/audio/joystick backend from neomody77/sdl3-switch (zlib) — plusplatform/switch/sdl3-switch-gles3.patch, which makes the EGL context honourSDL_GL_SetAttributeso the game gets GLES 3 instead of GLES 2. Without it the VAO-based renderer will not run. - SDL3_image (stb JPEG backend only) and SDL3_ttf (portlibs freetype, no harfbuzz).
Debugging on hardware:
TH_SWITCH_NXLINK=1 ./scripts/build_switch.sh game
nxlink -s touhou7.nro
📂 What This Fork Changes
Everything Switch-specific is confined to platform/switch/ or guarded by #ifdef __SWITCH__, keeping rebases onto upstream cheap:
| File | Purpose |
|---|---|
platform/switch/PlatformSwitch.cpp | romfs, optional nxlink stdio, SD data folder discovery |
platform/switch/SwitchAudio.cpp | miniaudio → libnx AUDOUT bridge |
platform/switch/icon.jpg | 256×256 NRO icon |
scripts/build_switch.sh | one-shot dependency + game build |
scripts/nacp_lang.py | per-language NACP titles |
src/GameWindow.cpp | 60 FPS frame pacer |
src/Controller.cpp | fixed Switch button layout |
src/SoundPlayer.cpp | device-less miniaudio engine at 48 kHz |
src/FileSystem.cpp, src/Supervisor.cpp, src/main.cpp | SD paths, config defaults, startup hooks |
One upstream bug was fixed along the way: ResultScreen::OpenScore allocated ScoreDat with a bare new, leaving decodedData uninitialised. On desktop, fresh OS pages happen to be zero and the "no score.dat yet" path survives; on Horizon the heap is dirty, so ReleaseScoreDat called free() on garbage and the game crashed on first launch. Worth upstreaming.