In /emu_65c02, rationalize character generator to support either CG ROM, video ROM...
authorNick Downing <nick@ndcode.org>
Sun, 19 Jun 2022 08:11:57 +0000 (18:11 +1000)
committerNick Downing <nick@ndcode.org>
Sun, 19 Jun 2022 13:14:59 +0000 (23:14 +1000)
.gitignore
emu_65c02/Makefile
emu_65c02/cg_default/Makefile [new file with mode: 0644]
emu_65c02/cg_default/cg_compile.py [new file with mode: 0755]
emu_65c02/cg_default/cg_extract.py [new file with mode: 0755]
emu_65c02/cg_default/cg_verify.py [new file with mode: 0755]
emu_65c02/cg_default/signetics2513.pdf [new file with mode: 0644]
emu_65c02/cg_rom_view.py [new file with mode: 0755]
emu_65c02/emu_65c02.c

index e269407..e6c7641 100644 (file)
@@ -28,6 +28,9 @@
 /disasm/star_blazer.asm0
 /disasm/star_blazer.dsk
 /emu_65c02/emu_65c02
+/emu_65c02/cg_default/*.pbm
+/emu_65c02/cg_default/*.png
+/emu_65c02/cg_default/cg_default.inc
 /orig/APPLE Computer and Peripheral Card Roms Collection.zip
 /orig/Apple_DOS_v3.3_1980_Apple.do
 /orig/Star_Blazer_1981_Star_Craft.do
index 7eaac48..98a1b09 100644 (file)
@@ -3,6 +3,7 @@ LDFLAGS=-g
 
 .PHONY: all
 all: \
+cg_default \
 emu_65c02 \
 Apple\ II+\ -\ 341-0011\ -\ Applesoft\ BASIC\ D000\ -\ 2716.bin \
 Apple\ II+\ -\ 341-0012\ -\ Applesoft\ BASIC\ D800\ -\ 2716.bin \
@@ -15,9 +16,15 @@ 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
 
+.PHONY: cg_default
+cg_default:
+       ${MAKE} ${MAKEFLAGS} -C $@
+
 emu_65c02: emu_65c02.o stty_sane.o vrEmu6502/src/vrEmu6502.o
        ${CC} ${LDFLAGS} -o $@ $^ -lSDL2
 
+emu_65c02.o: cg_default
+
 Apple\ II+\ -\ 341-0011\ -\ Applesoft\ BASIC\ D000\ -\ 2716.bin: \
 ../orig/APPLE\ Computer\ and\ Peripheral\ Card\ Roms\ Collection.zip
        rm -f "$@"
diff --git a/emu_65c02/cg_default/Makefile b/emu_65c02/cg_default/Makefile
new file mode 100644 (file)
index 0000000..6d33360
--- /dev/null
@@ -0,0 +1,48 @@
+# 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
+
+.PHONY: all
+all: cg_default.inc ascii1.png katakana1.png
+
+cg_default.inc: cg_ascii.png
+       ./cg_compile.py cg_default $< >$@
+
+ascii1.png: cg_ascii.png
+       ./cg_verify.py $< $@
+
+cg_ascii.png: ascii.png
+       ./cg_extract.py $< $@
+
+ascii.png: ascii.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 \
+$@
+
+ascii.pbm: signetics2513.pdf 
+       pdfimages -f 7 -l 7 $< a
+       mv a-000.pbm $@
+
+katakana1.png: cg_katakana.png
+       ./cg_verify.py $< $@
+
+cg_katakana.png: katakana.png
+       ./cg_extract.py $< $@
+
+katakana.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 $@
+
+clean:
+       rm -f *.pbm *.png cg_default.inc
diff --git a/emu_65c02/cg_default/cg_compile.py b/emu_65c02/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_65c02/cg_default/cg_extract.py b/emu_65c02/cg_default/cg_extract.py
new file mode 100755 (executable)
index 0000000..b08519d
--- /dev/null
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+
+import numpy
+import sys
+import PIL.Image
+
+EXIT_SUCCESS = 0
+EXIT_FAILURE = 1
+
+PITCH_X = 70
+PITCH_Y = 62
+
+CELL_X = 8
+CELL_Y = 6
+
+CHAR_X = 5
+CHAR_Y = 8
+
+THRESHOLD = .333
+
+if len(sys.argv) < 3:
+  print(f'usage: {sys.argv[0]} in.png out.png')
+  sys.exit(EXIT_FAILURE)
+in_png = sys.argv[1]
+out_png = sys.argv[2]
+
+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] == 8 * PITCH_X
+
+image_out = numpy.zeros((n_rows * CHAR_Y, 8 * CHAR_X), bool)
+
+for i in range(n_rows):
+  y0 = i * PITCH_Y
+  y1 = i * CHAR_Y
+  for j in range(8):
+    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(2, CELL_Y - 1)
+          for l in range(2, CELL_X - 1)
+        ],
+        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_65c02/cg_default/cg_verify.py b/emu_65c02/cg_default/cg_verify.py
new file mode 100755 (executable)
index 0000000..d1f025e
--- /dev/null
@@ -0,0 +1,69 @@
+#!/usr/bin/env python3
+
+import numpy
+import sys
+import PIL.Image
+
+EXIT_SUCCESS = 0
+EXIT_FAILURE = 1
+
+PITCH_X = 70
+PITCH_Y = 62
+
+CELL_X = 8
+CELL_Y = 6
+
+CHAR_X = 5
+CHAR_Y = 8
+
+if len(sys.argv) < 3:
+  print(f'usage: {sys.argv[0]} in.png out.png')
+  sys.exit(EXIT_FAILURE)
+in_png = sys.argv[1]
+out_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
+
+image_out = numpy.ones((n_rows * PITCH_Y, 8 * 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(8):
+    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_65c02/cg_default/signetics2513.pdf b/emu_65c02/cg_default/signetics2513.pdf
new file mode 100644 (file)
index 0000000..8028ca0
Binary files /dev/null and b/emu_65c02/cg_default/signetics2513.pdf differ
diff --git a/emu_65c02/cg_rom_view.py b/emu_65c02/cg_rom_view.py
new file mode 100755 (executable)
index 0000000..0d8a0e4
--- /dev/null
@@ -0,0 +1,69 @@
+#!/usr/bin/env python3
+
+import numpy
+import sys
+import PIL.Image
+
+EXIT_SUCCESS = 0
+EXIT_FAILURE = 1
+
+lsb_first = False # True for IIe, False for II+
+show_hi_bit = False
+while len(sys.argv) >= 2:
+  if sys.argv[1] == '--lsb_first':
+    lsb_first = True
+  elif sys.argv[1] == '--show_hi_bit':
+    show_hi_bit = True
+  else:
+    break
+  del sys.argv[1]
+if len(sys.argv) < 3:
+  print(f'usage: {sys.argv[0]} [--lsb_first] [--show_hi_bit] cg_rom.bin cg_rom.png')
+  sys.exit(EXIT_FAILURE)
+cg_rom_bin = sys.argv[1]
+cg_rom_png = sys.argv[2]
+
+with open(cg_rom_bin, 'rb') as fin:
+  data = list(fin.read())
+
+n_cols = 16
+assert len(data) % (n_cols * 8) == 0
+n_rows = len(data) // (n_cols * 8)
+
+data = (
+  (
+    numpy.array(data, numpy.uint8)[:, numpy.newaxis] >>
+      (
+        (
+          numpy.arange(8, dtype = numpy.int32)
+        if lsb_first else
+          numpy.arange(7, -1, -1, dtype = numpy.int32)
+        )
+      if show_hi_bit else
+        (
+          numpy.arange(7, dtype = numpy.int32)
+        if lsb_first else
+          numpy.arange(6, -1, -1, dtype = numpy.int32)
+        )
+      )[numpy.newaxis, :]
+  ) & 1
+).astype(bool)
+
+image = numpy.concatenate(
+  [
+    numpy.concatenate(
+      [
+        data[(i * n_cols + j) * 8:(i * n_cols + j + 1) * 8, :]
+        for j in range(n_cols)
+      ],
+      1
+    )
+    for i in range(n_rows)
+  ],
+  0
+)
+
+image_pil = PIL.Image.new('P', (image.shape[1], image.shape[0]))
+image_pil.frombytes(image.tobytes())
+image_pil.putpalette([0, 0, 0, 0xff, 0xff, 0xff])
+image_pil.save(cg_rom_png)
index 36f3e9d..b4d746f 100644 (file)
@@ -9,12 +9,13 @@
 #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 "vrEmu6502/src/vrEmu6502.h"
 
-#define APPLE_IIE 1
+#define APPLE_IIE 0
 #define APPLE_WIDTH 560
 #define APPLE_HEIGHT 192
 #define APPLE_TEXT1 0x400
@@ -148,15 +149,17 @@ uint32_t mono_palette[2] = {0xff000000, 0xffffffff};
 
 VrEmu6502 *cpu;
 
-#if APPLE_IIE
+#include "cg_default/cg_default.inc"
+
+#define CG_ROM_SIZE 0x800
+uint8_t *cg_rom;
+
 #define VIDEO_ROM_SIZE 0x1000
-uint8_t video_rom[VIDEO_ROM_SIZE];
+uint8_t *video_rom;
 
+#if APPLE_IIE
 #define MEM_SIZE 0x24000
 #else
-#define CG_ROM_SIZE 0x800
-uint8_t cg_rom[CG_ROM_SIZE];
-
 #define MEM_SIZE 0x14000
 #endif
 uint8_t mem[MEM_SIZE];
@@ -910,6 +913,7 @@ void termios_atexit(void) {
 
 int main(int argc, char **argv) {
   int argn = 1;
+  char *cg_rom_file = NULL;
 #if APPLE_IIE
   char *video_rom_file =
     "Apple IIe Video Enhanced - 342-0265-A - 2732.bin";
@@ -917,32 +921,8 @@ int main(int argc, char **argv) {
     "Apple IIe CD Enhanced - 342-0304-A - 2764.bin";
   char *ef_rom_file =
     "Apple IIe EF Enhanced - 342-0303-A - 2764.bin";
-  bool timing = false;
-  while (argn < argc) {
-    if (strcmp(argv[argn], "--help") == 0) {
-      printf("usage: %s [--video_rom=file.bin] [--cd_rom=file.bin] [--ef_rom=file.bin] [--timing] [program.obj[,aNNNN] ...] [-- child_executable [child_argument ...]]\n", argv[0]);
-      exit(EXIT_FAILURE);
-    }
-    if (memcmp(argv[argn], "--video_rom=", 12) == 0)
-      video_rom_file = argv[argn] + 12;
-    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 if (strcmp(argv[argn], "--timing") == 0)
-      timing = true;
-    else
-      break;
-    ++argn;
-  }
-
-  rom_load(video_rom_file, video_rom, VIDEO_ROM_SIZE);
-
-  rom_load(cd_rom_file, mem + 0x20000, 0x2000);
-  rom_load(ef_rom_file, mem + 0x22000, 0x2000);
 #else
-  char *cg_rom_file =
-    "Apple II+ - 7341-0036 - Character Generator Rev7+ - 2716.bin";
+  char *video_rom_file = NULL;
   char *d0_rom_file =
     "Apple II+ - 341-0011 - Applesoft BASIC D000 - 2716.bin";
   char *d8_rom_file =
@@ -955,26 +935,56 @@ int main(int argc, char **argv) {
     "Apple II+ - 341-0015 - Applesoft BASIC F000 - 2716.bin";
   char *f8_rom_file =
     "Apple II+ - 341-0020 - Applesoft BASIC Autostart Monitor F800 - 2716.bin";
+#endif
   bool timing = false;
   while (argn < argc) {
     if (strcmp(argv[argn], "--help") == 0) {
-      printf("usage: %s [--cg_rom=file.bin] [--d0_rom=file.bin] [--d8_rom=file.bin] [--e0_rom=file.bin] [--e8_rom=file.bin] [--f0_rom=file.bin] [--f8_rom=file.bin] [--timing] [program.obj[,aNNNN] ...] [-- child_executable [child_argument ...]]\n", argv[0]);
+      printf("usage: %s [--no-cg-rom|--cg-rom=file.bin] [--no-video-rom|--video-rom=file.bin] [--no-NN-rom|--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)
+    if (strcmp(argv[argn], "--no-cg-rom") == 0)
+      cg_rom_file = NULL;
+    else if (memcmp(argv[argn], "--cg-rom=", 9) == 0)
       cg_rom_file = argv[argn] + 9;
-    else if (memcmp(argv[argn], "--d0_rom=", 9) == 0)
+    else if (strcmp(argv[argn], "--no-video-rom") == 0)
+      video_rom_file = NULL;
+    else if (memcmp(argv[argn], "--video_rom=", 12) == 0)
+      video_rom_file = argv[argn] + 12;
+#if APPLE_IIE
+    else if (strcmp(argv[argn], "--no-cd-rom") == 0)
+      cd_rom_file = NULL;
+    else if (memcmp(argv[argn], "--cd-rom=", 9) == 0)
+      cd_rom_file = argv[argn] + 9;
+    else if (strcmp(argv[argn], "--no-ef-rom") == 0)
+      ef_rom_file = NULL;
+    else if (memcmp(argv[argn], "--ef-rom=", 9) == 0)
+      ef_rom_file = argv[argn] + 9;
+#else
+    else if (strcmp(argv[argn], "--no-d0-rom") == 0)
+      d0_rom_file = NULL;
+    else if (memcmp(argv[argn], "--d0-rom=", 9) == 0)
       d0_rom_file = argv[argn] + 9;
-    else if (memcmp(argv[argn], "--d8_rom=", 9) == 0)
+    else if (strcmp(argv[argn], "--no-d8-rom") == 0)
+      d8_rom_file = NULL;
+    else if (memcmp(argv[argn], "--d8-rom=", 9) == 0)
       d8_rom_file = argv[argn] + 9;
-    else if (memcmp(argv[argn], "--e0_rom=", 9) == 0)
+    else if (strcmp(argv[argn], "--no-e0-rom") == 0)
+      e0_rom_file = NULL;
+    else if (memcmp(argv[argn], "--e0-rom=", 9) == 0)
       e0_rom_file = argv[argn] + 9;
-    else if (memcmp(argv[argn], "--e8_rom=", 9) == 0)
+    else if (strcmp(argv[argn], "--no-e8-rom") == 0)
+      e8_rom_file = NULL;
+    else if (memcmp(argv[argn], "--e8-rom=", 9) == 0)
       e8_rom_file = argv[argn] + 9;
-    else if (memcmp(argv[argn], "--f0_rom=", 9) == 0)
+    else if (strcmp(argv[argn], "--no-f0-rom") == 0)
+      f0_rom_file = NULL;
+    else if (memcmp(argv[argn], "--f0-rom=", 9) == 0)
       f0_rom_file = argv[argn] + 9;
-    else if (memcmp(argv[argn], "--f8_rom=", 9) == 0)
+    else if (strcmp(argv[argn], "--no-f8-rom") == 0)
+      f8_rom_file = NULL;
+    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
@@ -982,8 +992,34 @@ int main(int argc, char **argv) {
     ++argn;
   }
 
-  rom_load(cg_rom_file, cg_rom, CG_ROM_SIZE);
+  // 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
+      for (int i = 0; i < 4; ++i)
+        memcpy(cg_rom + (i << 9), cg_default, CG_DEFAULT_SIZE);
+  }
 
+#if APPLE_IIE
+  rom_load(cd_rom_file, mem + 0x20000, 0x2000);
+  rom_load(ef_rom_file, mem + 0x22000, 0x2000);
+#else
   memset(mem + 0x10000, 0xff, 0x1000);
   rom_load(d0_rom_file, mem + 0x11000, 0x800);
   rom_load(d8_rom_file, mem + 0x11800, 0x800);
@@ -1317,6 +1353,26 @@ int main(int argc, char **argv) {
     SDL_RenderClear(renderer);
 
     // draw
+    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);
@@ -1395,29 +1451,33 @@ int main(int argc, char **argv) {
         ) | base;
 #endif
         for (int j = 0; j < 40; ++j) {
-#if APPLE_IIE // inverted, LSB first
-          int data = video_rom[(mem[line + j] << 3) | row];
-          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);
+          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;
             }
-            data >>= 1;
           }
-#else // MSB first
-          int data = cg_rom[(mem[line + j] << 3) | row];
-          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);
+          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;
             }
-            data <<= 1;
           }
-#endif
         }
       }