Buster Busts Loose! (PC port)

Buster Busts Loose! (PC port) is a Windows port of Tiny Toon Adventures: Buster Busts Loose! for SNES with widescreen, gamepad support, cheats, level select, and a music player.

README

Buster Busts Loose! — PC port

A Windows port of Tiny Toon Adventures: Buster Busts Loose! (SNES, Konami 1993), with widescreen, modern display options, gamepad support, cheats, a level select and a music player.

The game itself runs from your own copy of the original ROM. The port contains no game code or assets; it runs the ROM on a built-in SNES core (LakeSnes) extended for widescreen, then adds game-specific patches and a PC frontend.

Getting started

  1. Put BusterBustsLoose.exe and SDL2.dll in a folder (nothing else needs installing).
  2. Put the ROM next to them: Tiny Toon Adventures - Buster Busts Loose! (USA).sfc (1 MiB, CRC32 AB48D27A; headered copies are accepted too).
  3. Run BusterBustsLoose.exe.

If the ROM isn't found next to the program, a message says which ROM is needed and a file dialog asks for it; the location is remembered. You can also pass a ROM path on the command line or drag a ROM onto the exe. Any other file is rejected with a message naming what it is, read from the ROM's internal header: the European (PAL) or Japanese version, a modified USA ROM (hack or bad dump), or not a SNES game at all.

On Android (64-bit phones and tablets, Android 5.0 or later):

  1. Download the APK from the Releases page and open it on your phone. Android asks you to allow installing apps from your browser or file manager the first time.
  2. Copy your ROM to the phone.
  3. Start the app and tap Choose ROM. It's copied into the app, so you only do this once.

Touch controls appear on screen. A Bluetooth or USB controller works too, and hides them. Tap MENU or use the Back gesture for the menu.

Press Esc or Select (keyboard: Right Shift; gamepad: Back / View / Share / Minus) to open the menu. The game only uses Select as a second Start button, so the port takes it over; you can turn that off under Controls, and Select + Start then opens the menu instead.

Features

Widescreen

  • 16:10, 16:9, 21:9 or Fit window, used during gameplay (F10 toggles it).
  • The HUD is re-anchored: life/dash move to the left edge and lives/stars to the right. Panels that span the centre, like the football scoreboard, stay centred.
  • More of each level is actually drawn to the sides. The game's level streaming, its level-load pre-draw and its sprite culling are patched in memory, so scenery shows up in the extra space. Enemies, items and moving platforms are created and kept across the whole picture too, so they come in from the edges instead of appearing inside it.
  • The level's graphics and collision are checked every frame, so parts of the level the game only redraws for the old 4:3 screen never show up stale at the sides.
  • On stages that scroll to the right by themselves (the train, the asteroid belt) Buster still stops where the right edge of the 4:3 screen was: the levels are laid out for him waiting there while the train comes to him (running further ahead got him killed at 2-3's boxcar). Behind him he is only pushed off at the widescreen picture's edge. On 2-3's collapsing bridge the extra width is shown ahead of the train (nothing behind it there is laid out for being seen). On the airship Buster can use the whole picture.
  • At the start and end of a level, where the original camera stops, the picture is shifted so the level still fills the whole widescreen view (distant backgrounds shift at their own parallax rate; foreground layers the game lines things up with, like the mansion's candles and the train's hills, move with the level). One-screen rooms such as pinball are centred with even side bars in the stage's backdrop colour.
  • The game can show up to 256 sprites instead of 128, so the extra columns' enemies and scenery don't take sprite slots from the middle of the picture, and the emulated CPU gets a little more speed during widescreen play so busy scenes slow down no more than in 4:3.
  • Title screens, maps, cutscenes and the game over screen stay 4:3 (pillarboxed): they are drawn for exactly 256 pixels, so there is nothing correct to show at the sides.

Video

  • Windowed, borderless fullscreen or exclusive fullscreen (F11), monitor selection and window size. Rendering uses Direct3D 11 (falls back to other renderers if unavailable).
  • Aspect ratio: 4:3 CRT (the original 8:7 pixel shape), square pixels, or stretch.
  • Upscale filters: Scale2x, Scale3x, Scale4x, xBR 2x, xBR 4x.
  • Scaling: sharp, smooth, sharp-bilinear (crisp at any size) or integer.
  • CRT effects: scanlines (3 strengths) and an aperture-grille mask.
  • Unlimited sprites (no flicker), V-Sync, FPS counter.

Colours

  • Profiles: Original, Vivid, Extra vivid, Natural (CRT), Warm CRT, Cool, Soft.
  • Brightness, contrast, saturation and gamma sliders.

Audio

  • Volume, stereo/mono, mute while the menu is open.

Music player & sound test

  • Plays the whole soundtrack (27 tracks) through the game's own sound driver, with a voice visualiser and a play timer.
  • Sound test for all sound effects and jingles.

Level select

  • Start any area of any stage (28 areas), on Children, Normal or Challenge difficulty, optionally skipping the stage intro. Later areas are reached by starting the stage and completing the areas before them behind the loading screen, so their graphics and music load exactly as in normal play.
  • Bonus section: the between-stage "Game Time" roulette wheel and each bonus game on its own (Squash, Junction, Maze, Bingo, Seesaw).
  • Extras: the ending and the staff roll.

Cheats

  • Infinite health, infinite lives, infinite dash, invincibility, moon jump, start with 99 lives, five hearts, star bonus, debug stage select, passwords on every difficulty. Invincibility stops the three places the game registers a hit on Buster ($8F:F976, $84:FEF3, $84:D841) instead of holding his hit signal ($B8) set, which kept scripted events waiting for him to be clear of a hit from ever happening (2-1's jump rope, $83:AEF8; football's plays); it is off in football, where being tackled is part of a play. Moon jump only acts while Buster rises from a jump. With the lives and star cheats, lives stop at 99 (a 1-up at 99 showed 00).

Other

  • Keyboard and gamepad (XInput, DualShock/DualSense, Switch Pro and anything else SDL supports), fully rebindable, hot-plugging.
  • 9 save-state slots (F5 save / F9 load, or Load state / Save state on the main menu), fast forward (Tab / right trigger; can be switched off under Controls), screenshots (F12).
  • Pauses when the window loses focus (optional).
  • Version V1.0 (shown in the menu); the menu's Credits page has links to the author.

Default controls

SNESKeyboardGamepad (by position)
B (jump)Z / Spacebottom face button
Y (drop kick)A / Left Shiftleft face button
AXright face button
X (drop kick)Stop face button
L / R (dash)Q / WLB / RB
StartEnterStart / Menu
SelectRight ShiftBack / View (opens the menu, see above)
D-padArrow keys / IJKLD-pad / left stick
ShortcutKeyboardGamepad
MenuEsc, Right ShiftSelect (Back/View), Guide
Fast forwardTabRight trigger
Quick save / loadF5 / F9(bindable)
ScreenshotF12
FullscreenF11
Widescreen on/offF10

In menus: arrows / d-pad to move, Enter / Z / bottom button to confirm, Esc / X / right button to go back.

Files

Settings are stored in bbl.ini. Save states go in saves\, screenshots in screenshots\. These live next to the exe, or in %APPDATA%\BusterBustsLoosePC if that folder isn't writable. Delete bbl.ini to restore defaults.

Building from source

Requirements: Visual Studio 2022 (C compiler) and CMake 3.16+. SDL2 2.32.10 comes with the source (its headers and the x64 libraries, in third_party/SDL2-2.32.10); the full SDL2 development package for Visual C++ can be dropped in there instead.

cmake -S . -B build -G "Visual Studio 17 2022" -A x64
cmake --build build --config Release

Or run package.ps1, which builds and copies everything into dist\BusterBustsLoose.

Android

The same code builds for Android (android/), with SDL2 compiled from source. You need the Android SDK with NDK 28.2 and CMake 3.31.6 (both from the SDK manager) and JDK 17–21 for Gradle.

powershell android\fetch_sdl.ps1
cd android
gradlew assembleDebug

The APK ends up in android/app/build/outputs/apk/. On a phone:

  • The ROM is picked once with Android's file picker and copied into the app's storage.
  • Settings and saves live in app storage too.
  • The picture fills the screen (widescreen "Fit window").
  • On-screen touch controls appear (Controls > Touch controls). A Bluetooth or USB controller works too, and hides them.
  • Back opens the menu.
Source layout
PathContents
src/snes/SNES emulation core (LakeSnes, MIT), with widescreen rendering, per-line HUD remapping, playfield clipping, colour tables and debug hooks added
src/bbl.cGame wrapper: ROM check, frame runner, widescreen ROM patches, routine calls
src/hud.cWidescreen HUD re-anchoring
src/ui.c, src/uifont.hMenu drawing and its anti-aliased font
src/cheats.cCheat patches
src/music.cMusic player / sound test (drives the game's sound routines on a second instance)
src/levels.cLevel select table
src/filters.c, src/upscale.cColour profiles and upscalers
src/main.c, src/menu.c, src/finput.c, src/audio.c, src/ui.c, src/config.c, src/bgtask.cSDL2 frontend
tools/probe.cHeadless test and reverse-engineering tool (bblprobe)
tools/dis65816.py65816 disassembler used during development
tools/genfont.pyBakes the menu font into src/uifont.h
tools/makeicon.py, res/Makes the exe and window icon (res/icon.ico) from icon.png
Technical notes (USA ROM)
  • Game logic runs in the NMI handler; the idle loop at $80:80C1 only performs sound uploads ($80:9AA9). The port calls game routines by stepping the CPU to that loop and injecting a JSL.
  • Game state: $30/$32 = mode/sub-mode (3/9 = stage intro, 3/10 = playing), $68 stage, $94 area (6 = ending, 7 = staff roll), $6C difficulty, $8E lives (BCD), $9E hearts, $1D7E debug stage select. Holding Select when the debug stage select is confirmed opens the bonus section; on stage 1 that is a hidden bonus-game menu (cursor $0442).
  • The BG1 camera lives at $1900 ($1902 X, $1925 level width in screens - 1).
  • Level columns are streamed at camera +$140 / −$40 ($80:C998, $80:C9E6) and pre-drawn by a camera sweep on level load ($80:C777: +392 px, then back 8 px per step). Rows use $80:CC87 (49 columns: $80:CC8F, and $80:CCA0 for rows that start left of the level). In widescreen the column offsets follow the visible window every frame, rows cover the whole 64-column ring, and the load sweep is widened to up to 640 px. The camera cannot scroll left into void screens, so the sweep never starts more than one screen past the level's right edge.
  • BG1 and BG2 are 512 × 512 px tilemap rings; BG1's collision map ($7F:7000, read by $80:EAAB) mirrors its ring cell for cell. Every cell the column/row writers ($80:CAF4 / $80:CC87) fill is recorded, and before each frame the streamed range and the visible picture are checked: cells still holding another part of the level (left behind by events that only redraw the original 256 px, like entering the lit café, or by switching widescreen on mid-level) are redrawn straight away from the level data (screen blocks and metatiles in $7F, tile collision at $7F:CC00), graphics and collision together. Cells the game drew itself are never touched (objects change some of them: platforms, animations): every word the game writes into a ring through the VRAM port (a PPU hook) marks that cell as holding what the game wants there. Rows shown on scanlines the layer is scrolled away from its camera in the middle of the frame are left alone (2-3's collapsing bridge is such a band, see below). After a camera jump without a redraw (the train's, 1024 px up) what the game left on screen stays as it is inside its own 4:3 view and to the right of it, where it streams on; left of the 4:3 view it is redrawn from the level, as the original never shows it there. A state load learns the picture the same way, one frame after the load (the per-line scroll record is from before it until then); rows shown on lines scrolled on their own are taken as they are (1-4's boss kitchen keeps its conveyor chains in rows below the picture and shows them on such lines: learnt as unknown, they were redrawn from the level once the camera moved). The record is audited one column a frame: a cell recorded as holding its level cell but showing the level from 512 px away is stale and gets redrawn (2-5's locomotive boss showed the tender with a column of the level ahead, the picture having moved left over it; a state load could learn it wrong the same way).
  • 2-5's tender (the last car) has no lower left in the level data: the original camera only comes down to it (camera y 768) with Buster on the footplate past the cab, camera x 4522 or more, when the tender (map columns 548-571) is off screen. The wider picture showed the sky through it. The ring repair draws it in (tenderFill in bbl.c): under the number plate a plain panel framed like the plate (columns 552-563, rows 112-115), left of both the tender's right side mirrored (columns 548-551, rows 104-115). Only the tiles: the collision ring keeps the level's (empty) values. It checks the tender's tiles around it first and applies only in stage 2 area 5 ($68 = 1, $94 = 4).
  • 2-5's handcar chase: once Buster boards the railcar at camera 5120, the train controller ($8130, slot $10C0, state $24 at $83:E3C8) switches BG1 to the chase map ($81:901F, 4x2 screens) and clears the camera's high bytes (5120,768 -> 0,256). Those are the same ring cells, so the locomotive's last picture stays and the chase map streams in as the camera moves. The original's ring holds the level up to 320 px ahead of the camera there. With the extra width shown behind Buster it only reached 272 px ahead, the rest holding the level from 512 px back, and the chase (shown 4:3, scene43) streamed 376 px ahead. So 13 columns of boiler slid past in mid-air. A hook on the switch ($83:E3CE, chaseSwitch) draws the columns up to 320 px ahead from the level first, leaving out rows the game draws itself (the rails, in their own palette), and the chase streams at the original offsets (applyStreamOriginal). After the switch it's pixel-identical to 4:3.
  • BG2 pans with the view shift at its own scroll rate, fitted to its map. A BG2 moving 1:1 with BG1 is part of the level and pans with it: 6-3's giant ball is a one-screen BG2 map moved by its scroll.
  • 2-3's collapsing bridge: after the camera jump, the bottom of BG1 (from line 153) is a band scrolled by the bridge's own position ($7EC084), not the camera: a conveyor in the 512 px ring. $83:E81F lays the deck in the column 256 px ahead at every 32 px step, $83:E8B0 builds each trestle column once it is within 304 px, and the collapse clears it behind the train. In widescreen the build distance follows the picture's right edge (+16 px, no more: the ring repeats every 512 px), and the ring repair keeps out of the band (it put track back into the gap and moved the bridge's collision about). For the last stretch the game writes whole columns again (BG1's +$2E bit 15 clear: rows below line 152 included) so the mountain is in place when the bridge stops; 264 px ahead of the camera the band shows none of that on a 4:3 screen, but the widescreen column comes round the ring into the wider band (rock hanging in the air left of the train), so there such a column fills only the rows above the band (a hook where the column writer takes its row count, $80:CB05), and the build distance is the original one: the bridge stops there and all the original built falls, a longer build left 96 px of bridge standing over the gap before the rock. Enemies keep the original spawn range while the bridge collapses (an early flying lizard took the sprite graphics of the falling timbers), from the camera's cut down to it on (the band that tells the collapse is seen only a few frames after the cut, and in between the wider range spawned the bandit early). The cars are level maps too: the game moves each car from BG1 onto BG2 by jumping both cameras 256 px at once (BG1's map has no car there, BG2's has it), and BG2 scrolls down as the car falls. Only the 4:3 picture is laid out for that, so here BG1 is not drawn left of the 4:3 view (it showed the car it had just given up, floating over the gap) and BG2 beside it only as far as the car reaching out of the 4:3 view goes (its next car fell with the whole layer off to the right, and popped back up at the next jump). The PPU has a per-layer clip for this. The train there is in fact one car in BG1's map: its controller (type $8130, state at +$12, cars to go at +$3E) hands each car on when BG2's x reaches $11E, BG1's camera going back 256 px so the same car of the map stands next in line on the right ($83:DC33; after the last, forward 256 px: no car follows). The widescreen picture showed the right of the train empty until that jump, the next car appearing all at once; while one is to come (states 10 and 11, two or more to go) the ring repair shows there, above the band, what the jump will bring: the map 256 px to the left where the level has nothing, tiles and collision. The car in BG1's map is short, too: only its first 24 columns (from map x 2304, a screen's edge), as the rest is never on a 4:3 screen before the car goes over to BG2, whose map holds the whole car (32 columns from its left edge, one row lower). Right of the 4:3 screen the missing columns are drawn from BG2's where that row of the map matches BG2's, solid like their mirror image at the car's front (the train ended short of the next car, and the car's rear appeared at the hand-off, coupling them all at once). The enemy riding the car (list type $0122, at its front) is spawned when the spawn column, 304 px right of the camera, gets to it, once each time the camera passes it: 48 px into the widescreen picture's right part, and on the next car shown early only once the hand-off has taken the camera back; it is first drawn two frames later. Until the game draws it, a stand-in of its sprite pieces (their graphics are already loaded) is drawn where it will be, in the sprite table's extension (not while its slot is taken: then it would not come). On the frame a hand-off's jump is first seen, the ring repair leaves BG1 alone: the picture still shows where the camera was. While the next car comes, the controller holds BG1's camera at $0800 ($83:DC2C, while BG2's x is below $60); the auto-scroll moves it on half a pixel a frame, and every other frame the objects have been moved 1 px back with it (by the camera of the layer Buster stands on, $08BD bit 7: BG2; $80:C559) before it is set back. At 4:3 Buster stands on BG2's car then; right of the 4:3 screen he can stand on BG1's next car and was pushed backwards, so there he is moved on