/emu_65c02/cg_default/*.ppm
/galaxian/alien_typhoon.asm
/galaxian/galaxian.asm
+/galaxian/galaxian_shape.json
+/galaxian/galaxian_shape.png
/orig/APPLE Computer and Peripheral Card Roms Collection.zip
/orig/Alien_Typhoon_1981_Starcraft.do
/orig/Apple_DOS_v3.3_1980_Apple.do
.PHONY: all
all: \
+galaxian_shape.png \
galaxian.asm \
alien_typhoon.asm \
+galaxian_shape.png: galaxian_shape.json
+ ./shape_extract_png.py $^ $@
+
+galaxian_shape.json: galaxian.txt ../loader/galaxian.ihx
+ ./shape_extract.py $^ $@
+
galaxian.asm: \
galaxian_trace.txt \
galaxian.txt \
*.o \
*.rel \
*.rst \
+galaxian_shape.png \
+galaxian_shape.json \
galaxian.asm \
alien_typhoon.asm
0x3fd0,0x0028,VIDEO_LINE_BF,byte
0x486e,0x0001,velocity_y,byte
0x4870,0x0001,velocity_x_lo,byte
-0x7280,0x00d2,shape_ptr_lo_7280,byte
-0x7480,0x00d2,shape_ptr_lo_7480,byte
-0x7600,0x00d2,shape_ptr_hi_7600,byte
-0x7800,0x00d2,shape_ptr_hi_7800,byte
-0x7980,0x00d2,shape_width_bytes_7980,byte
-0x7b80,0x00d2,shape_width_bytes_7b80,byte
-0x7d80,0x0053,shape_height_bytes_7d80,byte
+0x7280,0x0380,shape_data_ptr_lo,byte
+0x7600,0x0380,shape_data_ptr_hi,byte
+0x7980,0x0380,shape_width_bytes,byte
+0x7d80,0x0080,shape_height,byte
0x8cf1,0x0008,y_8cf1,byte
0x8d01,0x0008,velocity_hi_8d01,byte
0x8d11,0x0008,x_hi_8d11,byte
0x93de,0x0001,,code_ign # accessing video_line_table before clipping y
# returns a = quotient, y = remainder, cf = 0
0x93f6,0x0001,div_a_by_7,code
-# the value from shape_ptr_lo/hi is relative to this value
-0x9414,0x0002,shape_ptr_base,word,byte
+# the value from shape_data_ptr_lo/hi is relative to this value
+0x9414,0x0002,shape_data_ptr_base,word,byte
0x9416,0x0007,x2_mod7_table,byte
0x941d,0x0007,x2_div7_table,byte
# these contain the pointer for shift count 0, shape 0
# the byte accessed will be base + shift count * 0x80 + shape
-0x9424,0x0002,shape_ptr_lo_base,word,byte
-0x9426,0x0002,shape_ptr_hi_base,word,byte
+0x9424,0x0002,shape_data_ptr_lo_base,word,byte
+0x9426,0x0002,shape_data_ptr_hi_base,word,byte
0x9428,0x0002,shape_width_bytes_base,word,byte
# takes x = 0 draw, x = 1 erase
# reads draw_erase_(x0|y0|shape)
--- /dev/null
+#!/usr/bin/env python3
+
+import json
+import sys
+from intelhex import IntelHex
+
+EXIT_SUCCESS = 0
+EXIT_FAILURE = 1
+
+pixel = False
+if len(sys.argv) < 4:
+ print(f'usage: {sys.argv[0]:s} addrs.txt in.ihx out.json')
+ sys.exit(EXIT_FAILURE)
+addrs_txt = sys.argv[1]
+in_ihx = sys.argv[2]
+out_json = sys.argv[3]
+
+print('reading addrs')
+shape_tables = {}
+shape_data_ptr_base = -1
+shapes = {}
+with open(addrs_txt) as fin:
+ def get_line():
+ while True:
+ line = fin.readline()
+ if len(line) == 0:
+ return []
+ i = line.find('#')
+ if i >= 0:
+ line = line[:i]
+ fields = line.strip().split(',')
+ if fields != ['']:
+ #print('fields', fields)
+ return fields
+
+ fields = get_line()
+ while len(fields):
+ assert len(fields) == 1
+ section = fields[0]
+ print(section)
+
+ if section == 'items':
+ fields = get_line()
+ while len(fields) >= 2:
+ assert len(fields) >= 4
+ addr = int(fields[0], 0)
+ size = int(fields[1], 0)
+ name = fields[2]
+ _type = fields[3]
+ if _type == 'byte' and name[:6] == 'shape_':
+ shape_tables[name[6:]] = (addr, size // 0x80)
+ elif _type == 'word' and name == 'shape_data_ptr_base':
+ shape_data_ptr_base = addr
+ fields = get_line()
+ continue
+
+ if section == 'shapes':
+ fields = get_line()
+ while len(fields) >= 2:
+ assert len(fields) == 2
+ index = int(fields[0], 0)
+ name = fields[1]
+ shapes[index] = name
+ fields = get_line()
+ continue
+
+ # unknown section, skip
+ fields = get_line()
+ while len(fields) >= 2:
+ fields = get_line()
+assert shape_data_ptr_base != -1
+
+print('reading ihx')
+intelhex = IntelHex(in_ihx)
+segments = [j for i in intelhex.segments() for j in i]
+for i in range(0, len(segments), 2):
+ print(f'[{segments[i]:04x}, {segments[i + 1]:04x})')
+
+print('extracting')
+data = []
+for i in range(0x80):
+ name = f'shape_{i:02x}'
+ if i in shapes:
+ name += '_' + shapes[i]
+ data.append({'name': name})
+
+i = 0
+shape_tables1 = list(shape_tables.items())
+while i < len(shape_tables1):
+ if (
+ i + 2 <= len(shape_tables1) and
+ shape_tables1[i][0][-3:] == '_lo' and
+ shape_tables1[i + 1][0][-3:] == '_hi'
+ ):
+ table, (addr_lo, shifts) = shape_tables1[i]
+ _, (addr_hi, _) = shape_tables1[i + 1]
+ table = table[:-3]
+ for j in range(0x80):
+ value = [
+ intelhex[addr_lo + j + k * 0x80] |
+ (intelhex[addr_hi + j + k * 0x80] << 8)
+ for k in range(shifts)
+ ]
+ data[j][table] = [f'0x{k:04x}' for k in value]
+ i += 2
+ else:
+ table, (addr, shifts) = shape_tables1[i]
+ for j in range(0x80):
+ value = [intelhex[addr + j + k * 0x80] for k in range(shifts)]
+ data[j][table] = [str(k) for k in value]
+ i += 1
+
+for i in range(0x80):
+ height = int(data[i]['height'][0], 0)
+ addr0 = int(data[i]['data_ptr'][0], 0)
+
+ # only try to extract if pointers are sensible
+ data1 = None
+ addr = addr0
+ for j in range(6):
+ width_bytes = int(data[i]['width_bytes'][j], 0)
+ addr += width_bytes * height
+ if addr != int(data[i]['data_ptr'][j + 1], 0):
+ break
+ else:
+ # good to go
+ data1 = []
+ addr = (
+ addr0 +
+ intelhex[shape_data_ptr_base] +
+ (intelhex[shape_data_ptr_base + 1] << 8)
+ )
+ for j in range(7):
+ width_bytes = int(data[i]['width_bytes'][j], 0)
+ data1.append(
+ [
+ [
+ f'0x{intelhex[addr + k * width_bytes + l]:02x}'
+ for l in range(width_bytes)
+ ]
+ for k in range(height)
+ ]
+ )
+ addr += width_bytes * height
+ data[i]['data'] = data1
+
+print('writing json')
+with open(out_json, 'w') as fout:
+ json.dump(data, fout, indent = 2)
--- /dev/null
+#!/usr/bin/env python3
+
+import json
+import numpy
+import sys
+import PIL.Image
+
+EXIT_SUCCESS = 0
+EXIT_FAILURE = 1
+
+PITCH_X = 64
+PITCH_Y = 32
+
+# see palette.py
+PALETTE = numpy.array(
+ [
+ [0x00, 0x00, 0x00],
+ [0xbc, 0x00, 0x89],
+ [0x00, 0x00, 0xbc],
+ [0xbc, 0x00, 0xe1],
+ [0x00, 0xbc, 0x89],
+ [0x80, 0x80, 0x80], #[0xbc, 0xbc, 0xbc],
+ [0x00, 0xbc, 0xe1],
+ [0xbc, 0xbc, 0xff],
+ [0xbc, 0xbc, 0x00],
+ [0xff, 0xbc, 0x89],
+ [0xc0, 0xc0, 0xc0], #[0xbc, 0xbc, 0xbc],
+ [0xff, 0xbc, 0xe1],
+ [0xbc, 0xff, 0x89],
+ [0xff, 0xff, 0xbc],
+ [0xbc, 0xff, 0xe1],
+ [0xff, 0xff, 0xff],
+ ],
+ numpy.uint8
+)
+
+if len(sys.argv) < 3:
+ print(f'usage: {sys.argv[0]:s} in.json out.png')
+ sys.exit(EXIT_FAILURE)
+in_json = sys.argv[1]
+out_png = sys.argv[2]
+
+print('reading json')
+with open(in_json) as fin:
+ data = json.load(fin)
+
+image_out = numpy.zeros((PITCH_Y * 16, PITCH_X * 8), numpy.uint8)
+
+for i in range(0x80):
+ j = i & 7
+ k = i >> 3
+ x = j * PITCH_X
+ y = k * PITCH_Y
+ bg = 0xa if (j ^ k) & 1 else 5
+ image_out[y:y + PITCH_Y, x:x + PITCH_X] = bg
+
+ data1 = data[i]['data']
+ if data1 is not None:
+ shape = numpy.array(
+ [[int(k, 0) for k in j] for j in data1[0]],
+ numpy.uint8
+ )
+ ys = shape.shape[0]
+ xs = shape.shape[1] * 7
+
+ # extract high bits, check consistent on each line
+ hibit = (shape >> 7).astype(bool)
+ assert numpy.all(hibit == hibit[:, :1])
+ hibit = hibit[:, 0]
+
+ # extract data bits
+ shape = (
+ (
+ shape[:, :, numpy.newaxis] >>
+ numpy.arange(
+ 7,
+ dtype = numpy.int32
+ )[numpy.newaxis, numpy.newaxis, :]
+ ) & 1
+ ).astype(bool).reshape((ys, xs))
+
+ # check remaining (shifted) shapes are as we expect
+ for j in range(1, 7):
+ shape1 = numpy.array(
+ [[int(l, 0) for l in k] for k in data1[j]],
+ numpy.uint8
+ )
+ ys1 = shape1.shape[0]
+ assert ys1 == ys
+ xs1 = shape1.shape[1] * 7
+ assert xs1 >= xs
+
+ # extract high bits, check consistent on each line
+ hibit1 = (shape1 >> 7).astype(bool)
+ assert numpy.all(hibit1 == hibit1[:, :1])
+ hibit1 = hibit1[:, 0]
+ assert numpy.all(hibit1 == hibit)
+
+ # extract data bits
+ shape1 = (
+ (
+ shape1[:, :, numpy.newaxis] >>
+ numpy.arange(
+ 7,
+ dtype = numpy.int32
+ )[numpy.newaxis, numpy.newaxis, :]
+ ) & 1
+ ).astype(bool).reshape((ys1, xs1))
+
+ # compare data bits
+ k = min(xs, xs1 - j)
+ assert not numpy.any(shape[:, k:])
+ assert not numpy.any(shape1[:, :j])
+ assert numpy.all(shape1[:, j:j + k] == shape[:, :k])
+ assert not numpy.any(shape1[:, j + k:])
+
+ # double the pixels and apply the shift by hibit value
+ shape1 = numpy.zeros((ys * 2, xs * 2 + 1), bool)
+ for j in range(ys):
+ k = int(hibit[j])
+ shape1[j * 2:j * 2 + 2, k:-1:2] = shape[j:j + 1, :]
+ shape1[j * 2:j * 2 + 2, k + 1::2] = shape[j:j + 1, :]
+ shape = shape1
+ ys = ys * 2
+ xs = xs * 2 + 1
+
+ image_out[y:y + ys, x:x + xs] = shape * 0xf
+
+print('writing png')
+image_out_pil = PIL.Image.new(
+ 'P',
+ (image_out.shape[1], image_out.shape[0]),
+ None
+)
+image_out_pil.frombytes(image_out.tobytes())
+image_out_pil.putpalette(list(PALETTE.reshape((0x30,))))
+image_out_pil.save(out_png)