Metadata-Version: 2.5
Name: dm2-tools
Version: 0.2.1
Summary: Python bindings for the Dungeon Master II asset parsers (over the dm2-ffi C ABI)
Project-URL: Homepage, https://git.lordran.net/jqueuniet/dm2-tools
Project-URL: Repository, https://git.lordran.net/jqueuniet/dm2-tools
Project-URL: Changelog, https://git.lordran.net/jqueuniet/dm2-tools/src/branch/main/CHANGELOG.md
Project-URL: Issues, https://git.lordran.net/jqueuniet/dm2-tools/issues
License-Expression: MIT OR Apache-2.0
License-File: LICENSE-APACHE
License-File: LICENSE-MIT
Keywords: bindings,dungeon-master,gamedev,parser
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Games/Entertainment
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.11
Provides-Extra: test
Requires-Dist: pytest; extra == 'test'
Description-Content-Type: text/markdown

# dm2-tools (Python bindings)

Pythonic bindings for the *Dungeon Master II: The Legend of Skullkeep* asset
parsers, layered over the `dm2-ffi` C ABI via `ctypes`. Every function takes
`bytes` in and returns `bytes` / `dict` / `str` / `bool` out; failures raise
`dm2_tools.Dm2Error`.

## Install

```bash
pip install ./bindings/python
```

This runs a hatchling build hook that invokes
`cargo build -p dm2-ffi --release` against the workspace and bundles the
resulting cdylib (`libdm2_ffi.dylib` / `libdm2_ffi.so` / `dm2_ffi.dll`) into
the wheel under `dm2_tools/_lib/`. **A Rust toolchain must be installed and on
`PATH`** to install this way. Python 3.11 or newer is required.

Tagged releases attach prebuilt wheels for x86-64 Linux (manylinux) and x86-64
Windows, which need no Rust toolchain — one wheel per platform covers every
supported Python, since the library is loaded through `ctypes` rather than
linked against a specific interpreter. macOS and other architectures build
from a checkout as above.

### Development / override

For local iteration without reinstalling, the module also looks for the
native library at:

1. `dm2_tools/_lib/<lib>` (bundled, present after a real install)
2. the path in the `DM2_FFI_LIB` environment variable, if set
3. `<repo-root>/target/release/<lib>` (the plain `cargo build` output — used
   automatically when running the package straight out of a checkout)

Set `DM2_FFI_LIB=/path/to/libdm2_ffi.so` (or `.dylib` / `.dll`) to point at a
specific build.

## Usage

```python
import dm2_tools

dump = dm2_tools.save_to_json(save_bytes)   # -> dict
dump["gameplay"]["champions"][0]["cur_hp"] = 999
edited = dm2_tools.save_from_json(dump)      # -> bytes
```

JSON-returning functions parse the C ABI's UTF-8 JSON payload with
`json.loads` and return a plain `dict`; JSON-consuming functions accept a
plain `dict` and serialize it with `json.dumps` before calling in.

## API

| Function | Signature | Notes |
|---|---|---|
| `version()` | `() -> str` | Native library version (matches this package's version). |
| `save_to_json(data)` | `bytes -> dict` | Self-contained lossless savegame dump. |
| `save_from_json(dump)` | `dict -> bytes` | Rebuild savegame bytes from a `save_to_json` dump. |
| `save_info_json(data)` | `bytes -> dict` | Lossy, human-readable gameplay-only savegame view. |
| `dungeon_to_json(data)` | `bytes -> dict` | Self-contained lossless `dungeon.dat` dump. |
| `dungeon_from_json(dump)` | `dict -> bytes` | Rebuild `dungeon.dat` bytes from a `dungeon_to_json` dump. |
| `dungeon_skproject_json(data)` | `bytes -> dict` | Lossy skproject-compatible `dungeon.dat` dump. |
| `graphics_header_json(data)` | `bytes -> dict` | Small header summary of a `graphics.dat` (format version, byte order, entry count). |
| `graphics_decode_png(data, cls1, cls2, cls4)` | `(bytes, int, int, int) -> bytes` | Decode the image at class tuple `(cls1, cls2, 0x01, cls4)` to PNG bytes. |
| `music_detect(data)` | `bytes -> str` | Sniff a standalone music file's container format. |
| `music_hmp_to_smf(data)` | `bytes -> bytes` | Convert a DOS HMI/HMP stream to a Standard MIDI File. |
| `music_render_mod_wav(data)` | `bytes -> bytes` | Render an Amiga ProTracker module to a stereo WAV. |
| `music_snd_to_wav(data)` | `bytes -> bytes` | Decode a Mac `'snd '` resource payload to a mono WAV. |
| `ftl_info_json(data)` | `bytes -> dict` | Small summary of an FTL 68k module container. |
| `ftl_roundtrip_ok(data)` | `bytes -> bool` | `True` if the FTL module re-emits byte-exact. |
| `resfork_list(data)` | `bytes -> list[dict]` | List a Mac resource fork's resources (`type_code`, `id`, `name`, `payload_len`). |
| `resfork_extract(data, type_code, id)` | `(bytes, bytes\|str, int) -> bytes` | Extract one resource's payload by `(type_code, id)`. `type_code` may be 4 raw bytes or a 4-character `str` (encoded as Mac Roman). |

All exceptions raised by the above are `dm2_tools.Dm2Error`.

## Testing

```bash
python -m pytest -q
```

Tests that need workspace fixtures look for them at `<repo-root>/fixtures/`
(override with `DM2_FIXTURES_DIR`); anything whose fixture isn't present
skips cleanly rather than failing.

## Scope

Out of scope for this package, intentionally deferred:

- Publishing to PyPI.
- Prebuilt / multi-platform wheels (CI-built manylinux, macOS universal2,
  Windows, …). Installing always compiles the cdylib locally via Cargo.
