Reverse‑engineering utilities and map renderer for Digital Devil Story: Megami Tensei (Nihon Telenet) on the NEC PC‑88.
This repo contains three scripts and a set of CSV tables that together parse game data from RAM dumps and render readable, annotated PNG maps. Already rendered maps included.
enemy_table_parser_0.1.8.py— parses the 512‑byte enemy table from RAM dumps and emits FULL and UNIQUE CSVs. Also optionally dumps the raw 64‑byte per‑map banks.teleport_table_parser_0.1.3.py— extracts the teleport/exits table from RAM dumps and writes a normalized CSV (source/destination world/map + 64×64 coordinates).ddsmt_map_dumper_0.3.py— renders 32×32 tile maps to PNG with overlays: exits/teleports (numbered), points of interest, items, and a right‑hand info block.
All three scripts are pure Python (3.8+) and have no third‑party dependencies.
data/
DDSMT_enemies_table.csv # Parsed enemy slots per map (FULL)
DDSMT_enemy_ID.csv # Enemy ID → name
DDSMT_object_ID.csv # Collision/object byte → name, class, color (for rendering)
DDSMT_teleport_table.csv # Normalized teleports/exits
DDSMT_world_ID.csv # World ID → name
DSSMT_pointsofinterest.csv # Optional POIs to overlay on maps
*.py # The three scripts
See
docs/DUMP_FORMAT.mdfor the RAM dump structure, coordinate systems, and table formats.
- Python: 3.8+ (no extra packages).
- Put your RAM dumps (
*.bin) into a folder alongside script and your tables in "/data". - Run the tools (examples below). Outputs go to CSV/PNG folders next to the scripts by default.
# 1) Enemy table → CSVs (FULL + UNIQUE), optional 64B bank dumps
python enemy_table_parser_0.1.8.py --glob "*.bin" --outdir-csv out_csv --outdir-banks enemy_table
# 2) Teleports/exits → CSV
python teleport_table_parser_0.1.3.py dumps/*.bin --out CSV --banks teleport_tables
# 3) Render maps (32×32) with teleports + POIs + items
python ddsmt_map_dumper_0.3.py --glob "*.bin" --teleport-csv data/DDSMT_teleport_table.csv --poi-csv data/DSSMT_pointsofinterest.csv --outdir-png map_pngsIf you have just a single dump file:
python ddsmt_map_dumper_0.3.py files w01_m01_malkuth.binParses an enemy table (512 bytes total) from one or many binary dumps of Digital Devil Story: Megami Tensei (PC‑88) and produces two CSV reports:
- FULL CSV — every slot on every map (bank) with raw hex attributes and an optional NAME lookup.
- UNIQUE CSV — de‑duplicated enemy variations (by the 4‑byte tuple ID/UNKNOWN/HP/SPEED), with counts and the list of levels (wXXmYY) where each variation appears. Additionally, it can dump the raw 64‑byte per‑map banks to a folder for archival / inspection.
- The enemy table occupies 512 bytes consisting of 8 banks × 64 bytes; each bank has 16 slots × 4 bytes.
- The 4 bytes per slot are interpreted as: ID, UNKNOWN, HP, SPEED (all shown as two‑digit hex).
- The table is located either:
- exactly the file size (== 512 bytes), or
- at offset 0xEE00 inside a larger dump (preferred), or
- otherwise we fallback to the last 512 bytes of the file (when
--no-offset-preferis used).
- The world index (1..20) is read from absolute byte 0x422 of the input file when available; otherwise it is
inferred from a filename token like
wNN(e.g.,w03m02.bin).
-
FULL CSV (default:
DDSMT_enemies_table_candidate.csv) with columns:mob_ID, on W, on M, mob_slot_on_map, ID, UNKNOWN, HP, SPEED, NAMENotes:
mob_IDis an auto‑incremented labelNNNN_mob_ID.on Wandon Mare zero‑padded world/map indices (e.g.,01,08).mob_slot_on_mapis 1..16 (human‑friendly slot number on the 64‑byte bank).NAMEis looked up from--names-csv(a simpleNAME,IDmapping); empty if not found.
-
UNIQUE CSV (default:
DDSMT_unique_enemies_candidate.csv) with columns:NAME, variations, ID, UNKNOWN, HP, SPEED, instances, exists on levelsNotes:
variationsprints the hex tuple as four space‑separated bytes.instancesis the total number of occurrences across all files/maps/slots.exists on levelslists the levels likew01m01,w01m02,...sorted by world then map.
-
64‑byte bank dumps (optional) saved as
enemy_table/wXXmYY.binfor each map YY = 1..8 of world XX. Enable/disable via--no-save-banks(banks are saved by default).
Parse all *.bin in the current directory and write both CSVs (also dump 64B banks by default):
python enemy_table_parser_0.1.8.py
Recurse into subfolders and include entries with ID==0: python enemy_table_parser_0.1.8.py --recursive --include-zero
Prefer the last 512 bytes instead of slicing at 0xEE00: python enemy_table_parser_0.1.8.py --no-offset-prefer
Use a custom names table and output locations: python enemy_table_parser_0.1.8.py --names-csv data/DDSMT_enemy_ID.csv --csv out_full.csv --unique-csv out_unique.csv
Only write CSVs without saving per‑map bank dumps: python enemy_table_parser_0.1.8.py --no-save-banks
--glob "*.bin": glob for input files (default:*.bin).--recursive: search subdirectories as well.--include-zero: keep entries where ID == 0 (they are omitted by default).--no-offset-prefer: do not prefer the 0xEE00 slice; when a file is larger than 512 bytes, take the last 512 bytes.--csv PATH: FULL CSV output path (default:DDSMT_enemies_table_candidate.csv).--unique-csv PATH: UNIQUE CSV output path (default:DDSMT_unique_enemies_candidate.csv).--bank-outdir DIR: where to writewXXmYY.bin(default:enemy_table/).--no-save-banks: disable saving 64‑byte banks.--names-csv PATH: CSV for name lookups.
For each processed file the script prints how the world was determined:
dump@0x422— read from absolute byte 0x422 of the file,filename token— extracted from the file name (e.g.,w12),fallback— unknown (treated as 0).
- Raises on undersized files (smaller than 512 bytes when slicing).
- Any other parsing problems are reported per‑file and do not stop batch processing.
- Python 3.8+ recommended. No third‑party dependencies.
Extracts a single 512‑byte teleport table (E3/E4 banks) from one or many binary dumps of
Digital Devil Story: Megami Tensei (PC‑88) and writes one consolidated CSV with clean, minimal columns.
By default, the raw 512‑byte blocks are also dumped to teleport_tables/wNN.ddstt for archival/verification.
Use --nodump to skip writing these raw block files.
The output CSV contains the following columns (left‑to‑right):
tele_id, src_w, src_m, dest_w, dest_m, src_x_coords, src_y_coords, dest_x_coords, dest_y_coords
tele_id: auto‑incremented label per row, formattedNNNN_tele_id.src_w: world index 1..20 inferred from the input file (prefer byte 0x422, fallback to filename tokenwNN).src_m: level index 1..8 (partition order).- Coordinates are decimal 0..63; values outside this range raise an error.
- If
--offsetis provided, it is used for all files. - If
--offsetis omitted and the file is exactly 512 bytes, offset 0 is used. - Otherwise, the default offset is 0xE300.
Single file (exactly 512 bytes; offset optional): python teleport_table_parser_0.1.3.py w1.bin --csv out.csv
Single file with explicit offset in a larger dump: python teleport_table_parser_0.1.3.py bigdump.bin --offset 0xE300 --csv out.csv
Batch mode over current directory (and subfolders with --recursive):
python teleport_table_parser_0.1.3.py --glob "*.bin" --recursive --offset 0xE300 --csv out.csv
Default raw block dumps (512 bytes each) to teleport_tables/ next to the script:
python teleport_table_parser_0.1.3.py
Skip dumping raw blocks: python teleport_table_parser_0.1.3.py --nodump
- Partition header (first 16 bytes): 8 pairs of
[ptr][bank]. For bank0xE3the address isptr, for bank0xE4the address isptr + 0x100. - At each address,
block[addr]holds the count of 8‑byte entries that follow for that level. - Each 8‑byte teleport record encodes:
dest_w,dest_m,src_x,src_y,dest_x,dest_y. - The script stitches all level partitions together into CSV rows in source level order
src_m = 1..8. src_wis determined from byte0x422when possible; otherwise from the file name tokenwNN.
- Prints a
[SRC_W]line for each file explaining howsrc_wwas determined. - On structural problems (wrong size/offset, bad partition address, out‑of‑range coordinates),
the script prints
[ERROR] <file>: <reason>and continues to the next file. - Final line announces success with total rows written.
- Python 3.8+ recommended. No third‑party dependencies.
This script renders maps for Digital Devil Story: Megami Tensei (PC-88) from RAM dumps. It produces PNG images with a three-part layout:
- Header: " - " and a gray subheader "wXXmYY".
- Map body: 32×32 collision-colored tiles, with overlays:
- Red highlights for teleports, numbered 1..N (clustered or raw).
- Colored squares for POIs (points of interest).
- Info block: Enemies (xN NAME), Items (colored square + "xN - NAME"), Exits ("→ wXXmYY (x, y)"), and optional POI list.
- RAM dumps (.bin). World/Map IDs are read from bytes 0x422/0x423 when available, with fallback to filename tokens (wNN, mNN).
- CSV tables from the
data/folder (overridable via CLI flags):DDSMT_object_ID.csv— object bytes → name, class, RGB (supports 0..1 or 0..255).DDSMT_teleport_table.csv— teleport links (src/dest world, map, coords).DDSMT_world_ID.csv— world_id → world_name.DDSMT_enemies_table.csv— rows mapping enemies to maps (counted per row).DSSMT_pointsofinterest.csv— optional POI entries (world,map,x,y,name).
- Normal mode: one PNG per dump into
map_pngs/(teleport numbers ON), plus optional BIN slices intomap_dumps/. - Debug mode (
--debug): two PNGs intomap_debug_pngs/— collision view and tilemap view — with grid + hex overlays. - PNGs may include coordinate grid (
--coord) and uncluttered clustering can be disabled (--noclean).
Examples: $ python ddsmt_map_dumper.py $ python ddsmt_map_dumper.py --coord --noclean $ python ddsmt_map_dumper.py --debug
Switches: --debug Produce debug PNGs (collision + tilemap). --coord Overlay 00..31 grid/coords in normal mode. --noclean Do not cluster adjacent teleports. --no-png Do not render PNGs. --no-bin Do not save BIN slices. --no-color Disable ANSI colors in console logs. --teleport-csv PATH Override teleport table CSV path. --world-csv PATH Override world names CSV path. --enemies-csv PATH Override enemies table CSV path. --poi-csv PATH Override POI CSV path. --outdir-png DIR PNG output directory (default: map_pngs). --outdir-bin DIR BIN output directory (default: map_dumps).
- Teleport coordinates (64×64) are mapped to 32×32 tiles via ceil-division and axis swap.
- Exits located on walls (0xC0) are skipped with a warning.
- Adjacent door tiles 0x41/0x42 are merged into a single "Door" entry.
- POI overlay colors are derived from object table rows where class == "POI".
The data/ directory includes curated CSVs used by the tools:
DDSMT_enemy_ID.csv— two columns: enemyNAME,ID(hex without0x).DDSMT_world_ID.csv—ID,WorldName(e.g.,01,Malkuth..14).DDSMT_object_ID.csv—byte,name,class,colorused by the map renderer to color tiles/items and label the info block.DSSMT_pointsofinterest.csv— POI rows (ID,class,on W,on M,x,y,Line1,Translation1,Line2,Translation2) that overlay onto rendered maps.DDSMT_enemies_table.csv— FULL enemy slots per map emitted by the parser.DDSMT_teleport_table.csv— normalized exits (src/dst world+map, 64×64 coords).
Details and column descriptions are in docs/DUMP_FORMAT.md.
All important “magic numbers” (offsets, strides, table sizes, coordinate transforms) live in each script’s Config section and are documented in docs/DUMP_FORMAT.md.
- Found a mistake in IDs, offsets, or coordinates? Open an issue with a dump snippet and your reasoning.
- PRs welcome for: new dumps, verified offsets, better color legends, or more POIs.
MIT (see LICENSE if included). The original game assets remain the property of their respective rights holders and are referenced here only for research and preservation purposes.
