A cross-platform Intel 8080 Space Invaders arcade emulator built with .NET 9 and Blazor WebAssembly.
- Accurate Intel 8080 CPU emulation - Full implementation of all 8080 opcodes
- Authentic display rendering - Color zones matching original arcade cabinet (green, red, white)
- Browser-based - Runs entirely in the browser using WebAssembly
- Authentic audio - Sound effects via Web Audio API with correct UFO looping behaviour
- Cross-platform - Runs on any modern browser (Chrome, Firefox, Safari, Edge)
- Mobile support - On-screen touch controls for phones and tablets
- Pause - Press P to pause and resume at any time
- Persistent high score - High score is saved in browser
localStorageand restored on next visit
dotnet build
dotnet runThen open your browser to https://localhost:5443 or http://localhost:5000.
dotnet publish -c ReleaseThe release build has WebAssembly AOT compilation enabled (<RunAOTCompilation>true). This compiles .NET IL to native WASM instructions at publish time, giving lower CPU usage and more consistent frame timing at the cost of a longer publish step and a larger initial download. The extra size is served Brotli-compressed by most static hosts, which reduces the impact significantly.
Note: AOT compilation requires the
wasm-tools.NET workload. If the publish step fails with a missing workload error, install it once with:sudo dotnet workload install wasm-tools
The published site requires a PIN to be entered before the game loads. This is intended to deter casual unauthorised access; it is not a cryptographic security boundary.
How it works
- The PIN is hashed with SHA-256 at the time you enter it and the result is compared to a hash constant stored in
Pages/Index.razor. The plain-text PIN is never stored anywhere in the repository. - Failed attempts are rate-limited to one try every 10 seconds.
- Input is sanitised before hashing (control characters stripped, length capped).
PIN gate is automatic per build configuration
| Configuration | PIN shown? |
|---|---|
dotnet run / dotnet watch run (Debug) |
No |
dotnet publish -c Release |
Yes |
No manual changes are needed for local development.
Disabling the PIN permanently
If you have cloned the repository and want to run the published build without a PIN, open Pages/Index.razor and locate the following constants near the top of the @code block:
#if DEBUG
private const bool PinEnabled = false;
#else
private const bool PinEnabled = true;
#endifChange the true in the #else branch to false, then republish.
Changing the PIN
- Compute the SHA-256 hash of your chosen PIN (UTF-8 encoded, no trailing newline), for example:
python3 -c "import hashlib; print(hashlib.sha256('YourNewPin'.encode()).hexdigest())" - In
Pages/Index.razor, replace the value of thePinHashconstant with the new hash string. - Rebuild / republish.
| Key | Action |
|---|---|
| C | Insert Coin |
| 1 | 1 Player Start |
| 2 | 2 Player Start |
| ← / → | Move |
| Space | Fire |
| P | Pause / Resume |
On mobile devices a set of on-screen touch buttons is displayed below the game screen, replacing the keyboard hints shown on desktop. The buttons are arranged in two rows that match the width of the game canvas:
| Row | Buttons |
|---|---|
| Top | 1P (1 player start) · COIN (insert coin) · 2P (2 player start) |
| Bottom | ◀ (move left) · FIRE · ▶ (move right) |
Mobile detection is automatic — touch controls appear only on phones and tablets, while desktop users continue to use the keyboard as normal.
Place the following ROM files in the wwwroot/roms/ directory:
| File | Address Range | MD5 Checksum |
|---|---|---|
invaders.h |
0x0000 - 0x07FF | E87815985F5208BFA25D567C3FB52418 |
invaders.g |
0x0800 - 0x0FFF | 9EC2DC89315A0D50C5E166F664F64A48 |
invaders.f |
0x1000 - 0x17FF | 7709A2576ADB6FEDCDFE175759E5C17A |
invaders.e |
0x1800 - 0x1FFF | 7D3B201F3E84AF3B4FCB8CE8619EC9C6 |
Place WAV sound files in the wwwroot/sounds/ directory:
ufo_lowpitch.wavshoot.wavexplosion.wavinvaderkilled.wavfastinvader1.wavfastinvader2.wavfastinvader3.wavfastinvader4.wavextendedPlay.wav
SpaceInvaders/
├── Program.cs # Blazor WASM entry point
├── App.razor # Root Blazor component
├── _Imports.razor # Global Razor imports
├── SpaceInvadersEmulator.cs # Emulator wrapper with Canvas rendering
├── Pages/
│ └── Index.razor # Main game page
├── MAINBOARD/
│ ├── Intel8080.cs # CPU emulation core
│ ├── Memory.cs # 64KB addressable memory
│ ├── Registers.cs # CPU registers
│ └── Flags.cs # Status flags (Z, S, P, CY, AC)
└── wwwroot/
├── index.html # HTML host page
├── css/app.css # Styles
├── js/game.js # Canvas and audio interop
├── roms/ # ROM files
└── sounds/ # Sound effect WAV files
- CPU Clock: 2 MHz emulated
- Display: 224x256 (rotated 90° from original 256x224)
- Refresh Rate: 60 Hz
- Color Overlay: Simulates original arcade color gel overlay
- Green: Player and shields area
- Red: UFO area at top
- White: Middle play area
The original cabinet mounted the CRT horizontally, reflecting the image through a half-silvered mirror angled at 45° over a backlit artwork panel. Game pixels appeared to float over the background scene with natural transparency. Several effects recreate this:
- Background image — cabinet artwork is composited beneath the game canvas.
- Half-mirror transparency — CSS screen blending causes unlit pixels to be fully transparent, letting the background show through, while lit pixels dominate — faithfully replicating the mirror effect.
- Background dimming — the artwork is darkened in CSS to simulate the light attenuation of the half-silvered glass.
- Vertical scanlines — a GPU-composited CSS overlay mimics the phosphor column structure of the CRT with no runtime CPU cost.
- Colour zones — the original cabinet used coloured cellophane strips over the screen. Pixel colours are rendered with a subtle blue bias to replicate the characteristic tint of CRT phosphors rather than pure digital colour.
Game logic and rendering run on separate timers to avoid tearing and correctly handle high-refresh-rate displays:
- Game logic — driven by a C#
PeriodicTimerat 60 Hz. Each tick runs one full frame of 8080 CPU cycles, collects sound triggers, and writes the pixel buffer to a JSImageDataobject viaupdateFrame(). - Rendering — driven by a JS
requestAnimationFrameloop. Each VSync it flushes the latestImageDatato the canvas viaputImageData(). This keeps drawing aligned with the display's paint cycle regardless of whether the display runs at 60, 120, or 144 Hz.
All sounds use the Web Audio API (AudioContext + AudioBuffer). Sounds are decoded once at load time and played by creating a new AudioBufferSourceNode per trigger — giving low latency and correct overlap without DOM node leaks. The UFO hum (ufo_lowpitch) is treated as a sustained level signal: it loops continuously while port 3 bit 0 is high and stops when the bit falls, matching the original hardware behaviour.
The high score is persisted in browser localStorage under the key spaceInvadersHighScore. On startup the stored value is decoded and written directly into the ROM's BCD RAM (0x20F4–0x20F5) so the game displays it natively from the first frame. It is saved once per game, triggered by detecting the ROM's gameMode flag (0x20EF) transitioning from 1 to 0 — the ROM's own authoritative end-of-game signal.
This project is licensed under the MIT License.
Space Invaders is © Taito Corporation. This emulator is for educational purposes only.
- Original Space Invaders by Tomohiro Nishikado (Taito, 1978)
- Intel 8080 documentation and reference materials
- Blazor WebAssembly