- Lua 79.5%
- JavaScript 8.2%
- HTML 8.1%
- Python 2.9%
- CSS 1.1%
- Other 0.1%
| .forgejo/workflows | ||
| .github/workflows | ||
| bizhawk | ||
| overlay | ||
| promo-site | ||
| ServerHost | ||
| state | ||
| tools | ||
| .dockerignore | ||
| .gitignore | ||
| overlay.db | ||
| POKEMON_FIELD_MATRIX.md | ||
| PokemonOverlayServer.exe | ||
| README.md | ||
| start_all_gold.bat | ||
Pokémon Stream Overlay
A local Pokémon stream-overlay system for BizHawk. The BizHawk Lua exporter reads the running game, writes normalized JSON state files, and the bundled HTTP server exposes those files together with a configurable browser overlay.
The project is designed for OBS Browser Sources and supports mainline Pokémon games across generations 1–7. Some experimental generation 8 reader files are also present, but generation 8 is not part of the primary supported target.
Main features
- Live party, trainer, location, money, language, badges, held items and diagnostic data.
- PC box export and a configurable Graveyard element.
- Pokémon details such as nickname/species, level, HP, shiny state, held item, capture ball and Pokérus where the game reader provides them.
- Overlay builder with draggable and resizable elements, z-index ordering, JSON import/export and compact shareable configuration URLs.
- Built-in layout presets for the classic dashboard, a bottom bar, a clear-center stream layout and a Nuzlocke team/Graveyard split.
- Per-element backgrounds, borders, shadows, scaling, sizing, custom CSS and visibility options.
- Configurable static or animated Pokémon sprites.
- Local API endpoints for game state, box state and health information.
- Desktop server window with buttons for opening and copying every useful link.
- Password-protected partner synchronization for Soul Link challenges.
- BizHawk settings window with automatic saving and an F3 toggle.
Download variants
Automated builds create three archives:
pokemon-stream-overlay-with-assets.zipincludesoverlay/assets.pokemon-stream-overlay-without-assets.zipexcludesoverlay/assetsfor a smaller download.pokemon-stream-overlay-server-host.zipis the standalone Docker relay and promotion-site host for server administrators.
The first two archives contain the player application with the same directory structure. The smaller player archive is intended for users who already maintain their own asset library. The ServerHost archive is a separate administrator deployment.
Player archives are intentionally minimal. They contain only PokemonOverlayServer.exe, README.md, bizhawk/, overlay/, and an empty writable state/ directory. Developer sources, build scripts, CI configuration, tools/, promo-site/, and ServerHost/ remain in the source repository and are not shipped to players.
Requirements
Normal Windows setup
- Windows 10 or newer.
- BizHawk with Lua support.
- A supported Pokémon ROM loaded in BizHawk.
PokemonOverlayServer.exefrom the release archive.- OBS or another browser-source capable application for final display.
Python is not required when using PokemonOverlayServer.exe.
Development setup
- Python 3.10 or newer to run
server.pydirectly. - Python with PyInstaller when rebuilding the server executable through
build_exe.bat.
Directory structure
pokemon-stream-overlay/
├─ PokemonOverlayServer.exe Compiled local HTTP server
├─ tools/server.py Python source for the HTTP server
├─ tools/relay_server.py Deployable central challenge relay
├─ tools/build_exe.bat Rebuilds the Windows server executable
├─ promo-site/ Static public promotion website
├─ ServerHost/ Docker host for the relay and promotion site
├─ bizhawk/
│ ├─ StreamOverlayExport.lua Main script loaded in BizHawk
│ ├─ core/ Memory, JSON, settings and shared helpers
│ ├─ games/ Generation- and game-specific readers/data
│ └─ tools/ Data-generation utilities
├─ overlay/
│ ├─ index.html Overlay builder
│ ├─ overlay.html Final browser overlay
│ ├─ debug.html Help, live information, and debug tools
│ ├─ assets/ Optional local sprites, badges and icons
│ └─ *.js / *.css Builder and renderer implementation
├─ state/
│ ├─ game_state.json Generated live state
│ ├─ box_state.json Generated box state
│ ├─ opponent_state.json Generated live opponent state
│ └─ encounter_state.json Persistent first encounters and catches
└─ tools/build-release.sh Creates both release ZIP variants
The JSON files in state/ and bizhawk/overlay_settings.json are generated locally and are intentionally excluded from release packages.
Quick start
- Extract the release archive to a normal writable folder.
- Start
PokemonOverlayServer.exe. The desktop server window shows all local links and connection status. - Open BizHawk and load a supported Pokémon game.
- Open Tools → Lua Console in BizHawk.
- Load
bizhawk/StreamOverlayExport.lua. - Open the builder at:
http://127.0.0.1:8765/overlay/
- Arrange and configure the overlay.
- Use the builder's generated final-overlay URL as an OBS Browser Source.
The default final overlay is available at:
http://127.0.0.1:8765/overlay/overlay.html
Server endpoints
| URL | Purpose |
|---|---|
http://127.0.0.1:8765/overlay/ |
Overlay builder |
http://127.0.0.1:8765/overlay/overlay.html |
Final overlay |
http://127.0.0.1:8765/debug/ |
User information hub, live data helper, and debug tools |
http://127.0.0.1:8765/api/state |
Current game state JSON |
http://127.0.0.1:8765/api/boxes |
Current PC-box JSON |
http://127.0.0.1:8765/api/encounters |
Persistent first encounters and caught status per ROM/location |
http://127.0.0.1:8765/api/partner |
Latest authenticated snapshot received from a configured partner |
http://127.0.0.1:8765/api/partner/state |
Partner game state in the same format as /api/state |
The information hub keeps everyday tools separate from diagnostics. Live data contains compact Pokémon cards, the searchable Nuzlocke route tracker, and a Partner tab that appears when a relay snapshot is available. Technical endpoint controls, the data tree, and raw JSON remain under Debug. Use Pause updates to temporarily stop every automatic refresh; otherwise each view is only rebuilt when its own data actually changes, so expanded details and filter selections stay in place.
Relay challenge connection
Partner synchronization uses a central relay instead of direct connections:
Player 1 <-> relay server <-> Player 2
The players do not open their local overlay ports to each other or to the internet. Both desktop apps publish their current snapshot to the relay, which validates the challenge password and returns the other player's snapshot.
Each player creates or selects a preset in Relay challenge connection and enters:
- The same relay server URL, for example
https://relay.example.com. - The same challenge name.
- A different player name for each of the two players.
- The same shared challenge password.
- Select Save preset and connect.
Use New to save configurations for additional challenges and partners. The relay limits a challenge to two player names; inactive slots expire after 24 hours. The first player to use a new challenge name creates it and establishes its password.
The connection status updates in the window. Local tools can read the complete received bundle from /api/partner. In the builder, enter only the server base URL (for example http://127.0.0.1:8765/); the overlay derives the local state, boxes, opponents, and partner endpoints automatically.
Presets and their passwords are stored locally in state/server_config.json; the latest received partner snapshot is cached in state/partner_state.json.
Running the central relay
The recommended deployment runs the relay and promotion site together in Docker:
docker compose -f ServerHost/docker-compose.yml up -d --build
By default, the promotion site is available at http://server:8080/, relay clients use http://server:8080, and health checks use http://server:8080/health. See ServerHost/README.md for production and persistence details.
The relay also has no third-party Python dependencies and can run directly on a VPS or another always-reachable computer:
python tools/relay_server.py --host 0.0.0.0 --port 8770 --database relay_state.db
Its health endpoint is http://server:8770/health. For internet use, place it behind an HTTPS reverse proxy and enter that public HTTPS base URL in both desktop apps. Plain HTTP exposes the challenge password and snapshots while they are in transit.
The relay stores challenge passwords as PBKDF2 hashes in SQLite; it never stores the original password. Challenge snapshots and player names are stored in the configured database.
Automatic route encounter tracking
The local server watches the current location, opponent state, party, and PC boxes. The first detected wild encounter for each location is persisted in state/encounter_state.json. If a newly owned Pokémon with the same species subsequently appears in the party or a scanned box, that encounter is marked as caught. Keep box scanning enabled when the party is full so captures sent directly to the PC can be recognized.
The information hub's Routes tab can also add routes manually when tracking starts after a run is already underway. Every saved route can be edited, renamed, moved to another ROM, or deleted. Pokémon are selected by name in the UI, while state/encounter_state.json continues to store the stable numeric species ID.
Duplicate clause: a Pokémon already known from the current party/PC or marked as caught earlier in the same ROM/run does not count as the first encounter on another route. The complete evolution family is treated as known, so (for example) Geodude, Graveler, and Golem exclude one another. That route remains open until an unknown evolution family is encountered. A missed species does not trigger this exclusion by itself.
The file is written as compact JSON without whitespace and only stores the fields needed by the overlay:
{"BPRD":{"locations":{"Route 45":{"mapIdentifier":"5:8","firstEncounter":207,"level":24,"caught":true}}}}
Readers that expose a dedicated wild-battle flag are tracked exactly. For readers where a wild battle and a single-Pokémon trainer battle cannot yet be distinguished, a single opponent is treated as a possible wild encounter.
The file survives emulator and server restarts. Reset it deliberately with:
Invoke-RestMethod -Method Post http://127.0.0.1:8765/api/encounters/reset
or:
curl -X POST http://127.0.0.1:8765/api/encounters/reset
Manual edits to state/encounter_state.json are detected by the running server automatically, normally within 200 ms. Valid changes become authoritative immediately and are rewritten in a compact format with one route per line; restarting the Lua script or server is not required.
The overlay builder includes an optional Fangstatus component. It shows open.webp when the current route has no encounter entry, caught.webp for a caught first encounter, and missed.webp for an uncaught first encounter.
| http://127.0.0.1:8765/health | Server and path diagnostics |
The server listens on port 8765. Only one instance can use that port at a time.
Additional starts now fail immediately instead of sharing the port and serving conflicting cached state.
BizHawk settings
The exporter creates:
bizhawk/overlay_settings.json
The settings window opens automatically only when no settings file exists. Press F3 to open or close it later.
Available data groups include:
- Team / party
- Trainer data
- Location data
- Badge data
- Money
- Box scanning
- Debug data
Changes apply immediately and are saved automatically. Closing the settings window using its X button does not overwrite enabled options with false values.
Overlay builder
The builder stores its working configuration in the browser and supports JSON download/upload. A generated URL embeds a compact configuration in the cfg query parameter.
The Server URL field expects only the server root, for example http://127.0.0.1:8765/. Older configurations that contain /api/state are normalized automatically.
Important notes:
- Generate a new URL after changing the layout.
- Do not manually truncate the generated URL.
- OBS must use the complete URL, including the entire
?cfg=...value. - Use the explicit refresh button when manually testing live data; the builder does not need a timer that interrupts dragging.
- Each component has independent font family, custom font URL, font size, fill color, outline, and text-alignment controls.
- Partner components read
/api/partner/statealongside the local player state. They provide separate game, player, location, money, language, badge-count, team, badge, held-item, status, and encounter-status elements. - The component list groups local sources under Spieler (
Cam 1,Game 1/2,Chat 1) and remote sources under Partner (Cam 2,Game 3/4,Chat 2).
Team and Graveyard cards
Both card types can display supported values including:
- Pokémon sprite or animation
- Nickname/species
- Level and HP
- Held-item icon
- Shiny marker
- Capture-ball icon in the bottom-left
- Pokérus marker in the top-left
Held-item and capture-ball icons each support configurable background enablement, color and opacity.
Graveyard boxes
The Graveyard element reads /api/boxes. Configure one or more box names in the builder. Its grid supports row-first or column-first fill order.
Assets
Place optional local files below overlay/assets. The exact subdirectories depend on the asset type referenced by the renderer, for example:
overlay/assets/
├─ badges/
├─ balls/
├─ items/
└─ pokemon/
The project can use fallback asset URLs in supported cases, but local assets are recommended for predictable streaming and offline use.
The release workflow does not generate or download copyrighted game artwork. It only packages assets already present in the repository.
Running from Python
For development, run:
python tools/server.py
Then open the same URLs listed above. Stop the server with Ctrl+C.
To run without the desktop window during development or automation, use:
python tools/server.py --headless
Rebuilding the server executable
Run:
tools\build_exe.bat
The batch file uses PyInstaller's windowed mode to create PokemonOverlayServer.exe without a console window. Rebuild the executable whenever server-side Python behavior changes.
Troubleshooting
Builder opens but shows default values
The generated URL was likely copied incompletely. Generate it again and make sure the full cfg query parameter is retained.
Overlay cannot load state
Check:
http://127.0.0.1:8765/health
Confirm that state/game_state.json, state/box_state.json and the overlay directory are detected.
No game data appears
- Confirm a supported ROM is running.
- Confirm
StreamOverlayExport.luais active in BizHawk's Lua Console. - Check the Lua Console for memory-domain or game-detection errors.
- Open the debug page and inspect the generated JSON.
Could not write overlay state
The exporter automatically retries short-lived file locks and retries the same state during the next update cycle if all immediate attempts fail. Persistent messages now include the operating-system error. If they continue, check that the project folder is writable and that antivirus, synchronization, or backup software is not holding files in state/.
Port 8765 is already in use
Close the other server process or change the port consistently in server.py, the executable build and the overlay URLs.
Box scanning is slow
Box scanning is generation-specific. The exporter caches unchanged box data and uses optimized active-box checks where available. Disable box scanning in the F3 settings window when it is not needed.
Creating release archives locally
On Windows or any system with Python 3:
python tools/build_release.py
On Linux and macOS, the existing shell wrapper calls the same builder:
bash tools/build-release.sh
The files are written to dist/:
dist/pokemon-stream-overlay-with-assets.zip
dist/pokemon-stream-overlay-without-assets.zip
dist/pokemon-stream-overlay-server-host.zip
Automated releases
GitHub Actions
The workflow at .github/workflows/build-release.yml runs manually, on pushes to main, and for version tags such as v1.0.0. Both ZIP files are attached as workflow artifacts. On version tags, they are also attached to the GitHub Release.
Forgejo Actions
The workflow at .forgejo/workflows/build-release.yml runs manually, on pushes to main, and for version tags. It uploads all three ZIP files as downloadable workflow artifacts. Artifact availability and retention depend on the Forgejo instance configuration.
The separate .forgejo/workflows/publish-container.yml workflow publishes the ServerHost OCI image as git.akurosia.de/akurosia/pokeoverlay/server-host. Non-versioned builds from main publish beta, latest, and a commit tag. Version tags publish latest, the exact version tag, and a commit tag. It uses a daemonless Kaniko build and therefore does not require runner Docker-socket access. Add a repository Actions secret named PACKAGE_REGISTRY_TOKEN containing a personal access token owned by akurosia and scoped to write:package. Do not use Forgejo's automatic FORGEJO_TOKEN, because that token cannot publish packages. PACKAGE_REGISTRY_USER is optional when the token belongs to the repository owner. Forgejo reserves the FORGEJO_, GITHUB_, and GITEA_ prefixes for built-in values, so custom secrets cannot use those prefixes.
Development notes
- Keep memory reads bounded to the selected BizHawk domain.
- Avoid full-memory or full-box scans on every update.
- Preserve generated JSON compatibility when extending readers.
- Keep overlay rendering incremental so unchanged elements do not flicker.
- Do not commit local state files or personal overlay settings.