Skip to content

Add RotW support #8#9

Open
feored wants to merge 79 commits intomainfrom
dev
Open

Add RotW support #8#9
feored wants to merge 79 commits intomainfrom
dev

Conversation

@feored
Copy link
Owner

@feored feored commented Mar 14, 2026

  • Added support for multiple versions by splitting the model into FormatId (layout)/GameEdition (family)/ExpansionType (mode), with v99/v105 expansion handling done differently on purpose.

  • Added blocking encode compatibility rules for known bad conversions, with an explicit opt-out (CompatibilityChecks::Ignore).

  • Unified the core API around one path: Save::parse / Save::summarize / save.check_compatibility / save.encode_for.

  • Added coverage and docs to match: new v105 fixtures, golden/compat/summary tests, updated README/examples, and bumped crate version to 0.2.0.

feored added 30 commits July 22, 2023 21:23
Read and write skills.
Basic save file parsing (npcs/items placeholders)
Add serde serialization and reorganize tests/consts files.
Add getters/setters checking for bounds in attributes.
…tion bugfix.

When all stats are written, parse one more attribute header to make sure we update byte_position with the position after the 0x1FF trailer.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds RotW (v105) support alongside legacy (v99) by introducing a format/layout abstraction, compatibility rules, and placeholder section models, plus updated docs/examples and new fixtures/tests.

Changes:

  • Introduce format module with layout detection, decode/encode orchestration, summarize API, and compatibility enforcement/override.
  • Split/modernize section models (character v99/v105 codecs, attributes bitstream, skills with D2R name mapping, waypoints, NPC/items placeholders).
  • Add fixtures + golden/compat/summary tests, update docs/examples, and bump crate metadata/version.

Reviewed changes

Copilot reviewed 44 out of 51 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
tests/save_format_golden.rs Golden + compatibility + forced-encode tests across v99/v105.
src/waypoints/tests.rs New waypoint tests (currently do not compile as written).
src/waypoints/mod.rs New waypoint model + parsing/encoding logic.
src/waypoints/consts.rs Waypoint section constants and waypoint name tables.
src/waypoints.rs Removed legacy waypoint section builder.
src/utils.rs New shared byte/bit utilities (read_bits/write_bits, endian helpers).
src/skills/tests.rs New skills parsing + name-mapping tests (includes Warlock mapping).
src/skills/named_d2r.rs D2R skill-name tables + normalized lookup helpers.
src/skills/mod.rs Skills section model + raw and named APIs.
src/skills.rs (Context only) legacy file likely replaced by module.
src/quests.rs Removed legacy quests placeholder.
src/npcs/mod.rs New NPC section placeholder preserving fixed-size raw payload.
src/npcs.rs Removed legacy NPC section builder.
src/items/mod.rs New items placeholder + empty-inventory trailer generation by layout/mode.
src/header/mod.rs Removed legacy header code.
src/header/mercenary.rs Removed legacy mercenary variant tables (replaced by simpler model).
src/header/character.rs Removed legacy character code (replaced by codecs).
src/format/tests.rs New format/layout/summary/encoding tests.
src/format/summary.rs New fast summary extraction (header + character fields).
src/format/mod.rs New format module wiring/re-exports.
src/format/layout.rs Layout metadata (offsets/ranges), format detection/fallback, checksum metadata.
src/format/encode.rs Unified encode pipeline with compatibility checks and placeholder section handling.
src/format/decode.rs Unified decode pipeline with strict/lax issues, section parsing, checksum metadata.
src/format/compatibility.rs Compatibility issue reporting and blocking encode validation.
src/character/v99.rs v99 character codec (decode/encode with raw preservation).
src/character/v105.rs v105 character codec (mode marker + raw preservation).
src/character/tests.rs Character codec/unit tests incl. v105 fixtures + title mapping.
src/character/mod.rs Character model, status bitfield, title helpers, canonical invariants.
src/character/mercenary/tests.rs Mercenary parse/write tests.
src/character/mercenary/mod.rs Mercenary model + parse/write helpers.
src/character/common.rs Shared character codec read/write helpers with bounds errors.
src/character/codec.rs Format-dispatched character codec entrypoints.
src/attributes/tests.rs Updated attributes tests targeting new bitstream utilities/types.
src/attributes/mod.rs New typed attributes model + packed bitstream parse/encode (Q8 helpers).
src/attributes.rs Removed legacy attributes implementation.
rustfmt.toml Expand formatting heuristics configuration.
notes.md Removed older notes (replaced by NOTES.md).
examples/inspect_issues.rs Example for strict vs lax parsing and issue inspection.
examples/edit_character.rs Example editing a save and encoding back with compatibility enforcement.
assets/test/barbrotw_v105.d2s New v105 RotW fixture.
assets/test/barbexp_v105.d2s New v105 expansion fixture.
assets/test/barbclassic_v105.d2s New v105 classic fixture.
assets/test/Warlock_v105.d2s New v105 Warlock fixture.
assets/test/Test.d2s Added/updated test fixture.
assets/test/Joe.d2s Added/updated v99 fixture.
README.md New/updated README with API usage and compatibility notes.
NOTES.md New consolidated reverse-engineering notes.
LICENSE Add MIT license file.
Cargo.toml Bump to 0.2.0 and add package metadata/deps (serde/log/etc.).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +20 to +27
assert_eq!(
parsed_waypoints.hell.act1[8],
WaypointInfo {
id: Waypoint::Catacombs,
name: "Catacombs",
act: Act::Act1,
acquired: true
}
Comment on lines +41 to +52
fn generate_waypoints_test() {
let expected_bytes: [u8; 81] = [
0x57, 0x53, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
];

assert_eq!(expected_bytes, generate(&Waypoints::default()));
}
Comment on lines +358 to +360
_ => Err(ParseHardError {
message: format!("Cannot convert ID > 8 to waypoint: {id:?}"),
}),
}
}

let skills_offset = selected_layout.attributes_offset() + byte_position.current_byte + 1;
Comment on lines +12 to +19
fn empty_items_layout_for_encode(target: FormatId, mode_marker: u8) -> items::EmptyLayout {
match target {
FormatId::V99 => items::EmptyLayout::LegacyExpansion,
FormatId::V105 | FormatId::Unknown(_) => match mode_marker {
crate::character::v105::MODE_CLASSIC => items::EmptyLayout::V105Classic,
crate::character::v105::MODE_ROTW => items::EmptyLayout::V105RotW,
_ => items::EmptyLayout::V105Expansion,
},
Comment on lines +86 to +94
/// Set skill value by raw class-local index.
pub fn set(&mut self, skill_index: usize, value: u8) {
self.points[skill_index] = value;
}

/// Get skill value by raw class-local index.
pub fn get(&self, skill_index: usize) -> u8 {
self.points[skill_index]
}
Comment on lines +15 to +18
let parsed_waypoints = match parse(&bytes) {
Ok(res) => res,
Err(e) => panic!("parse_waypoints_test: {0}", e),
};
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds “RotW” (v105) support and consolidates the crate’s save parsing/encoding around a unified format/layout abstraction, with explicit compatibility checks for unsafe conversions.

Changes:

  • Introduces FormatId (v99/v105/Unknown) layout detection + encode/decode/summarize orchestration under src/format/.
  • Adds new modeled sections (waypoints, skills, NPC/items placeholders) plus shared bit/byte utilities.
  • Adds fixtures, golden/compat/summary tests, and updates docs/examples; bumps crate metadata/version.

Reviewed changes

Copilot reviewed 46 out of 53 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/save_format_golden.rs Golden roundtrip + compatibility blocker tests (v99/v105/RotW)
src/waypoints/mod.rs New waypoint model with parse/encode + per-act APIs and errors
src/waypoints/consts.rs Waypoint section constants + waypoint name tables
src/waypoints/tests.rs Waypoints parse/roundtrip/unit tests
src/waypoints.rs Removes old waypoint section builder
src/utils.rs Adds shared parsing/bitstream read/write utilities + tests
src/skills/mod.rs New skills section model + D2R name-based helpers
src/skills/named_d2r.rs Default D2R skill name tables + normalized lookup
src/skills/tests.rs Skills section/unit tests (incl. Warlock names)
src/quests.rs Removes old quest section builder (replaced by new quests module elsewhere)
src/npcs/mod.rs NPC section placeholder model with header validation
src/npcs.rs Removes old NPC section builder
src/items/mod.rs Items placeholder + empty-trailer generation by layout/mode
src/format/mod.rs New format orchestration module exports
src/format/layout.rs Layout metadata + FormatId + fallback logic
src/format/decode.rs Unified decode path with strict/lax issues
src/format/encode.rs Unified encode path with compatibility enforcement + checksum
src/format/summary.rs Fast summary extraction (header + character only)
src/format/compatibility.rs Encode compatibility rules + blocker aggregation
src/format/edition_hint.rs Heuristic edition hint detection for unknown versions
src/format/tests.rs Format/layout/summary/encode behavioral tests
src/character/mod.rs New character model + title rules + status bits
src/character/codec.rs Per-format character codec dispatch
src/character/common.rs Shared character field read/write helpers
src/character/v99.rs v99 character codec implementation
src/character/v105.rs v105 character codec + mode marker handling
src/character/tests.rs Character codec/title tests incl. v105 fixture
src/character/mercenary/mod.rs Mercenary model parse/write helpers
src/character/mercenary/tests.rs Mercenary unit tests
src/attributes/mod.rs Attributes model + bitstream parse/encode + typed ids
src/attributes/tests.rs Attributes read/write tests + utils integration
examples/waypoints.rs Example: reading/writing waypoints with explicit errors
examples/inspect_issues.rs Example: strict vs lax parsing + issue reporting
examples/edit_character.rs Example: editing a save and re-encoding with compatibility checks
assets/test/*.d2s Adds/updates v105 fixtures + additional test saves
README.md Updated docs, API usage, compatibility/edition hint explanation
NOTES.md Consolidated/updated format reverse-engineering notes
notes.md Removes old notes file (replaced by NOTES.md)
rustfmt.toml Formatting heuristics update
Cargo.toml Crate metadata updates + version bump + new deps
LICENSE Adds MIT license file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +86 to +94
/// Set skill value by raw class-local index.
pub fn set(&mut self, skill_index: usize, value: u8) {
self.points[skill_index] = value;
}

/// Get skill value by raw class-local index.
pub fn get(&self, skill_index: usize) -> u8 {
self.points[skill_index]
}
Comment on lines +267 to +271
pub(crate) fn layout_for_encode(target: FormatId) -> &'static dyn Layout {
match target {
FormatId::V99 => &V99_LAYOUT,
FormatId::V105 | FormatId::Unknown(_) => &V105_LAYOUT,
}
Comment on lines +1 to +3
pub const SECTION_HEADER: [u8; 8] = [0x57, 0x53, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00];
pub const DIFFICULTY_HEADER: [u8; 2] = [0x02, 0x01];
pub const SECTION_TRAILER: u8 = 0x01;
Comment on lines +322 to +327
let class_skills = d2r_skills_for_class(class)?;
class_skills
.iter()
.position(|candidate| normalize_name(candidate) == normalized_query)
.ok_or(NamedSkillError::UnknownSkillName { class, skill_name: skill_name.to_string() })
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants