diff --git a/presets/apple2/applesinglebin.a b/presets/apple2/applesinglebin.a new file mode 100644 index 000000000..379d4233a --- /dev/null +++ b/presets/apple2/applesinglebin.a @@ -0,0 +1,146 @@ +; -------------------------------------------------- +; Example showing how to create an AppleSingle binary +; header and program that is automatically loaded +; into the desired PGM_START memory address. +; -------------------------------------------------- + + processor 6502 + +; -------------------------------------------------- +; Set PGM_START to desired start of program execution +; - PGM_START must be less than $C000 +; - PGM_START + SIZE must be less than $13000 +; -------------------------------------------------- +PGM_START equ $2000 +PGM_LENGTH equ PGM_END - PGM_START + + seg + org PGM_START - 58 ; Subtract 58 bytes for header + +; -------------------------------------------------- +; AppleSingle version 2 file header +; -------------------------------------------------- +; - https://github.com/cc65/cc65/blob/master/libsrc/apple2/exehdr.s +; - https://nulib.com/library/FTN.e00001.htm +; - https://www.kreativekorp.com/miscpages/a2info/filetypes.shtml +__HEADER_BEGIN + .byte $00, $05, $16, $00 ; Magic number + .byte $00, $02, $00, $00 ; Version number + + ; Home File System - 16 bytes + ; ProDOS + .byte $50,$72,$6F,$44,$4F,$53,$20,$20 + .byte $20,$20,$20,$20,$20,$20,$20,$20 + + .byte 0, 2 ; Number of entries + .byte 0, 0, 0, 1 ; Entry ID 1 - Data Fork + .byte 0, 0, >__DATA_OFFSET, <__DATA_OFFSET ; Offset + .byte 0, 0, >PGM_LENGTH, __PRODOS_OFFSET, <__PRODOS_OFFSET ; Offset + .byte 0, 0, >__PRODOS_LENGTH, <__PRODOS_LENGTH ; Length +__PRODOS_INFO + .byte 0, %11000011 ; Access - Destroy, Rename, Write, Read + .byte 0, 6 ; File Type - BIN + .byte 0, 0 ; Auxiliary Type high + .byte >PGM_START, PGM_START + lsr + lsr + lsr + lsr + tax + lda hexchar,x + ora #$80 + sta $040b + + ; High byte low nibble + lda #>PGM_START + and #$0F + tax + lda hexchar,x + ora #$80 + sta $040c + + ; Low byte high nibble + lda #start + lda #>PGM_START lsr lsr lsr @@ -65,18 +65,18 @@ pfx_loop tax lda hexchar,x ora #$80 - sta $0407 + sta $040b ; High byte low nibble - lda #>start + lda #>PGM_START and #$0F tax lda hexchar,x ora #$80 - sta $0408 + sta $040c ; Low byte high nibble - lda # extends BaseDebugPl } } - loadROM(title, data) { - this.machine.loadROM(data, title); + loadROM(title, data, origin?: number) { + this.machine.loadROM(data, title, origin); this.reset(); } diff --git a/src/common/devices.ts b/src/common/devices.ts index d22848fe8..70485c44c 100644 --- a/src/common/devices.ts +++ b/src/common/devices.ts @@ -68,7 +68,7 @@ export interface SampledAudioSource { } export interface AcceptsROM { - loadROM(data: Uint8Array, title?: string): void; + loadROM(data: Uint8Array, title?: string, origin?: number): void; } export interface AcceptsBIOS { @@ -247,7 +247,7 @@ export abstract class BasicHeadlessMachine implements HasCPU, Bus, AcceptsROM, P reset() { this.cpu.reset(); } - loadROM(data: Uint8Array, title?: string): void { + loadROM(data: Uint8Array, title?: string, origin?: number): void { if (!this.rom) this.rom = new Uint8Array(this.defaultROMSize); if (data.length > this.rom.length) throw new Error(`ROM too big: ${data.length} > ${this.rom.length}`); diff --git a/src/common/workertypes.ts b/src/common/workertypes.ts index 8950af995..a7c5b78e3 100644 --- a/src/common/workertypes.ts +++ b/src/common/workertypes.ts @@ -147,6 +147,7 @@ export interface WorkerOutputResult { params?: {} segments?: Segment[] debuginfo?: {} // optional info + origin?: number } export function isUnchanged(result: WorkerResult) : result is WorkerUnchangedResult { diff --git a/src/ide/ui.ts b/src/ide/ui.ts index c78f96aa1..313029a30 100644 --- a/src/ide/ui.ts +++ b/src/ide/ui.ts @@ -931,7 +931,7 @@ async function setCompileOutput(data: WorkerResult) { try { clearBreakpoint(); // so we can replace memory (TODO: change toolbar btn) _resetRecording(); - await platform.loadROM(getCurrentPresetTitle(), rom); + await platform.loadROM(getCurrentPresetTitle(), rom, data.origin); current_output = rom; if (!userPaused) _resume(); writeOutputROMFile(); diff --git a/src/machine/apple2.ts b/src/machine/apple2.ts index c8c96a959..730dd19a9 100644 --- a/src/machine/apple2.ts +++ b/src/machine/apple2.ts @@ -5,168 +5,168 @@ 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... - - // 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) => { } - }; - - 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> 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 < 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) { + } + loadROM(data, title?, origin?: number) { // is it a 16-sector 35-track disk image? if (data.length == 16 * 35 * 256) { var diskii = new DiskII(this, data); @@ -181,17 +181,17 @@ export class AppleII extends BasicScanlineMachine implements AcceptsBIOS { this.HDR_SIZE = 58; } else { // 4-byte DOS header? (TODO: hacky detection) - const origin = this.rom[0] | (this.rom[1] << 8); + const hdrOrigin = this.rom[0] | (this.rom[1] << 8); const size = this.rom[2] | (this.rom[3] << 8); - let isPlausible = origin < 0xc000 - && origin + size < 0x13000 - && (origin == 0x803 || (origin & 0xff) == 0); + let isPlausible = hdrOrigin < 0xc000 + && hdrOrigin + size < 0x13000 + && (hdrOrigin == 0x803 || (hdrOrigin & 0xff) == 0); if (size == data.length - 4 && isPlausible) { - this.LOAD_BASE = origin; + this.LOAD_BASE = hdrOrigin; this.HDR_SIZE = 4; } else { - // default = raw binary @ $803 - this.LOAD_BASE = 0x803; + // Load @ specified origin, fallback to $803 + this.LOAD_BASE = origin ?? 0x803; this.HDR_SIZE = 0; } } @@ -199,7 +199,7 @@ export class AppleII extends BasicScanlineMachine implements AcceptsBIOS { } } loadRAMWithProgram() { - console.log(`Loading program into Apple ][ RAM at \$${this.LOAD_BASE.toString(16)}`); + console.log(`Loading program into Apple ][ RAM at \$${this.LOAD_BASE.toString(16)} (${this.HDR_SIZE} bytes skipped for header)`); // truncate if needed to fit into RAM const exedata = this.rom.slice(this.HDR_SIZE, this.HDR_SIZE + this.ram.length - this.LOAD_BASE); this.ram.set(exedata, this.LOAD_BASE); @@ -209,27 +209,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 +247,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; + 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; + 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; + } + 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(); + } + + 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; + } + } + } + + 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; - case 3: - this.soundstate = this.soundstate ^ 1; + // Select ROM, write enable aux RAM bank 2. + case 0x1: + case 0x5: + this.auxRAMselected = false; + this.auxRAMbank = 2; + this.writeinhibit = false; 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)); - } + // 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; - 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; - 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; - } - 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; + this.setupLanguageCardConstants(); + return this.floatbus(); + } + + setupLanguageCardConstants() { + // reset language card constants + if (this.auxRAMbank == 2) + this.bank2rdoffset = -0x1000; // map 0xd000-0xdfff -> 0xc000-0xcfff 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(); - } - - 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; + 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"; } - } - } - - 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(); - } - - 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"; - } - } + } } -const GR_TXMODE = 1; -const GR_MIXMODE = 2; -const GR_PAGE1 = 4; -const GR_HIRES = 8; - -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 oldgrmode = -1; - var textbuf = new Array(40*24); - - 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, +const GR_TXMODE = 1; +const GR_MIXMODE = 2; +const GR_PAGE1 = 4; +const GR_HIRES = 8; + +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 oldgrmode = -1; + var textbuf = new Array(40 * 24); + + 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) ]; - 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; - - // 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; - } - } - - 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 { + 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, + ]; + + var colors_lut; + + /** + * 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; + + // 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++; + } + } + } + } + + 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; + } + } + + 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 +693,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 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; - } - } - } - - 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> 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 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; + + // 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 < apple.dirty.length; i++) + apple.dirty[i] = true; + } + + // first, draw top part of window + if ((apple.grswitch & GR_TXMODE) != 0) { + for (y = 0; y < 20; y++) + drawTextLine(y, flash); + } else { + if ((apple.grswitch & GR_HIRES) != 0) + drawHiresLines(0, 160); + else + for (y = 0; y < 20; y++) + drawLoresLine(y); + } + + // now do mixed part of window + if ((apple.grswitch & GR_TXMODE) != 0 || (apple.grswitch & GR_MIXMODE) != 0) { + for (y = 20; y < 24; y++) + drawTextLine(y, flash); + } else { + if ((apple.grswitch & GR_HIRES) != 0) + drawHiresLines(160, 192); + else + for (y = 20; y < 24; y++) + drawLoresLine(y); + } + for (var i = 0; i < apple.dirty.length; i++) + apple.dirty[i] = false; + } + + this.invalidate = function () { + oldgrmode = -1; + } } /*exported apple2_charset */ const apple2_charset = [ - 0x00,0x1c,0x22,0x2a,0x2e,0x2c,0x20,0x1e, - 0x00,0x08,0x14,0x22,0x22,0x3e,0x22,0x22, - 0x00,0x3c,0x22,0x22,0x3c,0x22,0x22,0x3c, - 0x00,0x1c,0x22,0x20,0x20,0x20,0x22,0x1c, - 0x00,0x3c,0x22,0x22,0x22,0x22,0x22,0x3c, - 0x00,0x3e,0x20,0x20,0x3c,0x20,0x20,0x3e, - 0x00,0x3e,0x20,0x20,0x3c,0x20,0x20,0x20, - 0x00,0x1e,0x20,0x20,0x20,0x26,0x22,0x1e, - 0x00,0x22,0x22,0x22,0x3e,0x22,0x22,0x22, - 0x00,0x1c,0x08,0x08,0x08,0x08,0x08,0x1c, - 0x00,0x02,0x02,0x02,0x02,0x02,0x22,0x1c, - 0x00,0x22,0x24,0x28,0x30,0x28,0x24,0x22, - 0x00,0x20,0x20,0x20,0x20,0x20,0x20,0x3e, - 0x00,0x22,0x36,0x2a,0x2a,0x22,0x22,0x22, - 0x00,0x22,0x22,0x32,0x2a,0x26,0x22,0x22, - 0x00,0x1c,0x22,0x22,0x22,0x22,0x22,0x1c, - 0x00,0x3c,0x22,0x22,0x3c,0x20,0x20,0x20, - 0x00,0x1c,0x22,0x22,0x22,0x2a,0x24,0x1a, - 0x00,0x3c,0x22,0x22,0x3c,0x28,0x24,0x22, - 0x00,0x1c,0x22,0x20,0x1c,0x02,0x22,0x1c, - 0x00,0x3e,0x08,0x08,0x08,0x08,0x08,0x08, - 0x00,0x22,0x22,0x22,0x22,0x22,0x22,0x1c, - 0x00,0x22,0x22,0x22,0x22,0x22,0x14,0x08, - 0x00,0x22,0x22,0x22,0x2a,0x2a,0x36,0x22, - 0x00,0x22,0x22,0x14,0x08,0x14,0x22,0x22, - 0x00,0x22,0x22,0x14,0x08,0x08,0x08,0x08, - 0x00,0x3e,0x02,0x04,0x08,0x10,0x20,0x3e, - 0x00,0x3e,0x30,0x30,0x30,0x30,0x30,0x3e, - 0x00,0x00,0x20,0x10,0x08,0x04,0x02,0x00, - 0x00,0x3e,0x06,0x06,0x06,0x06,0x06,0x3e, - 0x00,0x00,0x00,0x08,0x14,0x22,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3e, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x08,0x08,0x08,0x08,0x08,0x00,0x08, - 0x00,0x14,0x14,0x14,0x00,0x00,0x00,0x00, - 0x00,0x14,0x14,0x3e,0x14,0x3e,0x14,0x14, - 0x00,0x08,0x1e,0x28,0x1c,0x0a,0x3c,0x08, - 0x00,0x30,0x32,0x04,0x08,0x10,0x26,0x06, - 0x00,0x10,0x28,0x28,0x10,0x2a,0x24,0x1a, - 0x00,0x08,0x08,0x08,0x00,0x00,0x00,0x00, - 0x00,0x08,0x10,0x20,0x20,0x20,0x10,0x08, - 0x00,0x08,0x04,0x02,0x02,0x02,0x04,0x08, - 0x00,0x08,0x2a,0x1c,0x08,0x1c,0x2a,0x08, - 0x00,0x00,0x08,0x08,0x3e,0x08,0x08,0x00, - 0x00,0x00,0x00,0x00,0x00,0x08,0x08,0x10, - 0x00,0x00,0x00,0x00,0x3e,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08, - 0x00,0x00,0x02,0x04,0x08,0x10,0x20,0x00, - 0x00,0x1c,0x22,0x26,0x2a,0x32,0x22,0x1c, - 0x00,0x08,0x18,0x08,0x08,0x08,0x08,0x1c, - 0x00,0x1c,0x22,0x02,0x0c,0x10,0x20,0x3e, - 0x00,0x3e,0x02,0x04,0x0c,0x02,0x22,0x1c, - 0x00,0x04,0x0c,0x14,0x24,0x3e,0x04,0x04, - 0x00,0x3e,0x20,0x3c,0x02,0x02,0x22,0x1c, - 0x00,0x0e,0x10,0x20,0x3c,0x22,0x22,0x1c, - 0x00,0x3e,0x02,0x04,0x08,0x10,0x10,0x10, - 0x00,0x1c,0x22,0x22,0x1c,0x22,0x22,0x1c, - 0x00,0x1c,0x22,0x22,0x1e,0x02,0x04,0x38, - 0x00,0x00,0x00,0x08,0x00,0x08,0x00,0x00, - 0x00,0x00,0x00,0x08,0x00,0x08,0x08,0x10, - 0x00,0x04,0x08,0x10,0x20,0x10,0x08,0x04, - 0x00,0x00,0x00,0x3e,0x00,0x3e,0x00,0x00, - 0x00,0x10,0x08,0x04,0x02,0x04,0x08,0x10, - 0x00,0x1c,0x22,0x04,0x08,0x08,0x00,0x08, - 0x80,0x9c,0xa2,0xaa,0xae,0xac,0xa0,0x9e, - 0x80,0x88,0x94,0xa2,0xa2,0xbe,0xa2,0xa2, - 0x80,0xbc,0xa2,0xa2,0xbc,0xa2,0xa2,0xbc, - 0x80,0x9c,0xa2,0xa0,0xa0,0xa0,0xa2,0x9c, - 0x80,0xbc,0xa2,0xa2,0xa2,0xa2,0xa2,0xbc, - 0x80,0xbe,0xa0,0xa0,0xbc,0xa0,0xa0,0xbe, - 0x80,0xbe,0xa0,0xa0,0xbc,0xa0,0xa0,0xa0, - 0x80,0x9e,0xa0,0xa0,0xa0,0xa6,0xa2,0x9e, - 0x80,0xa2,0xa2,0xa2,0xbe,0xa2,0xa2,0xa2, - 0x80,0x9c,0x88,0x88,0x88,0x88,0x88,0x9c, - 0x80,0x82,0x82,0x82,0x82,0x82,0xa2,0x9c, - 0x80,0xa2,0xa4,0xa8,0xb0,0xa8,0xa4,0xa2, - 0x80,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xbe, - 0x80,0xa2,0xb6,0xaa,0xaa,0xa2,0xa2,0xa2, - 0x80,0xa2,0xa2,0xb2,0xaa,0xa6,0xa2,0xa2, - 0x80,0x9c,0xa2,0xa2,0xa2,0xa2,0xa2,0x9c, - 0x80,0xbc,0xa2,0xa2,0xbc,0xa0,0xa0,0xa0, - 0x80,0x9c,0xa2,0xa2,0xa2,0xaa,0xa4,0x9a, - 0x80,0xbc,0xa2,0xa2,0xbc,0xa8,0xa4,0xa2, - 0x80,0x9c,0xa2,0xa0,0x9c,0x82,0xa2,0x9c, - 0x80,0xbe,0x88,0x88,0x88,0x88,0x88,0x88, - 0x80,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0x9c, - 0x80,0xa2,0xa2,0xa2,0xa2,0xa2,0x94,0x88, - 0x80,0xa2,0xa2,0xa2,0xaa,0xaa,0xb6,0xa2, - 0x80,0xa2,0xa2,0x94,0x88,0x94,0xa2,0xa2, - 0x80,0xa2,0xa2,0x94,0x88,0x88,0x88,0x88, - 0x80,0xbe,0x82,0x84,0x88,0x90,0xa0,0xbe, - 0x80,0xbe,0xb0,0xb0,0xb0,0xb0,0xb0,0xbe, - 0x80,0x80,0xa0,0x90,0x88,0x84,0x82,0x80, - 0x80,0xbe,0x86,0x86,0x86,0x86,0x86,0xbe, - 0x80,0x80,0x80,0x88,0x94,0xa2,0x80,0x80, - 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0xbe, - 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80, - 0x80,0x88,0x88,0x88,0x88,0x88,0x80,0x88, - 0x80,0x94,0x94,0x94,0x80,0x80,0x80,0x80, - 0x80,0x94,0x94,0xbe,0x94,0xbe,0x94,0x94, - 0x80,0x88,0x9e,0xa8,0x9c,0x8a,0xbc,0x88, - 0x80,0xb0,0xb2,0x84,0x88,0x90,0xa6,0x86, - 0x80,0x90,0xa8,0xa8,0x90,0xaa,0xa4,0x9a, - 0x80,0x88,0x88,0x88,0x80,0x80,0x80,0x80, - 0x80,0x88,0x90,0xa0,0xa0,0xa0,0x90,0x88, - 0x80,0x88,0x84,0x82,0x82,0x82,0x84,0x88, - 0x80,0x88,0xaa,0x9c,0x88,0x9c,0xaa,0x88, - 0x80,0x80,0x88,0x88,0xbe,0x88,0x88,0x80, - 0x80,0x80,0x80,0x80,0x80,0x88,0x88,0x90, - 0x80,0x80,0x80,0x80,0xbe,0x80,0x80,0x80, - 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x88, - 0x80,0x80,0x82,0x84,0x88,0x90,0xa0,0x80, - 0x80,0x9c,0xa2,0xa6,0xaa,0xb2,0xa2,0x9c, - 0x80,0x88,0x98,0x88,0x88,0x88,0x88,0x9c, - 0x80,0x9c,0xa2,0x82,0x8c,0x90,0xa0,0xbe, - 0x80,0xbe,0x82,0x84,0x8c,0x82,0xa2,0x9c, - 0x80,0x84,0x8c,0x94,0xa4,0xbe,0x84,0x84, - 0x80,0xbe,0xa0,0xbc,0x82,0x82,0xa2,0x9c, - 0x80,0x8e,0x90,0xa0,0xbc,0xa2,0xa2,0x9c, - 0x80,0xbe,0x82,0x84,0x88,0x90,0x90,0x90, - 0x80,0x9c,0xa2,0xa2,0x9c,0xa2,0xa2,0x9c, - 0x80,0x9c,0xa2,0xa2,0x9e,0x82,0x84,0xb8, - 0x80,0x80,0x80,0x88,0x80,0x88,0x80,0x80, - 0x80,0x80,0x80,0x88,0x80,0x88,0x88,0x90, - 0x80,0x84,0x88,0x90,0xa0,0x90,0x88,0x84, - 0x80,0x80,0x80,0xbe,0x80,0xbe,0x80,0x80, - 0x80,0x90,0x88,0x84,0x82,0x84,0x88,0x90, - 0x80,0x9c,0xa2,0x84,0x88,0x88,0x80,0x88, - 0x00,0x1c,0x22,0x2a,0x2e,0x2c,0x20,0x1e, - 0x00,0x08,0x14,0x22,0x22,0x3e,0x22,0x22, - 0x00,0x3c,0x22,0x22,0x3c,0x22,0x22,0x3c, - 0x00,0x1c,0x22,0x20,0x20,0x20,0x22,0x1c, - 0x00,0x3c,0x22,0x22,0x22,0x22,0x22,0x3c, - 0x00,0x3e,0x20,0x20,0x3c,0x20,0x20,0x3e, - 0x00,0x3e,0x20,0x20,0x3c,0x20,0x20,0x20, - 0x00,0x1e,0x20,0x20,0x20,0x26,0x22,0x1e, - 0x00,0x22,0x22,0x22,0x3e,0x22,0x22,0x22, - 0x00,0x1c,0x08,0x08,0x08,0x08,0x08,0x1c, - 0x00,0x02,0x02,0x02,0x02,0x02,0x22,0x1c, - 0x00,0x22,0x24,0x28,0x30,0x28,0x24,0x22, - 0x00,0x20,0x20,0x20,0x20,0x20,0x20,0x3e, - 0x00,0x22,0x36,0x2a,0x2a,0x22,0x22,0x22, - 0x00,0x22,0x22,0x32,0x2a,0x26,0x22,0x22, - 0x00,0x1c,0x22,0x22,0x22,0x22,0x22,0x1c, - 0x00,0x3c,0x22,0x22,0x3c,0x20,0x20,0x20, - 0x00,0x1c,0x22,0x22,0x22,0x2a,0x24,0x1a, - 0x00,0x3c,0x22,0x22,0x3c,0x28,0x24,0x22, - 0x00,0x1c,0x22,0x20,0x1c,0x02,0x22,0x1c, - 0x00,0x3e,0x08,0x08,0x08,0x08,0x08,0x08, - 0x00,0x22,0x22,0x22,0x22,0x22,0x22,0x1c, - 0x00,0x22,0x22,0x22,0x22,0x22,0x14,0x08, - 0x00,0x22,0x22,0x22,0x2a,0x2a,0x36,0x22, - 0x00,0x22,0x22,0x14,0x08,0x14,0x22,0x22, - 0x00,0x22,0x22,0x14,0x08,0x08,0x08,0x08, - 0x00,0x3e,0x02,0x04,0x08,0x10,0x20,0x3e, - 0x00,0x3e,0x30,0x30,0x30,0x30,0x30,0x3e, - 0x00,0x00,0x20,0x10,0x08,0x04,0x02,0x00, - 0x00,0x3e,0x06,0x06,0x06,0x06,0x06,0x3e, - 0x00,0x00,0x00,0x08,0x14,0x22,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3e, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x08,0x08,0x08,0x08,0x08,0x00,0x08, - 0x00,0x14,0x14,0x14,0x00,0x00,0x00,0x00, - 0x00,0x14,0x14,0x3e,0x14,0x3e,0x14,0x14, - 0x00,0x08,0x1e,0x28,0x1c,0x0a,0x3c,0x08, - 0x00,0x30,0x32,0x04,0x08,0x10,0x26,0x06, - 0x00,0x10,0x28,0x28,0x10,0x2a,0x24,0x1a, - 0x00,0x08,0x08,0x08,0x00,0x00,0x00,0x00, - 0x00,0x08,0x10,0x20,0x20,0x20,0x10,0x08, - 0x00,0x08,0x04,0x02,0x02,0x02,0x04,0x08, - 0x00,0x08,0x2a,0x1c,0x08,0x1c,0x2a,0x08, - 0x00,0x00,0x08,0x08,0x3e,0x08,0x08,0x00, - 0x00,0x00,0x00,0x00,0x00,0x08,0x08,0x10, - 0x00,0x00,0x00,0x00,0x3e,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08, - 0x00,0x00,0x02,0x04,0x08,0x10,0x20,0x00, - 0x00,0x1c,0x22,0x26,0x2a,0x32,0x22,0x1c, - 0x00,0x08,0x18,0x08,0x08,0x08,0x08,0x1c, - 0x00,0x1c,0x22,0x02,0x0c,0x10,0x20,0x3e, - 0x00,0x3e,0x02,0x04,0x0c,0x02,0x22,0x1c, - 0x00,0x04,0x0c,0x14,0x24,0x3e,0x04,0x04, - 0x00,0x3e,0x20,0x3c,0x02,0x02,0x22,0x1c, - 0x00,0x0e,0x10,0x20,0x3c,0x22,0x22,0x1c, - 0x00,0x3e,0x02,0x04,0x08,0x10,0x10,0x10, - 0x00,0x1c,0x22,0x22,0x1c,0x22,0x22,0x1c, - 0x00,0x1c,0x22,0x22,0x1e,0x02,0x04,0x38, - 0x00,0x00,0x00,0x08,0x00,0x08,0x00,0x00, - 0x00,0x00,0x00,0x08,0x00,0x08,0x08,0x10, - 0x00,0x04,0x08,0x10,0x20,0x10,0x08,0x04, - 0x00,0x00,0x00,0x3e,0x00,0x3e,0x00,0x00, - 0x00,0x10,0x08,0x04,0x02,0x04,0x08,0x10, - 0x00,0x1c,0x22,0x04,0x08,0x08,0x00,0x08, - 0x80,0x9c,0xa2,0xaa,0xae,0xac,0xa0,0x9e, - 0x80,0x88,0x94,0xa2,0xa2,0xbe,0xa2,0xa2, - 0x80,0xbc,0xa2,0xa2,0xbc,0xa2,0xa2,0xbc, - 0x80,0x9c,0xa2,0xa0,0xa0,0xa0,0xa2,0x9c, - 0x80,0xbc,0xa2,0xa2,0xa2,0xa2,0xa2,0xbc, - 0x80,0xbe,0xa0,0xa0,0xbc,0xa0,0xa0,0xbe, - 0x80,0xbe,0xa0,0xa0,0xbc,0xa0,0xa0,0xa0, - 0x80,0x9e,0xa0,0xa0,0xa0,0xa6,0xa2,0x9e, - 0x80,0xa2,0xa2,0xa2,0xbe,0xa2,0xa2,0xa2, - 0x80,0x9c,0x88,0x88,0x88,0x88,0x88,0x9c, - 0x80,0x82,0x82,0x82,0x82,0x82,0xa2,0x9c, - 0x80,0xa2,0xa4,0xa8,0xb0,0xa8,0xa4,0xa2, - 0x80,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xbe, - 0x80,0xa2,0xb6,0xaa,0xaa,0xa2,0xa2,0xa2, - 0x80,0xa2,0xa2,0xb2,0xaa,0xa6,0xa2,0xa2, - 0x80,0x9c,0xa2,0xa2,0xa2,0xa2,0xa2,0x9c, - 0x80,0xbc,0xa2,0xa2,0xbc,0xa0,0xa0,0xa0, - 0x80,0x9c,0xa2,0xa2,0xa2,0xaa,0xa4,0x9a, - 0x80,0xbc,0xa2,0xa2,0xbc,0xa8,0xa4,0xa2, - 0x80,0x9c,0xa2,0xa0,0x9c,0x82,0xa2,0x9c, - 0x80,0xbe,0x88,0x88,0x88,0x88,0x88,0x88, - 0x80,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0x9c, - 0x80,0xa2,0xa2,0xa2,0xa2,0xa2,0x94,0x88, - 0x80,0xa2,0xa2,0xa2,0xaa,0xaa,0xb6,0xa2, - 0x80,0xa2,0xa2,0x94,0x88,0x94,0xa2,0xa2, - 0x80,0xa2,0xa2,0x94,0x88,0x88,0x88,0x88, - 0x80,0xbe,0x82,0x84,0x88,0x90,0xa0,0xbe, - 0x80,0xbe,0xb0,0xb0,0xb0,0xb0,0xb0,0xbe, - 0x80,0x80,0xa0,0x90,0x88,0x84,0x82,0x80, - 0x80,0xbe,0x86,0x86,0x86,0x86,0x86,0xbe, - 0x80,0x80,0x80,0x88,0x94,0xa2,0x80,0x80, - 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0xbe, - 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80, - 0x80,0x88,0x88,0x88,0x88,0x88,0x80,0x88, - 0x80,0x94,0x94,0x94,0x80,0x80,0x80,0x80, - 0x80,0x94,0x94,0xbe,0x94,0xbe,0x94,0x94, - 0x80,0x88,0x9e,0xa8,0x9c,0x8a,0xbc,0x88, - 0x80,0xb0,0xb2,0x84,0x88,0x90,0xa6,0x86, - 0x80,0x90,0xa8,0xa8,0x90,0xaa,0xa4,0x9a, - 0x80,0x88,0x88,0x88,0x80,0x80,0x80,0x80, - 0x80,0x88,0x90,0xa0,0xa0,0xa0,0x90,0x88, - 0x80,0x88,0x84,0x82,0x82,0x82,0x84,0x88, - 0x80,0x88,0xaa,0x9c,0x88,0x9c,0xaa,0x88, - 0x80,0x80,0x88,0x88,0xbe,0x88,0x88,0x80, - 0x80,0x80,0x80,0x80,0x80,0x88,0x88,0x90, - 0x80,0x80,0x80,0x80,0xbe,0x80,0x80,0x80, - 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x88, - 0x80,0x80,0x82,0x84,0x88,0x90,0xa0,0x80, - 0x80,0x9c,0xa2,0xa6,0xaa,0xb2,0xa2,0x9c, - 0x80,0x88,0x98,0x88,0x88,0x88,0x88,0x9c, - 0x80,0x9c,0xa2,0x82,0x8c,0x90,0xa0,0xbe, - 0x80,0xbe,0x82,0x84,0x8c,0x82,0xa2,0x9c, - 0x80,0x84,0x8c,0x94,0xa4,0xbe,0x84,0x84, - 0x80,0xbe,0xa0,0xbc,0x82,0x82,0xa2,0x9c, - 0x80,0x8e,0x90,0xa0,0xbc,0xa2,0xa2,0x9c, - 0x80,0xbe,0x82,0x84,0x88,0x90,0x90,0x90, - 0x80,0x9c,0xa2,0xa2,0x9c,0xa2,0xa2,0x9c, - 0x80,0x9c,0xa2,0xa2,0x9e,0x82,0x84,0xb8, - 0x80,0x80,0x80,0x88,0x80,0x88,0x80,0x80, - 0x80,0x80,0x80,0x88,0x80,0x88,0x88,0x90, - 0x80,0x84,0x88,0x90,0xa0,0x90,0x88,0x84, - 0x80,0x80,0x80,0xbe,0x80,0xbe,0x80,0x80, - 0x80,0x90,0x88,0x84,0x82,0x84,0x88,0x90, - 0x80,0x9c,0xa2,0x84,0x88,0x88,0x80,0x88 + 0x00, 0x1c, 0x22, 0x2a, 0x2e, 0x2c, 0x20, 0x1e, + 0x00, 0x08, 0x14, 0x22, 0x22, 0x3e, 0x22, 0x22, + 0x00, 0x3c, 0x22, 0x22, 0x3c, 0x22, 0x22, 0x3c, + 0x00, 0x1c, 0x22, 0x20, 0x20, 0x20, 0x22, 0x1c, + 0x00, 0x3c, 0x22, 0x22, 0x22, 0x22, 0x22, 0x3c, + 0x00, 0x3e, 0x20, 0x20, 0x3c, 0x20, 0x20, 0x3e, + 0x00, 0x3e, 0x20, 0x20, 0x3c, 0x20, 0x20, 0x20, + 0x00, 0x1e, 0x20, 0x20, 0x20, 0x26, 0x22, 0x1e, + 0x00, 0x22, 0x22, 0x22, 0x3e, 0x22, 0x22, 0x22, + 0x00, 0x1c, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1c, + 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x22, 0x1c, + 0x00, 0x22, 0x24, 0x28, 0x30, 0x28, 0x24, 0x22, + 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3e, + 0x00, 0x22, 0x36, 0x2a, 0x2a, 0x22, 0x22, 0x22, + 0x00, 0x22, 0x22, 0x32, 0x2a, 0x26, 0x22, 0x22, + 0x00, 0x1c, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c, + 0x00, 0x3c, 0x22, 0x22, 0x3c, 0x20, 0x20, 0x20, + 0x00, 0x1c, 0x22, 0x22, 0x22, 0x2a, 0x24, 0x1a, + 0x00, 0x3c, 0x22, 0x22, 0x3c, 0x28, 0x24, 0x22, + 0x00, 0x1c, 0x22, 0x20, 0x1c, 0x02, 0x22, 0x1c, + 0x00, 0x3e, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x00, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c, + 0x00, 0x22, 0x22, 0x22, 0x22, 0x22, 0x14, 0x08, + 0x00, 0x22, 0x22, 0x22, 0x2a, 0x2a, 0x36, 0x22, + 0x00, 0x22, 0x22, 0x14, 0x08, 0x14, 0x22, 0x22, + 0x00, 0x22, 0x22, 0x14, 0x08, 0x08, 0x08, 0x08, + 0x00, 0x3e, 0x02, 0x04, 0x08, 0x10, 0x20, 0x3e, + 0x00, 0x3e, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3e, + 0x00, 0x00, 0x20, 0x10, 0x08, 0x04, 0x02, 0x00, + 0x00, 0x3e, 0x06, 0x06, 0x06, 0x06, 0x06, 0x3e, + 0x00, 0x00, 0x00, 0x08, 0x14, 0x22, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x08, + 0x00, 0x14, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x14, 0x14, 0x3e, 0x14, 0x3e, 0x14, 0x14, + 0x00, 0x08, 0x1e, 0x28, 0x1c, 0x0a, 0x3c, 0x08, + 0x00, 0x30, 0x32, 0x04, 0x08, 0x10, 0x26, 0x06, + 0x00, 0x10, 0x28, 0x28, 0x10, 0x2a, 0x24, 0x1a, + 0x00, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x10, 0x20, 0x20, 0x20, 0x10, 0x08, + 0x00, 0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08, + 0x00, 0x08, 0x2a, 0x1c, 0x08, 0x1c, 0x2a, 0x08, + 0x00, 0x00, 0x08, 0x08, 0x3e, 0x08, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, + 0x00, 0x1c, 0x22, 0x26, 0x2a, 0x32, 0x22, 0x1c, + 0x00, 0x08, 0x18, 0x08, 0x08, 0x08, 0x08, 0x1c, + 0x00, 0x1c, 0x22, 0x02, 0x0c, 0x10, 0x20, 0x3e, + 0x00, 0x3e, 0x02, 0x04, 0x0c, 0x02, 0x22, 0x1c, + 0x00, 0x04, 0x0c, 0x14, 0x24, 0x3e, 0x04, 0x04, + 0x00, 0x3e, 0x20, 0x3c, 0x02, 0x02, 0x22, 0x1c, + 0x00, 0x0e, 0x10, 0x20, 0x3c, 0x22, 0x22, 0x1c, + 0x00, 0x3e, 0x02, 0x04, 0x08, 0x10, 0x10, 0x10, + 0x00, 0x1c, 0x22, 0x22, 0x1c, 0x22, 0x22, 0x1c, + 0x00, 0x1c, 0x22, 0x22, 0x1e, 0x02, 0x04, 0x38, + 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x08, 0x10, + 0x00, 0x04, 0x08, 0x10, 0x20, 0x10, 0x08, 0x04, + 0x00, 0x00, 0x00, 0x3e, 0x00, 0x3e, 0x00, 0x00, + 0x00, 0x10, 0x08, 0x04, 0x02, 0x04, 0x08, 0x10, + 0x00, 0x1c, 0x22, 0x04, 0x08, 0x08, 0x00, 0x08, + 0x80, 0x9c, 0xa2, 0xaa, 0xae, 0xac, 0xa0, 0x9e, + 0x80, 0x88, 0x94, 0xa2, 0xa2, 0xbe, 0xa2, 0xa2, + 0x80, 0xbc, 0xa2, 0xa2, 0xbc, 0xa2, 0xa2, 0xbc, + 0x80, 0x9c, 0xa2, 0xa0, 0xa0, 0xa0, 0xa2, 0x9c, + 0x80, 0xbc, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xbc, + 0x80, 0xbe, 0xa0, 0xa0, 0xbc, 0xa0, 0xa0, 0xbe, + 0x80, 0xbe, 0xa0, 0xa0, 0xbc, 0xa0, 0xa0, 0xa0, + 0x80, 0x9e, 0xa0, 0xa0, 0xa0, 0xa6, 0xa2, 0x9e, + 0x80, 0xa2, 0xa2, 0xa2, 0xbe, 0xa2, 0xa2, 0xa2, + 0x80, 0x9c, 0x88, 0x88, 0x88, 0x88, 0x88, 0x9c, + 0x80, 0x82, 0x82, 0x82, 0x82, 0x82, 0xa2, 0x9c, + 0x80, 0xa2, 0xa4, 0xa8, 0xb0, 0xa8, 0xa4, 0xa2, + 0x80, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xbe, + 0x80, 0xa2, 0xb6, 0xaa, 0xaa, 0xa2, 0xa2, 0xa2, + 0x80, 0xa2, 0xa2, 0xb2, 0xaa, 0xa6, 0xa2, 0xa2, + 0x80, 0x9c, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0x9c, + 0x80, 0xbc, 0xa2, 0xa2, 0xbc, 0xa0, 0xa0, 0xa0, + 0x80, 0x9c, 0xa2, 0xa2, 0xa2, 0xaa, 0xa4, 0x9a, + 0x80, 0xbc, 0xa2, 0xa2, 0xbc, 0xa8, 0xa4, 0xa2, + 0x80, 0x9c, 0xa2, 0xa0, 0x9c, 0x82, 0xa2, 0x9c, + 0x80, 0xbe, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x80, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0x9c, + 0x80, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0x94, 0x88, + 0x80, 0xa2, 0xa2, 0xa2, 0xaa, 0xaa, 0xb6, 0xa2, + 0x80, 0xa2, 0xa2, 0x94, 0x88, 0x94, 0xa2, 0xa2, + 0x80, 0xa2, 0xa2, 0x94, 0x88, 0x88, 0x88, 0x88, + 0x80, 0xbe, 0x82, 0x84, 0x88, 0x90, 0xa0, 0xbe, + 0x80, 0xbe, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xbe, + 0x80, 0x80, 0xa0, 0x90, 0x88, 0x84, 0x82, 0x80, + 0x80, 0xbe, 0x86, 0x86, 0x86, 0x86, 0x86, 0xbe, + 0x80, 0x80, 0x80, 0x88, 0x94, 0xa2, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xbe, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x88, 0x88, 0x88, 0x88, 0x88, 0x80, 0x88, + 0x80, 0x94, 0x94, 0x94, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x94, 0x94, 0xbe, 0x94, 0xbe, 0x94, 0x94, + 0x80, 0x88, 0x9e, 0xa8, 0x9c, 0x8a, 0xbc, 0x88, + 0x80, 0xb0, 0xb2, 0x84, 0x88, 0x90, 0xa6, 0x86, + 0x80, 0x90, 0xa8, 0xa8, 0x90, 0xaa, 0xa4, 0x9a, + 0x80, 0x88, 0x88, 0x88, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x88, 0x90, 0xa0, 0xa0, 0xa0, 0x90, 0x88, + 0x80, 0x88, 0x84, 0x82, 0x82, 0x82, 0x84, 0x88, + 0x80, 0x88, 0xaa, 0x9c, 0x88, 0x9c, 0xaa, 0x88, + 0x80, 0x80, 0x88, 0x88, 0xbe, 0x88, 0x88, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x88, 0x88, 0x90, + 0x80, 0x80, 0x80, 0x80, 0xbe, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x88, + 0x80, 0x80, 0x82, 0x84, 0x88, 0x90, 0xa0, 0x80, + 0x80, 0x9c, 0xa2, 0xa6, 0xaa, 0xb2, 0xa2, 0x9c, + 0x80, 0x88, 0x98, 0x88, 0x88, 0x88, 0x88, 0x9c, + 0x80, 0x9c, 0xa2, 0x82, 0x8c, 0x90, 0xa0, 0xbe, + 0x80, 0xbe, 0x82, 0x84, 0x8c, 0x82, 0xa2, 0x9c, + 0x80, 0x84, 0x8c, 0x94, 0xa4, 0xbe, 0x84, 0x84, + 0x80, 0xbe, 0xa0, 0xbc, 0x82, 0x82, 0xa2, 0x9c, + 0x80, 0x8e, 0x90, 0xa0, 0xbc, 0xa2, 0xa2, 0x9c, + 0x80, 0xbe, 0x82, 0x84, 0x88, 0x90, 0x90, 0x90, + 0x80, 0x9c, 0xa2, 0xa2, 0x9c, 0xa2, 0xa2, 0x9c, + 0x80, 0x9c, 0xa2, 0xa2, 0x9e, 0x82, 0x84, 0xb8, + 0x80, 0x80, 0x80, 0x88, 0x80, 0x88, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x88, 0x80, 0x88, 0x88, 0x90, + 0x80, 0x84, 0x88, 0x90, 0xa0, 0x90, 0x88, 0x84, + 0x80, 0x80, 0x80, 0xbe, 0x80, 0xbe, 0x80, 0x80, + 0x80, 0x90, 0x88, 0x84, 0x82, 0x84, 0x88, 0x90, + 0x80, 0x9c, 0xa2, 0x84, 0x88, 0x88, 0x80, 0x88, + 0x00, 0x1c, 0x22, 0x2a, 0x2e, 0x2c, 0x20, 0x1e, + 0x00, 0x08, 0x14, 0x22, 0x22, 0x3e, 0x22, 0x22, + 0x00, 0x3c, 0x22, 0x22, 0x3c, 0x22, 0x22, 0x3c, + 0x00, 0x1c, 0x22, 0x20, 0x20, 0x20, 0x22, 0x1c, + 0x00, 0x3c, 0x22, 0x22, 0x22, 0x22, 0x22, 0x3c, + 0x00, 0x3e, 0x20, 0x20, 0x3c, 0x20, 0x20, 0x3e, + 0x00, 0x3e, 0x20, 0x20, 0x3c, 0x20, 0x20, 0x20, + 0x00, 0x1e, 0x20, 0x20, 0x20, 0x26, 0x22, 0x1e, + 0x00, 0x22, 0x22, 0x22, 0x3e, 0x22, 0x22, 0x22, + 0x00, 0x1c, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1c, + 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x22, 0x1c, + 0x00, 0x22, 0x24, 0x28, 0x30, 0x28, 0x24, 0x22, + 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3e, + 0x00, 0x22, 0x36, 0x2a, 0x2a, 0x22, 0x22, 0x22, + 0x00, 0x22, 0x22, 0x32, 0x2a, 0x26, 0x22, 0x22, + 0x00, 0x1c, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c, + 0x00, 0x3c, 0x22, 0x22, 0x3c, 0x20, 0x20, 0x20, + 0x00, 0x1c, 0x22, 0x22, 0x22, 0x2a, 0x24, 0x1a, + 0x00, 0x3c, 0x22, 0x22, 0x3c, 0x28, 0x24, 0x22, + 0x00, 0x1c, 0x22, 0x20, 0x1c, 0x02, 0x22, 0x1c, + 0x00, 0x3e, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x00, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c, + 0x00, 0x22, 0x22, 0x22, 0x22, 0x22, 0x14, 0x08, + 0x00, 0x22, 0x22, 0x22, 0x2a, 0x2a, 0x36, 0x22, + 0x00, 0x22, 0x22, 0x14, 0x08, 0x14, 0x22, 0x22, + 0x00, 0x22, 0x22, 0x14, 0x08, 0x08, 0x08, 0x08, + 0x00, 0x3e, 0x02, 0x04, 0x08, 0x10, 0x20, 0x3e, + 0x00, 0x3e, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3e, + 0x00, 0x00, 0x20, 0x10, 0x08, 0x04, 0x02, 0x00, + 0x00, 0x3e, 0x06, 0x06, 0x06, 0x06, 0x06, 0x3e, + 0x00, 0x00, 0x00, 0x08, 0x14, 0x22, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x08, + 0x00, 0x14, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x14, 0x14, 0x3e, 0x14, 0x3e, 0x14, 0x14, + 0x00, 0x08, 0x1e, 0x28, 0x1c, 0x0a, 0x3c, 0x08, + 0x00, 0x30, 0x32, 0x04, 0x08, 0x10, 0x26, 0x06, + 0x00, 0x10, 0x28, 0x28, 0x10, 0x2a, 0x24, 0x1a, + 0x00, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x10, 0x20, 0x20, 0x20, 0x10, 0x08, + 0x00, 0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08, + 0x00, 0x08, 0x2a, 0x1c, 0x08, 0x1c, 0x2a, 0x08, + 0x00, 0x00, 0x08, 0x08, 0x3e, 0x08, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, + 0x00, 0x1c, 0x22, 0x26, 0x2a, 0x32, 0x22, 0x1c, + 0x00, 0x08, 0x18, 0x08, 0x08, 0x08, 0x08, 0x1c, + 0x00, 0x1c, 0x22, 0x02, 0x0c, 0x10, 0x20, 0x3e, + 0x00, 0x3e, 0x02, 0x04, 0x0c, 0x02, 0x22, 0x1c, + 0x00, 0x04, 0x0c, 0x14, 0x24, 0x3e, 0x04, 0x04, + 0x00, 0x3e, 0x20, 0x3c, 0x02, 0x02, 0x22, 0x1c, + 0x00, 0x0e, 0x10, 0x20, 0x3c, 0x22, 0x22, 0x1c, + 0x00, 0x3e, 0x02, 0x04, 0x08, 0x10, 0x10, 0x10, + 0x00, 0x1c, 0x22, 0x22, 0x1c, 0x22, 0x22, 0x1c, + 0x00, 0x1c, 0x22, 0x22, 0x1e, 0x02, 0x04, 0x38, + 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x08, 0x10, + 0x00, 0x04, 0x08, 0x10, 0x20, 0x10, 0x08, 0x04, + 0x00, 0x00, 0x00, 0x3e, 0x00, 0x3e, 0x00, 0x00, + 0x00, 0x10, 0x08, 0x04, 0x02, 0x04, 0x08, 0x10, + 0x00, 0x1c, 0x22, 0x04, 0x08, 0x08, 0x00, 0x08, + 0x80, 0x9c, 0xa2, 0xaa, 0xae, 0xac, 0xa0, 0x9e, + 0x80, 0x88, 0x94, 0xa2, 0xa2, 0xbe, 0xa2, 0xa2, + 0x80, 0xbc, 0xa2, 0xa2, 0xbc, 0xa2, 0xa2, 0xbc, + 0x80, 0x9c, 0xa2, 0xa0, 0xa0, 0xa0, 0xa2, 0x9c, + 0x80, 0xbc, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xbc, + 0x80, 0xbe, 0xa0, 0xa0, 0xbc, 0xa0, 0xa0, 0xbe, + 0x80, 0xbe, 0xa0, 0xa0, 0xbc, 0xa0, 0xa0, 0xa0, + 0x80, 0x9e, 0xa0, 0xa0, 0xa0, 0xa6, 0xa2, 0x9e, + 0x80, 0xa2, 0xa2, 0xa2, 0xbe, 0xa2, 0xa2, 0xa2, + 0x80, 0x9c, 0x88, 0x88, 0x88, 0x88, 0x88, 0x9c, + 0x80, 0x82, 0x82, 0x82, 0x82, 0x82, 0xa2, 0x9c, + 0x80, 0xa2, 0xa4, 0xa8, 0xb0, 0xa8, 0xa4, 0xa2, + 0x80, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xbe, + 0x80, 0xa2, 0xb6, 0xaa, 0xaa, 0xa2, 0xa2, 0xa2, + 0x80, 0xa2, 0xa2, 0xb2, 0xaa, 0xa6, 0xa2, 0xa2, + 0x80, 0x9c, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0x9c, + 0x80, 0xbc, 0xa2, 0xa2, 0xbc, 0xa0, 0xa0, 0xa0, + 0x80, 0x9c, 0xa2, 0xa2, 0xa2, 0xaa, 0xa4, 0x9a, + 0x80, 0xbc, 0xa2, 0xa2, 0xbc, 0xa8, 0xa4, 0xa2, + 0x80, 0x9c, 0xa2, 0xa0, 0x9c, 0x82, 0xa2, 0x9c, + 0x80, 0xbe, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x80, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0x9c, + 0x80, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0x94, 0x88, + 0x80, 0xa2, 0xa2, 0xa2, 0xaa, 0xaa, 0xb6, 0xa2, + 0x80, 0xa2, 0xa2, 0x94, 0x88, 0x94, 0xa2, 0xa2, + 0x80, 0xa2, 0xa2, 0x94, 0x88, 0x88, 0x88, 0x88, + 0x80, 0xbe, 0x82, 0x84, 0x88, 0x90, 0xa0, 0xbe, + 0x80, 0xbe, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xbe, + 0x80, 0x80, 0xa0, 0x90, 0x88, 0x84, 0x82, 0x80, + 0x80, 0xbe, 0x86, 0x86, 0x86, 0x86, 0x86, 0xbe, + 0x80, 0x80, 0x80, 0x88, 0x94, 0xa2, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xbe, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x88, 0x88, 0x88, 0x88, 0x88, 0x80, 0x88, + 0x80, 0x94, 0x94, 0x94, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x94, 0x94, 0xbe, 0x94, 0xbe, 0x94, 0x94, + 0x80, 0x88, 0x9e, 0xa8, 0x9c, 0x8a, 0xbc, 0x88, + 0x80, 0xb0, 0xb2, 0x84, 0x88, 0x90, 0xa6, 0x86, + 0x80, 0x90, 0xa8, 0xa8, 0x90, 0xaa, 0xa4, 0x9a, + 0x80, 0x88, 0x88, 0x88, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x88, 0x90, 0xa0, 0xa0, 0xa0, 0x90, 0x88, + 0x80, 0x88, 0x84, 0x82, 0x82, 0x82, 0x84, 0x88, + 0x80, 0x88, 0xaa, 0x9c, 0x88, 0x9c, 0xaa, 0x88, + 0x80, 0x80, 0x88, 0x88, 0xbe, 0x88, 0x88, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x88, 0x88, 0x90, + 0x80, 0x80, 0x80, 0x80, 0xbe, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x88, + 0x80, 0x80, 0x82, 0x84, 0x88, 0x90, 0xa0, 0x80, + 0x80, 0x9c, 0xa2, 0xa6, 0xaa, 0xb2, 0xa2, 0x9c, + 0x80, 0x88, 0x98, 0x88, 0x88, 0x88, 0x88, 0x9c, + 0x80, 0x9c, 0xa2, 0x82, 0x8c, 0x90, 0xa0, 0xbe, + 0x80, 0xbe, 0x82, 0x84, 0x8c, 0x82, 0xa2, 0x9c, + 0x80, 0x84, 0x8c, 0x94, 0xa4, 0xbe, 0x84, 0x84, + 0x80, 0xbe, 0xa0, 0xbc, 0x82, 0x82, 0xa2, 0x9c, + 0x80, 0x8e, 0x90, 0xa0, 0xbc, 0xa2, 0xa2, 0x9c, + 0x80, 0xbe, 0x82, 0x84, 0x88, 0x90, 0x90, 0x90, + 0x80, 0x9c, 0xa2, 0xa2, 0x9c, 0xa2, 0xa2, 0x9c, + 0x80, 0x9c, 0xa2, 0xa2, 0x9e, 0x82, 0x84, 0xb8, + 0x80, 0x80, 0x80, 0x88, 0x80, 0x88, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x88, 0x80, 0x88, 0x88, 0x90, + 0x80, 0x84, 0x88, 0x90, 0xa0, 0x90, 0x88, 0x84, + 0x80, 0x80, 0x80, 0xbe, 0x80, 0xbe, 0x80, 0x80, + 0x80, 0x90, 0x88, 0x84, 0x82, 0x84, 0x88, 0x90, + 0x80, 0x9c, 0xa2, 0x84, 0x88, 0x88, 0x80, 0x88 ]; // public domain ROM (http://a2go.applearchives.com/roms/) @@ -1120,92 +1085,92 @@ const APPLEIIGO_LZG = `TFpHAAAwAAAABYxwdy2NARUZHjRBUFBMRUlJR08gUk9NMS4wADQfNB80H /// Disk II /// - const NUM_DRIVES = 2; - const NUM_TRACKS = 35; - const TRACK_SIZE = 0x1880; - const SECTOR_SIZE = 383; - - const DISKII_PROM = [ - 0xA2,0x20,0xA0,0x00,0xA2,0x03,0x86,0x3C,0x8A,0x0A,0x24,0x3C,0xF0,0x10,0x05,0x3C - ,0x49,0xFF,0x29,0x7E,0xB0,0x08,0x4A,0xD0,0xFB,0x98,0x9D,0x56,0x03,0xC8,0xE8,0x10 - ,0xE5,0x20,0x58,0xFF,0xBA,0xBD,0x00,0x01,0x0A,0x0A,0x0A,0x0A,0x85,0x2B,0xAA,0xBD - ,0x8E,0xC0,0xBD,0x8C,0xC0,0xBD,0x8A,0xC0,0xBD,0x89,0xC0,0xA0,0x50,0xBD,0x80,0xC0 - ,0x98,0x29,0x03,0x0A,0x05,0x2B,0xAA,0xBD,0x81,0xC0,0xA9,0x56, - /*0x20,0xA8,0xFC,*/0xa9,0x00,0xea,0x88 - ,0x10,0xEB,0x85,0x26,0x85,0x3D,0x85,0x41,0xA9,0x08,0x85,0x27,0x18,0x08,0xBD,0x8C - ,0xC0,0x10,0xFB,0x49,0xD5,0xD0,0xF7,0xBD,0x8C,0xC0,0x10,0xFB,0xC9,0xAA,0xD0,0xF3 - ,0xEA,0xBD,0x8C,0xC0,0x10,0xFB,0xC9,0x96,0xF0,0x09,0x28,0x90,0xDF,0x49,0xAD,0xF0 - ,0x25,0xD0,0xD9,0xA0,0x03,0x85,0x40,0xBD,0x8C,0xC0,0x10,0xFB,0x2A,0x85,0x3C,0xBD - ,0x8C,0xC0,0x10,0xFB,0x25,0x3C,0x88,0xD0,0xEC,0x28,0xC5,0x3D,0xD0,0xBE,0xA5,0x40 - ,0xC5,0x41,0xD0,0xB8,0xB0,0xB7,0xA0,0x56,0x84,0x3C,0xBC,0x8C,0xC0,0x10,0xFB,0x59 - ,0xD6,0x02,0xA4,0x3C,0x88,0x99,0x00,0x03,0xD0,0xEE,0x84,0x3C,0xBC,0x8C,0xC0,0x10 - ,0xFB,0x59,0xD6,0x02,0xA4,0x3C,0x91,0x26,0xC8,0xD0,0xEF,0xBC,0x8C,0xC0,0x10,0xFB - ,0x59,0xD6,0x02,0xD0,0x87,0xA0,0x00,0xA2,0x56,0xCA,0x30,0xFB,0xB1,0x26,0x5E,0x00 - ,0x03,0x2A,0x5E,0x00,0x03,0x2A,0x91,0x26,0xC8,0xD0,0xEE,0xE6,0x27,0xE6,0x3D,0xA5 - ,0x3D,0xCD,0x00,0x08,0xA6,0x2B,0x90,0xDB,0x4C,0x01,0x08,0x00,0x00,0x00,0x00,0x00 - ]; +const NUM_DRIVES = 2; +const NUM_TRACKS = 35; +const TRACK_SIZE = 0x1880; +const SECTOR_SIZE = 383; + +const DISKII_PROM = [ + 0xA2, 0x20, 0xA0, 0x00, 0xA2, 0x03, 0x86, 0x3C, 0x8A, 0x0A, 0x24, 0x3C, 0xF0, 0x10, 0x05, 0x3C + , 0x49, 0xFF, 0x29, 0x7E, 0xB0, 0x08, 0x4A, 0xD0, 0xFB, 0x98, 0x9D, 0x56, 0x03, 0xC8, 0xE8, 0x10 + , 0xE5, 0x20, 0x58, 0xFF, 0xBA, 0xBD, 0x00, 0x01, 0x0A, 0x0A, 0x0A, 0x0A, 0x85, 0x2B, 0xAA, 0xBD + , 0x8E, 0xC0, 0xBD, 0x8C, 0xC0, 0xBD, 0x8A, 0xC0, 0xBD, 0x89, 0xC0, 0xA0, 0x50, 0xBD, 0x80, 0xC0 + , 0x98, 0x29, 0x03, 0x0A, 0x05, 0x2B, 0xAA, 0xBD, 0x81, 0xC0, 0xA9, 0x56, + /*0x20,0xA8,0xFC,*/0xa9, 0x00, 0xea, 0x88 + , 0x10, 0xEB, 0x85, 0x26, 0x85, 0x3D, 0x85, 0x41, 0xA9, 0x08, 0x85, 0x27, 0x18, 0x08, 0xBD, 0x8C + , 0xC0, 0x10, 0xFB, 0x49, 0xD5, 0xD0, 0xF7, 0xBD, 0x8C, 0xC0, 0x10, 0xFB, 0xC9, 0xAA, 0xD0, 0xF3 + , 0xEA, 0xBD, 0x8C, 0xC0, 0x10, 0xFB, 0xC9, 0x96, 0xF0, 0x09, 0x28, 0x90, 0xDF, 0x49, 0xAD, 0xF0 + , 0x25, 0xD0, 0xD9, 0xA0, 0x03, 0x85, 0x40, 0xBD, 0x8C, 0xC0, 0x10, 0xFB, 0x2A, 0x85, 0x3C, 0xBD + , 0x8C, 0xC0, 0x10, 0xFB, 0x25, 0x3C, 0x88, 0xD0, 0xEC, 0x28, 0xC5, 0x3D, 0xD0, 0xBE, 0xA5, 0x40 + , 0xC5, 0x41, 0xD0, 0xB8, 0xB0, 0xB7, 0xA0, 0x56, 0x84, 0x3C, 0xBC, 0x8C, 0xC0, 0x10, 0xFB, 0x59 + , 0xD6, 0x02, 0xA4, 0x3C, 0x88, 0x99, 0x00, 0x03, 0xD0, 0xEE, 0x84, 0x3C, 0xBC, 0x8C, 0xC0, 0x10 + , 0xFB, 0x59, 0xD6, 0x02, 0xA4, 0x3C, 0x91, 0x26, 0xC8, 0xD0, 0xEF, 0xBC, 0x8C, 0xC0, 0x10, 0xFB + , 0x59, 0xD6, 0x02, 0xD0, 0x87, 0xA0, 0x00, 0xA2, 0x56, 0xCA, 0x30, 0xFB, 0xB1, 0x26, 0x5E, 0x00 + , 0x03, 0x2A, 0x5E, 0x00, 0x03, 0x2A, 0x91, 0x26, 0xC8, 0xD0, 0xEE, 0xE6, 0x27, 0xE6, 0x3D, 0xA5 + , 0x3D, 0xCD, 0x00, 0x08, 0xA6, 0x2B, 0x90, 0xDB, 0x4C, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00 +]; class DiskIIState { - data : Uint8Array[]; - track : number = 0; - read_mode : boolean = true; - write_protect : boolean = false; - motor : boolean = false; - track_index : number = 0; + data: Uint8Array[]; + track: number = 0; + read_mode: boolean = true; + write_protect: boolean = false; + motor: boolean = false; + track_index: number = 0; } class DiskII extends DiskIIState implements SlotDevice, SavesState { - 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 +1183,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 +1207,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 +1282,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 +1316,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; - - /* - * 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 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 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 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; - } +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 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 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 10: write the last value as the checksum. - */ - out[i++] = (byte_translation[value]); + /* + * Step 6: write the 3-byte data header. + */ + out[i++] = (0xd5); + out[i++] = (0xaa); + out[i++] = (0xad); - /* - * 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; +} diff --git a/src/platform/apple2.ts b/src/platform/apple2.ts index ef6e5273a..78d07b153 100644 --- a/src/platform/apple2.ts +++ b/src/platform/apple2.ts @@ -24,6 +24,7 @@ const APPLE2_PRESETS: Preset[] = [ // {id:'zap.dasm', name:"ZAP!"}, // {id:'tb_6502.s', name:'Tom Bombem (assembler game)'}, { id: 'dos33bin.a', name: "DOS 3.3 Binary" }, + { id: 'applesinglebin.a', name: "AppleSingle Binary" }, ]; /// MAME support diff --git a/src/worker/tools/dasm.ts b/src/worker/tools/dasm.ts index 5a4cc9ad0..96fc2d869 100644 --- a/src/worker/tools/dasm.ts +++ b/src/worker/tools/dasm.ts @@ -133,6 +133,24 @@ function parseSymbolMap(asym: string) { return symbolmap; } +// Determine likely origin address from listing +function getMinListingOffset(listings: CodeListingMap): number | undefined { + let minOffset: number | undefined; + for (let key in listings) { + let lst = listings[key]; + if (lst && lst.lines) { + for (let line of lst.lines) { + if (line.iscode && line.offset > 0) { + if (minOffset === undefined || line.offset < minOffset) { + minOffset = line.offset; + } + } + } + } + } + return minOffset; +} + export function assembleDASM(step: BuildStep): BuildStepResult { load("dasm"); var unresolved = {}; @@ -216,6 +234,7 @@ export function assembleDASM(step: BuildStep): BuildStepResult { listings: listings, errors: errors, symbolmap: symbolmap, + origin: getMinListingOffset(listings), }; } @@ -268,6 +287,7 @@ export function assembleDASM2(step: BuildStep): BuildStepResult { output, errors, listings, - symbolmap + symbolmap, + origin: getMinListingOffset(listings), }; }