Halbu
A Rust library for parsing, editing, and safely re-encoding Diablo II: Resurrected .d2s save files.
It serves as the backend for Halbu Editor.
Features
- Parse and modify
.d2ssave files - Supports the D2R Legacy and RotW layouts (
v99,v105) - Editable sections:
- character data
- attributes
- skills
- quests
- waypoints
- mercenary data
- Partial parsing via summary API
- Strict or tolerant parsing modes
- Validation for post-edit sanity checks
- Compatibility checks for format conversion
Limitations
Some parts of the save format are not yet modeled:
- Items
- NPC section
These sections are preserved as raw bytes when possible, but may not round-trip identically after modifications.
Installation
Basic usage
Parse, modify, and write a save:
use ;
Parsing modes
Strict parsing fails on inconsistencies:
let parsed = parse?;
Lax parsing continues and reports issues:
let parsed = parse?;
if !parsed.issues.is_empty
Validation
Validation is an optional step to check the save for inconsistencies that may prevent the game from loading the save (e.g. invalid character name or mercenary level).
let report = save.validate;
if !report.is_valid
Compatibility and encoding
Compatibility checks apply when converting between formats during encoding.
use ;
use FormatId;
let parsed = parse?;
let save = parsed.save;
let target = V99;
let issues = save.check_compatibility;
if !issues.is_empty
// Safe (blocks on incompatibility)
let encoded = save.encode_for?;
// Unsafe (bypasses checks)
let forced = save.encode_for?;
Summary API
Read metadata without fully parsing the file:
let summary = summarize?;
println!;
Edition detection
For unknown versions, Halbu can try to guess which edition the save layout matches most closely:
use detect_edition_hint;
use GameEdition;
let hint = detect_edition_hint;
if hint == Some
Model overview
Halbu distinguishes between three related concepts:
FormatId- concrete file format (V99,V105, or unknown)GameEdition- edition family (D2RLegacy,RotW)ExpansionType- in-game expansion mode (Classic,Expansion,RotW)
Typical usage:
save.format()-> file formatsave.game_edition()-> edition familysave.expansion_type()-> in-game expansion
Compatibility rules (examples)
- Warlock requires RotW edition and expansion
- RotW expansion cannot be encoded to non-RotW formats
- Druid and Assassin cannot be encoded as Classic
- Unknown class IDs cannot be safely converted
Notes
- Level is stored in multiple sections; use
save.set_level(...)to keep it consistent - Additional reverse-engineering notes are available in
NOTES.md
Documentation
API docs: https://docs.rs/halbu
Changelog: CHANGELOG.md
References
These resources helped me understand the .d2s format. Many thanks to their authors.
- http://user.xmission.com/~trevin/DiabloIIv1.09_File_Format.shtml
- https://github.com/dschu012/D2SLib
- https://github.com/d07RiV/d07riv.github.io/blob/master/d2r.html
- https://github.com/oaken-source/pyd2s
- https://github.com/WalterCouto/D2CE/blob/main/d2s_File_Format.md
- https://github.com/krisives/d2s-format
- https://github.com/nokka/d2s/
- https://github.com/ThePhrozenKeep/D2MOO
- https://d2mods.info/forum/kb/index?c=4