Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

349 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

libd2

Discord

A clean-room reimplementation of the Diablo II 1.14d engine core in Zig (why Zig), reverse-engineered from the retail binary with no Blizzard code.

It began with the seed-driven map generator: give it a seed and it produces the same world the game does. It now covers the surrounding systems too, including item generation, the rules the server applies, the D2GS wire protocol, save files, and routing over a generated world. Every package stands on its own.

If libd2 is useful to you, you can sponsor the work.

Using it

language install guide
Rust cargo add libd2 guide
.NET dotnet add package LibD2 guide
Node, Bun, Deno, browser npm install libd2 guide
Go go get github.com/libd2/go guide
Zig add the packages as source modules guide
C header + native lib from a Release guide
C++ header + native lib from a Release guide

Anything else with a C FFI works as well. Packages with a C ABI export it as an export fn surface compiled to native shared and static libs with a plain header, plus a freestanding WebAssembly build, so the same artifacts serve every language above. There is no binding layer on either side: each package calls the same exported symbols. Two packages ship that today, drlg and item.

Building from source: docs/BUILDING.md. Why the project is written in Zig, and why that is what makes the packages above possible: docs/WHY-ZIG.md.

Try it without installing anything

A drlg-server instance is live, so you can generate a real act with one request:

curl --compressed "https://libd2.typeguru.nl/api/render?seed=1337&acts=1&difficulty=0"

acts is 1..5 and difficulty is 0/1/2. That returns every level of the act: rooms, preset objects and monsters, level adjacency, and the collision grid:

{ "seed": 1337, "levels": [
    { "levelNo": 1, "name": "RogueCamp", "displayName": "Rogue Encampment", "act": 1,
      "origin": [5520, 5400], "size": [280, 200],
      "rooms": [ ... ], "presets": [ ... ], "adjacents": [ ... ],
      "collisionWidth": 280, "collisionHeight": 200 }
  ] }

Act I at seed 1337 is 39 levels and about 230 KB gzipped, generated on the fly in well under a second. GET /health returns ok.

Quick start: TypeScript

npm install libd2
import { drlg } from 'libd2';

// Cold Plains (level 3) for seed 1337. The wasm loads lazily on first call, so no setup.
const map = await drlg.render(1337, 0, 0);
const coldPlains = map.levels.find(l => l.levelNo === 3)!;

// Shrines are already among the level's presets, so this is a filter, not a second pass.
const s = drlg.levelShrines(coldPlains);
console.log(`${s.length} shrines/wells:`);
for (const sh of s)
  console.log(`  ${sh.isWell ? 'well ' : 'shrine'} class ${sh.classId} at tile (${sh.tileX}, ${sh.tileY})`);

// 5 shrines/wells:
//   shrine class 2 at tile (995, 1124)
//   shrine class 84 at tile (994, 1114)
//   shrine class 81 at tile (1050, 1098)
//   well  class 130 at tile (1010, 1091)
//   shrine class 83 at tile (1002, 1090)

One package for the whole library: each subsystem is a namespace carrying its own wasm, and also a subpath export (import * as drlg from 'libd2/drlg') so a bundler can take one instead of all. Tiny typed shim, ESM, no native addon and no build step. The same package runs in Node, Bun, Deno and the browser: the wasm is freestanding and libc-free, so its import object is empty and the shim reads it with fetch or the filesystem depending on where it finds itself.

Where to get the artifacts:

  • .NET: LibD2 on NuGet. One package for the whole library, carrying a native build for every platform .NET runs on, so there is nothing to place by hand.
  • Native libs + headers: attached to the package's GitHub Release (<pkg>-vX.Y.Z), one archive per target: linux / macos / windows × x64 / arm64.
  • WebAssembly: published to npm as libd2, one package carrying every subsystem's wasm behind a tiny typed TypeScript shim. ESM; require() works from CommonJS on Node 22.12+, which is the floor the package declares. The older per-subsystem @jaenster/d2<pkg> packages are deprecated in favour of it.

Does it match the game?

Map generation is compared cell for cell against dumps captured from the retail engine: 11.1M subtiles per seed, every level of all five acts, zero differing cells. Two of the seeds are blind holdouts, captured up front and never looked at while developing. Those per-cell captures are all Nightmare; Normal and Hell are checked per level rather than per cell.

Other packages rest on weaker evidence than that, and it is worth knowing which before you depend on one: docs/VERIFICATION.md.

Packages in this repo

Everything under packages/ is a library, consumable on its own. Runnable programs built on top of them live under apps/.

package module depends on what it is
data d2-data The 1.14d Blizzard excel tables, @embedFiled, so no filesystem is needed and it cross-compiles to wasm.
core d2-core data Shared foundation: seed-RNG, stats, the Unit base type, the item bit-decoder, the Fog pool allocator.
formats d2-formats Parsers for D2 on-disk data: ds1, dt1, dc6/dcc/cof, the .d2s header. Bytes in, records out.
save d2-save core, data, item, formats The .d2s character save, read and write. Byte-exact round trip over real saves.
drlg d2-drlg formats, core, data The map generator. A seed in, and every level of all five acts out: rooms, tiles, collision, roads, objects and monsters.
render d2-render drlg, formats Turns generation output into visuals: automap cells and real DT1 tile art.
item d2-item core, data Seed-driven item drops: treasure class, item class, quality, and magic/rare affixes.
pathfinding d2-pathfinding drlg, core, data Routing over a generated world, walking or teleporting, across as many levels as it takes, using the movement gates the server enforces.
game d2-game core, data, drlg, net The rules engine: units, stats, combat, skills, monsters, missiles, objects, character state.
net d2-net The D2GS wire protocol, both directions, including the variable and bit-packed packets.
util d2-util Cross-cutting primitives: the D2GS Huffman packet codec and its framing.

Apps

app built on what it is
drlg-server drlg Serves a generated act as JSON over HTTP. A live instance runs at libd2.typeguru.nl; see below.

About the baked assets

packages/drlg embeds a handful of small, pre-baked binary blobs under src/blobs/ (subtile collision flags, DS1 level structure, automap sprites, and a slice of tile art) plus a few .dt1/.ds1 fixtures under src/maps/. These are derived from Blizzard game data and are included only so the generator builds and self-verifies out of the box. They are not redistributable game content; this repository is not affiliated with or endorsed by Blizzard Entertainment. Diablo II is © Blizzard Entertainment.

License

MIT, for the Zig source. See the note above about the baked assets.

About

Clean-room Zig reimplementation of the deterministic Diablo II 1.14d engine core (map gen, item gen, runtime sim)

Topics

Resources

Stars

9 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages