From 4a0741003313fa0113d9f972790e16cedfdbcc25 Mon Sep 17 00:00:00 2001 From: Fred Sauer Date: Sat, 21 Mar 2026 14:58:56 -0700 Subject: [PATCH] Reformat src/machine/apple2.ts --- src/machine/apple2.ts | 2465 ++++++++++++++++++++--------------------- 1 file changed, 1209 insertions(+), 1256 deletions(-) diff --git a/src/machine/apple2.ts b/src/machine/apple2.ts index c8c96a95..e610e2a8 100644 --- a/src/machine/apple2.ts +++ b/src/machine/apple2.ts @@ -1,171 +1,170 @@ - import { MOS6502, MOS6502State } from "../common/cpu/MOS6502"; import { Bus, BasicScanlineMachine, SavesState, AcceptsBIOS } from "../common/devices"; import { KeyFlags } from "../common/emu"; // TODO import { hex, lzgmini, stringToByteArray, RGBA, printFlags, arrayCompare } from "../common/util"; interface AppleIIStateBase { - ram : Uint8Array; - soundstate : number; - auxRAMselected,writeinhibit : boolean; - auxRAMbank : number; + ram: Uint8Array; + soundstate: number; + auxRAMselected, writeinhibit: boolean; + auxRAMbank: number; } interface AppleIIControlsState { - inputs : Uint8Array; // unused? - kbdlatch : number; + inputs: Uint8Array; // unused? + kbdlatch: number; } interface AppleIIState extends AppleIIStateBase, AppleIIControlsState { - c : MOS6502State; - grswitch : number; - slots: SlotDevice[]; + c: MOS6502State; + grswitch: number; + slots: SlotDevice[]; } interface SlotDevice extends Bus { - readROM(address: number) : number; - readConst(address: number) : number; + readROM(address: number): number; + readConst(address: number): number; } export class AppleII extends BasicScanlineMachine implements AcceptsBIOS { // approx: http://www.cs.columbia.edu/~sedwards/apple2fpga/ - cpuFrequency = 1022727; - sampleRate = this.cpuFrequency; - cpuCyclesPerLine = 65; - cpuCyclesPerFrame = this.cpuCyclesPerLine * 262; - canvasWidth = 280; - numVisibleScanlines = 192; - numTotalScanlines = 262; - defaultROMSize = 0x13000; // we'll never need one that big, but... + cpuFrequency = 1022727; + sampleRate = this.cpuFrequency; + cpuCyclesPerLine = 65; + cpuCyclesPerFrame = this.cpuCyclesPerLine * 262; + canvasWidth = 280; + numVisibleScanlines = 192; + numTotalScanlines = 262; + defaultROMSize = 0x13000; // we'll never need one that big, but... - // these are set later - LOAD_BASE = 0; - HDR_SIZE = 0; + // these are set later + LOAD_BASE = 0; + HDR_SIZE = 0; - ram = new Uint8Array(0x13000); // 64K + 16K LC RAM - 4K hardware + 12K ROM - bios : Uint8Array; - cpu = new MOS6502(); - grdirty = new Array(0xc000 >> 7); - grparams = {dirty:this.grdirty, grswitch:GR_TXMODE, mem:this.ram}; - ap2disp; - kbdlatch = 0; - soundstate = 0; - // language card switches - auxRAMselected = false; - auxRAMbank = 1; - writeinhibit = true; - // value to add when reading & writing each of these banks - // bank 1 is E000-FFFF, bank 2 is D000-DFFF - bank2rdoffset=0; - bank2wroffset=0; - // disk II - slots : SlotDevice[] = new Array(8); - // fake disk drive that loads program into RAM - fakeDrive : SlotDevice = { - readROM: (a) => { - var pc = this.cpu.getPC(); - if (pc >= 0xC600 && pc < 0xC700) { - // We're reading code to EXECUTE. - // Load the built program directly into memory, and "read" - // a JMP directly to it. - //console.log(`fakeDrive (EXEC): ${a.toString(16)}`); - switch (a) { - // JMP VM_BASE - case 0: - // SHOULD load program into RAM here, but have to do it - // below instead. - return 0; - case 1: return this.LOAD_BASE&0xff; - case 2: return (this.LOAD_BASE>>8)&0xff; - default: return 0; - } - } - else { - // We're reading code, but not executing it. - // This is probably the Monitor routine to identify whether - // this slot is a Disk ][ drive, so... give it what it wants. - //console.log(`fakeDrive (NOEX): ${a.toString(16)}`); - switch (a) { - case 0: - // Actually, if we get here, we probably ARE being - // executed. For some reason, the instruction at $C600 - // gets read for execution, BEFORE the PC gets set to - // the correct location. So we handle loading the program - // into RAM and returning the JMP here, instead of above - // where it would otherwise belong. - if (this.rom) { - this.loadRAMWithProgram(); - } - return 0x4c; // JMP - case 1: return 0x20; - case 3: return 0x00; - case 5: return 0x03; - case 7: return 0x3c; - default: return 0; - } - } - }, - readConst: (a) => { - return 0; - }, - read: (a) => { return this.floatbus(); }, - write: (a,v) => { } - }; + ram = new Uint8Array(0x13000); // 64K + 16K LC RAM - 4K hardware + 12K ROM + bios: Uint8Array; + cpu = new MOS6502(); + grdirty = new Array(0xc000 >> 7); + grparams = { dirty: this.grdirty, grswitch: GR_TXMODE, mem: this.ram }; + ap2disp; + kbdlatch = 0; + soundstate = 0; + // language card switches + auxRAMselected = false; + auxRAMbank = 1; + writeinhibit = true; + // value to add when reading & writing each of these banks + // bank 1 is E000-FFFF, bank 2 is D000-DFFF + bank2rdoffset = 0; + bank2wroffset = 0; + // disk II + slots: SlotDevice[] = new Array(8); + // fake disk drive that loads program into RAM + fakeDrive: SlotDevice = { + readROM: (a) => { + var pc = this.cpu.getPC(); + if (pc >= 0xC600 && pc < 0xC700) { + // We're reading code to EXECUTE. + // Load the built program directly into memory, and "read" + // a JMP directly to it. + //console.log(`fakeDrive (EXEC): ${a.toString(16)}`); + switch (a) { + // JMP VM_BASE + case 0: + // SHOULD load program into RAM here, but have to do it + // below instead. + return 0; + case 1: return this.LOAD_BASE & 0xff; + case 2: return (this.LOAD_BASE >> 8) & 0xff; + default: return 0; + } + } + else { + // We're reading code, but not executing it. + // This is probably the Monitor routine to identify whether + // this slot is a Disk ][ drive, so... give it what it wants. + //console.log(`fakeDrive (NOEX): ${a.toString(16)}`); + switch (a) { + case 0: + // Actually, if we get here, we probably ARE being + // executed. For some reason, the instruction at $C600 + // gets read for execution, BEFORE the PC gets set to + // the correct location. So we handle loading the program + // into RAM and returning the JMP here, instead of above + // where it would otherwise belong. + if (this.rom) { + this.loadRAMWithProgram(); + } + return 0x4c; // JMP + case 1: return 0x20; + case 3: return 0x00; + case 5: return 0x03; + case 7: return 0x3c; + default: return 0; + } + } + }, + readConst: (a) => { + return 0; + }, + read: (a) => { return this.floatbus(); }, + write: (a, v) => { } + }; - constructor() { - super(); - this.loadBIOS(new lzgmini().decode(stringToByteArray(atob(APPLEIIGO_LZG)))); - this.connectCPUMemoryBus(this); - // This line is inappropriate for real ROMs, but was there for - // the APPLE][GO ROM, so keeping it only in the constructor, for - // that special case (in case it really is important for this - // address to be an RTS). - this.bios[0xD39A - (0x10000 - this.bios.length)] = 0x60; // $d39a = RTS - } - saveState() : AppleIIState { - // TODO: automagic - return { - c: this.cpu.saveState(), - ram: this.ram.slice(), - kbdlatch: this.kbdlatch, - soundstate: this.soundstate, - grswitch: this.grparams.grswitch, - auxRAMselected: this.auxRAMselected, - auxRAMbank: this.auxRAMbank, - writeinhibit: this.writeinhibit, - slots: this.slots.map((slot) => { return slot && slot['saveState'] && slot['saveState']() }), - inputs: this.ram.slice(0,0) // unused - }; - } - loadState(s:AppleIIState) { - this.cpu.loadState(s.c); - this.ram.set(s.ram); - this.kbdlatch = s.kbdlatch; - this.soundstate = s.soundstate; - this.grparams.grswitch = s.grswitch; - this.auxRAMselected = s.auxRAMselected; - this.auxRAMbank = s.auxRAMbank; - this.writeinhibit = s.writeinhibit; - this.setupLanguageCardConstants(); - for (var i=0; i { return slot && slot['saveState'] && slot['saveState']() }), + inputs: this.ram.slice(0, 0) // unused + }; + } + loadState(s: AppleIIState) { + this.cpu.loadState(s.c); + this.ram.set(s.ram); + this.kbdlatch = s.kbdlatch; + this.soundstate = s.soundstate; + this.grparams.grswitch = s.grswitch; + this.auxRAMselected = s.auxRAMselected; + this.auxRAMbank = s.auxRAMbank; + this.writeinhibit = s.writeinhibit; + this.setupLanguageCardConstants(); + for (var i = 0; i < this.slots.length; i++) + if (this.slots[i] && this.slots[i]['loadState']) + this.slots[i]['loadState'](s.slots[i]); + this.ap2disp.invalidate(); // repaint entire screen + } + saveControlsState(): AppleIIControlsState { + return { inputs: null, kbdlatch: this.kbdlatch }; + } + loadControlsState(s: AppleIIControlsState) { + this.kbdlatch = s.kbdlatch; + } + loadBIOS(data, title?) { if (data.length != 0x3000) { - console.log(`apple2 loadBIOS !!!WARNING!!!: BIOS wants length 0x3000, but BIOS '${title}' has length 0x${data.length.toString(16)}`); - console.log("will load BIOS to end of memory anyway..."); + console.log(`apple2 loadBIOS !!!WARNING!!!: BIOS wants length 0x3000, but BIOS '${title}' has length 0x${data.length.toString(16)}`); + console.log("will load BIOS to end of memory anyway..."); } this.bios = Uint8Array.from(data); - } + } loadROM(data) { // is it a 16-sector 35-track disk image? if (data.length == 16 * 35 * 256) { @@ -209,27 +208,27 @@ export class AppleII extends BasicScanlineMachine implements AcceptsBIOS { this.ram[0xbf6f] = 0x01; } } - reset() { - this.auxRAMselected = false; - this.auxRAMbank = 1; - this.writeinhibit = true; - this.ram.fill(0, 0x300, 0x400); // Clear soft-reset vector - // (force hard reset) - super.reset(); - this.skipboot(); - } - skipboot() { - // execute until $c600 boot - for (var i=0; i<2000000; i++) { - this.cpu.advanceClock(); - if ((this.cpu.getPC()>>8) == 0xc6) break; - } - // get out of $c600 boot - for (var i=0; i<2000000; i++) { - this.cpu.advanceClock(); - if ((this.cpu.getPC()>>8) < 0xc6) break; - } - } + reset() { + this.auxRAMselected = false; + this.auxRAMbank = 1; + this.writeinhibit = true; + this.ram.fill(0, 0x300, 0x400); // Clear soft-reset vector + // (force hard reset) + super.reset(); + this.skipboot(); + } + skipboot() { + // execute until $c600 boot + for (var i = 0; i < 2000000; i++) { + this.cpu.advanceClock(); + if ((this.cpu.getPC() >> 8) == 0xc6) break; + } + // get out of $c600 boot + for (var i = 0; i < 2000000; i++) { + this.cpu.advanceClock(); + if ((this.cpu.getPC() >> 8) < 0xc6) break; + } + } readConst(address: number): number { if (address < 0xc000) { return this.ram[address]; @@ -247,459 +246,444 @@ export class AppleII extends BasicScanlineMachine implements AcceptsBIOS { return 0; } } - read(address:number) : number { - address &= 0xffff; - if (address < 0xc000 || address >= 0xd000) { - return this.readConst(address); - } else if (address < 0xc100) { - this.probe.logIORead(address, 0); // TODO: value - var slot = (address >> 4) & 0x0f; - switch (slot) - { - case 0: - return this.kbdlatch; - case 1: - this.kbdlatch &= 0x7f; - break; - case 3: - this.soundstate = this.soundstate ^ 1; - break; - case 5: - if ((address & 0x0f) < 8) { - // graphics - if ((address & 1) != 0) - this.grparams.grswitch |= 1 << ((address >> 1) & 0x07); - else - this.grparams.grswitch &= ~(1 << ((address >> 1) & 0x07)); - } - break; - case 6: - // tapein, joystick, buttons - switch (address & 7) { - // buttons (off) - case 1: - case 2: - case 3: - return this.floatbus() & 0x7f; - // joystick - case 4: - case 5: + read(address: number): number { + address &= 0xffff; + if (address < 0xc000 || address >= 0xd000) { + return this.readConst(address); + } else if (address < 0xc100) { + this.probe.logIORead(address, 0); // TODO: value + var slot = (address >> 4) & 0x0f; + switch (slot) { + case 0: + return this.kbdlatch; + case 1: + this.kbdlatch &= 0x7f; + break; + case 3: + this.soundstate = this.soundstate ^ 1; + break; + case 5: + if ((address & 0x0f) < 8) { + // graphics + if ((address & 1) != 0) + this.grparams.grswitch |= 1 << ((address >> 1) & 0x07); + else + this.grparams.grswitch &= ~(1 << ((address >> 1) & 0x07)); + } + break; + case 6: + // tapein, joystick, buttons + switch (address & 7) { + // buttons (off) + case 1: + case 2: + case 3: + return this.floatbus() & 0x7f; + // joystick + case 4: + case 5: + return this.floatbus() | 0x80; + default: + return this.floatbus(); + } + case 7: + // joy reset + if (address == 0xc070) return this.floatbus() | 0x80; - default: - return this.floatbus(); - } - case 7: - // joy reset - if (address == 0xc070) - return this.floatbus() | 0x80; - case 8: - return this.doLanguageCardIO(address); - case 9: case 10: case 11: case 12: case 13: case 14: case 15: - return (this.slots[slot-8] && this.slots[slot-8].read(address & 0xf)) | 0; + case 8: + return this.doLanguageCardIO(address); + case 9: case 10: case 11: case 12: case 13: case 14: case 15: + return (this.slots[slot - 8] && this.slots[slot - 8].read(address & 0xf)) | 0; + } + } else if (address >= 0xc100 && address < 0xc800) { + var slot = (address >> 8) & 7; + return (this.slots[slot] && this.slots[slot].readROM(address & 0xff)) | 0; } - } else if (address >= 0xc100 && address < 0xc800) { - var slot = (address >> 8) & 7; - return (this.slots[slot] && this.slots[slot].readROM(address & 0xff)) | 0; - } - return this.floatbus(); - } - write(address:number, val:number) : void { - address &= 0xffff; - val &= 0xff; - if (address < 0xc000) { - this.ram[address] = val; - this.grdirty[address>>7] = 1; - } else if (address < 0xc090) { - this.read(address); // strobe address, discard result - } else if (address < 0xc100) { - var slot = (address >> 4) & 0x0f; - this.slots[slot-8] && this.slots[slot-8].write(address & 0xf, val); - this.probe.logIOWrite(address, val); - } else if (address >= 0xd000 && !this.writeinhibit) { - if (address >= 0xe000) - this.ram[address] = val; - else - this.ram[address + this.bank2wroffset] = val; - } - } - // http://www.deater.net/weave/vmwprod/megademo/vapor_lock.html - // https://retrocomputing.stackexchange.com/questions/14012/what-is-dram-refresh-and-why-is-the-weird-apple-ii-video-memory-layout-affected - // http://www.apple-iigs.info/doc/fichiers/TheappleIIcircuitdescription1.pdf - // http://rich12345.tripod.com/aiivideo/softalk.html - // https://github.com/MiSTer-devel/Apple-II_MiSTer/blob/master/rtl/timing_generator.vhd - floatbus() : number { - var fcyc = this.frameCycles; - var yline = Math.floor(fcyc / 65); - var xcyc = Math.floor(fcyc % 65); - var addr = this.ap2disp.getAddressForScanline(yline); - return this.readConst(addr + xcyc); - } + return this.floatbus(); + } + write(address: number, val: number): void { + address &= 0xffff; + val &= 0xff; + if (address < 0xc000) { + this.ram[address] = val; + this.grdirty[address >> 7] = 1; + } else if (address < 0xc090) { + this.read(address); // strobe address, discard result + } else if (address < 0xc100) { + var slot = (address >> 4) & 0x0f; + this.slots[slot - 8] && this.slots[slot - 8].write(address & 0xf, val); + this.probe.logIOWrite(address, val); + } else if (address >= 0xd000 && !this.writeinhibit) { + if (address >= 0xe000) + this.ram[address] = val; + else + this.ram[address + this.bank2wroffset] = val; + } + } + // http://www.deater.net/weave/vmwprod/megademo/vapor_lock.html + // https://retrocomputing.stackexchange.com/questions/14012/what-is-dram-refresh-and-why-is-the-weird-apple-ii-video-memory-layout-affected + // http://www.apple-iigs.info/doc/fichiers/TheappleIIcircuitdescription1.pdf + // http://rich12345.tripod.com/aiivideo/softalk.html + // https://github.com/MiSTer-devel/Apple-II_MiSTer/blob/master/rtl/timing_generator.vhd + floatbus(): number { + var fcyc = this.frameCycles; + var yline = Math.floor(fcyc / 65); + var xcyc = Math.floor(fcyc % 65); + var addr = this.ap2disp.getAddressForScanline(yline); + return this.readConst(addr + xcyc); + } - connectVideo(pixels:Uint32Array) { - super.connectVideo(pixels); - this.ap2disp = this.pixels && new Apple2Display(this.pixels, this.grparams); - } - startScanline() { - } - drawScanline() { - // TODO: draw scanline via ap2disp - } - advanceFrame(trap) : number { - var clocks = super.advanceFrame(trap); - this.ap2disp && this.ap2disp.updateScreen(); - return clocks; - } - advanceCPU() { - this.audio.feedSample(this.soundstate, 1); - return super.advanceCPU(); - } + connectVideo(pixels: Uint32Array) { + super.connectVideo(pixels); + this.ap2disp = this.pixels && new Apple2Display(this.pixels, this.grparams); + } + startScanline() { + } + drawScanline() { + // TODO: draw scanline via ap2disp + } + advanceFrame(trap): number { + var clocks = super.advanceFrame(trap); + this.ap2disp && this.ap2disp.updateScreen(); + return clocks; + } + advanceCPU() { + this.audio.feedSample(this.soundstate, 1); + return super.advanceCPU(); + } - setKeyInput(key:number, code:number, flags:number) : void { - console.log(`setKeyInput: ${key} ${code} ${flags}`); - if (flags & KeyFlags.KeyDown) { - code = 0; - switch (key) { - case 16: case 17: case 18: case 91: - return; // ignore shift/ctrl/alt - don't set any key - case 8: - code=8; // left - if (flags & KeyFlags.Shift) { - // (possibly) soft reset - this.cpu.reset(); - return; - } - break; - case 13: code=13; break; // return - case 27: code=27; break; // escape - case 37: code=8; break; // left - case 39: code=21; break; // right - case 38: code=11; break; // up - case 40: code=10; break; // down - case 48: code = (flags & KeyFlags.Shift) ? 0x29 : 0x30; break; // ) or 0 - case 49: code = (flags & KeyFlags.Shift) ? 0x21 : 0x31; break; // ! or 1 - case 50: code = (flags & KeyFlags.Shift) ? 0x40 : 0x32; break; // @ or 2 - case 51: code = (flags & KeyFlags.Shift) ? 0x23 : 0x33; break; // # or 3 - case 52: code = (flags & KeyFlags.Shift) ? 0x24 : 0x34; break; // $ or 4 - case 53: code = (flags & KeyFlags.Shift) ? 0x25 : 0x35; break; // % or 5 - case 54: code = (flags & KeyFlags.Shift) ? 0x5e : 0x36; break; // ^ or 6 - case 55: code = (flags & KeyFlags.Shift) ? 0x26 : 0x37; break; // & or 7 - case 56: code = (flags & KeyFlags.Shift) ? 0x2a : 0x38; break; // * or 8 - case 57: code = (flags & KeyFlags.Shift) ? 0x28 : 0x39; break; // ( or 9 - case 61: code = (flags & KeyFlags.Shift) ? 0x5f : 0x2d; break; // _ or - - case 173: code = (flags & KeyFlags.Shift) ? 0x2b : 0x3d; break; // + or = - case 59: code = (flags & KeyFlags.Shift) ? 0x3a : 0x3b; break; // : or ; - case 186: code = (flags & KeyFlags.Shift) ? 0x3a : 0x3b; break; // : or ; - case 187: code = (flags & KeyFlags.Shift) ? 0x2b : 0x3d; break; // + or = - case 188: code = (flags & KeyFlags.Shift) ? 0x3c : 0x2c; break; - case 189: code = (flags & KeyFlags.Shift) ? 0x5f : 0x2d; break; // _ or - - case 190: code = (flags & KeyFlags.Shift) ? 0x3e : 0x2e; break; - case 191: code = (flags & KeyFlags.Shift) ? 0x3f : 0x2f; break; - case 222: code = (flags & KeyFlags.Shift) ? 0x22 : 0x27; break; - default: - code = key; - // convert to uppercase for Apple ][ - if (code >= 0x61 && code <= 0x7a) code -= 32; - // convert to control codes if Ctrl pressed - if (code >= 65 && code < 65+26) { - if (flags & KeyFlags.Ctrl) code -= 64; // ctrl + setKeyInput(key: number, code: number, flags: number): void { + console.log(`setKeyInput: ${key} ${code} ${flags}`); + if (flags & KeyFlags.KeyDown) { + code = 0; + switch (key) { + case 16: case 17: case 18: case 91: + return; // ignore shift/ctrl/alt - don't set any key + case 8: + code = 8; // left + if (flags & KeyFlags.Shift) { + // (possibly) soft reset + this.cpu.reset(); + return; + } + break; + case 13: code = 13; break; // return + case 27: code = 27; break; // escape + case 37: code = 8; break; // left + case 39: code = 21; break; // right + case 38: code = 11; break; // up + case 40: code = 10; break; // down + case 48: code = (flags & KeyFlags.Shift) ? 0x29 : 0x30; break; // ) or 0 + case 49: code = (flags & KeyFlags.Shift) ? 0x21 : 0x31; break; // ! or 1 + case 50: code = (flags & KeyFlags.Shift) ? 0x40 : 0x32; break; // @ or 2 + case 51: code = (flags & KeyFlags.Shift) ? 0x23 : 0x33; break; // # or 3 + case 52: code = (flags & KeyFlags.Shift) ? 0x24 : 0x34; break; // $ or 4 + case 53: code = (flags & KeyFlags.Shift) ? 0x25 : 0x35; break; // % or 5 + case 54: code = (flags & KeyFlags.Shift) ? 0x5e : 0x36; break; // ^ or 6 + case 55: code = (flags & KeyFlags.Shift) ? 0x26 : 0x37; break; // & or 7 + case 56: code = (flags & KeyFlags.Shift) ? 0x2a : 0x38; break; // * or 8 + case 57: code = (flags & KeyFlags.Shift) ? 0x28 : 0x39; break; // ( or 9 + case 61: code = (flags & KeyFlags.Shift) ? 0x5f : 0x2d; break; // _ or - + case 173: code = (flags & KeyFlags.Shift) ? 0x2b : 0x3d; break; // + or = + case 59: code = (flags & KeyFlags.Shift) ? 0x3a : 0x3b; break; // : or ; + case 186: code = (flags & KeyFlags.Shift) ? 0x3a : 0x3b; break; // : or ; + case 187: code = (flags & KeyFlags.Shift) ? 0x2b : 0x3d; break; // + or = + case 188: code = (flags & KeyFlags.Shift) ? 0x3c : 0x2c; break; + case 189: code = (flags & KeyFlags.Shift) ? 0x5f : 0x2d; break; // _ or - + case 190: code = (flags & KeyFlags.Shift) ? 0x3e : 0x2e; break; + case 191: code = (flags & KeyFlags.Shift) ? 0x3f : 0x2f; break; + case 222: code = (flags & KeyFlags.Shift) ? 0x22 : 0x27; break; + default: + code = key; + // convert to uppercase for Apple ][ + if (code >= 0x61 && code <= 0x7a) code -= 32; + // convert to control codes if Ctrl pressed + if (code >= 65 && code < 65 + 26) { + if (flags & KeyFlags.Ctrl) code -= 64; // ctrl + } + } + if (code) { + this.kbdlatch = (code | 0x80) & 0xff; } } - if (code) { - this.kbdlatch = (code | 0x80) & 0xff; - } - } - } - - doLanguageCardIO(address:number) { - // TODO: require two writes in a row for some things - switch (address & 0x0f) { + } + + doLanguageCardIO(address: number) { + // TODO: require two writes in a row for some things + switch (address & 0x0f) { // Select aux RAM bank 2, write protected. - case 0x0: - case 0x4: - this.auxRAMselected = true; - this.auxRAMbank = 2; - this.writeinhibit = true; - break; - // Select ROM, write enable aux RAM bank 2. - case 0x1: - case 0x5: - this.auxRAMselected = false; - this.auxRAMbank = 2; - this.writeinhibit = false; - break; - // Select ROM, write protect aux RAM (either bank). - case 0x2: - case 0x6: - case 0xA: - case 0xE: - this.auxRAMselected = false; - this.writeinhibit = true; - break; - // Select aux RAM bank 2, write enabled. - case 0x3: - case 0x7: - this.auxRAMselected = true; - this.auxRAMbank = 2; - this.writeinhibit = false; - break; - // Select aux RAM bank 1, write protected. - case 0x8: - case 0xC: - this.auxRAMselected = true; - this.auxRAMbank = 1; - this.writeinhibit = true; - break; - // Select ROM, write enable aux RAM bank 1. - case 0x9: - case 0xD: - this.auxRAMselected = false; - this.auxRAMbank = 1; - this.writeinhibit = false; - break; - // Select aux RAM bank 1, write enabled. - case 0xB: - case 0xF: - this.auxRAMselected = true; - this.auxRAMbank = 1; - this.writeinhibit = false; - break; - } - this.setupLanguageCardConstants(); - return this.floatbus(); - } + case 0x0: + case 0x4: + this.auxRAMselected = true; + this.auxRAMbank = 2; + this.writeinhibit = true; + break; + // Select ROM, write enable aux RAM bank 2. + case 0x1: + case 0x5: + this.auxRAMselected = false; + this.auxRAMbank = 2; + this.writeinhibit = false; + break; + // Select ROM, write protect aux RAM (either bank). + case 0x2: + case 0x6: + case 0xA: + case 0xE: + this.auxRAMselected = false; + this.writeinhibit = true; + break; + // Select aux RAM bank 2, write enabled. + case 0x3: + case 0x7: + this.auxRAMselected = true; + this.auxRAMbank = 2; + this.writeinhibit = false; + break; + // Select aux RAM bank 1, write protected. + case 0x8: + case 0xC: + this.auxRAMselected = true; + this.auxRAMbank = 1; + this.writeinhibit = true; + break; + // Select ROM, write enable aux RAM bank 1. + case 0x9: + case 0xD: + this.auxRAMselected = false; + this.auxRAMbank = 1; + this.writeinhibit = false; + break; + // Select aux RAM bank 1, write enabled. + case 0xB: + case 0xF: + this.auxRAMselected = true; + this.auxRAMbank = 1; + this.writeinhibit = false; + break; + } + this.setupLanguageCardConstants(); + return this.floatbus(); + } - setupLanguageCardConstants() { - // reset language card constants - if (this.auxRAMbank == 2) - this.bank2rdoffset = -0x1000; // map 0xd000-0xdfff -> 0xc000-0xcfff - else - this.bank2rdoffset = 0x3000; // map 0xd000-0xdfff -> 0x10000-0x10fff - if (this.auxRAMbank == 2) - this.bank2wroffset = -0x1000; // map 0xd000-0xdfff -> 0xc000-0xcfff - else - this.bank2wroffset = 0x3000; // map 0xd000-0xdfff -> 0x10000-0x10fff - } + setupLanguageCardConstants() { + // reset language card constants + if (this.auxRAMbank == 2) + this.bank2rdoffset = -0x1000; // map 0xd000-0xdfff -> 0xc000-0xcfff + else + this.bank2rdoffset = 0x3000; // map 0xd000-0xdfff -> 0x10000-0x10fff + if (this.auxRAMbank == 2) + this.bank2wroffset = -0x1000; // map 0xd000-0xdfff -> 0xc000-0xcfff + else + this.bank2wroffset = 0x3000; // map 0xd000-0xdfff -> 0x10000-0x10fff + } - getDebugCategories() { - return ['CPU','Stack','I/O','Disk']; - } - getDebugInfo(category:string, state:AppleIIState) { - switch (category) { - case 'I/O': return "AUX RAM Bank: " + state.auxRAMbank + - "\nAUX RAM Select: " + state.auxRAMselected + - "\nAUX RAM Write: " + !state.writeinhibit + - "\n\nGR Switches: " + printFlags(state.grswitch, ["Graphics","Mixed","Page2","Hires"], false) + - "\n"; - case 'Disk': return (this.slots[6] && this.slots[6]['toLongString'] && this.slots[6]['toLongString']()) || "\n"; - } - } + getDebugCategories() { + return ['CPU', 'Stack', 'I/O', 'Disk']; + } + getDebugInfo(category: string, state: AppleIIState) { + switch (category) { + case 'I/O': return "AUX RAM Bank: " + state.auxRAMbank + + "\nAUX RAM Select: " + state.auxRAMselected + + "\nAUX RAM Write: " + !state.writeinhibit + + "\n\nGR Switches: " + printFlags(state.grswitch, ["Graphics", "Mixed", "Page2", "Hires"], false) + + "\n"; + case 'Disk': return (this.slots[6] && this.slots[6]['toLongString'] && this.slots[6]['toLongString']()) || "\n"; + } + } } -const GR_TXMODE = 1; -const GR_MIXMODE = 2; -const GR_PAGE1 = 4; -const GR_HIRES = 8; +const GR_TXMODE = 1; +const GR_MIXMODE = 2; +const GR_PAGE1 = 4; +const GR_HIRES = 8; -type AppleGRParams = {dirty:boolean[], grswitch:number, mem:Uint8Array}; +type AppleGRParams = { dirty: boolean[], grswitch: number, mem: Uint8Array }; -var Apple2Display = function(pixels : Uint32Array, apple : AppleGRParams) { - var XSIZE = 280; - var YSIZE = 192; - var PIXELON = 0xffffffff; - var PIXELOFF = 0xff000000; +var Apple2Display = function (pixels: Uint32Array, apple: AppleGRParams) { + var XSIZE = 280; + var YSIZE = 192; + var PIXELON = 0xffffffff; + var PIXELOFF = 0xff000000; - var oldgrmode = -1; - var textbuf = new Array(40*24); + var oldgrmode = -1; + var textbuf = new Array(40 * 24); - const flashInterval = 250; + const flashInterval = 250; - // https://mrob.com/pub/xapple2/colors.html - const loresColor = [ - RGBA(0, 0, 0), - RGBA(227, 30, 96), - RGBA(96, 78, 189), - RGBA(255, 68, 253), - RGBA(0, 163, 96), - RGBA(156, 156, 156), - RGBA(20, 207, 253), - RGBA(208, 195, 255), - RGBA(96, 114, 3), - RGBA(255, 106, 60), - RGBA(156, 156, 156), - RGBA(255, 160, 208), - RGBA(20, 245, 60), - RGBA(208, 221, 141), - RGBA(114, 255, 208), - RGBA(255, 255, 255) - ]; - - const text_lut = [ - 0x000, 0x080, 0x100, 0x180, 0x200, 0x280, 0x300, 0x380, - 0x028, 0x0a8, 0x128, 0x1a8, 0x228, 0x2a8, 0x328, 0x3a8, - 0x050, 0x0d0, 0x150, 0x1d0, 0x250, 0x2d0, 0x350, 0x3d0 - ]; - - const hires_lut = [ - 0x0000, 0x0400, 0x0800, 0x0c00, 0x1000, 0x1400, 0x1800, 0x1c00, - 0x0080, 0x0480, 0x0880, 0x0c80, 0x1080, 0x1480, 0x1880, 0x1c80, - 0x0100, 0x0500, 0x0900, 0x0d00, 0x1100, 0x1500, 0x1900, 0x1d00, - 0x0180, 0x0580, 0x0980, 0x0d80, 0x1180, 0x1580, 0x1980, 0x1d80, - 0x0200, 0x0600, 0x0a00, 0x0e00, 0x1200, 0x1600, 0x1a00, 0x1e00, - 0x0280, 0x0680, 0x0a80, 0x0e80, 0x1280, 0x1680, 0x1a80, 0x1e80, - 0x0300, 0x0700, 0x0b00, 0x0f00, 0x1300, 0x1700, 0x1b00, 0x1f00, - 0x0380, 0x0780, 0x0b80, 0x0f80, 0x1380, 0x1780, 0x1b80, 0x1f80, - 0x0028, 0x0428, 0x0828, 0x0c28, 0x1028, 0x1428, 0x1828, 0x1c28, - 0x00a8, 0x04a8, 0x08a8, 0x0ca8, 0x10a8, 0x14a8, 0x18a8, 0x1ca8, - 0x0128, 0x0528, 0x0928, 0x0d28, 0x1128, 0x1528, 0x1928, 0x1d28, - 0x01a8, 0x05a8, 0x09a8, 0x0da8, 0x11a8, 0x15a8, 0x19a8, 0x1da8, - 0x0228, 0x0628, 0x0a28, 0x0e28, 0x1228, 0x1628, 0x1a28, 0x1e28, - 0x02a8, 0x06a8, 0x0aa8, 0x0ea8, 0x12a8, 0x16a8, 0x1aa8, 0x1ea8, - 0x0328, 0x0728, 0x0b28, 0x0f28, 0x1328, 0x1728, 0x1b28, 0x1f28, - 0x03a8, 0x07a8, 0x0ba8, 0x0fa8, 0x13a8, 0x17a8, 0x1ba8, 0x1fa8, - 0x0050, 0x0450, 0x0850, 0x0c50, 0x1050, 0x1450, 0x1850, 0x1c50, - 0x00d0, 0x04d0, 0x08d0, 0x0cd0, 0x10d0, 0x14d0, 0x18d0, 0x1cd0, - 0x0150, 0x0550, 0x0950, 0x0d50, 0x1150, 0x1550, 0x1950, 0x1d50, - 0x01d0, 0x05d0, 0x09d0, 0x0dd0, 0x11d0, 0x15d0, 0x19d0, 0x1dd0, - 0x0250, 0x0650, 0x0a50, 0x0e50, 0x1250, 0x1650, 0x1a50, 0x1e50, - 0x02d0, 0x06d0, 0x0ad0, 0x0ed0, 0x12d0, 0x16d0, 0x1ad0, 0x1ed0, - 0x0350, 0x0750, 0x0b50, 0x0f50, 0x1350, 0x1750, 0x1b50, 0x1f50, - 0x03d0, 0x07d0, 0x0bd0, 0x0fd0, 0x13d0, 0x17d0, 0x1bd0, 0x1fd0, - // just for floating bus, y >= 192 - 0x0078, 0x0478, 0x0878, 0x0c78, 0x1078, 0x1478, 0x1878, 0x1c78, - 0x00f8, 0x04f8, 0x08f8, 0x0cf8, 0x10f8, 0x14f8, 0x18f8, 0x1cf8, - 0x0178, 0x0578, 0x0978, 0x0d78, 0x1178, 0x1578, 0x1978, 0x1d78, - 0x01f8, 0x05f8, 0x09f8, 0x0df8, 0x11f8, 0x15f8, 0x19f8, 0x1df8, - 0x0278, 0x0678, 0x0a78, 0x0e78, 0x1278, 0x1678, 0x1a78, 0x1e78, - 0x02f8, 0x06f8, 0x0af8, 0x0ef8, 0x12f8, 0x16f8, 0x1af8, 0x1ef8, - 0x0378, 0x0778, 0x0b78, 0x0f78, 0x1378, 0x1778, 0x1b78, 0x1f78, - 0x03f8, 0x07f8, 0x0bf8, 0x0ff8, 0x13f8, 0x17f8, 0x1bf8, 0x1ff8, - 0x0000, 0x0400, 0x0800, 0x0c00, 0x1000, 0x1400, + // https://mrob.com/pub/xapple2/colors.html + const loresColor = [ + RGBA(0, 0, 0), + RGBA(227, 30, 96), + RGBA(96, 78, 189), + RGBA(255, 68, 253), + RGBA(0, 163, 96), + RGBA(156, 156, 156), + RGBA(20, 207, 253), + RGBA(208, 195, 255), + RGBA(96, 114, 3), + RGBA(255, 106, 60), + RGBA(156, 156, 156), + RGBA(255, 160, 208), + RGBA(20, 245, 60), + RGBA(208, 221, 141), + RGBA(114, 255, 208), + RGBA(255, 255, 255) ]; - var colors_lut; + const text_lut = [ + 0x000, 0x080, 0x100, 0x180, 0x200, 0x280, 0x300, 0x380, + 0x028, 0x0a8, 0x128, 0x1a8, 0x228, 0x2a8, 0x328, 0x3a8, + 0x050, 0x0d0, 0x150, 0x1d0, 0x250, 0x2d0, 0x350, 0x3d0 + ]; - /** - * This function makes the color lookup table for hires mode. - * We make a table of 1024 * 2 * 7 entries. - * Why? Because we assume each color byte has 10 bits - * (8 real bits + 1 on each side) and we need different colors - * for odd and even addresses (2) and each byte displays 7 pixels. - */ - { - colors_lut = new Array(256*4*2*7); - var i,j; - var c1,c2,c3 = 15; - var base = 0; + const hires_lut = [ + 0x0000, 0x0400, 0x0800, 0x0c00, 0x1000, 0x1400, 0x1800, 0x1c00, + 0x0080, 0x0480, 0x0880, 0x0c80, 0x1080, 0x1480, 0x1880, 0x1c80, + 0x0100, 0x0500, 0x0900, 0x0d00, 0x1100, 0x1500, 0x1900, 0x1d00, + 0x0180, 0x0580, 0x0980, 0x0d80, 0x1180, 0x1580, 0x1980, 0x1d80, + 0x0200, 0x0600, 0x0a00, 0x0e00, 0x1200, 0x1600, 0x1a00, 0x1e00, + 0x0280, 0x0680, 0x0a80, 0x0e80, 0x1280, 0x1680, 0x1a80, 0x1e80, + 0x0300, 0x0700, 0x0b00, 0x0f00, 0x1300, 0x1700, 0x1b00, 0x1f00, + 0x0380, 0x0780, 0x0b80, 0x0f80, 0x1380, 0x1780, 0x1b80, 0x1f80, + 0x0028, 0x0428, 0x0828, 0x0c28, 0x1028, 0x1428, 0x1828, 0x1c28, + 0x00a8, 0x04a8, 0x08a8, 0x0ca8, 0x10a8, 0x14a8, 0x18a8, 0x1ca8, + 0x0128, 0x0528, 0x0928, 0x0d28, 0x1128, 0x1528, 0x1928, 0x1d28, + 0x01a8, 0x05a8, 0x09a8, 0x0da8, 0x11a8, 0x15a8, 0x19a8, 0x1da8, + 0x0228, 0x0628, 0x0a28, 0x0e28, 0x1228, 0x1628, 0x1a28, 0x1e28, + 0x02a8, 0x06a8, 0x0aa8, 0x0ea8, 0x12a8, 0x16a8, 0x1aa8, 0x1ea8, + 0x0328, 0x0728, 0x0b28, 0x0f28, 0x1328, 0x1728, 0x1b28, 0x1f28, + 0x03a8, 0x07a8, 0x0ba8, 0x0fa8, 0x13a8, 0x17a8, 0x1ba8, 0x1fa8, + 0x0050, 0x0450, 0x0850, 0x0c50, 0x1050, 0x1450, 0x1850, 0x1c50, + 0x00d0, 0x04d0, 0x08d0, 0x0cd0, 0x10d0, 0x14d0, 0x18d0, 0x1cd0, + 0x0150, 0x0550, 0x0950, 0x0d50, 0x1150, 0x1550, 0x1950, 0x1d50, + 0x01d0, 0x05d0, 0x09d0, 0x0dd0, 0x11d0, 0x15d0, 0x19d0, 0x1dd0, + 0x0250, 0x0650, 0x0a50, 0x0e50, 0x1250, 0x1650, 0x1a50, 0x1e50, + 0x02d0, 0x06d0, 0x0ad0, 0x0ed0, 0x12d0, 0x16d0, 0x1ad0, 0x1ed0, + 0x0350, 0x0750, 0x0b50, 0x0f50, 0x1350, 0x1750, 0x1b50, 0x1f50, + 0x03d0, 0x07d0, 0x0bd0, 0x0fd0, 0x13d0, 0x17d0, 0x1bd0, 0x1fd0, + // just for floating bus, y >= 192 + 0x0078, 0x0478, 0x0878, 0x0c78, 0x1078, 0x1478, 0x1878, 0x1c78, + 0x00f8, 0x04f8, 0x08f8, 0x0cf8, 0x10f8, 0x14f8, 0x18f8, 0x1cf8, + 0x0178, 0x0578, 0x0978, 0x0d78, 0x1178, 0x1578, 0x1978, 0x1d78, + 0x01f8, 0x05f8, 0x09f8, 0x0df8, 0x11f8, 0x15f8, 0x19f8, 0x1df8, + 0x0278, 0x0678, 0x0a78, 0x0e78, 0x1278, 0x1678, 0x1a78, 0x1e78, + 0x02f8, 0x06f8, 0x0af8, 0x0ef8, 0x12f8, 0x16f8, 0x1af8, 0x1ef8, + 0x0378, 0x0778, 0x0b78, 0x0f78, 0x1378, 0x1778, 0x1b78, 0x1f78, + 0x03f8, 0x07f8, 0x0bf8, 0x0ff8, 0x13f8, 0x17f8, 0x1bf8, 0x1ff8, + 0x0000, 0x0400, 0x0800, 0x0c00, 0x1000, 0x1400, + ]; - // go thru odd and even - for (j=0; j<2; j++) - { - // go thru 1024 values - for (var b1=0; b1<1024; b1++) - { - // see if the hi bit is set - if ((b1 & 0x80) == 0) - { - c1 = 3; c2 = 12; // purple & green - } else - { - c1 = 6; c2 = 9; // blue & orange - } - // make a value consisting of: - // the 8th bit, then bits 0-7, then the 9th bit - var b = ((b1 & 0x100) >> 8) | ((b1 & 0x7f) << 1) | - ((b1 & 0x200) >> 1); - // go through each pixel - for (i=0; i<7; i++) - { - var c; - // is this pixel lit? - if (((2<> 4]; - for (i=0; i<4; i++) - { - pixels[base] = - pixels[base+1] = - pixels[base+2] = - pixels[base+3] = - pixels[base+4] = - pixels[base+5] = - pixels[base+6] = c; - base += XSIZE; - } - } + /** + * This function makes the color lookup table for hires mode. + * We make a table of 1024 * 2 * 7 entries. + * Why? Because we assume each color byte has 10 bits + * (8 real bits + 1 on each side) and we need different colors + * for odd and even addresses (2) and each byte displays 7 pixels. + */ + { + colors_lut = new Array(256 * 4 * 2 * 7); + var i, j; + var c1, c2, c3 = 15; + var base = 0; - function drawTextChar(x, y, b, invert) - { - var base = (y<<3)*XSIZE + x*7; // (x<<2) + (x<<1) + x - var on,off; - if (invert) - { - on = PIXELOFF; - off = PIXELON; - } else - { - on = PIXELON; - off = PIXELOFF; - } + // go thru odd and even + for (j = 0; j < 2; j++) { + // go thru 1024 values + for (var b1 = 0; b1 < 1024; b1++) { + // see if the hi bit is set + if ((b1 & 0x80) == 0) { + c1 = 3; c2 = 12; // purple & green + } else { + c1 = 6; c2 = 9; // blue & orange + } + // make a value consisting of: + // the 8th bit, then bits 0-7, then the 9th bit + var b = ((b1 & 0x100) >> 8) | ((b1 & 0x7f) << 1) | + ((b1 & 0x200) >> 1); + // go through each pixel + for (i = 0; i < 7; i++) { + var c; + // is this pixel lit? + if (((2 << i) & b) != 0) { + // are there pixels lit on both sides of this one? + if (((7 << i) & b) == (7 << i)) + // yes, make it white + c = 15; + else + // no, choose color based on odd/even byte + // and odd/even pixel column + c = ((((j ^ i) & 1) == 0) ? c1 : c2); + } else { + // are there pixels lit in the previous & next + // column but none in this? + if (((5 << i) & b) == (5 << i)) + // color this pixel + c = ((((j ^ i) & 1) != 0) ? c1 : c2); + else + c = 0; + } + colors_lut[base] = loresColor[c]; + base++; + } + } + } + } - for (var yy=0; yy<8; yy++) - { - var chr = apple2_charset[(b<<3)+yy]; - pixels[base] = ((chr & 64) > 0)?on:off; - pixels[base+1] = ((chr & 32) > 0)?on:off; - pixels[base+2] = ((chr & 16) > 0)?on:off; - pixels[base+3] = ((chr & 8) > 0)?on:off; - pixels[base+4] = ((chr & 4) > 0)?on:off; - pixels[base+5] = ((chr & 2) > 0)?on:off; - pixels[base+6] = ((chr & 1) > 0)?on:off; - base += XSIZE; - } - } + function drawLoresChar(x, y, b) { + var i, base, adr, c; + base = (y << 3) * XSIZE + x * 7; //(x<<2) + (x<<1) + x + c = loresColor[b & 0x0f]; + for (i = 0; i < 4; i++) { + pixels[base] = + pixels[base + 1] = + pixels[base + 2] = + pixels[base + 3] = + pixels[base + 4] = + pixels[base + 5] = + pixels[base + 6] = c; + base += XSIZE; + } + c = loresColor[b >> 4]; + for (i = 0; i < 4; i++) { + pixels[base] = + pixels[base + 1] = + pixels[base + 2] = + pixels[base + 3] = + pixels[base + 4] = + pixels[base + 5] = + pixels[base + 6] = c; + base += XSIZE; + } + } - this.getAddressForScanline = function(y:number) : number { + function drawTextChar(x, y, b, invert) { + var base = (y << 3) * XSIZE + x * 7; // (x<<2) + (x<<1) + x + var on, off; + if (invert) { + on = PIXELOFF; + off = PIXELON; + } else { + on = PIXELON; + off = PIXELOFF; + } + + for (var yy = 0; yy < 8; yy++) { + var chr = apple2_charset[(b << 3) + yy]; + pixels[base] = ((chr & 64) > 0) ? on : off; + pixels[base + 1] = ((chr & 32) > 0) ? on : off; + pixels[base + 2] = ((chr & 16) > 0) ? on : off; + pixels[base + 3] = ((chr & 8) > 0) ? on : off; + pixels[base + 4] = ((chr & 4) > 0) ? on : off; + pixels[base + 5] = ((chr & 2) > 0) ? on : off; + pixels[base + 6] = ((chr & 1) > 0) ? on : off; + base += XSIZE; + } + } + + this.getAddressForScanline = function (y: number): number { var base = hires_lut[y]; if ((apple.grswitch & GR_HIRES) && (y < 160 || !(apple.grswitch & GR_MIXMODE))) base = base | ((apple.grswitch & GR_PAGE1) ? 0x4000 : 0x2000); @@ -708,409 +692,389 @@ var Apple2Display = function(pixels : Uint32Array, apple : AppleGRParams) { return base; } - function drawHiresLines(y, maxy) - { - var yb = y*XSIZE; - for (; y < maxy; y++) - { - var base = hires_lut[y] + (((apple.grswitch & GR_PAGE1) != 0) ? 0x4000 : 0x2000); - if (!apple.dirty[base >> 7]) - { - yb += XSIZE; - continue; - } - var c1, c2; - var b = 0; - var b1 = apple.mem[base] & 0xff; - for (var x1=0; x1<20; x1++) - { - var b2 = apple.mem[base+1] & 0xff; - var b3 = apple.mem[base+2] & 0xff; - var d1 = (((b&0x40)<<2) | b1 | b2<<9) & 0x3ff; - for (var i=0; i<7; i++) - pixels[yb+i] = colors_lut[d1*7+i]; - var d2 = (((b1&0x40)<<2) | b2 | b3<<9) & 0x3ff; - for (var i=0; i<7; i++) - pixels[yb+7+i] = colors_lut[d2*7+7168+i]; - yb += 14; - base += 2; - b = b2; - b1 = b3; - } - } - } + function drawHiresLines(y, maxy) { + var yb = y * XSIZE; + for (; y < maxy; y++) { + var base = hires_lut[y] + (((apple.grswitch & GR_PAGE1) != 0) ? 0x4000 : 0x2000); + if (!apple.dirty[base >> 7]) { + yb += XSIZE; + continue; + } + var c1, c2; + var b = 0; + var b1 = apple.mem[base] & 0xff; + for (var x1 = 0; x1 < 20; x1++) { + var b2 = apple.mem[base + 1] & 0xff; + var b3 = apple.mem[base + 2] & 0xff; + var d1 = (((b & 0x40) << 2) | b1 | b2 << 9) & 0x3ff; + for (var i = 0; i < 7; i++) + pixels[yb + i] = colors_lut[d1 * 7 + i]; + var d2 = (((b1 & 0x40) << 2) | b2 | b3 << 9) & 0x3ff; + for (var i = 0; i < 7; i++) + pixels[yb + 7 + i] = colors_lut[d2 * 7 + 7168 + i]; + yb += 14; + base += 2; + b = b2; + b1 = b3; + } + } + } - function drawLoresLine(y) - { - // get the base address of this line - var base = text_lut[y] + - (((apple.grswitch & GR_PAGE1) != 0) ? 0x800 : 0x400); - // if (!dirty[base >> 7]) - // return; - for (var x=0; x<40; x++) - { - var b = apple.mem[base+x] & 0xff; - // if the char. changed, draw it - if (b != textbuf[y*40+x]) - { - drawLoresChar(x, y, b); - textbuf[y*40+x] = b; - } - } - } + function drawLoresLine(y) { + // get the base address of this line + var base = text_lut[y] + + (((apple.grswitch & GR_PAGE1) != 0) ? 0x800 : 0x400); + // if (!dirty[base >> 7]) + // return; + for (var x = 0; x < 40; x++) { + var b = apple.mem[base + x] & 0xff; + // if the char. changed, draw it + if (b != textbuf[y * 40 + x]) { + drawLoresChar(x, y, b); + textbuf[y * 40 + x] = b; + } + } + } - function drawTextLine(y, flash) - { - // get the base address of this line - var base = text_lut[y] + - (((apple.grswitch & GR_PAGE1) != 0) ? 0x800 : 0x400); - // if (!dirty[base >> 7]) - // return; - for (var x=0; x<40; x++) - { - var b = apple.mem[base+x] & 0xff; - var invert; - // invert flash characters 1/2 of the time - if (b >= 0x80) - { - invert = false; - } else if (b >= 0x40) - { - invert = flash; - if (flash) - b -= 0x40; - else - b += 0x40; - } else - invert = true; - // if the char. changed, draw it - if (b != textbuf[y*40+x]) - { - drawTextChar(x, y, b & 0x7f, invert); - textbuf[y*40+x] = b; - } - } - } + function drawTextLine(y, flash) { + // get the base address of this line + var base = text_lut[y] + + (((apple.grswitch & GR_PAGE1) != 0) ? 0x800 : 0x400); + // if (!dirty[base >> 7]) + // return; + for (var x = 0; x < 40; x++) { + var b = apple.mem[base + x] & 0xff; + var invert; + // invert flash characters 1/2 of the time + if (b >= 0x80) { + invert = false; + } else if (b >= 0x40) { + invert = flash; + if (flash) + b -= 0x40; + else + b += 0x40; + } else + invert = true; + // if the char. changed, draw it + if (b != textbuf[y * 40 + x]) { + drawTextChar(x, y, b & 0x7f, invert); + textbuf[y * 40 + x] = b; + } + } + } - this.updateScreen = function(totalrepaint) - { - var y; - var flash = (new Date().getTime() % (flashInterval<<1)) > flashInterval; + this.updateScreen = function (totalrepaint) { + var y; + var flash = (new Date().getTime() % (flashInterval << 1)) > flashInterval; - // if graphics mode changed, repaint whole screen - if (apple.grswitch != oldgrmode) - { - oldgrmode = apple.grswitch; - totalrepaint = true; - } - if (totalrepaint) - { - // clear textbuf if in text mode - if ((apple.grswitch & GR_TXMODE) != 0 || (apple.grswitch & GR_MIXMODE) != 0) - { - for (y=0; y<24; y++) - for (var x=0; x<40; x++) - textbuf[y*40+x] = -1; - } - for (var i=0; i { - emu : AppleII; - track_data : Uint8Array; - - constructor(emu : AppleII, image : Uint8Array) { - super(); - this.emu = emu; - this.data = new Array(NUM_TRACKS); - for (var i=0; i>1]; - else - this.track_data = null; - } - - toLongString() { - return "Track: " + (this.track / 2) + + emu: AppleII; + track_data: Uint8Array; + + constructor(emu: AppleII, image: Uint8Array) { + super(); + this.emu = emu; + this.data = new Array(NUM_TRACKS); + for (var i = 0; i < NUM_TRACKS; i++) { + var ofs = i * 16 * 256; + this.data[i] = nibblizeTrack(254, i, image.slice(ofs, ofs + 16 * 256)); + } + } + + saveState(): DiskIIState { + var s = { + data: new Array(NUM_TRACKS), + track: this.track, + read_mode: this.read_mode, + write_protect: this.write_protect, + motor: this.motor, + track_index: this.track_index + }; + for (var i = 0; i < NUM_TRACKS; i++) + s.data[i] = this.data[i].slice(0); + return s; + } + + loadState(s: DiskIIState) { + for (var i = 0; i < NUM_TRACKS; i++) + this.data[i].set(s.data[i]); + this.track = s.track; + this.read_mode = s.read_mode; + this.write_protect = s.write_protect; + this.motor = s.motor; + this.track_index = s.track_index; + if ((this.track & 1) == 0) + this.track_data = this.data[this.track >> 1]; + else + this.track_data = null; + } + + toLongString() { + return "Track: " + (this.track / 2) + "\nOffset: " + (this.track_index) + "\nMode: " + (this.read_mode ? "READ" : "WRITE") + "\nMotor: " + this.motor + "\nData: " + (this.track_data ? hex(this.track_data[this.track_index]) : '-') + "\n"; - } - - read_latch() : number { + } + + read_latch(): number { this.track_index = (this.track_index + 1) % TRACK_SIZE; if (this.track_data) { return (this.track_data[this.track_index] & 0xff); @@ -1218,16 +1182,14 @@ class DiskII extends DiskIIState implements SlotDevice, SavesState if (this.track_data != null) this.track_data[this.track_index] = value; } - - readROM(address) { return DISKII_PROM[address]; } - readConst(address) { return DISKII_PROM[address]; } - read(address) { return this.doIO(address, 0); } + + readROM(address) { return DISKII_PROM[address]; } + readConst(address) { return DISKII_PROM[address]; } + read(address) { return this.doIO(address, 0); } write(address, value) { this.doIO(address, value); } - doIO(address, value) : number - { - switch (address & 0x0f) - { + doIO(address, value): number { + switch (address & 0x0f) { /* * Turn motor phases 0 to 3 on. Turning on the previous phase + 1 * increments the track position, turning on the previous phase - 1 @@ -1244,69 +1206,66 @@ class DiskII extends DiskIIState implements SlotDevice, SavesState phase = (address >> 1) & 3; // if new phase is even and current phase is odd - if (phase == ((new_track - 1) & 3)) - { + if (phase == ((new_track - 1) & 3)) { if (new_track > 0) new_track--; } else - if (phase == ((new_track + 1) & 3)) - { - if (new_track < NUM_TRACKS*2-1) - new_track++; - } - if ((new_track & 1) == 0) - { - this.track_data = this.data[new_track>>1]; - console.log('track', new_track/2); + if (phase == ((new_track + 1) & 3)) { + if (new_track < NUM_TRACKS * 2 - 1) + new_track++; + } + if ((new_track & 1) == 0) { + this.track_data = this.data[new_track >> 1]; + console.log('track', new_track / 2); } else this.track_data = null; this.track = new_track; break; - /* - * Turn drive motor off. - */ + /* + * Turn drive motor off. + */ case 0x8: this.motor = false; break; - /* - * Turn drive motor on. - */ + /* + * Turn drive motor on. + */ case 0x9: this.motor = true; - break; - /* - * Select drive 1. - */ + break; + /* + * Select drive 1. + */ case 0xa: //drive = 0; break; - /* - * Select drive 2. - */ + /* + * Select drive 2. + */ case 0xb: //drive = 1; break; - /* - * Select write mode. - */ + /* + * Select write mode. + */ case 0xf: this.read_mode = false; - /* - * Read a disk byte if read mode is active. - */ + /* + * Read a disk byte if read mode is active. + */ case 0xC: if (this.read_mode) return this.read_latch(); break; - /* - * Select read mode and read the write protect status. - */ + /* + * Select read mode and read the write protect status. + */ case 0xE: this.read_mode = true; - /* - * Write a disk byte if write mode is active and the disk is not - * write protected. - */ + /* + * Write a disk byte if write mode is active and the disk is not + * write protected. + */ case 0xD: if (value >= 0 && !this.read_mode && !this.write_protect) this.write_latch(value); @@ -1322,27 +1281,27 @@ class DiskII extends DiskIIState implements SlotDevice, SavesState /* --------------- TRACK CONVERSION ROUTINES ---------------------- */ - /* - * Normal byte (lower six bits only) -> disk byte translation table. - */ - const byte_translation = [ - 0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6, - 0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3, - 0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc, - 0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3, - 0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, - 0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec, - 0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, - 0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff - ]; +/* + * Normal byte (lower six bits only) -> disk byte translation table. + */ +const byte_translation = [ + 0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6, + 0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3, + 0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc, + 0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3, + 0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, + 0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec, + 0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, + 0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff +]; - /* - * Sector skewing table. - */ +/* + * Sector skewing table. + */ - const skewing_table = [ - 0,7,14,6,13,5,12,4,11,3,10,2,9,1,8,15 - ]; +const skewing_table = [ + 0, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 15 +]; /* * Encode a 256-byte sector as SECTOR_SIZE disk bytes as follows: @@ -1356,149 +1315,143 @@ class DiskII extends DiskIIState implements SlotDevice, SavesState * 343 data block bytes * 3 data trailer bytes */ - function nibblizeSector(vol, trk, sector, inn, in_ofs, out, i) - { - var loop, checksum, prev_value, value; - var sector_buffer = new Uint8Array(258); - value = 0; +function nibblizeSector(vol, trk, sector, inn, in_ofs, out, i) { + var loop, checksum, prev_value, value; + var sector_buffer = new Uint8Array(258); + value = 0; - /* - * Step 1: write 6 sync bytes (0xff's). Normally these would be - * written as 10-bit bytes with two extra zero bits, but for the - * purpose of emulation normal 8-bit bytes will do, since the - * emulated drive will always be in sync. - */ - for (loop = 0; loop < 14; loop++) - out[i++] = 0xff; + /* + * Step 1: write 6 sync bytes (0xff's). Normally these would be + * written as 10-bit bytes with two extra zero bits, but for the + * purpose of emulation normal 8-bit bytes will do, since the + * emulated drive will always be in sync. + */ + for (loop = 0; loop < 14; loop++) + out[i++] = 0xff; - /* - * Step 2: write the 3-byte address header (0xd5 0xaa 0x96). - */ - out[i++] = 0xd5; - out[i++] = 0xaa; - out[i++] = 0x96; + /* + * Step 2: write the 3-byte address header (0xd5 0xaa 0x96). + */ + out[i++] = 0xd5; + out[i++] = 0xaa; + out[i++] = 0x96; - /* - * Step 3: write the address block. Use 4-and-4 encoding to convert - * the volume, track and sector and checksum into 2 disk bytes each. - * The checksum is a simple exclusive OR of the first three values. - */ - out[i++] = ((vol >> 1) | 0xaa); - out[i++] = (vol | 0xaa); - checksum = vol; - out[i++] = ((trk >> 1) | 0xaa); - out[i++] = (trk | 0xaa); - checksum ^= trk; - out[i++] = ((sector >> 1) | 0xaa); - out[i++] = (sector | 0xaa); - checksum ^= sector; - out[i++] = ((checksum >> 1) | 0xaa); - out[i++] = (checksum | 0xaa); + /* + * Step 3: write the address block. Use 4-and-4 encoding to convert + * the volume, track and sector and checksum into 2 disk bytes each. + * The checksum is a simple exclusive OR of the first three values. + */ + out[i++] = ((vol >> 1) | 0xaa); + out[i++] = (vol | 0xaa); + checksum = vol; + out[i++] = ((trk >> 1) | 0xaa); + out[i++] = (trk | 0xaa); + checksum ^= trk; + out[i++] = ((sector >> 1) | 0xaa); + out[i++] = (sector | 0xaa); + checksum ^= sector; + out[i++] = ((checksum >> 1) | 0xaa); + out[i++] = (checksum | 0xaa); - /* - * Step 4: write the 3-byte address trailer (0xde 0xaa 0xeb). - */ - out[i++] = (0xde); - out[i++] = (0xaa); - out[i++] = (0xeb); + /* + * Step 4: write the 3-byte address trailer (0xde 0xaa 0xeb). + */ + out[i++] = (0xde); + out[i++] = (0xaa); + out[i++] = (0xeb); - /* - * Step 5: write another 6 sync bytes. - */ - for (loop = 0; loop < 6; loop++) - out[i++] = (0xff); + /* + * Step 5: write another 6 sync bytes. + */ + for (loop = 0; loop < 6; loop++) + out[i++] = (0xff); - /* - * Step 6: write the 3-byte data header. - */ - out[i++] = (0xd5); - out[i++] = (0xaa); - out[i++] = (0xad); + /* + * Step 6: write the 3-byte data header. + */ + out[i++] = (0xd5); + out[i++] = (0xaa); + out[i++] = (0xad); - /* - * Step 7: read the next 256-byte sector from the old disk image file, - * and add two zero bytes to bring the number of bytes up to a multiple - * of 3. - */ - for (loop = 0; loop < 256; loop++) - sector_buffer[loop] = inn[loop + in_ofs] & 0xff; - sector_buffer[256] = 0; - sector_buffer[257] = 0; - - /* - * Step 8: write the first 86 disk bytes of the data block, which - * encodes the bottom two bits of each sector byte into six-bit - * values as follows: - * - * disk byte n, bit 0 = sector byte n, bit 1 - * disk byte n, bit 1 = sector byte n, bit 0 - * disk byte n, bit 2 = sector byte n + 86, bit 1 - * disk byte n, bit 3 = sector byte n + 86, bit 0 - * disk byte n, bit 4 = sector byte n + 172, bit 1 - * disk byte n, bit 5 = sector byte n + 172, bit 0 - * - * The scheme allows each pair of bits to be shifted to the right out - * of the disk byte, then shifted to the left into the sector byte. - * - * Before the 6-bit value is translated to a disk byte, it is exclusive - * ORed with the previous 6-bit value, hence the values written are - * really a running checksum. - */ - prev_value = 0; - for (loop = 0; loop < 86; loop++) - { - value = (sector_buffer[loop] & 0x01) << 1; - value |= (sector_buffer[loop] & 0x02) >> 1; - value |= (sector_buffer[loop + 86] & 0x01) << 3; - value |= (sector_buffer[loop + 86] & 0x02) << 1; - value |= (sector_buffer[loop + 172] & 0x01) << 5; - value |= (sector_buffer[loop + 172] & 0x02) << 3; - out[i++] = (byte_translation[value ^ prev_value]); - prev_value = value; - } - - /* - * Step 9: write the last 256 disk bytes of the data block, which - * encodes the top six bits of each sector byte. Again, each value - * is exclusive ORed with the previous value to create a running - * checksum (the first value is exclusive ORed with the last value of - * the previous step). - */ - - for (loop = 0; loop < 256; loop++) - { - value = (sector_buffer[loop] >> 2); - out[i++] = (byte_translation[value ^ prev_value]); - prev_value = value; - } - - /* - * Step 10: write the last value as the checksum. - */ - out[i++] = (byte_translation[value]); - - /* - * Step 11: write the 3-byte data trailer. - */ - out[i++] = (0xde); - out[i++] = (0xaa); - out[i++] = (0xeb); + /* + * Step 7: read the next 256-byte sector from the old disk image file, + * and add two zero bytes to bring the number of bytes up to a multiple + * of 3. + */ + for (loop = 0; loop < 256; loop++) + sector_buffer[loop] = inn[loop + in_ofs] & 0xff; + sector_buffer[256] = 0; + sector_buffer[257] = 0; + /* + * Step 8: write the first 86 disk bytes of the data block, which + * encodes the bottom two bits of each sector byte into six-bit + * values as follows: + * + * disk byte n, bit 0 = sector byte n, bit 1 + * disk byte n, bit 1 = sector byte n, bit 0 + * disk byte n, bit 2 = sector byte n + 86, bit 1 + * disk byte n, bit 3 = sector byte n + 86, bit 0 + * disk byte n, bit 4 = sector byte n + 172, bit 1 + * disk byte n, bit 5 = sector byte n + 172, bit 0 + * + * The scheme allows each pair of bits to be shifted to the right out + * of the disk byte, then shifted to the left into the sector byte. + * + * Before the 6-bit value is translated to a disk byte, it is exclusive + * ORed with the previous 6-bit value, hence the values written are + * really a running checksum. + */ + prev_value = 0; + for (loop = 0; loop < 86; loop++) { + value = (sector_buffer[loop] & 0x01) << 1; + value |= (sector_buffer[loop] & 0x02) >> 1; + value |= (sector_buffer[loop + 86] & 0x01) << 3; + value |= (sector_buffer[loop + 86] & 0x02) << 1; + value |= (sector_buffer[loop + 172] & 0x01) << 5; + value |= (sector_buffer[loop + 172] & 0x02) << 3; + out[i++] = (byte_translation[value ^ prev_value]); + prev_value = value; } - function nibblizeTrack(vol, trk, inn) - { - var out = new Uint8Array(TRACK_SIZE); - var out_pos = 0; - for (var sector = 0; sector < 16; sector++) { - nibblizeSector(vol, trk, sector, - inn, skewing_table[sector] << 8, - out, out_pos); - out_pos += SECTOR_SIZE; - } - while (out_pos < TRACK_SIZE) - out[out_pos++] = (0xff); - return out; + /* + * Step 9: write the last 256 disk bytes of the data block, which + * encodes the top six bits of each sector byte. Again, each value + * is exclusive ORed with the previous value to create a running + * checksum (the first value is exclusive ORed with the last value of + * the previous step). + */ + + for (loop = 0; loop < 256; loop++) { + value = (sector_buffer[loop] >> 2); + out[i++] = (byte_translation[value ^ prev_value]); + prev_value = value; } + /* + * Step 10: write the last value as the checksum. + */ + out[i++] = (byte_translation[value]); + /* + * Step 11: write the 3-byte data trailer. + */ + out[i++] = (0xde); + out[i++] = (0xaa); + out[i++] = (0xeb); + +} + +function nibblizeTrack(vol, trk, inn) { + var out = new Uint8Array(TRACK_SIZE); + var out_pos = 0; + for (var sector = 0; sector < 16; sector++) { + nibblizeSector(vol, trk, sector, + inn, skewing_table[sector] << 8, + out, out_pos); + out_pos += SECTOR_SIZE; + } + while (out_pos < TRACK_SIZE) + out[out_pos++] = (0xff); + return out; +}