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 dmcsb — pip 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 dictmaps[]/mapsinfo[str(level)]-> map descriptors (in file order / keyed by Level)thinglist[]-> 16 entries in storage order (Nonefor 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 typelocated_things(level)-> things on a level with their map(x, y)+ sub-square cell (dirN/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), withx,y;champion(offset)decodes onetile_data-> raw tile bytes;tile_grid(level)-> 2-D tile arraytext_data-> raw encoded dungeon textchksum-> trailing checksum word, orNoneif 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
Metadata
Release files for dmcsb 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| dmcsb-0.2.1.tar.gz | 84.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| dmcsb-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 166.2 kB
Release files / dmcsb-0.2.1.tar.gz
| Download URL | dmcsb-0.2.1.tar.gz |
|---|---|
| Size | 84.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
66e24f6d288b8921cb59dda907b43d64df5da7770116b979a41e33f9491785d8
|
|
BLAKE2b-256 checksum How to use checksums |
774e8ecee172818c5068e2fff749e685a12bf733bc3856eaf9ab1e95bfa887f2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.5
|
Release files / dmcsb-0.2.1-py3-none-any.whl
| Download URL | dmcsb-0.2.1-py3-none-any.whl |
|---|---|
| Size | 82.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3e14e7a3463b662029dd1a5ad147dff94e8adb13fb3343927f5761c5aa1e7016
|
|
BLAKE2b-256 checksum How to use checksums |
697e7cc69aed655f6fb686bb17f0f9d866e1640c9186c3db87e49223f42475dc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.5
|