mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-11-25 18:33:11 +00:00
cleaned up galaxian platform
This commit is contained in:
parent
698d0c616c
commit
43f65b99ae
@ -1,163 +1,143 @@
|
||||
"use strict";
|
||||
|
||||
import { Platform, BaseZ80Platform } from "../baseplatform";
|
||||
import { Platform, BaseZ80Platform } from "../baseplatform";
|
||||
import { PLATFORMS, RAM, newAddressDecoder, padBytes, noise, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap } from "../emu";
|
||||
import { hex } from "../util";
|
||||
import { MasterAudio, AY38910_Audio } from "../audio";
|
||||
|
||||
const GALAXIAN_PRESETS = [
|
||||
{id:'gfxtest.c', name:'Graphics Test'},
|
||||
{id:'shoot2.c', name:'Solarian Game'},
|
||||
{ id: 'gfxtest.c', name: 'Graphics Test' },
|
||||
{ id: 'shoot2.c', name: 'Solarian Game' },
|
||||
];
|
||||
|
||||
const GALAXIAN_KEYCODE_MAP = makeKeycodeMap([
|
||||
[Keys.A, 0, 0x10], // P1
|
||||
[Keys.LEFT, 0, 0x4],
|
||||
[Keys.A, 0, 0x10], // P1
|
||||
[Keys.LEFT, 0, 0x4],
|
||||
[Keys.RIGHT, 0, 0x8],
|
||||
[Keys.P2_A, 1, 0x10], // P2
|
||||
[Keys.P2_LEFT, 1, 0x4],
|
||||
[Keys.P2_A, 1, 0x10], // P2
|
||||
[Keys.P2_LEFT, 1, 0x4],
|
||||
[Keys.P2_RIGHT, 1, 0x8],
|
||||
[Keys.SELECT, 0, 0x1],
|
||||
[Keys.START, 1, 0x1],
|
||||
[Keys.START, 1, 0x1],
|
||||
[Keys.VK_2, 1, 0x2],
|
||||
]);
|
||||
|
||||
const SCRAMBLE_KEYCODE_MAP = makeKeycodeMap([
|
||||
[Keys.UP, 0, -0x1], // P1
|
||||
[Keys.B, 0, -0x2], // fire
|
||||
[Keys.VK_7, 0, -0x4], // credit
|
||||
[Keys.A, 0, -0x8], // bomb
|
||||
[Keys.RIGHT, 0, -0x10],
|
||||
[Keys.LEFT, 0, -0x20],
|
||||
[Keys.VK_6, 0, -0x40],
|
||||
[Keys.SELECT, 0, -0x80],
|
||||
[Keys.START, 1, -0x80],
|
||||
[Keys.VK_2, 1, -0x40],
|
||||
[Keys.DOWN, 2, -0x40],
|
||||
[Keys.UP, 0, -0x1], // P1
|
||||
[Keys.B, 0, -0x2], // fire
|
||||
[Keys.VK_7, 0, -0x4], // credit
|
||||
[Keys.A, 0, -0x8], // bomb
|
||||
[Keys.RIGHT, 0, -0x10],
|
||||
[Keys.LEFT, 0, -0x20],
|
||||
[Keys.VK_6, 0, -0x40],
|
||||
[Keys.SELECT, 0, -0x80],
|
||||
[Keys.START, 1, -0x80],
|
||||
[Keys.VK_2, 1, -0x40],
|
||||
[Keys.DOWN, 2, -0x40],
|
||||
//[Keys.VK_UP, 2, -0x10],
|
||||
]);
|
||||
|
||||
const GalaxianVideo = function(rom:Uint8Array, vram:RAM, oram:RAM, palette:Uint32Array, options) {
|
||||
|
||||
const _GalaxianPlatform = function(mainElement, options) {
|
||||
options = options || {};
|
||||
var romSize = options.romSize || 0x4000;
|
||||
var gfxBase = options.gfxBase || 0x2800;
|
||||
var palBase = options.palBase || 0x3800;
|
||||
var keyMap = options.keyMap || GALAXIAN_KEYCODE_MAP;
|
||||
var missileWidth = options.missileWidth || 4;
|
||||
var missileOffset = options.missileOffset || 0;
|
||||
|
||||
var cpu;
|
||||
var ram, vram, oram : RAM;
|
||||
var membus, iobus, rom, palette, outlatches;
|
||||
var video, audio, timer, pixels;
|
||||
var psg1, psg2;
|
||||
var inputs;
|
||||
var interruptEnabled = 0;
|
||||
var starsEnabled = 0;
|
||||
var watchdog_counter;
|
||||
var frameCounter = 0;
|
||||
|
||||
var XTAL = 18432000.0;
|
||||
var scanlinesPerFrame = 264;
|
||||
var cpuFrequency = XTAL/6; // 3.072 MHz
|
||||
var hsyncFrequency = XTAL/3/192/2; // 16 kHz
|
||||
var vsyncFrequency = hsyncFrequency/132/2; // 60.606060 Hz
|
||||
var vblankDuration = 1/vsyncFrequency * (20/132); // 2500 us
|
||||
var cpuCyclesPerLine = cpuFrequency/hsyncFrequency;
|
||||
var INITIAL_WATCHDOG = 8;
|
||||
var showOffscreenObjects = false;
|
||||
this.missileWidth = options.missileWidth || 4;
|
||||
this.missileOffset = options.missileOffset || 0;
|
||||
this.showOffscreenObjects = false;
|
||||
this.frameCounter = 0;
|
||||
this.starsEnabled = 0;
|
||||
var stars = [];
|
||||
for (var i=0; i<256; i++)
|
||||
for (var i = 0; i < 256; i++)
|
||||
stars[i] = noise();
|
||||
|
||||
function drawScanline(pixels, sl) {
|
||||
if (sl < 16 && !showOffscreenObjects) return; // offscreen
|
||||
if (sl >= 240 && !showOffscreenObjects) return; // offscreen
|
||||
this.advanceFrame = function() {
|
||||
this.frameCounter = (this.frameCounter + 1) & 0xff;
|
||||
}
|
||||
|
||||
this.drawScanline = function(pixels, sl) {
|
||||
if (sl < 16 && !this.showOffscreenObjects) return; // offscreen
|
||||
if (sl >= 240 && !this.showOffscreenObjects) return; // offscreen
|
||||
// draw tiles
|
||||
var pixofs = sl*264;
|
||||
var outi = pixofs; // starting output pixel in frame buffer
|
||||
for (var xx=0; xx<32; xx++) {
|
||||
var pixofs = sl * 264;
|
||||
var outi = pixofs; // starting output pixel in frame buffer
|
||||
for (var xx = 0; xx < 32; xx++) {
|
||||
var xofs = xx;
|
||||
var scroll = oram.mem[xofs*2]; // even entries control scroll position
|
||||
var attrib = oram.mem[xofs*2+1]; // odd entries control the color base
|
||||
var scroll = oram.mem[xofs * 2]; // even entries control scroll position
|
||||
var attrib = oram.mem[xofs * 2 + 1]; // odd entries control the color base
|
||||
var sl2 = (sl + scroll) & 0xff;
|
||||
var vramofs = (sl2>>3)<<5; // offset in VRAM
|
||||
var vramofs = (sl2 >> 3) << 5; // offset in VRAM
|
||||
var yy = sl2 & 7; // y offset within tile
|
||||
var tile = vram.mem[vramofs+xofs]; // TODO: why undefined?
|
||||
var color0 = (attrib & 7) << 2;
|
||||
var addr = gfxBase+(tile<<3)+yy;
|
||||
var data1 = rom[addr];
|
||||
var data2 = rom[addr+0x800];
|
||||
for (var i=0; i<8; i++) {
|
||||
var bm = 128>>i;
|
||||
var color = color0 + ((data1&bm)?1:0) + ((data2&bm)?2:0);
|
||||
var tile = vram.mem[vramofs + xofs]; // TODO: why undefined?
|
||||
var color0 = (attrib & 7) << 2;
|
||||
var addr = gfxBase + (tile << 3) + yy;
|
||||
var data1 = rom[addr];
|
||||
var data2 = rom[addr + 0x800];
|
||||
for (var i = 0; i < 8; i++) {
|
||||
var bm = 128 >> i;
|
||||
var color = color0 + ((data1 & bm) ? 1 : 0) + ((data2 & bm) ? 2 : 0);
|
||||
pixels[outi] = palette[color];
|
||||
outi++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// draw sprites
|
||||
for (var sprnum=7; sprnum>=0; sprnum--) {
|
||||
var base = (sprnum<<2) + 0x40;
|
||||
for (var sprnum = 7; sprnum >= 0; sprnum--) {
|
||||
var base = (sprnum << 2) + 0x40;
|
||||
var base0 = oram.mem[base];
|
||||
var sy = 240 - (base0 - ((sprnum<3)?1:0)); // the first three sprites match against y-1
|
||||
var sy = 240 - (base0 - ((sprnum < 3) ? 1 : 0)); // the first three sprites match against y-1
|
||||
var yy = (sl - sy);
|
||||
if (yy >= 0 && yy < 16) {
|
||||
var sx = oram.mem[base+3] + 1; // +1 pixel offset from tiles
|
||||
if (sx == 0 && !showOffscreenObjects)
|
||||
var sx = oram.mem[base + 3] + 1; // +1 pixel offset from tiles
|
||||
if (sx == 0 && !this.showOffscreenObjects)
|
||||
continue; // drawn off-buffer
|
||||
var code = oram.mem[base+1];
|
||||
var code = oram.mem[base + 1];
|
||||
var flipx = code & 0x40; // TODO: flipx
|
||||
if (code & 0x80) // flipy
|
||||
yy = 15-yy;
|
||||
yy = 15 - yy;
|
||||
code &= 0x3f;
|
||||
var color0 = (oram.mem[base+2] & 7) << 2;
|
||||
var color0 = 0;
|
||||
var addr = gfxBase+(code<<5)+(yy<8?yy:yy+8);
|
||||
var color0 = (oram.mem[base + 2] & 7) << 2;
|
||||
var addr = gfxBase + (code << 5) + (yy < 8 ? yy : yy + 8);
|
||||
outi = pixofs + sx; //<< 1
|
||||
var data1 = rom[addr];
|
||||
var data2 = rom[addr+0x800];
|
||||
for (var i=0; i<8; i++) {
|
||||
var bm = 128>>i;
|
||||
var color = ((data1&bm)?1:0) + ((data2&bm)?2:0);
|
||||
var data2 = rom[addr + 0x800];
|
||||
for (var i = 0; i < 8; i++) {
|
||||
var bm = 128 >> i;
|
||||
var color = ((data1 & bm) ? 1 : 0) + ((data2 & bm) ? 2 : 0);
|
||||
if (color)
|
||||
pixels[flipx?(outi+15-i):(outi+i)] = palette[color0 + color];
|
||||
pixels[flipx ? (outi + 15 - i) : (outi + i)] = palette[color0 + color];
|
||||
}
|
||||
var data1 = rom[addr+8];
|
||||
var data2 = rom[addr+0x808];
|
||||
for (var i=0; i<8; i++) {
|
||||
var bm = 128>>i;
|
||||
var color = ((data1&bm)?1:0) + ((data2&bm)?2:0);
|
||||
var data1 = rom[addr + 8];
|
||||
var data2 = rom[addr + 0x808];
|
||||
for (var i = 0; i < 8; i++) {
|
||||
var bm = 128 >> i;
|
||||
var color = ((data1 & bm) ? 1 : 0) + ((data2 & bm) ? 2 : 0);
|
||||
if (color)
|
||||
pixels[flipx?(outi+7-i):(outi+i+8)] = palette[color0 + color];
|
||||
pixels[flipx ? (outi + 7 - i) : (outi + i + 8)] = palette[color0 + color];
|
||||
}
|
||||
}
|
||||
}
|
||||
// draw bullets/shells
|
||||
var shell = 0xff;
|
||||
var missile = 0xff;
|
||||
for (var which=0; which<8; which++) {
|
||||
var sy = oram.mem[0x60 + (which<<2)+1];
|
||||
if (((sy + sl - ((which<3)?1:0))&0xff) == 0xff) {
|
||||
for (var which = 0; which < 8; which++) {
|
||||
var sy = oram.mem[0x60 + (which << 2) + 1];
|
||||
if (((sy + sl - ((which < 3) ? 1 : 0)) & 0xff) == 0xff) {
|
||||
if (which != 7)
|
||||
shell = which;
|
||||
else
|
||||
missile = which;
|
||||
}
|
||||
}
|
||||
for (var i=0; i<2; i++) {
|
||||
for (var i = 0; i < 2; i++) {
|
||||
which = i ? missile : shell;
|
||||
if (which != 0xff) {
|
||||
var sx = 255 - oram.mem[0x60 + (which<<2)+3];
|
||||
var outi = pixofs+sx-missileOffset;
|
||||
var sx = 255 - oram.mem[0x60 + (which << 2) + 3];
|
||||
var outi = pixofs + sx - this.missileOffset;
|
||||
var col = which == 7 ? 0xffffff00 : 0xffffffff;
|
||||
for (var j=0; j<missileWidth; j++)
|
||||
for (var j = 0; j < this.missileWidth; j++)
|
||||
pixels[outi++] = col;
|
||||
}
|
||||
}
|
||||
// draw stars
|
||||
if (starsEnabled) {
|
||||
var starx = ((frameCounter + stars[sl & 0xff]) & 0xff);
|
||||
if (this.starsEnabled) {
|
||||
var starx = ((this.frameCounter + stars[sl & 0xff]) & 0xff);
|
||||
if ((starx + sl) & 0x10) {
|
||||
var outi = pixofs + starx;
|
||||
if ((pixels[outi] & 0xffffff) == 0) {
|
||||
@ -165,11 +145,38 @@ const _GalaxianPlatform = function(mainElement, options) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const _GalaxianPlatform = function(mainElement, options) {
|
||||
options = options || {};
|
||||
var romSize = options.romSize || 0x4000;
|
||||
var palBase = options.palBase || 0x3800;
|
||||
var keyMap = options.keyMap || GALAXIAN_KEYCODE_MAP;
|
||||
|
||||
var cpu;
|
||||
var ram, vram, oram: RAM;
|
||||
var membus, iobus, rom, palette, outlatches;
|
||||
var video, audio, timer, pixels;
|
||||
var psg1, psg2;
|
||||
var inputs;
|
||||
var interruptEnabled = 0;
|
||||
var watchdog_counter;
|
||||
|
||||
var XTAL = 18432000.0;
|
||||
var scanlinesPerFrame = 264;
|
||||
var cpuFrequency = XTAL / 6; // 3.072 MHz
|
||||
var hsyncFrequency = XTAL / 3 / 192 / 2; // 16 kHz
|
||||
var vsyncFrequency = hsyncFrequency / 132 / 2; // 60.606060 Hz
|
||||
var vblankDuration = 1 / vsyncFrequency * (20 / 132); // 2500 us
|
||||
var cpuCyclesPerLine = cpuFrequency / hsyncFrequency;
|
||||
var INITIAL_WATCHDOG = 8;
|
||||
var gfx; // = new GalaxianVideo(rom, vram, oram, palette, options);
|
||||
|
||||
var m_protection_state = 0;
|
||||
var m_protection_result = 0;
|
||||
function scramble_protection_w(addr,data) {
|
||||
function scramble_protection_w(addr, data) {
|
||||
/*
|
||||
This is not fully understood; the low 4 bits of port C are
|
||||
inputs; the upper 4 bits are outputs. Scramble main set always
|
||||
@ -177,237 +184,240 @@ const _GalaxianPlatform = function(mainElement, options) {
|
||||
expects certain results in the upper nibble afterwards.
|
||||
*/
|
||||
m_protection_state = (m_protection_state << 4) | (data & 0x0f);
|
||||
switch (m_protection_state & 0xfff)
|
||||
{
|
||||
/* scramble */
|
||||
case 0xf09: m_protection_result = 0xff; break;
|
||||
case 0xa49: m_protection_result = 0xbf; break;
|
||||
case 0x319: m_protection_result = 0x4f; break;
|
||||
case 0x5c9: m_protection_result = 0x6f; break;
|
||||
switch (m_protection_state & 0xfff) {
|
||||
/* scramble */
|
||||
case 0xf09: m_protection_result = 0xff; break;
|
||||
case 0xa49: m_protection_result = 0xbf; break;
|
||||
case 0x319: m_protection_result = 0x4f; break;
|
||||
case 0x5c9: m_protection_result = 0x6f; break;
|
||||
|
||||
/* scrambls */
|
||||
case 0x246: m_protection_result ^= 0x80; break;
|
||||
case 0xb5f: m_protection_result = 0x6f; break;
|
||||
/* scrambls */
|
||||
case 0x246: m_protection_result ^= 0x80; break;
|
||||
case 0xb5f: m_protection_result = 0x6f; break;
|
||||
}
|
||||
}
|
||||
function scramble_protection_alt_r() {
|
||||
var bit = (m_protection_result >> 7) & 1;
|
||||
return (bit << 5) | ((bit^1) << 7);
|
||||
}
|
||||
|
||||
const bitcolors = [
|
||||
0x000021, 0x000047, 0x000097, // red
|
||||
0x002100, 0x004700, 0x009700, // green
|
||||
0x510000, 0xae0000 // blue
|
||||
];
|
||||
|
||||
class GalaxianPlatform extends BaseZ80Platform implements Platform {
|
||||
|
||||
scanline : number;
|
||||
poller;
|
||||
|
||||
getPresets() {
|
||||
return GALAXIAN_PRESETS;
|
||||
return (bit << 5) | ((bit ^ 1) << 7);
|
||||
}
|
||||
|
||||
start() {
|
||||
ram = new RAM(0x800);
|
||||
vram = new RAM(0x400);
|
||||
oram = new RAM(0x100);
|
||||
outlatches = new RAM(0x8);
|
||||
if (options.scramble) {
|
||||
inputs = [0xff,0xfc,0xf1];
|
||||
membus = {
|
||||
read: newAddressDecoder([
|
||||
[0x0000, 0x3fff, 0, function(a) { return rom ? rom[a] : null; }],
|
||||
[0x4000, 0x47ff, 0x7ff, function(a) { return ram.mem[a]; }],
|
||||
// [0x4800, 0x4fff, 0x3ff, function(a) { return vram.mem[a]; }],
|
||||
// [0x5000, 0x5fff, 0xff, function(a) { return oram.mem[a]; }],
|
||||
[0x7000, 0x7000, 0, function(a) { watchdog_counter = INITIAL_WATCHDOG; }],
|
||||
[0x7800, 0x7800, 0, function(a) { watchdog_counter = INITIAL_WATCHDOG; }],
|
||||
//[0x8000, 0x820f, 0, function(a) { return noise(); }], // TODO: remove
|
||||
[0x8100, 0x8100, 0, function(a) { return inputs[0]; }],
|
||||
[0x8101, 0x8101, 0, function(a) { return inputs[1]; }],
|
||||
[0x8102, 0x8102, 0, function(a) { return inputs[2] | scramble_protection_alt_r(); }],
|
||||
[0x8202, 0x8202, 0, function(a) { return m_protection_result; }], // scramble (protection)
|
||||
[0x9100, 0x9100, 0, function(a) { return inputs[0]; }],
|
||||
[0x9101, 0x9101, 0, function(a) { return inputs[1]; }],
|
||||
[0x9102, 0x9102, 0, function(a) { return inputs[2] | scramble_protection_alt_r(); }],
|
||||
[0x9212, 0x9212, 0, function(a) { return m_protection_result; }], // scramble (protection)
|
||||
//[0, 0xffff, 0, function(a) { console.log(hex(a)); return 0; }]
|
||||
]),
|
||||
write: newAddressDecoder([
|
||||
[0x4000, 0x47ff, 0x7ff, function(a,v) { ram.mem[a] = v; }],
|
||||
[0x4800, 0x4fff, 0x3ff, function(a,v) { vram.mem[a] = v; }],
|
||||
[0x5000, 0x5fff, 0xff, function(a,v) { oram.mem[a] = v; }],
|
||||
[0x6801, 0x6801, 0, function(a,v) { interruptEnabled = v & 1; /*console.log(a,v,cpu.getPC().toString(16));*/ }],
|
||||
[0x6802, 0x6802, 0, function(a,v) { /* TODO: coin counter */ }],
|
||||
[0x6803, 0x6803, 0, function(a,v) { /* TODO: backgroundColor = (v & 1) ? 0xFF000056 : 0xFF000000; */ }],
|
||||
[0x6804, 0x6804, 0, function(a,v) { starsEnabled = v & 1; }],
|
||||
[0x6808, 0x6808, 0, function(a,v) { missileWidth = v; }], // not on h/w
|
||||
[0x6809, 0x6809, 0, function(a,v) { missileOffset = v; }], // not on h/w
|
||||
[0x8202, 0x8202, 0, scramble_protection_w],
|
||||
//[0x8100, 0x8103, 0, function(a,v){ /* PPI 0 */ }],
|
||||
//[0x8200, 0x8203, 0, function(a,v){ /* PPI 1 */ }],
|
||||
//[0, 0xffff, 0, function(a,v) { console.log(hex(a),hex(v)); }]
|
||||
]),
|
||||
};
|
||||
} else {
|
||||
inputs = [0xe,0x8,0x0];
|
||||
membus = {
|
||||
read: newAddressDecoder([
|
||||
[0x0000, 0x3fff, 0, function(a) { return rom ? rom[a] : null; }],
|
||||
[0x4000, 0x47ff, 0x3ff, function(a) { return ram.mem[a]; }],
|
||||
[0x5000, 0x57ff, 0x3ff, function(a) { return vram.mem[a]; }],
|
||||
[0x5800, 0x5fff, 0xff, function(a) { return oram.mem[a]; }],
|
||||
[0x6000, 0x6000, 0, function(a) { return inputs[0]; }],
|
||||
[0x6800, 0x6800, 0, function(a) { return inputs[1]; }],
|
||||
[0x7000, 0x7000, 0, function(a) { return inputs[2]; }],
|
||||
[0x7800, 0x7800, 0, function(a) { watchdog_counter = INITIAL_WATCHDOG; }],
|
||||
]),
|
||||
write: newAddressDecoder([
|
||||
[0x4000, 0x47ff, 0x3ff, function(a,v) { ram.mem[a] = v; }],
|
||||
[0x5000, 0x57ff, 0x3ff, function(a,v) { vram.mem[a] = v; }],
|
||||
[0x5800, 0x5fff, 0xff, function(a,v) { oram.mem[a] = v; }],
|
||||
//[0x6004, 0x6007, 0x3, function(a,v) { }], // lfo freq
|
||||
//[0x6800, 0x6807, 0x7, function(a,v) { }], // sound
|
||||
//[0x7800, 0x7800, 0x7, function(a,v) { }], // pitch
|
||||
[0x6000, 0x6003, 0x3, function(a,v) { outlatches.mem[a] = v; }],
|
||||
[0x7001, 0x7001, 0, function(a,v) { interruptEnabled = v & 1; }],
|
||||
[0x7004, 0x7004, 0, function(a,v) { starsEnabled = v & 1; }],
|
||||
]),
|
||||
isContended: function() { return false; },
|
||||
};
|
||||
const bitcolors = [
|
||||
0x000021, 0x000047, 0x000097, // red
|
||||
0x002100, 0x004700, 0x009700, // green
|
||||
0x510000, 0xae0000 // blue
|
||||
];
|
||||
|
||||
class GalaxianPlatform extends BaseZ80Platform implements Platform {
|
||||
|
||||
scanline: number;
|
||||
poller;
|
||||
|
||||
getPresets() {
|
||||
return GALAXIAN_PRESETS;
|
||||
}
|
||||
audio = new MasterAudio();
|
||||
psg1 = new AY38910_Audio(audio);
|
||||
psg2 = new AY38910_Audio(audio);
|
||||
iobus = {
|
||||
read: function(addr) {
|
||||
return 0;
|
||||
},
|
||||
write: function(addr, val) {
|
||||
if (addr & 0x1) { psg1.selectRegister(val & 0xf); };
|
||||
if (addr & 0x2) { psg1.setData(val); };
|
||||
if (addr & 0x4) { psg2.selectRegister(val & 0xf); };
|
||||
if (addr & 0x8) { psg2.setData(val); };
|
||||
}
|
||||
};
|
||||
cpu = this.newCPU(membus, iobus);
|
||||
video = new RasterVideo(mainElement,264,264,{rotate:90});
|
||||
video.create();
|
||||
var idata = video.getFrameData();
|
||||
this.poller = setKeyboardFromMap(video, inputs, keyMap);
|
||||
pixels = video.getFrameData();
|
||||
timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
||||
}
|
||||
|
||||
pollControls() { this.poller.poll(); }
|
||||
|
||||
readAddress(a) {
|
||||
return (a == 0x7000 || a == 0x7800) ? null : membus.read(a); // ignore watchdog
|
||||
}
|
||||
|
||||
advance(novideo : boolean) {
|
||||
for (var sl=0; sl<scanlinesPerFrame; sl++) {
|
||||
this.scanline = sl;
|
||||
if (!novideo) {
|
||||
drawScanline(pixels, sl);
|
||||
start() {
|
||||
ram = new RAM(0x800);
|
||||
vram = new RAM(0x400);
|
||||
oram = new RAM(0x100);
|
||||
rom = new Uint8Array(romSize);
|
||||
palette = new Uint32Array(new ArrayBuffer(32 * 4));
|
||||
gfx = new GalaxianVideo(rom, vram, oram, palette, options);
|
||||
|
||||
outlatches = new RAM(0x8);
|
||||
if (options.scramble) {
|
||||
inputs = [0xff, 0xfc, 0xf1];
|
||||
membus = {
|
||||
read: newAddressDecoder([
|
||||
[0x0000, 0x3fff, 0, function(a) { return rom ? rom[a] : null; }],
|
||||
[0x4000, 0x47ff, 0x7ff, function(a) { return ram.mem[a]; }],
|
||||
// [0x4800, 0x4fff, 0x3ff, function(a) { return vram.mem[a]; }],
|
||||
// [0x5000, 0x5fff, 0xff, function(a) { return oram.mem[a]; }],
|
||||
[0x7000, 0x7000, 0, function(a) { watchdog_counter = INITIAL_WATCHDOG; }],
|
||||
[0x7800, 0x7800, 0, function(a) { watchdog_counter = INITIAL_WATCHDOG; }],
|
||||
//[0x8000, 0x820f, 0, function(a) { return noise(); }], // TODO: remove
|
||||
[0x8100, 0x8100, 0, function(a) { return inputs[0]; }],
|
||||
[0x8101, 0x8101, 0, function(a) { return inputs[1]; }],
|
||||
[0x8102, 0x8102, 0, function(a) { return inputs[2] | scramble_protection_alt_r(); }],
|
||||
[0x8202, 0x8202, 0, function(a) { return m_protection_result; }], // scramble (protection)
|
||||
[0x9100, 0x9100, 0, function(a) { return inputs[0]; }],
|
||||
[0x9101, 0x9101, 0, function(a) { return inputs[1]; }],
|
||||
[0x9102, 0x9102, 0, function(a) { return inputs[2] | scramble_protection_alt_r(); }],
|
||||
[0x9212, 0x9212, 0, function(a) { return m_protection_result; }], // scramble (protection)
|
||||
//[0, 0xffff, 0, function(a) { console.log(hex(a)); return 0; }]
|
||||
]),
|
||||
write: newAddressDecoder([
|
||||
[0x4000, 0x47ff, 0x7ff, function(a, v) { ram.mem[a] = v; }],
|
||||
[0x4800, 0x4fff, 0x3ff, function(a, v) { vram.mem[a] = v; }],
|
||||
[0x5000, 0x5fff, 0xff, function(a, v) { oram.mem[a] = v; }],
|
||||
[0x6801, 0x6801, 0, function(a, v) { interruptEnabled = v & 1; /*console.log(a,v,cpu.getPC().toString(16));*/ }],
|
||||
[0x6802, 0x6802, 0, function(a, v) { /* TODO: coin counter */ }],
|
||||
[0x6803, 0x6803, 0, function(a, v) { /* TODO: backgroundColor = (v & 1) ? 0xFF000056 : 0xFF000000; */ }],
|
||||
[0x6804, 0x6804, 0, function(a, v) { gfx.starsEnabled = v & 1; }],
|
||||
[0x6808, 0x6808, 0, function(a, v) { gfx.missileWidth = v; }], // not on h/w
|
||||
[0x6809, 0x6809, 0, function(a, v) { gfx.missileOffset = v; }], // not on h/w
|
||||
[0x8202, 0x8202, 0, scramble_protection_w],
|
||||
//[0x8100, 0x8103, 0, function(a,v){ /* PPI 0 */ }],
|
||||
//[0x8200, 0x8203, 0, function(a,v){ /* PPI 1 */ }],
|
||||
//[0, 0xffff, 0, function(a,v) { console.log(hex(a),hex(v)); }]
|
||||
]),
|
||||
};
|
||||
} else {
|
||||
inputs = [0xe, 0x8, 0x0];
|
||||
membus = {
|
||||
read: newAddressDecoder([
|
||||
[0x0000, 0x3fff, 0, function(a) { return rom ? rom[a] : null; }],
|
||||
[0x4000, 0x47ff, 0x3ff, function(a) { return ram.mem[a]; }],
|
||||
[0x5000, 0x57ff, 0x3ff, function(a) { return vram.mem[a]; }],
|
||||
[0x5800, 0x5fff, 0xff, function(a) { return oram.mem[a]; }],
|
||||
[0x6000, 0x6000, 0, function(a) { return inputs[0]; }],
|
||||
[0x6800, 0x6800, 0, function(a) { return inputs[1]; }],
|
||||
[0x7000, 0x7000, 0, function(a) { return inputs[2]; }],
|
||||
[0x7800, 0x7800, 0, function(a) { watchdog_counter = INITIAL_WATCHDOG; }],
|
||||
]),
|
||||
write: newAddressDecoder([
|
||||
[0x4000, 0x47ff, 0x3ff, function(a, v) { ram.mem[a] = v; }],
|
||||
[0x5000, 0x57ff, 0x3ff, function(a, v) { vram.mem[a] = v; }],
|
||||
[0x5800, 0x5fff, 0xff, function(a, v) { oram.mem[a] = v; }],
|
||||
//[0x6004, 0x6007, 0x3, function(a,v) { }], // lfo freq
|
||||
//[0x6800, 0x6807, 0x7, function(a,v) { }], // sound
|
||||
//[0x7800, 0x7800, 0x7, function(a,v) { }], // pitch
|
||||
[0x6000, 0x6003, 0x3, function(a, v) { outlatches.mem[a] = v; }],
|
||||
[0x7001, 0x7001, 0, function(a, v) { interruptEnabled = v & 1; }],
|
||||
[0x7004, 0x7004, 0, function(a, v) { gfx.starsEnabled = v & 1; }],
|
||||
]),
|
||||
isContended: function() { return false; },
|
||||
};
|
||||
}
|
||||
this.runCPU(cpu, cpuCyclesPerLine);
|
||||
audio = new MasterAudio();
|
||||
psg1 = new AY38910_Audio(audio);
|
||||
psg2 = new AY38910_Audio(audio);
|
||||
iobus = {
|
||||
read: function(addr) {
|
||||
return 0;
|
||||
},
|
||||
write: function(addr, val) {
|
||||
if (addr & 0x1) { psg1.selectRegister(val & 0xf); };
|
||||
if (addr & 0x2) { psg1.setData(val); };
|
||||
if (addr & 0x4) { psg2.selectRegister(val & 0xf); };
|
||||
if (addr & 0x8) { psg2.setData(val); };
|
||||
}
|
||||
};
|
||||
cpu = this.newCPU(membus, iobus);
|
||||
video = new RasterVideo(mainElement, 264, 264, { rotate: 90 });
|
||||
video.create();
|
||||
var idata = video.getFrameData();
|
||||
this.poller = setKeyboardFromMap(video, inputs, keyMap);
|
||||
pixels = video.getFrameData();
|
||||
timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
||||
}
|
||||
// visible area is 256x224 (before rotation)
|
||||
if (!novideo) {
|
||||
video.updateFrame(0, 0, 0, 0, showOffscreenObjects ? 264 : 256, 264);
|
||||
|
||||
pollControls() { this.poller.poll(); }
|
||||
|
||||
readAddress(a) {
|
||||
return (a == 0x7000 || a == 0x7800) ? null : membus.read(a); // ignore watchdog
|
||||
}
|
||||
frameCounter = (frameCounter + 1) & 0xff;
|
||||
if (watchdog_counter-- <= 0) {
|
||||
console.log("WATCHDOG FIRED, PC ", hex(cpu.getPC())); // TODO: alert on video
|
||||
|
||||
advance(novideo: boolean) {
|
||||
for (var sl = 0; sl < scanlinesPerFrame; sl++) {
|
||||
this.scanline = sl;
|
||||
if (!novideo) {
|
||||
gfx.drawScanline(pixels, sl);
|
||||
}
|
||||
this.runCPU(cpu, cpuCyclesPerLine);
|
||||
}
|
||||
// visible area is 256x224 (before rotation)
|
||||
if (!novideo) {
|
||||
video.updateFrame(0, 0, 0, 0, gfx.showOffscreenObjects ? 264 : 256, 264);
|
||||
}
|
||||
gfx.advanceFrame();
|
||||
if (watchdog_counter-- <= 0) {
|
||||
console.log("WATCHDOG FIRED, PC ", hex(cpu.getPC())); // TODO: alert on video
|
||||
this.reset();
|
||||
}
|
||||
// NMI interrupt @ 0x66
|
||||
if (interruptEnabled) { cpu.nonMaskableInterrupt(); }
|
||||
}
|
||||
|
||||
getRasterScanline() { return this.scanline; }
|
||||
|
||||
loadROM(title, data) {
|
||||
rom.set(padBytes(data, romSize));
|
||||
|
||||
for (var i = 0; i < 32; i++) {
|
||||
var b = rom[palBase + i];
|
||||
palette[i] = 0xff000000;
|
||||
for (var j = 0; j < 8; j++)
|
||||
if (((1 << j) & b))
|
||||
palette[i] += bitcolors[j];
|
||||
}
|
||||
|
||||
this.reset();
|
||||
}
|
||||
// NMI interrupt @ 0x66
|
||||
if (interruptEnabled) { cpu.nonMaskableInterrupt(); }
|
||||
}
|
||||
|
||||
getRasterScanline() { return this.scanline; }
|
||||
|
||||
loadROM(title, data) {
|
||||
rom = padBytes(data, romSize);
|
||||
loadState(state) {
|
||||
cpu.loadState(state.c);
|
||||
ram.mem.set(state.b);
|
||||
vram.mem.set(state.bv);
|
||||
oram.mem.set(state.bo);
|
||||
watchdog_counter = state.wdc;
|
||||
interruptEnabled = state.ie;
|
||||
gfx.starsEnabled = state.se;
|
||||
gfx.frameCounter = state.fc;
|
||||
inputs[0] = state.in0;
|
||||
inputs[1] = state.in1;
|
||||
inputs[2] = state.in2;
|
||||
}
|
||||
saveState() {
|
||||
return {
|
||||
c: this.getCPUState(),
|
||||
b: ram.mem.slice(0),
|
||||
bv: vram.mem.slice(0),
|
||||
bo: oram.mem.slice(0),
|
||||
fc: gfx.frameCounter,
|
||||
ie: interruptEnabled,
|
||||
se: gfx.starsEnabled,
|
||||
wdc: watchdog_counter,
|
||||
in0: inputs[0],
|
||||
in1: inputs[1],
|
||||
in2: inputs[2],
|
||||
};
|
||||
}
|
||||
loadControlsState(state) {
|
||||
inputs[0] = state.in0;
|
||||
inputs[1] = state.in1;
|
||||
inputs[2] = state.in2;
|
||||
}
|
||||
saveControlsState() {
|
||||
return {
|
||||
in0: inputs[0],
|
||||
in1: inputs[1],
|
||||
in2: inputs[2],
|
||||
};
|
||||
}
|
||||
getCPUState() {
|
||||
return cpu.saveState();
|
||||
}
|
||||
|
||||
palette = new Uint32Array(new ArrayBuffer(32*4));
|
||||
for (var i=0; i<32; i++) {
|
||||
var b = rom[palBase+i];
|
||||
palette[i] = 0xff000000;
|
||||
for (var j=0; j<8; j++)
|
||||
if (((1<<j) & b))
|
||||
palette[i] += bitcolors[j];
|
||||
}
|
||||
this.reset();
|
||||
isRunning() {
|
||||
return timer && timer.isRunning();
|
||||
}
|
||||
pause() {
|
||||
timer.stop();
|
||||
audio.stop();
|
||||
}
|
||||
resume() {
|
||||
timer.start();
|
||||
audio.start();
|
||||
}
|
||||
reset() {
|
||||
cpu.reset();
|
||||
//audio.reset();
|
||||
if (!this.getDebugCallback()) cpu.setTstates(0); // TODO?
|
||||
watchdog_counter = INITIAL_WATCHDOG;
|
||||
}
|
||||
}
|
||||
|
||||
loadState(state) {
|
||||
cpu.loadState(state.c);
|
||||
ram.mem.set(state.b);
|
||||
vram.mem.set(state.bv);
|
||||
oram.mem.set(state.bo);
|
||||
watchdog_counter = state.wdc;
|
||||
interruptEnabled = state.ie;
|
||||
starsEnabled = state.se;
|
||||
frameCounter = state.fc;
|
||||
inputs[0] = state.in0;
|
||||
inputs[1] = state.in1;
|
||||
inputs[2] = state.in2;
|
||||
}
|
||||
saveState() {
|
||||
return {
|
||||
c:this.getCPUState(),
|
||||
b:ram.mem.slice(0),
|
||||
bv:vram.mem.slice(0),
|
||||
bo:oram.mem.slice(0),
|
||||
fc:frameCounter,
|
||||
ie:interruptEnabled,
|
||||
se:starsEnabled,
|
||||
wdc:watchdog_counter,
|
||||
in0:inputs[0],
|
||||
in1:inputs[1],
|
||||
in2:inputs[2],
|
||||
};
|
||||
}
|
||||
loadControlsState(state) {
|
||||
inputs[0] = state.in0;
|
||||
inputs[1] = state.in1;
|
||||
inputs[2] = state.in2;
|
||||
}
|
||||
saveControlsState() {
|
||||
return {
|
||||
in0:inputs[0],
|
||||
in1:inputs[1],
|
||||
in2:inputs[2],
|
||||
};
|
||||
}
|
||||
getCPUState() {
|
||||
return cpu.saveState();
|
||||
}
|
||||
|
||||
isRunning() {
|
||||
return timer && timer.isRunning();
|
||||
}
|
||||
pause() {
|
||||
timer.stop();
|
||||
audio.stop();
|
||||
}
|
||||
resume() {
|
||||
timer.start();
|
||||
audio.start();
|
||||
}
|
||||
reset() {
|
||||
cpu.reset();
|
||||
//audio.reset();
|
||||
if (!this.getDebugCallback()) cpu.setTstates(0); // TODO?
|
||||
watchdog_counter = INITIAL_WATCHDOG;
|
||||
}
|
||||
}
|
||||
|
||||
return new GalaxianPlatform();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user