1// 2026 Applied Engineering Serial Pro peripheral for RetroAppleJS. 2// Firmware target: Serial Pro v2.0. 3// 4// EMU_CARD_serialpro.js 5// 6// Implemented in this stage: 7// - authoritative 8 KiB 27C64 EPROM image 8// - slot-dependent $Cn00-$CnFF ROM directly from that EPROM 9// - shared $C800-$CFFF expansion-ROM ownership/release behavior 10// - Applied Engineering EPROM bank-register clear/write switches 11// - 6818-compatible RTC / battery RAM address-data interface 12// - live clock initialized from the browser host's local date/time 13// - Stage 2A minimal legacy-6551 ACIA register interface 14// - byte-stream bridge between the 6551 and the Serial Pro oTERM endpoint 15// - Stage 2C one-byte 6551 receiver holding register / RDRF behavior 16// - Stage 2D command/control decoding, modem lines and IRQ generation 17// - cycle-paced 6551 character timing with optional CPU-speed synchronization 18// - Stage 3A optional browser Web Serial transport for physical USB-UARTs 19// - 6818 update-ended interrupt generation (UIE/UF/IRQF) on Apple II IRQ 20// 21// Still intentionally deferred: 22// - 6818 periodic/alarm interrupts and NMI routing 23// - browser-persistent battery RAM across page reloads 24// - detailed 6551 receiver-overrun timing 25// - bit-level parity/framing error injection/validation against an external endpoint 26// - serial echo/break waveform timing and modem-line UI controls 27// 28// The physical EPROM layout recovered from the Serial Pro v2.0 27C64 is: 29// $0000-$07FF HostROM bank 0 30// $0800-$0FFF HostROM bank 1 31// $1000-$17FF HostROM bank 2 32// $1800-$18FF copyright / identification page 33// $1900-$1FFF seven 256-byte slot-specific ROM pages (slots 1..7) 34// 35// SHA-1 4f878637ef7f5fa83713482f85369fa72121f898 36// SHA-256 b2346ad096bbfddfb1fac5ad353e325b892fb248d7719714b245560e51e6dc35 37 38 39const SERIALPRO_EPROM = new Uint8Array([ 40 // EPROM $0000-$07FF: HOSTROM BANK 0 41 0x4C,0x19,0xC8,0x08,0x78,0xA9,0x00,0x48, 42 0x20,0x13,0xC8,0x68,0xA4,0x26,0x99,0x87, 43 0xC0,0x28,0x60,0x8A,0xA4,0x26,0x99,0x87, 44 0xC0,0x20,0x57,0xC8,0xA9,0x16,0x20,0x64, 45 0xC8,0xA2,0x00,0x60,0x20,0x57,0xC8,0x4A, 46 0x20,0x85,0xCC,0xBD,0xB8,0x05,0xAA,0x60, 47 0xA5,0x27,0x09,0x80,0xC9,0xA0,0x90,0x08, 48 0x49,0xFF,0xF0,0x08,0xFE,0xB8,0x06,0x60, 49 0x49,0x88,0xD0,0x08,0xDE,0xB8,0x06,0x10, 50 0x03,0x9D,0xB8,0x06,0x60,0x20,0xC0,0xC8, 51 0xAE,0xF8,0x07,0x9D,0xB8,0x05,0x60,0x48, 52 0x84,0x26,0x8E,0xF8,0x07,0xA9,0x00,0x9D, 53 0xB8,0x05,0x68,0x60,0x48,0xA2,0x01,0x20, 54 0x03,0xC8,0xAE,0xF8,0x07,0xA9,0x28,0x20, 55 0x5F,0xCC,0x9D,0x38,0x07,0xA9,0x26,0x20, 56 0x5F,0xCC,0x9D,0xB8,0x03,0xA9,0x22,0x20, 57 0x5F,0xCC,0xF0,0x1C,0x4A,0x90,0x0A,0x4A, 58 0xA9,0x03,0xB0,0x02,0xA9,0x80,0x9D,0xB8, 59 0x04,0xA9,0x25,0x20,0x5F,0xCC,0x9D,0x38, 60 0x06,0x68,0x29,0x95,0x48,0xA9,0x09,0x2C, 61 0xA9,0x01,0x9D,0x38,0x05,0x68,0x1D,0x38, 62 0x07,0x9D,0x38,0x07,0xA9,0x23,0x20,0x5F, 63 0xCC,0x99,0x8B,0xC0,0xA9,0x24,0x20,0x5F, 64 0xCC,0x99,0x8A,0xC0,0xB9,0x88,0xC0,0x60, 65 0x20,0x57,0xC8,0x20,0x54,0xCC,0x29,0x7F, 66 0xB0,0x08,0x85,0x27,0x20,0x57,0xC8,0x20, 67 0xB8,0xCB,0xAC,0xF8,0x07,0xBE,0xB8,0x05, 68 0x60,0x50,0x2D,0xBD,0x38,0x05,0x29,0x7F, 69 0x9D,0x38,0x05,0xA9,0x1F,0x39,0x8A,0xC0, 70 0xD0,0x05,0xA9,0x66,0x20,0x64,0xC8,0xE4, 71 0x37,0xD0,0x0A,0xA9,0x07,0xC5,0x36,0xF0, 72 0x04,0x85,0x36,0xD0,0x0D,0xE4,0x39,0xD0, 73 0x09,0xA9,0x05,0x85,0x38,0x4C,0xAD,0xC9, 74 0xB0,0xFB,0x4C,0x71,0xC9,0xBD,0x38,0x07, 75 0x29,0x0A,0xC9,0x0A,0xD0,0x14,0xA4,0x27, 76 0xBD,0xB8,0x04,0x29,0x7F,0xC0,0x92,0xF0, 77 0x06,0x09,0x80,0xC0,0x94,0xD0,0x03,0x9D, 78 0xB8,0x04,0x60,0x00,0x00,0x00,0x00,0x00, 79 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 80 0x00,0x00,0x00,0x00,0x00,0x2C,0x58,0xFF, 81 0x50,0xB8,0xA9,0x00,0xAE,0xF8,0x07,0x9D, 82 0xB8,0x05,0x8A,0x0A,0x0A,0x0A,0x0A,0x85, 83 0x26,0x50,0x0F,0x20,0x7B,0xCC,0x90,0xFB, 84 0xA0,0x00,0x91,0x3C,0x20,0xBA,0xFC,0x90, 85 0xF2,0x60,0xA0,0x00,0xB1,0x3C,0x85,0x27, 86 0x20,0x6D,0xCC,0x20,0xBA,0xFC,0x90,0xF2, 87 0x60,0xBD,0xB8,0x04,0x0A,0x10,0x12,0x6A, 88 0x29,0xBF,0x9D,0xB8,0x04,0x68,0x48,0xAA, 89 0xA5,0x27,0x09,0x20,0x9D,0x00,0x02,0x85,
90 0x27,0x20,0x88,0xCA,0xF0,0x55,0x20,0xB8, 91 0xCB,0x20,0x88,0xCA,0xF0,0x08,0xBD,0xB8, 92 0x04,0x10,0x03,0x20,0xB4,0xCA,0x68,0xAA, 93 0x68,0xA8,0xA5,0x27,0x60,0x00,0x00,0x00, 94 0x00,0x00,0x4C,0xCA,0xC8,0x20,0x88,0xCA, 95 0xF0,0x0F,0xBD,0xB8,0x06,0x10,0x05,0x5E, 96 0xB8,0x06,0xD0,0x0A,0x20,0xBE,0xCC,0xB0, 97 0x0B,0x20,0x54,0xCB,0x90,0xF6,0x20,0x32, 98 0xCC,0x20,0x0D,0xC9,0xA5,0x27,0xC9,0xE0, 99 0x90,0xBF,0xBD,0xB8,0x03,0x29,0xC0,0xF0, 100 0xB8,0xBD,0xB8,0x04,0x09,0x40,0x9D,0xB8, 101 0x04,0xD0,0xAE,0x20,0x6B,0xCB,0xB0,0xB6, 102 0xA5,0x27,0x48,0xBD,0x38,0x07,0x29,0xC0, 103 0xD0,0x16,0xA5,0x24,0xF0,0x33,0xC9,0x08, 104 0xF0,0x04,0xC9,0x10,0xD0,0x0A,0x09,0xF0, 105 0x3D,0xB8,0x06,0x18,0x65,0x24,0x85,0x24, 106 0xBD,0xB8,0x06,0xC5,0x24,0xF0,0x1A,0xA9, 107 0xA0,0x90,0x08,0xBD,0x38,0x07,0x0A,0x10, 108 0x10,0xA9,0x88,0x85,0x27,0x20,0x30,0xC8, 109 0x20,0xC5,0xCB,0x20,0x75,0xCA,0x4C,0x08, 110 0xCA,0x68,0x20,0x4B,0xCA,0xBD,0x38,0x07, 111 0x30,0x16,0xBC,0xB8,0x06,0x0A,0x30,0x0E, 112 0x98,0xA0,0x00,0x38,0xFD,0x38,0x06,0xC9, 113 0xF8,0x90,0x03,0x69,0x27,0xA8,0x84,0x24, 114 0x4C,0x9E,0xC9,0x85,0x27,0x48,0xBD,0xB8, 115 0x04,0x29,0x30,0xF0,0x0C,0xC9,0x20,0xA5, 116 0x27,0x29,0x7F,0x90,0x02,0x09,0x80,0x85, 117 0x27,0x20,0xBD,0xCB,0x20,0x30,0xC8,0x68, 118 0x85,0x27,0x49,0x8D,0x0A,0xD0,0x06,0x9D, 119 0xB8,0x06,0x85,0x24,0x60,0xBD,0xB8,0x04, 120 0x10,0x0D,0xBD,0x38,0x06,0xF0,0x08,0x18, 121 0xFD,0xB8,0x06,0xA9,0x8D,0x90,0xC4,0x60, 122 0xAE,0xF8,0x07,0xBD,0x38,0x07,0x29,0x02, 123 0x60,0x70,0x13,0x90,0x08,0xA2,0x02,0x20, 124 0x03,0xC8,0x4C,0x9E,0xC9,0xA2,0x0A,0x20, 125 0x03,0xC8,0x68,0x4C,0xA0,0xC9,0xA2,0x12, 126 0x20,0x03,0xC8,0x4C,0x9E,0xC9,0xA2,0x09, 127 0x20,0x03,0xC8,0x60,0xA9,0x7F,0x3D,0x38, 128 0x07,0x9D,0x38,0x07,0xA9,0x00,0x48,0x20, 129 0xDB,0xCA,0x68,0x90,0x0E,0xA8,0x20,0x14, 130 0xCB,0x98,0x90,0xF2,0x48,0x20,0xBD,0xCB, 131 0x4C,0xBF,0xCA,0x20,0x32,0xCC,0xA9,0x8D, 132 0x85,0x27,0x60,0xA4,0x24,0xB1,0x28,0x85, 133 0x27,0xA5,0x4F,0x29,0x03,0xD0,0x0E,0xA4, 134 0x24,0xA9,0xDF,0xD1,0x28,0xD0,0x02,0xA5, 135 0x27,0x91,0x28,0xE6,0x4F,0xBD,0xB8,0x04, 136 0x0A,0x90,0x0C,0x20,0x54,0xCB,0xB0,0x08, 137 0x20,0xBE,0xCC,0x90,0xDC,0x70,0xD4,0x60, 138 0x20,0x32,0xCC,0x20,0x0D,0xC9,0x20,0x12, 139 0xCD,0x4C,0xDB,0xCA,0xBD,0x38,0x07,0x0A, 140 0x10,0x2D,0xA5,0x27,0xC9,0x9B,0xD0,0x07, 141 0x88,0x10,0x02,0xA0,0x02,0x18,0x60,0xC0, 142 0x01,0x90,0x1C,0xF0,0x0C,0xC9,0xC1,0x90, 143 0x16,0xC9,0xDB,0xB0,0x12,0x09,0x20,0xD0, 144 0x0A,0x49,0xB0,0xC9,0x0B,0xB0,0x06,0xA8, 145 0xB9,0x49,0xCB,0x85,0x27,0xA0,0x02,0x38, 146 0x60,0x9B,0x9C,0x9F,0xDB,0xDC,0xDF,0xFB, 147 0xFC,0xFD,0xFE,0xFF,0x20,0x7B,0xCC,0x90, 148 0x11,0x09,0x80,0xC9,0x8A,0xD0,0x0A,0x18, 149 0xBD,0x38,0x07,0x29,0x20,0xD0,0x03,0xA9, 150 0x8A,0x38,0x60,0xA9,0x22,0x20,0x5F,0xCC, 151 0x4A,0xB0,0x3A,0xBD,0xB8,0x04,0x29,0x07, 152 0xF0,0x05,0x20,0x77,0xCD,0x38,0x60,0xA5, 153 0x27,0x29,0x7F,0xDD,0x38,0x05,0xD0,0x09, 154 0xFE,0xB8,0x04,0x8A,0x48,0xA9,0x8F,0x48, 155 0x60,0xBD,0x38,0x07,0x29,0x08,0xF0,0x15, 156 0x20,0x54,0xCB,0x90,0x10,0xC9,0x93,0xF0, 157 0x0E,0x48,0xBD,0x38,0x07,0x4A,0x4A,0x68, 158 0x90,0x04,0x9D,0xB8,0x06,0x18,0x60,0x20, 159 0x54,0xCC,0xC9,0x91,0xD0,0xF9,0x18,0x60, 160 0x20,0x6B,0xCB,0xB0,0xFA,0xBD,0x38,0x07, 161 0x10,0x03,0x20,0x12,0xCD,0xA5,0x27,0x48, 162 0xA9,0x22,0x20,0x5F,0xCC,0xC9,0x03,0xD0, 163 0x45,0xBD,0x38,0x04,0xC9,0x99,0xB0,0x10, 164 0xC9,0x95,0x90,0x22,0xC9,0x96,0x68,0x48, 165 0x29,0x7F,0x49,0x1B,0xD0,0x18,0x90,0x19, 166 0xBD,0xB8,0x04,0x29,0x1F,0x09,0x80,0x85, 167 0x27,0x20,0x6D,0xCC,0x20,0x54,0xCC,0x49, 168 0x86,0xD0,0xF6,0x9D,0x38,0x04,0xFE,0x38, 169 0x04,0x68,0x48,0x85,0x27,0x49,0x8D,0x0A, 170 0xD0,0x0C,0xBD,0xB8,0x03,0x29,0x30,0xF0, 171 0x05,0xA9,0xC8,0x9D,0x38,0x04,0x20,0x6D, 172 0xCC,0x20,0xFC,0xCE,0xBD,0x38,0x07,0x4A, 173 0xA5,0x27,0x2A,0xC9,0x1B,0xD0,0x07,0xA9, 174 0x8A,0x85,0x27,0x20,0xC5,0xCB,0x68,0x85, 175 0x27,0x60,0x48,0xA4,0x24,0xA5,0x27,0x91, 176 0x28,0x68,0xC9,0x95,0xD0,0x13,0xA5,0x27, 177 0xC9,0x20,0xB0,0x0D,0xBC,0xB8,0x03,0x10, 178 0x08,0x49,0xC0,0xC0,0xC0,0xB0,0x02,0x49, 179 0x20,0x85,0x27,0x60,0x20,0x54,0xCB,0xB0, 180 0x05,0x20,0xAC,0xCC,0x90,0xF6,0x60,0x08, 181 0x78,0xA4,0x26,0x99,0x84,0xC0,0xB9,0x85, 182 0xC0,0x28,0x09,0x00,0x60,0x18,0x20,0x85, 183 0xCC,0x90,0xFA,0x8A,0x48,0xA9,0x8B,0x48, 184 0xA5,0x27,0x60,0x38,0x20,0x85,0xCC,0x90, 185 0x03,0xB9,0x88,0xC0,0x60,0xA4,0x26,0xB9, 186 0x89,0xC0,0x90,0x18,0x29,0x2F,0xC9,0x20, 187 0x90,0x06,0xC9,0x28,0x09,0x08,0xD0,0x08, 188 0xC9,0x08,0x29,0x07,0xF0,0x05,0x09,0x20, 189 0x9D,0xB8,0x05,0x60,0x29,0x70,0xC9,0x10, 190 0xF0,0x01,0x18,0x60,0x18,0xBD,0x38,0x07, 191 0x29,0x04,0xF0,0x09,0xAD,0x00,0xC0,0x10, 192 0x04,0x8D,0x10,0xC0,0x38,0x60,0xE6,0x4E, 193 0xD0,0x02,0xE6,0x4F,0x20,0xAC,0xCC,0xB8, 194 0x90,0x3F,0x20,0x32,0xCC,0x20,0x6B,0xCB, 195 0x90,0x36,0xA0,0x07,0xB9,0x0A,0xCD,0x85, 196 0x27,0x98,0x8D,0x10,0xC0,0x48,0x20,0x12, 197 0xCD,0x68,0xA8,0x88,0x10,0xEE,0xAD,0x00, 198 0xC0,0x10,0xFB,0x8D,0x10,0xC0,0xC9,0x88, 199 0xF0,0xE0,0x85,0x27,0x20,0x12,0xCD,0x20, 200 0x6B,0xCB,0xBD,0xB8,0x04,0x29,0x07,0xD0, 201 0xE5,0xA9,0x8D,0x85,0x27,0x2C,0x58,0xFF, 202 0x38,0x60,0xBA,0xCF,0xD2,0xD0,0xA0,0xC5, 203 0xC1,0x8D,0xBD,0x38,0x07,0x29,0x02,0xF0, 204 0x0D,0xBD,0xB8,0x04,0x29,0x38,0xF0,0x06, 205 0x8A,0x48,0xA9,0x67,0x48,0x60,0xBD,0xB8, 206 0x03,0x2A,0x2A,0x2A,0x29,0x03,0xA8,0xA5, 207 0x27,0x09,0x80,0xC0,0x01,0xF0,0x16,0xC9, 208 0xC0,0x90,0x12,0xC9,0xE0,0x90,0x08,0x49, 209 0x20,0xC0,0x02,0xD0,0x08,0xF0,0x04,0xC0, 210 0x03,0xD0,0x02,0x29,0x1F,0x4C,0xF6,0xFD, 211 0xAE,0xF8,0x07,0xBD,0x38,0x06,0x85,0x36, 212 0xBD,0xB8,0x04,0x4A,0x4A,0x4A,0x29,0x07, 213 0x09,0xC0,0x85,0x37,0x60,0xA5,0x36,0x9D, 214 0x38,0x06,0x68,0x85,0x36,0x68,0x85,0x37, 215 0x68,0x85,0x38,0x68,0x85,0x39,0x60,0xA8, 216 0xA5,0x27,0x29,0x7F,0xC9,0x20,0xF0,0x29, 217 0xC9,0x0D,0xD0,0x13,0x20,0x11,0xCE,0xC0, 218 0x04,0xF0,0x04,0xC0,0x05,0xD0,0x1A,0xA9, 219 0x2C,0x20,0x5F,0xCC,0x4C,0x8E,0xCE,0xC9,
220 0x60,0x90,0x02,0x29,0x5F,0x88,0xF0,0x0A, 221 0x88,0xF0,0x2D,0x88,0xF0,0x4F,0x88,0xF0, 222 0x79,0x60,0xC9,0x20,0xB0,0x10,0xDD,0x38, 223 0x05,0xD0,0x06,0xDE,0xB8,0x04,0x4C,0x6D, 224 0xCC,0x9D,0x38,0x05,0xD0,0x53,0x49,0x30, 225 0xC9,0x0A,0xB0,0x08,0xFE,0xB8,0x04,0xA0, 226 0x2B,0x4C,0x85,0xCE,0xA0,0xFD,0xD0,0x6D, 227 0x49,0x30,0xC9,0x0A,0xB0,0x1B,0x48,0xA9, 228 0x2B,0x20,0x5F,0xCC,0x0A,0xBA,0x48,0x0A, 229 0x0A,0x7D,0x00,0x01,0xE8,0x7D,0x00,0x01, 230 0x9A,0xAE,0xF8,0x07,0xA0,0x2B,0x4C,0x85, 231 0xCE,0xA0,0x3A,0xD0,0x48,0x4A,0x49,0x22, 232 0xD0,0x3B,0xA9,0x2C,0x20,0x5F,0xCC,0x90, 233 0x05,0x1D,0x38,0x07,0xD0,0x05,0x49,0xFF, 234 0x3D,0x38,0x07,0x9D,0x38,0x07,0xA9,0x06, 235 0x2C,0xA9,0x00,0xAE,0xF8,0x07,0x5D,0xB8, 236 0x04,0x29,0x07,0x5D,0xB8,0x04,0x9D,0xB8, 237 0x04,0x60,0xA0,0x04,0xD9,0xF2,0xCE,0xF0, 238 0x04,0x88,0x10,0xF8,0x60,0xAD,0xF8,0x07, 239 0x48,0xA9,0x93,0x48,0x60,0xA9,0x20,0x9D, 240 0xB8,0x05,0x4C,0x0E,0xCE,0xA5,0x27,0x29, 241 0x7F,0xC9,0x60,0x90,0x02,0x29,0x5F,0x85, 242 0x27,0xC8,0xC8,0xC8,0xB9,0x96,0xCE,0xF0, 243 0xE4,0x45,0x27,0x0A,0xD0,0xF3,0xB9,0x97, 244 0xCE,0x29,0x20,0xD0,0x07,0xBD,0x38,0x07, 245 0x29,0x10,0xD0,0xE5,0xBD,0x38,0x07,0x4A, 246 0x4A,0xB9,0x97,0xCE,0x90,0x01,0x0A,0x10, 247 0xD8,0xB9,0x97,0xCE,0x20,0x13,0xCE,0xB9, 248 0x97,0xCE,0x29,0x10,0xC9,0x10,0xB9,0x98, 249 0xCE,0xB0,0x0B,0xA0,0x2C,0x48,0xAD,0xF8, 250 0x07,0x48,0xA9,0x83,0x48,0x60,0xAA,0x20, 251 0x03,0xC8,0xAE,0xF8,0x07,0x60,0x3F,0xF0, 252 0x09,0x42,0x65,0x69,0x43,0x85,0x51,0x45, 253 0xE3,0x80,0x46,0xE3,0x04,0x47,0xA4,0x1A, 254 0x48,0x90,0x32,0x49,0x90,0x71,0x4B,0x90, 255 0x79,0x4C,0xE3,0x01,0x4D,0xE3,0x20,0x51, 256 0x45,0x61,0x52,0xC5,0x59,0x53,0x43,0x40, 257 0x53,0x90,0x22,0x54,0x45,0x51,0x54,0x83, 258 0x40,0x57,0x90,0x2A,0x58,0xE3,0x08,0x5A, 259 0xE5,0x89,0x00,0x42,0xF6,0x39,0x43,0xF6, 260 0x19,0x44,0xF6,0x49,0x46,0xF6,0x29,0x48, 261 0x90,0x3A,0x4C,0xF6,0x21,0x4E,0x90,0x81, 262 0x50,0xF6,0x41,0x53,0x56,0x31,0x54,0xD6, 263 0x11,0x00,0x53,0x49,0x32,0x4D,0x52,0x80, 264 0x40,0x20,0x02,0x01,0x09,0x80,0xA8,0xBD, 265 0xB8,0x03,0xC0,0x8C,0xF0,0x0C,0xC0,0x8A, 266 0xF0,0x06,0xC0,0x8D,0xD0,0x15,0x4A,0x4A, 267 0x4A,0x4A,0x29,0x03,0xA8,0xB9,0x24,0xCF, 268 0xF0,0x09,0xA8,0xA9,0x08,0x20,0x28,0xCF, 269 0x88,0xD0,0xF8,0x60,0x00,0x04,0x20,0xFA, 270 0x48,0xA9,0x90,0x38,0xE9,0x01,0xD0,0xFB, 271 0x68,0xE9,0x01,0xD0,0xF3,0x60,0xFF,0xFF, 272 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 273 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 274 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 275 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 276 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 277 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 278 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 279 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 280 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 281 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 282 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 283 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 284 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 285 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 286 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 287 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 288 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 289 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 290 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 291 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 292 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 293 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 294 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 295 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 296 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 297 // EPROM $0800-$0FFF: HOSTROM BANK 1 298 0x00,0x00,0x00,0x08,0x78,0xA9,0x01,0x48, 299 0x20,0x13,0xC8,0x68,0xA4,0x26,0x99,0x87, 300 0xC0,0x28,0x60,0x8A,0xA4,0x26,0x99,0x87, 301 0xC0,0x8A,0x4A,0x4A,0x29,0xFE,0xAA,0xBD, 302 0x2C,0xC8,0x48,0xBD,0x2B,0xC8,0x48,0xAE, 303 0xF8,0x07,0x60,0x4E,0xC8,0xAF,0xC8,0x0A, 304 0xCF,0x14,0xCF,0x1F,0xCF,0x28,0xCF,0x3A, 305 0xCF,0x59,0xCF,0x75,0xCF,0x7C,0xCF,0x8F, 306 0xCF,0x96,0xCF,0xA2,0xCF,0xAB,0xCF,0xC8, 307 0xCF,0xD4,0xCF,0xDD,0xCF,0xEB,0xCF,0xA9, 308 0x20,0x20,0x91,0xC8,0xC9,0xAE,0xD0,0x09, 309 0xA9,0x21,0x20,0x91,0xC8,0xC9,0x55,0xF0, 310 0x1C,0xA0,0x20,0xB9,0x7A,0xC8,0x20,0xA7, 311 0xC8,0xC8,0xC0,0x2D,0x90,0xF5,0xA0,0x0A, 312 0xA9,0x20,0x20,0xA7,0xC8,0xC8,0xA9,0x02, 313 0x20,0xA7,0xC8,0xA4,0x26,0xA9,0x00,0x9D, 314 0xB8,0x04,0x9D,0x38,0x04,0x9D,0xB8,0x05, 315 0x9D,0x38,0x06,0x9D,0xB8,0x06,0x60,0xA9, 316 0x2B,0xA4,0x26,0x99,0x84,0xC0,0xB9,0x85, 317 0xC0,0x60,0xAE,0x55,0x02,0x1E,0x0B,0x50, 318 0x00,0x00,0x09,0x06,0x00,0x00,0x00,0x48, 319 0xAD,0xF8,0x07,0x48,0xA9,0x83,0x48,0x60, 320 0x8D,0x0C,0xC0,0x8D,0x00,0xC0,0x8D,0x0E, 321 0xC0,0x20,0x2F,0xFB,0xA4,0x26,0xA9,0x00, 322 0x99,0x8A,0xC0,0x20,0x97,0xCF,0x20,0x84, 323 0xFE,0x20,0x58,0xFC,0x20,0x4F,0xC8,0xA9, 324 0x00,0x85,0x27,0xA9,0x9C,0xA0,0xCC,0x20, 325 0x54,0xCB,0xAD,0xF8,0x07,0x49,0x70,0x20, 326 0xED,0xFD,0xA9,0x14,0x85,0x25,0xA9,0xC2, 327 0xA0,0xCC,0x20,0x54,0xCB,0x20,0xB1,0xC9, 328 0xAD,0x00,0xC0,0x10,0xF8,0x8D,0x10,0xC0, 329 0xC9,0xE0,0x90,0x02,0x29,0xDF,0xA4,0x27, 330 0xC9,0xC9,0xF0,0x68,0xC9,0x8B,0xF0,0x64, 331 0xC9,0xCD,0xF0,0x72,0xC9,0x8D,0xF0,0x6E, 332 0xC9,0x8A,0xF0,0x6A,0xC9,0xCA,0xF0,0x70, 333 0xC9,0x88,0xF0,0x6C,0xC9,0xCB,0xF0,0x71, 334 0xC9,0x95,0xF0,0x6D,0xC9,0xA0,0xD0,0x1D, 335 0xC0,0x1E,0xD0,0x19,0x20,0xE8,0xCA,0x20, 336 0x2D,0xCA,0x20,0x47,0xCB,0x48,0xA9,0x00, 337 0x20,0xD6,0xCA,0x68,0xC9,0x30,0x90,0x05, 338 0xA0,0x1C,0x4C,0x94,0xC9,0xC9,0x9B,0xD0, 339 0xA7,0x85,0x27,0xA0,0x0B,0xA9,0x02,0x20, 340 0xA7,0xC8,0x20,0x58,0xFC,0xA9,0xF3,0xA0, 341 0xCE,0x20,0x54,0xCB,0xAD,0xF3,0x03,0x4D, 342 0xF4,0x03,0xC9,0xA5,0xD0,0x03,0x6C,0xF2,
343 0x03,0x6C,0xFC,0xFF,0x88,0x88,0x10,0x02, 344 0xA0,0x1E,0x84,0x27,0xC0,0x1E,0xF0,0x03, 345 0x20,0x9A,0xC9,0x4C,0xED,0xC8,0xC8,0xC8, 346 0xC0,0x20,0x90,0xEE,0xA0,0x00,0xF0,0xEA, 347 0x20,0x9A,0xC9,0x20,0x5B,0xCA,0x4C,0xED, 348 0xC8,0x20,0x9A,0xC9,0x20,0x77,0xCA,0x4C, 349 0xED,0xC8,0x98,0x48,0xA4,0x27,0x20,0xE8, 350 0xCA,0xA9,0x02,0xC0,0x1E,0xD0,0x02,0xA9, 351 0x82,0xA0,0x0B,0x20,0xA7,0xC8,0x68,0xA8, 352 0x60,0xA9,0x01,0x85,0x25,0xA0,0x00,0x84, 353 0x40,0xC0,0x12,0xD0,0x03,0x20,0x8E,0xFD, 354 0x20,0x2D,0xCA,0x08,0x20,0xF7,0xC9,0x28, 355 0x90,0x1F,0x20,0xE8,0xCA,0x20,0x47,0xCB, 356 0x48,0xA9,0xFF,0xA4,0x40,0xC4,0x27,0xD0, 357 0x02,0xA9,0x3F,0x85,0x32,0x68,0x20,0xDA, 358 0xFD,0xA0,0x01,0x20,0x38,0xCB,0x4C,0xEC, 359 0xC9,0x20,0x12,0xCB,0xA4,0x40,0xC8,0xC8, 360 0xC0,0x20,0x90,0xC3,0x4C,0x84,0xFE,0xA9, 361 0xFF,0x85,0x32,0x20,0x8E,0xFD,0xA0,0x00, 362 0xB1,0x3C,0x85,0x3E,0xC8,0xB1,0x3C,0x85, 363 0x3F,0xA0,0x00,0xA2,0x11,0xB1,0x3E,0x30, 364 0x04,0xCA,0xC8,0xD0,0xF8,0xA9,0xA0,0x20, 365 0xED,0xFD,0xCA,0x10,0xF8,0x20,0x58,0xCB, 366 0xA9,0xBA,0x20,0xED,0xFD,0xA9,0xA0,0x20, 367 0xED,0xFD,0x4C,0xED,0xFD,0x84,0x3C,0xC0, 368 0x0E,0xF0,0x15,0xC0,0x10,0xF0,0x11,0xA4, 369 0x3C,0xB9,0x67,0xCB,0x85,0x3C,0xB9,0x68, 370 0xCB,0xC9,0x80,0x09,0xC0,0x85,0x3D,0x60, 371 0xA9,0x22,0x20,0x91,0xC8,0xD0,0xE8,0xA4, 372 0x3C,0xB9,0x79,0xCB,0x85,0x3C,0xB9,0x7A, 373 0xCB,0xD0,0xE6,0x20,0x2D,0xCA,0xB0,0x4E, 374 0x20,0xF5,0xCA,0x90,0x25,0x88,0x88,0x88, 375 0xC0,0x05,0xB0,0x20,0x8A,0x0A,0x86,0x3E, 376 0x65,0x3E,0x69,0x02,0xA8,0xD0,0x15,0x20, 377 0x2D,0xCA,0xB0,0x47,0x20,0xF5,0xCA,0x90, 378 0x09,0xE0,0x01,0xF0,0x05,0xC8,0xC8,0xC8, 379 0xD0,0x02,0xA0,0x05,0xB1,0x3C,0x48,0xA0, 380 0x02,0xA6,0x26,0xB1,0x3C,0x9D,0x84,0xC0, 381 0x68,0x5D,0x85,0xC0,0xC8,0x31,0x3C,0x48, 382 0x88,0xB1,0x3C,0x9D,0x84,0xC0,0x68,0x5D, 383 0x85,0xC0,0x48,0x4C,0xDB,0xCA,0x20,0x47, 384 0xCB,0xC9,0x00,0xF0,0x0A,0xF8,0x38,0xE9, 385 0x01,0xA0,0x03,0xD1,0x3C,0xB0,0x17,0xA0, 386 0x04,0xD0,0x11,0x20,0x47,0xCB,0xF8,0x18, 387 0x69,0x01,0xA0,0x04,0xD1,0x3C,0x90,0x06, 388 0xF0,0x04,0xA0,0x03,0xB1,0x3C,0xD8,0x48, 389 0x20,0xE8,0xCA,0xA0,0x02,0xB1,0x3C,0xA8, 390 0xAD,0xF8,0x07,0x48,0xA9,0x83,0x48,0x60, 391 0xA9,0x0A,0xA6,0x26,0x9D,0x84,0xC0,0xBD, 392 0x85,0xC0,0x30,0xF4,0x60,0x20,0xE8,0xCA, 393 0x20,0x47,0xCB,0xA0,0x03,0x31,0x3C,0x48, 394 0xA0,0x04,0xB1,0x3C,0xAA,0x68,0xC8,0xD1, 395 0x3C,0xF0,0x06,0xC8,0xC8,0xCA,0xD0,0xF6, 396 0x18,0x60,0xA9,0xFF,0xA4,0x40,0xC4,0x27, 397 0xD0,0x02,0xA9,0x3F,0x85,0x32,0x20,0xF5, 398 0xCA,0xB0,0x08,0xA9,0x2F,0x85,0x3E,0xA9, 399 0xCE,0xD0,0x08,0xC8,0xB1,0x3C,0x85,0x3E, 400 0xC8,0xB1,0x3C,0x85,0x3F,0x20,0x58,0xCB, 401 0xA9,0xFF,0x85,0x32,0xA9,0xA0,0x20,0xED, 402 0xFD,0xC8,0xC0,0x0F,0x90,0xF8,0x60,0xA0, 403 0x02,0xB1,0x3C,0xA6,0x26,0x9D,0x84,0xC0, 404 0xBD,0x85,0xC0,0x60,0x85,0x3E,0x84,0x3F, 405 0xA0,0xFF,0xC8,0xB1,0x3E,0x09,0x80,0x20, 406 0xED,0xFD,0xB1,0x3E,0x10,0xF4,0x60,0x8B, 407 0x4B,0x9C,0x4B,0xCE,0x4B,0xDF,0x4B,0xEA, 408 0x4B,0x28,0x4C,0x39,0x4C,0xFE,0x4B,0x44, 409 0x4C,0x7E,0xCC,0x83,0xCC,0x88,0xCC,0x64, 410 0x4C,0x8D,0xCC,0x92,0xCC,0x97,0xCC,0x12, 411 0x4C,0x1D,0x4C,0x12,0xCD,0x22,0xFF,0x04, 412 0x00,0x7F,0xCD,0x02,0x98,0xCD,0x01,0x8C, 413 0xCD,0x03,0x9F,0xCD,0x1D,0xCD,0x23,0x1F, 414 0x0F,0x11,0xAC,0xCD,0x12,0xAE,0xCD,0x13, 415 0xB0,0xCD,0x14,0xB3,0xCD,0x15,0xB8,0xCD, 416 0x16,0xBB,0xCD,0x17,0xBE,0xCD,0x18,0xC1, 417 0xCD,0x19,0xC5,0xCD,0x1A,0xC9,0xCD,0x1B, 418 0xCD,0xCD,0x1C,0xD1,0xCD,0x1D,0xD5,0xCD, 419 0x1E,0xD9,0xCD,0x1F,0xDD,0xCD,0x26,0xCD, 420 0x23,0x60,0x04,0x00,0xE2,0xCD,0x20,0xE3, 421 0xCD,0x40,0xE4,0xCD,0x60,0xE5,0xCD,0x2F, 422 0xCD,0x23,0x80,0x02,0x00,0xE6,0xCD,0x80, 423 0xE7,0xCD,0x38,0xCD,0x24,0xE0,0x05,0x00, 424 0xE8,0xCD,0x20,0xEC,0xCD,0x60,0xEF,0xCD, 425 0xA0,0xF3,0xCD,0xE0,0xF7,0xCD,0x3E,0xCD, 426 0x25,0xFF,0x05,0x00,0xFC,0xCD,0x28,0x05, 427 0xCE,0x48,0x07,0xCE,0x50,0x09,0xCE,0x84, 428 0x0B,0xCE,0xC8,0xCE,0x28,0x08,0x02,0x00, 429 0xEB,0xCE,0x08,0xE4,0xCE,0xDA,0xCE,0x28, 430 0x80,0x02,0x00,0xEB,0xCE,0x80,0xE4,0xCE, 431 0x49,0xCD,0x26,0x30,0x04,0x00,0x0E,0xCE, 432 0x10,0x16,0xCE,0x20,0x1D,0xCE,0x30,0x25, 433 0xCE,0x51,0xCD,0x28,0x01,0x02,0x00,0x2A, 434 0xCE,0x01,0x2C,0xCE,0x34,0xCE,0x29,0xFF, 435 0x09,0x06,0x75,0xCE,0x01,0x80,0xCE,0x08, 436 0x8F,0xCE,0x03,0x44,0xCE,0x02,0x6F,0xCE, 437 0x04,0x56,0xCE,0x05,0x4D,0xCE,0x00,0x60, 438 0xCE,0x07,0x65,0xCE,0x68,0xCD,0x06,0xFF, 439 0x07,0x01,0x96,0xCE,0x02,0x9C,0xCE,0x03, 440 0xA2,0xCE,0x04,0xA9,0xCE,0x05,0xB2,0xCE, 441 0x06,0xBA,0xCE,0x07,0xC0,0xCE,0x5C,0xCD, 442 0x09,0x01,0x99,0x60,0xCD,0x08,0x01,0x12, 443 0x65,0xCD,0x07,0x01,0x31,0x6F,0xCD,0x04, 444 0x00,0x23,0x73,0xCD,0x02,0x00,0x59,0x79, 445 0xCD,0x00,0x00,0x59,0x41,0x50,0x50,0x4C, 446 0x49,0x45,0x44,0x20,0x45,0x4E,0x47,0x49, 447 0x4E,0x45,0x45,0x52,0x49,0x4E,0x47,0x20, 448 0x53,0x45,0x52,0x49,0x41,0x4C,0x20,0x50,
449 0x52,0x4F,0x2E,0x2E,0x2E,0x53,0x4C,0x4F, 450 0x54,0xA0,0x0D,0x55,0x53,0x45,0x20,0x41, 451 0x52,0x52,0x4F,0x57,0x53,0x20,0x4F,0x52, 452 0x20,0x3C,0x52,0x45,0x54,0x55,0x52,0x4E, 453 0x3E,0x20,0x54,0x4F,0x20,0x53,0x45,0x4C, 454 0x45,0x43,0x54,0x2F,0x4D,0x4F,0x44,0x49, 455 0x46,0x59,0x0D,0x55,0x53,0x45,0x20,0x3C, 456 0x45,0x53,0x43,0x41,0x50,0x45,0x3E,0x20, 457 0x54,0x4F,0x20,0x45,0x58,0x49,0x54,0x20, 458 0x43,0x4F,0x4E,0x46,0x49,0x47,0x55,0x52, 459 0x41,0x54,0x49,0x4F,0x4E,0x20,0x4D,0x45, 460 0x4E,0xD5,0x44,0x45,0x56,0x49,0x43,0x45, 461 0x20,0x54,0x59,0x50,0xC5,0x42,0x41,0x55, 462 0x44,0x20,0x52,0x41,0x54,0xC5,0x44,0x41, 463 0x54,0x41,0x20,0x42,0x49,0x54,0xD3,0x53, 464 0x54,0x4F,0x50,0x20,0x42,0x49,0x54,0xD3, 465 0x50,0x41,0x52,0x49,0x54,0xD9,0x4C,0x49, 466 0x4E,0x45,0x20,0x4C,0x45,0x4E,0x47,0x54, 467 0xC8,0x43,0x52,0x20,0x44,0x45,0x4C,0x41, 468 0xD9,0x47,0x45,0x4E,0x45,0x52,0x41,0x54, 469 0x45,0x20,0x4C,0xC6,0x59,0x45,0x41,0xD2, 470 0x4D,0x4F,0x4E,0x54,0xC8,0x44,0x41,0xD9, 471 0x57,0x45,0x45,0x4B,0x44,0x41,0xD9,0x48, 472 0x4F,0x55,0xD2,0x4D,0x49,0x4E,0x55,0x54, 473 0xC5,0x53,0x45,0x43,0x4F,0x4E,0xC4,0x43, 474 0x4F,0x4D,0x4D,0x55,0x4E,0x49,0x43,0x41, 475 0x54,0x49,0x4F,0xCE,0x50,0x38,0x20,0x45, 476 0x4D,0x55,0x4C,0x41,0x54,0x49,0x4F,0xCE, 477 0x50,0x52,0x49,0x4E,0x54,0x45,0xD2,0x50, 478 0x38,0x41,0x20,0x45,0x4D,0x55,0x4C,0x41, 479 0x54,0x49,0x4F,0xCE,0x35,0xB0,0x37,0xB5, 480 0x31,0x31,0xB0,0x31,0x33,0x34,0x2E,0xB5, 481 0x31,0x35,0xB0,0x33,0x30,0xB0,0x36,0x30, 482 0xB0,0x31,0x32,0x30,0xB0,0x31,0x38,0x30, 483 0xB0,0x32,0x34,0x30,0xB0,0x33,0x36,0x30, 484 0xB0,0x34,0x38,0x30,0xB0,0x37,0x32,0x30, 485 0xB0,0x39,0x36,0x30,0xB0,0x31,0x39,0x32, 486 0x30,0xB0,0xB8,0xB7,0xB6,0xB5,0xB1,0xB2, 487 0x4E,0x4F,0x4E,0xC5,0x4F,0x44,0xC4,0x45, 488 0x56,0x45,0xCE,0x4D,0x41,0x52,0xCB,0x53, 489 0x50,0x41,0x43,0xC5,0x55,0x4E,0x4C,0x49, 490 0x4D,0x49,0x54,0x45,0xC4,0x34,0xB0,0x37, 491 0xB2,0x38,0xB0,0x31,0x33,0xB2,0x4E,0x4F, 492 0x20,0x44,0x45,0x4C,0x41,0xD9,0x33,0x32, 493 0x20,0x4D,0x53,0x45,0xC3,0x32,0x35,0x30, 494 0x20,0x4D,0x53,0x45,0xC3,0x32,0x20,0x53, 495 0x45,0xC3,0x4E,0xCF,0x59,0x45,0xD3,0x45, 496 0x52,0x52,0x4F,0xD2,0x50,0x52,0x49,0x4E,
497 0x54,0x45,0x52,0x20,0x47,0x52,0x41,0x50, 498 0x48,0x49,0x43,0xD3,0x4F,0x54,0x48,0x45, 499 0x52,0x20,0x49,0x44,0xD3,0x4F,0x54,0x48, 500 0x45,0x52,0x20,0x4F,0x4B,0xC9,0x4F,0x4B, 501 0x49,0x44,0x41,0x54,0x41,0x20,0x38,0xB4, 502 0x45,0x50,0x53,0x4F,0xCE,0x4D,0x41,0x4E, 503 0x4E,0x45,0x53,0x4D,0x41,0x4E,0xCE,0x41, 504 0x4E,0x41,0x44,0x45,0xD8,0x49,0x4D,0x41, 505 0x47,0x45,0x57,0x52,0x49,0x54,0x45,0xD2, 506 0x4E,0x45,0x43,0x38,0x30,0x32,0x33,0x2C, 507 0x20,0x43,0x2E,0x49,0x54,0x4F,0xC8,0x49, 508 0x44,0x53,0x20,0x34,0x34,0xB0,0x53,0x55, 509 0x4E,0x44,0x41,0xD9,0x4D,0x4F,0x4E,0x44, 510 0x41,0xD9,0x54,0x55,0x45,0x53,0x44,0x41, 511 0xD9,0x57,0x45,0x44,0x4E,0x45,0x53,0x44, 512 0x41,0xD9,0x54,0x48,0x55,0x52,0x53,0x44, 513 0x41,0xD9,0x46,0x52,0x49,0x44,0x41,0xD9, 514 0x53,0x41,0x54,0x55,0x52,0x44,0x41,0xD9, 515 0x58,0x4F,0x4E,0x2F,0x58,0x4F,0x46,0x46, 516 0x20,0x48,0x41,0x4E,0x44,0x53,0x48,0x41, 517 0x4B,0xC5,0x4C,0x4F,0x43,0x41,0x4C,0x20, 518 0x45,0x43,0x48,0xCF,0x45,0x4E,0x41,0x42, 519 0x4C,0x45,0xC4,0x44,0x49,0x53,0x41,0x42, 520 0x4C,0x45,0xC4,0x43,0x4F,0x4E,0x46,0x49, 521 0x47,0x55,0x52,0x41,0x54,0x49,0x4F,0x4E, 522 0x20,0x43,0x4F,0x4D,0x50,0x4C,0x45,0x54, 523 0x45,0x44,0x8D,0x20,0x8F,0xC8,0x4A,0x6A, 524 0x6A,0xA0,0x03,0xD0,0x19,0x20,0x8F,0xC8, 525 0x0A,0x0A,0x0A,0x0A,0xA0,0x02,0xD0,0x0E, 526 0x20,0x8F,0xC8,0x0A,0x0A,0xA0,0x01,0xD0, 527 0x05,0x20,0x8F,0xC8,0xA0,0x00,0x5D,0xB8, 528 0x03,0x39,0xF5,0xCF,0x5D,0xB8,0x03,0x9D, 529 0xB8,0x03,0x60,0x20,0x8F,0xC8,0x29,0x07, 530 0x0A,0x0A,0x0A,0x0A,0xC5,0x26,0xF0,0x11, 531 0x4A,0x5D,0xB8,0x04,0x29,0x38,0x5D,0xB8, 532 0x04,0x9D,0xB8,0x04,0xA9,0x00,0x9D,0x38, 533 0x06,0x60,0x20,0x8F,0xC8,0x29,0x0F,0xD0, 534 0x07,0xA9,0x23,0x20,0x5F,0xCC,0x29,0x0F, 535 0x09,0x10,0x59,0x8B,0xC0,0x29,0x1F,0x59, 536 0x8B,0xC0,0x99,0x8B,0xC0,0x60,0x20,0x8F, 537 0xC8,0x88,0x4C,0x80,0xCF,0x20,0x8F,0xC8, 538 0x4A,0x6A,0x6A,0x6A,0x59,0x8B,0xC0,0x29, 539 0xE0,0x59,0x8B,0xC0,0x99,0x8B,0xC0,0x60, 540 0xBD,0xB8,0x04,0x09,0x80,0xD0,0x11,0x99, 541 0x89,0xC0,0x20,0x93,0xFE,0x20,0x89,0xFE, 542 0xAE,0xF8,0x07,0xBD,0xB8,0x04,0x29,0x7F, 543 0x9D,0xB8,0x04,0x60,0xB9,0x8A,0xC0,0x48, 544 0x09,0x0C,0x99,0x8A,0xC0,0xA9,0xE9,0x48, 545 0xA9,0x90,0x38,0xE9,0x01,0xD0,0xFB,0x68, 546 0xE9,0x01,0xD0,0xF3,0x68,0x99,0x8A,0xC0, 547 0x60,0xA9,0x28,0x9D,0x38,0x06,0xBD,0x38, 548 0x07,0x09,0x80,0xD0,0x05,0xA9,0xFE,0x3D, 549 0x38,0x07,0x9D,0x38,0x07,0x60,0x20,0x8F, 550 0xC8,0xC9,0x28,0x90,0xF8,0x9D,0x38,0x06, 551 0xA9,0x3F,0xD0,0xEB,0xBD,0x38,0x05,0x09, 552 0x80,0x9D,0x38,0x05,0x60,0x03,0x0C,0x30, 553 0xC0,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 554 // EPROM $1000-$17FF: HOSTROM BANK 2 555 0x00,0x00,0x00,0x08,0x78,0xA9,0x01,0x48, 556 0x20,0x13,0xC8,0x68,0xA4,0x26,0x99,0x87, 557 0xC0,0x28,0x60,0x8A,0xA4,0x26,0x99,0x87, 558 0xC0,0x8A,0x4A,0x4A,0x29,0xFE,0xAA,0xBD, 559 0x2C,0xC8,0x48,0xBD,0x2B,0xC8,0x48,0xAE, 560 0xF8,0x07,0x60,0x2C,0xC9,0x7E,0xC9,0x4A, 561 0xC8,0xE4,0xCA,0x24,0xCE,0x82,0xCE,0x2E, 562 0xCF,0x37,0xCF,0xA4,0x26,0x99,0x84,0xC0, 563 0xB9,0x85,0xC0,0x60,0x48,0x8A,0x48,0xA9, 564 0x83,0x48,0x60,0x20,0xBE,0xDE,0x20,0x7B, 565 0xDD,0x20,0xFD,0xE5,0xA0,0x00,0xB1,0x5E, 566 0x29,0x7F,0xC9,0x21,0xF0,0x57,0x85,0x27, 567 0xAE,0xF8,0x07,0x20,0x2D,0xC9,0x20,0xBE, 568 0xDE,0x20,0xE3,0xDF,0x20,0x6C,0xDD,0xAE, 569 0xF8,0x07,0x20,0x7F,0xC9,0xA2,0x00,0xA0, 570 0x00,0xBD,0x00,0x02,0x29,0x7F,0xC9,0x22, 571 0xF0,0x08,0xC9,0x0D,0xF0,0x09,0x99,0x00, 572 0x02,0xC8,0xE8,0xE0,0x19,0x90,0xEA,0xA9, 573 0x00,0x99,0x00,0x02,0x84,0x9D,0x98,0x20, 574 0x52,0xE4,0xA0,0x00,0x91,0x83,0xC8,0xA5, 575 0x71,0x91,0x83,0xC8,0xA5,0x72,0x91,0x83, 576 0xA0,0x02,0xA2,0x00,0xA5,0x9D,0x20,0xE2, 577 0xE5,0xAE,0xF8,0x07,0x60,0x20,0xBE,0xDE, 578 0x20,0x7B,0xDD,0x20,0xFD,0xE5,0x85,0x9D, 579 0xA0,0x00,0x84,0x83,0x84,0x84,0xB9,0x0A, 580 0xC9,0xF0,0x1B,0x30,0x26,0x29,0x0F,0xA8, 581 0xA5,0x27,0xC0,0x06,0xD0,0x02,0x69,0x00, 582 0xAE,0xF8,0x07,0x20,0x44,0xC8,0xA4,0x84, 583 0xC8,0xC0,0x14,0x90,0xDF,0x60,0x20,0x1E, 584 0xC9,0xB0,0xFA,0xC9,0x0A,0xB0,0xF7,0x85, 585 0x27,0x90,0xEB,0xA5,0x27,0x0A,0x0A,0x0A, 586 0x0A,0x85,0x27,0x20,0x1E,0xC9,0xB0,0xE5, 587 0xC9,0x0A,0xB0,0xE1,0x05,0x27,0x85,0x27, 588 0x90,0xD4,0x00,0xFF,0x49,0x00,0xFF,0x48, 589 0x00,0xFF,0x47,0x00,0x46,0x00,0xFF,0x44, 590 0x00,0xFF,0x42,0x00,0xFF,0x40,0xA4,0x83, 591 0xC4,0x9D,0xB0,0x08,0xE6,0x83,0xB1,0x5E, 592 0x29,0x7F,0x49,0x30,0x60,0xA5,0x27,0xA0, 593 0x1A,0x4C,0x44,0xC8,0x8A,0x48,0xA9,0xA8, 594 0x48,0x60,0xB9,0x85,0xC0,0x38,0xE9,0x01, 595 0x20,0x54,0xCA,0xD0,0x46,0xA4,0x26,0x99, 596 0x84,0xC0,0xC9,0x16,0xF0,0xEC,0xC9,0x14, 597 0xF0,0x03,0xA9,0xFF,0x2C,0xA9,0x3F,0x39, 598 0x85,0xC0,0x20,0x4B,0xCA,0xD0,0x2C,0x98, 599 0x48,0x18,0x79,0x60,0xCA,0xE9,0x3F,0xD0,
600 0x26,0x68,0xA8,0xD0,0x20,0x88,0xF0,0xF9, 601 0x20,0xC3,0xC9,0x20,0x58,0xCA,0xC9,0x8D, 602 0xD0,0x11,0x85,0x27,0x98,0xAA,0x60,0x20, 603 0x34,0xC9,0xA9,0x00,0x85,0x27,0x20,0xA6, 604 0xC9,0xD0,0x04,0xA4,0x2A,0xC8,0x98,0x85, 605 0x2A,0xA8,0xB9,0x60,0xCA,0x30,0xDC,0xC9, 606 0x40,0xB0,0xC4,0xC9,0x10,0xB0,0xA6,0xA8, 607 0xD0,0xCB,0xA9,0x8D,0xD0,0xCD,0xA9,0x1A, 608 0x20,0x3B,0xC8,0x29,0x7F,0xA0,0x06,0xD9, 609 0xBC,0xC9,0xF0,0x04,0x88,0x10,0xF8,0xC8, 610 0xB9,0x60,0xCA,0x60,0x3A,0x3C,0x3E,0x20, 611 0x23,0x25,0x26,0x88,0xF0,0x53,0x88,0xF0, 612 0x65,0xA9,0x0B,0x20,0x3B,0xC8,0x29,0x02, 613 0xD0,0x0D,0xA9,0x04,0x20,0x3B,0xC8,0x30, 614 0x03,0xA9,0xC1,0x60,0xA9,0xD0,0x60,0xA0, 615 0xC1,0xAD,0x0C,0x02,0x0A,0x0A,0x0A,0x0A, 616 0x49,0xB0,0x4D,0x0D,0x02,0xD0,0x04,0xA9, 617 0x12,0xD0,0x0E,0xC9,0x12,0x90,0x0A,0xA0, 618 0xD0,0xC9,0x13,0x90,0x04,0xF8,0xE9,0x12, 619 0xD8,0x48,0x4A,0x4A,0x4A,0x4A,0xD0,0x02, 620 0x49,0x10,0x49,0xB0,0x8D,0x0C,0x02,0x68, 621 0x29,0x0F,0x09,0xB0,0x8D,0x0D,0x02,0x98, 622 0x60,0xE6,0x27,0xAC,0x01,0x02,0xB9,0xFC, 623 0xC9,0x8D,0x01,0x02,0xB9,0x03,0xCA,0x8D, 624 0x02,0x02,0xB9,0x0A,0xCA,0x60,0xAD,0x06, 625 0x02,0xAC,0x05,0x02,0xC0,0xB1,0x90,0x02, 626 0x69,0x09,0xA8,0xB9,0x10,0xCA,0x8D,0x05, 627 0x02,0xB9,0x1C,0xCA,0x8D,0x06,0x02,0xB9, 628 0x28,0xCA,0x60,0x48,0x4A,0x4A,0x4A,0x4A, 629 0x20,0x54,0xCA,0x68,0x29,0x0F,0x09,0xB0, 630 0xA4,0x27,0x99,0x00,0x02,0xE6,0x27,0x60, 631 0x1B,0x39,0x2E,0x14,0x07,0x27,0x35,0x18, 632 0xAC,0xB0,0x16,0xAC,0x17,0xAC,0x14,0xAC, 633 0x12,0xAC,0x10,0x00,0xA2,0x4E,0x6F,0xAE, 634 0x16,0x19,0x00,0xA2,0x16,0xA0,0x45,0xAF, 635 0x19,0x64,0x00,0x18,0xAF,0x17,0x01,0xA2, 636 0x55,0x5C,0xA0,0x04,0xCD,0x00,0xA0,0x4E, 637 0x55,0xA0,0x04,0xCD,0x00,0xA2,0x47,0x4E, 638 0x00,0xA0,0x43,0x4A,0x00,0x16,0x02,0xA0, 639 0x18,0x03,0xA0,0x17,0x01,0xA0,0x14,0xBA, 640 0x12,0xBA,0x10,0x01,0xD3,0xCD,0xD4,0xD7, 641 0xD4,0xC6,0xD3,0xD5,0xCF,0xD5,0xC5,0xC8, 642 0xD2,0xC1,0xCE,0xCE,0xC5,0xC4,0xD5,0xC9, 643 0xD4,0xCA,0xC6,0xCD,0xC1,0xCD,0xCA,0xCA, 644 0xC1,0xD3,0xCF,0xCE,0xC4,0xC1,0xC5,0xC1, 645 0xD0,0xC1,0xD5,0xD5,0xD5,0xC5,0xC3,0xCF, 646 0xC5,0xCE,0xC2,0xD2,0xD2,0xD9,0xCE,0xCC, 647 0xC7,0xD0,0xD4,0xD6,0xC3,0xA2,0x00,0xB5, 648 0x00,0x48,0xE8,0xE0,0x0C,0x90,0xF8,0xA9, 649 0x2A,0x20,0x3B,0xC8,0x29,0xEF,0x85,0x08, 650 0xA9,0x29,0x20,0x3B,0xC8,0x85,0x06,0xAA, 651 0xA9,0x00,0x85,0x00,0xBD,0x0A,0xCE,0x29, 652 0x07,0x85,0x04,0xA9,0x01,0x85,0x09,0x20, 653 0x0E,0xCC,0xA5,0x08,0x4A,0xB0,0x5B,0xA9, 654 0x00,0x85,0x00,0x20,0x13,0xCC,0xA0,0x00, 655 0x84,0x02,0xA9,0xFF,0x48,0xA5,0x04,0x85, 656 0x01,0x20,0x02,0xCD,0x48,0xC6,0x01,0x10, 657 0xF8,0xA9,0x07,0x85,0x03,0xA9,0x00,0xA4, 658 0x04,0xBA,0xE8,0x5E,0x00,0x01,0x2A,0x88, 659 0x10,0xF8,0x20,0xBB,0xCC,0xC6,0x03,0xD0, 660 0xEC,0x68,0x10,0xFD,0xA4,0x02,0xC8,0xC0, 661 0x28,0x90,0xCD,0x20,0x3E,0xCC,0xB0,0xC6, 662 0x38,0xA5,0x00,0x65,0x04,0xC9,0xA0,0x90, 663 0xB8,0xA8,0xA5,0x08,0x29,0xA2,0xC9,0x02, 664 0xF0,0x05,0x98,0xC9,0xC0,0x90,0xAA,0x4C, 665 0xC0,0xCB,0xA0,0x01,0x84,0x03,0x88,0x84, 666 0x02,0x20,0x13,0xCC,0xA9,0xC0,0x85,0x01, 667 0xC6,0x01,0xE6,0x02,0x20,0x02,0xCD,0x48, 668 0xC6,0x02,0x20,0x02,0xCD,0x0A,0x85,0x05, 669 0x68,0xA6,0x03,0x4A,0x66,0x05,0xCA,0xD0, 670 0xFA,0x20,0xE2,0xCC,0x20,0xBB,0xCC,0xA5, 671 0x01,0xD0,0xDD,0x20,0x3E,0xCC,0xB0,0xD4, 672 0xA6,0x04,0xA4,0x03,0xC8,0xC0,0x08,0x90, 673 0x04,0xE6,0x02,0xA0,0x01,0xCA,0x10,0xF4, 674 0x84,0x03,0xA4,0x02,0xC0,0x28,0x90,0xB9, 675 0xA9,0x24,0x20,0x9F,0xCC,0xA5,0x08,0x29, 676 0x02,0xF0,0x2E,0x20,0x01,0xCC,0xA2,0x00, 677 0xBD,0x50,0x06,0xE0,0x28,0x90,0x1A,0x20, 678 0x0B,0xCC,0xBD,0xA8,0x06,0xE0,0x50,0x90, 679 0x10,0x20,0x0B,0xCC,0xBD,0x00,0x07,0xE0, 680 0x78,0x90,0x06,0x20,0x0B,0xCC,0xBD,0x58, 681 0x07,0x20,0xEB,0xCC,0xE8,0xE0,0xA0,0x90, 682 0xD7,0xA2,0x0B,0x68,0x95,0x00,0xCA,0x10, 683 0xFA,0xA9,0x0D,0x20,0xEB,0xCC,0xA9,0x0A, 684 0x4C,0xEB,0xCC,0xF0,0xF4,0x60,0xA9,0x00, 685 0x4C,0x9F,0xCC,0xA9,0x09,0x20,0x9F,0xCC, 686 0x20,0x53,0xCC,0xA9,0x12,0x20,0x9F,0xCC, 687 0xA5,0x08,0x4A,0xA5,0x06,0x2A,0x0A,0xA8, 688 0xA5,0x08,0x10,0x01,0xC8,0xBE,0xB5,0xCD, 689 0xBD,0xD9,0xCD,0xC9,0xFF,0xF0,0x06,0x20, 690 0xEB,0xCC,0xE8,0xD0,0xF3,0x60,0xA5,0x08, 691 0x10,0x0A,0x49,0x30,0x85,0x08,0x29,0x10, 692 0xF0,0x02,0x38,0x60,0xA9,0x1B,0x20,0x9F, 693 0xCC,0x18,0x60,0xA5,0x06,0xC9,0x02,0xF0, 694 0x0D,0xA6,0x09,0xF0,0x08,0xA9,0x20,0x20, 695 0xEB,0xCC,0xCA,0xD0,0xFA,0x60,0xA9,0x3B, 696 0x20,0xEB,0xCC,0xA5,0x09,0x4A,0xA0,0x06, 697 0x65,0x09,0xB0,0x04,0x88,0x10,0xF9,0x2C, 698 0xA9,0xFF,0xA2,0x00,0x20,0x89,0xCC,0xA2, 699 0x01,0x20,0x89,0xCC,0x09,0x30,0x4C,0xEB, 700 0xCC,0xA0,0x2F,0xD0,0x03,0xFD,0x9D,0xCC, 701 0xDD,0x9D,0xCC,0xC8,0xB0,0xF7,0x48,0x98, 702 0x20,0xEB,0xCC,0x68,0x60,0x64,0x0A,0x18, 703 0x65,0x06,0xA8,0xB9,0x41,0xCD,0xA8,0xB9, 704 0x6E,0xCD,0x48,0x29,0x7F,0xF0,0x08,0x84, 705 0x07,0x20,0xEB,0xCC,0xA4,0x07,0xC8,0x68, 706 0x10,0xED,0x60,0xA4,0x06,0xBE,0x0A,0xCE, 707 0x10,0x05,0x85,0x05,0x20,0xE2,0xCC,0x24, 708 0x08,0x50,0x02,0x49,0xFF,0x39,0x1C,0xCE, 709 0x19,0x13,0xCE,0xC9,0x03,0xD0,0x08,0xBE, 710 0x01,0xCE,0xF0,0x03,0x20,0xEB,0xCC,0x4C, 711 0xEB,0xCC,0xA6,0x04,0x46,0x05,0x2A,0xCA, 712 0x10,0xFA,0x60,0x85,0x27,0xA4,0x26,0xB9, 713 0x89,0xC0,0x29,0x70,0xC9,0x10,0xD0,0xF7, 714 0xAD,0xF8,0x07,0x48,0xA9,0x8B,0x48,0xA5, 715 0x27,0x60,0xA4,0x02,0xC0,0x28,0xB0,0x30, 716 0xA5,0x00,0x65,0x01,0xC9,0xC0,0xB0,0x28, 717 0x48,0x29,0xC7,0x85,0x0A,0x09,0x08,0x85, 718 0x0B,0x68,0x0A,0x0A,0x66,0x0A,0x0A,0x26, 719 0x0B,0x66,0x0A,0x0A,0x26,0x0B,0x0A,0x66,
720 0x0A,0xA5,0x08,0x29,0x20,0xF0,0x06,0xA5, 721 0x0B,0x49,0x60,0x85,0x0B,0xB1,0x0A,0x60, 722 0xA9,0xFF,0x24,0x08,0x70,0x02,0xA9,0x00, 723 0x60,0x02,0x06,0x0C,0x01,0x10,0x01,0x04, 724 0x01,0x01,0x23,0x27,0x28,0x2B,0x27,0x2C, 725 0x27,0x27,0x00,0x14,0x16,0x00,0x1C,0x19, 726 0x1C,0x1D,0x20,0x1C,0x01,0x01,0x30,0x31, 727 0x01,0x31,0x01,0x01,0x34,0x38,0x3A,0x3D, 728 0x3E,0x40,0x3E,0x42,0x00,0x00,0x80,0x8D, 729 0x1B,0xA3,0x1B,0x3E,0x1B,0x4E,0x1B,0x54, 730 0x31,0xB6,0x12,0x0D,0x1C,0xC0,0x1B,0x25, 731 0x39,0x8E,0x1B,0xCB,0x1B,0x53,0xB0,0x1B, 732 0x25,0xB2,0x83,0x1B,0x47,0xB0,0x1B,0x25, 733 0xB5,0x1B,0x41,0x08,0x8A,0x8A,0x30,0x40, 734 0xB6,0x8E,0x03,0x0E,0x03,0x82,0xB0,0x03, 735 0x02,0x8D,0x03,0x0B,0x03,0x82,0x1B,0xB2, 736 0x0F,0x1B,0xC1,0x9D,0x03,0x82,0x1B,0xB6, 737 0x0F,0x1B,0x41,0x1B,0xBC,0x00,0x03,0x06, 738 0x09,0x0C,0x10,0x14,0x18,0x27,0x27,0x27, 739 0x27,0x27,0x27,0x27,0x27,0x1C,0x1F,0x22, 740 0x25,0x27,0x27,0x27,0x27,0x0C,0x10,0x14, 741 0x18,0x00,0x03,0x06,0x09,0x27,0x27,0x27, 742 0x27,0x18,0x01,0xFF,0x30,0x02,0xFF,0xC0, 743 0x00,0xFF,0x80,0x01,0xFF,0x32,0x38,0x30, 744 0xFF,0x35,0x36,0x30,0xFF,0x31,0x39,0x32, 745 0xFF,0x33,0x38,0x34,0xFF,0x02,0x18,0xFF, 746 0x04,0x30,0xFF,0x01,0x40,0xFF,0x03,0x00, 747 0xFF,0x00,0x00,0x00,0x03,0x00,0x03,0x00, 748 0x00,0x03,0x07,0x87,0x05,0x86,0x06,0x86, 749 0x87,0x07,0x85,0x00,0x00,0xC0,0x00,0x00, 750 0x00,0x00,0x00,0x00,0xFF,0xFF,0x3F,0x7F, 751 0x7F,0x7F,0xFF,0xFF,0x3F,0xA5,0x29,0x48, 752 0xA5,0x28,0x48,0xA9,0x00,0x85,0x28,0xA9, 753 0x04,0x85,0x29,0x20,0x01,0xCC,0xA0,0x00, 754 0x20,0x1C,0xCF,0x6A,0x10,0x03,0x8D,0x55, 755 0xC0,0x48,0x84,0x3C,0xB1,0x28,0x8D,0x54, 756 0xC0,0x48,0x20,0x1C,0xCF,0x0A,0x68,0x90, 757 0x02,0x09,0x80,0xC9,0xFF,0xD0,0x02,0xA9, 758 0xA0,0xC9,0xE0,0xB0,0x08,0x09,0xC0,0xC9, 759 0xE0,0x90,0x02,0x49,0x40,0x20,0xEB,0xCC, 760 0xA4,0x3C,0x68,0x49,0x80,0x10,0xD2,0xC8, 761 0xC0,0x28,0x90,0xC4,0x20,0xFF,0xCE,0x90, 762 0xBA,0x20,0x01,0xCC,0x68,0x85,0x28,0x68, 763 0x85,0x29,0x60,0xA2,0x0A,0xB5,0x3A,0x48, 764 0xBD,0xF5,0xCE,0x95,0x3A,0xCA,0x10,0xF5, 765 0xA2,0x0D,0x8E,0xB0,0xC0,0xAD,0xB1,0xC0, 766 0x85,0x41,0xCA,0x8E,0xB0,0xC0,0xAD,0xB1, 767 0xC0,0x85,0x44,0xA9,0x18,0x48,0xAE,0xF8, 768 0x07,0x20,0x01,0xCC,0xA9,0x50,0x48,0x20, 769 0xDD,0xCE,0x09,0x80,0xC9,0xA0,0xB0,0x02, 770 0x09,0xC0,0x20,0xEB,0xCC,0xE6,0x41,0xD0, 771 0x02,0xE6,0x44,0x68,0x38,0xE9,0x01,0xD0, 772 0xE5,0x68,0x38,0xE9,0x01,0xD0,0xD6,0x20, 773 0x01,0xCC,0xA2,0x00,0x68,0x95,0x3A,0xE8, 774 0xE0,0x0B,0x90,0xF8,0x60,0xA5,0x44,0x0A, 775 0x29,0x0C,0xAA,0xBD,0xB0,0xC0,0x46,0x42, 776 0xA5,0x44,0x4A,0x26,0x42,0xAD,0xF8,0x07, 777 0x48,0xA9,0xC6,0x48,0x60,0xAD,0xFF,0xCF, 778 0xAD,0x00,0xC3,0xAD,0x00,0xCC,0x60,0x18, 779 0xA5,0x28,0x49,0x80,0x85,0x28,0x30,0x13, 780 0xE6,0x29,0xA5,0x29,0xC9,0x08,0x90,0x0B, 781 0x4A,0x85,0x29,0xA5,0x28,0x69,0x28,0x85, 782 0x28,0xC9,0x78,0x60,0xAD,0xB3,0xFB,0x49, 783 0x06,0xD0,0x08,0xAD,0x1F,0xC0,0x0A,0xAD, 784 0x1E,0xC0,0x60,0xA9,0x00,0x18,0x60,0xBD, 785 0xB8,0x04,0x29,0xCF,0x9D,0xB8,0x04,0x60, 786 0xA9,0x2B,0x20,0x3B,0xC8,0x29,0x01,0x18, 787 0x69,0x01,0x0A,0x0A,0x0A,0x0A,0x1D,0xB8, 788 0x04,0x9D,0xB8,0x04,0x60,0xFF,0xFF,0xFF, 789 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 790 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 791 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 792 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 793 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 794 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 795 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 796 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 797 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 798 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 799 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 800 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 801 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 802 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 803 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 804 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 805 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 806 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 807 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 808 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 809 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 810 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 811 // EPROM $1800-$18FF: COPYRIGHT / IDENTIFICATION PAGE 812 0x43,0x4F,0x50,0x59,0x52,0x49,0x47,0x48, 813 0x54,0x20,0x28,0x43,0x29,0x20,0x31,0x39, 814 0x38,0x36,0x20,0x41,0x50,0x50,0x4C,0x49, 815 0x45,0x44,0x20,0x45,0x4E,0x47,0x49,0x4E, 816 0x45,0x45,0x52,0x49,0x4E,0x47,0x20,0x43, 817 0x4F,0x50,0x59,0x52,0x49,0x47,0x48,0x54, 818 0x20,0x28,0x43,0x29,0x20,0x31,0x39,0x38, 819 0x36,0x20,0x41,0x50,0x50,0x4C,0x49,0x45, 820 0x44,0x20,0x45,0x4E,0x47,0x49,0x4E,0x45, 821 0x45,0x52,0x49,0x4E,0x47,0x20,0x43,0x4F, 822 0x50,0x59,0x52,0x49,0x47,0x48,0x54,0x20, 823 0x28,0x43,0x29,0x20,0x31,0x39,0x38,0x36, 824 0x20,0x41,0x50,0x50,0x4C,0x49,0x45,0x44, 825 0x20,0x45,0x4E,0x47,0x49,0x4E,0x45,0x45,
826 0x52,0x49,0x4E,0x47,0x20,0x43,0x4F,0x50, 827 0x59,0x52,0x49,0x47,0x48,0x54,0x20,0x28, 828 0x43,0x29,0x20,0x31,0x39,0x38,0x36,0x20, 829 0x41,0x50,0x50,0x4C,0x49,0x45,0x44,0x20, 830 0x45,0x4E,0x47,0x49,0x4E,0x45,0x45,0x52, 831 0x49,0x4E,0x47,0x20,0x43,0x4F,0x50,0x59, 832 0x52,0x49,0x47,0x48,0x54,0x20,0x28,0x43, 833 0x29,0x20,0x31,0x39,0x38,0x36,0x20,0x41, 834 0x50,0x50,0x4C,0x49,0x45,0x44,0x20,0x45, 835 0x4E,0x47,0x49,0x4E,0x45,0x45,0x52,0x49, 836 0x4E,0x47,0x20,0x43,0x4F,0x50,0x59,0x52, 837 0x49,0x47,0x48,0x54,0x20,0x28,0x43,0x29, 838 0x20,0x31,0x39,0x38,0x36,0x20,0x41,0x50, 839 0x50,0x4C,0x49,0x45,0x44,0x20,0x45,0x4E, 840 0x47,0x49,0x4E,0x45,0x45,0x52,0x49,0x4E, 841 0x47,0x20,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 842 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 843 0xFF,0xFF,0xFF,0xFF,0xA4,0x5D,0xFF,0xFF, 844 845 // EPROM $1900-$19FF: SLOT 1 ROM ($C100-$C1FF) 846 0x2C,0x58,0xFF,0x70,0x57,0x38,0x90,0x18, 847 0xB8,0x50,0x51,0x01,0x31,0x2E,0x34,0x3A, 848 0x40,0x20,0x46,0xC1,0x4C,0xAE,0xCA,0x20, 849 0x46,0xC1,0x4C,0x1A,0xC1,0x18,0x90,0x01, 850 0x38,0xB8,0x85,0x27,0x98,0x48,0x8A,0x48, 851 0x20,0x46,0xC1,0x4C,0x91,0xCA,0x20,0x46, 852 0xC1,0x4C,0x19,0xC8,0x20,0x46,0xC1,0x4C, 853 0xC0,0xC8,0x20,0x46,0xC1,0x4C,0xCA,0xC8, 854 0x20,0x46,0xC1,0x4C,0x24,0xC8,0x08,0x78, 855 0x8D,0xFF,0xCF,0x8D,0x96,0xC0,0xAE,0x95, 856 0xC0,0xA2,0xC1,0xA0,0x10,0x8E,0xF8,0x07, 857 0x84,0x26,0x28,0x60,0x85,0x27,0x98,0x48, 858 0x8A,0x48,0x20,0x46,0xC1,0x4C,0xD9,0xC8, 859 0xA2,0x03,0xB5,0x36,0x48,0xCA,0x10,0xFA, 860 0x20,0x50,0xCD,0xA5,0x27,0x48,0x09,0x80, 861 0x20,0xED,0xFD,0x68,0x85,0x27,0x20,0x46, 862 0xC1,0x4C,0x65,0xCD,0x8C,0x94,0xC0,0x68, 863 0x8D,0x95,0xC0,0x60,0x8D,0x98,0xC0,0x60, 864 0xA9,0x00,0xF0,0x0B,0xA9,0x2A,0x8D,0x94, 865 0xC0,0xAD,0x95,0xC0,0x19,0xF7,0xCE,0xA0, 866 0x2A,0x8C,0x94,0xC0,0x8D,0x95,0xC0,0x38, 867 0x60,0xA2,0x19,0xA0,0x0A,0x8C,0x94,0xC0, 868 0xAD,0x95,0xC0,0x30,0xF8,0x88,0x8C,0x94, 869 0xC0,0xAD,0x95,0xC0,0x8E,0x94,0xC0,0x8D, 870 0x95,0xC0,0xCA,0x88,0x10,0xF0,0x60,0x20, 871 0x3A,0x00,0x48,0x20,0x46,0xC1,0xA9,0x02, 872 0x8D,0x97,0xC0,0x68,0x60,0xFF,0xFF,0xFF, 873 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 874 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 875 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 876 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x2C, 877 0xC6,0xC1,0x4C,0x22,0xC1,0xC1,0xC5,0x20, 878 879 // EPROM $1A00-$1AFF: SLOT 2 ROM ($C200-$C2FF) 880 0x2C,0x58,0xFF,0x70,0x57,0x38,0x90,0x18, 881 0xB8,0x50,0x51,0x01,0x31,0x2E,0x34,0x3A, 882 0x40,0x20,0x46,0xC2,0x4C,0xAE,0xCA,0x20, 883 0x46,0xC2,0x4C,0x1A,0xC2,0x18,0x90,0x01, 884 0x38,0xB8,0x85,0x27,0x98,0x48,0x8A,0x48, 885 0x20,0x46,0xC2,0x4C,0x91,0xCA,0x20,0x46, 886 0xC2,0x4C,0x19,0xC8,0x20,0x46,0xC2,0x4C, 887 0xC0,0xC8,0x20,0x46,0xC2,0x4C,0xCA,0xC8, 888 0x20,0x46,0xC2,0x4C,0x24,0xC8,0x08,0x78, 889 0x8D,0xFF,0xCF,0x8D,0xA6,0xC0,0xAE,0xA5, 890 0xC0,0xA2,0xC2,0xA0,0x20,0x8E,0xF8,0x07, 891 0x84,0x26,0x28,0x60,0x85,0x27,0x98,0x48, 892 0x8A,0x48,0x20,0x46,0xC2,0x4C,0xD9,0xC8, 893 0xA2,0x03,0xB5,0x36,0x48,0xCA,0x10,0xFA, 894 0x20,0x50,0xCD,0xA5,0x27,0x48,0x09,0x80, 895 0x20,0xED,0xFD,0x68,0x85,0x27,0x20,0x46, 896 0xC2,0x4C,0x65,0xCD,0x8C,0xA4,0xC0,0x68, 897 0x8D,0xA5,0xC0,0x60,0x8D,0xA8,0xC0,0x60, 898 0xA9,0x00,0xF0,0x0B,0xA9,0x2A,0x8D,0xA4, 899 0xC0,0xAD,0xA5,0xC0,0x19,0xF7,0xCE,0xA0, 900 0x2A,0x8C,0xA4,0xC0,0x8D,0xA5,0xC0,0x38, 901 0x60,0xA2,0x19,0xA0,0x0A,0x8C,0xA4,0xC0, 902 0xAD,0xA5,0xC0,0x30,0xF8,0x88,0x8C,0xA4, 903 0xC0,0xAD,0xA5,0xC0,0x8E,0xA4,0xC0,0x8D, 904 0xA5,0xC0,0xCA,0x88,0x10,0xF0,0x60,0x20, 905 0x3A,0x00,0x48,0x20,0x46,0xC2,0xA9,0x02, 906 0x8D,0xA7,0xC0,0x68,0x60,0xFF,0xFF,0xFF, 907 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 908 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 909 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 910 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x2C, 911 0xC6,0xC2,0x4C,0x22,0xC2,0xC1,0xC5,0x20, 912 913 // EPROM $1B00-$1BFF: SLOT 3 ROM ($C300-$C3FF) 914 0x2C,0x58,0xFF,0x70,0x57,0x38,0x90,0x18, 915 0xB8,0x50,0x51,0x01,0x31,0x2E,0x34,0x3A, 916 0x40,0x20,0x46,0xC3,0x4C,0xAE,0xCA,0x20, 917 0x46,0xC3,0x4C,0x1A,0xC3,0x18,0x90,0x01, 918 0x38,0xB8,0x85,0x27,0x98,0x48,0x8A,0x48, 919 0x20,0x46,0xC3,0x4C,0x91,0xCA,0x20,0x46, 920 0xC3,0x4C,0x19,0xC8,0x20,0x46,0xC3,0x4C, 921 0xC0,0xC8,0x20,0x46,0xC3,0x4C,0xCA,0xC8, 922 0x20,0x46,0xC3,0x4C,0x24,0xC8,0x08,0x78, 923 0x8D,0xFF,0xCF,0x8D,0xB6,0xC0,0xAE,0xB5, 924 0xC0,0xA2,0xC3,0xA0,0x30,0x8E,0xF8,0x07, 925 0x84,0x26,0x28,0x60,0x85,0x27,0x98,0x48, 926 0x8A,0x48,0x20,0x46,0xC3,0x4C,0xD9,0xC8, 927 0xA2,0x03,0xB5,0x36,0x48,0xCA,0x10,0xFA, 928 0x20,0x50,0xCD,0xA5,0x27,0x48,0x09,0x80, 929 0x20,0xED,0xFD,0x68,0x85,0x27,0x20,0x46, 930 0xC3,0x4C,0x65,0xCD,0x8C,0xB4,0xC0,0x68, 931 0x8D,0xB5,0xC0,0x60,0x8D,0xB8,0xC0,0x60, 932 0xA9,0x00,0xF0,0x0B,0xA9,0x2A,0x8D,0xB4, 933 0xC0,0xAD,0xB5,0xC0,0x19,0xF7,0xCE,0xA0, 934 0x2A,0x8C,0xB4,0xC0,0x8D,0xB5,0xC0,0x38, 935 0x60,0xA2,0x19,0xA0,0x0A,0x8C,0xB4,0xC0, 936 0xAD,0xB5,0xC0,0x30,0xF8,0x88,0x8C,0xB4, 937 0xC0,0xAD,0xB5,0xC0,0x8E,0xB4,0xC0,0x8D, 938 0xB5,0xC0,0xCA,0x88,0x10,0xF0,0x60,0x20, 939 0x3A,0x00,0x48,0x20,0x46,0xC3,0xA9,0x02, 940 0x8D,0xB7,0xC0,0x68,0x60,0xFF,0xFF,0xFF, 941 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 942 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 943 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 944 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x2C, 945 0xC6,0xC3,0x4C,0x22,0xC3,0xC1,0xC5,0x20, 946 947 // EPROM $1C00-$1CFF: SLOT 4 ROM ($C400-$C4FF) 948 0x2C,0x58,0xFF,0x70,0x57,0x38,0x90,0x18, 949 0xB8,0x50,0x51,0x01,0x31,0x2E,0x34,0x3A, 950 0x40,0x20,0x46,0xC4,0x4C,0xAE,0xCA,0x20, 951 0x46,0xC4,0x4C,0x1A,0xC4,0x18,0x90,0x01, 952 0x38,0xB8,0x85,0x27,0x98,0x48,0x8A,0x48, 953 0x20,0x46,0xC4,0x4C,0x91,0xCA,0x20,0x46, 954 0xC4,0x4C,0x19,0xC8,0x20,0x46,0xC4,0x4C, 955 0xC0,0xC8,0x20,0x46,0xC4,0x4C,0xCA,0xC8, 956 0x20,0x46,0xC4,0x4C,0x24,0xC8,0x08,0x78, 957 0x8D,0xFF,0xCF,0x8D,0xC6,0xC0,0xAE,0xC5, 958 0xC0,0xA2,0xC4,0xA0,0x40,0x8E,0xF8,0x07, 959 0x84,0x26,0x28,0x60,0x85,0x27,0x98,0x48, 960 0x8A,0x48,0x20,0x46,0xC4,0x4C,0xD9,0xC8, 961 0xA2,0x03,0xB5,0x36,0x48,0xCA,0x10,0xFA, 962 0x20,0x50,0xCD,0xA5,0x27,0x48,0x09,0x80, 963 0x20,0xED,0xFD,0x68,0x85,0x27,0x20,0x46, 964 0xC4,0x4C,0x65,0xCD,0x8C,0xC4,0xC0,0x68, 965 0x8D,0xC5,0xC0,0x60,0x8D,0xC8,0xC0,0x60, 966 0xA9,0x00,0xF0,0x0B,0xA9,0x2A,0x8D,0xC4, 967 0xC0,0xAD,0xC5,0xC0,0x19,0xF7,0xCE,0xA0, 968 0x2A,0x8C,0xC4,0xC0,0x8D,0xC5,0xC0,0x38, 969 0x60,0xA2,0x19,0xA0,0x0A,0x8C,0xC4,0xC0, 970 0xAD,0xC5,0xC0,0x30,0xF8,0x88,0x8C,0xC4, 971 0xC0,0xAD,0xC5,0xC0,0x8E,0xC4,0xC0,0x8D, 972 0xC5,0xC0,0xCA,0x88,0x10,0xF0,0x60,0x20, 973 0x3A,0x00,0x48,0x20,0x46,0xC4,0xA9,0x02, 974 0x8D,0xC7,0xC0,0x68,0x60,0xFF,0xFF,0xFF, 975 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 976 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 977 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 978 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x2C, 979 0xC6,0xC4,0x4C,0x22,0xC4,0xC1,0xC5,0x20, 980 981 // EPROM $1D00-$1DFF: SLOT 5 ROM ($C500-$C5FF) 982 0x2C,0x58,0xFF,0x70,0x57,0x38,0x90,0x18, 983 0xB8,0x50,0x51,0x01,0x31,0x2E,0x34,0x3A, 984 0x40,0x20,0x46,0xC5,0x4C,0xAE,0xCA,0x20, 985 0x46,0xC5,0x4C,0x1A,0xC5,0x18,0x90,0x01, 986 0x38,0xB8,0x85,0x27,0x98,0x48,0x8A,0x48, 987 0x20,0x46,0xC5,0x4C,0x91,0xCA,0x20,0x46, 988 0xC5,0x4C,0x19,0xC8,0x20,0x46,0xC5,0x4C, 989 0xC0,0xC8,0x20,0x46,0xC5,0x4C,0xCA,0xC8, 990 0x20,0x46,0xC5,0x4C,0x24,0xC8,0x08,0x78, 991 0x8D,0xFF,0xCF,0x8D,0xD6,0xC0,0xAE,0xD5, 992 0xC0,0xA2,0xC5,0xA0,0x50,0x8E,0xF8,0x07, 993 0x84,0x26,0x28,0x60,0x85,0x27,0x98,0x48, 994 0x8A,0x48,0x20,0x46,0xC5,0x4C,0xD9,0xC8, 995 0xA2,0x03,0xB5,0x36,0x48,0xCA,0x10,0xFA, 996 0x20,0x50,0xCD,0xA5,0x27,0x48,0x09,0x80, 997 0x20,0xED,0xFD,0x68,0x85,0x27,0x20,0x46, 998 0xC5,0x4C,0x65,0xCD,0x8C,0xD4,0xC0,0x68, 999 0x8D,0xD5,0xC0,0x60,0x8D,0xD8,0xC0,0x60, 1000 0xA9,0x00,0xF0,0x0B,0xA9,0x2A,0x8D,0xD4, 1001 0xC0,0xAD,0xD5,0xC0,0x19,0xF7,0xCE,0xA0, 1002 0x2A,0x8C,0xD4,0xC0,0x8D,0xD5,0xC0,0x38, 1003 0x60,0xA2,0x19,0xA0,0x0A,0x8C,0xD4,0xC0, 1004 0xAD,0xD5,0xC0,0x30,0xF8,0x88,0x8C,0xD4, 1005 0xC0,0xAD,0xD5,0xC0,0x8E,0xD4,0xC0,0x8D, 1006 0xD5,0xC0,0xCA,0x88,0x10,0xF0,0x60,0x20, 1007 0x3A,0x00,0x48,0x20,0x46,0xC5,0xA9,0x02, 1008 0x8D,0xD7,0xC0,0x68,0x60,0xFF,0xFF,0xFF, 1009 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 1010 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 1011 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 1012 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x2C, 1013 0xC6,0xC5,0x4C,0x22,0xC5,0xC1,0xC5,0x20, 1014 1015 // EPROM $1E00-$1EFF: SLOT 6 ROM ($C600-$C6FF) 1016 0x2C,0x58,0xFF,0x70,0x57,0x38,0x90,0x18, 1017 0xB8,0x50,0x51,0x01,0x31,0x2E,0x34,0x3A, 1018 0x40,0x20,0x46,0xC6,0x4C,0xAE,0xCA,0x20, 1019 0x46,0xC6,0x4C,0x1A,0xC6,0x18,0x90,0x01, 1020 0x38,0xB8,0x85,0x27,0x98,0x48,0x8A,0x48, 1021 0x20,0x46,0xC6,0x4C,0x91,0xCA,0x20,0x46, 1022 0xC6,0x4C,0x19,0xC8,0x20,0x46,0xC6,0x4C, 1023 0xC0,0xC8,0x20,0x46,0xC6,0x4C,0xCA,0xC8, 1024 0x20,0x46,0xC6,0x4C,0x24,0xC8,0x08,0x78, 1025 0x8D,0xFF,0xCF,0x8D,0xE6,0xC0,0xAE,0xE5, 1026 0xC0,0xA2,0xC6,0xA0,0x60,0x8E,0xF8,0x07, 1027 0x84,0x26,0x28,0x60,0x85,0x27,0x98,0x48, 1028 0x8A,0x48,0x20,0x46,0xC6,0x4C,0xD9,0xC8, 1029 0xA2,0x03,0xB5,0x36,0x48,0xCA,0x10,0xFA, 1030 0x20,0x50,0xCD,0xA5,0x27,0x48,0x09,0x80, 1031 0x20,0xED,0xFD,0x68,0x85,0x27,0x20,0x46, 1032 0xC6,0x4C,0x65,0xCD,0x8C,0xE4,0xC0,0x68, 1033 0x8D,0xE5,0xC0,0x60,0x8D,0xE8,0xC0,0x60, 1034 0xA9,0x00,0xF0,0x0B,0xA9,0x2A,0x8D,0xE4, 1035 0xC0,0xAD,0xE5,0xC0,0x19,0xF7,0xCE,0xA0, 1036 0x2A,0x8C,0xE4,0xC0,0x8D,0xE5,0xC0,0x38, 1037 0x60,0xA2,0x19,0xA0,0x0A,0x8C,0xE4,0xC0, 1038 0xAD,0xE5,0xC0,0x30,0xF8,0x88,0x8C,0xE4, 1039 0xC0,0xAD,0xE5,0xC0,0x8E,0xE4,0xC0,0x8D, 1040 0xE5,0xC0,0xCA,0x88,0x10,0xF0,0x60,0x20, 1041 0x3A,0x00,0x48,0x20,0x46,0xC6,0xA9,0x02, 1042 0x8D,0xE7,0xC0,0x68,0x60,0xFF,0xFF,0xFF, 1043 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 1044 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 1045 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 1046 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x2C, 1047 0xC6,0xC6,0x4C,0x22,0xC6,0xC1,0xC5,0x20, 1048 1049 // EPROM $1F00-$1FFF: SLOT 7 ROM ($C700-$C7FF) 1050 0x2C,0x58,0xFF,0x70,0x57,0x38,0x90,0x18, 1051 0xB8,0x50,0x51,0x01,0x31,0x2E,0x34,0x3A, 1052 0x40,0x20,0x46,0xC7,0x4C,0xAE,0xCA,0x20, 1053 0x46,0xC7,0x4C,0x1A,0xC7,0x18,0x90,0x01, 1054 0x38,0xB8,0x85,0x27,0x98,0x48,0x8A,0x48, 1055 0x20,0x46,0xC7,0x4C,0x91,0xCA,0x20,0x46, 1056 0xC7,0x4C,0x19,0xC8,0x20,0x46,0xC7,0x4C, 1057 0xC0,0xC8,0x20,0x46,0xC7,0x4C,0xCA,0xC8, 1058 0x20,0x46,0xC7,0x4C,0x24,0xC8,0x08,0x78, 1059 0x8D,0xFF,0xCF,0x8D,0xF6,0xC0,0xAE,0xF5, 1060 0xC0,0xA2,0xC7,0xA0,0x70,0x8E,0xF8,0x07, 1061 0x84,0x26,0x28,0x60,0x85,0x27,0x98,0x48, 1062 0x8A,0x48,0x20,0x46,0xC7,0x4C,0xD9,0xC8, 1063 0xA2,0x03,0xB5,0x36,0x48,0xCA,0x10,0xFA, 1064 0x20,0x50,0xCD,0xA5,0x27,0x48,0x09,0x80, 1065 0x20,0xED,0xFD,0x68,0x85,0x27,0x20,0x46, 1066 0xC7,0x4C,0x65,0xCD,0x8C,0xF4,0xC0,0x68, 1067 0x8D,0xF5,0xC0,0x60,0x8D,0xF8,0xC0,0x60, 1068 0xA9,0x00,0xF0,0x0B,0xA9,0x2A,0x8D,0xF4, 1069 0xC0,0xAD,0xF5,0xC0,0x19,0xF7,0xCE,0xA0, 1070 0x2A,0x8C,0xF4,0xC0,0x8D,0xF5,0xC0,0x38, 1071 0x60,0xA2,0x19,0xA0,0x0A,0x8C,0xF4,0xC0, 1072 0xAD,0xF5,0xC0,0x30,0xF8,0x88,0x8C,0xF4, 1073 0xC0,0xAD,0xF5,0xC0,0x8E,0xF4,0xC0,0x8D, 1074 0xF5,0xC0,0xCA,0x88,0x10,0xF0,0x60,0x20, 1075 0x3A,0x00,0x48,0x20,0x46,0xC7,0xA9,0x02, 1076 0x8D,0xF7,0xC0,0x68,0x60,0xFF,0xFF,0xFF, 1077 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 1078 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 1079 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 1080 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x2C, 1081 0xC6,0xC7,0x4C,0x22,0xC7,0xC1,0xC5,0x20 1082 1083]); 1084 1085
1086// Discovery container. Apple2IO mounts a separate live instance. 1087if(oEMU===undefined) var oEMU = {"component":{"IO":{"SerialPro":new SerialProCard()}}} 1088else oEMU.component.IO.SerialPro = new SerialProCard(); 1089 1090 1091function SerialProCard() 1092{ 1093 const bDebug = false; 1094 1095 this.id = {"PCODE":"SPC", "icon":"fa fa-terminal"}; 1096 1097 /* 1098 * SPC remains the Apple II slot peripheral. 1099 * 1100 * SPGPT is a text-aware peer device that can speak either US-ASCII (GPT8) 1101 * or UTF-16LE (GPT16) over that raw serial byte stream. Declaring both here 1102 * makes device provisioning deterministic and avoids relying on constructor 1103 * wrapping in an extension script. 1104 */ 1105 this.deviceConfig = [ 1106 { 1107 "DCODE":"SPSERIAL" 1108 ,"hostPCODE":"SPC" 1109 ,"coID":"SerialProLine" 1110 ,"icon":"fa fa-exchange-alt" 1111 ,"description":"Serial Pro external serial line" 1112 } 1113 ,{ 1114 "DCODE":"SPTERM" 1115 ,"hostPCODE":"SPC" 1116 ,"coID":"SerialProTerminalDevice" 1117 ,"icon":"fa fa-terminal" 1118 ,"description":"Serial Pro browser terminal console" 1119 } 1120 ,{ 1121 "DCODE":"SPGPT" 1122 ,"hostPCODE":"SPC" 1123 ,"coID":"SerialProGPTDevice" 1124 ,"icon":"fa fa-robot" 1125 ,"description":"Serial Pro GPT8/GPT16 serial peer" 1126 } 1127 ]; 1128 1129 var state = 1130 { 1131 "active":true 1132 // Keep the complete byte, not just the physical-bank bits. Serial Pro 1133 // firmware uses selector values such as $01/$09 and $02/$0A/$12: the 1134 // low two bits choose the physical 2 KiB EPROM quarter while higher 1135 // bits remain meaningful to the software dispatcher in the selected bank. 1136 ,"romSelector":0x00 1137 ,"cpuSync":true 1138 }; 1139 1140 this.state = state; 1141 1142 /* 1143 * Serial Pro clock/RAM device. 1144 * 1145 * AE documents a 6818-compatible register map behind two slot-I/O ports: 1146 * $C084+$s0 address register (write only) 1147 * $C085+$s0 selected register/RAM data (read/write) 1148 * 1149 * Registers $00-$0D follow the 6818 clock map; $0E-$3F are battery RAM. 1150 * The real card's firmware normally programs Register A=$20 (32.768 kHz) 1151 * and Register B=$02 (24-hour, BCD, running), so those are our power-up 1152 * defaults. Battery RAM starts clear and is then initialized by the real 1153 * Serial Pro firmware when its $20/$21 signature is absent. 1154 * 1155 * This object survives reset()/restart() for as long as the mounted card 1156 * instance exists, modeling battery-backed RAM within an emulator session. 1157 * Browser-persistent NVRAM across page reloads is intentionally a later step. 1158 */ 1159 var rtcStartMs = Date.now(); 1160 var rtc = 1161 { 1162 "address":0x00 1163 ,"ram":new Uint8Array(0x40) 1164 ,"baseTimeMs":rtcStartMs 1165 ,"hostBaseMs":rtcStartMs 1166 ,"weekdayOffset":0 1167 ,"irq":false 1168 ,"nmi":false 1169 ,"lastUpdateSecond":Math.floor(rtcStartMs/1000) 1170 ,"lastIRQSampleTick":0 1171 }; 1172 1173 // Minimal interrupt stage required by the resident HGR clock. 1174 // Register B: UIE=$10, SET/HOLD=$80. 1175 // Register C: UF=$10, IRQF=$80. Reading C acknowledges the RTC request. 1176 const RTC_B_UIE = 0x10; 1177 const RTC_B_SET = 0x80; 1178 const RTC_C_UF = 0x10; 1179 const RTC_C_IRQF = 0x80; 1180 1181 // Diagnostic route for the Apple II+ resident-clock experiment. 1182 // The physical Serial Pro can route the 6818 interrupt to NMI. 1183 // Keep the 6551 ACIA on IRQ; only the RTC update-ended source uses NMI. 1184 const RTC_UPDATE_INTERRUPT_ROUTE = "nmi"; 1185 1186 rtc.ram[0x0A] = 0x20; // Register A: 32.768 kHz, UIP clear on read 1187 rtc.ram[0x0B] = 0x02; // Register B: 24-hour, BCD, clock running 1188 rtc.ram[0x0D] = 0x80; // Register D: valid time/RAM 1189 1190 1191 var serialpro = this; // stand-in where 'this' is absent inside callbacks 1192 1193 1194 var serialLineDevice = null; 1195 var serialLineUnsubscribe = null; 1196 var serialTerminalConsoleDevice = null; 1197 1198 /* 1199 * Apple2IO.attach() calls SPSERIAL.bindHost(), which in turn binds the line 1200 * device here. Existing browser endpoints become adapters subscribed to the 1201 * line instead of being called directly from the 6551 timing code. 1202 */ 1203 this.bindSerialLineDevice = function(device) 1204 { 1205 if(serialLineUnsubscribe) 1206 { 1207 serialLineUnsubscribe(); 1208 serialLineUnsubscribe = null; 1209 } 1210 1211 serialLineDevice = device || null; 1212 1213 if(serialLineDevice && 1214 typeof(serialLineDevice.subscribe)=="function") 1215 { 1216 serialLineUnsubscribe = 1217 serialLineDevice.subscribe(function(bytes,meta) 1218 { 1219 for(var i=0;i<bytes.length;i++) 1220 { 1221 var d8 = bytes[i] & 0xFF; 1222
1223 if(typeof(serialpro.serialTerminalWriteByte)=="function") 1224 serialpro.serialTerminalWriteByte(d8); 1225 1226 serialPhysicalWriteByte(d8); 1227 } 1228 }); 1229 } 1230 1231 return !!serialLineDevice; 1232 }; 1233 1234 /* 1235 * Child peer devices such as SPGPT may attach after SPSERIAL and need the 1236 * live external-line device. Keep the actual 6551 state private to SPC; 1237 * only expose the device boundary. 1238 */ 1239 this.getSerialLineDevice = function() 1240 { 1241 return serialLineDevice; 1242 }; 1243 1244 this.bindSerialTerminalConsoleDevice = function(device) 1245 { 1246 serialTerminalConsoleDevice = device || null; 1247 return !!serialTerminalConsoleDevice; 1248 }; 1249 1250 this.getSerialTerminalConsoleDevice = function() 1251 { 1252 return serialTerminalConsoleDevice; 1253 }; 1254 1255 /* 1256 * Remote/external bytes arrive here only through SPSERIAL. Keep receive 1257 * framing/timing inside the card: this queue is the simulated line feeding 1258 * the 6551 receive-shift register. 1259 */ 1260 this.serialLineReceiveBytes = function(bytes,meta) 1261 { 1262 if(!bytes || !bytes.length) 1263 return 0; 1264 1265 aciaSyncNow(); 1266 1267 for(var i=0;i<bytes.length;i++) 1268 serialTerminalState.toCard.push(bytes[i] & 0xFF); 1269 1270 aciaPrimeReceiver(); 1271 return bytes.length; 1272 }; 1273 1274 /* 1275 * Start a new application-level serial session from a deterministic RX 1276 * boundary. A normal Apple II/6551 reset intentionally preserves remote 1277 * bytes already on the simulated line; changing GPT8/GPT16 encoding must 1278 * not preserve those bytes because they belong to the previous protocol. 1279 */ 1280 this.serialLineResetReceiveSession = function() 1281 { 1282 aciaSyncNow(); 1283 1284 var info = { 1285 queued:serialTerminalState.toCard.length 1286 ,rxFull:!!acia.rxFull 1287 ,rxBusy:!!acia.rxBusy 1288 ,rxData:acia.rxData & 0xFF 1289 ,rxShift:acia.rxShift & 0xFF 1290 }; 1291 1292 serialTerminalState.toCard.length = 0; 1293 acia.rxData = 0x00; 1294 acia.rxFull = false; 1295 acia.rxErrors = 0x00; 1296 acia.rxShift = 0x00; 1297 acia.rxBusy = false; 1298 acia.rxTicks = 0; 1299 1300 return info; 1301 }; 1302 1303 this.serialLineReceiveInfo = function() 1304 { 1305 return { 1306 queued:serialTerminalState.toCard.length 1307 ,rxFull:!!acia.rxFull 1308 ,rxBusy:!!acia.rxBusy 1309 ,rxData:acia.rxData & 0xFF 1310 ,rxShift:acia.rxShift & 0xFF 1311 }; 1312 }; 1313 1314 function serialLineInjectBytes(bytes,meta) 1315 { 1316 if(serialLineDevice && 1317 typeof(serialLineDevice.receiveBytes)=="function") 1318 return serialLineDevice.receiveBytes(bytes,meta || {}); 1319 1320 // Defensive bootstrap fallback before attached devices are provisioned. 1321 return serialpro.serialLineReceiveBytes(bytes,meta || {}); 1322 } 1323 1324 function serialLineTransmitByte(d8) 1325 { 1326 d8 &= 0xFF; 1327 1328 if(serialLineDevice && 1329 typeof(serialLineDevice.transmitBytes)=="function") 1330 { 1331 return serialLineDevice.transmitBytes( 1332 new Uint8Array([d8]), 1333 {"source":"acia"} 1334 ); 1335 } 1336 1337 /* 1338 * Defensive fallback for tools that construct SerialProCard without 1339 * running Apple2IO.provisionPeripheral(). 1340 */ 1341 if(typeof(serialpro.serialTerminalWriteByte)=="function") 1342 serialpro.serialTerminalWriteByte(d8); 1343 serialPhysicalWriteByte(d8); 1344 return 1; 1345 } 1346 1347 /* 1348 * Stage 2D: legacy 6551 ACIA. 1349 * 1350 * Serial Pro firmware polls the legacy 6551 TDRE bit before transmitting, 1351 * therefore model the classic 6551/W65C51S register semantics rather than 1352 * the later W65C51N transmitter behavior. 1353 * 1354 * Register offsets within SlotIO: 1355 * $08 data read RX / write TX 1356 * $09 status read / programmed reset on write 1357 * $0A command read/write 1358 * $0B control read/write 1359 * 1360 * Stage 2D interprets Command/Control, applies the selected data word length, 1361 * exposes modem-control state and drives the shared Apple II IRQ line for 1362 * enabled RX, TX and DCD/DSR-change conditions. 1363 * 1364 * Character transfers are paced from the emulator's completed 6502-cycle 1365 * counter. With CPU sync enabled (default), turbo CPU speed accelerates the 1366 * UART by the same factor. With CPU sync disabled, completed CPU cycles are 1367 * normalized by baseClock/targetClock so the selected baud remains close to 1368 * real-world x1 timing without using Date.now()/performance.now(). We pace
1369 * complete serial frames rather than individual TXD/RXD waveform edges. 1370 */ 1371 const ACIA_IRQ_RX = 0x01; 1372 const ACIA_IRQ_TX = 0x02; 1373 const ACIA_IRQ_MODEM = 0x04; 1374 const ACIA_BAUD_TABLE = 1375 [null,50,75,109.92,134.58,150,300,600,1200,1800,2400,3600,4800,7200,9600,19200]; 1376 1377 var acia = 1378 { 1379 "command":0x00 1380 ,"control":0x00 1381 ,"rxData":0x00 1382 ,"rxFull":false 1383 ,"rxErrors":0x00 1384 ,"rxShift":0x00 1385 ,"rxBusy":false 1386 ,"rxTicks":0 1387 ,"txData":0x00 1388 ,"txEmpty":true 1389 ,"txBusy":false 1390 ,"txTicks":0 1391 ,"irq":false 1392 ,"irqReasons":0x00 1393 ,"lastClockTick":0 1394 ,"dsrReady":true 1395 ,"dcdDetected":true 1396 ,"ctsClear":true 1397 ,"dsrStatusReady":true 1398 ,"dcdStatusDetected":true 1399 ,"modemStatusLatched":false 1400 }; 1401 1402 function serialProIRQSourceID() 1403 { 1404 var slot = serialpro.mount ? Number(serialpro.mount.slotN)-1 : "?"; 1405 return "SerialPro:"+slot; 1406 } 1407 1408 // The physical card ORs its interrupt-capable devices onto the selected 1409 // Apple II interrupt line. Keep one emulator IRQ source asserted while 1410 // either the 6551 ACIA or the 6818 RTC still has a pending request. 1411 function serialProDriveIRQ() 1412 { 1413 var asserted = !!(acia.irq || rtc.irq); 1414 if(typeof(apple2plus)!="object" || !apple2plus || 1415 typeof(apple2plus.hwObj)!="function") 1416 return false; 1417 1418 var hw = apple2plus.hwObj(); 1419 if(!hw) return false; 1420 1421 if(typeof(hw.setIRQSource)=="function") 1422 hw.setIRQSource(serialProIRQSourceID(),asserted); 1423 else 1424 hw.irq_signal = asserted ? 1 : 0; // compatibility fallback 1425 return asserted; 1426 } 1427 1428 function aciaDriveIRQ() 1429 { 1430 return serialProDriveIRQ(); 1431 } 1432 1433 function aciaAssertIRQ(reason) 1434 { 1435 acia.irqReasons |= reason & 0xFF; 1436 acia.irq = true; 1437 aciaDriveIRQ(); 1438 } 1439 1440 function aciaClearIRQ() 1441 { 1442 acia.irqReasons = 0x00; 1443 acia.irq = false; 1444 aciaDriveIRQ(); 1445 } 1446 1447 function rtcAssertIRQ() 1448 { 1449 rtc.irq = true; 1450 return serialProDriveIRQ(); 1451 } 1452 1453 function rtcClearIRQ() 1454 { 1455 rtc.irq = false; 1456 return serialProDriveIRQ(); 1457 } 1458 1459 function rtcDriveNMI(active) 1460 { 1461 if(typeof(apple2plus)!="object" || !apple2plus || 1462 typeof(apple2plus.hwObj)!="function") 1463 return false; 1464 1465 var hw = apple2plus.hwObj(); 1466 if(!hw) return false; 1467 1468 // EMU_cpu6502.js consumes nmi_signal when the NMI edge is accepted. 1469 // Clearing Register C before CPU acceptance also cancels the pending edge. 1470 hw.nmi_signal = active ? 1 : 0; 1471 return !!active; 1472 } 1473 1474 function rtcAssertNMI() 1475 { 1476 // Do not manufacture a new edge while the same RTC request is pending. 1477 if(rtc.nmi) return true; 1478 1479 rtc.nmi = true; 1480 1481 // RTC is routed away from the shared IRQ line. Preserve any ACIA IRQ. 1482 rtc.irq = false; 1483 serialProDriveIRQ(); 1484 1485 return rtcDriveNMI(true); 1486 } 1487 1488 function rtcClearNMI() 1489 { 1490 rtc.nmi = false; 1491 rtcDriveNMI(false); 1492 return true; 1493 } 1494 1495 function rtcAssertInterrupt() 1496 { 1497 return RTC_UPDATE_INTERRUPT_ROUTE=="nmi" 1498 ? rtcAssertNMI() 1499 : rtcAssertIRQ(); 1500 } 1501 1502 function rtcClearInterrupt() 1503 { 1504 return RTC_UPDATE_INTERRUPT_ROUTE=="nmi" 1505 ? rtcClearNMI() 1506 : rtcClearIRQ(); 1507 } 1508 1509 /* 1510 * Keep 6551 register decoding independent from the source of the register 1511 * bytes. The Serial Pro has two related serial states: 1512 * 1513 * RTC battery RAM $23/$24 = saved/configured CONTROL + COMMAND 1514 * acia.control/command = live 6551 hardware registers 1515 * 1516 * The ROM control panel edits the saved bytes first; firmware subsequently 1517 * copies them into the live 6551 when it (re)initializes the interface. 1518 */ 1519 function serialWordLengthFromControl(control) 1520 { 1521 return 8 - (((control & 0xFF) >> 5) & 0x03); 1522 } 1523 1524 1525 function aciaWordLength() 1526 { 1527 return serialWordLengthFromControl(acia.control); 1528 } 1529 1530 function aciaDataMask() 1531 { 1532 return (0xFF >> (8-aciaWordLength())) & 0xFF; 1533 } 1534 1535 function serialParityFromCommand(command) 1536 { 1537 command &= 0xFF; 1538 if((command & 0x20)==0) return "none"; 1539 return ["odd","even","mark","space"][(command >> 6) & 0x03]; 1540 } 1541 1542 function aciaParity() 1543 { 1544 return serialParityFromCommand(acia.command); 1545 } 1546 1547 function aciaParityChecked() 1548 { 1549 if((acia.command & 0x20)==0) return false;
1550 return ((acia.command >> 6) & 0x03) < 2; // odd/even only 1551 } 1552 1553 function serialStopBitsFromRegisters(control,command) 1554 { 1555 control &= 0xFF; 1556 command &= 0xFF; 1557 if((control & 0x80)==0) return 1; 1558 1559 var bits = serialWordLengthFromControl(control); 1560 var parity = serialParityFromCommand(command); 1561 if(bits==5 && parity=="none") return 1.5; 1562 if(bits==8 && parity!="none") return 1; 1563 return 2; 1564 } 1565 1566 function aciaStopBits() 1567 { 1568 return serialStopBitsFromRegisters(acia.control,acia.command); 1569 } 1570 1571 function serialBaudFromControl(control) 1572 { 1573 return ACIA_BAUD_TABLE[control & 0x0F]; 1574 } 1575 1576 function aciaBaudRate() 1577 { 1578 return serialBaudFromControl(acia.control); 1579 } 1580 1581 function serialFormatSignature(control,command) 1582 { 1583 return [ 1584 serialBaudFromControl(control) 1585 ,serialWordLengthFromControl(control) 1586 ,serialParityFromCommand(command) 1587 ,serialStopBitsFromRegisters(control,command) 1588 ,(control & 0x10)!=0 ? "rx=tx" : "rx=ext" 1589 ].join("/"); 1590 } 1591 1592 function serialFormatLabel(control,command) 1593 { 1594 var baud = serialBaudFromControl(control); 1595 var parity = serialParityFromCommand(command); 1596 var parityLetter = { 1597 "none":"N" 1598 ,"odd":"O" 1599 ,"even":"E" 1600 ,"mark":"M" 1601 ,"space":"S" 1602 }[parity] || "?"; 1603 1604 return String(baud==null ? "EXT" : baud) 1605 +" "+serialWordLengthFromControl(control) 1606 +parityLetter 1607 +String(serialStopBitsFromRegisters(control,command)); 1608 } 1609 1610 function serialConfiguredState() 1611 { 1612 // Until the AE/$55 NVRAM signature exists, preserve the pre-existing 1613 // browser behavior by presenting the live ACIA state. Once firmware 1614 // initializes the battery RAM, $23/$24 become authoritative for SER. 1615 var nvramValid = rtc.ram[0x20]==0xAE && rtc.ram[0x21]==0x55; 1616 var control = nvramValid ? (rtc.ram[0x23] & 0xFF) : (acia.control & 0xFF); 1617 var command = nvramValid ? (rtc.ram[0x24] & 0xFF) : (acia.command & 0xFF); 1618 1619 return { 1620 "valid":nvramValid 1621 ,"control":control 1622 ,"command":command 1623 ,"baud":serialBaudFromControl(control) 1624 ,"dataBits":serialWordLengthFromControl(control) 1625 ,"parity":serialParityFromCommand(command) 1626 ,"stopBits":serialStopBitsFromRegisters(control,command) 1627 ,"rxClockInternal":(control & 0x10)!=0 1628 }; 1629 } 1630 1631 function aciaCPUMultiplier() 1632 { 1633 var base = typeof(_o)!="undefined" ? Number(_o.CPU_ClocksTicks_s) : NaN; 1634 var target = typeof(_o)!="undefined" ? Number(_o.CPU_TargetTicks_s) : NaN; 1635 return Number.isFinite(base) && base>0 && Number.isFinite(target) && target>0 1636 ? target/base 1637 : 1; 1638 } 1639 1640 function aciaEffectiveBaudRate() 1641 { 1642 var baud = aciaBaudRate(); 1643 if(!Number.isFinite(baud)) return baud; 1644 return state.cpuSync ? baud*aciaCPUMultiplier() : baud; 1645 } 1646 1647 function aciaFrameBits() 1648 { 1649 return 1 // start bit 1650 + aciaWordLength() 1651 + (aciaParity()=="none" ? 0 : 1) 1652 + aciaStopBits(); 1653 } 1654 1655 function aciaFrameTicks() 1656 { 1657 var baud = aciaBaudRate(); 1658 var base = typeof(_o)!="undefined" ? Number(_o.CPU_ClocksTicks_s) : NaN; 1659 if(!Number.isFinite(baud) || baud<=0 || !Number.isFinite(base) || base<=0) 1660 return Infinity; 1661 return base*aciaFrameBits()/baud; 1662 } 1663 1664 function aciaClockScale() 1665 { 1666 if(state.cpuSync) return 1; 1667 var multiplier = aciaCPUMultiplier(); 1668 return multiplier>0 ? 1/multiplier : 1; 1669 } 1670 1671 function aciaClockNow(ctx) 1672 { 1673 if(ctx && ctx.io && typeof(ctx.io.getClockTicks)=="function") 1674 return Number(ctx.io.getClockTicks()); 1675 1676 if(typeof(apple2plus)=="object" && apple2plus && 1677 typeof(apple2plus.hwObj)=="function") 1678 {
1679 var hw = apple2plus.hwObj(); 1680 if(hw && hw.io && typeof(hw.io.getClockTicks)=="function") 1681 return Number(hw.io.getClockTicks()); 1682 } 1683 return Number(acia.lastClockTick) || 0; 1684 } 1685 1686 function aciaAdvanceTiming(serialTicks) 1687 { 1688 var frameTicks = aciaFrameTicks(); 1689 if(!Number.isFinite(frameTicks) || frameTicks<=0 || 1690 !Number.isFinite(serialTicks) || serialTicks<=0) 1691 return false; 1692 1693 if(acia.txBusy) 1694 { 1695 acia.txTicks += serialTicks; 1696 if(acia.txTicks>=frameTicks) 1697 { 1698 var txByte = acia.txData & aciaDataMask(); 1699 acia.txTicks = 0; 1700 acia.txBusy = false; 1701 1702 if(!aciaBreakActive()) 1703 serialLineTransmitByte(txByte); 1704 1705 1706 acia.txEmpty = true; 1707 if(aciaTransmitIRQEnabled()) 1708 aciaAssertIRQ(ACIA_IRQ_TX); 1709 } 1710 } 1711 1712 if(acia.rxBusy) 1713 { 1714 acia.rxTicks += serialTicks; 1715 if(acia.rxTicks>=frameTicks) 1716 { 1717 acia.rxTicks = 0; 1718 acia.rxBusy = false; 1719 acia.rxData = acia.rxShift & aciaDataMask(); 1720 acia.rxFull = true; 1721 if(aciaReceiverIRQEnabled()) 1722 aciaAssertIRQ(ACIA_IRQ_RX); 1723 } 1724 } 1725 return true; 1726 } 1727 1728 function aciaSyncClock(clockTick) 1729 { 1730 clockTick = Number(clockTick); 1731 if(!Number.isFinite(clockTick)) return false; 1732 1733 var previous = Number(acia.lastClockTick); 1734 acia.lastClockTick = clockTick; 1735 if(!Number.isFinite(previous) || clockTick<=previous) return true; 1736 1737 return aciaAdvanceTiming((clockTick-previous)*aciaClockScale()); 1738 } 1739 1740 function aciaSyncNow(ctx) 1741 { 1742 return aciaSyncClock(aciaClockNow(ctx)); 1743 } 1744 1745 function aciaDTRAsserted() 1746 { 1747 return (acia.command & 0x01)!=0; // DTRB low 1748 } 1749 1750 function aciaReceiverIRQEnabled() 1751 { 1752 return aciaDTRAsserted() && (acia.command & 0x02)==0; 1753 } 1754 1755 function aciaTransmitMode() 1756 { 1757 return (acia.command >> 2) & 0x03; 1758 } 1759 1760 function aciaEchoMode() 1761 { 1762 return (acia.command & 0x10)!=0; 1763 } 1764 1765 function aciaRTSAsserted() 1766 { 1767 if(aciaEchoMode()) return true; // echo mode forces RTSB low 1768 return aciaTransmitMode()!=0; // TIC 00 is RTSB high 1769 } 1770 1771 function aciaTransmitIRQEnabled() 1772 { 1773 return aciaDTRAsserted() && !aciaEchoMode() && aciaTransmitMode()==1; 1774 } 1775 1776 function aciaBreakActive() 1777 { 1778 return !aciaEchoMode() && aciaTransmitMode()==3; 1779 } 1780 1781 function aciaEvaluateEnabledInterrupts() 1782 { 1783 if(aciaReceiverIRQEnabled()) 1784 { 1785 if(acia.rxFull) 1786 aciaAssertIRQ(ACIA_IRQ_RX); 1787 if(acia.modemStatusLatched) 1788 aciaAssertIRQ(ACIA_IRQ_MODEM); 1789 } 1790 1791 if(aciaTransmitIRQEnabled() && acia.txEmpty) 1792 aciaAssertIRQ(ACIA_IRQ_TX); 1793 } 1794 1795 function aciaHardwareReset() 1796 { 1797 acia.command = 0x00; 1798 acia.control = 0x00; 1799 acia.rxData = 0x00; 1800 acia.rxFull = false; 1801 acia.rxErrors = 0x00; 1802 acia.rxShift = 0x00; 1803 acia.rxBusy = false; 1804 acia.rxTicks = 0; 1805 acia.txData = 0x00; 1806 acia.txEmpty = true; 1807 acia.txBusy = false; 1808 acia.txTicks = 0; 1809 acia.lastClockTick = aciaClockNow(); 1810 acia.dsrStatusReady = acia.dsrReady; 1811 acia.dcdStatusDetected = acia.dcdDetected; 1812 acia.modemStatusLatched = false; 1813 aciaClearIRQ(); 1814 1815 // A browser serial port stays physically open across Apple II RESET. 1816 serialPhysicalCheckConfiguration(); 1817 serialPhysicalSyncOutputs(); 1818 } 1819 1820 function aciaProgramReset() 1821 { 1822 // Legacy 6551 programmed reset: clear Command bits 4..0 and Status 1823 // overrun. Parity selection bits 7..5 and Control are unchanged. 1824 acia.command &= 0xE0; 1825 acia.rxErrors &= ~0x04; 1826 aciaClearIRQ(); 1827 serialPhysicalCheckConfiguration(); 1828 serialPhysicalSyncOutputs(); 1829 } 1830
1831 function aciaWriteCommand(value) 1832 { 1833 acia.command = value & 0xFF; 1834 1835 // Leaving a blocked condition may start a waiting transmitter frame. 1836 aciaTryTransmit(); 1837 aciaEvaluateEnabledInterrupts(); 1838 serialPhysicalCheckConfiguration(); 1839 serialPhysicalSyncOutputs(); 1840 } 1841 1842 function aciaWriteControl(value) 1843 { 1844 acia.control = value & 0xFF; 1845 aciaTryTransmit(); 1846 aciaPrimeReceiver(); 1847 serialPhysicalCheckConfiguration(); 1848 } 1849 1850 /* 1851 * Receiver bridge. serialTerminalState.toCard[] is the remote-end byte 1852 * stream waiting on the simulated line. A byte first occupies a simple 1853 * receive-shift stage for one configured frame time; only then does RDRF 1854 * become visible to the CPU. 1855 * 1856 * This stage deliberately retains the existing one-byte receive holding 1857 * model: the next queued frame starts after DATA is consumed. Detailed 1858 * overrun behavior remains a later refinement. 1859 */ 1860 function aciaPrimeReceiver() 1861 { 1862 if(acia.rxFull || acia.rxBusy) return true; 1863 if(!serialTerminalState || !serialTerminalState.toCard) return false; 1864 if(!serialTerminalState.toCard.length) return false; 1865 if((acia.control & 0x10)==0) return false; // external receiver clock 1866 if(!Number.isFinite(aciaBaudRate())) return false; 1867 1868 acia.rxShift = serialTerminalState.toCard.shift() & aciaDataMask(); 1869 acia.rxTicks = 0; 1870 acia.rxBusy = true; 1871 return true; 1872 } 1873 1874 function aciaRXPending() 1875 { 1876 return !!acia.rxFull; 1877 } 1878 1879 function aciaReadData(ctx) 1880 { 1881 if(!aciaRXPending()) 1882 return acia.rxData & 0xFF; 1883 1884 // Safe debugger/memory inspection must not consume the receive register. 1885 if(ctx && ctx.bRO===true) 1886 return acia.rxData & 0xFF; 1887 1888 var value = acia.rxData & 0xFF; 1889 acia.rxFull = false; 1890 acia.rxErrors = 0x00; // PE/FE/OVRN clear after DATA read 1891 // Start, but do not instantly complete, the next queued remote frame. 1892 aciaPrimeReceiver(); 1893 return value; 1894 } 1895 1896 function aciaReadStatus(ctx) 1897 { 1898 var status = acia.rxErrors & 0x07; 1899 1900 if(acia.rxFull) status |= 0x08; // RDRF 1901 if(acia.txEmpty) status |= 0x10; // TDRE 1902 if(!acia.dcdStatusDetected) status |= 0x20;// DCD high/not detected 1903 if(!acia.dsrStatusReady) status |= 0x40;// DSR high/not ready 1904 if(acia.irq) status |= 0x80; 1905 1906 if(!(ctx && ctx.bRO===true)) 1907 { 1908 var hadModemLatch = acia.modemStatusLatched; 1909 aciaClearIRQ(); // Status read clears IRQ bit 1910 1911 /* 1912 * DCD/DSR status is latched on the first transition. If either 1913 * physical input changed again before Status was read, expose the 1914 * new level now and immediately generate the next modem interrupt. 1915 */ 1916 if(hadModemLatch) 1917 { 1918 acia.modemStatusLatched = false; 1919 1920 if(acia.dcdStatusDetected!=acia.dcdDetected || 1921 acia.dsrStatusReady!=acia.dsrReady) 1922 { 1923 acia.dcdStatusDetected = acia.dcdDetected; 1924 acia.dsrStatusReady = acia.dsrReady; 1925 acia.modemStatusLatched = true; 1926 1927 if(aciaReceiverIRQEnabled()) 1928 aciaAssertIRQ(ACIA_IRQ_MODEM); 1929 } 1930 } 1931 } 1932 1933 return status & 0xFF; 1934 } 1935 1936 function aciaTryTransmit() 1937 { 1938 if(acia.txEmpty || acia.txBusy || !acia.ctsClear) 1939 return false; 1940 if(!Number.isFinite(aciaBaudRate())) 1941 return false; 1942 1943 acia.txTicks = 0; 1944 acia.txBusy = true; 1945 return true; 1946 } 1947 1948 function aciaWriteData(d8) 1949 { 1950 acia.txData = d8 & 0xFF; 1951 acia.txEmpty = false; 1952 aciaTryTransmit(); 1953 } 1954 function aciaLatchModemStatusChange() 1955 { 1956 if(acia.modemStatusLatched) 1957 return; 1958 1959 acia.dcdStatusDetected = acia.dcdDetected; 1960 acia.dsrStatusReady = acia.dsrReady; 1961 acia.modemStatusLatched = true; 1962 1963 if(aciaReceiverIRQEnabled()) 1964 aciaAssertIRQ(ACIA_IRQ_MODEM); 1965 } 1966 1967 function aciaDescribe() 1968 {
1969 var baud = aciaBaudRate(); 1970 var receiverInternal = (acia.control & 0x10)!=0; 1971 1972 return { 1973 "command":acia.command & 0xFF 1974 ,"control":acia.control & 0xFF 1975 ,"status":aciaReadStatus({"bRO":true}) 1976 ,"wordLength":aciaWordLength() 1977 ,"dataMask":aciaDataMask() 1978 ,"parity":aciaParity() 1979 ,"parityCheck":aciaParityChecked() 1980 ,"stopBits":aciaStopBits() 1981 ,"txBaud":baud 1982 ,"effectiveTxBaud":aciaEffectiveBaudRate() 1983 ,"cpuSync":state.cpuSync 1984 ,"cpuMultiplier":aciaCPUMultiplier() 1985 ,"txClock":baud==null ? "external/16" : "internal" 1986 ,"rxBaud":receiverInternal ? baud : null 1987 ,"rxClock":receiverInternal ? "transmit-rate" : "external/16" 1988 ,"txBusy":acia.txBusy 1989 ,"rxBusy":acia.rxBusy 1990 ,"dtr":aciaDTRAsserted() 1991 ,"rts":aciaRTSAsserted() 1992 ,"cts":acia.ctsClear 1993 ,"dcd":acia.dcdDetected 1994 ,"dsr":acia.dsrReady 1995 ,"echo":aciaEchoMode() 1996 ,"break":aciaBreakActive() 1997 ,"rxIRQEnabled":aciaReceiverIRQEnabled() 1998 ,"txIRQEnabled":aciaTransmitIRQEnabled() 1999 ,"irq":acia.irq 2000 ,"irqReasons":acia.irqReasons & 0xFF 2001 ,"rxFull":acia.rxFull 2002 ,"txEmpty":acia.txEmpty 2003 }; 2004 2005 } 2006 2007 2008 this.action = 2009 { 2010 "SlotROM": 2011 { 2012 "RD":{"callback":function(addr,ctx) { 2013 return serialpro.readROM(addr,ctx); 2014 }} 2015 }, 2016 "SlotIO": 2017 { 2018 "RD":{"callback":function(addr,ctx) { 2019 return serialpro.readSlotIO(addr,ctx); 2020 }}, 2021 "WR":{"callback":function(addr,d8,ctx) { 2022 return serialpro.writeSlotIO(addr,d8,ctx); 2023 }} 2024 }, 2025 "HostROM": 2026 { 2027 "RD":{"callback":function(addr,ctx) { 2028 return serialpro.readHostROM(addr,ctx); 2029 }} 2030 } 2031 }; 2032 2033 2034 function ownsExpansionROM() 2035 { 2036 if(!serialpro.mount) return false; 2037 return oEMU.component.IO.ACTION_MAP.Hslot == (serialpro.mount.slotN-1); 2038 } 2039 2040 2041 function selectExpansionROM(ctx) 2042 { 2043 // Safe/debugger ROM inspection must not alter shared C8 ownership. 2044 if(ctx && ctx.bRO===true) return false; 2045 if(!serialpro.mount || !serialpro.mount.ranges || !serialpro.mount.ranges.HostROM) 2046 return false; 2047 2048 var CIO = oEMU.component.IO; 2049 if(!CIO.ACTION_MAP) return false; 2050 2051 if(CIO.ACTION_MAP.Hslot == (serialpro.mount.slotN-1)) 2052 return true; 2053 2054 CIO.ACTION_MAP.Hslot = serialpro.mount.slotN-1; 2055 2056 /* 2057 * Apple II expansion-ROM selection is independent from the Serial Pro's 2058 * own EPROM bank register. Reading this card's $Cnxx page claims the 2059 * shared $C800-$CFFF window for this slot; BANKCLR/BANKREG merely choose 2060 * which 2 KiB quarter of the card's 27C64 appears in that window. 2061 */ 2062 var range = serialpro.mount.ranges.HostROM; 2063 for(var i=range.from;i<=range.to;i++) 2064 CIO.ACTION_MAP.RD[i] = serialpro.action.HostROM.RD.callback; 2065 2066 if(bDebug) 2067 console.log("Serial Pro claims $C800-$CFFF from PR#"+(serialpro.mount.slotN-1)); 2068 2069 return true; 2070 } 2071 2072 2073 function releaseExpansionROM() 2074 { 2075 if(!serialpro.mount || !oEMU.component.IO.ACTION_MAP) return false; 2076 2077 var CIO = oEMU.component.IO; 2078 if(CIO.ACTION_MAP.Hslot != (serialpro.mount.slotN-1)) 2079 return false; 2080 2081 CIO.ACTION_MAP.Hslot = null; 2082 return true; 2083 } 2084 2085 function physicalHostROMBank() 2086 { 2087 /* 2088 * Reverse-engineered from the complete v2.0 EPROM and its dispatcher: 2089 * 2090 * selector .... ..00 -> EPROM $0000-$07FF 2091 * selector .... ..01 -> EPROM $0800-$0FFF 2092 * selector .... ..10 -> EPROM $1000-$17FF 2093 * selector .... ..11 -> EPROM $1800-$1FFF 2094 * 2095 * AE documents the two write-only switches, but not this bit-level 2096 * encoding. The firmware proves the low-two-bit relationship: e.g. 2097 * $01 and $09 use physical bank 1, while $02/$0A/$12 use bank 2. 2098 */ 2099 return state.romSelector & 0x03; 2100 } 2101 2102 function rtcIsBinary() 2103 { 2104 return (rtc.ram[0x0B] & 0x04) != 0; 2105 } 2106 2107 function rtcIs24Hour() 2108 { 2109 return (rtc.ram[0x0B] & 0x02) != 0; 2110 } 2111 2112 function rtcToBCD(value) 2113 { 2114 value = Math.max(0,Math.floor(value)); 2115 return (((Math.floor(value/10) % 10) << 4) | (value % 10)) & 0xFF; 2116 } 2117 2118 function rtcFromBCD(value) 2119 { 2120 return (((value >> 4) & 0x0F) * 10) + (value & 0x0F); 2121 } 2122 2123 function rtcEncode(value) 2124 { 2125 return rtcIsBinary() ? (value & 0xFF) : rtcToBCD(value); 2126 } 2127 2128 function rtcDecode(value) 2129 { 2130 return rtcIsBinary() ? (value & 0xFF) : rtcFromBCD(value); 2131 } 2132 2133 function rtcCurrentDate() 2134 { 2135 var timeMs = rtc.baseTimeMs; 2136 2137 // Register B bit 7 is SET/HOLD. While clear the clock advances at 2138 // wall-clock speed; while set it remains frozen for coherent updates. 2139 if((rtc.ram[0x0B] & 0x80)==0) 2140 timeMs += Date.now() - rtc.hostBaseMs; 2141 2142 return new Date(timeMs); 2143 } 2144 2145 function rtcRebase(date) 2146 { 2147 rtc.baseTimeMs = date.getTime();
2148 rtc.hostBaseMs = Date.now(); 2149 2150 // A programmed clock jump must not be mistaken for a natural 2151 // update-ended boundary on the next peripheral timing sample. 2152 rtc.lastUpdateSecond = Math.floor(rtc.baseTimeMs/1000); 2153 } 2154 2155 function rtcUpdateEndedPoll() 2156 { 2157 var second = Math.floor(rtcCurrentDate().getTime()/1000); 2158 2159 // SET/HOLD suppresses time updates. Keep the detector aligned with the 2160 // frozen image so releasing SET waits for the next real second edge. 2161 if((rtc.ram[0x0B] & RTC_B_SET)!=0) 2162 { 2163 rtc.lastUpdateSecond = second; 2164 return false; 2165 } 2166 2167 if(!Number.isFinite(rtc.lastUpdateSecond)) 2168 { 2169 rtc.lastUpdateSecond = second; 2170 return false; 2171 } 2172 2173 if(second==rtc.lastUpdateSecond) 2174 return false; 2175 2176 // A delayed emulator slice can cross more than one wall-clock second. 2177 // Register C is level/flag based, so one pending UF is sufficient. 2178 rtc.lastUpdateSecond = second; 2179 rtc.ram[0x0C] |= RTC_C_UF; 2180 2181 if((rtc.ram[0x0B] & RTC_B_UIE)!=0) 2182 { 2183 rtc.ram[0x0C] |= RTC_C_IRQF; 2184 rtcAssertInterrupt(); 2185 } 2186 return true; 2187 } 2188 2189 function rtcIRQSampleTicks() 2190 { 2191 var base = typeof(_o)!="undefined" ? Number(_o.CPU_ClocksTicks_s) : NaN; 2192 return Number.isFinite(base) && base>0 2193 ? Math.max(1,Math.floor(base/32)) // about 32 polls/sec at x1 2194 : 32768; 2195 } 2196 2197 function rtcSyncClock(clockTick) 2198 { 2199 clockTick = Number(clockTick); 2200 if(!Number.isFinite(clockTick)) return false; 2201 2202 var previous = Number(rtc.lastIRQSampleTick); 2203 2204 // CPU RESET/restart can move the completed-cycle counter backwards. 2205 if(!Number.isFinite(previous) || clockTick<previous) 2206 { 2207 rtc.lastIRQSampleTick = clockTick; 2208 rtc.lastUpdateSecond = Math.floor(rtcCurrentDate().getTime()/1000); 2209 return true; 2210 } 2211 2212 if(clockTick-previous < rtcIRQSampleTicks()) 2213 return true; 2214 2215 rtc.lastIRQSampleTick = clockTick; 2216 rtcUpdateEndedPoll(); 2217 return true; 2218 } 2219 2220 function rtcEncodeHour(date) 2221 { 2222 var hour = date.getHours(); 2223 2224 if(rtcIs24Hour()) 2225 return rtcEncode(hour); 2226 2227 var pm = hour >= 12; 2228 hour %= 12; 2229 if(hour==0) hour = 12; 2230 return rtcEncode(hour) | (pm ? 0x80 : 0x00); 2231 } 2232 2233 function rtcDecodeHour(value) 2234 { 2235 if(rtcIs24Hour()) 2236 return Math.min(23,rtcDecode(value & 0x7F)); 2237 2238 var pm = (value & 0x80) != 0; 2239 var hour = rtcDecode(value & 0x7F); 2240 if(hour<1) hour = 1; 2241 if(hour>12) hour = 12; 2242 if(hour==12) hour = 0; 2243 return hour + (pm ? 12 : 0); 2244 } 2245 2246 function rtcReadData(ctx) 2247 { 2248 var reg = rtc.address & 0x3F; 2249 var date = rtcCurrentDate(); 2250 2251 switch(reg) 2252 { 2253 case 0x00: return rtcEncode(date.getSeconds()); 2254 case 0x01: return rtc.ram[reg]; // seconds alarm 2255 case 0x02: return rtcEncode(date.getMinutes()); 2256 case 0x03: return rtc.ram[reg]; // minutes alarm 2257 case 0x04: return rtcEncodeHour(date); 2258 case 0x05: return rtc.ram[reg]; // hours alarm 2259 case 0x06: 2260 { 2261 // 6818 day-of-week range is 1..7. JavaScript uses Sunday=0. 2262 var weekday = ((date.getDay() + rtc.weekdayOffset) % 7) + 1; 2263 return rtcEncode(weekday); 2264 } 2265 case 0x07: return rtcEncode(date.getDate()); 2266 case 0x08: return rtcEncode(date.getMonth()+1); 2267 case 0x09: return rtcEncode(date.getFullYear()%100); 2268 2269 case 0x0A: 2270 /* 2271 * Register A bit 7 is Update-In-Progress. RetroAppleJS does not 2272 * model the 1.984 ms ripple/update interval cycle-accurately; a 2273 * JavaScript Date snapshot is atomic for our purposes, so UIP is 2274 * deliberately reported clear. This is sufficient for the AE 2275 * firmware's $CsA9 safe-clock-image loop. 2276 */ 2277 return rtc.ram[0x0A] & 0x7F; 2278 2279 case 0x0B: 2280 return rtc.ram[0x0B]; 2281 2282 case 0x0C: 2283 { 2284 // Register C reports the latched interrupt flags. A real CPU read 2285 // acknowledges all RTC sources and releases the RTC IRQ request. 2286 // Debugger/read-only inspection must remain side-effect free. 2287 var flags = rtc.ram[0x0C] & 0xF0; 2288 if(!(ctx && ctx.bRO===true)) 2289 { 2290 rtc.ram[0x0C] = 0x00; 2291 rtcClearInterrupt(); 2292 } 2293 return flags; 2294 } 2295 2296 case 0x0D: 2297 return 0x80; // valid time/RAM 2298 } 2299 2300 // $0E-$3F are battery-backed RAM and are not affected by RTC updates. 2301 return rtc.ram[reg] & 0xFF; 2302 } 2303 2304 function rtcWriteData(value) 2305 { 2306 var reg = rtc.address & 0x3F; 2307 value &= 0xFF; 2308 2309 if(reg==0x01 || reg==0x03 || reg==0x05) 2310 { 2311 rtc.ram[reg] = value; // alarm registers 2312 return; 2313 } 2314 2315 if(reg==0x0A) 2316 { 2317 rtc.ram[0x0A] = value & 0x7F; // UIP is read only 2318 return; 2319 } 2320 2321 if(reg==0x0B) 2322 { 2323 var wasHeld = (rtc.ram[0x0B] & 0x80) != 0; 2324 var willHold = (value & 0x80) != 0; 2325 2326 // Freeze the exact current emulated clock before asserting SET. 2327 if(!wasHeld && willHold) 2328 rtcRebase(rtcCurrentDate()); 2329 2330 rtc.ram[0x0B] = value; 2331 2332 // Clearing SET restarts elapsed-time accumulation from the frozen 2333 // value rather than jumping by the amount of time spent halted. 2334 if(wasHeld && !willHold) 2335 {
2336 rtc.hostBaseMs = Date.now(); 2337 rtc.lastUpdateSecond = Math.floor(rtc.baseTimeMs/1000); 2338 } 2339 return; 2340 } 2341 2342 if(reg==0x0C || reg==0x0D) 2343 return; // read-only registers 2344 2345 if(reg>=0x0E) 2346 { 2347 rtc.ram[reg] = value; // battery RAM 2348 2349 // The ROM control panel stores the saved 6551 configuration in 2350 // battery RAM $23/$24. Defer DOM work to the dashboard refresh 2351 // sequencer; merely flag that the configured SER controls changed. 2352 if(serialUI && (reg==0x23 || reg==0x24)) 2353 serialUI.configDirty = true; 2354 return; 2355 } 2356 2357 var date = rtcCurrentDate(); 2358 var decoded = rtcDecode(value); 2359 2360 switch(reg) 2361 { 2362 case 0x00: 2363 date.setSeconds(Math.max(0,Math.min(59,decoded)),0); 2364 break; 2365 case 0x02: 2366 date.setMinutes(Math.max(0,Math.min(59,decoded))); 2367 break; 2368 case 0x04: 2369 date.setHours(rtcDecodeHour(value)); 2370 break; 2371 case 0x06: 2372 { 2373 // The DOW counter is independently writable on the 6818. Keep 2374 // an offset from JavaScript's calendar weekday so it advances 2375 // naturally while still honoring a value explicitly written. 2376 var requested = Math.max(1,Math.min(7,decoded)); 2377 var actual = date.getDay()+1; 2378 rtc.weekdayOffset = (requested - actual + 7) % 7; 2379 return; 2380 } 2381 case 0x07: 2382 date.setDate(Math.max(1,Math.min(31,decoded))); 2383 break; 2384 case 0x08: 2385 date.setMonth(Math.max(1,Math.min(12,decoded))-1); 2386 break; 2387 case 0x09: 2388 { 2389 var year = Math.max(0,Math.min(99,decoded)); 2390 var century = Math.floor(date.getFullYear()/100)*100; 2391 date.setFullYear(century + year); 2392 break; 2393 } 2394 } 2395 2396 rtcRebase(date); 2397 } 2398 2399 this.readSlotIO = function(addr,ctx) 2400 { 2401 addr &= 0x0F; 2402 2403 if(addr>=0x08 && addr<=0x0B) 2404 aciaSyncNow(ctx); 2405 2406 switch(addr) 2407 { 2408 case 0x05: // $C085+$s0: selected 6818 Clock/RAM data 2409 return rtcReadData(ctx); 2410 case 0x08: // $C088+$s0: 6551 Receiver Data Register 2411 return aciaReadData(ctx); 2412 2413 case 0x09: // $C089+$s0: 6551 Status Register 2414 return aciaReadStatus(ctx); 2415 2416 case 0x0A: // $C08A+$s0: 6551 Command Register 2417 return acia.command & 0xFF; 2418 2419 case 0x0B: // $C08B+$s0: 6551 Control Register 2420 return acia.control & 0xFF; 2421 } 2422 2423 // $x4, $x6 and $x7 are write-only. 2424 return 0x00; 2425 }; 2426 2427 this.readROM = function(addr,ctx) 2428 { 2429 /* 2430 * $Cn00-$CnFF slot-page view. 2431 * 2432 * The 27C64 does not contain one generic SlotROM. Its final seven 2433 * 256-byte pages are pre-assembled for slots 1..7: 2434 * 2435 * slot 1 -> EPROM $1900 2436 * slot 2 -> EPROM $1A00 2437 * ... 2438 * slot 7 -> EPROM $1F00 2439 * 2440 * Therefore a live card moved to another slot automatically exposes 2441 * the authentic slot-specific bytes; no run-time patching is needed. 2442 */ 2443 selectExpansionROM(ctx); 2444 2445 addr &= 0xFF; 2446 if(!serialpro.mount) return 0xFF; 2447 2448 /* 2449 * RetroAppleJS stores peripheral slots with an internal +1 offset: 2450 * Apple slot 2 -> mount.slotN == 3 2451 * (slotID2n()/slot2ID() in EMU_apple2io.js). 2452 * Convert back to the real Apple slot number before selecting the 2453 * corresponding pre-assembled SlotROM page in the 27C64. 2454 */ 2455 var slotN = Number(serialpro.mount.slotN) - 1; 2456 if(!Number.isInteger(slotN) || slotN<1 || slotN>7) return 0xFF; 2457 2458 var eprom_addr = 0x1800 + (slotN << 8) + addr; 2459 return SERIALPRO_EPROM[eprom_addr] & 0xFF; 2460 }; 2461 2462 2463 this.writeSlotIO = function(addr,d8,ctx) 2464 { 2465 /* 2466 * AE Serial Pro Programmer's Reference register map: 2467 * 2468 * $C084+$s0 6818 Clock/RAM Address (write only) 2469 * $C085+$s0 6818 Clock/RAM Data (read/write) 2470 * $C086+$s0 EPROM Bank Register Clear (write only) 2471 * $C087+$s0 EPROM Bank Register (write only) 2472 * $C088+$s0 6551 Data (read/write) 2473 * $C089+$s0 6551 Status / Program Reset (read/write) 2474 * $C08A+$s0 6551 Command (read/write) 2475 * $C08B+$s0 6551 Control (read/write) 2476 * 2477 * Apple2IO passes SlotIO as an offset $0..$F within the mounted slot's 2478 * $C0s0-$C0sF range, hence BANKCLR/BANKREG are offsets $06/$07. 2479 * 2480 * IMPORTANT: this register is independent of C8 ownership. A bank write 2481 * changes which EPROM bytes back logical $C800-$CFFF immediately, even 2482 * while the CPU is executing there. It does not itself claim or release 2483 * the Apple II expansion-ROM window. 2484 */ 2485 addr &= 0x0F; 2486 d8 &= 0xFF; 2487 2488 if(addr>=0x08 && addr<=0x0B) 2489 aciaSyncNow(ctx); 2490 2491 switch(addr) 2492 { 2493 case 0x04: // $C084+$s0: 6818 Clock/RAM address register 2494 rtc.address = d8 & 0x3F; 2495 return 0x00; 2496 2497 case 0x05: // $C085+$s0: selected 6818 Clock/RAM data 2498 rtcWriteData(d8); 2499 return 0x00; 2500 case 0x06: // $C086+$s0: EPROM Bank Register Clear 2501 // The hardware operation is selected by the address; the byte 2502 // written is irrelevant. Return to the resident HostROM bank 0. 2503 state.romSelector = 0x00; 2504 if(bDebug) console.log("Serial Pro EPROM BANKCLR -> selector $00"); 2505 return 0x00; 2506 2507 case 0x07: // $C087+$s0: EPROM Bank Register 2508 // Retain the whole selector. Only bits 0-1 are used to choose 2509 // the physical 2 KiB quarter; higher bits are decoded by firmware. 2510 state.romSelector = d8; 2511 if(bDebug) 2512 console.log( 2513 "Serial Pro EPROM BANKREG <- $"+d8.toString(16).padStart(2,"0")+ 2514 " (physical bank "+physicalHostROMBank()+")" 2515 ); 2516 return 0x00; 2517 case 0x08: // $C088+$s0: 6551 Transmitter Data Register 2518 aciaWriteData(d8); 2519 return 0x00; 2520 2521 case 0x09: // $C089+$s0: 6551 programmed reset; d8 is don't-care 2522 aciaProgramReset(); 2523 return 0x00; 2524 2525 case 0x0A: // $C08A+$s0: 6551 Command Register
2526 aciaWriteCommand(d8); 2527 return 0x00; 2528 2529 case 0x0B: // $C08B+$s0: 6551 Control Register 2530 aciaWriteControl(d8); 2531 return 0x00; 2532 } 2533 return 0x00; 2534 }; 2535 2536 2537 this.readHostROM = function(addr,ctx) 2538 { 2539 /* 2540 * $C800-$CFFF shared expansion-ROM view. 2541 * 2542 * The CPU-visible address selects one byte within a 2 KiB window, while 2543 * the Serial Pro EPROM selector chooses which physical 27C64 quarter is 2544 * behind that window: 2545 * 2546 * EPROM = ((romSelector & $03) * $0800) + (CPU - $C800) 2547 * 2548 * This lookup is performed on every fetch. Consequently a firmware 2549 * instruction such as STA $C087,Y can switch banks and the very next 2550 * opcode fetched at the same logical $C8xx address comes from the newly 2551 * selected physical bank, matching the observed AE firmware design. 2552 */ 2553 var rel_addr; 2554 2555 if(ctx && Number.isFinite(ctx.rel_addr)) 2556 rel_addr = ctx.rel_addr; 2557 else if(ctx && Number.isFinite(ctx.line)) 2558 rel_addr = ctx.line + (addr & 0xFF); 2559 else 2560 rel_addr = (serialpro.action.HostROM.base || 0x800) + (addr & 0xFF); 2561 2562 if(!ownsExpansionROM()) return 0x00; 2563 var bank_addr = rel_addr - 0x800; 2564 var eprom_addr = (physicalHostROMBank() << 11) + bank_addr; 2565 var d8 = bank_addr>=0 && bank_addr<0x800 2566 ? SERIALPRO_EPROM[eprom_addr] 2567 : 0x00; 2568 2569 // $CFFF releases C8 ownership only. It does NOT clear the AE bank 2570 // register; BANKCLR at $C086+$s0 is a separate hardware operation. 2571 if((rel_addr & 0x0FFF)==0x0FFF && !(ctx && ctx.bRO===true)) 2572 releaseExpansionROM(); 2573 2574 return d8 & 0xFF; 2575 }; 2576 2577 /* 2578 * Peripheral controls: compact live RTC editor. 2579 * 2580 * The RTC display uses the same dashboard refresh sequencer and visual 2581 * sync convention as the Disk II surface map: fa-sync-alt when stopped, 2582 * fa-stop-circle while live updates are active. 2583 * 2584 * Input fields remain editable while live sync is active. The refresh 2585 * callback never rewrites the field that currently owns keyboard focus, 2586 * so the user's cursor/selection remains undisturbed. Invalid input 2587 * marks only the invalid field red and disables SET; the user's live-sync 2588 * state remains unchanged while editing. 2589 */ 2590 2591 var rtcUI = 2592 { 2593 "syncRequested":false 2594 ,"controlID":"" 2595 ,"refreshEvent":"" 2596 }; 2597 2598 var serialUI = 2599 { 2600 "controlID":"" 2601 ,"refreshEvent":"" 2602 ,"configDirty":true 2603 }; 2604 2605 2606 function serialConfigWriteControl(value) 2607 { 2608 rtc.ram[0x23] = value & 0xFF; 2609 serialUI.configDirty = true; 2610 return rtc.ram[0x23]; 2611 } 2612 2613 function serialConfigWriteCommand(value) 2614 { 2615 rtc.ram[0x24] = value & 0xFF; 2616 serialUI.configDirty = true; 2617 return rtc.ram[0x24]; 2618 } 2619 2620 function rtcUIPad2(value) { return String(value).padStart(2,"0"); } 2621 2622 function rtcUIValues() 2623 { 2624 var date = rtcCurrentDate(); 2625 2626 return { 2627 "date": date.getFullYear() 2628 + "-" + rtcUIPad2(date.getMonth()+1) 2629 + "-" + rtcUIPad2(date.getDate()) 2630 ,"time": rtcUIPad2(date.getHours()) 2631 + ":" + rtcUIPad2(date.getMinutes()) 2632 + ":" + rtcUIPad2(date.getSeconds()) 2633 }; 2634 } 2635 2636 function rtcUIElement(controlID,suffix) 2637 { 2638 return document.getElementById(controlID+"_"+suffix); 2639 } 2640 2641 function rtcUIFieldValidity(element,valid) 2642 { 2643 if(element) 2644 element.style.borderColor = valid ? "transparent" : "#D00000"; 2645 } 2646 2647 function rtcUIParse(controlID,markValidity) 2648 { 2649 var dateEl = rtcUIElement(controlID,"rtc_date"); 2650 var timeEl = rtcUIElement(controlID,"rtc_time"); 2651 if(!dateEl || !timeEl) 2652 return {"valid":false,"dateValid":false,"timeValid":false,"date":null}; 2653 2654 var dm = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dateEl.value); 2655 var tm = /^(\d{2}):(\d{2}):(\d{2})$/.exec(timeEl.value); 2656 2657 var dateValid = !!dm; 2658 var timeValid = !!tm; 2659 var year,month,day,hour,minute,second; 2660 2661 if(dateValid) 2662 { 2663 year = Number(dm[1]);
2664 month = Number(dm[2]); 2665 day = Number(dm[3]); 2666 2667 var calendar = new Date(0); 2668 calendar.setFullYear(year,month-1,day); 2669 calendar.setHours(12,0,0,0); 2670 2671 dateValid = 2672 month>=1 && month<=12 && 2673 day>=1 && day<=31 && 2674 calendar.getFullYear()==year && 2675 calendar.getMonth()==month-1 && 2676 calendar.getDate()==day; 2677 } 2678 2679 if(timeValid) 2680 { 2681 hour = Number(tm[1]); 2682 minute = Number(tm[2]); 2683 second = Number(tm[3]); 2684 2685 timeValid = 2686 hour>=0 && hour<=23 && 2687 minute>=0 && minute<=59 && 2688 second>=0 && second<=59; 2689 } 2690 2691 if(markValidity) 2692 { 2693 rtcUIFieldValidity(dateEl,dateValid); 2694 rtcUIFieldValidity(timeEl,timeValid); 2695 } 2696 2697 if(!dateValid || !timeValid) 2698 return { 2699 "valid":false 2700 ,"dateValid":dateValid 2701 ,"timeValid":timeValid 2702 ,"date":null 2703 }; 2704 2705 var date = new Date(0); 2706 date.setFullYear(year,month-1,day); 2707 date.setHours(hour,minute,second,0); 2708 2709 // Also reject a local time which JavaScript normalised, for example a 2710 // non-existent wall-clock time inside a daylight-saving transition. 2711 var valid = 2712 date.getFullYear()==year && 2713 date.getMonth()==month-1 && 2714 date.getDate()==day && 2715 date.getHours()==hour && 2716 date.getMinutes()==minute && 2717 date.getSeconds()==second; 2718 2719 if(markValidity && !valid) 2720 { 2721 rtcUIFieldValidity(dateEl,false); 2722 rtcUIFieldValidity(timeEl,false); 2723 } 2724 2725 return { 2726 "valid":valid 2727 ,"dateValid":valid && dateValid 2728 ,"timeValid":valid && timeValid 2729 ,"date":valid ? date : null 2730 }; 2731 } 2732 2733 function rtcUIUpdateSyncButton() 2734 { 2735 if(!rtcUI.controlID) return false; 2736 2737 var monitor = rtcUIElement(rtcUI.controlID,"rtc_monitoring"); 2738 if(!monitor) return false; 2739 2740 var active = rtcUI.syncRequested; 2741 2742 oCOM.POPUP.set_class( 2743 monitor 2744 ,"fa-stop-circle" 2745 ,"fa-sync-alt" 2746 ,active 2747 ); 2748 monitor.title = active ? "Stop date/time sync" : "Start date/time sync"; 2749 return active; 2750 } 2751 2752 function rtcUIUpdateSetButton(controlID,valid) 2753 { 2754 var setButton = rtcUIElement(controlID,"rtc_set"); 2755 if(setButton) setButton.disabled = !valid; 2756 return !!valid; 2757 } 2758 2759 function rtcUIRefresh(controlID,preserveFocus) 2760 { 2761 var values = rtcUIValues(); 2762 var dateEl = rtcUIElement(controlID,"rtc_date"); 2763 var timeEl = rtcUIElement(controlID,"rtc_time"); 2764 var activeEl = document.activeElement; 2765 2766 if(dateEl && (!preserveFocus || activeEl!==dateEl)) 2767 dateEl.value = values.date; 2768 2769 if(timeEl && (!preserveFocus || activeEl!==timeEl)) 2770 timeEl.value = values.time; 2771 return true; 2772 }; 2773 2774 2775 this.deviceToolRTCInput = function(controlID) 2776 { 2777 rtcUI.controlID = controlID; 2778 2779 var parsed = rtcUIParse(controlID,true); 2780 rtcUIUpdateSetButton(controlID,parsed.valid); 2781 2782 return parsed.valid; 2783 }; 2784 2785 this.deviceToolRTCSyncToggle = function(controlID) 2786 { 2787 rtcUI.controlID = controlID; 2788 rtcUI.syncRequested = !rtcUI.syncRequested; 2789 2790 var active = rtcUIUpdateSyncButton(); 2791 2792 if(active) 2793 { 2794 rtcUIRefresh(controlID,true); 2795 var parsed = rtcUIParse(controlID,true); 2796 rtcUIUpdateSetButton(controlID,parsed.valid); 2797 } 2798 2799 return active; 2800 }; 2801 2802 this.deviceToolRTCMonitoring = function() 2803 { 2804 if(!rtcUI.controlID || !rtcUI.syncRequested) return false; 2805 2806 // Keep the sync state visually stable while the user edits a field. 2807 // The focused field is never rewritten, so its cursor/selection stays 2808 // intact. The other field may continue following the live RTC. 2809 rtcUIRefresh(rtcUI.controlID,true); 2810 2811 var parsed = rtcUIParse(rtcUI.controlID,true); 2812 rtcUIUpdateSetButton(rtcUI.controlID,parsed.valid); 2813 return true; 2814 }; 2815 2816 function serialUIElement(controlID,suffix) 2817 { 2818 return document.getElementById(controlID+"_"+suffix); 2819 } 2820 2821 function serialUISetSelect(controlID,suffix,value,force) 2822 { 2823 var el = serialUIElement(controlID,suffix); 2824 if(el && (force || document.activeElement!==el))
2825 el.value = String(value); 2826 } 2827 2828 function serialUISetChecked(controlID,suffix,value,force) 2829 { 2830 var el = serialUIElement(controlID,suffix); 2831 if(el && (force || document.activeElement!==el)) 2832 el.checked = !!value; 2833 } 2834 2835 function serialUISetSignal(controlID,suffix,value,known) 2836 { 2837 var el = serialUIElement(controlID,suffix); 2838 if(!el) return; 2839 2840 el.textContent = "â"; 2841 el.style.color = !known ? "#999999" : (value ? "#008000" : "#A0A0A0"); 2842 } 2843 2844 function serialUIPhysicalTitle() 2845 { 2846 if(!serialPhysical.connected) 2847 return "Connect a physical USB serial adapter through Web Serial"; 2848 2849 var label = serialPhysical.openConfig ? serialPhysical.openConfig.label : "serial"; 2850 var title = "Physical USB serial connected @ "+label; 2851 2852 if(serialPhysical.configStale) 2853 title += "; ACIA format changed â disconnect and reconnect to apply it"; 2854 2855 if(serialPhysical.port && typeof(serialPhysical.port.getInfo)=="function") 2856 { 2857 var info = serialPhysical.port.getInfo() || {}; 2858 if(Number.isFinite(info.usbVendorId)) 2859 title += " VID $"+Number(info.usbVendorId).toString(16).toUpperCase().padStart(4,"0"); 2860 if(Number.isFinite(info.usbProductId)) 2861 title += " PID $"+Number(info.usbProductId).toString(16).toUpperCase().padStart(4,"0"); 2862 } 2863 2864 return title; 2865 } 2866 2867 function serialUIRefresh(controlID) 2868 { 2869 if(!controlID) return false; 2870 2871 /* 2872 * The SER row represents the configuration saved by the Serial Pro ROM, 2873 * not necessarily the live 6551 registers. This is what makes changes 2874 * made in the Bank-1 control panel appear in the browser immediately. 2875 * 2876 * If the ROM changed $23/$24 since the previous dashboard t
2876ick, force 2877 * these finite-choice controls even if one still owns browser focus. 2878 * Normal monitoring keeps the original focus-preservation behavior. 2879 */ 2880 var configured = serialConfiguredState(); 2881 var forceConfigured = !!serialUI.configDirty; 2882 2883 serialUISetSelect(controlID,"serial_baud",configured.baud,forceConfigured); 2884 serialUISetSelect(controlID,"serial_data",configured.dataBits,forceConfigured); 2885 serialUISetSelect(controlID,"serial_parity",configured.parity,forceConfigured); 2886 serialUISetSelect(controlID,"serial_stop",configured.stopBits,forceConfigured); 2887 serialUISetChecked(controlID,"serial_rxclock",configured.rxClockInternal,forceConfigured); 2888 serialUI.configDirty = false; 2889 2890 // CPU sync and modem outputs are live emulator/6551 state, not NVRAM. 2891 serialUISetChecked(controlID,"serial_cpu_sync",state.cpuSync); 2892 serialUISetChecked(controlID,"physical_dtr",aciaDTRAsserted()); 2893 serialUISetChecked(controlID,"physical_rts",aciaRTSAsserted()); 2894 2895 var button = serialUIElement(controlID,"physical_toggle"); 2896 if(button) 2897 { 2898 button.disabled = typeof(navigator)=="undefined" || !navigator.serial; 2899 button.textContent = serialPhysical.connected ? "DISCONNECT" : "CONNECT"; 2900 button.title = serialUIPhysicalTitle(); 2901 button.style.color = serialPhysical.connected 2902 ? (serialPhysical.configStale ? "#C07000" : "#008000") 2903 : ""; 2904 } 2905 2906 var signals = serialPhysical.signals; 2907 var known = serialPhysical.connected && !!signals; 2908 serialUISetSignal(controlID,"physical_cts",known && signals.cts,known); 2909 serialUISetSignal(controlID,"physical_dcd",known && signals.dcd,known); 2910 serialUISetSignal(controlID,"physical_dsr",known && signals.dsr,known); 2911 serialUISetSignal(controlID,"physical_ri", known && signals.ri, known); 2912 2913 var physicalLabel = serialUIElement(controlID,"physical_status"); 2914 if(physicalLabel) 2915 { 2916 physicalLabel.textContent = serialPhysical.connected 2917 ? (serialPhysical.configStale ? "STALE" : "ONLINE") 2918 : "OFFLINE"; 2919 physicalLabel.style.color = serialPhysical.connected 2920 ? (serialPhysical.configStale ? "#C07000" : "#008000") 2921 : "#777777"; 2922 physicalLabel.title = serialUIPhysicalTitle(); 2923 } 2924 2925 var baudSelect = serialUIElement(controlID,"serial_baud"); 2926 if(baudSelect) 2927 { 2928 var programmed = aciaBaudRate(); 2929 var effective = aciaEffectiveBaudRate(); 2930 2931 } 2932 2933 var cpuSync = serialUIElement(controlID,"serial_cpu_sync"); 2934 if(cpuSync) 2935 cpuSync.title = state.cpuSync 2936 ? "CPU sync ON: serial timing follows the CPU speed multiplier" 2937 : "CPU sync OFF: serial timing is normalized to approximately x1 regardless of CPU acceleration"; 2938 var configuredLabel = serialFormatLabel(configured.control,configured.command); 2939 var activeLabel = serialFormatLabel(acia.control,acia.command); 2940 var pending = serialFormatSignature(configured.control,configured.command) 2941 != serialFormatSignature(acia.control,acia.command); 2942 2943 baudSelect.title = 2944 "Serial Pro configured "+configuredLabel 2945 +"; live 6551 "+activeLabel 2946 +(pending ? " (pending/reinitialization differs)" : "") 2947 +(state.cpuSync 2948 ? "; effective live baud "+effective 2949 : "; CPU sync is off so live serial timing remains approximately x1"); 2950 return true; 2951 } 2952 2953 this.deviceToolSerialSetting = function(controlID,setting,value) 2954 { 2955 controlID = String(controlID || ""); 2956 setting = String(setting || ""); 2957 2958 // Apply elapsed cycles using the old serial format before changing it. 2959 aciaSyncNow(); 2960 2961 /* 2962 * Browser-originated format changes update BOTH layers: 2963 * - RTC RAM $23/$24, so the ROM control panel sees the same setting; 2964 * - the live 6551, preserving the browser UI's immediate behavior. 2965 * 2966 * Each layer preserves its own unrelated bits. This is important for 2967 * live DTR/RTS state, which must not be overwritten by a parity edit. 2968 */ 2969 var configured = serialConfiguredState(); 2970 2971 if(setting=="baud") 2972 { 2973 var baud = Number(value); 2974 var index = -1; 2975 for(var i=1;i<ACIA_BAUD_TABLE.length;i++) 2976 if(ACIA_BAUD_TABLE[i]===baud) { index=i; break; } 2977 2978 if(index<0) return false;
2979 serialConfigWriteControl((configured.control & 0xF0) | index); 2980 aciaWriteControl((acia.control & 0xF0) | index); 2981 } 2982 else if(setting=="data") 2983 { 2984 var bits = Number(value); 2985 if(bits<5 || bits>8) return false; 2986 var dataBits = (((8-bits) & 0x03) << 5); 2987 serialConfigWriteControl((configured.control & 0x9F) | dataBits); 2988 aciaWriteControl((acia.control & 0x9F) | dataBits); 2989 } 2990 else if(setting=="parity") 2991 { 2992 var parityBits = { 2993 "none":0x00 2994 ,"odd":0x20 2995 ,"even":0x60 2996 ,"mark":0xA0 2997 ,"space":0xE0 2998 }[String(value)]; 2999 3000 if(parityBits===undefined) return false; 3001 serialConfigWriteCommand((configured.command & 0x1F) | parityBits); 3002 aciaWriteCommand((acia.command & 0x1F) | parityBits); 3003 } 3004 else if(setting=="stop") 3005 { 3006 var stop = Number(value); 3007 if(stop!=1 && stop!=1.5 && stop!=2) return false; 3008 3009 // The 6551 has one SBN bit. With SBN=1 the effective result is 3010 // 1, 1.5 or 2 stop bits depending on word length/parity. 3011 serialConfigWriteControl( 3012 stop==1 ? (configured.control & 0x7F) : (configured.control | 0x80) 3013 ); 3014 aciaWriteControl( 3015 stop==1 ? (acia.control & 0x7F) : (acia.control | 0x80) 3016 ); 3017 } 3018 else return false; 3019 3020 serialUIRefresh(controlID); 3021 return true; 3022 }; 3023 3024 this.deviceToolSerialFlag = function(controlID,setting,enabled) 3025 { 3026 enabled = !!enabled; 3027 // Preserve elapsed timing under the mode/register state that existed 3028 // before this UI-originated change. 3029 aciaSyncNow(); 3030 3031 if(setting=="cpusync") 3032 { 3033 state.cpuSync = enabled; 3034 serialPhysicalCheckConfiguration(); 3035 } 3036 else if(setting=="rxclock") 3037 { 3038 var configured = serialConfiguredState(); 3039 serialConfigWriteControl(enabled 3040 ? (configured.control | 0x10) 3041 : (configured.control & 0xEF)); 3042 aciaWriteControl(enabled 3043 ? (acia.control | 0x10) 3044 : (acia.control & 0xEF)); 3045 } 3046 else if(setting=="dtr") 3047 { 3048 aciaWriteCommand(enabled 3049 ? (acia.command | 0x01) 3050 : (acia.command & 0xFE)); 3051 } 3052 else if(setting=="rts") 3053 { 3054 /* 3055 * Manual RTS control chooses the non-IRQ transmitter mode TIC=10 3056 * when asserted and TIC=00 when released. Clear echo mode because 3057 * echo forces RTS asserted regardless of TIC. 3058 */ 3059 var command = acia.command & ~0x1C; 3060 if(enabled) command |= 0x08; 3061 aciaWriteCommand(command); 3062 } 3063 else return false; 3064 3065 serialUIRefresh(controlID); 3066 return true; 3067 }; 3068 3069 this.deviceToolPhysicalToggle = async function(controlID) 3070 { 3071 try 3072 { 3073 if(serialPhysical.connected) 3074 await serialPhysicalDisconnect(false); 3075 else 3076 await serialPhysicalConnect(); 3077 } 3078 finally 3079 { 3080 serialUIRefresh(controlID); 3081 } 3082 3083 return serialPhysical.connected; 3084 }; 3085 3086 this.deviceToolSerialMonitoring = function() 3087 { 3088 if(!serialUI.controlID) return false; 3089 3090 // ROM writes to RTC RAM $23/$24 set serialUI.configDirty. The normal 3091 // dashboard cadence then propagates the saved format into the SER row. 3092 // CPU target speed may also change without touching an ACIA register. 3093 if(serialPhysical.connected) serialPhysicalCheckConfiguration(); 3094 return serialUIRefresh(serialUI.controlID); 3095 }; 3096 3097 /* 3098 * Serial-terminal endpoint. 3099 * 3100 * This UI exists before the 6551 implementation so Stage 2 has a stable 3101 * browser endpoint to attach to: 3102 * 3103 * serialTerminalReadByte() terminal -> future 6551 RX 3104 * serialTerminalWriteByte() future 6551 TX -> terminal 3105 * 3106 * Enter queues the exact 8-bit input line followed by CR ($0D). 3107 */ 3108 var serialTerminalState = 3109 { 3110 "toCard":[] 3111 ,"transcript":[] 3112 ,"transcriptChars":0 3113 ,"lastRxCR":false 3114 ,"theme":"dark" 3115 ,"displayMode":"ascii" 3116 }; 3117 3118 /* 3119 * Stage 3A: optional physical Web Serial transport. 3120 * 3121 * This is deliberately a transport beside oTERM, not part of the
31216551 3122 * register model. The ACIA remains synchronous from the Apple II's point 3123 * of view while the browser/OS/USB-UART serializes bytes in real time. 3124 * 3125 * The physical port is opened using the ACIA format that exists at connect 3126 * time. Web Serial can represent 7/8 data bits, 1/2 stop bits and 3127 * none/even/odd parity. Unsupported 6551 formats are rejected rather than 3128 * silently approximated. ACIA format changes while connected mark the 3129 * physical link stale; disconnect/reconnect applies the new format. 3130 * 3131 * DTR, RTS and BREAK are synchronized live with setSignals(). CTS/DCD/DSR 3132 * are sampled for diagnostics, but Stage 3A intentionally does not feed 3133 * them into the emulated ACIA. Many cheap USB-UART modules expose only 3134 * TXD/RXD and would otherwise appear permanently blocked by unused modem 3135 * inputs. A later stage can make modem-line bridging explicit/optional. 3136 */ 3137 var serialPhysical = 3138 { 3139 "port":null 3140 ,"reader":null 3141 ,"writer":null 3142 ,"readTask":null 3143 ,"txTask":null 3144 ,"txBusy":false 3145 ,"txQueue":[] 3146 ,"connected":false 3147 ,"closing":false 3148 ,"openConfig":null 3149 ,"configStale":false 3150 ,"signals":null 3151 ,"signalTimer":null 3152 ,"lastIncomingCR":false 3153 ,"lastError":"" 3154 ,"lastSignalError":"" 3155 }; 3156 3157 function serialPhysicalErrorText(error) 3158 { 3159 if(!error) return "Unknown error"; 3160 return error.name && error.message 3161 ? error.name+": "+error.message 3162 : (error.message || String(error)); 3163 } 3164 3165 this.serialTerminalConsoleWrite = function(text,meta) 3166 { 3167 text = String(text===undefined ? "" : text); 3168 if(!text.length) return 0; 3169 if(text.charAt(text.length-1)!="\n") text += "\n"; 3170 3171 serialTerminalRemember("meta",text); 3172 var terminal = serialTerminalLive(); 3173 if(terminal) terminal.write(text,"meta"); 3174 return text.length; 3175 }; 3176 3177 function serialPhysicalMeta(text) 3178 { 3179 return serialpro.serialTerminalConsoleWrite( 3180 text, 3181 {"source":"webserial"} 3182 ); 3183 } 3184 3185 function serialPhysicalConfiguration() 3186 { 3187 var programmedBaud = aciaBaudRate(); 3188 var baud = aciaEffectiveBaudRate(); 3189 var dataBits = aciaWordLength(); 3190 var stopBits = aciaStopBits(); 3191 var parity = aciaParity(); 3192 var receiverInternal = (acia.control & 0x10)!=0; 3193 3194 if(!Number.isFinite(baud) || baud<=0 || Math.floor(baud)!=baud) 3195 return { 3196 "ok":false 3197 ,"reason":"Web Serial requires a positive integer effective baud rate; ACIA baud is " 3198 +String(programmedBaud)+", effective baud is "+String(baud) 3199 }; 3200 3201 if(!receiverInternal) 3202 return {"ok":false, 3203 "reason":"Web Serial cannot reproduce the ACIA external receive clock; select the internal receive rate"}; 3204 3205 if(dataBits!=7 && dataBits!=8) 3206 return {"ok":false, 3207 "reason":"Web Serial supports 7 or 8 data bits; ACIA is configured for "+dataBits}; 3208 3209 if(stopBits!=1 && stopBits!=2) 3210 return {"ok":false, 3211 "reason":"Web Serial supports 1 or 2 stop bits; ACIA is configured for "+stopBits}; 3212 3213 if(parity!="none" && parity!="even" && parity!="odd") 3214 return {"ok":false, 3215 "reason":"Web Serial supports none/even/odd parity; ACIA parity is "+parity}; 3216 3217 var parityLetter = parity=="none" ? "N" : (parity=="even" ? "E" : "O"); 3218 var multiplier = aciaCPUMultiplier(); 3219 var signature = [baud,dataBits,parity,stopBits].join("/"); 3220 var label = baud+" "+dataBits+parityLetter+stopBits; 3221 if(state.cpuSync && Math.abs(multiplier-1)>1e-9) 3222 label += " (ACIA "+programmedBaud+" x"+multiplier+")"; 3223 3224 return { 3225 "ok":true 3226 ,"signature":signature 3227 ,"label":label 3228 ,"options":{ 3229 "baudRate":baud 3230 ,"dataBits":dataBits 3231 ,"stopBits":stopBits 3232 ,"parity":parity 3233 ,"flowControl":"none" 3234 } 3235 }; 3236 } 3237 3238 function serialPhysicalUpdateButton() 3239 { 3240 var popup = document.getElementById("serialProTerminal_popup"); 3241 if(!popup) return false;
3242 3243 var icon = popup.querySelector("[data-serial-webserial]"); 3244 if(!icon) return false; 3245 3246 var title; 3247 if(serialPhysical.connected && serialPhysical.configStale) 3248 { 3249 title = "USB serial connected, but ACIA format changed; reconnect to apply it"; 3250 icon.style.color = "#C07000"; 3251 } 3252 else if(serialPhysical.connected) 3253 { 3254 title = "Disconnect physical USB serial port"; 3255 icon.style.color = "#008000"; 3256 } 3257 else 3258 { 3259 title = "Connect physical USB serial port (Web Serial)"; 3260 icon.style.color = ""; 3261 } 3262 3263 icon.title = title; 3264 if(icon.parentElement) icon.parentElement.title = title; 3265 return serialPhysical.connected; 3266 } 3267 3268 function serialPhysicalCheckConfiguration() 3269 { 3270 if(!serialPhysical || !serialPhysical.connected) 3271 return false; 3272 3273 var config = serialPhysicalConfiguration(); 3274 var stale = !config.ok || !serialPhysical.openConfig || 3275 config.signature!=serialPhysical.openConfig.signature; 3276 var changed = stale!=serialPhysical.configStale; 3277 3278 serialPhysical.configStale = stale; 3279 serialPhysicalUpdateButton(); 3280 3281 if(changed && stale) 3282 serialPhysicalMeta( 3283 "Web Serial ACIA format changed; reconnect the USB serial port before physical byte transfer continues." 3284 ); 3285 3286 return stale; 3287 } 3288 3289 async function serialPhysicalSyncOutputs() 3290 { 3291 if(!serialPhysical || !serialPhysical.connected || !serialPhysical.port) 3292 return false; 3293 3294 try 3295 { 3296 await serialPhysical.port.setSignals({ 3297 "dataTerminalReady":aciaDTRAsserted() 3298 ,"requestToSend":aciaRTSAsserted() 3299 ,"break":aciaBreakActive() 3300 }); 3301 serialPhysical.lastSignalError = ""; 3302 return true; 3303 } 3304 catch(error) 3305 { 3306 var message = serialPhysicalErrorText(error); 3307 if(message!=serialPhysical.lastSignalError) 3308 { 3309 serialPhysical.lastSignalError = message; 3310 serialPhysicalMeta("Web Serial output-signal warning: "+message); 3311 } 3312 return false; 3313 } 3314 } 3315 3316 async function serialPhysicalPollSignals() 3317 { 3318 if(!serialPhysical.connected || !serialPhysical.port) 3319 return null; 3320 3321 try 3322 { 3323 var signals = await serialPhysical.port.getSignals(); 3324 serialPhysical.signals = { 3325 "cts":!!signals.clearToSend 3326 ,"dcd":!!signals.dataCarrierDetect 3327 ,"dsr":!!signals.dataSetReady 3328 ,"ri":!!signals.ringIndicator 3329 }; 3330 serialPhysical.lastSignalError = ""; 3331 return serialPhysical.signals; 3332 } 3333 catch(error) 3334 { 3335 var message = serialPhysicalErrorText(error); 3336 if(message!=serialPhysical.lastSignalError) 3337 { 3338 serialPhysical.lastSignalError = message; 3339 serialPhysicalMeta("Web Serial input-signal warning: "+message); 3340 } 3341 return null; 3342 } 3343 } 3344 3345 function serialPhysicalDisplayByte(d8) 3346 { 3347 d8 &= 0xFF; 3348 if(d8==0x0D) 3349 { 3350 serialPhysical.lastIncomingCR = true; 3351 return "\n"; 3352 } 3353 if(d8==0x0A) 3354 { 3355 if(serialPhysical.lastIncomingCR) 3356 { 3357 serialPhysical.lastIncomingCR = false; 3358 return ""; 3359 } 3360 return "\n"; 3361 } 3362 3363 serialPhysical.lastIncomingCR = false; 3364 if(d8==0x09) return "\t"; 3365 if(d8>=0x20 && d8<=0x7E) return String.fromCharCode(d8); 3366 return "[$"+serialTerminalHexByte(d8)+"]"; 3367 } 3368 3369 function serialPhysicalReceiveChunk(chunk) 3370 { 3371 if(!chunk || !chunk.length || serialPhysical.configStale) 3372 return 0; 3373 3374 var display = ""; 3375 for(var i=0;i<chunk.length;i++) 3376 display += serialPhysicalDisplayByte(chunk[i] & 0xFF); 3377 3378 var accepted = serialLineInjectBytes( 3379 chunk, 3380 {"source":"webserial"} 3381 ); 3382 3383 if(display.length) 3384 { 3385 serialTerminalRemember("tx",display); 3386 var terminal = serialTerminalLive(); 3387 if(terminal) terminal.write(display,"tx"); 3388 } 3389 return accepted; 3390 } 3391 3392 async function serialPhysicalReadLoop(port) 3393 { 3394 while(serialPhysical.connected && serialPhysical.port===port && port.readable) 3395 { 3396 var reader = port.readable.getReader(); 3397 serialPhysical.reader = reader; 3398 try 3399 { 3400 while(serialPhysical.connected) 3401 { 3402 var result = await reader.read(); 3403 if(result.done) break; 3404 if(result.value) serialPhysicalReceiveChunk(result.value); 3405 } 3406 } 3407 catch(error) 3408 { 3409 if(serialPhysical.connected && !serialPhysical.closing) 3410 serialPhysicalMeta("Web Serial read event: "+serialPhysicalErrorText(error)); 3411 } 3412 finally 3413 { 3414 try { reader.releaseLock(); } catch(ignore) {} 3415 if(serialPhysical.reader===reader) serialPhysical.reader = null; 3416 } 3417 3418 if(!serialPhysical.connected || !port.readable) break; 3419 } 3420 3421 if(serialPhysical.connected && !serialPhysical.closing) 3422 setTimeout(function(){ serialPhysicalDisconnect(true); },0); 3423 } 3424 3425 function serialPhysicalFlushTX() 3426 { 3427 if(serialPhysical.txBusy || !serialPhysical.connected || 3428 !serialPhysical.writer || serialPhysical.configStale) 3429 return serialPhysical.txTask; 3430 3431 serialPhysical.txBusy = true; 3432 serialPhysical.txTask = (async function() 3433 { 3434 try 3435 { 3436 while(serialPhysical.connected && serialPhysical.writer && 3437 !serialPhysical.configStale && serialPhysical.txQueue.length) 3438 { 3439 var count = Math.min(256,serialPhysical.txQueue.length); 3440 var bytes = new Uint8Array(serialPhysical.txQueue.splice(0,count)); 3441 await serialPhysical.writer.write(bytes); 3442 } 3443 } 3444 catch(error) 3445 { 3446 serialPhysical.lastError = serialPhysicalErrorText(error); 3447 serialPhysicalMeta("Web Serial write failed: "+serialPhysical.lastError); 3448 setTimeout(function(){ serialPhysicalDisconnect(true); },0); 3449 } 3450 finally { serialPhysical.txBusy = false; } 3451 })(); 3452 3453 return serialPhysical.txTask; 3454 } 3455 3456 function serialPhysicalWriteByte(d8) 3457 { 3458 if(!serialPhysical || !serialPhysical.connected || 3459 !serialPhysical.writer || serialPhysical.configStale) 3460 return false;
3461 3462 serialPhysical.txQueue.push(d8 & 0xFF); 3463 serialPhysicalFlushTX(); 3464 return true; 3465 } 3466 3467 function serialPhysicalPortLabel(port) 3468 { 3469 if(!port || typeof(port.getInfo)!="function") return "USB serial port"; 3470 3471 var info = port.getInfo() || {}; 3472 var parts = []; 3473 if(Number.isFinite(info.usbVendorId)) 3474 parts.push("VID $"+Number(info.usbVendorId).toString(16).toUpperCase().padStart(4,"0")); 3475 if(Number.isFinite(info.usbProductId)) 3476 parts.push("PID $"+Number(info.usbProductId).toString(16).toUpperCase().padStart(4,"0")); 3477 return parts.length ? parts.join(" ") : "USB serial port"; 3478 } 3479 3480 async function serialPhysicalConnect() 3481 { 3482 if(serialPhysical.connected) return true; 3483 3484 if(typeof(navigator)=="undefined" || !navigator.serial) 3485 { 3486 serialPhysicalMeta( 3487 "Web Serial is unavailable. Use a supported Chromium browser in a secure context (HTTPS)." 3488 ); 3489 return false; 3490 } 3491 3492 var config = serialPhysicalConfiguration(); 3493 if(!config.ok) 3494 { 3495 serialPhysicalMeta("Web Serial cannot open this ACIA format: "+config.reason+"."); 3496 return false; 3497 } 3498 3499 var port = null; 3500 try 3501 { 3502 // Deliberately unfiltered: let the browser chooser select the CH340 3503 // or another compatible USB serial adapter explicitly. 3504 port = await navigator.serial.requestPort(); 3505 await port.open(config.options); 3506 3507 if(!port.writable) 3508 throw new Error("Selected serial port has no writable stream"); 3509 3510 serialPhysical.port = port; 3511 serialPhysical.writer = port.writable.getWriter(); 3512 serialPhysical.connected = true; 3513 serialPhysical.closing = false; 3514 serialPhysical.openConfig = config; 3515 serialPhysical.configStale = false; 3516 serialPhysical.lastError = ""; 3517 serialPhysical.txQueue = []; 3518 3519 serialPhysicalUpdateButton(); 3520 await serialPhysicalSyncOutputs(); 3521 await serialPhysicalPollSignals(); 3522 3523 serialPhysical.readTask = serialPhysicalReadLoop(port); 3524 serialPhysical.signalTimer = setInterval(serialPhysicalPollSignals,500); 3525 3526 serialPhysicalMeta( 3527 "Web Serial connected: "+serialPhysicalPortLabel(port)+" @ "+config.label+"." 3528 ); 3529 return true; 3530 } 3531 catch(error) 3532 { 3533 serialPhysical.lastError = serialPhysicalErrorText(error); 3534 if(port) 3535 { 3536 try { await port.close(); } catch(ignore) {} 3537 } 3538 3539 serialPhysical.port = null; 3540 serialPhysical.writer = null; 3541 serialPhysical.connected = false; 3542 serialPhysical.openConfig = null; 3543 serialPhysicalUpdateButton(); 3544 3545 if(error && error.name=="NotFoundError") 3546 serialPhysicalMeta("Web Serial device selection cancelled."); 3547 else 3548 serialPhysicalMeta("Web Serial connection failed: "+serialPhysical.lastError); 3549 return false; 3550 } 3551 } 3552 3553 async function serialPhysicalDisconnect(silent) 3554 { 3555 if(serialPhysical.closing) return false; 3556 if(!serialPhysical.port && !serialPhysical.connected) return false; 3557 3558 serialPhysical.closing = true; 3559 serialPhysical.connected = false; 3560 serialPhysicalUpdateButton(); 3561 3562 if(serialPhysical.signalTimer) 3563 { 3564 clearInterval(serialPhysical.signalTimer); 3565 serialPhysical.signalTimer = null; 3566 } 3567 3568 var reader = serialPhysical.reader; 3569 if(reader) 3570 { 3571 try { await reader.cancel(); } catch(ignore) {} 3572 } 3573 3574 if(serialPhysical.readTask) 3575 { 3576 try { await serialPhysical.readTask; } catch(ignore) {} 3577 } 3578 if(serialPhysical.txTask) 3579 { 3580 try { await serialPhysical.txTask; } catch(ignore) {} 3581 } 3582 if(serialPhysical.writer) 3583 { 3584 try { serialPhysical.writer.releaseLock(); } catch(ignore) {} 3585 } 3586 3587 var port = serialPhysical.port; 3588 if(port) 3589 { 3590 try { await port.close(); } catch(ignore) {} 3591 } 3592 3593 serialPhysical.port = null; 3594 serialPhysical.reader = null; 3595 serialPhysical.writer = null; 3596 serialPhysical.readTask = null; 3597 serialPhysical.txTask = null; 3598 serialPhysical.txBusy = false;
3599 serialPhysical.txQueue = []; 3600 serialPhysical.openConfig = null; 3601 serialPhysical.configStale = false; 3602 serialPhysical.signals = null; 3603 serialPhysical.closing = false; 3604 serialPhysicalUpdateButton(); 3605 3606 if(!silent) serialPhysicalMeta("Web Serial disconnected."); 3607 return true; 3608 } 3609 3610 function serialTerminalHexByte(d8) 3611 { 3612 return ("0"+((d8 & 0xFF).toString(16).toUpperCase())).slice(-2); 3613 } 3614 3615 this.serialPhysicalState = function() 3616 { 3617 var info = serialPhysical.port && typeof(serialPhysical.port.getInfo)=="function" 3618 ? serialPhysical.port.getInfo() 3619 : {}; 3620 3621 return { 3622 "supported":typeof(navigator)!="undefined" && !!navigator.serial 3623 ,"connected":serialPhysical.connected 3624 ,"configuration":serialPhysical.openConfig 3625 ? serialPhysical.openConfig.options 3626 : null 3627 ,"configurationLabel":serialPhysical.openConfig 3628 ? serialPhysical.openConfig.label 3629 : null 3630 ,"configurationStale":serialPhysical.configStale 3631 ,"usbVendorId":Number.isFinite(info.usbVendorId) ? info.usbVendorId : null 3632 ,"usbProductId":Number.isFinite(info.usbProductId) ? info.usbProductId : null 3633 ,"signals":serialPhysical.signals 3634 ,"txQueued":serialPhysical.txQueue.length 3635 ,"lastError":serialPhysical.lastError 3636 }; 3637 }; 3638 3639 this.serialPhysicalConnect = function() 3640 { 3641 return serialPhysicalConnect(); 3642 }; 3643 3644 this.serialPhysicalDisconnect = function() 3645 { 3646 return serialPhysicalDisconnect(false); 3647 }; 3648 3649 this.serialPhysicalToggle = function() 3650 { 3651 return serialPhysical.connected 3652 ? serialPhysicalDisconnect(false) 3653 : serialPhysicalConnect(); 3654 }; 3655 3656 this.serialPhysicalPollSignals = function() 3657 { 3658 return serialPhysicalPollSignals(); 3659 }; 3660 3661 function serialTerminalPopup() 3662 { 3663 var popup = document.getElementById("serialProTerminal_popup"); 3664 if(popup) return popup; 3665 3666 popup = document.createElement("div"); 3667 popup.id = "serialProTerminal_popup"; 3668 popup.hidden = true; 3669 popup.className = "appbox com_popup_frame serialpro_terminal_popup"; 3670 popup.style.cssText = 3671 "position:absolute;z-index:3;left:800px;top:32px;" 3672 +"width:450px;height:450px;text-align:left;" 3673 +"padding:0px;margin:0px"; 3674 document.body.appendChild(popup); 3675 return popup; 3676 } 3677 3678 function serialTerminalSlotN() 3679 { 3680 return serialpro.mount ? Number(serialpro.mount.slotN) : null; 3681 } 3682 3683 function serialTerminalLive() 3684 { 3685 var popup = document.getElementById("serialProTerminal_popup"); 3686 var slotN = serialTerminalSlotN(); 3687 3688 if(!popup || popup.hidden!==false || slotN===null) return null; 3689 if(Number(popup.getAttribute("data-slotN"))!==slotN) return null; 3690 return popup._terminal || null; 3691 } 3692 3693 function serialTerminalRemember(kind,text) 3694 { 3695 text = String(text===undefined ? "" : text); 3696 if(!text.length) return; 3697 3698 var rows = serialTerminalState.transcript; 3699 var last = rows.length ? rows[rows.length-1] : null; 3700 3701 if(last && last.kind===kind && !last.bytes && last.text.length<4096) 3702 last.text += text; 3703 else 3704 rows.push({"kind":kind,"text":text}); 3705 3706 serialTerminalState.transcriptChars += text.length; 3707 while(serialTerminalState.transcriptChars>65536 && rows.length>1) 3708 { 3709 var dropped = rows.shift(); 3710 serialTerminalState.transcriptChars -= dropped.bytes 3711 ? dropped.bytes.length 3712 : dropped.text.length; 3713 } 3714 } 3715 3716 function serialTerminalRememberByte(d8) 3717 { 3718 d8 &= 0xFF; 3719 3720 var rows = serialTerminalState.transcript; 3721 var last = rows.length ? rows[rows.length-1] : null; 3722 3723 if(last && last.kind==="rx" && last.bytes && last.bytes.length<4096) 3724 last.bytes.push(d8); 3725 else 3726 rows.push({"kind":"rx","bytes":[d8]}); 3727 3728 serialTerminalState.transcriptChars++; 3729 while(serialTerminalState.transcriptChars>65536 && rows.length>1) 3730 { 3731 var dropped = rows.shift(); 3732 serialTerminalState.transcriptChars -= dropped.bytes 3733 ? dropped.bytes.length 3734 : dropped.text.length; 3735 } 3736 } 3737 3738 function serialTerminalDisplayByte(d8) 3739 { 3740 d8 &= 0xFF; 3741 3742 /* 3743 * Apple II text normally travels with bit 7 set. APPLE ASCII mode is 3744 * intended for LIST/PR# capture and printer-like output: strip bit 7
3745 * before interpreting CR/LF/TAB and printable ASCII. RAW mode retains 3746 * the original diagnostic rendering of non-printing 8-bit values. 3747 */ 3748 var ascii = serialTerminalState.displayMode==="ascii"; 3749 var c = ascii ? (d8 & 0x7F) : d8; 3750 3751 if(c===0x0D) 3752 { 3753 serialTerminalState.lastRxCR = true; 3754 return "\n"; 3755 } 3756 3757 if(c===0x0A) 3758 { 3759 if(serialTerminalState.lastRxCR) 3760 { 3761 serialTerminalState.lastRxCR = false; 3762 return ""; 3763 } 3764 return "\n"; 3765 } 3766 3767 serialTerminalState.lastRxCR = false; 3768 if(c===0x09) return "\t"; 3769 3770 if(ascii) 3771 { 3772 // Backspace/NUL and other Apple control traffic should not turn a 3773 // human-readable printer/listing stream back into byte notation. 3774 if(c===0x0C) return "\n"; // form feed: readable break 3775 if(c>=0x20 && c<=0x7E) return String.fromCharCode(c); 3776 return ""; 3777 } 3778 3779 if(c>=0x20 && c<=0x7E) return String.fromCharCode(c); 3780 3781 // Keep otherwise non-printing octets visible in the terminal. 3782 return "[$"+serialTerminalHexByte(d8)+"]"; 3783 } 3784 3785 function serialTerminalRenderRow(row) 3786 { 3787 if(row && row.bytes) 3788 { 3789 var text = ""; 3790 for(var i=0;i<row.bytes.length;i++) 3791 text += serialTerminalDisplayByte(row.bytes[i]); 3792 return text; 3793 } 3794 3795 return row && row.text ? row.text : ""; 3796 } 3797 3798 function serialTerminalRenderTranscript(terminal) 3799 { 3800 if(!terminal || !terminal._o || !terminal._o.DOM) return false; 3801 3802 terminal._o.DOM.output.innerHTML = ""; 3803 serialTerminalState.lastRxCR = false; 3804 3805 var rows = serialTerminalState.transcript; 3806 for(var i=0;i<rows.length;i++) 3807 { 3808 var text = serialTerminalRenderRow(rows[i]); 3809 if(text.length) terminal.write(text,rows[i].kind); 3810 } 3811 3812 return true; 3813 } 3814 3815 this.serialTerminalQueueLine = function(line) 3816 { 3817 line = String(line===undefined ? "" : line); 3818 var bytes = new Uint8Array(line.length + 1); 3819 3820 for(var i=0;i<line.length;i++) 3821 bytes[i] = line.charCodeAt(i) & 0xFF; 3822 3823 // ENTER on the terminal is the remote endpoint sending CR. 3824 bytes[line.length] = 0x0D; 3825 serialTerminalRemember("tx",">"+line+"\n"); 3826 return serialLineInjectBytes( 3827 bytes, 3828 {"source":"oterm"} 3829 ); 3830 3831 }; 3832 3833 this.serialTerminalPending = function() 3834 { 3835 return serialTerminalState.toCard.length + (acia.rxFull ? 1 : 0) + (acia.rxBusy ? 1 : 0); 3836 }; 3837 3838 /* 3839 * Diagnostic/configuration API for the simulated modem side. 3840 * Boolean line names use their logical meaning, hiding the 6551's active-low 3841 * pin names: cts=true means CTSB low/clear-to-send, dcd=true means carrier 3842 * detected, and dsr=true means data set ready. 3843 */ 3844 this.serialTerminalACIAState = function() 3845 { 3846 aciaSyncNow(); 3847 return aciaDescribe(); 3848 }; 3849 3850 this.serialTerminalSetModemLines = function(lines) 3851 { 3852 aciaSyncNow(); 3853 lines = lines || {}; 3854 3855 var oldCTS = acia.ctsClear; 3856 var modemChanged = false; 3857 3858 if(Object.prototype.hasOwnProperty.call(lines,"cts")) 3859 acia.ctsClear = !!lines.cts; 3860 3861 if(Object.prototype.hasOwnProperty.call(lines,"dcd")) 3862 { 3863 var nextDCD = !!lines.dcd; 3864 modemChanged = modemChanged || nextDCD!=acia.dcdDetected; 3865 acia.dcdDetected = nextDCD; 3866 } 3867 3868 if(Object.prototype.hasOwnProperty.call(lines,"dsr")) 3869 { 3870 var nextDSR = !!lines.dsr; 3871 modemChanged = modemChanged || nextDSR!=acia.dsrReady; 3872 acia.dsrReady = nextDSR; 3873 } 3874 3875 if(modemChanged) 3876 aciaLatchModemStatusChange(); 3877 3878 // CTSB returning low allows a held transmitter frame to begin. 3879 if(!oldCTS && acia.ctsClear) 3880 aciaTryTransmit(); 3881 3882 return aciaDescribe(); 3883 }; 3884 3885 this.serialTerminalReadByte = function() 3886 { 3887 aciaSyncNow(); 3888 // Diagnostic helper: consume through the same receive-register path 3889 // used by CPU reads so console tests cannot bypass Stage 2C semantics. 3890 if(!acia.rxFull) 3891 aciaPrimeReceiver(); 3892 3893 return acia.rxFull ? aciaReadData(null) : null; 3894 }; 3895 3896 this.serialTerminalWriteByte = function(d8) 3897 { 3898 d8 &= 0xFF; 3899 serialTerminalRememberByte(d8); 3900 3901 var text = serialTerminalDisplayByte(d8); 3902 if(!text.length) return d8 & 0xFF; 3903 3904 var terminal = serialTerminalLive(); 3905 if(terminal) terminal.write(text,"rx"); 3906 3907 return d8 & 0xFF; 3908 }; 3909 3910 this.serialTerminalWriteText = function(text) 3911 { 3912 text = String(text===undefined ? "" : text); 3913 for(var i=0;i<text.length;i++)
3914 serialpro.serialTerminalWriteByte(text.charCodeAt(i) & 0xFF); 3915 return text.length; 3916 }; 3917 3918 this.serialTerminalClear = function() 3919 { 3920 serialTerminalState.transcript = []; 3921 serialTerminalState.transcriptChars = 0; 3922 serialTerminalState.lastRxCR = false; 3923 3924 var terminal = serialTerminalLive(); 3925 if(terminal) terminal.clear(); 3926 return true; 3927 }; 3928 3929 function serialTerminalSelectionInsideOutput(terminal) 3930 { 3931 if(!terminal || !terminal._o || !terminal._o.DOM || 3932 typeof(window)==="undefined" || typeof(window.getSelection)!="function") 3933 return false; 3934 3935 var selection = window.getSelection(); 3936 if(!selection || selection.rangeCount<1 || selection.isCollapsed) 3937 return false; 3938 3939 var output = terminal._o.DOM.output; 3940 function inside(node) 3941 { 3942 if(!node) return false; 3943 if(node.nodeType===3) node = node.parentNode; 3944 return node===output || output.contains(node); 3945 } 3946 3947 return inside(selection.anchorNode) || inside(selection.focusNode); 3948 } 3949 3950 function serialTerminalProtectStreaming(terminal) 3951 { 3952 if(!terminal || terminal._serialProWriteWrapped) return false; 3953 3954 var dom = terminal._o && terminal._o.DOM; 3955 if(!dom) return false; 3956 3957 // Some emulator-level UI styles disable text selection. Override that 3958 // locally: the Serial Pro output is intentionally printer-like text. 3959 if(dom.output && dom.output.style) 3960 { 3961 dom.output.style.userSelect = "text"; 3962 dom.output.style.webkitUserSelect = "text"; 3963 } 3964 3965 var baseWrite = terminal.write.bind(terminal); 3966 terminal.write = function(text,channel) 3967 { 3968 var input = dom.input; 3969 var preserveSelection = serialTerminalSelectionInsideOutput(terminal); 3970 var scrollTop = dom.container.scrollTop; 3971 var scrollLeft = dom.container.scrollLeft; 3972 3973 // oTERM write() currently calls resetCommand(). Preserve an input 3974 // line being typed while asynchronous serial bytes continue to 3975 // arrive; this also prevents a stream from stealing edit state. 3976 var inputValue = input ? input.value : ""; 3977 var inputActive = input && document.activeElement===input; 3978 var selectionStart = input ? input.selectionStart : 0; 3979 var selectionEnd = input ? input.selectionEnd : 0; 3980 3981 var result = baseWrite(text,channel); 3982 3983 if(input) 3984 { 3985 input.value = inputValue; 3986 // Never refocus the live command field across a streamed write 3987 // while browser output text is selected; focus would collapse 3988 // the Range the user is trying to keep/copy. 3989 if(inputActive && !preserveSelection) 3990 { 3991 try { input.focus({"preventScroll":true}); } 3992 catch(ignore) { input.focus(); } 3993 try { input.setSelectionRange(selectionStart,selectionEnd); } 3994 catch(ignore) {} 3995 } 3996 } 3997 3998 /* 3999 * Appending new Text nodes does not invalidate the browser Range. 4000 * Suppress oTERM's automatic scroll-to-input while a non-collapsed 4001 * output selection exists so the selected visual scope stays put 4002 * even though the stream continues growing below it. 4003 */ 4004 if(preserveSelection) 4005 { 4006 dom.container.scrollTop = scrollTop; 4007 dom.container.scrollLeft = scrollLeft; 4008 } 4009 4010 return result; 4011 }; 4012 4013 terminal._serialProWriteWrapped = true; 4014 return true; 4015 } 4016 4017 function serialTerminalApplyDisplayMode() 4018 { 4019 var popup = document.getElementById("serialProTerminal_popup"); 4020 if(!popup) return false; 4021 4022 var button = popup.querySelector("[data-serial-terminal-display]"); 4023 if(!button) return false; 4024 4025 var ascii = serialTerminalState.displayMode==="ascii"; 4026 button.textContent = ascii ? "ASCII" : "RAW"; 4027 button.title = ascii
4028 ? "APPLE ASCII display: bit 7 stripped; click for raw byte display" 4029 : "Raw 8-bit byte display; click for APPLE ASCII"; 4030 return ascii; 4031 } 4032 4033 this.serialTerminalDisplayToggle = function() 4034 { 4035 serialTerminalState.displayMode = 4036 serialTerminalState.displayMode==="ascii" ? "raw" : "ascii"; 4037 4038 var terminal = serialTerminalLive(); 4039 if(terminal) serialTerminalRenderTranscript(terminal); 4040 serialTerminalApplyDisplayMode(); 4041 return serialTerminalState.displayMode; 4042 }; 4043 4044 function serialTerminalCopyText() 4045 { 4046 var terminal = serialTerminalLive(); 4047 if(!terminal || !terminal._o || !terminal._o.DOM) return ""; 4048 // The toolbar pictogram is deliberately COPY ALL. A user's explicit 4049 // selection remains available to the browser's normal Cmd/Ctrl-C. 4050 var output = terminal._o.DOM.output; 4051 return output.innerText!==undefined ? output.innerText : output.textContent; 4052 } 4053 4054 this.serialTerminalCopy = async function() 4055 { 4056 var text = serialTerminalCopyText(); 4057 if(!text.length) return 0; 4058 4059 var copied = false; 4060 try 4061 { 4062 if(typeof(navigator)!=="undefined" && navigator.clipboard && 4063 typeof(navigator.clipboard.writeText)==="function") 4064 { 4065 await navigator.clipboard.writeText(text); 4066 copied = true; 4067 } 4068 } 4069 catch(ignore) {} 4070 4071 if(!copied) 4072 { 4073 var area = document.createElement("textarea"); 4074 area.value = text; 4075 area.setAttribute("readonly",""); 4076 area.style.cssText = "position:fixed;left:-10000px;top:-10000px;"; 4077 document.body.appendChild(area); 4078 area.select(); 4079 try { copied = document.execCommand("copy"); } catch(ignore) {} 4080 document.body.removeChild(area); 4081 } 4082 4083 return copied ? text.length : 0; 4084 }; 4085 4086 function serialTerminalApplyTheme() 4087 { 4088 var popup = document.getElementById("serialProTerminal_popup"); 4089 if(!popup) return false; 4090 4091 var terminal = popup._terminal; 4092 var root = terminal && terminal._o && terminal._o.DOM 4093 ? terminal._o.DOM.root 4094 : document.getElementById("serialProTerminal_host"); 4095 if(!root) return false; 4096 4097 var light = serialTerminalState.theme==="light"; 4098 root.classList.toggle("serialpro_terminal_light",light); 4099 root.classList.toggle("dotmatrix_bck2",light); 4100 4101 var icon = popup.querySelector("[data-serial-terminal-theme]"); 4102 if(icon) 4103 { 4104 icon.title = light 4105 ? "Switch terminal to dark mode" 4106 : "Switch terminal to light dot-matrix paper mode"; 4107 icon.setAttribute("aria-label",icon.title); 4108 } 4109 4110 return light; 4111 } 4112 4113 this.serialTerminalThemeToggle = function() 4114 { 4115 serialTerminalState.theme = 4116 serialTerminalState.theme==="light" ? "dark" : "light"; 4117 4118 serialTerminalApplyTheme(); 4119 4120 var terminal = serialTerminalLive(); 4121 if(terminal && terminal._o && terminal._o.DOM && terminal._o.DOM.input) 4122 terminal._o.DOM.input.focus(); 4123 4124 return serialTerminalState.theme; 4125 }; 4126 4127 this.serialTerminalHelp = function() 4128 { 4129 var terminal = serialTerminalLive(); 4130 if(!terminal) return false; 4131 4132 terminal.output( 4133 "<b>Serial Pro terminal</b><br>" 4134 +"Type a line and press Enter to send its 8-bit bytes followed by CR ($0D).<br>" 4135 +"A bare Enter sends CR. Arrow Up/Down recalls terminal history; Escape clears the input line.<br>" 4136 +"Incoming bytes from the Serial Pro appear in this window.<br><br>" 4137 +"ASCII mode strips the Apple II text high bit, making PR# / LIST and printer streams directly readable; RAW preserves diagnostic byte notation.<br>" 4138 +"Select terminal output normally and use Cmd/Ctrl-C for that selection. The copy pictogram always copies the complete terminal transcript.<br>" 4139 +"A live stream continues while text is selected without forcing the view back to the newest output.<br>" 4140 +"Use <i class=\"fa fa-adjust\"></i> to switch between dark terminal mode and light dot-matrix paper mode.<br>" 4141 +"Use <i class=\"fa fa-plug\"></i> to connect/disconnect an optional physical USB serial port through Web Serial.<br>" 4142 +"Physical RX/TX shares the same 6551 endpoint; modem inputs are diagnostic-only in Stage 3A.<br>" 4143 +"CPU sync makes serial timing follow CPU acceleration; uncheck it to keep the selected baud approximately at x1 timing.<br>" 4144 +"<i>Current stage:</i> cycle-paced 6551 character timing + Stage 3A physical byte transport;
4144 individual serial waveform edges remain deferred." 4145 ); 4146 return true; 4147 }; 4148 4149 this.serialTerminalToggle = function() 4150 { 4151 if(typeof(TERMINAL)!="function") 4152 { 4153 console.error("Serial Pro terminal requires COM_oTERM.js"); 4154 return false; 4155 } 4156 4157 var slotN = serialTerminalSlotN(); 4158 if(slotN===null) return false; 4159 4160 var io = apple2plus.hwObj().io; 4161 var slotID = io.slot2ID(slotN); 4162 var popup = serialTerminalPopup(); 4163 var sameSlot = Number(popup.getAttribute("data-slotN"))===slotN; 4164 4165 if(popup.hidden===false && sameSlot) 4166 { 4167 oCOM.POPUP.off("serialProTerminal_popup"); 4168 return false; 4169 } 4170 4171 // Reopening the same slot keeps the live oTERM instance and its DOM. 4172 if(sameSlot && popup._terminal) 4173 { 4174 oCOM.POPUP.on("serialProTerminal_popup"); 4175 serialPhysicalUpdateButton(); 4176 serialTerminalApplyDisplayMode(); 4177 setTimeout(function(){ popup._terminal._o.DOM.input.focus(); },0); 4178 return true; 4179 } 4180 4181 var call = "apple2plus.hwObj().io.SLOT2obj("+slotN+")"; 4182 popup.setAttribute("data-slotN",String(slotN)); 4183 popup.innerHTML = "" 4184 + "<div class=\"com_popup_title\" style=\"height:30px;padding:0px 6px;display:flex;align-items:center;gap:6px;\">" 4185 + " <b>Serial Pro #"+slotID+" terminal</b>" 4186 + " <span style=\"flex:1 1 auto\"></span>" 4187 + " <button class=\"appbut skinny\" type=button data-serial-terminal-display"
4188 + " title=\"APPLE ASCII display: bit 7 stripped; click for raw byte display\"" 4189 + " onclick=\""+call+".serialTerminalDisplayToggle()\">ASCII</button>" 4190 + " <button class=\"appbut skinny\" type=button title=\"Copy complete terminal transcript to clipboard\"" 4191 + " onmousedown=\"event.preventDefault()\"" 4192 + " onclick=\""+call+".serialTerminalCopy()\"><i class=\"fa fa-copy\"></i></button>" 4193 + " <button class=\"appbut skinny\" type=button" 4194 + " title=\"Connect physical USB serial port (Web Serial)\"" 4195 + " onclick=\""+call+".serialPhysicalToggle()\">" 4196 + " <i class=\"fa fa-plug\" data-serial-webserial></i>" 4197 + " </button>" 4198 + " <button class=\"appbut skinny\" type=button" 4199 + " title=\"Switch terminal to light dot-matrix paper mode\"" 4200 + " onclick=\""+call+".serialTerminalThemeToggle()\">" 4201 + " <i class=\"fa fa-adjust\" data-serial-terminal-theme></i>" 4202 + " </button>" 4203 + " <button class=\"appbut skinny\" type=button title=\"Terminal help\"" 4204 + " onclick=\""+call+".serialTerminalHelp()\">?</button>" 4205 + " <button class=\"appbut skinny\" type=button title=\"Clear terminal display\"" 4206 + " onclick=\""+call+".serialTerminalClear()\">CLEAR</button>" 4207 + " <button class=\"appbut skinny\" type=button title=\"Close serial terminal\"" 4208 + " onclick=\"oCOM.POPUP.off('serialProTerminal_popup')\">X</button>" 4209 + "</div>" 4210 + "<div class=\"com_popup_body\">" 4211 + " <div id=\"serialProTerminal_host\" class=\"serialpro_terminal_host\"></div>" 4212 + "</div>"; 4213 4214 oCOM.POPUP.on("serialProTerminal_popup"); 4215 4216 var terminal = new TERMINAL({
4217 "container":"serialProTerminal_host" 4218 ,"welcome":"" 4219 ,"prompt":"" 4220 ,"separator":">" 4221 ,"storageKey":"SerialProTerminal_"+slotID 4222 ,"preserveWhitespace":true 4223 ,"allowEmptyInput":true 4224 }); 4225 4226 popup._terminal = terminal; 4227 serialTerminalProtectStreaming(terminal); 4228 serialTerminalApplyTheme(); 4229 serialTerminalApplyDisplayMode(); 4230 serialPhysicalUpdateButton(); 4231 4232 terminal.onInput(function(command,parameters,commandLine,rawLine) 4233 { 4234 serialpro.serialTerminalQueueLine(rawLine); 4235 return true; 4236 }); 4237 4238 var rows = serialTerminalState.transcript; 4239 if(rows.length) 4240 { 4241 serialTerminalRenderTranscript(terminal); 4242 } 4243 else 4244 { 4245 serialpro.serialTerminalConsoleWrite( 4246 "Serial terminal endpoint ready; 6551 ACIA Stage 2D + optional Web Serial Stage 3A.\n", 4247 {"source":"spterm"} 4248 ); 4249 } 4250 4251 setTimeout(function(){ terminal._o.DOM.input.focus(); },0); 4252 return true; 4253 }; 4254 4255 this.deviceToolRTCSet = function(controlID) 4256 { 4257 rtcUI.controlID = controlID; 4258 4259 var parsed = rtcUIParse(controlID,true); 4260 rtcUIUpdateSetButton(controlID,parsed.valid); 4261 4262 if(!parsed.valid) return false; 4263 rtc.weekdayOffset = 0; 4264 rtcRebase(parsed.date); 4265 rtcUIRefresh(controlID,false); 4266 4267 rtcUIFieldValidity(rtcUIElement(controlID,"rtc_date"),true); 4268 rtcUIFieldValidity(rtcUIElement(controlID,"rtc_time"),true); 4269 rtcUIUpdateSetButton(controlID,true); 4270 return true; 4271 }; 4272 4273 this.deviceToolRTCHost = function(controlID) 4274 { 4275 rtcUI.controlID = controlID; 4276 rtc.weekdayOffset = 0; 4277 rtcRebase(new Date()); 4278 rtcUIRefresh(controlID,false); 4279 4280 rtcUIFieldValidity(rtcUIElement(controlID,"rtc_date"),true); 4281 rtcUIFieldValidity(rtcUIElement(controlID,"rtc_time"),true); 4282 rtcUIUpdateSetButton(controlID,true); 4283 return true; 4284 }; 4285 4286 this.deviceToolSlotHTML = function(ctx) 4287 { 4288 ctx = ctx || {}; 4289 4290 var toolboxID = ctx.toolboxID || ("device_tool_"+ctx.slotID); 4291 var slotID = ctx.slotID==null ? "?" : String(ctx.slotID); 4292 var slotN = Number(ctx.slotN); 4293 var values = rtcUIValues(); 4294 4295 // Child controls must not use the "device_tool_" prefix: Apple2IO 4296 // reserves that prefix for whole toolbox containers and hides them 4297 // when switching the visible peripheral. 4298 var controlID = "spc_ctrl_"+slotID; 4299 4300 4301 var call = "apple2plus.hwObj().io.SLOT2obj("+slotN+")"; 4302 4303 4304 rtcUI.controlID = controlID; 4305 rtcUI.refreshEvent = "SPC_RTC_monitoring_"+slotID; 4306 serialUI.controlID = controlID; 4307 serialUI.refreshEvent = "SPC_SERIAL_monitoring_"+slotID; 4308 4309 // The dashboard refresh sequencer is already paced by 4310 // EMU_DashboardRefresh_s. Keep the event registered and let the card's 4311 // requested/validation state decide whether a visual update is made. 4312 oCOM.addRefreshEvent( 4313 function(){ serialpro.deviceToolRTCMonitoring(); }, 4314 rtcUI.refreshEvent, 4315 true 4316 ); 4317 4318 oCOM.addRefreshEvent( 4319 function(){ serialpro.deviceToolSerialMonitoring(); }, 4320 serialUI.refreshEvent, 4321 true 4322 ); 4323 4324 var syncClass = rtcUI.syncRequested ? "fa-stop-circle" : "fa-sync-alt"; 4325 var configured = serialConfiguredState(); 4326 4327 var baudOptions = ""; 4328 for(var b=1;b<ACIA_BAUD_TABLE.length;b++) 4329 { 4330 var rate = ACIA_BAUD_TABLE[b]; 4331 baudOptions += "<option value=\""+rate+"\"" 4332 +(configured.baud===rate ? " selected" : "")+">"+rate+"</option>"; 4333 } 4334 4335 function option(value,label,current) 4336 { 4337 return "<option value=\""+value+"\"" 4338 +(String(value)===String(current) ? " selected" : "")+">"+label+"</option>"; 4339 } 4340 4341 var dataOptions = "" 4342 + option(8,"8 BIT",configured.dataBits) 4343 + option(7,"7 BIT",configured.dataBits) 4344 + option(6,"6 BIT",configured.dataBits) 4345 + option(5,"5 BIT",configured.dataBits); 4346 4347 var parityOptions = "" 4348 + option("none","NONE",configured.parity) 4349 + option("odd","ODD",configured.parity) 4350 + option("even","EVEN",configured.parity) 4351 + option("mark","MARK",configured.parity) 4352 + option("space","SPACE",configured.parity); 4353 4354 var stopOptions = "" 4355 + option(1,"1 STOP",configured.stopBits) 4356 + option(1.5,"1.5 STOP",configured.stopBits) 4357 + option(2,"2 STOP",configured.stopBits); 4358 4359 var rxClockChecked = configured.rxClockInternal ? " checked" : ""; 4360 var dtrChecked = aciaDTRAsserted() ? " checked" : ""; 4361 var rtsChecked = aciaRTSAsserted() ? " checked" : ""; 4362 var cpuSyncChecked = state.cpuSync ? " checked" : ""; 4363 4364 return "" 4365 + "<div class=toolbox id=\""+toolboxID+"\" hidden>" 4366 + " <div class=appbox style=\"box-sizing:border-box;text-align:left;height:76px;padding:3px 6px;display:flex;flex-direction:column;gap:2px;font-size:11px;white-space:nowrap;\">" 4367 + " <div style=\"height:21px;
4367display:flex;align-items:center;gap:4px;\">" 4368 + " <b style=\"display:inline-block;width:28px;\">RTC</b>" 4369 + " <input id=\""+controlID+"_rtc_date\" type=text value=\""+values.date+"\" maxlength=10" 4370 + " title=\"Date YYYY-MM-DD; edit and press SET\"" 4371 + " oninput=\""+call+".deviceToolRTCInput('"+controlID+"')\"" 4372 + " style=\"box-sizing:border-box;width:76px;height:19px;font:11px monospace;padding:0px 2px;border:1px solid transparent;\">" 4373 + " <input id=\""+controlID+"_rtc_time\" type=text value=\""+values.time+"\" maxlength=8" 4374 + " title=\"Time HH:MM:SS; edit and press SET\"" 4375 + " oninput=\""+call+".deviceToolRTCInput('"+controlID+"')\"" 4376 + " style=\"box-sizing:border-box;width:62px;height:19px;font:11px monospace;padding:0px 2px;border:1px solid transparent;\">" 4377 + " <button class=\"appbut\" type=button style=\"text-align:center;margin-left:0px;padding:4px\" title=\"Date/time sync\">" 4378 + " <i class=\"fa "+syncClass+"\" id=\""+controlID+"_rtc_monitoring\"" 4379 + " title=\""+(rtcUI.syncRequested ? "Stop date/time sync" : "Start date/time sync")+"\"" 4380 + " onclick=\""+call+".deviceToolRTCSyncToggle('"+controlID+"')\"></i>" 4381 + " </button>" 4382 + " <button class=\"appbut skinny\" id=\""+controlID+"_rtc_set\" type=button title=\"Set RTC from fields\"" 4383 + " onclick=\""+call+".deviceToolRTCSet('"+controlID+"')\">SET</button>" 4384 + " <button class=\"appbut skinny\" type=button title=\"Set RTC from browser host time\"" 4385 + " onclick=\""+call+".deviceToolRTCHost('"+controlID+"')\">HOST</button>" 4386 + " <label title=\"Follow CPU speed; uncheck to keep selected baud approximately at x1 timing\" style=\"display:flex;align-items:center;gap:2px;margin-left:auto;\">" 4387 + " <input id=\""+controlID+"_serial_cpu_sync\" type=checkbox"+cpuSyncChecked 4388 + " onchange=\""+call+".deviceToolSerialFlag('"+controlID+"','cpusync',this.checked)\">CPU sync" 4389 + " </label>" 4390 + " </div>" 4391 4392 + " <div style=\"height:21px;display:flex;align-items:center;gap:4px;\">" 4393 + " <b style=\"display:inline-block;width:28px;\">SER</b>" 4394 + " <select id=\""+controlID+"_serial_baud\" title=\"6551 baud rate; physical Web Serial uses this rate on next connection\"" 4395 + " onchange=\""+call+".deviceToolSerialSetting('"+controlID+"','baud',this.value)\"" 4396 + " style=\"height:19px;font:11px monospace;padding:0px 1px;\">"+baudOptions+"</select>" 4397 + " <select id=\""+controlID+"_serial_data\" title=\"6551 data word length\"" 4398 + " onchange=\""+call+".deviceToolSerialSetting('"+controlID+"','data',this.value)\"" 4399 + " style=\"height:19px;font:11px monospace;padding:0px 1px;\">"+dataOptions+"</select>" 4400 + " <select id=\""+controlID+"_serial_parity\" title=\"6551 parity mode\"" 4401 + " onchange=\""+call+".deviceToolSerialSetting('"+controlID+"','parity',this.value)\"" 4402 + " style=\"height:19px;font:11px monospace;padding:0px 1px;\">"+parityOptions+"</select>" 4403 + " <select id=\""+controlID+"_serial_stop\" title=\"6551 effective stop-bit selection; SBN=1 depends on word length/parity\"" 4404 + " onchange=\""+call+".deviceToolSerialSetting('"+controlID+"','stop',this.value)\"" 4405 + " style=\"height:19px;font:11px monospace;padding:0px 1px;\">"+stopOptions+"</select>" 4406 + " <label title=\"Use transmit baud as the 6551 receiver clock; required by physical Web Serial\" style=\"display:flex;align-items:center;gap:2px;\">" 4407 + " <input id=\""+controlID+"_serial_rxclock\" type=checkbox"+rxClockChecked 4408 + " onchange=\""+call+".deviceToolSerialFlag('"+controlID+"','rxclock',this.checked)\">RX=TX" 4409 + " </label>" 4410 + " </div>" 4411 4412 + " <div style=\"height:21px;
4412display:flex;align-items:center;gap:4px;\">" 4413 + " <b style=\"display:inline-block;width:28px;\">USB</b>" 4414 + " <button class=\"appbut skinny\" id=\""+controlID+"_physical_toggle\" type=button" 4415 + " title=\"Connect a physical USB serial adapter through Web Serial\"" 4416 + " onclick=\""+call+".deviceToolPhysicalToggle('"+controlID+"')\">CONNECT</button>" 4417 + " <span id=\""+controlID+"_physical_status\" style=\"display:inline-block;width:41px;font-size:10px;color:#777;\">OFFLINE</span>" 4418 + " <label title=\"6551 DTR output; synchronized live to Web Serial DTR when connected\" style=\"display:flex;align-items:center;gap:1px;\">" 4419 + " <input id=\""+controlID+"_physical_dtr\" type=checkbox"+dtrChecked 4420 + " onchange=\""+call+".deviceToolSerialFlag('"+controlID+"','dtr',this.checked)\">DTR" 4421 + " </label>" 4422 + " <label title=\"Manual 6551 RTS output; selects non-IRQ transmit mode and synchronizes live to Web Serial RTS\" style=\"display:flex;align-items:center;gap:1px;\">" 4423 + " <input id=\""+controlID+"_physical_rts\" type=checkbox"+rtsChecked 4424 + " onchange=\""+call+".deviceToolSerialFlag('"+controlID+"','rts',this.checked)\">RTS" 4425 + " </label>" 4426 + " <span title=\"Physical Clear To Send (diagnostic only)\">CTS<span id=\""+controlID+"_physical_cts\" style=\"color:#999\">â</span></span>" 4427 + " <span title=\"Physical Data Carrier Detect (diagnostic only)\">DCD<span id=\""+controlID+"_physical_dcd\" style=\"color:#999\">â</span></span>" 4428 + " <span title=\"Physical Data Set Ready (diagnostic only)\">DSR<span id=\""+controlID+"_physical_dsr\" style=\"color:#999\">â</span></span>" 4429 + " <span title=\"Physical Ring Indicator (diagnostic only)\">RI<span id=\""+controlID+"_physical_ri\" style=\"color:#999\">â</span></span>" 4430 + " <span style=\"flex:1 1 auto\"></span>" 4431 + " <button class=\"appbut skinny\" type=button title=\"Open serial terminal\"" 4432 + " onclick=\""+call+".serialTerminalToggle()\"><i class=\"fa fa-terminal\"></i></button>" 4433 + " </div>" 4434 + " </div>" 4435 + "</div>"; 4436 }; 4437 4438 function resetROMState() 4439 { 4440 state.romSelector = 0x00; 4441 releaseExpansionROM(); 4442 } 4443 4444 this.syncClock = function(clockTick) 4445 { 4446 // Keep RTC wall-clock interrupt detection beside the existing 4447 // cycle-paced ACIA timing. The RTC itself remains tied to host time; 4448 // clockTick is used only to throttle Date.now() sampling. 4449 var rtcResult = rtcSyncClock(clockTick); 4450 var aciaResult = aciaSyncClock(clockTick); 4451 return rtcResult || aciaResult; 4452 }; 4453 4454 this.reset = function() 4455 { 4456 resetROMState(); 4457 aciaHardwareReset(); 4458 // Bytes already typed at the remote end remain on the simulated line. 4459 aciaPrimeReceiver(); 4460 }; 4461 4462 this.restart = function() 4463 { 4464 resetROMState(); 4465 aciaHardwareReset(); 4466 // Bytes already typed at the remote end remain on the simulated line. 4467 aciaPrimeReceiver(); 4468 }; 4469}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.