halbu 0.2.1

Diablo II save file parsing library.
Documentation
# 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](https://github.com/feored/halbu-editor)**.

---

## Features

- Parse and modify `.d2s` save 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

```bash
cargo add halbu
```


## Basic usage

Parse, modify, and write a save:

```rust
use halbu::{CompatibilityChecks, Save, Strictness};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let bytes = std::fs::read("Hero.d2s")?;

    let parsed = Save::parse(&bytes, Strictness::Strict)?;
    let mut save = parsed.save;

    save.character.name = "Halbu".to_string();
    save.skills.set_all(20);

    let encoded = save.encode_for(save.format(), CompatibilityChecks::Enforce)?;
    std::fs::write("Halbu.d2s", encoded)?;

    Ok(())
}
```


## Parsing modes

Strict parsing fails on inconsistencies:

```rust
let parsed = Save::parse(&bytes, Strictness::Strict)?;
```

Lax parsing continues and reports issues:

```rust
let parsed = Save::parse(&bytes, Strictness::Lax)?;
if !parsed.issues.is_empty() {
    eprintln!("Parse issues: {:?}", parsed.issues);
}
```


## 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).

```rust
let report = save.validate();
if !report.is_valid() {
    eprintln!("Validation issues: {:?}", report.issues);
}
```


## Compatibility and encoding

Compatibility checks apply when converting between formats during encoding.

```rust
use halbu::{CompatibilityChecks, Save, Strictness};
use halbu::format::FormatId;

let parsed = Save::parse(&bytes, Strictness::Strict)?;
let save = parsed.save;

let target = FormatId::V99;
let issues = save.check_compatibility(target);

if !issues.is_empty() {
    eprintln!("Compatibility issues: {issues:?}");
}

// Safe (blocks on incompatibility)
let encoded = save.encode_for(target, CompatibilityChecks::Enforce)?;

// Unsafe (bypasses checks)
let forced = save.encode_for(target, CompatibilityChecks::Ignore)?;
```


## Summary API

Read metadata without fully parsing the file:

```rust
let summary = Save::summarize(&bytes, Strictness::Lax)?;

println!(
    "name={:?} class={:?} level={:?} expansion={:?}",
    summary.name,
    summary.class,
    summary.level,
    summary.expansion_type
);
```

## Edition detection

For unknown versions, Halbu can try to guess which edition the save layout matches most closely:

```rust
use halbu::format::detect_edition_hint;
use halbu::GameEdition;

let hint = detect_edition_hint(&bytes);

if hint == Some(GameEdition::RotW) {
    // likely RotW (v105-style layout)
}
```


## 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 format
- `save.game_edition()` -> edition family
- `save.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](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