"use strict"; import { Platform, Base6502Platform, BaseMAMEPlatform, getOpcodeMetadata_6502 } from "../baseplatform"; import { PLATFORMS, RAM, newAddressDecoder, padBytes, noise, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap, dumpRAM } from "../emu"; import { hex, lzgmini } from "../util"; import { SampleAudio } from "../audio"; declare var jt; // 6502 const APPLE2_PRESETS = [ {id:'sieve.c', name:'Sieve'}, {id:'keyboardtest.c', name:'Keyboard Test'}, {id:'mandel.c', name:'Mandelbrot'}, {id:'tgidemo.c', name:'TGI Graphics Demo'}, {id:'siegegame.c', name:'Siege Game'}, {id:'cosmic.c', name:'Cosmic Impalas'}, {id:'hgrtest.a', name:"HGR Test"}, {id:'conway.a', name:"Conway's Game of Life"}, {id:'lz4fh.a', name:"LZ4FH Graphics Compression"}, // {id:'tb_6502.s', name:'Tom Bombem (assembler game)'}, ]; const GR_TXMODE = 1; const GR_MIXMODE = 2; const GR_PAGE1 = 4; const GR_HIRES = 8; type AppleGRParams = {dirty:boolean[], grswitch:number, mem:number[]}; const _Apple2Platform = function(mainElement) { const cpuFrequency = 1023000; const cpuCyclesPerLine = 65; const VM_BASE = 0x803; // where to JMP after pr#6 const LOAD_BASE = VM_BASE; //0x7c9; // where to load ROM const PGM_BASE = VM_BASE; //0x800; // where to load ROM const HDR_SIZE = PGM_BASE - LOAD_BASE; var cpu, ram, bus; var video, ap2disp, audio, timer; var grdirty = new Array(0xc000 >> 7); var grswitch = GR_TXMODE; var kbdlatch = 0; var soundstate = 0; var pgmbin; // language card switches var auxRAMselected = false; var auxRAMbank = 1; var writeinhibit = true; // value to add when reading & writing each of these banks // bank 1 is E000-FFFF, bank 2 is D000-DFFF var bank2rdoffset=0, bank2wroffset=0; var grparams : AppleGRParams; class Apple2Platform extends Base6502Platform { getPresets() { return APPLE2_PRESETS; } start() { cpu = new jt.M6502(); ram = new RAM(0x13000); // 64K + 16K LC RAM - 4K hardware // ROM var rom = new lzgmini().decode(APPLEIIGO_LZG); ram.mem.set(rom, 0xd000); ram.mem[0xbf00] = 0x4c; // fake DOS detect for C ram.mem[0xbf6f] = 0x01; // fake DOS detect for C // bus bus = { read: function(address) { address &= 0xffff; if (address < 0xc000) { return ram.mem[address]; } else if (address >= 0xd000) { //return rom[address - 0xd000] & 0xff; //console.log(hex(address), rom[address-0xd000], ram.mem[address]); if (!auxRAMselected) return rom[address - 0xd000]; else if (address >= 0xe000) return ram.mem[address]; else return ram.mem[address + bank2rdoffset]; } else if (address < 0xc100) { var slot = (address >> 4) & 0x0f; switch (slot) { case 0: return kbdlatch; case 1: kbdlatch &= 0x7f; break; case 3: soundstate = soundstate ^ 1; break; case 5: if ((address & 0x0f) < 8) { // graphics if ((address & 1) != 0) grswitch |= 1 << ((address >> 1) & 0x07); else grswitch &= ~(1 << ((address >> 1) & 0x07)); } break; case 6: // tapein, joystick, buttons switch (address & 7) { // buttons (off) case 1: case 2: case 3: return noise() & 0x7f; // joystick case 4: case 5: return noise() | 0x80; default: return noise(); } case 7: // joy reset if (address == 0xc070) return noise() | 0x80; case 8: return 0; // TODO doLanguageCardIO(address, value); case 9: case 10: case 11: case 12: case 13: case 14: case 15: return noise(); // return slots[slot-8].doIO(address, value); } } else { switch (address) { // JMP VM_BASE case 0xc600: { // load program into RAM if (pgmbin) ram.mem.set(pgmbin.slice(HDR_SIZE), PGM_BASE); return 0x4c; } case 0xc601: return VM_BASE&0xff; case 0xc602: return (VM_BASE>>8)&0xff; default: return noise(); } } return noise(); }, write: function(address, val) { address &= 0xffff; val &= 0xff; if (address < 0xc000) { ram.mem[address] = val; grdirty[address>>7] = 1; } else if (address < 0xc100) { this.read(address); // strobe address, discard result } else if (address >= 0xd000 && !writeinhibit) { if (address >= 0xe000) ram.mem[address] = val; else ram.mem[address + bank2wroffset] = val; } } }; cpu.connectBus(bus); // create video/audio video = new RasterVideo(mainElement,280,192); audio = new SampleAudio(cpuFrequency); video.create(); video.setKeyboardEvents((key,code,flags) => { if (flags & 1) { if (code) { // convert to uppercase for Apple ][ if (code >= 0x61 && code <= 0x7a) code -= 0x20; kbdlatch = (code | 0x80) & 0xff; } else if (key) { switch (key) { case 16: return; // shift case 17: return; // ctrl case 18: return; // alt case 37: key=8; break; // left case 39: key=21; break; // right case 38: key=11; break; // up case 40: key=10; break; // down } if (key >= 65 && key < 65+26) { if (flags & 5) key -= 64; // ctrl } kbdlatch = (key | 0x80) & 0xff; } } }); var idata = video.getFrameData(); grparams = {dirty:grdirty, grswitch:grswitch, mem:ram.mem}; ap2disp = new Apple2Display(idata, grparams); timer = new AnimationTimer(60, this.nextFrame.bind(this)); } advance(novideo : boolean) { // 262.5 scanlines per frame var clock = 0; var debugCond = this.getDebugCallback(); var rendered = false; for (var sl=0; sl<262; sl++) { for (var i=0; i 0xc000-0xcfff else bank2rdoffset = 0x3000; // map 0xd000-0xdfff -> 0x10000-0x10fff if (auxRAMbank == 2) bank2wroffset = -0x1000; // map 0xd000-0xdfff -> 0xc000-0xcfff else bank2wroffset = 0x3000; // map 0xd000-0xdfff -> 0x10000-0x10fff } return new Apple2Platform(); // return inner class from constructor }; var Apple2Display = function(pixels : number[], 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 = 500; const loresColor = [ (0xff000000), (0xffff00ff), (0xff00007f), (0xff7f007f), (0xff007f00), (0xff7f7f7f), (0xff0000bf), (0xff0000ff), (0xffbf7f00), (0xffffbf00), (0xffbfbfbf), (0xffff7f7f), (0xff00ff00), (0xffffff00), (0xff00bf7f), (0xffffffff), ]; 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 ]; 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 = 1; c2 = 12; // red & green } else { c1 = 7; 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; } } 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