+#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);
}
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);
}
case USLEEP_LO:
return usleep_lo;
+ case DOS_LO:
+ return dos_lo;
}
return 0xff;
}
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);
}
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;
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);
}