loading…
Search for a command to run...
loading…
Diablo II Resurrected modding toolkit — character builder, mod build/deploy pipeline, save inspector, CASC reader, and MCP server for Claude/Codex/AI integratio
Diablo II Resurrected modding toolkit — character builder, mod build/deploy pipeline, save inspector, CASC reader, and MCP server for Claude/Codex/AI integration. Linux/Steam Deck.
Tests Python 3.11+ License: MIT Platform: Linux
Horadric Tools is a Python/MCP toolkit for offline Diablo II: Resurrected save and
data-mod workflows. It builds .d2s characters from YAML, scans save files
before they are loaded, builds data mods for single player, and exposes
the same capabilities through an MCP server.
The repository is Linux / Steam Deck first. Windows path detection exists but needs more testing. The repo does not ship Blizzard game data, private save files, or Battle.net automation.
Horadric Tools provides:
d2r-chargen: YAML-to-.d2s character generation, import, diff, validation,
and scanning.d2r-mod: local data extraction, overlay builds, JSON string patches, CASC
deploy helpers, diffs, audits, and cleanup commands.d2r_mcp: 23 MCP tools for lookup, save inspection, character generation,
and mod-pipeline automation.tools/ for corpus scans, follower payload inspection,
model-row comparison, and repo hygiene checks.scripts/merc_status_external_scan.sh
for the merc-status open question (aggregate-only JSON; no paths or per-file examples).Character generation currently covers equipment, runewords, charms, stats,
skills, mercenary gear, Iron Golems, and experimental template-derived bound
demons. Bound-demon template recipes are documented in
docs/bound-demon-template-recipes.md;
they keep source affixes separate from Bind Demon skill affixes and do not
treat template-derived support as arbitrary algorithmic synthesis. A narrow
registry-backed synthesis_validated surface is also available for exact
validated packages; list those ids with d2r-chargen bound-demon-packages.
For .d2s work, the expected loop is: write to staging, recompute size and
checksum, scan, then promote only scanner-clean output.
git clone https://github.com/crabsmadethis/d2r-horadric-tools.git
cd d2r-horadric-tools
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
Generate local lookup data from your D2R install:
d2r-mod extract
If the install is not auto-detected:
d2r-mod extract --game-dir "/path/to/Diablo II Resurrected"
Validate the included example character:
d2r-chargen validate ExamplePaladin
Build it only when your save directory is configured and you are ready to write the generated save:
d2r-chargen build ExamplePaladin --force
d2r-chargen scan ExamplePaladin
D2R caches saves at startup, so fully exit and relaunch the game after changing a local save file.
The tracked chars/ directory contains curated examples only. Keep disposable
or validation-specific character drafts outside the repo and point the tools at
them with D2R_CHARS=/path/to/chars.
schema_version: 1
name: ExamplePaladin
class: paladin
level: 85
progression: hell_complete
stats:
strength: 156
dexterity: 125
vitality: 250
energy: 25
skills:
Blessed Hammer: 20
Concentration: 20
Vigor: 20
equipment:
- slot: helm
unique: Harlequin Crest
- slot: body
runeword: Enigma
base: utp
- slot: weapon
runeword: Heart of the Oak
base: fla
inventory:
charms:
- magic_grand_charm:
count: 8
properties:
skill_tab: [1, 15]
life: 40
See chars/ExamplePaladin.yaml for a fuller example.
Iron Golem payloads are authored with an iron_golem: block on Necromancers
that have IronGolem learned. The supported public surface includes normal,
magic, ethereal, set, rare, crafted, socketed-normal, and runeword payloads.
Runeword golems are written as a JM-less parent followed by generated rune
filler records inside the golem block. Unique payloads require explicit
canonicalization opt-in because D2R rewrites some bytes on save/exit. Manual
Iron Golem socket filler authoring and broad jewel-filler authoring remain
gated; character stash_items expose only the narrow live-positive normal
parent shape with one magic jew or validated unique cjw filler.
class: necromancer
skills:
IronGolem: 1
iron_golem:
item:
runeword: Insight
base: 7wc
Supported class names:
amazon, sorceress, necromancer, paladin, barbarian, druid, assassin, warlock
Common equipment slots:
helm, body, weapon, shield, hands, belt, feet, neck, ring_right, ring_left,
weapon_switch, shield_switch
d2r-chargend2r-chargen list
d2r-chargen validate <name> [--yaml-only]
d2r-chargen build <name> [--phase N] [--force]
d2r-chargen scan <name>
d2r-chargen import <name> [--force]
d2r-chargen diff <file1> <file2>
d2r-chargen bound-demon-packages [--json] [--all]
validate checks YAML and binary encoding without writing a save, including
bound-demon and Iron Golem payloads when the YAML requests them. build writes
through the safety pipeline and should be followed by scan before the result
is loaded in game.
For bound demons, normal reusable authoring should use template_path or a
listed synthesis_validated package id. Raw context slices, generated-name
requests, aura-choice requests, pcount/stat knobs, and algorithmic
monster: NAME synthesis fail before any save is written unless a validated
package explicitly owns that behavior.
Bound-demon template workflow:
python3 tools/d2s_demon_template_inspect.py <template.d2s>
python3 tools/d2s_demon_template_inspect.py <template.d2s> \
--extract-payload .local-demon-templates/<template>.bin \
--emit-yaml-snippet
The emitted YAML snippet is preserve-only. It names template_path and the
payload row index, but does not add source or skill affix edits. Put character
drafts and extracted templates outside the repo:
export D2R_CHARS=/path/to/local/chars
export D2R_SAVES=/path/to/offline-saves
d2r-chargen validate MyWarlock
d2r-chargen build MyWarlock --force
d2r-chargen scan MyWarlock
The public template catalog currently documents Black Lancer, Mauler, Lister-style/Baal Subject 5, and Hephasto-family entries in docs/bound-demon-template-recipes.md.
stash_items can also build the narrow socketed-normal parent shape that has
passed Offline validation: exactly one magic jew filler, or the validated
unique Guardian's Thunder cjw, under a normal parent. Broader jewel
families and socketed magic/rare parents are still research-gated.
stash_items:
- normal: true
base: flc
socketed: true
num_sockets: 1
socket_fillers:
- base: jew
magic: true
properties:
fire_res: 5
Normal stash_items may also carry explicit stack quantities for misc and
quest-style bases that use the D2R quantity field. This is fixture-backed for
representative tokens, essences, keys, Worldstone shards, and tomes; live
validation should still be run before promising a new quest-item family.
stash_items:
- normal: true
base: toa
quantity: 1
- normal: true
base: pk1
quantity: 4
- normal: true
base: tbk
quantity: 20
d2r-modd2r-mod extract [--game-dir PATH]
d2r-mod build [--no-regen]
d2r-mod deploy [--force] [--no-casc]
d2r-mod undeploy
d2r-mod diff [--summary]
d2r-mod inject [--from-dir PATH]
d2r-mod audit [--skills] [--items]
d2r-mod clean
d2r-mod update
Commands that read or write game data need a detected D2R install or an
explicit --game-dir / D2R_GAME_DIR.
Overlays modify D2R data tables declaratively. Place overlay files in
overlays/ and run d2r-mod build.
target: data/global/excel/UniqueItems.txt
changes:
- row: {index: "The Gnasher"}
set:
prop4: "dmg%"
min4: "50"
max4: "50"
comment: "Buff The Gnasher with +50% Enhanced Damage"
If no overlays/ directory exists, the build proceeds with vanilla data only.
See examples/sample_overlay.yaml for a complete
example.
D2R reads most item, mercenary, and UI strings from JSON files under
data/local/lng/strings/. Put YAML specs in patches/json_strings/ to add or
override strings:
description: "Rename a few potions"
target: item-names.json
entries:
- key: "vps"
value: "Wild Rice Cake"
- key: "MyCustomItem"
value: "Heart of the Mountain"
After d2r-mod build, patched JSON files are written to
build/data/local/lng/strings/. D2R caches strings at startup, so fully exit
and relaunch to see changes.
Horadric Tools ships an MCP server with 23 tools across lookup, save inspection, chargen, and mod-pipeline categories.
Install the package and the MCP SDK before launching the server:
pip install -e .
pip install mcp
Launch it with:
python3 -m d2r_mcp
Claude Code:
claude mcp add d2r-tools --transport stdio --scope user -- python3 -m d2r_mcp
Generic MCP config:
{
"mcpServers": {
"d2r-tools": {
"command": "python3",
"args": ["-m", "d2r_mcp"],
"env": {}
}
}
}
See d2r_mcp/README.md for the full tool catalog and safety notes.
The save-file workflow is intentionally conservative:
.d2s template when possible.d2r-chargen scan <name>.Scanner hard errors are deployment blockers unless there is bit-level evidence that the scanner is wrong. Detailed save-format notes live in docs/d2s_format.md.
d2r_chargen/ Character YAML parser, save writer, scanner, importer, diff
d2r_mod/ Data extraction, overlays, CASC read/write, deploy pipeline
d2r_mcp/ MCP server, tool wrappers, response envelope, MCP docs
chars/ Curated example character YAML
examples/ Public overlay examples
docs/ Save-format notes, validation docs, public research writeups
tools/ Standalone diagnostics, corpus tools, hygiene checks
plugin/ Optional Claude Code plugin commands
tests/ Unit, MCP, scanner, mod-pipeline, and fixture-gated tests
Generated data, build output, local evidence, and extracted game files should stay untracked.
| Platform | Status |
|---|---|
| Linux / Steam Deck (Proton) | Supported, tested |
| Windows | Path detection included, needs more testing |
| macOS | Unsupported for playing D2R; useful for docs and code work only |
pip install mcp)Start with CONTRIBUTING.md. Before opening a PR, run:
python tools/public_hygiene_check.py
ruff check .
pytest tests/ -v --timeout=60 \
-m "not integration and not slow and not e2e and not smoke" \
--ignore=tests/fixtures/ \
--ignore=tests/test_chargen.py \
--ignore=tests/test_decoder.py \
--ignore=tests/test_fixtures.py \
--ignore=tests/test_importer.py
MIT
Run in your terminal:
claude mcp add crabsmadethis-d2r-horadric-tools -- npx CSA PROJECT - FZCO © 2026 IFZA Business Park, DDP, Premises Number 31174 - 001
Security
Low riskAutomated heuristic from public metadata — not a security guarantee.