Have you ever played an online PC game?
These days, cross-platform support is everywhere, and you can jump into games on pretty much any device without a second thought.
But just a few years ago, if you heard “PC game,” it almost always meant Windows only.
You’d wait through a painfully long download and installation, then finally launch the game — a ritual many of us remember all too well.
This time, I took GunZ: The Duel, a 2003 Windows-exclusive online TPS, and brought it fully into the browser using WebAssembly and WebGL.
No download. No installation.
Just open the page in Google Chrome.
A game that once took hours (or even days) of setup now starts with nothing more than a URL.
Doesn’t that sound exciting?
In this article, I’ll walk you through every technical challenge I tackled to make this GunZ browser port happen.
GunZ: The Duel — Now a Full Web Game
GunZ was developed by MAIET Entertainment in Korea and published in Japan by GameOn. Its signature style — wall-kicking, sword-and-gun combos, and insane aerial combat — made it a cult classic. I played it a ton back in the day.
In 2007 the entire source code (C++ client + server) leaked, and the community has been maintaining it ever since.
After more than 20 years, no one had ever managed to run it in a browser… until now.
Just click and play. Works on Linux, macOS, iPhone, Android — literally anything with a modern browser (though phones are… let’s say “challenging” 😂).
This isn’t a “kinda works” or “looks similar” demo.
It’s the complete original game, running exactly as it did in 2003.
In this post I’ll cover:
- How I actually pulled it off
- Why I chose to revive a 20-year-old game right now
- All the past failures that led to this moment
Past Failures and the Road to a True Browser Port
Previous Attempts (and Why They Failed)
This isn’t my first try at browser GunZ.
A few years ago I attempted to rebuild it from scratch in JavaScript + Three.js:
https://github.com/LostMyCode/three-gunz
I got map rendering working, but re-implementing the entire game engine was overwhelming. The project died quietly (the repo is still there if you’re curious).
The WebAssembly Route
GunZ is almost entirely written in C++.
We all know you can compile C++ to WebAssembly with Emscripten, so in theory it should be easy, right?
Wrong.
GunZ is extremely Windows-dependent. It calls Windows-specific APIs for rendering, audio, input — everything. The rendering engine uses Direct3D 9 directly.
Emscripten can’t magically turn Direct3D calls into something the browser understands.
So any serious port meant rewriting thousands of lines of rendering code to use WebGL instead.
No way I was doing that by hand.
That’s why the project stayed in the “theoretically possible but I’ll die before finishing” category for years.
Then AI Entered the Chat
I kept thinking: “The full C++ source code exists… there has to be a way.”
Manually rewriting everything was impossible.
But what if I let AI do the heavy lifting?
That’s exactly what I did.
I used two tools:
- Google Antigravity (AI Pro plan — ¥2,900/month)
- Claude Code (Max 5x plan — $100)
I started with Google Antigravity.
The AI handled:
- Analyzing the massive C++ codebase
- Finding every Windows API dependency
- Abstracting Direct3D calls
- Fixing the build system for Emscripten
Watching an AI understand and refactor a huge legacy codebase in hours instead of days was honestly mind-blowing. One evening I went out drinking with friends… came back and the work was done. First time I ever felt that rush.
The Google plan was amazing — until Google suddenly tightened the rate limits. A project this size would hit weekly quotas instantly.
So I switched to Claude Code (the $100 Max 5x plan).
Holy crap. Quotas barely moved, and bugs that had me stuck for a week were fixed in hours.
I’m not exaggerating when I say I can’t imagine working without it anymore.
The Magic Trick: Real-Time Direct3D → WebGL Translation Layer
The biggest blocker was the rendering engine — tens of thousands of lines all calling Direct3D 9 directly.
Rewriting every call was insane.
Building a full transpiler was also unrealistic.
So I did this instead:
“What if the game keeps calling Direct3D exactly as before, and I just translate every command to WebGL on the fly?”
That’s exactly what I built.
https://github.com/LostMyCode/d3d9-webgl
I inserted a translation layer between the game and the graphics API.
The original game code was left 100% untouched.
| Approach | What It Means | Problem |
|---|---|---|
| Manually rewrite game code | Change every D3D9 call to WebGL | Tens of thousands of changes, impossible to maintain |
| Build a transpiler | Auto-convert D3D9 → WebGL | Semantic differences make it impossible |
| Translation layer | D3D9-compatible API that outputs WebGL | Only have to implement the wrapper |
This approach let me remove the biggest Windows dependency without touching the original game.
(Again, mostly done by Antigravity and Claude — these AIs are terrifyingly good.)
I wrote a separate deep-dive on the D3D9→WebGL wrapper if you’re interested:
d3d9-webgl — Run Legacy D3D9 Code in the Browser Without Rewriting It
not a pro ・ Mar 5
Everything Else I Had to Port
Rendering was the hardest part, but it wasn’t the only one.
Network: The Server Runs Inside the Browser
This part surprises everyone.
I also compiled the original C++ server to WebAssembly and run it as a Web Worker in the same browser tab.
Instead of real network packets, the client and server talk through postMessage.
Everything runs locally in one tab.
Player data, stats, etc. are saved with SQLite + IDBFS (IndexedDB), so your progress survives tab closes — a completely serverless game server.
Of course, you can still connect to a real server via WebSocket for online play with other people.
Sound: FMOD → Web Audio API (1,260 lines)
The original game used FMOD. I replaced it entirely with the Web Audio API, re-implementing 3D spatial audio (PannerNode), BGM streaming, distance culling, and everything else the game needed.
Input: Turning Browser Events into Win32 Messages
I convert keyboard/mouse browser events into the exact Win32 messages (WM_KEYDOWN, WM_MOUSEMOVE, WM_CHAR, etc.) the original engine expects.
Pointer Lock API for mouse capture and an HTML <input> overlay for login text input were also required.
Filesystem & Asset Loading: 2-Stage + Cache API
GunZ assets are stored in custom .mrs archives.
I load the 7 critical files first, then stream the rest (weapons, maps, etc.) in the background.
Cache API ensures second visits are instant.
Game Data Optimization (This Took Forever)
Since everything has to come over the network, I spent weeks shrinking the data.
I created version after version (default → v2 → … → v10) until load times were acceptable.
The biggest win? Converting all sound effects from WAV to Opus.
44.3 MB → 5.30 MB — an 88% reduction.
| Aspect | WAV (Waveform Audio) | Opus |
|---|---|---|
| Compression | None (Linear PCM) | Lossy (extremely efficient) |
| Sound Quality | Perfect (raw) | Excellent (especially at low bitrates) |
| File Size | Huge | Tiny |
| Latency | Zero | Very low (5–26 ms) |
| Browser Compatibility | Universal | Modern browsers & mobile |
| License | Completely free | Royalty-free |
Result? You can start playing in under 10 seconds.
All assets are served from S3 + CloudFront, so they load fast from anywhere in the world.
Final Thoughts
Waiting for the right moment was key.
I knew almost nothing about WebGL or Direct3D. If I had tried this five years ago, I would have given up.
But in the AI era, it finally became possible.
Thank you, Antigravity. Thank you, Claude Code. You two are legends.
To everyone who played GunZ back in the day
I want as many old players as possible to feel this nostalgia again — no install, just click and play.
If you enjoyed this, please share it!
And yes, we have a Discord community too:
https://discord.gg/ABG8JxuBQm
20 years later, a childhood game is running inside a <canvas> tag.
The dream of bringing old games back to the web with just a click became real at exactly the right time.
If you’re a developer who geeks out over this kind of stuff, or just someone who wants to relive the glory days — I hope you give it a try.
Thanks for reading all the way to the end!
Now go click that link and kick some walls. 🚀














Top comments (0)