Nintendo DS homebrew written in Embedded Swift, ported from the official libnds examples. Each example is a faithful translation of its C/C++ original that compiles with an Embedded Swift toolchain and runs on real hardware / emulators (melonDS, DeSmuME).
| Directory | Ported from | Demonstrates |
|---|---|---|
| hello_world | hello_world |
Console output, VBlank IRQ handler, touch read |
| ansi_console | Graphics/Printing/ansi_console |
ANSI cursor escape sequences |
| print_both_screens | Graphics/Printing/print_both_screens |
A text console on each screen (main + sub) |
| console_windows | Graphics/Printing/console_windows |
Multiple windowed consoles over one map |
| stopwatch | time/stopwatch |
Hardware timer elapsed/pause, formatted output |
| timercallback | time/timercallback |
Timer IRQ callback (C function pointer), PSG sound |
| realtimeclock | time/RealTimeClock |
RTC via time()/gmtime, digital + 3D analog watch face, day-of-week calc |
| pxi | pxi |
Dual-CPU: Swift ARM9 ↔ custom C ARM7 over a PXI channel (firmware JEDEC ID, touch pressure) |
| keyboard_stdin | input/keyboard/keyboard_stdin |
On-screen keyboard, callback field, iscanf stdin |
| keyboard_async | input/keyboard/keyboard_async |
Polled keyboard via keyboardUpdate |
| sprites_simple | Graphics/Sprites/simple |
OAM sprites on both engines, VRAM banks, palettes |
| touch_test | input/Touch_Pad/touch_test |
grit-converted sprite, OAM, touch min/max tracking |
| custom_font | Graphics/Printing/custom_font |
grit bitmap font loaded into a console |
| bg_256color | Graphics/Backgrounds/256_color_bmp |
grit 8bpp bitmap background |
| bg_16bit | Graphics/Backgrounds/16bit_color_bmp |
grit 16bpp bitmap, LZ77 decompress into VRAM |
| double_buffer | Graphics/Backgrounds/Double_Buffer |
Double-buffered bitmap BG via map-base flip |
| tilemap | Graphics/grit/256colorTilemap |
grit tiles+map+palette, BGCTRL/char-base |
| effects_windows | Graphics/Effects/windows |
Hardware display window over a BG |
| sprite_rotate | Graphics/Sprites/sprite_rotate |
oamRotateScale, size-doubling |
| bitmap_sprites | Graphics/Sprites/bitmap_sprites |
Sprites in bitmap/256/16-colour formats |
| sprite_ext_palettes | Graphics/Sprites/sprite_extended_palettes |
Extended sprite palettes |
| animate_simple | Graphics/Sprites/animate_simple |
Two sprite-animation strategies (copy vs. preload) |
| allocation_test | Graphics/Sprites/allocation_test |
Sprite-gfx allocator stress test, oamAllocateGfx/Free |
| ext_palettes_bg | Graphics/Ext_Palettes/backgrounds |
Extended BG palettes, 8bpp tiled BGs |
| rotscale_text | Graphics/Printing/rotscale_text |
Rotating/scaling text on an ExRotation BG |
| ortho | Graphics/3D/Ortho |
Orthographic vs. perspective projection toggle |
| both_screens_3d | Graphics/3D/3D_Both_Screens |
3D on both screens via display capture (cube + pyramid) |
| nehe1 | Graphics/3D/nehe/lesson01 |
Minimal NeHe 3D setup |
| nehe2 | Graphics/3D/nehe/lesson02 |
First polygons (triangle + quad) |
| nehe3 | Graphics/3D/nehe/lesson03 |
Per-vertex colour |
| nehe4 | Graphics/3D/nehe/lesson04 |
Rotation (glRotatef) |
| nehe5 | Graphics/3D/nehe/lesson05 |
Solid 3D pyramid + colour cube |
| nehe6 | Graphics/3D/nehe/lesson06 |
PCX texture (bin2s blob + runtime loadPCX) on a cube |
| nehe7 | Graphics/3D/nehe/lesson07 |
Lit textured cube, normals, toggleable lighting |
| nehe8 | Graphics/3D/nehe/lesson08 |
Alpha-blended translucent cube |
| nehe9 | Graphics/3D/nehe/lesson09 |
Blended colour-cycling star field |
| nehe10 | Graphics/3D/nehe/lesson10 |
Textured world walkthrough (runtime-parsed World.txt), hardware fog + shadow-polygon cube |
| nehe10b | Graphics/3D/nehe/lesson10b |
All-fixed-point variant: v16/t16 geometry, integer LUT camera (sinLerp/glRotatef32i) |
| nehe11 | Graphics/3D/nehe/lesson11 |
Waving-flag texture grid mesh |
| mixed_text_3d | Graphics/3D/Mixed_Text_3D |
3D + text console sharing one screen, %f printf |
| display_list | Graphics/3D/Display_List |
Hand-built display list (packed FIFO commands) |
| simple_tri | Graphics/3D/Simple_Tri |
3D engine, GL pipeline, fixed-point + float GL calls |
| simple_quad | Graphics/3D/Simple_Quad |
3D GL_QUAD, D-pad rotation |
| display_list_2 | Graphics/3D/Display_List_2 |
bin2o display list (teapot), hardware lighting |
| toon_shading | Graphics/3D/Toon_Shading |
Display-list blob, toon table, stylus rotate |
| textured_quad | Graphics/3D/Textured_Quad |
Texture blob, glTexImage2D, texcoords/normals |
| textured_cube | Graphics/3D/Textured_Cube |
Texture blob, vertex/uv tables, display-capture motion blur |
| env_mapping | Graphics/3D/Env_Mapping |
TEXGEN_NORMAL reflection mapping, texture matrix |
| picking | Graphics/3D/Picking |
3D picking via gluPickMatrix + position test |
| boxtest | Graphics/3D/BoxTest |
Hardware frustum BoxTest/BoxTestf, cull modes, vertex/poly RAM + cpuTiming readout |
| capture | Graphics/capture/ScreenShot |
Rotating bitmap/256/16-colour sprites, display capture → 24-bit BMP saved via libfat |
| rotation | Graphics/Backgrounds/rotation |
Raw bitmap+palette blobs, rotation/scale background |
| all_in_one | Graphics/Backgrounds/all_in_one |
~40-demo menu: every BG type/size, HW+SW scrolling, mosaic/rotate/scale, ext palettes, multilayer — pre-assembled grit .s assets |
| paletted_cube | Graphics/3D/Paletted_Cube |
All DS texture formats (grit .tga + compressed blobs), palette swap |
| gl2d_primitives | Graphics/gl2d/primitives |
Easy GL2D: boxes/triangles/lines/pixels, sinLerp |
| gl2d_dual_screen | Graphics/gl2d/dual_screen |
GL2D mirrored to both screens via display capture |
| gl2d_fonts | Graphics/gl2d/fonts |
GL2D sprite-set bitmap fonts (grit atlas + uvcoord tables) |
| gl2d_scrolling | Graphics/gl2d/scrolling |
GL2D tile-set scrolling engine + animated sprite, camera |
| gl2d_sprites | Graphics/gl2d/sprites |
GL2D sprite rotate/scale/stretch/flip, palette-swap tint |
| gl2d_2dplus3d | Graphics/gl2d/2Dplus3D |
3D textured mesh (ported vertex buffer) + GL2D 2D sprites, fixed-point atan2 |
| wifi_autoconnect | dswifi/autoconnect |
dswifi: connect via firmware WFC settings, print DHCP IP/gateway/DNS |
| wifi_httpget | dswifi/httpget |
dswifi: DNS resolve + TCP socket + live HTTP/1.1 GET, prints response |
| wifi_ap_search | dswifi/ap_search |
dswifi: AP scan/select UI, signal-strength icon (VBlank IRQ), WEP/WPA key entry via on-screen keyboard, connect + DNS |
| exception_test | debugging/exceptionTest |
Default exception handler, raw memory access |
Requirements:
- devkitPro with
devkitARM,libnds,calico, andndstool(dkp-pacman -S nds-dev). - An Embedded-Swift-capable Swift toolchain (a recent
DEVELOPMENT-SNAPSHOT).
export DEVKITPRO=/opt/devkitpro
export DEVKITARM=$DEVKITPRO/devkitARM
cd hello_world && make # or any other example dir
# -> hello_world.nds
make SWIFTC=/path/to/swiftc # if swiftc isn't the Embedded toolchain on PATHThe repo is a Swift package (Package.swift) that vends an
idiomatic Swift overlay over libnds, so an example can import NDS and write
import NDS
IRQ.vblank.set { frame += 1 }
Console.demoInit()
Console.print("Hello DS dev'rs\n")
while System.mainLoop {
System.waitForVBlank()
Keys.scan()
if Keys.down.contains(.start) { break }
}instead of the raw C (videoSetMode(MODE_0_2D.rawValue), irqSet, nds_puts, …).
Sources/CNDSis a systemLibrary target: the C interop (module map, umbrella header, and the shim) that exposes<nds.h>+ gl2d + dswifi asimport CNDS. This is the single source of truth the Makefiles also use.Sources/NDSis the overlay — one file per libnds subsystem (Video, Background, Sprite, Console, VideoGL, GL2D, Sound, Input, Timer, Interrupt, DMA, and the rest). Each C prefix becomes a caselessenumnamespace, C flag masks becomeOptionSets (Key,IRQ), and the macro-only APIs the importer drops (RGB15,inttov16, the pointer/register macros) get real Swift types. Every wrapper is@inline(__always), so it compiles down to the exact underlying libnds call — zero overhead.
Sources/NDS/Exports.swift does @_exported import CNDS, so import NDS also
re-exports the full raw libnds API: nothing is hidden, and the two styles mix
freely. Every example is ported to the overlay — each source/main.swift
does import NDS and uses the idiomatic wrappers (Video, Background,
Sprite/OAM, GL, GL2D, Keys, Console, Wifi, …), dropping to raw
CNDS/_Volatile only for the handful of genuinely low-level bits (custom
hardware registers in capture / all_in_one, the
glColorTableEXT palette calls, raw sockets in wifi_httpget).
Note: SwiftPM cannot cross-compile Embedded Swift to the DS's bare-metal
armv5te, soswift builddoes not produce a.nds. The package is a source library; the Makefiles below compile theNDStarget into a standalone Embedded Swift module and link it into each ROM.
Everything shared lives in common/ and Sources/; each
example is just a source/main.swift plus a three-line Makefile that sets
TARGET and includes common/common.mk. Examples with assets set
GRAPHICS := gfx (a directory of .png/.bmp/.tga + .grit pairs) and/or
DATA := data (a directory of .bin blobs); both can be set at once.
EXTRA_HEADERS := … lists hand-written headers (e.g. texture-packer uvcoord
tables) to expose to Swift with the same nds_asset_*() accessor treatment.
ASM_ASSETS := asmdata points at a directory of ready-made grit/Cearn .s
data files (assembled and linked as-is); pair it with EXTRA_HEADERS for the
matching extern const …[] declarations so the symbols reach Swift via the same
nds_asset_*() accessors (used by all_in_one).
ARM7_SRC := arm7 points at a directory of ARM7 C sources: instead of packaging
calico's prebuilt ARM7, common.mk compiles them with
ds7.specs + -lcalico_ds7 into a custom ARM7 binary and feeds that to
ndstool -7. Only the ARM9 is Embedded Swift; the ARM7 stays C (low-level
system code). Used by the dual-CPU pxi example.
For examples that set GRAPHICS or DATA, common.mk runs
grit on each image and bin2s on each .bin blob, compiles the generated
data, and gathers the generated headers into a single bridging header handed to
Swift — so the symbols (ballTiles, fontPal, teapot_bin, …) are visible
alongside import CNDS. grit images become tile/palette tables; blobs become
raw byte arrays for display lists, textures, bitmaps, etc.
A C global array imports into Swift as a tuple (a copy), not a pointer. Two ways to use the data:
- Copying it yourself (e.g. into sprite VRAM):
withUnsafeBytes(of: ballTiles) { … }and loop — the temporary is valid for the closure body, which is all a synchronous copy needs. - Handing a pointer to libnds (e.g.
ConsoleFont.gfxforconsoleSetFont): use the generatednds_asset_<symbol>()accessor, which returns the address of the real linked symbol. Passing awithUnsafeBytestemporary here is a bug — the pointer dangles once the closure returns and libnds renders garbage (a black screen, in the custom_font case).
The build pipeline (common.mk)
NDSoverlay module → object +.swiftmodule. The Sources/NDS files are compiled once as a standalone Embedded Swift module (-module-name NDS -parse-as-library -emit-module), producingNDS.oandNDS.swiftmodulein the example'sbuild/.- Embedded Swift
main.swift→ object. Targetsarmv5te-none-none-eabi, matching the DS's ARM946E-S, and imports theNDSmodule built in step 1. Release toolchains only ship an armv4t Embedded stdlib slice, so this needs a patched toolchain with an armv5te slice (setSWIFTCin common.mk or on the command line). Embedded clang is pointed at newlib's headers, libnds, calico, and the Sources/CNDS module map. - C shim → object with devkitARM (Sources/CNDS/shim.c).
- Link
main.swift.o+NDS.o+shim.oagainst the modern calico-based libnds (-specs=…/ds9.specs,-lnds9 -lcalico_ds9). - Package the
.ndswithndstool(calico's prebuilt ARM7 + default icon).
The C shim (Sources/CNDS/shim.c, shim.h)
Bridges the gaps between Embedded Swift and libnds:
- Variadic stdio. Swift imports C variadic functions but cannot call them,
so
nds_puts/nds_printf_1i/nds_printf_2iforward intoiprintf,nds_printf_1f/nds_printf_2fintoprintf(for%f), andnds_scanf_strintoiscanf— all formatting stays inside libnds. - Macro-only APIs. Preprocessor macros the Swift importer can't surface are
re-exported as real functions:
nds_timer_freq_1024(TIMER_FREQ_1024),nds_bus_clock(BUS_CLOCK), theSPRITE_PALETTE*/SPRITE_GFX*/BG_PALETTEpointer macros, theGFX_TEX_COORD/GFX_BUSY/GFX_POLYGON_RAM_USAGE3D registers, and the display-capture motion-blur registers (nds_motion_blur_*). - Runtime support devkitARM doesn't provide for this target:
posix_memalign(Swift's allocator wants it; newlib only hasmemalign).__atomic_*outline helpers — armv5te has no atomic instructions, so LLVM emits libcalls; implemented with a short interrupt lock (safe on the single ARM9 core).arc4random_buf— referenced by Swift's runtime; the NDS has no entropy source, so a small non-cryptographic xorshift fills the buffer.
import CNDS(Sources/CNDS/module.modulemap) exposes<nds.h>+ the shim;import NDSadds the idiomatic overlay on top (and re-exportsCNDS).- A non-capturing top-level Swift function bridges automatically to the C
function-pointer types used by
irqSet/timerStart, and can be assigned to a callback field on a struct too (e.g.kbd.pointee.OnKeyPressed = …). - libnds C enums (
VideoMode, …) import as Swift types — pass.rawValuewhere a function wants the underlyingu32(e.g.videoSetMode(MODE_0_2D.rawValue)). touchReadreturnsBool; some libnds calls return distinct enum types, so match Swift's stricter typing rather than the C "everything is an int" style.- A few constants use the function-like
BIT(n)macro (e.g.KEY_TOUCH), which the importer drops — spell the bit out in Swift (1 << 14). - 3D / GL: the float GL entry points (
glRotateX,gluPerspective,gluLookAt, …) are real inline functions and import fine — Embedded Swift's soft-float calls them directly.POLY_ALPHA(n)is also an inline function, butinttov16/floattof32are function-like macros, so they're reimplemented as tiny Swift helpers. Functions typed to take a GL enum (glMatrixMode,glBegin) accept the enum value directly; where one wants a rawint/u32(glEnable, an OR withPOLY_CULL_NONE), pass.rawValue. - In a file named
main.swift, top-level code is the entry point — no@main. swiWaitForVBlankis a macro alias; call the underlyingthreadWaitForVBlank().KEY_*are#defined as plain integers, sokeysDown() & KEY_STARTworks directly (no.rawValue).DutyCycle_50is a macro alias for the importable enumSoundDuty_50.
The ported set above covers every major interop pattern: console, timers, IRQ callbacks, PSG sound, input/keyboard, sprites/OAM, 3D/GL, grit graphics, and bin2o binary blobs (display lists, textures, all DS texture formats). What's left in the upstream example tree still needs its own toolchain or runtime work, not just more of the same translation:
- Audio (
audio/maxmod/*) — needs themmutilsoundbank pipeline. - Filesystem (
filesystem/*) — libfat + DLDI / NitroFS. (The libfat write path is already exercised by capture; the dedicatedfilesystem/*demos additionally need NitroFS / directory traversal.) - Wi-Fi (
dswifi/*) — done: all three examples (wifi_autoconnect, wifi_httpget, wifi_ap_search) are ported on the dswifi stack. - Dual-CPU — the pxi example is done (Swift ARM9 + a custom C
ARM7, via
ARM7_SRC); thecombinedtemplates would reuse the same path. - Large GL2D demos (
Graphics/gl2d/*) — big, asset-heavy (but buildable on the existing grit + bin2o + GL foundations).
The common/ scaffolding (grit + bin2o pipelines) applies to the asset-heavy
graphics ones unchanged; the others are each a distinct piece of plumbing.