mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-10-30 12:03:54 +02:00
114 lines
4.2 KiB
Python
Executable File
114 lines
4.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Reconstruct the VT1200 X-server image from the raw EPROM dumps.
|
|
|
|
The VT1200 firmware is held in 14 x 128 KiB (27C010) byte-wide EPROMs per
|
|
firmware revision. The EPROMs are wired as 16-bit words: consecutive part
|
|
numbers form a pair, and within a pair the ODD-numbered part holds the low
|
|
byte and the EVEN-numbered part holds the high byte of the word.
|
|
|
|
Example for v2.1:
|
|
pair (23-210E9, 23-211E9) -> 256 KiB bank, 210E9 = high byte,
|
|
211E9 = low byte
|
|
|
|
There are two possible orderings of the seven 256 KiB banks:
|
|
|
|
* ``linear`` (default) -- CPU *address* order. The ROM is top-aligned at
|
|
physical byte 0x07E40000, and the bank with the *highest* part numbers sits
|
|
at the lowest address. In this order file offset == physical byte - base,
|
|
which is what a disassembler/emulator wants.
|
|
* ``--part-order`` -- legacy ascending part-number order (bank 0 first).
|
|
|
|
The image is 1.75 MiB (0x1C0000) and can be fed to a TMS34010 disassembler
|
|
(e.g. smart34010's ``dis34`` or MAME's ``unidasm``) or loaded into an emulator
|
|
at base 0x07E40000. See MEMORY_MAP.md. Note the firmware stores *bit*
|
|
addresses (physical byte = bit >> 3).
|
|
|
|
Usage:
|
|
./reconstruct.py v2.2 # -> vt1200_v22.bin (address order)
|
|
./reconstruct.py v2.1 out.bin
|
|
./reconstruct.py v2.1 --part-order
|
|
"""
|
|
import re
|
|
import os
|
|
import glob
|
|
import argparse
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
# Physical byte address the ROM is mapped at; also the size of one image.
|
|
ROM_BASE = 0x07E40000
|
|
|
|
|
|
def load(dirname):
|
|
roms = {}
|
|
for path in sorted(glob.glob(os.path.join(HERE, dirname, "*.bin"))):
|
|
m = re.search(r"(\d+)E9", os.path.basename(path))
|
|
if not m:
|
|
continue
|
|
with open(path, "rb") as fh:
|
|
roms[int(m.group(1))] = fh.read()
|
|
return roms
|
|
|
|
|
|
def reconstruct(dirname, linear=True):
|
|
"""De-interleave the EPROMs of a revision.
|
|
|
|
Returns (parts, image) where parts is the ascending list of part numbers
|
|
and image is the reconstructed ROM. With linear=True the banks are placed
|
|
in CPU address order (file offset == physical byte - ROM_BASE).
|
|
"""
|
|
roms = load(dirname)
|
|
parts = sorted(roms)
|
|
if len(parts) % 2:
|
|
raise SystemExit(f"{dirname}: expected an even number of EPROMs, got {len(parts)}")
|
|
banks = []
|
|
for i in range(0, len(parts), 2):
|
|
even_pn, odd_pn = parts[i], parts[i + 1] # odd part = low byte
|
|
low, high = roms[odd_pn], roms[even_pn]
|
|
if len(low) != len(high):
|
|
raise SystemExit(f"{dirname}: size mismatch between {odd_pn} and {even_pn}")
|
|
img = bytearray(len(low) * 2)
|
|
img[0::2] = low
|
|
img[1::2] = high
|
|
banks.append(bytes(img))
|
|
if linear:
|
|
banks.reverse() # highest part number sits at the lowest address
|
|
return parts, b"".join(banks)
|
|
|
|
|
|
def main(argv=None):
|
|
ap = argparse.ArgumentParser(
|
|
description=__doc__,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
)
|
|
ap.add_argument("revision", help="ROM directory, e.g. v2.1 or v2.2")
|
|
ap.add_argument("output", nargs="?", help="output file (default vt1200_vNN.bin)")
|
|
ap.add_argument(
|
|
"--linear", dest="linear", action="store_true",
|
|
help="emit CPU address order (default): offset == physical byte - 0x07E40000",
|
|
)
|
|
ap.add_argument(
|
|
"--part-order", dest="linear", action="store_false",
|
|
help="emit legacy ascending part-number order",
|
|
)
|
|
ap.set_defaults(linear=True)
|
|
args = ap.parse_args(argv)
|
|
|
|
default = "vt1200_" + args.revision.replace(".", "") + ".bin"
|
|
out = args.output or os.path.join(HERE, default)
|
|
parts, img = reconstruct(args.revision, linear=args.linear)
|
|
with open(out, "wb") as fh:
|
|
fh.write(img)
|
|
|
|
order = "address" if args.linear else "part-number"
|
|
strings = len(re.findall(rb"[\x20-\x7e]{16,}", img))
|
|
print(f"{args.revision}: {len(parts)} EPROMs ({parts[0]}..{parts[-1]}) "
|
|
f"{order} order -> {out} ({len(img)} bytes = 0x{len(img):X}), "
|
|
f"{strings} text runs")
|
|
if args.linear:
|
|
print(f" load base: 0x{ROM_BASE:08X} (bit 0x{ROM_BASE * 8:08X})")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|