OpenSNES
Modern Open-Source SNES Development SDK
Loading...
Searching...
No Matches
Troubleshooting OpenSNES

Common issues and their solutions.

Build Issues

"OPENSNES not defined" or "No rule to make target 'common.mk'"

Cause: The Makefile can't find the OpenSNES installation.

Fix: Set OPENSNES in your Makefile or set the environment variable:

# Option 1: Set in Makefile (recommended for project portability)
OPENSNES := /path/to/opensnes
# Option 2: Set environment variable
export OPENSNES_HOME=/path/to/opensnes

Empty compiler/ directories or "cc65816 not found"

Cause: Submodules weren't cloned.

Fix:

cd /path/to/opensnes
git submodule update --init --recursive
make compiler

"wla-65816: command not found"

Cause: Compiler toolchain not built.

Fix:

cd /path/to/opensnes
make compiler

"clang: command not found" or "cc: command not found"

Cause: Missing host C compiler.

Fix (by platform):

# macOS
xcode-select --install
# Ubuntu/Debian
sudo apt install clang
# Fedora
sudo dnf install clang
# Windows MSYS2 (UCRT64)
pacman -S mingw-w64-ucrt-x86_64-clang

Build fails with "unhandled op: XXX"

Cause: The QBE backend doesn't support a specific operation (usually 32-bit or complex expressions).

Fix: Simplify your code:

// BAD: 32-bit operations are slow and may not work
u32 bigValue = x * 1000;
// GOOD: Use 16-bit when possible
u16 value = x * 10;
// BAD: Complex expressions in one line
result = (a * b + c) / d;
// GOOD: Break it down
u16 temp = a * b;
temp += c;
result = temp / d;
static u16 b
Definition main.c:157
static u16 a
Definition main.c:157
static u16 c
Definition main.c:157
unsigned int u32
32-bit unsigned integer (0 to 4294967295)
Definition types.h:90
unsigned short u16
16-bit unsigned integer (0 to 65535)
Definition types.h:53

Static variables with initializers

Initialized static variables now work correctly:

// Both work correctly:
static u8 counter = 0; // Initial value copied from ROM to RAM at startup
static u8 counter; // Zero-initialized by C standard
unsigned char u8
8-bit unsigned integer (0 to 255)
Definition types.h:47

The CopyInitData routine in crt0.asm copies initial values from ROM to RAM before main() is called.

Runtime Issues (Black Screen / Crash)

ROM loads but screen is black

This is the most common issue. Causes and fixes:

1. Missing setScreenOn()

// The screen is OFF by default. You must turn it on:
void setScreenOn(void)
Enable screen display.
Definition console.h:115

2. No VBlank loop

// Without this, the CPU runs too fast and corrupts VRAM
while (1) {
WaitForVBlank(); // Required!
// Game logic here
}
void WaitForVBlank(void)
Wait for next VBlank period.

3. Console not initialized

// Always call consoleInit() first if using text/console functions
void consoleInit(void)
Initialize SNES hardware.

4. Memory overlap (WRAM mirror bug)

Bank $00 addresses $0000-$1FFF mirror Bank $7E addresses $7E:0000-$1FFF. If you have variables in both ranges, they'll overwrite each other.

Diagnose:

python3 devtools/symmap/symmap.py --check-overlap game.sym

Fix: The library uses ORGA $0300 to avoid the overlap zone.

ROM runs but crashes after a few seconds

Causes:

  1. Stack overflow - Too many function calls or large local arrays
  2. DMA during active display - Only do DMA in VBlank
  3. Uninitialized pointers - C doesn't zero local variables

Debug:

  1. Use luna to see where it crashes (run_until_pc / state over MCP — see the debugging tutorial)
  2. Check the call stack depth
  3. Look for NULL pointer dereferences

Sprites don't appear

Checklist:

// 1. Initialize OAM
// 2. Load sprite graphics to VRAM
oamInitGfxSet(sprite_gfx, sprite_gfx_end - sprite_gfx,
0, 0x0000, OBJ_SIZE8_L16);
// 3. Set sprite properties — oamSet(id, x, y, tile, palette, priority, flags)
oamSet(0, x, y, 0, 0, 3, 0);
// 4. Visibility is the Y position itself: a valid on-screen Y (set by
// oamSet above) shows the sprite; oamHide() parks it at Y=240.
// 5. Update OAM (usually done in VBlank by the library)
// The NMI handler calls oamUpdate() automatically
static const u8 sprite_pal[]
Sprite palette — single palette shared by player and target.
Definition main.c:124
void oamInit(u16 size, u16 name_base)
Initialize the sprite (OAM) system.
#define OAM_DEFAULT_SIZE
Default sprite size mode — small=8×8 / large=16×16.
Definition sprite.h:261
#define OAM_DEFAULT_TILE_BASE
Default sprite tile base — 0 = tiles at VRAM word $0000.
Definition sprite.h:263
void oamSet(u16 id, u16 x, u16 y, u16 tile, u16 palette, u16 priority, u16 flags)
Set sprite properties.
#define OBJ_SIZE8_L16
Sprite size indices (for oamInit, oamInitGfxSet).
Definition sprite.h:49
void oamInitGfxSet(const u8 *tileSource, u16 tileSize, const u8 *tilePalette, u16 paletteSize, u8 paletteEntry, u16 vramAddr, u8 oamSize)
Initialize sprite graphics and palette (PVSnesLib compatible).
u8 sprite_pal_end[]
Definition main.c:47

Controller input not working

Checklist:

// Input is read automatically by the NMI handler every VBlank.
// Just call the query functions after WaitForVBlank:
u16 pressed = padPressed(0); // Just pressed this frame
u16 held = padHeld(0); // Currently held
u16 released = padReleased(0); // Just released this frame
// Check correct button bits
if (pressed & KEY_A) { } // A button (bit 7)
if (pressed & KEY_B) { } // B button (bit 15)
#define KEY_A
Definition input.h:81
#define KEY_B
Definition input.h:71
u16 padReleased(u8 pad)
Get buttons released this frame.
u16 padHeld(u8 pad)
Get buttons currently held.
u16 padPressed(u8 pad)
Get buttons pressed this frame.

Audio not playing

For basic audio:

audioLoadSample(0, sample_brr, sample_brr_end - sample_brr, 0); // id, BRR data, size, loop point
audioPlaySample(0); // play sample id 0
void audioInit(void)
Initialize the audio system.
u8 audioPlaySample(u8 sampleId)
Play a sample with default settings.
u8 audioLoadSample(u8 id, const u8 *brrData, u16 size, u16 loopPoint)
Load a BRR sample into SPC700 RAM.

For SNESMOD:

snesmodLoadModule(MOD_MUSIC);
// MUST call every frame:
while (1) {
snesmodProcess(); // Required!
}
void snesmodSetSoundbank(u8 bank)
Set the soundbank bank number.
void snesmodInit(void)
Initialize SNESMOD audio engine.
void snesmodPlay(u8 startPosition)
Start module playback.
void snesmodLoadModule(u16 moduleId)
Load a module from the soundbank.
void snesmodProcess(void)
Process audio commands.

Graphics Issues

Wrong colors / garbled palette

Cause: BGR555 format mismatch.

SNES uses BGR555 (5 bits blue, 5 green, 5 red), not RGB. Use gfx4snes to convert PNGs correctly:

gfx4snes -i sprite.png -o sprite

Tiles appear corrupted

Causes:

  1. Wrong tile format (2bpp vs 4bpp vs 8bpp)
  2. VRAM address overlap
  3. DMA transfer size mismatch

Check your graphics mode:

// Mode 1: BG1/BG2 are 4bpp, BG3 is 2bpp
// Make sure you're loading the right format
bgInitTileSet(0, tiles_4bpp, ...); // 4bpp for BG0 in Mode 1
void bgInitTileSet(u8 bgNumber, const u8 *tileSource, const u8 *tilePalette, u8 paletteEntry, u16 tileSize, u16 paletteSize, u16 colorMode, u16 vramAddr)
Initialize tileset with tiles and palette.
#define BG_MODE1
Definition video.h:35
void setMode(u8 mode, u8 flags)
Set background mode.

Scrolling is jerky

Cause: Updating scroll registers outside VBlank.

Fix:

while (1) {
// Update scroll AFTER VBlank starts
bgSetScroll(0, scrollX, scrollY);
// Game logic
}
void bgSetScroll(u8 bg, u16 x, u16 y)
Set background scroll position.

Memory Issues

"ROM too large" or linker errors about size

Cause: Too much data in your ROM.

Check ROM size:

ls -la game.sfc

Solutions:

  1. Compress graphics
  2. Use smaller audio samples
  3. Split into multiple banks (advanced)

Variables have wrong values

Check symbol placement:

grep "my_variable" game.sym
  • Good (RAM): 00:0234 my_variable
  • Bad (ROM): 00:8234 my_variable

If it's in ROM ($8000+), you can't write to it.

Getting More Help

Diagnostic Commands

# Check for memory overlaps
python3 devtools/symmap/symmap.py --check-overlap game.sym
# View symbol map
cat game.sym | grep "variable_name"
# Inspect ROM header
xxd -s 0x7FC0 -l 32 game.sfc
# Check ROM size
ls -la game.sfc

Interactive debugging with luna

See the debugging tutorial for the full workflows (who-writes-this-variable, break-at-function, OAM shadow-vs-hardware, poke and observe). A third-party GUI debugger also works:

Using Mesen Debugger (alternative)

  1. Load ROM in Mesen
  2. Tools → Debugger
  3. Set breakpoint at your code
  4. Step through and watch registers/memory

Reporting Bugs

Include in your issue:

  1. OpenSNES version (git rev-parse HEAD)
  2. OS and compiler version
  3. Minimal code that reproduces the issue
  4. Error messages (full output)
  5. What you expected vs what happened