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.0.tar.gz (83.4 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.0-py3-none-any.whl (81.9 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: dmcsb-0.2.0.tar.gz
  • Upload date:
  • Size: 83.4 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.0.tar.gz
Algorithm Hash digest
SHA256 800c3d9b993f736bc2039e04ced83710b6fae72339addfacc909dbadd3e4ff25
MD5 1c5ef9e54b354ca2c365b537e7acf69b
BLAKE2b-256 a9441c6566a92a10063351286406fb54654d22d6184d754926d810209d983b5f

See more details on using hashes here.

File details

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

File metadata

  • Download URL: dmcsb-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 81.9 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.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9f9067b98b8dc6b6cf2a1fe998c39ceddb25b9608544d62a6c1c49fefd5c5828
MD5 4a571af321d108d16a3fbbf72bdbe8a3
BLAKE2b-256 d606610be0655f50e9b609aa0800ad3f1dca09bd532fb3ef50351ab7158a9955

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