In /emu_z80 upgrade the emulator to /emu_65c02 from Star Blazer disassembly project...
authorNick Downing <nick@ndcode.org>
Fri, 1 Jul 2022 14:21:46 +0000 (00:21 +1000)
committerNick Downing <nick@ndcode.org>
Fri, 1 Jul 2022 14:24:51 +0000 (00:24 +1000)
12 files changed:
.gitignore
emu_z80/Makefile
emu_z80/cg_default/John Macdougall LowerCase Adapter (LCA) Alt-1.pdf [new file with mode: 0644]
emu_z80/cg_default/Makefile [new file with mode: 0644]
emu_z80/cg_default/cg_compile.py [new file with mode: 0755]
emu_z80/cg_default/cg_extract.py [new file with mode: 0755]
emu_z80/cg_default/cg_verify.py [new file with mode: 0755]
emu_z80/cg_default/signetics2513.pdf [new file with mode: 0644]
emu_z80/emu_z80.c
emu_z80/stty_sane.c [new file with mode: 0644]
emu_z80/stty_sane.h [new file with mode: 0644]
emu_z80/test.asm

index 9369270..c318fe7 100644 (file)
@@ -6,5 +6,9 @@
 *.o
 *.rel
 *.rst
+/emu_z80/cg_default/*.inc
+/emu_z80/cg_default/*.pbm
+/emu_z80/cg_default/*.png
+/emu_z80/cg_default/*.ppm
 /emu_z80/emu_z80
 /orig/pacman.zip
index ec9cf98..76f0956 100644 (file)
@@ -6,22 +6,132 @@ ASLINK=../asxv5pxx/asxmak/linux/exe/aslink
 
 # need to install intelhex package in Python first:
 #   pip3 install --user intelhex
-HEX2BIN=python3 $(HOME)/.local/bin/hex2bin.py
+HEX2BIN=hex2bin.py
 
 .PHONY: all
-all: emu_z80 test.bin
+all: \
+cg_default \
+emu_z80 \
+test.bin
+#apple_ii+_applesoft_rom.ihx \
+#Apple\ II+\ -\ 341-0011\ -\ Applesoft\ BASIC\ D000\ -\ 2716.bin \
+#Apple\ II+\ -\ 341-0012\ -\ Applesoft\ BASIC\ D800\ -\ 2716.bin \
+#Apple\ II+\ -\ 341-0013\ -\ Applesoft\ BASIC\ E000\ -\ 2716.bin \
+#Apple\ II+\ -\ 341-0014\ -\ Applesoft\ BASIC\ E800\ -\ 2716.bin \
+#Apple\ II+\ -\ 341-0015\ -\ Applesoft\ BASIC\ F000\ -\ 2716.bin \
+#apple_ii+_autostart_monitor_rom.ihx \
+#Apple\ II+\ -\ 341-0020\ -\ Applesoft\ BASIC\ Autostart\ Monitor\ F800\ -\ 2716.bin \
+#Apple\ II+\ -\ 7341-0036\ -\ Character\ Generator\ Rev7+\ -\ 2716.bin \
+#apple_iie_rom.ihx \
+#Apple\ IIe\ CD\ Enhanced\ -\ 342-0304-A\ -\ 2764.bin \
+#Apple\ IIe\ EF\ Enhanced\ -\ 342-0303-A\ -\ 2764.bin \
+#Apple\ IIe\ Video\ Enhanced\ -\ 342-0265-A\ -\ 2732.bin
 
-emu_z80: emu_z80.o z80/z80.o
-       $(CC) $(LDFLAGS) -o $@ $^
+.PHONY: cg_default
+cg_default:
+       # seems to try to build "make w -C cg_default", could be make bug?
+       #${MAKE} ${MAKEFLAGS} -C $@
+       ${MAKE} -C $@
+
+emu_z80: emu_z80.o stty_sane.o z80/z80.o
+       ${CC} ${LDFLAGS} -o $@ $^ -lSDL2 -lm
+
+emu_z80.o: cg_default
+
+#apple_ii+_applesoft_rom.ihx: \
+#Apple\ II+\ -\ 341-0011\ -\ Applesoft\ BASIC\ D000\ -\ 2716.bin \
+#Apple\ II+\ -\ 341-0012\ -\ Applesoft\ BASIC\ D800\ -\ 2716.bin \
+#Apple\ II+\ -\ 341-0013\ -\ Applesoft\ BASIC\ E000\ -\ 2716.bin \
+#Apple\ II+\ -\ 341-0014\ -\ Applesoft\ BASIC\ E800\ -\ 2716.bin \
+#Apple\ II+\ -\ 341-0015\ -\ Applesoft\ BASIC\ F000\ -\ 2716.bin
+#      ./rom2hex.py 0x11000 "\
+#Apple II+ - 341-0011 - Applesoft BASIC D000 - 2716.bin,\
+#Apple II+ - 341-0012 - Applesoft BASIC D800 - 2716.bin,\
+#Apple II+ - 341-0013 - Applesoft BASIC E000 - 2716.bin,\
+#Apple II+ - 341-0014 - Applesoft BASIC E800 - 2716.bin,\
+#Apple II+ - 341-0015 - Applesoft BASIC F000 - 2716.bin" \
+#$@
+#
+#Apple\ II+\ -\ 341-0011\ -\ Applesoft\ BASIC\ D000\ -\ 2716.bin: \
+#../orig/APPLE\ Computer\ and\ Peripheral\ Card\ Roms\ Collection.zip
+#      rm -f "$@"
+#      unzip "$<" "$@"
+#      touch "$@"
+#
+#Apple\ II+\ -\ 341-0012\ -\ Applesoft\ BASIC\ D800\ -\ 2716.bin: \
+#../orig/APPLE\ Computer\ and\ Peripheral\ Card\ Roms\ Collection.zip
+#      rm -f "$@"
+#      unzip "$<" "$@"
+#      touch "$@"
+#
+#Apple\ II+\ -\ 341-0013\ -\ Applesoft\ BASIC\ E000\ -\ 2716.bin: \
+#../orig/APPLE\ Computer\ and\ Peripheral\ Card\ Roms\ Collection.zip
+#      rm -f "$@"
+#      unzip "$<" "$@"
+#      touch "$@"
+#
+#Apple\ II+\ -\ 341-0014\ -\ Applesoft\ BASIC\ E800\ -\ 2716.bin: \
+#../orig/APPLE\ Computer\ and\ Peripheral\ Card\ Roms\ Collection.zip
+#      rm -f "$@"
+#      unzip "$<" "$@"
+#      touch "$@"
+#
+#Apple\ II+\ -\ 341-0015\ -\ Applesoft\ BASIC\ F000\ -\ 2716.bin: \
+#../orig/APPLE\ Computer\ and\ Peripheral\ Card\ Roms\ Collection.zip
+#      rm -f "$@"
+#      unzip "$<" "$@"
+#      touch "$@"
+#
+#apple_ii+_autostart_monitor_rom.ihx: \
+#Apple\ II+\ -\ 341-0020\ -\ Applesoft\ BASIC\ Autostart\ Monitor\ F800\ -\ 2716.bin
+#      ./rom2hex.py --reset-vector 0x13800 "$<" $@
+#
+#Apple\ II+\ -\ 341-0020\ -\ Applesoft\ BASIC\ Autostart\ Monitor\ F800\ -\ 2716.bin: \
+#../orig/APPLE\ Computer\ and\ Peripheral\ Card\ Roms\ Collection.zip
+#      rm -f "$@"
+#      unzip "$<" "$@"
+#      touch "$@"
+#
+#Apple\ II+\ -\ 7341-0036\ -\ Character\ Generator\ Rev7+\ -\ 2716.bin: \
+#../orig/APPLE\ Computer\ and\ Peripheral\ Card\ Roms\ Collection.zip
+#      rm -f "$@"
+#      unzip "$<" "$@"
+#      touch "$@"
+#
+#apple_iie_rom.ihx: \
+#Apple\ IIe\ CD\ Enhanced\ -\ 342-0304-A\ -\ 2764.bin \
+#Apple\ IIe\ EF\ Enhanced\ -\ 342-0303-A\ -\ 2764.bin
+#      ./rom2hex.py --reset-vector 0x20000 "\
+#Apple IIe CD Enhanced - 342-0304-A - 2764.bin,\
+#Apple IIe EF Enhanced - 342-0303-A - 2764.bin" \
+#$@
+#
+#Apple\ IIe\ CD\ Enhanced\ -\ 342-0304-A\ -\ 2764.bin: \
+#../orig/APPLE\ Computer\ and\ Peripheral\ Card\ Roms\ Collection.zip
+#      rm -f "$@"
+#      unzip "$<" "$@"
+#      touch "$@"
+#
+#Apple\ IIe\ EF\ Enhanced\ -\ 342-0303-A\ -\ 2764.bin: \
+#../orig/APPLE\ Computer\ and\ Peripheral\ Card\ Roms\ Collection.zip
+#      rm -f "$@"
+#      unzip "$<" "$@"
+#      touch "$@"
+#
+#Apple\ IIe\ Video\ Enhanced\ -\ 342-0265-A\ -\ 2732.bin: \
+#../orig/APPLE\ Computer\ and\ Peripheral\ Card\ Roms\ Collection.zip
+#      rm -f "$@"
+#      unzip "$<" "$@"
+#      touch "$@"
 
 test.bin: test.ihx
-       $(HEX2BIN) $< $@
+       ${HEX2BIN} $< $@
 
 test.ihx: test.rel
-       $(ASLINK) -n -m -u -i $@ $^
+       ${ASLINK} -n -m -u -i $@ $^
 
 test.rel: test.asm
-       $(ASZ80) -l -o $<
+       ${ASZ80} -l -o $<
 
 .PHONY: clean
 clean:
diff --git a/emu_z80/cg_default/John Macdougall LowerCase Adapter (LCA) Alt-1.pdf b/emu_z80/cg_default/John Macdougall LowerCase Adapter (LCA) Alt-1.pdf
new file mode 100644 (file)
index 0000000..0d6d2dd
Binary files /dev/null and b/emu_z80/cg_default/John Macdougall LowerCase Adapter (LCA) Alt-1.pdf differ
diff --git a/emu_z80/cg_default/Makefile b/emu_z80/cg_default/Makefile
new file mode 100644 (file)
index 0000000..9036464
--- /dev/null
@@ -0,0 +1,131 @@
+.PHONY: all
+all: \
+cg_signetics.inc \
+signetics1.png \
+cg_katakana.inc \
+katakana1.png \
+cg_lowercase_20.inc \
+lowercase_201.png \
+cg_lowercase_40.inc \
+lowercase_401.png \
+cg_lowercase_60.inc \
+lowercase_601.png
+
+# signetics 2513
+# diagrams have 5 * 8 cell characters at 8.75 * 10.33 cell spacing
+# reduce each cell down to 8 * 6 pixels
+# new diagram will have 40 * 48 pixel characters at 70 * 62 pixel spacing
+
+cg_signetics.inc: cg_signetics.png
+       ./cg_compile.py cg_signetics $< >$@
+
+signetics1.png: cg_signetics.png
+       ./cg_verify.py 70,62 8,6 5,8 $< $@
+
+cg_signetics.png: signetics0.png
+       ./cg_extract.py 70,62 8,6 5,8 .333 $< $@
+
+signetics0.png: signetics.pbm
+       convert $< \
+-grayscale Rec709Luminance \
+-distort Perspective \
+"396,543 0,0  1423,537 490,0  402,2136 0,434  1426,2131 490,434" \
+-crop 560x496+0+0 \
+$@
+
+signetics.pbm: signetics2513.pdf 
+       pdfimages -f 7 -l 7 $< a
+       mv a-000.pbm $@
+
+cg_katakana.inc: cg_katakana.png
+       ./cg_compile.py cg_katakana $< >$@
+
+katakana1.png: cg_katakana.png
+       ./cg_verify.py 70,62 8,6 5,8 $< $@
+
+cg_katakana.png: katakana0.png
+       ./cg_extract.py 70,62 8,6 5,8 .333 $< $@
+
+katakana0.png: katakana.pbm
+       convert $< \
+-grayscale Rec709Luminance \
+-distort Perspective \
+"240,304 0,0  1467,314 490,0  229,2206 0,434  1457,2211 490,434" \
+-crop 560x496+0+0 \
+$@
+
+katakana.pbm: signetics2513.pdf 
+       pdfimages -f 8 -l 8 $< a
+       mv a-000.pbm $@
+
+# LCA
+# diagrams have 5 * 8 cell characters at 19 * 13 cell spacing
+# reduce each cell down to 8 * 6 pixels
+# new diagram will have 40 * 48 pixel characters at 152 * 78 pixel spacing
+
+cg_lowercase_20.inc: cg_lowercase_20.png
+       ./cg_compile.py cg_lowercase_20 $< >$@
+
+lowercase_201.png: cg_lowercase_20.png
+       ./cg_verify.py 152,78 8,6 5,8 $< $@
+
+cg_lowercase_20.png: lowercase_200.png
+       ./cg_extract.py 152,78 8,6 5,8 .667 $< $@
+
+lowercase_200.png: lowercase_20.ppm
+       convert $< \
+-grayscale Rec709Luminance \
+-distort Perspective \
+"2025,778 168,0  2028,2879 1080,0  568,413 8,234  569,2864 1072,234" \
+-crop 1216x312+0+0 \
+$@
+
+lowercase_20.ppm: John\ Macdougall\ LowerCase\ Adapter\ (LCA)\ Alt-1.pdf
+       pdfimages -f 13 -l 13 "$<" a
+       mv a-000.ppm $@
+
+cg_lowercase_40.inc: cg_lowercase_40.png
+       ./cg_compile.py cg_lowercase_40 $< >$@
+
+lowercase_401.png: cg_lowercase_40.png
+       ./cg_verify.py 152,78 8,6 5,8 $< $@
+
+cg_lowercase_40.png: lowercase_400.png
+       ./cg_extract.py 152,78 8,6 5,8 .667 $< $@
+
+lowercase_400.png: lowercase_40.ppm
+       convert $< \
+-grayscale Rec709Luminance \
+-distort Perspective \
+"505,2879 8,0  505,429 1072,0  1967,2895 0,234  2227,460 1056,276" \
+-crop 1216x312+0+0 \
+$@
+#458
+
+lowercase_40.ppm: John\ Macdougall\ LowerCase\ Adapter\ (LCA)\ Alt-1.pdf
+       pdfimages -f 12 -l 12 "$<" a
+       mv a-000.ppm $@
+
+cg_lowercase_60.inc: cg_lowercase_60.png
+       ./cg_compile.py cg_lowercase_60 $< >$@
+
+lowercase_601.png: cg_lowercase_60.png
+       ./cg_verify.py 152,78 8,6 5,8 $< $@
+
+cg_lowercase_60.png: lowercase_600.png
+       ./cg_extract.py 152,78 8,6 5,8 .667 $< $@
+
+lowercase_600.png: lowercase_60.ppm
+       convert $< \
+-grayscale Rec709Luminance \
+-distort Perspective \
+"510,2880 8,0  585,428 1072,12  2048,2896 0,246  1972,478 1048,234" \
+-crop 1216x312+0+0 \
+$@
+
+lowercase_60.ppm: John\ Macdougall\ LowerCase\ Adapter\ (LCA)\ Alt-1.pdf
+       pdfimages -f 18 -l 18 "$<" a
+       mv a-000.ppm $@
+
+clean:
+       rm -f *.inc *.pbm *.png *.ppm
diff --git a/emu_z80/cg_default/cg_compile.py b/emu_z80/cg_default/cg_compile.py
new file mode 100755 (executable)
index 0000000..b97a8de
--- /dev/null
@@ -0,0 +1,64 @@
+#!/usr/bin/env python3
+
+import numpy
+import sys
+import PIL.Image
+
+EXIT_SUCCESS = 0
+EXIT_FAILURE = 1
+
+CHAR_X = 5
+CHAR_Y = 8
+
+if len(sys.argv) < 3:
+  print(f'usage: {sys.argv[0]} name in.png')
+  sys.exit(EXIT_FAILURE)
+name = sys.argv[1]
+in_png = sys.argv[2]
+
+image_in_pil = PIL.Image.open(in_png)
+assert image_in_pil.mode == 'P'
+image_in = numpy.frombuffer(
+  image_in_pil.tobytes(),
+  dtype = numpy.uint8
+).reshape((image_in_pil.size[1], image_in_pil.size[0])).astype(bool)
+
+assert image_in.shape[0] % CHAR_Y == 0
+n_rows = image_in.shape[0] // CHAR_Y
+assert image_in.shape[1] == 8 * CHAR_X
+
+chars = numpy.stack(
+  [
+    numpy.bitwise_or.reduce(
+      (
+        image_in[i * CHAR_Y:(i + 1) * CHAR_Y, j * CHAR_X:(j + 1) * CHAR_X] <<
+          numpy.arange(5, 0, -1, dtype = numpy.int32)[numpy.newaxis, :]
+      ).astype(numpy.uint8),
+      1
+    )
+    for i in range(n_rows)
+    for j in range(8)
+  ],
+  0
+)
+n_chars = n_rows * 8
+
+sys.stdout.write(
+  '''#define {0:s}_SIZE 0x{1:x}
+uint8_t {2:s}[{3:s}_SIZE] = {{
+{4:s}}};
+'''.format(
+    name.upper(),
+    n_chars * 8,
+    name,
+    name.upper(),
+    ''.join(
+      [
+        '  {0:s}\n'.format(
+          ' '.join([f'0x{chars[i, j]:02x},' for j in range(8)])
+        )
+        for i in range(n_chars)
+      ]
+    )
+  )
+)
diff --git a/emu_z80/cg_default/cg_extract.py b/emu_z80/cg_default/cg_extract.py
new file mode 100755 (executable)
index 0000000..a55e514
--- /dev/null
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+
+import numpy
+import sys
+import PIL.Image
+
+EXIT_SUCCESS = 0
+EXIT_FAILURE = 1
+
+if len(sys.argv) < 7:
+  print(f'usage: {sys.argv[0]} pitch_(x,y) cell_(x,y) char_(x,y) threshold in.png out.png')
+  sys.exit(EXIT_FAILURE)
+[pitch_x, pitch_y] = [int(i) for i in sys.argv[1].split(',')]
+[cell_x, cell_y] = [int(i) for i in sys.argv[2].split(',')]
+[char_x, char_y] = [int(i) for i in sys.argv[3].split(',')]
+threshold = float(sys.argv[4])
+in_png = sys.argv[5]
+out_png = sys.argv[6]
+
+image_in_pil = PIL.Image.open(in_png)
+assert image_in_pil.mode == 'L'
+image_in = numpy.frombuffer(
+  image_in_pil.tobytes(),
+  dtype = numpy.uint8
+).reshape((image_in_pil.size[1], image_in_pil.size[0]))
+
+image_in = image_in.astype(numpy.float32) / 255.
+mask = image_in < 12.92 * .0031308
+image_in[mask] *= 1. / 12.92
+image_in[~mask] = ((1. / 1.055) * image_in[~mask] + (.055 / 1.055)) ** 2.4
+
+assert image_in.shape[0] % pitch_y == 0
+n_rows = image_in.shape[0] // pitch_y
+assert image_in.shape[1] % pitch_x == 0
+n_cols = image_in.shape[1] // pitch_x
+
+image_out = numpy.zeros((n_rows * char_y, n_cols * char_x), bool)
+
+for i in range(n_rows):
+  y0 = i * pitch_y
+  y1 = i * char_y
+  for j in range(n_cols):
+    x0 = j * pitch_x
+    x1 = j * char_x
+    shape0 = image_in[y0:y0 + char_y * cell_y, x0:x0 + char_x * cell_x]
+    shape1 = numpy.mean(
+      numpy.stack(
+        [
+          shape0[k::cell_y, l::cell_x]
+          for k in range(1, cell_y)
+          for l in range(1, cell_x)
+        ],
+        2
+      ),
+      2
+    ) < threshold
+    image_out[y1:y1 + char_y, x1:x1 + char_x] = shape1
+
+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([0, 0, 0, 0xff, 0xff, 0xff])
+image_out_pil.save(out_png)
diff --git a/emu_z80/cg_default/cg_verify.py b/emu_z80/cg_default/cg_verify.py
new file mode 100755 (executable)
index 0000000..7b90805
--- /dev/null
@@ -0,0 +1,64 @@
+#!/usr/bin/env python3
+
+import numpy
+import sys
+import PIL.Image
+
+EXIT_SUCCESS = 0
+EXIT_FAILURE = 1
+
+if len(sys.argv) < 6:
+  print(f'usage: {sys.argv[0]} pitch_(x,y) cell_(x,y) char_(x,y) in.png out.png')
+  sys.exit(EXIT_FAILURE)
+[pitch_x, pitch_y] = [int(i) for i in sys.argv[1].split(',')]
+[cell_x, cell_y] = [int(i) for i in sys.argv[2].split(',')]
+[char_x, char_y] = [int(i) for i in sys.argv[3].split(',')]
+in_png = sys.argv[4]
+out_png = sys.argv[5]
+
+image_in_pil = PIL.Image.open(in_png)
+assert image_in_pil.mode == 'P'
+image_in = numpy.frombuffer(
+  image_in_pil.tobytes(),
+  dtype = numpy.uint8
+).reshape((image_in_pil.size[1], image_in_pil.size[0])).astype(bool)
+
+assert image_in.shape[0] % char_y == 0
+n_rows = image_in.shape[0] // char_y
+assert image_in.shape[1] % char_x == 0
+n_cols = image_in.shape[1] // char_x
+
+image_out = numpy.ones((n_rows * pitch_y, n_cols * pitch_x), numpy.float32)
+
+shape1 = numpy.full(
+  (char_y * cell_y + 1, char_x * cell_x + 1),
+  .5,
+  numpy.float32
+)
+for i in range(n_rows):
+  y0 = i * char_y
+  y1 = i * pitch_y
+  for j in range(n_cols):
+    x0 = j * char_x
+    x1 = j * pitch_x
+    shape0 = image_in[y0:y0 + char_y, x0:x0 + char_x]
+    for k in range(1, cell_y):
+      for l in range(1, cell_x):
+        shape1[k:-1:cell_y, l:-1:cell_x] = numpy.logical_not(shape0)
+    image_out[
+      y1:y1 + char_y * cell_y + 1,
+      x1:x1 + char_x * cell_x + 1
+    ] = shape1
+
+mask = image_out < .0031308
+image_out[mask] *= 12.92
+image_out[~mask] = 1.055 * image_out[~mask] ** (1. / 2.4) - 0.055
+image_out = numpy.round(image_out * 255.).astype(numpy.uint8)
+
+image_out_pil = PIL.Image.new(
+  'L',
+  (image_out.shape[1], image_out.shape[0]),
+  None
+)
+image_out_pil.frombytes(image_out.tobytes())
+image_out_pil.save(out_png)
diff --git a/emu_z80/cg_default/signetics2513.pdf b/emu_z80/cg_default/signetics2513.pdf
new file mode 100644 (file)
index 0000000..8028ca0
Binary files /dev/null and b/emu_z80/cg_default/signetics2513.pdf differ
index 1bba91c..c84b9f6 100644 (file)
+#define _XOPEN_SOURCE 600
 #include <fcntl.h>
 #include <poll.h>
+#include <signal.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <termios.h>
+#include <time.h>
 #include <unistd.h>
+#include <SDL2/SDL.h>
+#include "stty_sane.h"
 #include "z80/z80.h"
 
-#define STDIN_DATA 0
-#define STDOUT_DATA 1
-#define STDERR_DATA 2
-#define STDIN_STATUS 3
-#define STDOUT_STATUS 4
-#define STDERR_STATUS 5
-#define USLEEP_LO 6
-#define USLEEP_HI 7
-#define SYS_EXIT 8
+#define APPLE_IIE 1
+#define APPLE_WIDTH 560
+#define APPLE_HEIGHT 192
+#define APPLE_TEXT1 0xf400
+#define APPLE_TEXT2 0xf800
+#define APPLE_HIRES1 0x1000
+#define APPLE_HIRES2 0x3000
+
+// includes cool down sequence
+#define PADDED_WIDTH (APPLE_WIDTH + 3)
+#define PADDED_HEIGHT APPLE_HEIGHT
+#define PADDED_WORDS ((PADDED_WIDTH + 31) >> 5)
+
+#define WINDOW_X_SCALE 2
+#define WINDOW_Y_SCALE 4
+#define WINDOW_WIDTH (PADDED_WIDTH * WINDOW_X_SCALE)
+#define WINDOW_HEIGHT (PADDED_HEIGHT * WINDOW_Y_SCALE)
+
+#define CYCLES_PER_SAMPLE 34 // 44100 Hz @ 1.5 MHz
+#define SAMPLES_PER_UPDATE 500
+
+#define IO_PAGE 0xe000
+#define HW_KBD 0xe000 // R last key pressed + 128
+#define HW_CLR80COL 0xe000 // W use $C002-C005 for aux mem (80STOREOFF)
+#define HW_SET80COL 0xe001 // W use PAGE2 for aux mem (80STOREON)
+#define HW_RDMAINRAM 0xe002 // W if 80STORE off: read main mem $0200-BFFF
+#define HW_RDCARDRAM 0xe003 // W if 80STORE off: read aux mem $0200-BFFF
+#define HW_WRMAINRAM 0xe004 // W if 80STORE off: write main mem $0200-BFFF
+#define HW_WRCARDRAM 0xe005 // W if 80STORE off: write aux mem $0200-BFFF
+#define HW_SETSLOTCXROM 0xe006 // W use peripheral ROM ($C100-CFFF)
+#define HW_SETINTCXROM 0xe007 // W use internal ROM ($C100-CFFF)
+#define HW_SETSTDZP 0xe008 // W use main stack and zero page
+#define HW_SETALTZP 0xe009 // W use aux stack and zero page
+#define HW_SETINTC3ROM 0xe00a // W use internal slot 3 ROM
+#define HW_SETSLOTC3ROM 0xe00b // W use external slot 3 ROM
+#define HW_CLR80VID 0xe00c // W disable 80-column display mode
+#define HW_SET80VID 0xe00d // W enable 80-column display mode
+#define HW_CLRALTCHAR 0xe00e // W use primary char set
+#define HW_SETALTCHAR 0xe00f // W use alternate char set
+#define HW_KBDSTRB 0xe010 // RW keyboard strobe
+#define HW_RDLCBNK2 0xe011 // R bit 7: reading from LC bank 2 ($Dx)?
+#define HW_RDLCRAM 0xe012 // R bit 7: reading from LC RAM?
+#define HW_RDRAMRD 0xe013 // R bit 7: reading from aux/alt 48K?
+#define HW_RDRAMWRT 0xe014 // R bit 7: writing to aux/alt 48K?
+#define HW_RD80COL 0xe018 // R bit 7: 80STORE is on?
+#define HW_RDTEXT 0xe01a // R bit 7: using text mode?
+#define HW_RDPAGE2 0xe01c // R bit 7: using page 2?
+#define HW_RD80VID 0xe01f // R bit 7: using 80 columns?
+#define HW_TAPEOUT 0xe020 // RW toggle cassette output
+#define HW_SPKR 0xe030 // RW toggle speaker
+#define HW_TXTCLR 0xe050 // RW
+#define HW_TXTSET 0xe051 // RW
+#define HW_MIXCLR 0xe052 // RW
+#define HW_MIXSET 0xe053 // RW
+#define HW_PAGE1 0xe054 // RW display page 1
+#define HW_PAGE2 0xe055 // RW display page 2 (or read/write aux mem)
+#define HW_LORES 0xe056 // RW
+#define HW_HIRES 0xe057 // RW
+#define HW_SETAN0 0xe058 // RW annunciator 0 off
+#define HW_CLRAN0 0xe059 // RW annunciator 0 on
+#define HW_SETAN1 0xe05a // RW annunciator 1 off
+#define HW_CLRAN1 0xe05b // RW annunciator 1 on
+#define HW_SETAN2 0xe05c // RW annunciator 2 off
+#define HW_CLRAN2 0xe05d // RW annunciator 2 on
+#define HW_SETAN3 0xe05e // RW annunciator 2 off
+#define HW_CLRAN3 0xe05f // RW annunciator 3 on
+#define HW_TAPEIN 0xe060 // R
+#define HW_PB0 0xe061 // R switch input 0 / open-apple (orig: BUTN0)
+#define HW_PB1 0xe062 // R switch input 1 / closed-apple (orig: BUTN1)
+#define HW_PB2 0xe063 // R
+#define HW_PADDL0 0xe064 // R
+#define HW_PADDL1 0xe065 // R
+#define HW_PADDL2 0xe066 // R
+#define HW_PADDL3 0xe067 // R
+#define HW_PTRIG 0xe070
+#define HW_LC_BANK2_RAM_WP 0xe080 // RW
+#define HW_LC_BANK2_ROM_WE 0xe081 // RWx2
+#define HW_LC_BANK2_ROM_WP 0xe082 // RW
+#define HW_LC_BANK2_RAM_WE 0xe083 // RWx2
+#define HW_LC_BANK1_RAM_WP 0xe084 // RW
+#define HW_LC_BANK1_ROM_WE 0xe085 // RWx2
+#define HW_LC_BANK1_ROM_WP 0xe086 // RW
+#define HW_LC_BANK1_RAM_WE 0xe087 // RWx2
+#define HW_CLRROM 0xefff // disable slot C8 ROM
+
+#define STDIN_DATA 0xf0
+#define STDOUT_DATA 0xf1
+#define STDERR_DATA 0xf2
+#define STDIN_STATUS 0xf3
+#define STDOUT_STATUS 0xf4
+#define STDERR_STATUS 0xf5
+#define USLEEP_LO 0xf6
+#define USLEEP_HI 0xf7
+#define SYS_EXIT 0xf8
+#define DOS_LO 0xf9
+#define DOS_HI 0xfa
+
+#define RESET_VECTOR 0xfffc
 
 #define TRACE 0
+#define MEM_TRACE 0
+
+extern char **environ;
+
+int fd_in = STDIN_FILENO;
+int fd_out = STDOUT_FILENO;
+int child_pid;
+struct termios termios_attr;
+
+SDL_Window *window;
+SDL_Renderer *renderer;
+SDL_Texture *texture;
+SDL_Surface *surface;
+uint32_t frame[WINDOW_HEIGHT][WINDOW_WIDTH];
+
+// see palette.py
+uint32_t palette[0x10] = {
+  0xff000000,
+  0xffbc0089,
+  0xff0000bc,
+  0xffbc00e1,
+  0xff00bc89,
+  0xffbcbcbc,
+  0xff00bce1,
+  0xffbcbcff,
+  0xffbcbc00,
+  0xffffbc89,
+  0xffbcbcbc,
+  0xffffbce1,
+  0xffbcff89,
+  0xffffffbc,
+  0xffbcffe1,
+  0xffffffff,
+};
+
+// can make this green or amber to be more realistic
+uint32_t mono_palette[2] = {0xff000000, 0xffffffff};
 
 z80 cpu;
 
-int stdin_fd;
-int g_argn = 0;
-int g_argc = 1;
-const char *default_argv = "-";
-const char **g_argv = &default_argv;
+#if APPLE_IIE
+#include "cg_default/cg_lowercase_20.inc"
+#include "cg_default/cg_lowercase_40.inc"
+#include "cg_default/cg_lowercase_60.inc"
+#else
+#include "cg_default/cg_signetics.inc"
+#endif
+
+#define CG_ROM_SIZE 0x800
+uint8_t *cg_rom;
+
+#define VIDEO_ROM_SIZE 0x1000
+uint8_t *video_rom;
+
+#if APPLE_IIE
+#define MEM_SIZE 0x24000
+#else
+#define MEM_SIZE 0x14000
+#endif
+uint8_t mem[MEM_SIZE];
+
+#if TRACE
+#define N_PC_PAIRS 0x10001
+struct pc_pair {
+  uint16_t pc0;
+  uint16_t pc1;
+} pc_pairs[N_PC_PAIRS];
 
-#define MEMORY_SIZE 0x10000
-uint8_t memory[MEMORY_SIZE];
+#define TRACE_REG_A 0
+#define TRACE_REG_X 1
+#define TRACE_REG_Y 2
+#define TRACE_REG_S 3
+#define TRACE_REG_P 4
+#define N_TRACE_REGS 5
+struct trace {
+  uint8_t min_unsigned;
+  int8_t min_signed;
+  uint8_t min_bitwise;
+  uint8_t max_unsigned;
+  int8_t max_signed;
+  uint8_t max_bitwise;
+} trace[MEM_SIZE][N_TRACE_REGS];
+#endif
+#if 1
+uint8_t start_at_mission = 1;
+#endif
+uint8_t key_waiting;
 uint8_t usleep_lo;
-int exit_flag;
+uint8_t dos_lo;
+int usleep_count, exit_flag;
+
+#if APPLE_IIE
+#define C00X_SOFT_SWITCH_80COL 1
+#define C00X_SOFT_SWITCH_RDCARDRAM 2
+#define C00X_SOFT_SWITCH_WRCARDRAM 4
+#define C00X_SOFT_SWITCH_INTCXROM 8
+#define C00X_SOFT_SWITCH_ALTZP 0x10
+#define C00X_SOFT_SWITCH_SLOTC3ROM 0x20
+#define C00X_SOFT_SWITCH_80VID 0x40
+#define C00X_SOFT_SWITCH_ALTCHAR 0x80
+uint8_t c00x_soft_switches = 0;
+#endif
+
+#define C0X0_SOFT_SWITCH_TAPEOUT 4
+#define C0X0_SOFT_SWITCH_SPKR 8
+uint8_t c0x0_soft_switches = 0;
+
+// every n cycles, sample the tape and speaker outputs to here
+#define C0X0_SOFT_SWITCHES_BUF_SIZE 0x400 // next power of 2 >= 441 * 2
+int c0x0_soft_switches_buf_head;
+int c0x0_soft_switches_buf_count;
+uint8_t c0x0_soft_switches_buf[C0X0_SOFT_SWITCHES_BUF_SIZE];
 
-uint8_t rb(void *userdata, uint16_t addr) {
-  return memory[addr];
+#define C05X_SOFT_SWITCH_TEXT 1
+#define C05X_SOFT_SWITCH_MIXED 2
+#define C05X_SOFT_SWITCH_PAGE2 4
+#define C05X_SOFT_SWITCH_HIRES 8
+#define C05X_SOFT_SWITCH_NOTAN0 0x10
+#define C05X_SOFT_SWITCH_NOTAN1 0x20
+#define C05X_SOFT_SWITCH_NOTAN2 0x40
+#define C05X_SOFT_SWITCH_NOTAN3 0x80
+uint8_t c05x_soft_switches = C05X_SOFT_SWITCH_TEXT;
+
+#define C06X_INPUT_TAPEIN 1
+#define C06X_INPUT_PB0 2
+#define C06X_INPUT_PB1 4
+#define C06X_INPUT_PB2 8
+#define C06X_INPUT_PADDL0 0x10
+#define C06X_INPUT_PADDL1 0x20
+#define C06X_INPUT_PADDL2 0x40
+#define C06X_INPUT_PADDL3 0x80
+#define C06X_INPUT_PADDLX 0xf0
+uint8_t c06x_inputs;
+
+#define JOYSTICK_COUNT 0x900 // 9 cycles per loop of ROM_PREAD
+int joystick_count = JOYSTICK_COUNT;
+int joystick_axes[4] = {
+  JOYSTICK_COUNT,
+  JOYSTICK_COUNT,
+  JOYSTICK_COUNT,
+  JOYSTICK_COUNT
+};
+
+#define LC_BANK 4
+#define LC_BANK2 0
+#define LC_BANK1 4
+#define LC_SELECT 3
+#define LC_SELECT_RAM_WP 0
+#define LC_SELECT_ROM_WE 1
+#define LC_SELECT_ROM_WP 2
+#define LC_SELECT_RAM_WE 3
+uint8_t lc_state = LC_SELECT_ROM_WP | LC_BANK2;
+
+void rom_load(char *name, uint8_t *data, int size) {
+  int fd = open(name, O_RDONLY);
+  if (fd == -1) {
+    perror(name);
+    exit(EXIT_FAILURE);
+  }
+  if (read(fd, data, size) != size) {
+    perror("read()");
+    exit(EXIT_FAILURE);
+  }
+  close(fd);
+}
+
+int load_bin(char *name, int addr, int len) {
+  int fd = open(name, O_RDONLY);
+  if (fd == -1) {
+    perror(name);
+    exit(EXIT_FAILURE);
+  }
+
+  if (addr == -1)
+    addr = 0;
+  if (len == -1) {
+    len = MEM_SIZE - addr;
+    if (read(fd, mem + addr, len) == -1) { // short read OK
+      perror("read()");
+      exit(EXIT_FAILURE);
+    }
+  }
+  else if (read(fd, mem + addr, len) != len) {
+    perror("read()");
+    exit(EXIT_FAILURE);
+  }
+
+  close(fd);
+  return addr;
 }
 
-void wb(void *userdata, uint16_t addr, uint8_t val) {
-  memory[addr] = val;
+int load_a2bin(char *name, int addr, int len) {
+  int fd = open(name, O_RDONLY);
+  if (fd == -1) {
+    perror(name);
+    exit(EXIT_FAILURE);
+  }
+
+  uint8_t header[4];
+  if (read(fd, header, 4) != 4) {
+    perror("read()");
+    exit(EXIT_FAILURE);
+  }
+  if (addr == -1)
+    addr = header[0] + (header[1] << 8);
+  if (len == -1)
+    len = header[2] + (header[3] << 8);
+  int end_addr = addr + len;
+  if (end_addr < addr || end_addr > MEM_SIZE) {
+    fprintf(stderr, "invalid load range: [0x%x, 0x%x)\n", addr, end_addr);
+    exit(EXIT_FAILURE);
+  }
+
+  if (read(fd, mem + addr, len) != len) {
+    perror("read()");
+    exit(EXIT_FAILURE);
+  }
+
+  close(fd);
+  return addr;
 }
 
-// call with g_argn < g_argc
-void open_stdin(void) {
-  if (strcmp(g_argv[g_argn], "-") == 0)
-    stdin_fd = STDIN_FILENO;
+int load_ihx(char *name, int addr0, int len0) {
+  if (addr0 != -1 || len0 != -1) {
+    fprintf(stderr, "ihx loader does not implement address/length options\n");
+    exit(EXIT_FAILURE);
+  }
+
+  FILE *fp = fopen(name, "r");
+  if (fp == NULL) {
+    perror(name);
+    exit(EXIT_FAILURE);
+  }
+
+  int base = 0, entry_point = 0;
+  bool had_eof = false;
+  char line[0x100];
+  while (fgets(line, 0x100, fp)) {
+    for (char *p = line; *p; ++p)
+      if (*p == '\n') {
+        *p = 0;
+        break;
+      }
+
+    if (had_eof) {
+      fprintf(stderr, "garbage after EOF record: %s\n", line);
+      exit(EXIT_FAILURE);
+    }
+
+    if (line[0] != ':') {
+      fprintf(stderr, "require colon: %s\n", line);
+      exit(EXIT_FAILURE);
+    }
+
+    uint8_t buf[0x7f];
+    int len;
+    for (len = 0; len < 0x7f; ++len) {
+      char *p = line + 1 + len * 2;
+      if (*p == 0)
+        break;
+      if (*p == '\n') {
+        *p = 0;
+        break;
+      }
+      uint8_t c = p[2];
+      p[2] = 0;
+
+      char *q;
+      buf[len] = (uint8_t)strtol(p, &q, 16);
+      p[2] = c;
+      if (q != p + 2) {
+        fprintf(stderr, "not hex byte: %s\n", p);
+        exit(EXIT_FAILURE);
+      }
+    }
+
+    if (len == 0) {
+      fprintf(stderr, "empty line: %s\n", line);
+      exit(EXIT_FAILURE);
+    }
+
+    uint8_t checksum = 0;
+    for (int i = 0; i < len; ++i)
+      checksum += buf[i];
+    if (checksum) {
+      checksum -= buf[len - 1];
+      fprintf(
+        stderr,
+        "checksum %02x, should be %02x\n",
+        checksum,
+        buf[len - 1]
+      );
+      exit(EXIT_FAILURE);
+    }
+
+    len -= 5;
+    if (len != buf[0]) {
+      fprintf(stderr, "incorrect length: %s\n", line);
+      exit(EXIT_FAILURE);
+    }
+
+    int addr = (buf[1] << 8) | buf[2], end_addr;
+    switch (buf[3]) {
+    case 0:
+      addr += base;
+      end_addr = addr + len;
+      if (end_addr < addr || end_addr > MEM_SIZE) {
+        fprintf(stderr, "invalid load range: [0x%x, 0x%x)\n", addr, end_addr);
+        exit(EXIT_FAILURE);
+      }
+      memcpy(mem + addr, buf + 4, len);
+      break;
+    case 1:
+      had_eof = true;
+      break;
+    case 4:
+      if (len < 2) {
+        fprintf(stderr, "invalid extended linear address record: %s\n", line);
+        exit(EXIT_FAILURE);
+      }
+      base = (buf[4] << 24) | (buf[5] << 16);
+      break;
+    case 5:
+      if (len < 4) {
+        fprintf(stderr, "invalid start linear address record: %s\n", line);
+        exit(EXIT_FAILURE);
+      }
+      entry_point = (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7];
+      break;
+    default:
+      fprintf(stderr, "unknown record type: 0x%x\n", buf[3]);
+      exit(EXIT_FAILURE);
+    }
+  }
+  if (!had_eof) {
+    fprintf(stderr, "no EOF record\n");
+    exit(EXIT_FAILURE);
+  }
+
+  fclose(fp);
+  return entry_point;
+}
+
+int load(char *name) {
+  char *p;
+  for (p = name; *p && *p != ','; ++p)
+    ;
+
+  char *format;
+  for (format = p - 1; format >= name && *format != '.'; --format)
+    ;
+
+  int addr = -1, len = -1, c = *p;
+  *p++ = 0;
+  while (c) {
+    char *q;
+    for (q = p; *q && *q != ','; ++q)
+      ;
+    c = *q;
+    *q++ = 0;
+    int option = *p & ~0x20;
+    if (option == 'A' || option == 'L') {
+      ++p;
+      int base = 0;
+      if (*p == '$') {
+        ++p;
+        base = 16;
+      }
+      int value = (int)strtol(p, NULL, base);
+      if (value < 0) {
+        printf("negative load option %c value: -0x%x\n", option, -value);
+        exit(EXIT_FAILURE);
+      }
+      if (value > MEM_SIZE) {
+        printf("too large load option %c value: 0x%x\n", option, value);
+        exit(EXIT_FAILURE);
+      }
+      switch (option) {
+      case 'A':
+        addr = value;
+        break;
+      case 'L':
+        len = value;
+        break;
+      }
+    }
+    else {
+      fprintf(stderr, "unknown load option: %s\n", p);
+      exit(EXIT_FAILURE);
+    }
+    p = q;
+  }
+
+  if (strcmp(format, ".bin") == 0)
+    return load_bin(name, addr, len);
+  if (strcmp(format, ".a2bin") == 0)
+    return load_a2bin(name, addr, len);
+  if (strcmp(format, ".ihx") == 0)
+    return load_ihx(name, addr, len);
+  fprintf(stderr, "unknown load format: %s\n", format);
+  exit(EXIT_FAILURE);
+}
+
+void dos(char *line) {
+  //fprintf(stderr, "dos: %s\n", line);
+  if (memcmp(line, "BLOAD", 5) == 0) {
+    char *p;
+    for (p = line + 5; *p == ' '; ++p)
+      ;
+    if (strcmp(p, "RBOOT") != 0) {
+      for (char *q = p; *q; ++q)
+        if (*q == ' ')
+          *q = '_';
+        else if (*q >= 'A' && *q <= 'Z')
+          *q += 'a' - 'A';
+      load(p);
+    }
+  }
   else {
-    stdin_fd = open(g_argv[g_argn], O_RDONLY);
-    if (stdin_fd == -1) {
-      perror(g_argv[g_argn]);
+    fprintf(stderr, "unrecognized DOS command: %s\n", line);
+    exit(EXIT_FAILURE);
+  }
+}
+
+uint8_t rb(void *userdata, uint16_t addr0) {
+  // for joystick, for now count memory accesses as proxy for cycles
+  if (joystick_count < JOYSTICK_COUNT) {
+    ++joystick_count;
+    if (joystick_axes[0] < joystick_count)
+      c06x_inputs &= ~C06X_INPUT_PADDL0;
+    if (joystick_axes[1] < joystick_count)
+      c06x_inputs &= ~C06X_INPUT_PADDL1;
+    if (joystick_axes[2] < joystick_count)
+      c06x_inputs &= ~C06X_INPUT_PADDL2;
+    if (joystick_axes[3] < joystick_count)
+      c06x_inputs &= ~C06X_INPUT_PADDL3;
+  }
+
+  int addr = addr0;
+  if ((addr & 0xff00) != IO_PAGE) {
+#if APPLE_IIE
+    if (addr < 0x200) {
+      if (c00x_soft_switches & C00X_SOFT_SWITCH_ALTZP)
+        addr |= 0x10000;
+    }
+    else if (addr < 0xc000) {
+      if (
+        (c00x_soft_switches & C00X_SOFT_SWITCH_80COL) &&
+          (
+            (addr >= APPLE_TEXT1 && addr < APPLE_TEXT2) ||
+              (
+                (c05x_soft_switches & C05X_SOFT_SWITCH_HIRES) &&
+                  addr >= APPLE_HIRES1 &&
+                  addr < APPLE_HIRES2
+              )
+          )
+      ) {
+        if (c05x_soft_switches & C05X_SOFT_SWITCH_PAGE2)
+          addr |= 0x10000;
+      }
+      else if (c00x_soft_switches & C00X_SOFT_SWITCH_RDCARDRAM)
+        addr |= 0x10000;
+    }
+    else if (
+      addr < 0xd000 ||
+        (lc_state & LC_SELECT) == LC_SELECT_ROM_WE ||
+        (lc_state & LC_SELECT) == LC_SELECT_ROM_WP
+    )
+      addr += 0x20000 - 0xc000;
+    else {
+      if (addr < 0xe000 && (lc_state & LC_BANK) == LC_BANK1)
+        addr -= 0x1000;
+      if (c00x_soft_switches & C00X_SOFT_SWITCH_RDCARDRAM)
+        addr |= 0x10000;
+    }
+#else
+    if (addr < 0xc000)
+      ;
+    else if (
+      addr < 0xd000 ||
+        (lc_state & LC_SELECT) == LC_SELECT_ROM_WE ||
+        (lc_state & LC_SELECT) == LC_SELECT_ROM_WP
+    )
+      addr += 0x10000 - 0xc000;
+    else if (addr < 0xe000 && (lc_state & LC_BANK) == LC_BANK1)
+      addr -= 0x1000;
+#endif
+
+#if 0 // breakpoint
+    //if (addr >= 0x8c08 && addr < 0x8e00) { // data fetch
+    //if (addr >= 0x9580 && addr < 0x9600) { // data fetch
+    if (addr >= 0x5c00 && addr < 0x5c1b) { // data fetch
+      int pc = vrEmu6502GetPC(cpu);
+      fprintf(stderr, "pc=%04x addr=%04x\n", pc, addr);
+      exit_flag = 0x101;
+      vrEmu6502Jam(cpu);
+    } 
+#endif
+
+#if 0 // breakpoint
+    if (addr == 0x17d1) { // opcode fetch
+      int fd = open("core.bin", O_WRONLY | O_CREAT, 0666);
+      if (fd == -1) {
+        perror("core.bin");
+        exit(EXIT_FAILURE);
+      }
+      if (write(fd, mem, MEM_SIZE) != MEM_SIZE) {
+        perror("write()");
+        exit(EXIT_FAILURE);
+      }
+      close(fd);
       exit(EXIT_FAILURE);
     }
+#endif
+
+#if 0 // draw object
+    if (addr == 0x1ab4) { // opcode fetch
+      int pc = vrEmu6502GetPC(cpu);
+      int object = vrEmu6502GetX(cpu);
+      int shape = mem[0xabd0 + object]; // object_shape
+
+      const struct {int shape; const char *name;} shapes[138] = {
+        {0x00, "small0"},
+        {0x01, "small1"},
+        {0x02, "small2"},
+        {0x03, "small3"},
+        {0x04, "small4"},
+        {0x05, "small5"},
+        {0x06, "small6"},
+        {0x07, "small7"},
+        {0x20, "bomb0"},
+        {0x21, "bomb1"},
+        {0x22, "bomb2"},
+        {0x23, "bomb3"},
+        {0x50, "missile_launcher0_empty"},
+        {0x51, "missile_launcher1_empty"},
+        {0x52, "missile_launcher2_empty"},
+        {0x56, "missile_launcher2"},
+        {0x54, "missile_launcher0"},
+        {0x55, "missile_launcher1"},
+        {0x40, "missile0"},
+        {0x44, "missile4"},
+        {0x48, "missile8"},
+        {0x4c, "missile12"},
+        {0x41, "missile1"},
+        {0x47, "missile7"},
+        {0x49, "missile9"},
+        {0x4f, "missile15"},
+        {0x42, "missile2"},
+        {0x46, "missile6"},
+        {0x4a, "missile10"},
+        {0x4e, "missile14"},
+        {0x43, "missile3"},
+        {0x45, "missile5"},
+        {0x4b, "missile11"},
+        {0x4d, "missile13"},
+        {0x60, "fragment0"},
+        {0x61, "fragment1"},
+        {0x62, "fragment2"},
+        {0x63, "fragment3"},
+        {0x64, "fragment4"},
+        {0x65, "fragment5"},
+        {0x66, "fragment6"},
+        {0x67, "fragment7"},
+        {0x0c, "bird0"},
+        {0x0d, "bird1"},
+        {0x0e, "bird2"},
+        {0x0f, "bird3"},
+        {0x08, "bullet3"},
+        {0x09, "bullet5"},
+        {0x0a, "bullet7"},
+        {0x0b, "bullet9"},
+        {0x14, "parachute0"},
+        {0x15, "parachute1"},
+        {0x16, "parachute_open"},
+        {0x88, "explosion0"},
+        {0x8a, "explosion2"},
+        {0x89, "explosion1"},
+        {0x8b, "explosion3"},
+        {0x10, "ship"},
+        {0x11, "ship_open"},
+        {0x12, "exhaust0"},
+        {0x13, "exhaust1"},
+        {0x17, "fuel"},
+        {0x18, "supply_plane"},
+        {0x28, "balloon0"},
+        {0x29, "balloon1"},
+        {0x2a, "balloon2"},
+        {0x2e, "enemy_plane0"},
+        {0x2f, "enemy_plane1"},
+        {0x2c, "helicopter"},
+        {0x1c, "tank"},
+        {0x1e, "missile_tank"},
+        {0x1f, "missile_tank_empty"},
+        {0x36, "pylon"},
+        {0x30, "silo0"},
+        {0x31, "silo1"},
+        {0x70, "haystack"},
+        {0x71, "house"},
+        {0x72, "headquarters"},
+        {0x73, "radar0"},
+        {0x74, "radar1"},
+        {0x75, "radar2"},
+        {0x76, "icbm"},
+        {0x78, "tree"},
+        {0x79, "cactus"},
+        {0xaa, "text_score"},
+        {0xab, "text_colon"},
+        {0xac, "text_high"},
+        {0xb0, "text_mission"},
+        {0xb2, "text_fuel"},
+        {0xb3, "text_empty"},
+        {0xb4, "text_bomb"},
+        {0xb6, "text_ship_left"},
+        {0xb7, "text_complete"},
+        {0xb8, "text_the"},
+        {0xb9, "text_radar"},
+        {0xba, "text_icbm"},
+        {0xbc, "text_attack"},
+        {0xbd, "text_tank"},
+        {0xbe, "text_headquarters"},
+        {0xbf, "text_a"},
+        {0xa0, "text_0"},
+        {0xa1, "text_1"},
+        {0xa2, "text_2"},
+        {0xa3, "text_3"},
+        {0xa4, "text_4"},
+        {0xa5, "text_5"},
+        {0xa6, "text_6"},
+        {0xa7, "text_7"},
+        {0xa8, "text_8"},
+        {0xa9, "text_9"},
+        {0xff, "blank"},
+        {0xf0, "ground0"},
+        {0xf1, "ground1"},
+        {0xf2, "ground2"},
+        {0xf3, "ground3"},
+        {0xc0, "ships0"},
+        {0xc1, "ships1"},
+        {0xc2, "ships2"},
+        {0xc3, "ships3"},
+        {0xd2, "text_by"},
+        {0xd3, "text_tony"},
+        {0xd4, "text_suzuki"},
+        {0xd8, "text_game"},
+        {0xd9, "text_over"},
+        {0xda, "text_bonus"},
+        {0xdc, "text_great"},
+        {0xdd, "text_performance"},
+        {0xe1, "text_copyright"},
+        {0xe2, "text_star_craft"},
+        {0xe3, "text_inc"},
+        {0xe4, "text_thanks"},
+        {0xe5, "text_lot_comma"},
+        {0xe6, "text_raly"},
+        {0xe7, "text_minus_20"},
+        {0xe8, "text_20_40_60_80"},
+        {0xe9, "text_100_120_300_1500"},
+        {0xd0, "text_star"},
+        {0xd1, "text_blazer"},
+      };
+      const char *name = "unknown";
+      for (int i = 0; i < 138; ++i)
+        if (shape == shapes[i].shape) {
+          name = shapes[i].name;
+          break;
+        }
+
+      fprintf(
+        stderr,
+        "pc=%04x object=%02x x=%02x..%02x y=%02x..%02x shape=%02x(%s)\n",
+        pc,
+        object,
+        mem[0xad20 + object], // object_x0
+        mem[0xad90 + object], // object_x1
+        mem[0xae00 + object], // object_y0
+        mem[0xae70 + object], // object_y1
+        shape,
+        name
+      );
+    }
+#endif
+
+#if 0 // start at mission
+    if (addr == 0x1726) // opcode fetch
+      vrEmu6502SetX(cpu, start_at_mission);
+#endif
+
+#if MEM_TRACE
+    {
+      int pc = cpu ? vrEmu6502GetPC(cpu) : 0;
+      fprintf(stderr, "pc=%04x addr=%05x rd=%02x\n", pc, addr, mem[addr]);
+    }
+#endif
+    return mem[addr];
   }
+
+  switch (addr) {
+  case HW_KBD:
+    return key_waiting;
+  case HW_KBDSTRB:
+    key_waiting &= 0x7f;
+    break;
+#if APPLE_IIE
+  case HW_RDLCBNK2:
+    return ((lc_state & LC_BANK) != LC_BANK1) << 7;
+  case HW_RDLCRAM:
+    return (
+      (lc_state & LC_SELECT) != LC_SELECT_ROM_WE &&
+        (lc_state & LC_SELECT) != LC_SELECT_ROM_WP
+    ) << 7;
+  case HW_RDRAMRD:
+    return ((c00x_soft_switches & C00X_SOFT_SWITCH_RDCARDRAM) != 0) << 7;
+  case HW_RDRAMWRT:
+    return ((c00x_soft_switches & C00X_SOFT_SWITCH_WRCARDRAM) != 0) << 7;
+  case HW_RD80COL:
+    return ((c00x_soft_switches & C00X_SOFT_SWITCH_80COL) != 0) << 7;
+  case HW_RDTEXT:
+    return ((c05x_soft_switches & C05X_SOFT_SWITCH_TEXT) != 0) << 7;
+  case HW_RDPAGE2:
+    return (
+      (c05x_soft_switches & C05X_SOFT_SWITCH_PAGE2) &&
+        (c00x_soft_switches & C00X_SOFT_SWITCH_80COL) == 0
+    ) << 7;
+  case HW_RD80VID:
+    return ((c00x_soft_switches & C00X_SOFT_SWITCH_80VID) != 0) << 7;
+#endif
+  case HW_TAPEOUT: // 0xc020
+  case HW_SPKR: // 0xc030
+    c0x0_soft_switches ^= 1 << ((addr >> 4) & 7);
+    break;
+  case HW_TXTCLR: // 0xc050
+  case HW_MIXCLR: // 0xc052
+  case HW_PAGE1: // 0xc054
+  case HW_LORES: // 0xc056
+  case HW_SETAN0: // 0xc058
+  case HW_SETAN1: // 0xc05a
+  case HW_SETAN2: // 0xc05c
+  case HW_SETAN3: // 0xc05e
+    c05x_soft_switches &= ~(1 << ((addr >> 1) & 7));
+    break;
+  case HW_TXTSET : // 0xc051
+  case HW_MIXSET : // 0xc053
+  case HW_PAGE2 : // 0xc055
+  case HW_HIRES : // 0xc057
+  case HW_CLRAN0 : // 0xc059
+  case HW_CLRAN1 : // 0xc05b
+  case HW_CLRAN2 : // 0xc05d
+  case HW_CLRAN3 : // 0xc05f
+    c05x_soft_switches |= 1 << ((addr >> 1) & 7);
+    break;
+  case HW_TAPEIN: // 0xc060
+  case HW_PB0: // 0xc061
+  case HW_PB1: // 0xc062
+  case HW_PB2: // 0xc063
+  case HW_PADDL0: // 0xc064
+  case HW_PADDL1: // 0xc065
+  case HW_PADDL2: // 0xc066
+  case HW_PADDL3: // 0xc067
+    return ((c06x_inputs & (1 << (addr & 7))) != 0) << 7;
+  case HW_PTRIG:
+    joystick_count = 0;
+    c06x_inputs |= C06X_INPUT_PADDLX;
+    break;
+  case HW_LC_BANK2_RAM_WP: // 0xc080
+  case HW_LC_BANK2_ROM_WE: // 0xc081
+  case HW_LC_BANK2_ROM_WP: // 0xc082
+  case HW_LC_BANK2_RAM_WE: // 0xc083
+  case HW_LC_BANK1_RAM_WP: // 0xc084
+  case HW_LC_BANK1_ROM_WE: // 0xc085
+  case HW_LC_BANK1_ROM_WP: // 0xc086
+  case HW_LC_BANK1_RAM_WE: // 0xc087
+    lc_state = addr & 7; // should check RWx2 for write enable
+    break;
+  }
+  return 0xff;
 }
 
-void close_stdin(void) {
-  if (stdin_fd != STDIN_FILENO)
-    close(stdin_fd);
+void wb(void *userdata, uint16_t addr0, uint8_t val) {
+  // for joystick, for now count memory accesses as proxy for cycles
+  if (joystick_count < JOYSTICK_COUNT) {
+    ++joystick_count;
+    if (joystick_axes[0] < joystick_count)
+      c06x_inputs &= ~C06X_INPUT_PADDL0;
+    if (joystick_axes[1] < joystick_count)
+      c06x_inputs &= ~C06X_INPUT_PADDL1;
+    if (joystick_axes[2] < joystick_count)
+      c06x_inputs &= ~C06X_INPUT_PADDL2;
+    if (joystick_axes[3] < joystick_count)
+      c06x_inputs &= ~C06X_INPUT_PADDL3;
+  }
+
+  int addr = addr0;
+#if 0 // vectors
+  if (addr >= 4 && addr < 0x80) {
+    int pc = vrEmu6502GetPC(cpu);
+    if (pc != 0x3f52) {
+      fprintf(stderr, "pc=%04x w=%04x v=%02x\n", pc, addr, val);
+      exit(EXIT_FAILURE);
+    }
+  }
+#endif
+
+  if ((addr & 0xff00) != IO_PAGE) {
+#if APPLE_IIE
+    if (addr < 0x200) {
+      if (c00x_soft_switches & C00X_SOFT_SWITCH_ALTZP)
+        addr |= 0x10000;
+    }
+    else if (addr < 0xc000) {
+      if (
+        (c00x_soft_switches & C00X_SOFT_SWITCH_80COL) &&
+          (
+            (addr >= APPLE_TEXT1 && addr < APPLE_TEXT2) ||
+              (
+                (c05x_soft_switches & C05X_SOFT_SWITCH_HIRES) &&
+                  addr >= APPLE_HIRES1 &&
+                  addr < APPLE_HIRES2
+              )
+          )
+      ) {
+        if (c05x_soft_switches & C05X_SOFT_SWITCH_PAGE2)
+          addr |= 0x10000;
+      }
+      else if (c00x_soft_switches & C00X_SOFT_SWITCH_WRCARDRAM)
+        addr |= 0x10000;
+    }
+    else if (
+      addr < 0xd000 ||
+        (lc_state & LC_SELECT) == LC_SELECT_RAM_WP ||
+        (lc_state & LC_SELECT) == LC_SELECT_ROM_WP
+    ) {
+#if MEM_TRACE
+      int pc = cpu ? vrEmu6502GetPC(cpu): 0;
+      addr += 0x20000 - 0xc000;
+      fprintf(stderr, "pc=%04x addr=%05x wr=%02x (nop)\n", pc, addr, mem[addr]);
+#endif
+      return;
+    }
+    else {
+      if (addr < 0xe000 && (lc_state & LC_BANK) == LC_BANK1)
+        addr -= 0x1000;
+      if (c00x_soft_switches & C00X_SOFT_SWITCH_WRCARDRAM)
+        addr |= 0x10000;
+    }
+#else
+    if (addr < 0xc000)
+      ;
+    else if (
+      addr < 0xd000 ||
+        (lc_state & LC_SELECT) == LC_SELECT_RAM_WP ||
+        (lc_state & LC_SELECT) == LC_SELECT_ROM_WP
+    ) {
+#if MEM_TRACE
+      int pc = cpu ? vrEmu6502GetPC(cpu) : 0;
+      addr += 0x10000 - 0xc000;
+      fprintf(stderr, "pc=%04x addr=%05x wr=%02x (nop)\n", pc, addr, mem[addr]);
+#endif
+    }
+    else if (addr < 0xe000 && (lc_state & LC_BANK) == LC_BANK1)
+      addr -= 0x1000;
+#endif
+    mem[addr] = val;
+#if MEM_TRACE
+    {
+      int pc = cpu ? vrEmu6502GetPC(cpu) : 0;
+      fprintf(stderr, "pc=%04x addr=%05x wr=%02x\n", pc, addr, mem[addr]);
+    }
+#endif
+
+#if 0 // sound effects
+    if (addr >= 0xab80 && addr < 0xab90) {
+      int pc = vrEmu6502GetPC(cpu);
+      fprintf(stderr, "pc=%04x w=%04x v=%02x\r\n", pc, addr, val);
+    }
+#endif
+
+#if 0 // decimals
+    if (addr >= 0xb0 && addr < 0xc0) {
+      int pc = vrEmu6502GetPC(cpu);
+      fprintf(stderr, "pc=%04x w=%04x v=%02x", pc, addr, val);
+      for (int i = 0xb0; i < 0xc0; i += 2) {
+        int v = mem[i] + (mem[i + 1] << 8);
+        fprintf(stderr, " dec_%02x=%04x", i, v);
+      }
+      fprintf(stderr, "\n");
+    }
+#endif
+
+#if 0 // demo mode
+    if (addr >= 0xf0 && addr < 0xf3) {
+      int pc = vrEmu6502GetPC(cpu);
+      fprintf(
+        stderr,
+        "pc=%04x w=%04x v=%02x demo_mode=%02x button_state=%02x key_state=%02x\n",
+        pc,
+        addr,
+        val,
+        mem[0xf0],
+        mem[0xf1],
+        mem[0xf2]
+      );
+    }
+#endif
+
+    return;
+  }
+
+  switch (addr) {
+  case HW_KBDSTRB:
+    key_waiting &= 0x7f;
+    break;
+#if APPLE_IIE
+  case HW_CLR80COL: // 0xc000
+    c05x_soft_switches &= ~C05X_SOFT_SWITCH_PAGE2;
+    // fallthru
+  case HW_RDMAINRAM: // 0xc002
+  case HW_WRMAINRAM: // 0xc004
+  case HW_SETSLOTCXROM: // 0xc006
+  case HW_SETSTDZP: // 0xc008
+  case HW_SETINTC3ROM: // 0xc00a
+  case HW_CLR80VID: // 0xc00c
+  case HW_CLRALTCHAR: // 0xc00e
+    c00x_soft_switches &= ~(1 << ((addr >> 1) & 7));
+    break;
+  case HW_SET80COL: // 0xc001
+    c05x_soft_switches &= ~C05X_SOFT_SWITCH_PAGE2;
+    // fallthru
+  case HW_RDCARDRAM: // 0xc003
+  case HW_WRCARDRAM: // 0xc005
+  case HW_SETINTCXROM: // 0xc007
+  case HW_SETALTZP: // 0xc009
+  case HW_SETSLOTC3ROM: // 0xc00b
+  case HW_SET80VID: // 0xc00d
+  case HW_SETALTCHAR: // 0xc00f
+    c00x_soft_switches |= 1 << ((addr >> 1) & 7);
+    break;
+#endif
+  case HW_TAPEOUT: // 0xc020
+  case HW_SPKR: // 0xc030
+    c0x0_soft_switches ^= 1 << ((addr >> 4) & 7);
+    break;
+  case HW_TXTCLR: // 0xc050
+  case HW_MIXCLR: // 0xc052
+  case HW_PAGE1: // 0xc054
+  case HW_LORES: // 0xc056
+  case HW_SETAN0: // 0xc058
+  case HW_SETAN1: // 0xc05a
+  case HW_SETAN2: // 0xc05c
+  case HW_SETAN3: // 0xc05e
+    c05x_soft_switches &= ~(1 << ((addr >> 1) & 7));
+    break;
+  case HW_TXTSET : // 0xc051
+  case HW_MIXSET : // 0xc053
+  case HW_PAGE2 : // 0xc055
+  case HW_HIRES : // 0xc057
+  case HW_CLRAN0 : // 0xc059
+  case HW_CLRAN1 : // 0xc05b
+  case HW_CLRAN2 : // 0xc05d
+  case HW_CLRAN3 : // 0xc05f
+    c05x_soft_switches |= 1 << ((addr >> 1) & 7);
+    break;
+  case HW_LC_BANK2_RAM_WP: // 0xc080
+  case HW_LC_BANK2_ROM_WE: // 0xc081
+  case HW_LC_BANK2_ROM_WP: // 0xc082
+  case HW_LC_BANK2_RAM_WE: // 0xc083
+  case HW_LC_BANK1_RAM_WP: // 0xc084
+  case HW_LC_BANK1_ROM_WE: // 0xc085
+  case HW_LC_BANK1_ROM_WP: // 0xc086
+  case HW_LC_BANK1_RAM_WE: // 0xc087
+    lc_state = addr & 7; // should check RWx2 for write enable
+    break;
+  }
 }
 
 uint8_t in(z80 *const z, uint8_t port) {
   switch (port) {
   case STDIN_DATA:
     {
-      uint8_t data = 4; // EOT
-      if (g_argn < g_argc)
-        while (true) {
-          ssize_t count = read(stdin_fd, &data, 1);
-          if (count == -1) {
-            perror("read()");
-            exit(EXIT_FAILURE);
-          }
-          if (count)
-            break;
-          close_stdin();
-          ++g_argn;
-          if (g_argn >= g_argc)
-            break;
-          open_stdin();
-        }
+      uint8_t data = 'X' - 0x40;
+      ssize_t count = read(fd_in, &data, 1);
+      if (count == -1) {
+        perror("read()");
+        exit(EXIT_FAILURE);
+      }
+      if (data == 'X' - 0x40) { // count == 0 or ctrl-x (unassigned by hrcg)
+        exit_flag = 0x101;
+        cpu.halted = true;
+      }
       return data;
     }
   case STDIN_STATUS:
     {
-      if (g_argn >= g_argc)
-        return 1; // if no more input, force application to read EOT
-      struct pollfd fd = {stdin_fd, POLLIN, 0};
+      struct pollfd fd = {fd_in, POLLIN | POLLPRI, 0};
       if (poll(&fd, 1, 0) == -1) {
         perror("poll()");
         exit(EXIT_FAILURE);
@@ -93,7 +1117,7 @@ uint8_t in(z80 *const z, uint8_t port) {
     }
   case STDOUT_STATUS:
     {
-      struct pollfd fd = {STDOUT_FILENO, POLLOUT, 0};
+      struct pollfd fd = {fd_out, POLLOUT, 0};
       if (poll(&fd, 1, 0) == -1) {
         perror("poll()");
         exit(EXIT_FAILURE);
@@ -111,6 +1135,8 @@ uint8_t in(z80 *const z, uint8_t port) {
     }
   case USLEEP_LO:
     return usleep_lo;
+  case DOS_LO:
+    return dos_lo;
   }
   return 0xff;
 }
@@ -118,7 +1144,7 @@ uint8_t in(z80 *const z, uint8_t port) {
 void out(z80 *const z, uint8_t port, uint8_t val) {
   switch (port) {
   case STDOUT_DATA:
-    if (write(STDOUT_FILENO, &val, 1) == -1) {
+    if (write(fd_out, &val, 1) == -1) {
       perror("write()");
       exit(EXIT_FAILURE);
     }
@@ -133,48 +1159,422 @@ void out(z80 *const z, uint8_t port, uint8_t val) {
     usleep_lo = val;
     break;
   case USLEEP_HI:
-    usleep(usleep_lo | (val << 8));
+    usleep_count = usleep_lo | (val << 8);
+    cpu.halted = true; // force a screen update before going to sleep
     break;
   case SYS_EXIT:
     exit_flag = val | 0x100;
     cpu.halted = true;
     break;
+  case DOS_LO:
+    dos_lo = val;
+    break;
+  case DOS_HI:
+    dos((char *)(mem + (dos_lo | (val << 8))));
+    break;
+  }
+}
+
+void sigchld_handler(int signum) {
+  if (child_pid == 0) {
+    fprintf(stderr, "SIGCHLD: no child process yet\n");
+    exit(EXIT_FAILURE);
   }
+
+  int wstatus;
+  if (waitpid(child_pid, &wstatus, WNOHANG) == -1) {
+    perror("waitpid()");
+    exit(EXIT_FAILURE);
+  }
+
+  if (!WIFEXITED(wstatus)) {
+    fprintf(stderr, "SIGCHLD: child process was killed\n");
+    exit(EXIT_FAILURE);
+  }
+
+  // reflect child's exit status to parent
+  //fprintf(stderr, "SIGCHLD: child exit status %d\n", WEXITSTATUS(wstatus));
+  exit(WEXITSTATUS(wstatus));
+}
+
+void termios_atexit(void) {
+  if (tcsetattr(fd_in, TCSADRAIN, &termios_attr) == -1)
+    perror("tcsetattr()");
 }
 
 int main(int argc, char **argv) {
   int argn = 1;
+  char *cg_rom_file = NULL;
+  char *video_rom_file = NULL;
+#if APPLE_IIE
+  char *cd_rom_file = NULL;
+  char *ef_rom_file = NULL;
+#else
+  char *d0_rom_file = NULL;
+  char *d8_rom_file = NULL;
+  char *e0_rom_file = NULL;
+  char *e8_rom_file = NULL;
+  char *f0_rom_file = NULL;
+  char *f8_rom_file = NULL;
+#endif
   bool timing = false;
-  if (argn < argc && strcmp(argv[argn], "-t") == 0) {
-    timing = true;
+  while (argn < argc) {
+    if (strcmp(argv[argn], "--help") == 0) {
+      printf("usage: %s [--cg-rom=file.bin] [--video-rom=file.bin] [--NN-rom=file.bin ...] [--timing] [program.obj[,aNNNN] ...] [-- child-executable [child-argument ...]]\n", argv[0]);
+      exit(EXIT_FAILURE);
+    }
+    if (memcmp(argv[argn], "--cg-rom=", 9) == 0)
+      cg_rom_file = argv[argn] + 9;
+    else if (memcmp(argv[argn], "--video_rom=", 12) == 0)
+      video_rom_file = argv[argn] + 12;
+#if APPLE_IIE
+    else if (memcmp(argv[argn], "--cd-rom=", 9) == 0)
+      cd_rom_file = argv[argn] + 9;
+    else if (memcmp(argv[argn], "--ef-rom=", 9) == 0)
+      ef_rom_file = argv[argn] + 9;
+#else
+    else if (memcmp(argv[argn], "--d0-rom=", 9) == 0)
+      d0_rom_file = argv[argn] + 9;
+    else if (memcmp(argv[argn], "--d8-rom=", 9) == 0)
+      d8_rom_file = argv[argn] + 9;
+    else if (memcmp(argv[argn], "--e0-rom=", 9) == 0)
+      e0_rom_file = argv[argn] + 9;
+    else if (memcmp(argv[argn], "--e8-rom=", 9) == 0)
+      e8_rom_file = argv[argn] + 9;
+    else if (memcmp(argv[argn], "--f0-rom=", 9) == 0)
+      f0_rom_file = argv[argn] + 9;
+    else if (memcmp(argv[argn], "--f8-rom=", 9) == 0)
+      f8_rom_file = argv[argn] + 9;
+#endif
+    else if (strcmp(argv[argn], "--timing") == 0)
+      timing = true;
+    else
+      break;
     ++argn;
   }
 
-  if (argn >= argc) {
-    printf("usage: %s [-t] program.bin\n", argv[0]);
+  // the IIe emulation can run without a video ROM and fall back to
+  // II+ style character generation, this is done for the benefit
+  // of DHGR or 128K games that require Apple IIe but not text mode
+  if (video_rom_file) {
+    video_rom = malloc(VIDEO_ROM_SIZE);
+    if (video_rom == NULL) {
+      perror("malloc()");
+      exit(EXIT_FAILURE);
+    }
+    rom_load(video_rom_file, video_rom, VIDEO_ROM_SIZE);
+  }
+  else {
+    cg_rom = malloc(CG_ROM_SIZE);
+    if (cg_rom == NULL) {
+      perror("malloc()");
+      exit(EXIT_FAILURE);
+    }
+    if (cg_rom_file)
+      rom_load(cg_rom_file, cg_rom, CG_ROM_SIZE);
+    else {
+#if APPLE_IIE
+      memcpy(cg_rom, cg_lowercase_40, CG_LOWERCASE_40_SIZE);
+      memcpy(cg_rom + 0x100, cg_lowercase_20, CG_LOWERCASE_20_SIZE);
+      memcpy(cg_rom + 0x200, cg_lowercase_40, CG_LOWERCASE_40_SIZE);
+      memcpy(cg_rom + 0x300, cg_lowercase_20, CG_LOWERCASE_20_SIZE);
+      memcpy(cg_rom + 0x400, cg_lowercase_40, CG_LOWERCASE_40_SIZE);
+      memcpy(cg_rom + 0x500, cg_lowercase_20, CG_LOWERCASE_20_SIZE);
+      memcpy(cg_rom + 0x600, cg_lowercase_40, CG_LOWERCASE_40_SIZE);
+      memcpy(cg_rom + 0x700, cg_lowercase_60, CG_LOWERCASE_60_SIZE);
+#else
+      memcpy(cg_rom, cg_signetics, CG_SIGNETICS_SIZE);
+      memcpy(cg_rom + 0x200, cg_signetics, CG_SIGNETICS_SIZE);
+      memcpy(cg_rom + 0x400, cg_signetics, CG_SIGNETICS_SIZE);
+      memcpy(cg_rom + 0x600, cg_signetics, CG_SIGNETICS_SIZE);
+#endif
+    }
+  }
+
+  int entry_point = -1;
+#if APPLE_IIE
+  memset(mem + 0x20000, 0xff, 0x4000);
+  if (cd_rom_file)
+    load_bin(cd_rom_file, 0x20000, 0x2000);
+  if (ef_rom_file)
+    load_bin(ef_rom_file, 0x22000, 0x2000);
+#else
+  memset(mem + 0x10000, 0xff, 0x4000);
+  if (d0_rom_file)
+    load_bin(d0_rom_file, 0x11000, 0x800);
+  if (d8_rom_file)
+    load_bin(d8_rom_file, 0x11800, 0x800);
+  if (e0_rom_file)
+    load_bin(e0_rom_file, 0x12000, 0x800);
+  if (e0_rom_file)
+    load_bin(e8_rom_file, 0x12800, 0x800);
+  if (e0_rom_file)
+    load_bin(f0_rom_file, 0x13000, 0x800);
+  if (f8_rom_file)
+    load_bin(f8_rom_file, 0x13800, 0x800);
+#endif
+
+  while (argn < argc) {
+    char *p = argv[argn++];
+    if (strcmp(p, "--") == 0)
+      break;
+    entry_point = load(p);
+  }
+
+  // do this before creating the CPU
+  if (entry_point != -1) {
+#if APPLE_IIE
+    mem[RESET_VECTOR + 0x20000 - 0xc000] = (uint8_t)(entry_point & 0xff);
+    mem[RESET_VECTOR + 1 + 0x20000 - 0xc000] = (uint8_t)(entry_point >> 8);
+#else
+    mem[RESET_VECTOR + 0x10000 - 0xc000] = (uint8_t)(entry_point & 0xff);
+    mem[RESET_VECTOR + 1 + 0x10000 - 0xc000] = (uint8_t)(entry_point >> 8);
+#endif
+  }
+
+  // open pty and child process if requested
+  if (argn < argc) {
+    int fd_master = posix_openpt(O_RDWR);
+    if (fd_master == -1) {
+      perror("posix_openpt()");
+      exit(EXIT_FAILURE);
+    }
+
+    if (grantpt(fd_master) == -1) {
+      perror("grantpt()");
+      exit(EXIT_FAILURE);
+    }
+
+    if (unlockpt(fd_master) == -1) {
+      perror("unlockpt()");
+      exit(EXIT_FAILURE);
+    }
+
+    // open the slave side of the pty
+    char *slave_name = ptsname(fd_master);
+    int fd_slave = open(slave_name, O_RDWR);
+    if (fd_slave == -1) {
+      perror(slave_name);
+      exit(EXIT_FAILURE);
+    }
+
+    signal(SIGCHLD, sigchld_handler);
+    child_pid = fork();
+    if (child_pid == 0) {
+      close(fd_master);
+
+      // make the current process a new session leader
+      // see http://www.rkoucha.fr/tech_corner/pty_pdip.html
+      if (setsid() == -1) {
+        perror("setsid()");
+        exit(EXIT_FAILURE);
+      }
+
+      // as the child is a session leader, set the controlling terminal to be
+      // the slave side of the pty (mandatory for programs like the shell to
+      // make them manage correctly their outputs)
+      // see http://www.rkoucha.fr/tech_corner/pty_pdip.html
+      if (ioctl(fd_slave, TIOCSCTTY, 1) == -1) {
+        perror("TIOCSCTTY");
+        exit(EXIT_FAILURE);
+      }
+
+      // inform applications like /bin/ls to provide 40-column output
+      struct winsize winsize = {.ws_row = 24, .ws_col = 40};
+      if (ioctl(fd_slave, TIOCSWINSZ, &winsize) == -1) {
+        perror("TIOCSWINSZ");
+        exit(EXIT_FAILURE);
+      }
+
+      // for some reason, the termios configuration MUST be done after fork()
+      struct termios attr;
+      if (tcgetattr(fd_slave, &attr) == -1) {
+        perror("tcgetattr()");
+        exit(EXIT_FAILURE);
+      }
+
+#if 1
+      stty_sane(&attr);
+      attr.c_cc[VERASE] = 8; // ctrl-h
+#elif 1 // see cfmakeraw() in termios man page
+      // with minimal cooked mode (+ICRNL, +OPOST, +ECHO, +ICANON, +ISIG)
+      attr.c_iflag &= ~(
+        IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | IXON
+      );
+      attr.c_iflag |= ICRNL;
+      attr.c_oflag |= OPOST;
+      attr.c_lflag &= ~(ECHONL | IEXTEN);
+      attr.c_lflag |= ECHO | ICANON | ISIG;
+      attr.c_cflag &= ~(CSIZE | PARENB);
+      attr.c_cflag |= CS8;
+#else // see https://github.com/python/cpython/blob/3.10/Lib/tty.py
+      // with minimal cooked mode (+ICRNL, +OPOST, +ECHO, +ICANON, +ISIG)
+      attr.c_iflag &= ~(BRKINT | INPCK | ISTRIP | IXON);
+      attr.c_iflag |= ICRNL;
+      attr.c_oflag |= OPOST;
+      attr.c_cflag &= ~(CSIZE | PARENB);
+      attr.c_cflag |= CS8;
+      attr.c_lflag &= ~IEXTEN;
+      attr.c_lflag |= ECHO | ICANON | ISIG;
+      attr.c_cc[VMIN] = 1;
+      attr.c_cc[VTIME] = 0;
+#endif
+      if (tcsetattr(fd_slave, TCSAFLUSH, &attr) == -1) {
+        perror("tcsetattr()");
+        exit(EXIT_FAILURE);
+      }
+
+      int fd_err = dup(STDERR_FILENO);
+      if (fd_err == -1) {
+        perror("dup()");
+        exit(EXIT_FAILURE);
+      }
+      if (fcntl(fd_err, F_SETFD, FD_CLOEXEC) == -1) {
+        perror("fcntl()");
+        exit(EXIT_FAILURE);
+      }
+
+      dup2(fd_slave, STDIN_FILENO);
+      dup2(fd_slave, STDOUT_FILENO);
+      dup2(fd_slave, STDERR_FILENO);
+      close(fd_slave);
+
+      int n = argc - argn;
+      char **p = malloc((n + 1) * sizeof(char *));
+      memcpy(p, argv + argn, n * sizeof(char *));
+      p[n] = NULL;
+      execve(argv[argn], p, environ);
+
+      dup2(fd_err, STDERR_FILENO);
+      perror(argv[argn]);
+      exit(EXIT_FAILURE);
+    }
+    close(fd_slave);
+
+    fd_in = fd_master;
+    fd_out = fd_master;
+  }
+
+  if (isatty(fd_in)) {
+    if (tcgetattr(fd_in, &termios_attr) == -1) {
+      perror("tcgetattr()");
+      exit(EXIT_FAILURE);
+    }
+    atexit(termios_atexit);
+
+    struct termios attr = termios_attr;
+#if 1 // see cfmakeraw() in termios man page
+    attr.c_iflag &= ~(
+      IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON
+    );
+    attr.c_oflag &= ~OPOST;
+    attr.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
+    attr.c_cflag &= ~(CSIZE | PARENB);
+    attr.c_cflag |= CS8;
+#else // see https://github.com/python/cpython/blob/3.10/Lib/tty.py
+    attr.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
+    attr.c_oflag &= ~OPOST;
+    attr.c_cflag &= ~(CSIZE | PARENB);
+    attr.c_cflag |= CS8;
+    attr.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
+    attr.c_cc[VMIN] = 1;
+    attr.c_cc[VTIME] = 0;
+#endif
+    if (tcsetattr(fd_in, TCSAFLUSH, &attr) == -1) {
+      perror("tcsetattr()");
+      exit(EXIT_FAILURE);
+    }
+  }
+
+  if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK) < 0) {
+    fprintf(stderr, "SDL_Init(): %s\n\n", SDL_GetError());
     exit(EXIT_FAILURE);
   }
 
-  int fd = open(argv[argn], O_RDONLY);
-  if (fd == -1) {
-    perror(argv[argn]);
+  if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1")) {
+    fprintf(stderr, "SDL_SetHint(): %s\n", SDL_GetError());
     exit(EXIT_FAILURE);
   }
-  if (read(fd, memory, MEMORY_SIZE) == -1) {
-    perror("read()");
+
+  window = SDL_CreateWindow(
+    "render",
+    SDL_WINDOWPOS_UNDEFINED,
+    SDL_WINDOWPOS_UNDEFINED,
+    WINDOW_WIDTH,
+    WINDOW_HEIGHT,
+    SDL_WINDOW_SHOWN
+  );
+  if (window == NULL) {
+    fprintf(stderr, "SDL_CreateWindow(): %s\n", SDL_GetError());
     exit(EXIT_FAILURE);
   }
-  close(fd);
 
-  // implement "cat" functionality for stdin
-  // if not enough arguments, supply default argument of "-"
-  ++argn;
-  if (argn < argc) {
-    g_argn = argn;
-    g_argc = argc;
-    g_argv = (const char **)argv;
+  renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
+  if (renderer == NULL) {
+    fprintf(stderr, "SDL_CreateRenderer(): %s\n", SDL_GetError());
+    exit(EXIT_FAILURE);
+  }
+
+  texture = SDL_CreateTexture(
+    renderer,
+    SDL_PIXELFORMAT_ARGB8888,
+    SDL_TEXTUREACCESS_STREAMING,
+    WINDOW_WIDTH,
+    WINDOW_HEIGHT
+  );
+  if (texture == NULL) {
+    fprintf(stderr, "SDL_CreateTexture(): %s\n", SDL_GetError());
+    exit(EXIT_FAILURE);
+  }
+
+  SDL_AudioSpec audio_spec;
+  memset(&audio_spec, 0, sizeof(SDL_AudioSpec));
+  audio_spec.freq = 44100;
+  audio_spec.format = AUDIO_S16SYS;
+  audio_spec.channels = 1;
+  audio_spec.silence = 0;
+  audio_spec.samples = 0x200; // next power of 2 >= 441
+ //SDL_AudioSpec audio_spec_obtained;
+  SDL_AudioDeviceID audio_device_id = SDL_OpenAudioDevice(
+    NULL,
+    0,
+    &audio_spec,
+    NULL, //&audio_spec_obtained,
+    0
+  );
+  if (audio_device_id == 0) {
+    fprintf(stderr, "SDL_OpenAudioDevice(): %s\n", SDL_GetError());
+    exit(EXIT_FAILURE);
+  }
+ //fprintf(
+ // stderr,
+ // "freq %d format %d channels %d silence %d samples %d\r\n",
+ // audio_spec_obtained.freq,
+ // audio_spec_obtained.format,
+ // audio_spec_obtained.channels,
+ // audio_spec_obtained.silence,
+ // audio_spec_obtained.samples
+ //);
+  SDL_PauseAudioDevice(audio_device_id, 0);
+
+  SDL_Joystick *joystick = NULL;
+  if (SDL_NumJoysticks()) {
+    joystick = SDL_JoystickOpen(0);
+    if (joystick == NULL) {
+      fprintf(stderr, "SDL_JoystickOpen(): %s\n", SDL_GetError());
+      exit(EXIT_FAILURE);
+    }
+
+    if (SDL_JoystickEventState(SDL_ENABLE) != 1) {
+      fprintf(stderr, "SDL_JoystickEventState(): %s\n", SDL_GetError());
+      exit(EXIT_FAILURE);
+    }
+
+    // we should get the current position to use until first event,
+    // for now just centre it (no joystick is fully to right and down)
+    for (int i = 0; i < 4; ++i)
+      joystick_axes[i] = JOYSTICK_COUNT >> 1;
   }
-  open_stdin();
 
   z80_init(&cpu);
   cpu.read_byte = rb;
@@ -182,45 +1582,540 @@ int main(int argc, char **argv) {
   cpu.port_in = in;
   cpu.port_out = out;
 
-  long n, nb_instructions = 0;
 #if TRACE
-  do {
-    int pc = cpu.pc;
-    int ip = cpu.c | cpu.b << 8;
-    int dsp = cpu.sp;
-    int rsp = cpu.ix;
-    fprintf(
-      stderr,
-      "pc=%04x:%02x,%02x,%02x,%02x ip=%04x:%04x dsp=%04x:%04x rsp=%04x:%04x\n",
-      pc,
-      memory[pc],
-      memory[(pc + 1) & 0xffff],
-      memory[(pc + 2) & 0xffff],
-      memory[(pc + 3) & 0xffff],
-      ip,
-      memory[ip] | (memory[(ip + 1) & 0xffff] << 8),
-      dsp,
-      memory[dsp] | (memory[(dsp + 1) & 0xffff] << 8),
-      rsp,
-      memory[rsp] | (memory[(rsp + 1) & 0xffff] << 8)
-    );
+  memset(pc_pairs, 0xff, N_PC_PAIRS * sizeof(struct pc_pair));
+
+  for (int i = 0; i < MEM_SIZE; ++i)
+    for (int j = 0; j < N_TRACE_REGS; ++j) {
+      trace[i][j].min_unsigned = 0xff;
+      trace[i][j].min_signed = 0x7f;
+      trace[i][j].min_bitwise = 0xff;
+      trace[i][j].max_unsigned = 0x00;
+      trace[i][j].max_signed = -0x80;
+      trace[i][j].max_bitwise = 0x00;
+    }
+#endif
+
+  // main loop
+  long nb_instructions = 0, nb_cycles = 0;
+  int update_count = 0;
+  float spkr_rc = 0.f;
+  while (true) {
+    SDL_Event event;
+    while (SDL_PollEvent(&event))
+      switch (event.type) {
+      case SDL_QUIT:
+        goto quit;
+      case SDL_KEYDOWN:
+        {
+          SDL_KeyboardEvent *e = (SDL_KeyboardEvent *)&event;
+          int i = e->keysym.sym;
+          switch (i) {
+          case SDLK_LEFT:
+            key_waiting = 0x88;
+            break;
+          case SDLK_RIGHT:
+            key_waiting = 0x95;
+            break;
+          default:
+            if (i < 0x20)
+              key_waiting = i | 0x80;
+            else if ((i >= 0x40 && i < 0x80) && (e->keysym.mod & KMOD_CTRL))
+              key_waiting = (i & 0x1f) | 0x80;
+            break;
+          }
+        }
+        break;
+      case SDL_TEXTINPUT:
+        {
+          SDL_TextInputEvent *e = (SDL_TextInputEvent *)&event;
+          int i = e->text[0];
+          switch (i) {
+#if 0 // sound effects
+          case 'a':
+          case 'b':
+          case 'c':
+          case 'd':
+          case 'e':
+          case 'f':
+            i += '0' + 10 - 'a';
+            // fallthru
+         case '0':
+         case '1':
+          case '2':
+          case '3':
+          case '4':
+          case '5':
+          case '6':
+         case '7':
+          case '8':
+          case '9':
+            mem[0xab80 + (i & 0xf)] = 1;
+            break;
+#endif
+
+#if 0
+          case '1':
+          case '2':
+          case '3':
+          case '4':
+          case '5':
+            // mission
+            //mem[0xb6] = i - '0';
+            //mem[0xb7] = 0;
+            start_at_mission = i - '0';
+            // the following seems to cause problems if done
+            // while GAME OVER is displayed, try without it
+            //key_waiting = 0x92; // ctrl-r
+            break;
+          case 'b':
+          case 'B':
+            // full bomb
+            mem[0xba] = 0x30;
+            mem[0xbb] = 0;
+            break;
+          case 'e':
+          case 'E':
+            // empty fuel
+            mem[0xb8] = 1;
+            mem[0xb9] = 0;
+            break;
+          case 'f':
+          case 'F':
+            // full fuel
+            mem[0xb8] = 0;
+            mem[0xb9] = 3;
+            break;
+          case 'r':
+          case 'R':
+            // empty ship_left
+            mem[0xbc] = 1;
+            mem[0xbd] = 0;
+            break;
+          case 's':
+          case 'S':
+            // full ship_left
+            mem[0xbc] = 4;
+            mem[0xbd] = 0;
+            break;
+#endif
+          default: 
+            key_waiting = i | 0x80;
+            break;
+          }
+        }
+        break;
+      case SDL_JOYBUTTONUP:
+        {
+          SDL_JoyButtonEvent *e = (SDL_JoyButtonEvent *)&event;
+          if (
+            joystick &&
+              e->which == SDL_JoystickInstanceID(joystick) &&
+              e->button < 3
+          )
+            c06x_inputs &= ~(C06X_INPUT_PB0 << e->button);
+        }
+        break;
+      case SDL_JOYBUTTONDOWN:
+        {
+          SDL_JoyButtonEvent *e = (SDL_JoyButtonEvent *)&event;
+          if (
+            joystick &&
+              e->which == SDL_JoystickInstanceID(joystick) &&
+              e->button < 3
+          )
+            c06x_inputs |= C06X_INPUT_PB0 << e->button;
+        }
+        break;
+      case SDL_JOYAXISMOTION:
+        {
+          SDL_JoyAxisEvent *e = (SDL_JoyAxisEvent *)&event;
+          if (
+            joystick &&
+              e->which == SDL_JoystickInstanceID(joystick) &&
+              e->axis < 4
+          )
+            joystick_axes[e->axis] =
+              ((e->value + 0x8000) * JOYSTICK_COUNT) >> 16;
+        }
+        break;
+      }
+
+    // video update
+    if (++update_count >= SAMPLES_PER_UPDATE) {
+      update_count = 0;
 
-    n = z80_step(&cpu, 1);
-    nb_instructions += n;
-  } while (n);
+      SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0xff);
+      SDL_RenderClear(renderer);
+
+      struct timespec timeval;
+      clock_gettime(CLOCK_MONOTONIC, &timeval);
+      bool flash_state = timeval.tv_nsec >= 500000000;
+  
+      // video ROM does inverse/flash by manipulating base address
+      int flash_base[4] = {
+        0,
+#if APPLE_IIE
+        // in alternate character mode, use video ROM directly (mousetext)
+        (c00x_soft_switches & C00X_SOFT_SWITCH_ALTCHAR) ?
+          0x200 :
+#endif
+          flash_state ? 0 : 0x400,
+        0x400,
+        0x600
+      };
+  
+      // CG ROM does inverse/flash by XORing onto the value from ROM
+      int flash_xor[4] = {0x7f, flash_state ? 0x7f : 0, 0, 0};
+  
+      memset(frame, 0, WINDOW_HEIGHT * WINDOW_WIDTH * sizeof(uint32_t));
+      for (int i = 0; i < APPLE_HEIGHT; ++i) {
+        int base = (i >> 6) * 40 | ((i & 0x38) << 4);
+        int row = i & 7;
+        uint32_t buf[PADDED_WORDS];
+        memset(buf, 0, PADDED_WORDS * sizeof(uint32_t));
+  
+        if (c05x_soft_switches & C05X_SOFT_SWITCH_HIRES) {
+          // hires
+#if APPLE_IIE
+          int line = (
+            (c05x_soft_switches & C05X_SOFT_SWITCH_PAGE2) &&
+              (c00x_soft_switches & C00X_SOFT_SWITCH_80COL) == 0 ?
+              APPLE_HIRES2 :
+              APPLE_HIRES1
+          ) + (base | (row << 10));
 #else
-  do {
-    n = z80_step(&cpu, 1000);
-    nb_instructions += n;
-  } while (n >= 1000);
+          int line = (
+            (c05x_soft_switches & C05X_SOFT_SWITCH_PAGE2) ?
+              APPLE_HIRES2 :
+              APPLE_HIRES1
+          ) + (base | (row << 10));
 #endif
+#if APPLE_IIE
+          if (c00x_soft_switches & C00X_SOFT_SWITCH_80VID) {
+            // dhgr -- no support for SETDHIRES soft switch yet
+            int aux_line = line | 0x10000;
+            int l = 1; // 1 pixel delay, similar to hgr with hi-bit set
+            for (int j = 0; j < 40; ++j) {
+              int data = mem[aux_line + j];
+              for (int k = 0; k < 7; ++k) {
+                if (data & 1)
+                  buf[l >> 5] |= 1 << (l & 0x1f);
+                ++l;
+                data >>= 1;
+              }
+              /*int*/ data = mem[line + j];
+              for (int k = 0; k < 7; ++k) {
+                if (data & 1)
+                  buf[l >> 5] |= 1 << (l & 0x1f);
+                ++l;
+                data >>= 1;
+              }
+            }
+          }
+          else
+#endif
+          for (int j = 0; j < 40; ++j) {
+            int data = mem[line + j];
+            int hibit = data >> 7;
+            for (int k = 0; k < 7; ++k) {
+              if (data & 1) {
+                int l = ((j * 7 + k) << 1) + hibit;
+                buf[l >> 5] |= 1 << (l & 0x1f);
+                ++l;
+                buf[l >> 5] |= 1 << (l & 0x1f);
+              }
+              data >>= 1;
+            }
+          }
+        }
+        else {
+          // text or gr (only text for now)
+#if APPLE_IIE
+          int line = (
+            (c05x_soft_switches & C05X_SOFT_SWITCH_PAGE2) &&
+              (c00x_soft_switches & C00X_SOFT_SWITCH_80COL) == 0 ?
+              APPLE_TEXT2 :
+              APPLE_TEXT1
+          ) + base;
+#else
+          int line = (
+            (c05x_soft_switches & C05X_SOFT_SWITCH_PAGE2) ?
+              APPLE_TEXT2 :
+              APPLE_TEXT1
+          ) + base;
+#endif
+          for (int j = 0; j < 40; ++j) {
+            int glyph = mem[line + j];
+            if (video_rom) { // inverted, LSB first
+              int data = video_rom[
+                row | ((glyph & 0x3f) << 3) | flash_base[glyph >> 6]
+              ];
+              for (int k = 0; k < 7; ++k) {
+                if ((data & 1) == 0) {
+                  int l = (j * 7 + k) << 1;
+                  buf[l >> 5] |= 1 << (l & 0x1f);
+                  ++l;
+                  buf[l >> 5] |= 1 << (l & 0x1f);
+                }
+                data >>= 1;
+              }
+            }
+            else { // MSB first
+              int data = cg_rom[row | (glyph << 3)] ^ flash_xor[glyph >> 6];
+              for (int k = 0; k < 7; ++k) {
+                if (data & 0x40) {
+                  int l = (j * 7 + k) << 1;
+                  buf[l >> 5] |= 1 << (l & 0x1f);
+                  ++l;
+                  buf[l >> 5] |= 1 << (l & 0x1f);
+                }
+                data <<= 1;
+              }
+            }
+          }
+        }
+  
+        if (c05x_soft_switches & C05X_SOFT_SWITCH_TEXT) {
+          // text (no colour burst)
+          int x = 3 * WINDOW_X_SCALE / 2, y = i * WINDOW_Y_SCALE;
+          for (int j = 0; j < APPLE_WIDTH; ++j) {
+            uint32_t v = mono_palette[(buf[j >> 5] >> (j & 0x1f)) & 1];
+            for (int l = 0; l < WINDOW_X_SCALE; ++l) {
+              for (int m = 0; m < WINDOW_Y_SCALE; ++m)
+                frame[y + m][x] = v;
+              ++x;
+            }
+          }
+        }
+        else {
+          // hires or gr (colour burst)
+          int k = 0, x = 0, y = i * WINDOW_Y_SCALE;
+          for (int j = 0; j < PADDED_WIDTH; ++j) {
+            int mask = 1 << (j & 3);
+            k = (k & ~mask) | ((buf[j >> 5] >> (j & 0x1c)) & mask);
+            uint32_t v = palette[k];
+            for (int l = 0; l < WINDOW_X_SCALE; ++l) {
+              for (int m = 0; m < WINDOW_Y_SCALE; ++m)
+                frame[y + m][x] = v;
+              ++x;
+            }
+          }
+        }
+      }
+      SDL_UpdateTexture(texture, NULL, frame, WINDOW_WIDTH * sizeof(uint32_t));
+      SDL_RenderCopy(renderer, texture, NULL, NULL);
+      SDL_RenderPresent(renderer);
+    }
+    // audio update
+    if (c0x0_soft_switches_buf_count >= 0x100) {
+      int queue_level = SDL_GetQueuedAudioSize(audio_device_id);
+ // queue level should not fall to 0, if it does increase SAMPLES_PER_UPDATE
+ //fprintf(stderr, "%d %d\r\n", c0x0_soft_switches_buf_count, queue_level);
+      if (queue_level < 0x400) {
+        int16_t audio_buf[0x100];
+        for (int i = 0; i < 0x100; ++i) {
+          float spkr = (
+            c0x0_soft_switches_buf[c0x0_soft_switches_buf_head] &
+              C0X0_SOFT_SWITCH_SPKR
+          ) != 0;
+          c0x0_soft_switches_buf_head =
+            (c0x0_soft_switches_buf_head + 1) &
+              (C0X0_SOFT_SWITCHES_BUF_SIZE - 1);
+          --c0x0_soft_switches_buf_count;
+  
+          spkr -= spkr_rc;
+          spkr_rc += spkr * (1.f / 220.5f); // 200 Hz high pass
+          audio_buf[i] = (int16_t)roundf(spkr * 0x3fff);
+        }
+  
+        if (
+          SDL_QueueAudio(
+            audio_device_id,
+            audio_buf,
+            0x100 * sizeof(int16_t)
+          )
+        ) {
+          fprintf(stderr, "SDL_QueueAudio(): %s\n", SDL_GetError());
+          exit(EXIT_FAILURE);
+        }
+      }
+    }
+
+    // cpu update 
+    if (c0x0_soft_switches_buf_count >= C0X0_SOFT_SWITCHES_BUF_SIZE) {
+#if 1 // for star blazer game which doesn't do usleep
+ //fprintf(stderr, "sleep\r\n");
+      SDL_Delay(1);
+#else
+      if (usleep_count) {
+#if 1 // hack to avoid lagginess in ribbit game
+        struct pollfd fd = {fd_in, POLLIN | POLLPRI, 0};
+        if (poll(&fd, 1, (usleep_count + 999) / 1000) == -1) {
+          perror("poll()");
+          exit(EXIT_FAILURE);
+        }
+#else
+        usleep(usleep_count);
+#endif
+      }
+#endif
+    }
+    else {
+      int i, j;
+#if TRACE
+      struct pc_pair pc_pair = {vrEmu6502GetPC(cpu), 0};
+      for (int k = 0; k < CYCLES_PER_SAMPLE; k += j) {
+        i = z80_step(&cpu, 1);
+        j = i * 4; // for now 
+        nb_instructions += i;
+        nb_cycles += j;
+        if (i == 0)
+          goto jam; //break;
+
+        pc_pair.pc1 = vrEmu6502GetPC(cpu);
+        bool show_trace = false;
+
+        int hash = (19 * pc_pair.pc0 + 37 * pc_pair.pc1) % N_PC_PAIRS;
+        for (int i = 0; i < N_PC_PAIRS; ++i) {
+          if (
+            pc_pairs[hash].pc0 == pc_pair.pc0 &&
+              pc_pairs[hash].pc1 == pc_pair.pc1
+          )
+            break;
+          if (
+            pc_pairs[hash].pc0 == 0xffff &&
+              pc_pairs[hash].pc1 == 0xffff
+          ) {
+            pc_pairs[hash] = pc_pair;
+            show_trace = true;
+            break;
+          }
+          ++hash;
+          if (hash >= N_PC_PAIRS)
+            hash = 0;
+        }
+
+        uint8_t regs[N_TRACE_REGS] = {
+          vrEmu6502GetAcc(cpu),
+          vrEmu6502GetX(cpu),
+          vrEmu6502GetY(cpu),
+          vrEmu6502GetStackPointer(cpu),
+          vrEmu6502GetStatus(cpu)
+        };
+        for (int i = 0; i < N_TRACE_REGS; ++i) {
+          if (regs[i] < trace[pc_pair.pc1][i].min_unsigned) {
+            trace[pc_pair.pc1][i].min_unsigned = regs[i];
+            show_trace = true;
+          }
+          if ((int8_t)regs[i] < trace[pc_pair.pc1][i].min_signed) {
+            trace[pc_pair.pc1][i].min_signed = (int8_t)regs[i];
+            show_trace = true;
+          }
+          if (~regs[i] & trace[pc_pair.pc1][i].min_bitwise) {
+            trace[pc_pair.pc1][i].min_bitwise &= regs[i];
+            show_trace = true;
+          }
+          if (regs[i] > trace[pc_pair.pc1][i].max_unsigned) {
+            trace[pc_pair.pc1][i].max_unsigned = regs[i];
+            show_trace = true;
+          }
+          if ((int8_t)regs[i] > trace[pc_pair.pc1][i].max_signed) {
+            trace[pc_pair.pc1][i].max_signed = (int8_t)regs[i];
+            show_trace = true;
+          }
+          if (regs[i] & ~trace[pc_pair.pc1][i].max_bitwise) {
+            trace[pc_pair.pc1][i].max_bitwise |= regs[i];
+            show_trace = true;
+          }
+        }
+
+        if (show_trace)
+          fprintf(
+            stderr,
+            "pc=%04x,%04x a=%02x%02x%02x,%02x%02x%02x x=%02x%02x%02x,%02x%02x%02x y=%02x%02x%02x,%02x%02x%02x s=%02x%02x%02x,%02x%02x%02x p=%02x%02x%02x,%02x%02x%02x\n",
+            pc_pair.pc0,
+            pc_pair.pc1,
+            trace[pc_pair.pc1][TRACE_REG_A].min_unsigned,
+            (uint8_t)trace[pc_pair.pc1][TRACE_REG_A].min_signed,
+            trace[pc_pair.pc1][TRACE_REG_A].min_bitwise,
+            trace[pc_pair.pc1][TRACE_REG_A].max_unsigned,
+            (uint8_t)trace[pc_pair.pc1][TRACE_REG_A].max_signed,
+            trace[pc_pair.pc1][TRACE_REG_A].max_bitwise,
+            trace[pc_pair.pc1][TRACE_REG_X].min_unsigned,
+            (uint8_t)trace[pc_pair.pc1][TRACE_REG_X].min_signed,
+            trace[pc_pair.pc1][TRACE_REG_X].min_bitwise,
+            trace[pc_pair.pc1][TRACE_REG_X].max_unsigned,
+            (uint8_t)trace[pc_pair.pc1][TRACE_REG_X].max_signed,
+            trace[pc_pair.pc1][TRACE_REG_X].max_bitwise,
+            trace[pc_pair.pc1][TRACE_REG_Y].min_unsigned,
+            (uint8_t)trace[pc_pair.pc1][TRACE_REG_Y].min_signed,
+            trace[pc_pair.pc1][TRACE_REG_Y].min_bitwise,
+            trace[pc_pair.pc1][TRACE_REG_Y].max_unsigned,
+            (uint8_t)trace[pc_pair.pc1][TRACE_REG_Y].max_signed,
+            trace[pc_pair.pc1][TRACE_REG_Y].max_bitwise,
+            trace[pc_pair.pc1][TRACE_REG_S].min_unsigned,
+            (uint8_t)trace[pc_pair.pc1][TRACE_REG_S].min_signed,
+            trace[pc_pair.pc1][TRACE_REG_S].min_bitwise,
+            trace[pc_pair.pc1][TRACE_REG_S].max_unsigned,
+            (uint8_t)trace[pc_pair.pc1][TRACE_REG_S].max_signed,
+            trace[pc_pair.pc1][TRACE_REG_S].max_bitwise,
+            trace[pc_pair.pc1][TRACE_REG_P].min_unsigned,
+            (uint8_t)trace[pc_pair.pc1][TRACE_REG_P].min_signed,
+            trace[pc_pair.pc1][TRACE_REG_P].min_bitwise,
+            trace[pc_pair.pc1][TRACE_REG_P].max_unsigned,
+            (uint8_t)trace[pc_pair.pc1][TRACE_REG_P].max_signed,
+            trace[pc_pair.pc1][TRACE_REG_P].max_bitwise
+          );
+        pc_pair.pc0 = pc_pair.pc1;
+      }
+jam:
+#else
+      i = z80_step(&cpu, CYCLES_PER_SAMPLE / 4); // for now
+      j = i * 4; // for now
+      nb_instructions += i;
+      nb_cycles += j;
+      //if (j < CYCLES_PER_UPDATE)
+      //  break;
+#endif
+      if (exit_flag)
+        break;
+      cpu.halted = false;
+
+      // sample tape and speaker outputs at CYCLES_PER_SAMPLE rate
+#if 0
+      if (c0x0_soft_switches_buf_count >= C0X0_SOFT_SWITCHES_BUF_SIZE) {
+        // buffer full, drop oldest sample
+        c0x0_soft_switches_buf_head =
+          (c0x0_soft_switches_buf_head + 1) &
+            (C0X0_SOFT_SWITCHES_BUF_SIZE - 1);
+        --c0x0_soft_switches_buf_count;
+      }
+#endif
+      c0x0_soft_switches_buf[
+        (c0x0_soft_switches_buf_head + c0x0_soft_switches_buf_count++) &
+          (C0X0_SOFT_SWITCHES_BUF_SIZE - 1)
+      ] = c0x0_soft_switches;
+    }
+  }
+
+quit:
+  //vrEmu6502Destroy(cpu);
+
+  if (joystick)
+    SDL_JoystickClose(joystick);
+  SDL_CloseAudioDevice(audio_device_id);
+  SDL_DestroyRenderer(renderer);
+  SDL_DestroyWindow(window);
+  SDL_Quit();
 
   if (timing)
     fprintf(
       stderr,
       "%lu instructions executed on %lu cycles\n",
       nb_instructions,
-      cpu.cyc
+      nb_cycles
     );
   exit(exit_flag & 0xff);
 }
diff --git a/emu_z80/stty_sane.c b/emu_z80/stty_sane.c
new file mode 100644 (file)
index 0000000..00c5175
--- /dev/null
@@ -0,0 +1,243 @@
+// see https://github.com/wertarbyte/coreutils/blob/master/src/stty.c
+// this is a cut-down version which changes the data tables into inline code
+
+#include "stty_sane.h"
+
+#ifndef _POSIX_VDISABLE
+# define _POSIX_VDISABLE 0
+#endif
+
+#define Control(c) ((c) & 0x1f)
+/* Canonical values for control characters. */
+#ifndef CINTR
+# define CINTR Control ('c')
+#endif
+#ifndef CQUIT
+# define CQUIT 28
+#endif
+#ifndef CERASE
+# define CERASE 127
+#endif
+#ifndef CKILL
+# define CKILL Control ('u')
+#endif
+#ifndef CEOF
+# define CEOF Control ('d')
+#endif
+#ifndef CEOL
+# define CEOL _POSIX_VDISABLE
+#endif
+#ifndef CSTART
+# define CSTART Control ('q')
+#endif
+#ifndef CSTOP
+# define CSTOP Control ('s')
+#endif
+#ifndef CSUSP
+# define CSUSP Control ('z')
+#endif
+#if defined VEOL2 && !defined CEOL2
+# define CEOL2 _POSIX_VDISABLE
+#endif
+/* Some platforms have VSWTC, others VSWTCH.  In both cases, this control
+   character is initialized by CSWTCH, if present.  */
+#if defined VSWTC && !defined VSWTCH
+# define VSWTCH VSWTC
+#endif
+/* ISC renamed swtch to susp for termios, but we'll accept either name.  */
+#if defined VSUSP && !defined VSWTCH
+# define VSWTCH VSUSP
+# if defined CSUSP && !defined CSWTCH
+#  define CSWTCH CSUSP
+# endif
+#endif
+#if defined VSWTCH && !defined CSWTCH
+# define CSWTCH _POSIX_VDISABLE
+#endif
+
+/* SunOS 5.3 loses (^Z doesn't work) if `swtch' is the same as `susp'.
+   So the default is to disable `swtch.'  */
+#if defined __sparc__ && defined __svr4__
+# undef CSWTCH
+# define CSWTCH _POSIX_VDISABLE
+#endif
+
+#if defined VWERSE && !defined VWERASE /* AIX-3.2.5 */
+# define VWERASE VWERSE
+#endif
+#if defined VDSUSP && !defined CDSUSP
+# define CDSUSP Control ('y')
+#endif
+#if !defined VREPRINT && defined VRPRNT /* Irix 4.0.5 */
+# define VREPRINT VRPRNT
+#endif
+#if defined VREPRINT && !defined CRPRNT
+# define CRPRNT Control ('r')
+#endif
+#if defined CREPRINT && !defined CRPRNT
+# define CRPRNT Control ('r')
+#endif
+#if defined VWERASE && !defined CWERASE
+# define CWERASE Control ('w')
+#endif
+#if defined VLNEXT && !defined CLNEXT
+# define CLNEXT Control ('v')
+#endif
+#if defined VDISCARD && !defined VFLUSHO
+# define VFLUSHO VDISCARD
+#endif
+#if defined VFLUSH && !defined VFLUSHO /* Ultrix 4.2 */
+# define VFLUSHO VFLUSH
+#endif
+#if defined CTLECH && !defined ECHOCTL /* Ultrix 4.3 */
+# define ECHOCTL CTLECH
+#endif
+#if defined TCTLECH && !defined ECHOCTL        /* Ultrix 4.2 */
+# define ECHOCTL TCTLECH
+#endif
+#if defined CRTKIL && !defined ECHOKE  /* Ultrix 4.2 and 4.3 */
+# define ECHOKE CRTKIL
+#endif
+#if defined VFLUSHO && !defined CFLUSHO
+# define CFLUSHO Control ('o')
+#endif
+#if defined VSTATUS && !defined CSTATUS
+# define CSTATUS Control ('t')
+#endif
+
+void stty_sane(struct termios *attr) {
+  attr->c_cflag |= CREAD;
+
+  attr->c_iflag &= ~IGNBRK;
+  attr->c_iflag |= BRKINT;
+  attr->c_iflag &= ~INLCR;
+  attr->c_iflag &= ~IGNCR;
+  attr->c_iflag |= ICRNL;
+  attr->c_iflag &= ~IXOFF;
+#ifdef IUCLC
+  attr->c_iflag &= ~IUCLC;
+#endif
+#ifdef IXANY
+  attr->c_iflag &= ~IXANY;
+#endif
+#ifdef IMAXBEL
+  attr->c_iflag |= IMAXBEL;
+#endif
+#ifdef IUTF8
+  attr->c_iflag &= ~IUTF8;
+#endif
+
+  attr->c_oflag |= OPOST;
+#ifdef OLCUC
+  attr->c_oflag &= ~OLCUC;
+#endif
+#ifdef OCRNL
+  attr->c_oflag &= ~OCRNL;
+#endif
+#ifdef ONLCR
+  attr->c_oflag |= ONLCR;
+#endif
+#ifdef ONOCR
+  attr->c_oflag &= ~ONOCR;
+#endif
+#ifdef ONLRET
+  attr->c_oflag &= ~ONLRET;
+#endif
+#ifdef OFILL
+  attr->c_oflag &= ~OFILL;
+#endif
+#ifdef OFDEL
+  attr->c_oflag &= ~OFDEL;
+#endif
+#ifdef NLDLY
+  attr->c_oflag = (attr->c_oflag & ~NLDLY) | NL0;
+#endif
+#ifdef CRDLY
+  attr->c_oflag = (attr->c_oflag & ~CRDLY) | CR0;
+#endif
+#ifdef TABDLY
+# ifdef TAB0
+  attr->c_oflag = (attr->c_oflag & ~TABDLY) | TAB0;
+# endif
+#else
+# ifdef OXTABS
+  attr->c_oflag &= ~OXTABS;
+# endif
+#endif
+#ifdef BSDLY
+  attr->c_oflag = (attr->c_oflag & ~BSDLY) | BS0;
+#endif
+#ifdef VTDLY
+  attr->c_oflag = (attr->c_oflag & ~VTDLY) | VT0;
+#endif
+#ifdef FFDLY
+  attr->c_oflag = (attr->c_oflag & ~FFDLY) | FF0;
+#endif
+
+  attr->c_lflag |= ISIG;
+  attr->c_lflag |= ICANON;
+#ifdef IEXTEN
+  attr->c_lflag |= IEXTEN;
+#endif
+  attr->c_lflag |= ECHO;
+  attr->c_lflag |= ECHOE;
+  attr->c_lflag |= ECHOK;
+  attr->c_lflag &= ~ECHONL;
+  attr->c_lflag &= ~NOFLSH;
+#ifdef XCASE
+  attr->c_lflag &= ~XCASE;
+#endif
+#ifdef TOSTOP
+  attr->c_lflag &= ~TOSTOP;
+#endif
+#ifdef ECHOPRT
+  attr->c_lflag &= ~ECHOPRT;
+#endif
+#ifdef ECHOCTL
+  attr->c_lflag |= ECHOCTL;
+#endif
+#ifdef ECHOKE
+  attr->c_lflag |= ECHOKE;
+#endif
+
+  attr->c_cc[VINTR] = CINTR;
+  attr->c_cc[VQUIT] = CQUIT;
+  attr->c_cc[VERASE] = CERASE;
+  attr->c_cc[VKILL] = CKILL;
+  attr->c_cc[VEOF] = CEOF;
+  attr->c_cc[VEOL] = CEOL;
+#ifdef VEOL2
+  attr->c_cc[VEOL2] = CEOL2;
+#endif
+#ifdef VSWTCH
+  attr->c_cc[VSWTCH] = CSWTCH;
+#endif
+  attr->c_cc[VSTART] = CSTART;
+  attr->c_cc[VSTOP] = CSTOP;
+  attr->c_cc[VSUSP] = CSUSP;
+#ifdef VDSUSP
+  attr->c_cc[VDSUSP] = CDSUSP;
+#endif
+#ifdef VREPRINT
+  attr->c_cc[VREPRINT] = CRPRNT;
+#else
+# ifdef CREPRINT /* HPUX 10.20 needs this */
+  attr->c_cc[CREPRINT] = CRPRNT;
+# endif
+#endif
+#ifdef VWERASE
+  attr->c_cc[VWERASE] = CWERASE;
+#endif
+#ifdef VLNEXT
+  attr->c_cc[VLNEXT] = CLNEXT;
+#endif
+#ifdef VFLUSHO
+  attr->c_cc[VFLUSHO] = CFLUSHO;
+#endif
+#ifdef VSTATUS
+  attr->c_cc[VSTATUS] = CSTATUS;
+#endif
+
+  attr->c_cc[VMIN] = 1;
+  attr->c_cc[VTIME] = 0;
+}
diff --git a/emu_z80/stty_sane.h b/emu_z80/stty_sane.h
new file mode 100644 (file)
index 0000000..767e481
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef _STTY_SANE_H
+#define _STTY_SANE_H
+
+#include <termios.h>
+
+void stty_sane(struct termios *attr);
+
+#endif
index cf9aa68..410dc42 100644 (file)
@@ -1,15 +1,48 @@
-STDIN_DATA = 0
-STDOUT_DATA = 1
-STDERR_DATA = 2
-STDIN_STATUS = 3
-STDOUT_STATUS = 4
-STDERR_STATUS = 5
-USLEEP_LO = 6
-USLEEP_HI = 7
-SYS_EXIT = 8
+STDIN_DATA = 0xf0
+STDOUT_DATA = 0xf1
+STDERR_DATA = 0xf2
+STDIN_STATUS = 0xf3
+STDOUT_STATUS = 0xf4
+STDERR_STATUS = 0xf5
+USLEEP_LO = 0xf6
+USLEEP_HI = 0xf7
+SYS_EXIT = 0xf8
+
+; read
+HW_KBD = 0xe000
+
+; write
+HW_CLR80COL = 0xe000
+HW_SET80COL = 0xe001
+HW_CLR80VID = 0xe00c
+HW_SET80VID = 0xe00d
+HW_KBDSTRB = 0xe010
+
+; read/write
+HW_CLRTEXT = 0xe050
+HW_SETTEXT = 0xe051
+HW_CLRMIXED = 0xe052
+HW_SETMIXED = 0xe053
+HW_PAGE1 = 0xe054
+HW_PAGE2 = 0xe055
+HW_CLRHIRES = 0xe056
+HW_SETHIRES = 0xe057
+HW_SETDHIRES = 0xe05e
+HW_SETIOUDIS = 0xe07e
+HW_CLRIOUDIS = 0xe07f
 
        .area   text
 
+       ld      (HW_CLR80COL),a
+       ld      a,(HW_CLRIOUDIS)
+
+       ld      a,(HW_SETHIRES)
+       ld      a,(HW_CLRTEXT)
+       ld      a,(HW_SETDHIRES)
+       ld      a,(HW_CLRMIXED)
+       ld      a,(HW_PAGE1)
+       ld      (HW_SET80VID),a
+
        ld      hl,message
        ld      b,message_end - message
 print_message:
@@ -31,7 +64,7 @@ in_wait:
 
 in_char:
        in      a,(STDIN_DATA)
-       cp      4 ; EOT
+       cp      'X - 0x40 ; EOT
        jr      z,done
 
        ld      e,a