d2r_save_inspect
Read and summarize a Diablo II Resurrected .d2s save file, extracting character class, level, stats, progression, and mercenary information. For validation, use the dedicated scan tool.
Instructions
Summarize a .d2s save: class, level, stats, progression, merc.
No scanner pass — use d2r_save_scan for validation.
Args: path: Absolute path to .d2s file, or bare character name.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes |
Implementation Reference
- d2r_mcp/server.py:133-142 (registration)The MCP tool registration for 'd2r_save_inspect' as a FastMCP tool using the @mcp.tool() decorator. It delegates to _save_inspect (imported as inspect from d2r_mcp.save).
@mcp.tool() async def d2r_save_inspect(path: str) -> dict: """Summarize a .d2s save: class, level, stats, progression, merc. No scanner pass — use d2r_save_scan for validation. Args: path: Absolute path to .d2s file, or bare character name. """ return _save_inspect(path) - d2r_mcp/server.py:109-114 (registration)Import of the 'inspect' function from d2r_mcp.save, aliased as _save_inspect for use in the tool handler.
from d2r_mcp.save import ( scan as _save_scan, inspect as _save_inspect, list_items as _save_list_items, diff as _save_diff, ) - d2r_mcp/save.py:77-118 (handler)The actual implementation of the inspect logic. Decodes class, level, progression, stats, name, and merc info from the .d2s binary header. Uses _resolve_save_path for path handling and returns an envelope via ok().
def inspect(path: str) -> dict: """Return a human-readable character summary. Decodes class, level, progression, stats, waypoints, and merc info from the .d2s header and stats section. Does not run the full scanner; use scan() for validation. """ resolved = _resolve_save_path(path) if not os.path.exists(resolved): return error("not_found", f"save file not found: {path}") from d2r_chargen.importer import _read_name, _decode_character_stats with open(resolved, "rb") as f: data = bytearray(f.read()) cls_id = data[0x18] class_name = _CLASS_NAMES[cls_id] if 0 <= cls_id < len(_CLASS_NAMES) \ else f"class_{cls_id}" level = data[0x1B] prog = data[0x15] progression = {0x00: "normal", 0x05: "nightmare", 0x0F: "hell"}.get( prog, f"0x{prog:02X}" ) stats = _decode_character_stats(data) # Merc summary (kept minimal — full merc decode is the scanner's job) merc = {"present": False} merc_type = int.from_bytes(data[0xA8:0xAA], "little") if merc_type not in (0, 0xFFFF): merc["present"] = True merc["type_id"] = merc_type merc["xp"] = int.from_bytes(data[0xAA:0xAE], "little") return ok( character=_read_name(data), **{"class": class_name}, class_id=cls_id, level=level, progression=progression, stats=stats, merc=merc, ) - d2r_mcp/save.py:12-28 (helper)Helper function _resolve_save_path that resolves user-supplied paths (absolute, ~-expanded, or bare character names) to an absolute .d2s file path.
def _resolve_save_path(path: str) -> str: """Resolve a user-supplied path to an absolute .d2s path. Accepts absolute paths, ~-prefixed paths, or bare character names (resolved against the Steam saves directory). """ expanded = os.path.expanduser(path) if os.path.isabs(expanded) and os.path.exists(expanded): return expanded if not expanded.endswith(".d2s"): candidate = os.path.join(SAVES, f"{expanded}.d2s") if os.path.exists(candidate): return candidate candidate = os.path.join(SAVES, expanded) if os.path.exists(candidate): return candidate return expanded # caller handles non-existence - d2r_mcp/save.py:73-74 (helper)CLASS_NAMES lookup list used by inspect to translate the binary class ID byte to a human-readable class name (Amazon, Sorceress, etc.).
_CLASS_NAMES = ["Amazon", "Sorceress", "Necromancer", "Paladin", "Barbarian", "Druid", "Assassin", "Warlock"]