DEV Community

Virtastic
Virtastic

Posted on

I've been rebuilding Diablo II 1.13c as open source C++

For the last while I've been working on D2-Web, an open source reimplementation of Diablo II: Lord of Destruction at patch 1.13c. It's C++20, MIT licensed, and the code is on GitHub: https://github.com/Virtastic/d2-web

Right now it builds to WebAssembly and runs in desktop Chrome, so you can try it at https://diablo2.virtastic.app with your own copy of the game. There's a short video of it running here:

Why I care about the source more than the browser

Diablo II has been a closed binary for 25 years. Everything the community built on top of it, and they built a lot, had to work around that: offset tables, memory patches, injected DLLs, all tied to one exact patch. I wanted a version of the game where you could open a file and see how a monster decides where to walk, or how an item picks its affixes, and then change it and send a pull request.

The browser build is just the first place it runs. The thing I'm actually excited abw exists as readable source that anyone can build, fork and keep alive.

None of Blizzard's files are in the repo or hosted on the site. You point it at yourl and it reads the files locally.

What works

It's an early alpha. All five acts are playable, with all seven classes, the original menus, music and cinematics, and saves stored in the browser. I've also added a few optional things the original
never had: a widescreen HUD, controller and touch support, life bars and a shared stlt.

Multiplayer doesn't work yet, and neither does any browser other than desktop Chrome

How I check it matches the original

I want it to be 1:1 with 1.13c: same damage rolls, same drops, same monster AI, samesy to say and hard to argue with, so I don't rely on it.

Instead I record a play session as a fixed stream of input and replay it through the real 1.13c game running headless, then through D2-Web. Every tick, I compare the network packets, unit state, the
draw list and the sound calls between the two. What comes out is a count of records s zero, that session matches. When it isn't, the first differing tick tells me where to look.

Screenshots don't count as proof in this setup, and neither does me playing it and thinking it looked fine. If a comparison doesn't give the same number twice in a row, the comparison is broken, not
the game.

That last part bit me early. I spent a long time chasing thousands of terrain differ from how I'd recorded the session, not from the engine. Once the replay made the
same calls in the same order as the recording, most of them went away. Now I check t trust any count it gives me.

A small technical choice

The original is 32-bit x86. I compile to 64-bit WebAssembly anyway, but anything the-bit value stays an explicit uint32_t or int32_t in the code: seeds, unit IDs,save layouts, offsets. That way the pointer size of the build never sneaks into game behaviour.

Prior art

None of this would be possible without twenty-odd years of work by the Diablo II modented the file formats, the data tables, map generation, item generation, the network protocol and the save format. I lean on all of it and cross-check against it. When soriginal 1.13c binary as the tiebreaker.

If you want to help

If you know 1.13c well, the most useful thing you can do is play it and tell me wher what you remember. Issues and pull requests are welcome, especially from anyone who
knows a subsystem like drops, skills or AI inside out. There's a Docker image if youther with your own game files or with players bringing theirs. The README has the
details.

Discord: https://discord.gg/PzFfDkbSue
D2-Web is a fan project and isn't affiliated with Blizzard. You need your own copy of Diablo II and Lord of Destruction to play.

I've done a few other browser ports of old games too, if that's your thing: Morrowind (OpenMW, with multiplayer), Jagged Alliance 2 (Stracciatella) and FreeCAD.

Top comments (0)