Diablo 2 Map generator for v1.13c
Usage:
d2-map.exe [D2 Game Path] [options]
Options:
--seed [-s] Map Seed
--difficulty [-d] Game Difficulty [0: Normal, 1: Nightmare, 2: Hell]
--act [-a] Dump a specific act [0: ActI, 1: ActII, 2: ActIII, 3: ActIV, 4: Act5]
--map [-m] Dump a specific Map [0: Rogue Encampment ...]
--verbose [-v] Increase logging level
Examples:
# Dump ActI from Normal mode for seed 1122334
d2-map.exe /home/diablo2 --seed 1122334 --difficulty 0 --act 0
# Dump all acts from Hell mode for seed 1122334
d2-map.exe /home/diablo2 --seed 1122334 --difficulty 2
{
/** Level code */
"id": 74,
/** Name given back by diablo2 client */
"name": "Arcane Sanctuary",
/** how far offset this map is from the top left of the game world */
"offset": {
"x": 25000,
"y": 5000
},
/** Dimensions of the map */
"size": {
"width": 1000,
"height": 1000
},
/** Important objects / NPCs found in this level */
"objects": [
{"id": 53, "type": "exit", "x": 137, "y": 0, "name": "Palace Cellar Level 2" },
{"id": 250, "type": "npc", "x": 440, "y": 20, "name": "The Summoner"},
{"id": 371, "type": "npc", "x": 458, "y": 203, "name": "Lightning Spire"},
{"id": 305, "type": "object", "x": 237, "y": 401, "name": "teleportation pad", "op": 27}
{"id": 402, "type": "object", "x": 449, "y": 449, "name": "Waypoint", "op": 23},
{"id": 298, "type": "object", "x": 427, "y": 426, "name": "portal", "op": 34}
],
/** Map Collision data */
"map": [
// Map data for x offset 0 - Using run length encoding
[1, 149] // 1 pixel of collision, 149 pixels of open space, 150 - map.size.width pixels of collision
]
}
Collision maps are encoded using a simple run length encoding to save on space
Given this small map
[1,5,1],
[2,3,2],
[1,5,1]
It would generate the following word where X
is collision and .
is open space
X.....X
XX...XX
X.....X
A simple rendering engine could be using a HTMLCanvas
's ctx.fillRect(x, y, width, height)
function to draw one row at a time
for (let y = 0; y < map.length; y++){
let x = 0;
let fill = true;
for (const offset of row) {
if (fill) ctx.fillRect(x, y, offset, 1, "black");
x = x + offset;
fill = !fill
}
}
- npm v16
- yarn
- docker
This Map generation client can be used with
- Diablo 2 LOD 1.14d & ProjectD2 or
- Diablo 2 LOD 1.13c
This is the easiest method to get working:
docker pull blacha/diablo2
docker run -it -v "/E/Games/Diablo II":/app/game docker.io/blacha/diablo2:latest /bin/bash
wine regedit /app/d2.install.reg
wine bin/d2-map.exe game --seed 10 --level 1 --difficulty 0
The last wine command should generate the JSON for one level, this is to test that it works.
From the source code folder: Remember to change "/E/Games/Diablo II" in the below commands to your D2 installation folder.
yarn install
yarn build
cd packages/map
yarn bundle-server
yarn bundle-www
xcopy static dist\www
docker build . -t diablo2/map
docker run -it -v "/E/Games/Diablo II":/app/game diablo2/map /bin/bash
wine regedit /app/d2.install.reg
wine bin/d2-map.exe game --seed 10 --level 1 --difficulty 0
exit
The above wine command should generate the JSON for one level, this is to test that it works. You can try using different seeds, levels and difficulties this way if you like.
Now you run this server so you can send requests for seeds/difficulties to generate all the maps for that given seed:
docker run -v "/E/Games/Diablo II":/app/game -p 8899:8899 diablo2/map
or if you're using the public docker image:
docker run -v "/E/Games/Diablo II":/app/game -p 8899:8899 docker.io/blacha/diablo2:latest
Then you can do a simple curl command to generate:
curl localhost:8899/v1/map/:seed/:difficulty/:act.json
e.g. curl localhost:8899/v1/map/0x3607656c/Normal/ActI.json
Numbers can be used for Act/Difficulty instead
Act 1 in Normal
/v1/map/0x3607656c/0/0.json
Act 5 in Hell
/v1/map/0x3607656c/2/4.json
The map server can also generate images
curl localhost:8899/v1/map/:seed/:difficulty/:level.png
Tower cellar level 3 in hell
http://localhost:8899/v1/map/0xff00ff/Hell/23.png
The server can control multiple map processes, when starting the server the $DIABLO2_CLUSTER_SIZE
environment variable controls how many map processes to start.
When the diablo 2game client update the offsets need to call change
Inside d2_ptrs.h
is a list of offsets needed to initialize the game
The easiest way to trace the initialization steps is to run Diablo inside of wine with WINEDEBUG=+snoop
this logs every external call made by the initialization of the game.
export WINEDEBUG=+snoop
wine Game.exe 2> snoop.log
This generates a massive log file however inside are common call patterns
Here is the function call for Fog_10021 this can be used to trace the initialization
0024:CALL Fog.10021(<unknown, check return>) ret=0040829b
...
0024:RET Fog.10021() retval=00000028 ret=0040829b
Which relates to the FUNCPTR
in d2_ptrs.h
FUNCPTR(FOG, 10021, VOID __fastcall, (CHAR * szProg), -10021)
The initialization of D2 always starts with a few Fog.dll calls followed by two D2Win.dll calls
// d2_client.c
FOG_10021("D2");
FOG_10019(DIABLO_2, (DWORD)ExceptionHandler, DIABLO_2_VERSION, 1);
FOG_10101(1, 0);
FOG_10089(1);
if (!FOG_10218()) {
log_error("InitFailed", lk_s("dll", "Fog.dll"));
ExitProcess(1);
}
Looking for the final RET Fog.10218
will generally show on the next line the first D2Win.dll
CAll
0024:CALL Fog.10218(<unknown, check return>) ret=00407636
0024:RET Fog.10218() retval=00000001 ret=00407636
0024:CALL D2Win.10086(<unknown, check return>) ret=00407644
...
0024:RET D2Win.10086() retval=00000001 ret=00407644
0024:CALL D2Win.10005(<unknown, check return>) ret=00407659
So in this client the first D2Win calls are D2Win_10086
and D2Win_10005