Skip to main content

Dungeon Master Tools — extract data from Dungeon Master / Chaos Strikes Back files (GRAPHICS.DAT, DUNGEON.DAT)

Project description

Dungeon Master Tools

Dungeon Master Tools is a collection of Tools in Python for the Amiga/PC/Atari ST Game, packaged as the importable dmcsb package (published on PyPI as dmcsbpip install dmcsb):

  • dmcsb.dungeon (DungeonFile) - loads and extracts the data in DUNGEON.DAT (big-endian Amiga/Atari ST and little-endian PC)
  • dmcsb.graphics (GraphicsFile) - library for GRAPHICS.DAT handling (big-endian and little-endian PC)
  • dmcsb.versions - byte-order / game-version auto-detection (checksum registry + structural)

Byte order and game variant are auto-detected on load (see dmcsb.versions): a CRC32 registry of known files pins the exact release, falling back to structural detection (endian signature + item count). For GRAPHICS.DAT the matching bundled item map is then selected automatically (e.g. graphics_pc.map for a PC file).

Installation

pip install dmcsb              # optional: "dmcsb[pil]" enables GraphicsFile.to_pil()

or, from a source checkout, pip install -e . (or just run from the repo root — the package is importable without installing).

Command line

Installing puts a dmcsb command on your PATH (equivalently python3 -m dmcsb):

dmcsb dump GRAPHICS.DAT [out_dir]      # -> PNG/WAV/TXT/.bin + an HTML contact sheet
dmcsb dungeon DUNGEON.DAT --level 1    # print one dungeon level (map, things, champions)
dmcsb uncompress DUNGEON.DAT [out.dat] # write a decompressed copy (no parsing)
dmcsb identify GRAPHICS.DAT|DUNGEON.DAT # auto-detect the version / byte order of a file

dmcsb dump also takes --map / --palettes / --zones to override the bundled tables. The same three operations are available as standalone scripts in examples/ (thin wrappers over the library, runnable from a source checkout).

After loading a DungeonFile you can access all data from the object:

  • hdr -> header dict
  • maps[] / mapsinfo[str(level)] -> map descriptors (in file order / keyed by Level)
  • thinglist[] -> 16 entries in storage order (None for unused types)
  • things[name] -> decoded list by type: "Door", "Creature", "Weapon", "Sensor", ... (whole dungeon)
  • things_on_level(level) -> things located on one level, grouped by type
  • located_things(level) -> things on a level with their map (x, y) + sub-square cell (dir N/E/S/W)
  • item_name(category, type) -> human item name (Weapon/Armour/Potion/Junk/Container/Scroll)
  • text(textstring) / texts() -> decoded message strings (signs, ...)
  • scroll_text(scroll) -> the text a scroll points at (via its TextStringThingIndex)
  • champions() -> all champion sheets in the dungeon (name, title, gender, stats, skills); champions_on_level(level) -> only those whose sheet sits on that level (e.g. the Hall of Champions), with x,y; champion(offset) decodes one
  • tile_data -> raw tile bytes; tile_grid(level) -> 2-D tile array
  • text_data -> raw encoded dungeon text
  • chksum -> trailing checksum word, or None if the file has none

for more info see: http://dmweb.free.fr/?q=node/217

The library is silent (no prints); load from a path, bytes, or an open file. Big-endian (Amiga / Atari ST) and little-endian (PC) files are both supported and auto-detected; d.endian ('>'/'<') and d.variant report what was detected.

from dmcsb import open_dungeon

d = open_dungeon("DUNGEON.DAT")          # or open_dungeon(data=raw_bytes)
print(d.hdr['OrnamentRandomSeed'])
print(d.mapsinfo['1']['Difficulty'])
for door in d.things['Door']:                 # every door in the dungeon
    print(door['Type'], door['OrnamentOrdinal'])

for name, items in d.things_on_level(1).items():   # only level 1, grouped by type
    print(name, len(items))

for i, visible, msg in d.texts():             # decoded scroll/sign messages
    print(i, repr(msg))
for c in d.champions():                       # initial party champions
    print(c['name'], c['title'], c['Health'], c['Strength'])

Text uses DM's 5-bit packing (3 codes per 16-bit word). The decoder handles letters, spaces, ., line breaks and the standard DM/CSB escape tables (so e.g. THE / YOU expand inside titles and messages); pass an escape= callback to override them. It targets the normal/"new font" text used by the Amiga versions. Champion statistics/skills are stored as hex nibbles and decoded to integers (Stamina is kept as the raw value, i.e. 10x the number shown in-game).


When you run the demo, it prints one dungeon level:

python3 examples/demo_dungeon.py [level] [DUNGEON.DAT]

level defaults to 1; the optional second argument is the path to the file (defaults to DUNGEON.DAT). For example python3 examples/demo_dungeon.py 5 shows Level 5. With no arguments, Level 1 is shown:

Map at Level 1 ----------------
  RawMapDataByteOffset: 376
  OffsetMapX: 0, OffsetMapY: 14
  Width: 31, Height: 31, Level: 1
  RandomFloorOrnamentCount: 2
  FloorOrnamentCount: 3
  RandomWallOrnamentCount: 3
  WallOrnamentCount: 12
  Difficulty: 1
  CreatureTypeCount: 2
  DoorOrnamentCount: 3
  DoorSet1: 1, DoorSet0: 0
  WallSet: 0, FloorSet: 0

Creatures/Wall/Floor/Doors used ----------------
Creature: 6, 10, 
WallOrnate: 33, 4, 35, 51, 15, 1, 49, 38, 46, 45, 44, 5, 
FloorOrnate: 2, 8, 1, 
DoorDeco: 4, 8, 3, 

MapData ----------------

  1 4 1 1 1 1 1 1   1 1 4 1 1   1 1 1         1     1 1 4 1   1 1 
  1     3     4     1       1 1 1 1 1 4 4 1   1 1 1 1     1   1 5 
  1 1         1   1 1 1 1       1 1 1     1   1 1       1 1 1     
    4         1   1 1   1   1 1           1   1   1 1 1     1 5   
    1                   1   1 1 1 1 1 1 1 5   4   1   1 4 1 1     
  1 1   1 4 1 4 1 1   1 1   1   1 1 1 1     1 1   2               
  1     1             1           1       1 1     1 1           1 
  1 1 1 1   1 1 1     1   1 1 1   1 1 1 1 1 1       1   1 1 1 1 1 
  1     1   1   2     1   1 1 1               1 1 1 1   1   1   4 
    1 1 1   4   1   1 1   1         1 1 1 1 1 1         1   1   1 
    1       1       1   1 1   1 1   1         1 1 1 1 1 1   1   1 
  1 1 1 1 1 1   1   1   1 1 1 1 1   1   1   1       4       1     
  1             1 1 1   4               1   1 1 1   1 1 1   1 1   
  1 1 1 4 1 1 4 1       1         1 1 1 1 1 1   1 1     1         
    1                 1 1   1 1   1 1 1 1 1       1 1   1 1 1 1 1 
    1 1 1 1 4 1 1 1 1 1     2     1 1 1 1 1   1     1       1   1 
      1 1 1               1 5     1 1 1 1 1   1 1 1 1 1 1 1 1   1 
              1 1 1 1     1       1 1 1 1 1         1 1 1 1 1   1 
  1 1 1 1 1 1 1     1     1   1       4       1                   
  1 1 1 1 1 1 1   1 1     1   1 1 1 1 1 1 1 1 1         1 1 1 1   
  1   1 1 1 1 1   1   1 1 1           4           1 1 1 1 1 1 1 5 
  1 1 1         1 1   1 1 1   1 1 1   5 1 1 1 4 1 1 1 1 1 1     1 
    1           4     1       1   1 1 1   1                     1 
    1 1 1   1 1 1 1   1 1 1 1 1                     1   1 1 1 1 1 
        1   1 1 1 1                     1 1 1   1 1 1   1   1     
        1       1 1 1 1 1   1 1 1       1   1   1   1 1 1   1 1   
        1     3   1 1   1 1 1   1 1 4 1 1   1   1                 
        1     1     1 1   1       4         1   1 1 1 1 1 1 4 1   
        1     1     1 1   1 1 1   1       1 1 1     1         1   
        1     1 1 1       1 1 6           1 1 1 1 1 1 5 1 1 1 1   
      1 1 1 1     1       1   1 1         1 1 1                   
      1 1   1 1 1 1           1 1                   

The demo also prints every Thing on the level (grouped by type) after the map.

To just decompress a compressed dungeon to a raw file (no parsing):

python3 examples/uncompress_dungeon.py DUNGEON.DAT [output.dat]

The output defaults to <input>.uncompressed.

dmcsb package — GRAPHICS.DAT library

The graphics handling lives in the installable dmcsb package. It loads a GRAPHICS.DAT into memory and decodes images (IMG1/IMG2 Amiga·Atari, IMG3/IMG4 PC), the FNT1 font, sounds (SND1/SND2 signed, SND3 PC unsigned), text (TXT1 high-bit terminated, TXT2 null terminated) and the LAY1 screen layout. Byte order and the bundled item map are auto-detected per file. The data tables (image map, palettes, zone names) are bundled inside the package and applied automatically — you can override them by passing your own paths.

Use it in your own project (after pip install dmcsb — see Installation):

from dmcsb import open_graphics

g = open_graphics("GRAPHICS.DAT")   # bundled map + palettes auto-applied
# overrides: open_graphics("GRAPHICS.DAT", map="my_map.txt", palettes="my_pal.txt")
# from memory: open_graphics(data=open("GRAPHICS.DAT","rb").read())

len(g)                  # number of items
list(g)                 # [(index, type_name), ...]
g.info(i)               # {type, name, palette, sizes, compressed}

g.image(i)              # image/FNT1 -> (w, h, rgb_bytes)
g.image_indexed(i)      # -> (w, h, palette_indices) for your own coloring
g.to_pil(i)             # -> PIL.Image (needs Pillow)
g.font()                # finds FNT1 -> (w, h, rgb_bytes)
g.sound(i)              # SND1/SND2/SND3 -> signed 8-bit PCM
g.text(i)               # TXT1/TXT2 -> [str]
g.layout()              # LAY1 -> {'signature','ranges','records'} (relative coords)
g.endian / g.variant    # detected byte order ('>'/'<') and version key
g.first_of_type("LAY1") /  g.indices_of_type("SND3")

To dump everything into a folder of PNG/WAV/TXT plus an HTML contact sheet, see the example app (it consumes the library):

python3 examples/dump_graphics.py GRAPHICS.DAT gfx/
# optional overrides: ... gfx/ my_graphics_map palettes.txt zones.txt

Many Thanks to ChristopheF & Sphenx

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dmcsb-0.2.1.tar.gz (84.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dmcsb-0.2.1-py3-none-any.whl (82.1 kB view details)

Uploaded Python 3

File details

Details for the file dmcsb-0.2.1.tar.gz.

File metadata

  • Download URL: dmcsb-0.2.1.tar.gz
  • Upload date:
  • Size: 84.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for dmcsb-0.2.1.tar.gz
Algorithm Hash digest
SHA256 66e24f6d288b8921cb59dda907b43d64df5da7770116b979a41e33f9491785d8
MD5 18635b4b0ec456ff614e39b07e5337dd
BLAKE2b-256 774e8ecee172818c5068e2fff749e685a12bf733bc3856eaf9ab1e95bfa887f2

See more details on using hashes here.

File details

Details for the file dmcsb-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: dmcsb-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 82.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for dmcsb-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 3e14e7a3463b662029dd1a5ad147dff94e8adb13fb3343927f5761c5aa1e7016
MD5 b1675a0d2c0398439b5aed5cf6cc39c5
BLAKE2b-256 697e7cc69aed655f6fb686bb17f0f9d866e1640c9186c3db87e49223f42475dc

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page