diff --git a/src/baseplatform.ts b/src/baseplatform.ts index 2d637bdd..d66b551b 100644 --- a/src/baseplatform.ts +++ b/src/baseplatform.ts @@ -1038,7 +1038,7 @@ export function lookupSymbol(platform:Platform, addr:number, extra:boolean) { /// new Machine platform adapters import { Bus, Resettable, FrameBased, VideoSource, SampledAudioSource, AcceptsROM, AcceptsKeyInput, SavesState, SavesInputState, HasCPU } from "./devices"; -import { Probeable, RasterFrameBased } from "./devices"; +import { Probeable, RasterFrameBased, AcceptsPaddleInput } from "./devices"; import { SampledAudio } from "./audio"; import { ProbeRecorder } from "./recorder"; @@ -1054,6 +1054,9 @@ function hasAudio(arg:any): arg is SampledAudioSource { function hasKeyInput(arg:any): arg is AcceptsKeyInput { return typeof arg.setKeyInput === 'function'; } +function hasPaddleInput(arg:any): arg is AcceptsPaddleInput { + return typeof arg.setPaddleInput === 'function'; +} function isRaster(arg:any): arg is RasterFrameBased { return typeof arg.getRasterY === 'function'; } @@ -1111,6 +1114,9 @@ export abstract class BaseMachinePlatform extends BaseDebugPl this.video.setKeyboardEvents(m.setKeyInput.bind(m)); this.poller = new ControllerPoller(m.setKeyInput.bind(m)); } + if (hasPaddleInput(m)) { + this.video.setupMouseEvents(); + } if (hasProbe(m)) { this.probeRecorder = new ProbeRecorder(m); this.startProbing = () => { @@ -1128,7 +1134,13 @@ export abstract class BaseMachinePlatform extends BaseDebugPl this.reset(); } - pollControls() { this.poller && this.poller.poll(); } + pollControls() { + this.poller && this.poller.poll(); + if (hasPaddleInput(this.machine)) { + this.machine.setPaddleInput(0, this.video.paddle_x); + this.machine.setPaddleInput(1, this.video.paddle_y); + } + } advance(novideo:boolean) { this.machine.advanceFrame(999999, this.getDebugCallback()); diff --git a/src/cpu/ZilogZ80.ts b/src/cpu/ZilogZ80.ts index 555319a8..f6e37d06 100644 --- a/src/cpu/ZilogZ80.ts +++ b/src/cpu/ZilogZ80.ts @@ -253,7 +253,7 @@ let run_instruction = function() /// @param non_maskable - true if this is a non-maskable interrupt /// @param data - the value to be placed on the data bus, if needed /////////////////////////////////////////////////////////////////////////////// -let interrupt = function(non_maskable, data) +let interrupt = function(non_maskable:boolean, data:number) : boolean { //console.log(non_maskable, data, iff1, iff2, do_delayed_ei, do_delayed_di); if (non_maskable) @@ -270,6 +270,7 @@ let interrupt = function(non_maskable, data) push_word(pc); pc = 0x66; cycle_counter += 11; + return true; } else if (iff1) { @@ -312,6 +313,7 @@ let interrupt = function(non_maskable, data) cycle_counter += 19; } //console.log(imode,data,pc); + return true; } }; @@ -3345,6 +3347,7 @@ let cycle_counts_dd = [ this.interrupt = interrupt; this.getPC = ():number => { return pc; } this.getSP = ():number => { return sp; } + this.getHalted = ():boolean => { return halted; } } export interface Z80State { @@ -3361,6 +3364,8 @@ export class Z80 implements CPU, InstructionBased, IOBusConnected, SavesState= 0 && this.cpu.interrupt(false, this.retryData)) { + this.retryData = -1; + } return this.cpu.advanceInsn(); } reset() { this.cpu.reset(); } interrupt(data:number) { - this.cpu.interrupt(false, data); + if (!this.cpu.interrupt(false, data) && this.retryInterrupts) { + this.retryData = data; + } } NMI() { this.cpu.interrupt(true, 0); diff --git a/src/devices.ts b/src/devices.ts index 0421868c..e84026cb 100644 --- a/src/devices.ts +++ b/src/devices.ts @@ -93,11 +93,15 @@ export interface SavesInputState { saveControlsState() : CS; } -// TODO +// TODO: joystick export interface AcceptsKeyInput { setKeyInput(key:number, code:number, flags:number) : void; } +export interface AcceptsPaddleInput { + setPaddleInput(controller:number, value:number) : void; +} + export interface Probeable { connectProbe(probe: ProbeAll) : void; } diff --git a/src/machine/astrocade.ts b/src/machine/astrocade.ts new file mode 100644 index 00000000..25f056b1 --- /dev/null +++ b/src/machine/astrocade.ts @@ -0,0 +1,632 @@ + +import { Z80, Z80State } from "../cpu/ZilogZ80"; +import { BasicScanlineMachine, AcceptsPaddleInput, Bus } from "../devices"; +import { KeyFlags, newAddressDecoder, padBytes, Keys, makeKeycodeMap, newKeyboardHandler } from "../emu"; +import { TssChannelAdapter, MasterAudio, AY38910_Audio } from "../audio"; +import { hex, rgb2bgr, lzgmini, stringToByteArray } from "../util"; + +// http://metopal.com/projects/ballybook/doku.php + +// TODO: fix keys, more controllers, vibrato/noise, border color, debug info, rotate +// http://atariage.com/forums/topic/251416-programming-the-bally-arcadeastrocade/ + +const ASTROCADE_KEYCODE_MAP = makeKeycodeMap([ + // player 1 + [Keys.UP, 0x10, 0x1], + [Keys.DOWN, 0x10, 0x2], + [Keys.LEFT, 0x10, 0x4], + [Keys.RIGHT, 0x10, 0x8], + [Keys.A, 0x10, 0x10], + // player 2 + [Keys.P2_UP, 0x11, 0x1], + [Keys.P2_DOWN, 0x11, 0x2], + [Keys.P2_LEFT, 0x11, 0x4], + [Keys.P2_RIGHT, 0x11, 0x8], + [Keys.P2_A, 0x11, 0x10], + // keypad $14 + [Keys.VK_P, 0x14, 0x1], + [Keys.VK_SLASH, 0x14, 0x2], + [Keys.VK_X, 0x14, 0x4], + [Keys.VK_MINUS, 0x14, 0x8], + [Keys.VK_COMMA, 0x14, 0x10], + [Keys.VK_EQUALS, 0x14, 0x20], + // keypad $15 + [Keys.VK_O, 0x15, 0x1], + [Keys.VK_L, 0x15, 0x2], + [Keys.VK_9, 0x15, 0x4], + [Keys.VK_6, 0x15, 0x8], + [Keys.VK_3, 0x15, 0x10], + [Keys.VK_PERIOD, 0x15, 0x20], + // keypad $16 + [Keys.VK_I, 0x16, 0x1], + [Keys.VK_K, 0x16, 0x2], + [Keys.VK_8, 0x16, 0x4], + [Keys.VK_5, 0x16, 0x8], + [Keys.VK_2, 0x16, 0x10], + [Keys.VK_0, 0x16, 0x20], + // keypad $17 + [Keys.VK_U, 0x17, 0x1], + [Keys.VK_J, 0x17, 0x2], + [Keys.VK_7, 0x17, 0x4], + [Keys.VK_4, 0x17, 0x8], + [Keys.VK_1, 0x17, 0x10], + [Keys.VK_BACK_SLASH, 0x17, 0x20], +]); + +const audioOversample = 2; + +const _BallyAstrocade = function(arcade:boolean) { + + var cpu : Z80; + var ram : Uint8Array; + var membus, iobus : Bus; + var rom, bios; + var pixels; + var psg; + //var watchdog_counter; + const swidth = arcade ? 320 : 160; + const sheight = arcade ? 204 : 102; + const swbytes = swidth >> 2; + const INITIAL_WATCHDOG = 256; + const PIXEL_ON = 0xffeeeeee; + const PIXEL_OFF = 0xff000000; + + // state variables + var inputs = new Uint8Array(0x20); + var magicop = 0; + var xpand = 0; + var xplower = false; + var shift2 = 0; + var horcb = 0; + var inmod = 0; + var inlin = 0; + var infbk = 0; + var verbl = sheight; + var palette = new Uint32Array(8); + var palinds = new Uint8Array(8); + var refreshlines = 0; + var dirtylines = new Uint8Array(arcade ? 262 : 131); + var vidactive = false; + var rotdata = new Uint8Array(4); + var rotcount = 0; + var intst = 0; + + + function ramwrite(a: number, v: number) { + // set RAM + ram[a] = v; + // mark scanline as dirty + dirtylines[((a & 0xfff) / swbytes) | 0] = 1; + // this was old behavior where we updated instantly + // but it had problems if we had mid-screen palette changes + //ramupdate(a, v); + } + + function ramupdate(a: number, v: number) { + var ofs = a * 4 + 3; // 4 pixels per byte + for (var i = 0; i < 4; i++) { + var lr = ((a % swbytes) >= (horcb & 0x3f)) ? 0 : 4; + pixels[ofs--] = palette[lr + (v & 3)]; + v >>= 2; + } + } + + function refreshline(y: number) { + var ofs = y * swidth / 4; + for (var i = 0; i < swidth / 4; i++) + ramupdate(ofs + i, ram[ofs + i]); + } + + function magicwrite(a: number, v: number) { + // expand + if (magicop & 0x8) { + var v2 = 0; + if (!xplower) + v >>= 4; + for (var i = 0; i < 4; i++) { + var pix = (v & 1) ? ((xpand >> 2) & 3) : (xpand & 3); + v2 |= pix << (i * 2); + v >>= 1; + } + v = v2; + xplower = !xplower; + } + // rotate + if (magicop & 0x4) { + if (rotcount & 4) { + // drain buffer + var sh = 2 * (~rotcount & 3); + v = (((rotdata[3] >> sh) & 3) << 6) | + (((rotdata[2] >> sh) & 3) << 4) | + (((rotdata[1] >> sh) & 3) << 2) | + (((rotdata[0] >> sh) & 3) << 0); + } else { + // fill buffer + rotdata[rotcount & 3] = v; + } + rotcount++; + } else { + // shift + var sh = (magicop & 3) << 1; + var v2 = (v >> sh) | shift2; + shift2 = (v << (8 - sh)) & 0xff; + v = v2; + } + // flop + if (magicop & 0x40) { + v = + ((v & 0x03) << 6) | + ((v & 0x0c) << 2) | + ((v & 0x30) >> 2) | + ((v & 0xc0) >> 6); + } + // or/xor + if (magicop & 0x30) { + var oldv = ram[a]; + // collision detect + var icpt = 0; + if ((oldv & 0xc0) && (v & 0xc0)) icpt |= 0x1; + if ((oldv & 0x30) && (v & 0x30)) icpt |= 0x2; + if ((oldv & 0x0c) && (v & 0x0c)) icpt |= 0x4; + if ((oldv & 0x03) && (v & 0x03)) icpt |= 0x8; + // apply op + if (magicop & 0x10) + v |= oldv; + if (magicop & 0x20) + v ^= oldv; // TODO: what if both? + // upper 4 bits persist, lower are just since last write + intst = (intst & 0xf0) | icpt | (icpt << 4); + } + // commit write to ram/screen + ramwrite(a, v); + } + + function setpalette(a: number, v: number) { + palinds[a & 7] = v & 0xff; + palette[a & 7] = ASTROCADE_PALETTE[v & 0xff]; + refreshall(); + } + + function setbordercolor() { + var col = horcb >> 6; + // TODO + } + + function refreshall() { + refreshlines = sheight; + } + + this.drawScanline = (sl:number) => { + // interrupt + if (sl == inlin && (inmod & 0x8)) { + cpu.retryInterrupts = !(inmod & 0x4); + cpu.interrupt(infbk); + } + // refresh this line in frame buffer? + if (sl < sheight && refreshlines > 0) { + dirtylines[sl] = 0; + refreshline(sl); + refreshlines--; + } + else if (dirtylines[sl]) { + dirtylines[sl] = 0; + refreshline(sl); + } + } + + this.init = (machine:BallyAstrocade, c:Z80, r:Uint8Array, inp:Uint8Array, psgg) => { + cpu = c; + ram = r; + inputs = inp; + psg = psgg; + //bios = padBytes(ASTROCADE_MINIMAL_BIOS, 0x2000); + bios = padBytes(new lzgmini().decode(stringToByteArray(atob(ASTROLIBRE_BIOS_LZG))), 0x2000); + if (!arcade) { + // game console + membus = { + read: newAddressDecoder([ + [0x0000, 0x1fff, 0x1fff, function(a) { return bios[a]; }], + [0x2000, 0x3fff, 0x1fff, function(a) { return rom ? rom[a] : 0; }], + [0x4000, 0x4fff, 0xfff, function(a) { return ram[a]; }], + ]), + write: newAddressDecoder([ + [0x4000, 0x4fff, 0xfff, ramwrite], + [0x0000, 0x3fff, 0xfff, magicwrite], + ]), + // TODO: correct values? + //isContended: () => { return true; }, + //contend: (addr: number) => { return vidactive && addr >= 0x4000 ? 1 : 0; }, + } + } else { + // arcade game + membus = { + read: newAddressDecoder([ + [0x4000, 0x7fff, 0x3fff, function(a) { return ram[a]; }], // screen RAM + [0xd000, 0xdfff, 0xfff, function(a) { return ram[a + 0x4000]; }], // static RAM + [0x0000, 0xafff, 0xffff, function(a) { return rom ? rom[a] : 0; }], // ROM (0-3fff,8000-afff) + ]), + write: newAddressDecoder([ + [0x4000, 0x7fff, 0x3fff, ramwrite], + [0xd000, 0xdfff, 0xfff, function(a, v) { ramwrite(a + 0x4000, v); }], // static RAM + [0x0000, 0x3fff, 0x3fff, magicwrite], + ]), + //isContended: () => { return true; }, + //TODO: contend: (addr: number) => { return vidactive ? 1 : 0; }, + } + } + iobus = { + read: function(addr) { + addr &= 0x1f; + var rtn; + switch (addr) { + case 8: + rtn = intst; + intst = 0; + break; + default: + rtn = inputs[addr]; + break; + // $10 = watchdog + } + return rtn; + }, + write: function(addr, val) { + addr &= 0x1f; + val &= 0xff; + switch (addr) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + setpalette(addr, val); + break; + case 9: // HORCB (horizontal boundary byte) + horcb = val; + setbordercolor(); + refreshall(); + break; + case 0xa: // VERBL (vertical blank) + verbl = arcade ? val : val >> 1; + refreshall(); + break; + case 0xb: // OTIR (set palette) + var c = cpu.saveState(); + //console.log(c.BC>>8, c.HL); + setpalette((c.BC >> 8) - 1, membus.read(c.HL)); + break; + case 0xc: // magic register + magicop = val; + shift2 = 0; + rotcount = 0; + xplower = false; + break; + case 0xd: // INFBK (interrupt feedback) + infbk = val; + break; + case 0xe: // INMOD (interrupt enable) + inmod = val; + break; + case 0xf: // INLIN (interrupt line) + inlin = arcade ? val : val >> 1; + break; + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + psg.setACRegister(addr, val); + break; + case 0x18: + var c = cpu.saveState(); + psg.setACRegister((c.BC >> 8) - 1, membus.read(c.HL)); + break; + case 0x19: // XPAND + xpand = val; + break; + default: + console.log('IO write', hex(addr, 4), hex(val, 2)); + break; + } + } + } + machine.connectCPUMemoryBus(membus); + machine.connectCPUIOBus(iobus); + this.membus = membus; + this.iobus = iobus; + // default palette + for (var i = 0; i < 8; i++) { + setpalette(i,i); + } + }; + + this.loadState = (state) => { + cpu.loadState(state.c); + ram.set(state.ram); + palette.set(state.palette); + palinds.set(state.palinds); + magicop = state.magicop; + xpand = state.xpand; + xplower = state.xplower; + shift2 = state.shift2; + horcb = state.horcb; + inmod = state.inmod; + inlin = state.inlin; + infbk = state.infbk; + verbl = state.verbl; + rotcount = state.rotcount; + rotdata.set(state.rotdata); + intst = state.intst; + inputs.set(state.inputs); + refreshall(); + } + + this.saveState = () => { + return { + c: cpu.saveState(), + ram: ram.slice(0), + inputs: inputs.slice(0), + palette: palette.slice(0), + palinds: palinds.slice(0), + magicop: magicop, + xpand: xpand, + xplower: xplower, + shift2: shift2, + horcb: horcb, + inmod: inmod, + inlin: inlin, + infbk: infbk, + verbl: verbl, + rotcount: rotcount, + rotdata: rotdata.slice(0), + intst: intst + }; + } + this.reset = () => { + // TODO? + magicop = xpand = inmod = inlin = infbk = shift2 = horcb = 0; + verbl = sheight; + xplower = false; + //watchdog_counter = INITIAL_WATCHDOG; + } + + this.toLongString = (st) => { + var s = ""; + //s += " Scan Y: " + st.sl; + s += "\n INLIN: " + st.inlin; + s += "\n VERBL: " + st.verbl; + s += "\nMAGICOP: $" + hex(st.magicop); + s += "\n XPAND: $" + hex(st.xpand); + s += "\nXPLOWER: " + st.xplower; + s += "\n SHIFT2: $" + hex(st.shift2); + s += "\n HORCB: $" + hex(st.horcb); + s += "\n INMOD: $" + hex(st.inmod); + s += "\n INFBK: $" + hex(st.infbk); + s += "\n INTST: $" + hex(st.intst); // intercept status + s += "\nPalette: "; + for (var i = 0; i < 8; i++) + s += hex(palinds[i]); + s += "\n"; + return s; + } + + this.connectVideo = function(pix) { + pixels = pix; + } + this.loadROM = function(data) { + rom = data; + } + this.loadBIOS = function(data) { + bios = data; + } + this.getVisiblePixelWords = function() { + return verbl * swidth; + } +} + +export class BallyAstrocade extends BasicScanlineMachine implements AcceptsPaddleInput { + + cpuFrequency = 1789000; + numTotalScanlines = 262; + numVisibleScanlines : number; + cpuCyclesPerLine : number; + defaultROMSize : number; + canvasWidth : number; + sampleRate = 60 * 262 * audioOversample; + ram : Uint8Array; + cpu : Z80; + m; // _BallyAstrocade + + handler; + psg: AstrocadeAudio; + audioadapter; + backbuffer : Uint32Array; + frontbuffer : Uint32Array; + + constructor(arcade:boolean) { + super(); + this.cpu = new Z80(); + this.psg = new AstrocadeAudio(new MasterAudio()); + this.audioadapter = new TssChannelAdapter(this.psg.psg, audioOversample, this.sampleRate); + this.handler = newKeyboardHandler(this.inputs, ASTROCADE_KEYCODE_MAP); + this.defaultROMSize = arcade ? 0xb000 : 0x2000; + this.ram = new Uint8Array(arcade ? 0x5000 : 0x1000); + this.numVisibleScanlines = arcade ? 204 : 102; + this.canvasWidth = arcade ? 320 : 160; + this.cpuCyclesPerLine = (this.cpuFrequency / (60 * this.numVisibleScanlines * 1.286)) | 0; + //this.cpuCyclesPerHBlank = Math.floor(this.cpuCyclesPerLine * 0.33); + //this.cpuCyclesPerVisible = this.cpuCyclesPerLine - this.cpuCyclesPerHBlank; + this.m = new _BallyAstrocade(arcade); + this.m.init(this, this.cpu, this.ram, this.inputs, this.psg); + } + + setKeyInput(key:number, code:number, flags:number) : void { + this.handler(key,code,flags); + } + + read(a:number) : number { + return this.m.membus.read(a); + } + write(a:number, v:number) : void { + this.m.membus.write(a,v); + } + + connectVideo(pixels:Uint32Array) : void { + super.connectVideo(pixels); + this.frontbuffer = pixels; + this.backbuffer = new Uint32Array(pixels.length); + this.m.connectVideo(this.backbuffer); + } + postFrame() { + // copy back buffer to front buffer, omitting bottom non-visible pixels + var nbytes = this.m.getVisiblePixelWords(); + this.frontbuffer.set(this.backbuffer.slice(0, nbytes), 0); + this.frontbuffer.fill(0, nbytes); + } + + setPaddleInput(controller:number, value:number) : void { + switch (controller) { + case 0: this.inputs[0x1c] = value & 0xff; break; + case 1: this.inputs[0x1d] = value & 0xff; break; + } + } + + startScanline() { + } + + drawScanline() { + var sl = this.scanline; + // double scanlines in consumer mode + //for (var i = 0; i < 2; i++) { + /* + // TODO: simulate contention during visible part of scanline + vidactive = sl < verbl; + extra = this.advance(this.cpuCyclesPerVisible - extra); + vidactive = false; + extra = this.advance(this.cpuCyclesPerHBlank - extra); + this.scanline++; + } + */ + this.m.drawScanline(sl); + /* + if (watchdog_counter-- <= 0) { + console.log("WATCHDOG FIRED"); // TODO: alert on video + this.reset(); + } + */ + } + + + loadROM(data) { + super.loadROM(data); + this.m.loadROM(this.rom); + this.reset(); + } + loadBIOS(data) { + this.m.loadBIOS(padBytes(data, 0x2000)); + this.reset(); + } + reset() { + super.reset(); + this.m.reset(); + } + loadState(state) { + this.m.loadState(state); + } + saveState() { + return this.m.saveState(); + } + getDebugCategories() { + return ['CPU','Stack','Astro']; + } + getDebugInfo(category, state) { + switch (category) { + case 'Astro': return this.m.toLongString(state); + } + } + +} + +///// + +// TODO: https://github.com/mamedev/mame/blob/master/src/devices/sound/astrocde.cpp +class AstrocadeAudio extends AY38910_Audio { + setACRegister(addr: number, val: number) { + addr &= 0x7; + val &= 0xff; + switch (addr) { + case 0: + this.psg.setClock(1789000 * 16 / (val + 1)); + this.psg.writeRegisterAY(7, 0x7 ^ 0xff); // disable noise + break; + case 1: + case 2: + case 3: + var i = (addr - 1) * 2; + var j = val * 2 + 1; + this.psg.writeRegisterAY(i, j & 0xff); // freq lo + this.psg.writeRegisterAY(i + 1, (j >> 8) & 0xff); // freq hi + break; + case 5: + this.psg.writeRegisterAY(10, val & 0xf); // tone c vol + break; + case 6: + this.psg.writeRegisterAY(8, val & 0xf); // tone a vol + this.psg.writeRegisterAY(9, (val >> 4) & 0xf); // tone b vol + break; + } + } +} + +export const _BallyArcade = function() { + this.__proto__ = new (_BallyAstrocade as any)(true); + // TODO: inputs[0x13] = 0xfe; // dip switch on arcade + // TODO: arcade controls, bit blit + var _in = this.saveControlsState(); + _in.in[0x10] = 0xff; // switches + _in.in[0x13] = 0xfe; // dip switches + this.loadControlsState(_in); +} + +///// + +//http://glankonian.com/~lance/astrocade_palette.html +var ASTROCADE_PALETTE = [0x000000, 0x242424, 0x484848, 0x6D6D6D, 0x919191, 0xB6B6B6, 0xDADADA, 0xFFFFFF, 0x2500BB, 0x4900E0, 0x6E11FF, 0x9235FF, 0xB75AFF, 0xDB7EFF, 0xFFA3FF, 0xFFC7FF, 0x4900B0, 0x6D00D5, 0x9201F9, 0xB625FF, 0xDA4AFF, 0xFF6EFF, 0xFF92FF, 0xFFB7FF, 0x6A009F, 0x8E00C3, 0xB300E7, 0xD718FF, 0xFB3CFF, 0xFF61FF, 0xFF85FF, 0xFFA9FF, 0x870087, 0xAB00AB, 0xD000D0, 0xF40EF4, 0xFF32FF, 0xFF56FF, 0xFF7BFF, 0xFF9FFF, 0x9F006A, 0xC3008E, 0xE700B3, 0xFF07D7, 0xFF2CFB, 0xFF50FF, 0xFF74FF, 0xFF99FF, 0xB00049, 0xD5006D, 0xF90092, 0xFF05B6, 0xFF29DA, 0xFF4DFF, 0xFF72FF, 0xFF96FF, 0xBB0025, 0xE00049, 0xFF006E, 0xFF0692, 0xFF2AB7, 0xFF4FDB, 0xFF73FF, 0xFF98FF, 0xBF0000, 0xE30024, 0xFF0048, 0xFF0B6D, 0xFF3091, 0xFF54B6, 0xFF79DA, 0xFF9DFF, 0xBB0000, 0xE00000, 0xFF0023, 0xFF1447, 0xFF396C, 0xFF5D90, 0xFF82B5, 0xFFA6D9, 0xB00000, 0xD50000, 0xF90000, 0xFF2124, 0xFF4548, 0xFF6A6C, 0xFF8E91, 0xFFB3B5, 0x9F0000, 0xC30000, 0xE70C00, 0xFF3003, 0xFF5527, 0xFF794B, 0xFF9E70, 0xFFC294, 0x870000, 0xAB0000, 0xD01E00, 0xF44200, 0xFF670A, 0xFF8B2E, 0xFFAF53, 0xFFD477, 0x6A0000, 0x8E0D00, 0xB33100, 0xD75600, 0xFB7A00, 0xFF9E17, 0xFFC33B, 0xFFE75F, 0x490000, 0x6D2100, 0x924500, 0xB66A00, 0xDA8E00, 0xFFB305, 0xFFD729, 0xFFFC4E, 0x251100, 0x493500, 0x6E5A00, 0x927E00, 0xB7A300, 0xDBC700, 0xFFEB1E, 0xFFFF43, 0x002500, 0x244900, 0x486D00, 0x6D9200, 0x91B600, 0xB6DB00, 0xDAFF1B, 0xFFFF3F, 0x003700, 0x005B00, 0x238000, 0x47A400, 0x6CC900, 0x90ED00, 0xB5FF1E, 0xD9FF43, 0x004700, 0x006C00, 0x009000, 0x24B400, 0x48D900, 0x6CFD05, 0x91FF29, 0xB5FF4E, 0x005500, 0x007900, 0x009D00, 0x03C200, 0x27E600, 0x4BFF17, 0x70FF3B, 0x94FF5F, 0x005F00, 0x008300, 0x00A800, 0x00CC00, 0x0AF00A, 0x2EFF2E, 0x53FF53, 0x77FF77, 0x006500, 0x008A00, 0x00AE00, 0x00D203, 0x00F727, 0x17FF4B, 0x3BFF70, 0x5FFF94, 0x006800, 0x008C00, 0x00B100, 0x00D524, 0x00F948, 0x05FF6C, 0x29FF91, 0x4EFFB5, 0x006600, 0x008B00, 0x00AF23, 0x00D447, 0x00F86C, 0x00FF90, 0x1EFFB5, 0x43FFD9, 0x006100, 0x008524, 0x00AA48, 0x00CE6D, 0x00F391, 0x00FFB6, 0x1BFFDA, 0x3FFFFE, 0x005825, 0x007C49, 0x00A16E, 0x00C592, 0x00EAB7, 0x00FFDB, 0x1EFFFF, 0x43FFFF, 0x004B49, 0x00706D, 0x009492, 0x00B9B6, 0x00DDDA, 0x05FFFF, 0x29FFFF, 0x4EFFFF, 0x003C6A, 0x00608E, 0x0085B3, 0x00A9D7, 0x00CEFB, 0x17F2FF, 0x3BFFFF, 0x5FFFFF, 0x002A87, 0x004FAB, 0x0073D0, 0x0097F4, 0x0ABCFF, 0x2EE0FF, 0x53FFFF, 0x77FFFF, 0x00179F, 0x003BC3, 0x0060E7, 0x0384FF, 0x27A8FF, 0x4BCDFF, 0x70F1FF, 0x94FFFF, 0x0002B0, 0x0027D5, 0x004BF9, 0x2470FF, 0x4894FF, 0x6CB9FF, 0x91DDFF, 0xB5FFFF, 0x0000BB, 0x0013E0, 0x2337FF, 0x475BFF, 0x6C80FF, 0x90A4FF, 0xB5C9FF, 0xD9EDFF]; +// swap palette RGB to BGR +for (var i = 0; i < 256; i++) { + var x = ASTROCADE_PALETTE[i]; + ASTROCADE_PALETTE[i] = rgb2bgr(x) | 0xff000000; +} + +/* +0000 F3 [ 4] 196 DI ; disable interrupts +0001 21 00 20 [10] 197 LD HL,#0x2000 +0004 7E [ 7] 198 LD A,(HL) ; A <- mem[0x2000] +0005 FE 55 [ 7] 199 CP #0x55 ; found sentinel byte? ($55) +0007 CA 0D 00 [10] 200 JP Z,FoundSentinel ; yes, load program +000A C3 00 20 [10] 201 JP _main ; jump to test program +000D 202 FoundSentinel: +000D 31 CE 4F [10] 203 LD SP,#0x4fce ; position stack below BIOS vars +0010 CD 84 02 [17] 204 CALL _bios_init ; misc. bios init routines +0013 21 05 20 [10] 205 LD HL,#0x2005 ; cartridge start vector +0016 7E [ 7] 206 LD A,(HL) +0017 23 [ 6] 207 INC HL +0018 66 [ 7] 208 LD H,(HL) +0019 6F [ 4] 209 LD L,A +001A E9 [ 4] 210 JP (HL) ; jump to cart start vector +*/ +var ASTROCADE_MINIMAL_BIOS = [ + 0xf3, 0x21, 0x00, 0x20, 0x7e, 0xfe, 0x55, 0xca, 0x0d, 0x00, 0xc3, 0x00, 0x20, + 0x31, 0xce, 0x4f, + 0x21, 0x05, 0x20, 0x7e, 0x23, 0x66, 0x6f, + 0xe9, +]; + +var ASTROLIBRE_BIOS_LZG = `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`; + diff --git a/src/platform/astrocade.ts b/src/platform/astrocade.ts index 03aca813..69a1ea0a 100644 --- a/src/platform/astrocade.ts +++ b/src/platform/astrocade.ts @@ -1,9 +1,8 @@ -"use strict"; -import { Platform, BaseZ80Platform } from "../baseplatform"; -import { PLATFORMS, RAM, newAddressDecoder, padBytes, noise, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap } from "../emu"; -import { hex, lzgmini, stringToByteArray, rgb2bgr, clamp } from "../util"; -import { MasterAudio, AY38910_Audio } from "../audio"; +import { BallyAstrocade } from "../machine/astrocade"; +import { BaseZ80MachinePlatform } from "../baseplatform"; +import { Platform } from "../baseplatform"; +import { PLATFORMS } from "../emu"; // http://metopal.com/projects/ballybook/doku.php @@ -27,605 +26,20 @@ const ASTROCADE_BIOS_PRESETS = [ { id: 'bios.c', name: 'BIOS' }, ]; -// TODO: fix keys, more controllers, vibrato/noise, border color, debug info, rotate -// http://atariage.com/forums/topic/251416-programming-the-bally-arcadeastrocade/ +class BallyAstrocadePlatform extends BaseZ80MachinePlatform implements Platform { -const ASTROCADE_KEYCODE_MAP = makeKeycodeMap([ - // player 1 - [Keys.UP, 0x10, 0x1], - [Keys.DOWN, 0x10, 0x2], - [Keys.LEFT, 0x10, 0x4], - [Keys.RIGHT, 0x10, 0x8], - [Keys.A, 0x10, 0x10], - // player 2 - [Keys.P2_UP, 0x11, 0x1], - [Keys.P2_DOWN, 0x11, 0x2], - [Keys.P2_LEFT, 0x11, 0x4], - [Keys.P2_RIGHT, 0x11, 0x8], - [Keys.P2_A, 0x11, 0x10], - // keypad $14 - [Keys.VK_P, 0x14, 0x1], - [Keys.VK_SLASH, 0x14, 0x2], - [Keys.VK_X, 0x14, 0x4], - [Keys.VK_MINUS, 0x14, 0x8], - [Keys.VK_COMMA, 0x14, 0x10], - [Keys.VK_EQUALS, 0x14, 0x20], - // keypad $15 - [Keys.VK_O, 0x15, 0x1], - [Keys.VK_L, 0x15, 0x2], - [Keys.VK_9, 0x15, 0x4], - [Keys.VK_6, 0x15, 0x8], - [Keys.VK_3, 0x15, 0x10], - [Keys.VK_PERIOD, 0x15, 0x20], - // keypad $16 - [Keys.VK_I, 0x16, 0x1], - [Keys.VK_K, 0x16, 0x2], - [Keys.VK_8, 0x16, 0x4], - [Keys.VK_5, 0x16, 0x8], - [Keys.VK_2, 0x16, 0x10], - [Keys.VK_0, 0x16, 0x20], - // keypad $17 - [Keys.VK_U, 0x17, 0x1], - [Keys.VK_J, 0x17, 0x2], - [Keys.VK_7, 0x17, 0x4], - [Keys.VK_4, 0x17, 0x8], - [Keys.VK_1, 0x17, 0x10], - [Keys.VK_BACK_SLASH, 0x17, 0x20], -]); + newMachine() { return new BallyAstrocade(false); } + getPresets() { return ASTROCADE_PRESETS; } + getDefaultExtension() { return ".c"; }; + readAddress(a) { return this.machine.read(a); } + // TODO loadBios(bios) { this.machine.loadBIOS(a); } -const _BallyAstrocadePlatform = function(mainElement, arcade) { - - var cpu, ram, membus, iobus, rom, bios; - var video, timer, pixels; - var audio, psg; - //var watchdog_counter; - const swidth = arcade ? 320 : 160; - const sheight = arcade ? 204 : 102; - const swbytes = Math.floor(swidth / 4); - const cpuFrequency = 1789000; - const cpuCyclesPerLine = Math.floor(cpuFrequency / (60 * 262.5)); - const cpuCyclesPerHBlank = Math.floor(cpuCyclesPerLine * 0.33); - const cpuCyclesPerVisible = cpuCyclesPerLine - cpuCyclesPerHBlank; - const INITIAL_WATCHDOG = 256; - const PIXEL_ON = 0xffeeeeee; - const PIXEL_OFF = 0xff000000; - - // state variables - var inputs = new Uint8Array(0x20); - var magicop = 0; - var xpand = 0; - var xplower = false; - var shift2 = 0; - var horcb = 0; - var inmod = 0; - var inlin = 0; - var infbk = 0; - var verbl = sheight; - var palette = new Uint32Array(8); - var palinds = new Uint8Array(8); - var refreshlines = 0; - var dirtylines = new Uint8Array(arcade ? 262 : 131); - var vidactive = false; - var rotdata = new Uint8Array(4); - var rotcount = 0; - var intst = 0; - - function ramwrite(a: number, v: number) { - // set RAM - ram.mem[a] = v; - // mark scanline as dirty - dirtylines[Math.floor((a & 0xfff) / swbytes)] = 1; - // this was old behavior where we updated instantly - // but it had problems if we had mid-screen palette changes - //ramupdate(a, v); - } - - function ramupdate(a: number, v: number) { - var ofs = a * 4 + 3; // 4 pixels per byte - for (var i = 0; i < 4; i++) { - var lr = ((a % swbytes) >= (horcb & 0x3f)) ? 0 : 4; - pixels[ofs--] = palette[lr + (v & 3)]; - v >>= 2; - } - } - - function refreshline(y: number) { - var ofs = y * swidth / 4; - for (var i = 0; i < swidth / 4; i++) - ramupdate(ofs + i, ram.mem[ofs + i]); - } - - function magicwrite(a: number, v: number) { - // expand - if (magicop & 0x8) { - var v2 = 0; - if (!xplower) - v >>= 4; - for (var i = 0; i < 4; i++) { - var pix = (v & 1) ? ((xpand >> 2) & 3) : (xpand & 3); - v2 |= pix << (i * 2); - v >>= 1; - } - v = v2; - xplower = !xplower; - } - // rotate - if (magicop & 0x4) { - if (rotcount & 4) { - // drain buffer - var sh = 2 * (~rotcount & 3); - v = (((rotdata[3] >> sh) & 3) << 6) | - (((rotdata[2] >> sh) & 3) << 4) | - (((rotdata[1] >> sh) & 3) << 2) | - (((rotdata[0] >> sh) & 3) << 0); - } else { - // fill buffer - rotdata[rotcount & 3] = v; - } - rotcount++; - } else { - // shift - var sh = (magicop & 3) << 1; - var v2 = (v >> sh) | shift2; - shift2 = (v << (8 - sh)) & 0xff; - v = v2; - } - // flop - if (magicop & 0x40) { - v = - ((v & 0x03) << 6) | - ((v & 0x0c) << 2) | - ((v & 0x30) >> 2) | - ((v & 0xc0) >> 6); - } - // or/xor - if (magicop & 0x30) { - var oldv = ram.mem[a]; - // collision detect - var icpt = 0; - if ((oldv & 0xc0) && (v & 0xc0)) icpt |= 0x1; - if ((oldv & 0x30) && (v & 0x30)) icpt |= 0x2; - if ((oldv & 0x0c) && (v & 0x0c)) icpt |= 0x4; - if ((oldv & 0x03) && (v & 0x03)) icpt |= 0x8; - // apply op - if (magicop & 0x10) - v |= oldv; - if (magicop & 0x20) - v ^= oldv; // TODO: what if both? - // upper 4 bits persist, lower are just since last write - intst = (intst & 0xf0) | icpt | (icpt << 4); - } - // commit write to ram/screen - ramwrite(a, v); - } - - function setpalette(a: number, v: number) { - palinds[a & 7] = v & 0xff; - palette[a & 7] = ASTROCADE_PALETTE[v & 0xff]; - refreshall(); - } - - function setbordercolor() { - var col = horcb >> 6; - // TODO - } - - function refreshall() { - refreshlines = sheight; - } - - class BallyAstrocadePlatform extends BaseZ80Platform implements Platform { - - scanline: number; - poller; - - getPresets() { - return ASTROCADE_PRESETS; - } - - start = function() { - ram = new RAM(arcade ? 0x5000 : 0x1000); - //bios = padBytes(ASTROCADE_MINIMAL_BIOS, 0x2000); - bios = padBytes(new lzgmini().decode(stringToByteArray(atob(ASTROLIBRE_BIOS_LZG))), 0x2000); - if (!arcade) { - // game console - membus = { - read: newAddressDecoder([ - [0x0000, 0x1fff, 0x1fff, function(a) { return bios[a]; }], - [0x2000, 0x3fff, 0x1fff, function(a) { return rom ? rom[a] : 0; }], - [0x4000, 0x4fff, 0xfff, function(a) { return ram.mem[a]; }], - ]), - write: newAddressDecoder([ - [0x4000, 0x4fff, 0xfff, ramwrite], - [0x0000, 0x3fff, 0xfff, magicwrite], - ]), - // TODO: correct values? - isContended: () => { return true; }, - contend: (addr: number) => { return vidactive && addr >= 0x4000 ? 1 : 0; }, - }; - } else { - // arcade game - membus = { - read: newAddressDecoder([ - [0x4000, 0x7fff, 0x3fff, function(a) { return ram.mem[a]; }], // screen RAM - [0xd000, 0xdfff, 0xfff, function(a) { return ram.mem[a + 0x4000]; }], // static RAM - [0x0000, 0xafff, 0xffff, function(a) { return rom ? rom[a] : 0; }], // ROM (0-3fff,8000-afff) - ]), - write: newAddressDecoder([ - [0x4000, 0x7fff, 0x3fff, ramwrite], - [0xd000, 0xdfff, 0xfff, function(a, v) { ramwrite(a + 0x4000, v); }], // static RAM - [0x0000, 0x3fff, 0x3fff, magicwrite], - ]), - isContended: () => { return true; }, - contend: (addr: number) => { return vidactive ? 1 : 0; }, - }; - } - iobus = { - isULAPort: function(addr) { - return false; // TODO? - }, - contend: function(addr) { - return 0; // TODO? - }, - read: function(addr) { - addr &= 0x1f; - var rtn; - switch (addr) { - case 8: - rtn = intst; - intst = 0; - break; - default: - rtn = inputs[addr]; - break; - // $10 = watchdog - } - return rtn; - }, - write: function(addr, val) { - addr &= 0x1f; - val &= 0xff; - switch (addr) { - case 0: - case 1: - case 2: - case 3: - case 4: - case 5: - case 6: - case 7: - setpalette(addr, val); - break; - case 9: // HORCB (horizontal boundary byte) - horcb = val; - setbordercolor(); - refreshall(); - break; - case 0xa: // VERBL (vertical blank) - verbl = arcade ? val : val >> 1; - refreshall(); - break; - case 0xb: // OTIR (set palette) - var c = cpu.saveState(); - //console.log(c.BC>>8, c.HL); - setpalette((c.BC >> 8) - 1, membus.read(c.HL)); - break; - case 0xc: // magic register - magicop = val; - shift2 = 0; - rotcount = 0; - xplower = false; - break; - case 0xd: // INFBK (interrupt feedback) - infbk = val; - break; - case 0xe: // INMOD (interrupt enable) - inmod = val; - break; - case 0xf: // INLIN (interrupt line) - inlin = arcade ? val : val >> 1; - break; - case 0x10: - case 0x11: - case 0x12: - case 0x13: - case 0x14: - case 0x15: - case 0x16: - case 0x17: - psg.setACRegister(addr, val); - break; - case 0x18: - var c = cpu.saveState(); - psg.setACRegister((c.BC >> 8) - 1, membus.read(c.HL)); - break; - case 0x19: // XPAND - xpand = val; - break; - default: - console.log('IO write', hex(addr, 4), hex(val, 2)); - break; - } - } - }; - cpu = this.newCPU(membus, iobus, { applyContention: true }); - audio = new MasterAudio(); - psg = new AstrocadeAudio(audio); - video = new RasterVideo(mainElement, swidth, sheight, {}); - video.create(); - video.setupMouseEvents(); - var idata = video.getFrameData(); - this.poller = setKeyboardFromMap(video, inputs, ASTROCADE_KEYCODE_MAP); - pixels = video.getFrameData(); - timer = new AnimationTimer(60, this.nextFrame.bind(this)); - // default palette - for (var i = 0; i < 8; i++) { - palinds[i] = i; - palette[i] = ASTROCADE_PALETTE[i]; - } - } - - readAddress(addr) { - return membus.read(addr); - } - - loadControls() { - inputs[0x1c] = video.paddle_x & 0xff; - inputs[0x1d] = video.paddle_y & 0xff; - } - - pollControls() { this.poller.poll(); } - - advance(novideo: boolean) { - this.scanline = 0; - var extra = 0; // keep track of spare cycles - for (var sl = 0; sl < 131; sl++) { - // double scanlines in consumer mode - for (var i = 0; i < 2; i++) { - // simulate contention during visible part of scanline - vidactive = sl < verbl; - extra = this.runCPU(cpu, cpuCyclesPerVisible - extra); - vidactive = false; - extra = this.runCPU(cpu, cpuCyclesPerHBlank - extra); - this.scanline++; - } - // interrupt - if (sl == inlin && (inmod & 0x8)) { - cpu.retryInterrupts = !(inmod & 0x4); - cpu.interrupt(infbk); - } - // refresh this line in frame buffer? - if (sl < sheight && refreshlines > 0) { - dirtylines[sl] = 0; - refreshline(sl); - refreshlines--; - } - else if (dirtylines[sl]) { - dirtylines[sl] = 0; - refreshline(sl); - } - } - if (!novideo) { - video.updateFrame(0, 0, 0, 0, swidth, verbl); - video.clearRect(0, verbl, swidth, sheight - verbl); - this.loadControls(); - } - /* - if (watchdog_counter-- <= 0) { - console.log("WATCHDOG FIRED"); // TODO: alert on video - this.reset(); - } - */ - } - - getRasterScanline() { return this.scanline; } - - loadROM(title, data) { - rom = padBytes(data, arcade ? 0xb000 : 0x2000); - this.reset(); - } - - loadBIOS(title, data) { - bios = padBytes(data, 0x2000); - this.reset(); - } - - loadState(state) { - cpu.loadState(state.c); - ram.mem.set(state.b); - palette.set(state.palette); - palinds.set(state.palinds); - magicop = state.magicop; - xpand = state.xpand; - xplower = state.xplower; - shift2 = state.shift2; - horcb = state.horcb; - inmod = state.inmod; - inlin = state.inlin; - infbk = state.infbk; - verbl = state.verbl; - rotcount = state.rotcount; - rotdata.set(state.rotdata); - intst = state.intst; - this.scanline = state.sl; - this.loadControlsState(state); - refreshall(); - } - saveState() { - return { - c: this.getCPUState(), - b: ram.mem.slice(0), - in: inputs.slice(0), - palette: palette.slice(0), - palinds: palinds.slice(0), - magicop: magicop, - xpand: xpand, - xplower: xplower, - shift2: shift2, - horcb: horcb, - inmod: inmod, - inlin: inlin, - infbk: infbk, - verbl: verbl, - rotcount: rotcount, - rotdata: rotdata.slice(0), - intst: intst, - sl: this.scanline, - }; - } - loadControlsState(state) { - inputs.set(state.in); - } - saveControlsState() { - return { - in: inputs.slice(0) - }; - } - getCPUState() { - return cpu.saveState(); - } - - isRunning() { - return timer && timer.isRunning(); - } - pause() { - timer.stop(); - audio.stop(); - } - resume() { - timer.start(); - audio.start(); - } - reset() { - cpu.reset(); - psg.reset(); - // TODO? - magicop = xpand = inmod = inlin = infbk = shift2 = horcb = 0; - verbl = sheight; - xplower = false; - //watchdog_counter = INITIAL_WATCHDOG; - } - getDebugCategories() { - return super.getDebugCategories().concat(['Astro']); - } - getDebugInfo(category, state) { - switch (category) { - case 'Astro': return this.toLongString(state); - default: return super.getDebugInfo(category, state); - } - } - toLongString(st) { - var s = ""; - s += " Scan Y: " + st.sl; - s += "\n INLIN: " + st.inlin; - s += "\n VERBL: " + st.verbl; - s += "\nMAGICOP: $" + hex(st.magicop); - s += "\n XPAND: $" + hex(st.xpand); - s += "\nXPLOWER: " + st.xplower; - s += "\n SHIFT2: $" + hex(st.shift2); - s += "\n HORCB: $" + hex(st.horcb); - s += "\n INMOD: $" + hex(st.inmod); - s += "\n INFBK: $" + hex(st.infbk); - s += "\n INTST: $" + hex(st.intst); // intercept status - s += "\nPalette: "; - for (var i = 0; i < 8; i++) - s += hex(palinds[i]); - s += "\n"; - return s; - } - - } - return new BallyAstrocadePlatform(); } + ///// -// TODO: https://github.com/mamedev/mame/blob/master/src/devices/sound/astrocde.cpp -class AstrocadeAudio extends AY38910_Audio { - setACRegister(addr: number, val: number) { - addr &= 0x7; - val &= 0xff; - switch (addr) { - case 0: - this.psg.setClock(1789000 * 16 / (val + 1)); - this.psg.writeRegisterAY(7, 0x7 ^ 0xff); // disable noise - break; - case 1: - case 2: - case 3: - var i = (addr - 1) * 2; - var j = val * 2 + 1; - this.psg.writeRegisterAY(i, j & 0xff); // freq lo - this.psg.writeRegisterAY(i + 1, (j >> 8) & 0xff); // freq hi - break; - case 5: - this.psg.writeRegisterAY(10, val & 0xf); // tone c vol - break; - case 6: - this.psg.writeRegisterAY(8, val & 0xf); // tone a vol - this.psg.writeRegisterAY(9, (val >> 4) & 0xf); // tone b vol - break; - } - } -} - -const _BallyArcadePlatform = function(mainElement) { - this.__proto__ = new (_BallyAstrocadePlatform as any)(mainElement, true); - // TODO: inputs[0x13] = 0xfe; // dip switch on arcade - // TODO: arcade controls, bit blit - var _in = this.saveControlsState(); - _in.in[0x10] = 0xff; // switches - _in.in[0x13] = 0xfe; // dip switches - this.loadControlsState(_in); -} - -const _BallyAstrocadeBIOSPlatform = function(mainElement) { - this.__proto__ = new (_BallyAstrocadePlatform as any)(mainElement); - - this.getPresets = function() { - return ASTROCADE_BIOS_PRESETS; - } - - this.loadROM = this.loadBIOS; -} - -///// - -PLATFORMS['astrocade'] = _BallyAstrocadePlatform; -PLATFORMS['astrocade-bios'] = _BallyAstrocadeBIOSPlatform; -PLATFORMS['astrocade-arcade'] = _BallyArcadePlatform; - -//http://glankonian.com/~lance/astrocade_palette.html -var ASTROCADE_PALETTE = [0x000000, 0x242424, 0x484848, 0x6D6D6D, 0x919191, 0xB6B6B6, 0xDADADA, 0xFFFFFF, 0x2500BB, 0x4900E0, 0x6E11FF, 0x9235FF, 0xB75AFF, 0xDB7EFF, 0xFFA3FF, 0xFFC7FF, 0x4900B0, 0x6D00D5, 0x9201F9, 0xB625FF, 0xDA4AFF, 0xFF6EFF, 0xFF92FF, 0xFFB7FF, 0x6A009F, 0x8E00C3, 0xB300E7, 0xD718FF, 0xFB3CFF, 0xFF61FF, 0xFF85FF, 0xFFA9FF, 0x870087, 0xAB00AB, 0xD000D0, 0xF40EF4, 0xFF32FF, 0xFF56FF, 0xFF7BFF, 0xFF9FFF, 0x9F006A, 0xC3008E, 0xE700B3, 0xFF07D7, 0xFF2CFB, 0xFF50FF, 0xFF74FF, 0xFF99FF, 0xB00049, 0xD5006D, 0xF90092, 0xFF05B6, 0xFF29DA, 0xFF4DFF, 0xFF72FF, 0xFF96FF, 0xBB0025, 0xE00049, 0xFF006E, 0xFF0692, 0xFF2AB7, 0xFF4FDB, 0xFF73FF, 0xFF98FF, 0xBF0000, 0xE30024, 0xFF0048, 0xFF0B6D, 0xFF3091, 0xFF54B6, 0xFF79DA, 0xFF9DFF, 0xBB0000, 0xE00000, 0xFF0023, 0xFF1447, 0xFF396C, 0xFF5D90, 0xFF82B5, 0xFFA6D9, 0xB00000, 0xD50000, 0xF90000, 0xFF2124, 0xFF4548, 0xFF6A6C, 0xFF8E91, 0xFFB3B5, 0x9F0000, 0xC30000, 0xE70C00, 0xFF3003, 0xFF5527, 0xFF794B, 0xFF9E70, 0xFFC294, 0x870000, 0xAB0000, 0xD01E00, 0xF44200, 0xFF670A, 0xFF8B2E, 0xFFAF53, 0xFFD477, 0x6A0000, 0x8E0D00, 0xB33100, 0xD75600, 0xFB7A00, 0xFF9E17, 0xFFC33B, 0xFFE75F, 0x490000, 0x6D2100, 0x924500, 0xB66A00, 0xDA8E00, 0xFFB305, 0xFFD729, 0xFFFC4E, 0x251100, 0x493500, 0x6E5A00, 0x927E00, 0xB7A300, 0xDBC700, 0xFFEB1E, 0xFFFF43, 0x002500, 0x244900, 0x486D00, 0x6D9200, 0x91B600, 0xB6DB00, 0xDAFF1B, 0xFFFF3F, 0x003700, 0x005B00, 0x238000, 0x47A400, 0x6CC900, 0x90ED00, 0xB5FF1E, 0xD9FF43, 0x004700, 0x006C00, 0x009000, 0x24B400, 0x48D900, 0x6CFD05, 0x91FF29, 0xB5FF4E, 0x005500, 0x007900, 0x009D00, 0x03C200, 0x27E600, 0x4BFF17, 0x70FF3B, 0x94FF5F, 0x005F00, 0x008300, 0x00A800, 0x00CC00, 0x0AF00A, 0x2EFF2E, 0x53FF53, 0x77FF77, 0x006500, 0x008A00, 0x00AE00, 0x00D203, 0x00F727, 0x17FF4B, 0x3BFF70, 0x5FFF94, 0x006800, 0x008C00, 0x00B100, 0x00D524, 0x00F948, 0x05FF6C, 0x29FF91, 0x4EFFB5, 0x006600, 0x008B00, 0x00AF23, 0x00D447, 0x00F86C, 0x00FF90, 0x1EFFB5, 0x43FFD9, 0x006100, 0x008524, 0x00AA48, 0x00CE6D, 0x00F391, 0x00FFB6, 0x1BFFDA, 0x3FFFFE, 0x005825, 0x007C49, 0x00A16E, 0x00C592, 0x00EAB7, 0x00FFDB, 0x1EFFFF, 0x43FFFF, 0x004B49, 0x00706D, 0x009492, 0x00B9B6, 0x00DDDA, 0x05FFFF, 0x29FFFF, 0x4EFFFF, 0x003C6A, 0x00608E, 0x0085B3, 0x00A9D7, 0x00CEFB, 0x17F2FF, 0x3BFFFF, 0x5FFFFF, 0x002A87, 0x004FAB, 0x0073D0, 0x0097F4, 0x0ABCFF, 0x2EE0FF, 0x53FFFF, 0x77FFFF, 0x00179F, 0x003BC3, 0x0060E7, 0x0384FF, 0x27A8FF, 0x4BCDFF, 0x70F1FF, 0x94FFFF, 0x0002B0, 0x0027D5, 0x004BF9, 0x2470FF, 0x4894FF, 0x6CB9FF, 0x91DDFF, 0xB5FFFF, 0x0000BB, 0x0013E0, 0x2337FF, 0x475BFF, 0x6C80FF, 0x90A4FF, 0xB5C9FF, 0xD9EDFF]; -// swap palette RGB to BGR -for (var i = 0; i < 256; i++) { - var x = ASTROCADE_PALETTE[i]; - ASTROCADE_PALETTE[i] = rgb2bgr(x) | 0xff000000; -} - -/* -0000 F3 [ 4] 196 DI ; disable interrupts -0001 21 00 20 [10] 197 LD HL,#0x2000 -0004 7E [ 7] 198 LD A,(HL) ; A <- mem[0x2000] -0005 FE 55 [ 7] 199 CP #0x55 ; found sentinel byte? ($55) -0007 CA 0D 00 [10] 200 JP Z,FoundSentinel ; yes, load program -000A C3 00 20 [10] 201 JP _main ; jump to test program -000D 202 FoundSentinel: -000D 31 CE 4F [10] 203 LD SP,#0x4fce ; position stack below BIOS vars -0010 CD 84 02 [17] 204 CALL _bios_init ; misc. bios init routines -0013 21 05 20 [10] 205 LD HL,#0x2005 ; cartridge start vector -0016 7E [ 7] 206 LD A,(HL) -0017 23 [ 6] 207 INC HL -0018 66 [ 7] 208 LD H,(HL) -0019 6F [ 4] 209 LD L,A -001A E9 [ 4] 210 JP (HL) ; jump to cart start vector -*/ -var ASTROCADE_MINIMAL_BIOS = [ - 0xf3, 0x21, 0x00, 0x20, 0x7e, 0xfe, 0x55, 0xca, 0x0d, 0x00, 0xc3, 0x00, 0x20, - 0x31, 0xce, 0x4f, - 0x21, 0x05, 0x20, 0x7e, 0x23, 0x66, 0x6f, - 0xe9, -]; - -var ASTROLIBRE_BIOS_LZG = `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`; +PLATFORMS['astrocade'] = BallyAstrocadePlatform; +//PLATFORMS['astrocade-bios'] = _BallyAstrocadeBIOSPlatform; +//PLATFORMS['astrocade-arcade'] = _BallyArcadePlatform; diff --git a/src/platform/vicdual.ts b/src/platform/vicdual.ts index 67aa734a..07ff60b7 100644 --- a/src/platform/vicdual.ts +++ b/src/platform/vicdual.ts @@ -1,4 +1,3 @@ -"use strict"; import { VicDual } from "../machine/vicdual"; import { BaseZ80MachinePlatform } from "../baseplatform";