Last active
August 29, 2015 14:16
-
-
Save tehmaze/911e0aa8141920fc5919 to your computer and use it in GitHub Desktop.
C64 PRG to XBIN converter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# | |
# (c) 2015 Wijnand Modderman-Lenstra, https://maze.io/ | |
# | |
# Convert a C64 memory dump (VICE) to eXtended Binary (XBIN) | |
# | |
# To capture a dump in VICE, attach to the monitor, and run the following:: | |
# > $01 $35 | |
# save "dump.ram" 0 $0000 $FFFF | |
# | |
# Now, using dump.ram, you can invoke this script and get an XBIN. Jay. | |
# | |
# Thanks to the folks over at NURDspace https://nurdspace.nl/ for explaining | |
# the C64 and VIC-II memory layout. | |
import argparse | |
import os | |
import struct | |
import sys | |
# VICE chargen ROM | |
# FIXME should actually read the chargen ROM from the C64 memory map, but for | |
# now this will do as none of my PRGs have custom fonts | |
chargen = os.path.expanduser('~/.vice/C64/chargen') | |
# http://www.pepto.de/projects/colorvic/ | |
colors = [ | |
0x000000, 0xffffff, 0x68372b, 0x70a4b2, 0x6f3d86, 0x588d43, 0x352879, 0xb8c76f, | |
0x6f4f25, 0x433900, 0x9a6759, 0x444444, 0x6c6c6c, 0x9ad284, 0x6c5eb5, 0x959595 | |
] | |
def load_dump(filename): | |
with open(filename, 'rb') as fp: | |
data = bytearray(fp.read()) | |
# VICE dumps include the loader address | |
if len(data) == 0x10002 and data[0] == 0x00 and data[1] == 0x00: | |
data = data[2:] | |
assert len(data) == 0x10000, 'not a 64k dump' | |
return data | |
def run(): | |
parser = argparse.ArgumentParser() | |
parser.add_argument('ram', nargs=1) | |
args = parser.parse_args() | |
dump = load_dump(args.ram[0]) | |
base = os.path.splitext(os.path.basename(args.ram[0]))[0] | |
# http://codebase64.org/doku.php?id=base:vicii_memory_organizing | |
print '$d018 = {:08b}'.format(dump[0xd018]) | |
bitmap = ((dump[0xd018] >> 3) & 0b1) * 0x2000 | |
charmemp = ((dump[0xd018] >> 1) & 0b111) * 0x0800 | |
charmem = 0xd800 | |
screenmem = ((dump[0xd018] >> 4) & 0b1111) * 0x0400 | |
if charmemp == 0x1000: | |
case = 'upper' | |
elif charmemp == 0x1800: | |
case = 'mixed' | |
else: | |
case = 'unknown' | |
print ' bitmap ${:04x}'.format(bitmap) | |
print ' charmem ${:04x}'.format(charmem) | |
print ' charmem ${:04x} ({} case)'.format(charmemp, case) | |
print ' screenmem ${:04x}'.format(screenmem) | |
print '$dd00 = {:08b}'.format(dump[0xdd00]), | |
bank = [3, 2, 1, 0][dump[0xdd00] & 3] | |
print '-> bank{}'.format(bank) | |
# Dump text map, 40x25 characters in screenmem | |
textmap = [] | |
for offset in xrange(0x0000, 0x03e8, 40): | |
p = screenmem + offset | |
t = dump[p:p + 40] | |
textmap.append(t) | |
# Dump color map, 40x25 characters, 3 bits per color | |
background = dump[0xd021] & 0xf | |
colormap = [] | |
for offset in xrange(0x0000, 0x03e8, 40): | |
p = charmem + offset | |
c = dump[p:p + 40] | |
colormap.append(map(lambda x: x & 0xf, c)) | |
# Let's make an XBin | |
xbin = bytearray('XBIN\x1a') # ID + EOF char | |
xbin.extend(struct.pack('<HH', 40, 25)) # Width and Height | |
xbin.extend(struct.pack('<B', 8)) # FontSize 8x256 | |
xbin.extend(struct.pack('<B', | |
0b00001011, # Nonblink, Palette & Font | |
)) # Flags | |
for color in colors: | |
r = (color >> 16) & 0xff | |
g = (color >> 8) & 0xff | |
b = (color >> 0) & 0xff | |
xbin.extend(struct.pack('<BBB', r >> 2, g >> 2, b >> 2)) | |
with open(chargen, 'rb') as fp: | |
if case == 'mixed': # skip first 256 glyphs | |
fp.read(2048) | |
xbin.extend(fp.read(2048)) | |
for h in range(25): | |
for w in range(40): | |
xbin.append(textmap[h][w]) | |
xbin.append(colormap[h][w] | (background << 4)) | |
# SAUCE stuff | |
size = len(xbin) | |
xbin.extend('\x1aSAUCE00') | |
xbin.extend('\x00' * 35) # Title | |
xbin.extend('\x00' * 20) # Author | |
xbin.extend('\x00' * 20) # Group | |
xbin.extend('19700101') # Date | |
xbin.extend(struct.pack('<I', size)) | |
xbin.extend(struct.pack('<B', 6)) # DataType XBin | |
xbin.extend(struct.pack('<B', 0)) # FileType None | |
xbin.extend(struct.pack('<HHHH', 8, 25, 0, 0)) # Char Width, Lines, N/A, N/A | |
xbin.extend(struct.pack('<B', 0)) # Comments | |
xbin.extend(struct.pack('<B', 0b00001001)) # Legacy Ratio, iCE Colors | |
xbin.extend('\x00' * 22) # TInfoS | |
print 'saving to {}.xb'.format(base) | |
with open(base + '.xb', 'wb') as fp: | |
fp.write(xbin) | |
if __name__ == '__main__': | |
sys.exit(run()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment