2019-09-08 17:36:26 +00:00
|
|
|
|
2019-10-26 01:55:50 +00:00
|
|
|
import { MOS6502, MOS6502State } from "../common/cpu/MOS6502";
|
|
|
|
import { BasicMachine, RasterFrameBased, Bus, ProbeAll } from "../common/devices";
|
|
|
|
import { KeyFlags, newAddressDecoder, padBytes, Keys, makeKeycodeMap, newKeyboardHandler, EmuHalt, dumpRAM } from "../common/emu";
|
|
|
|
import { lzgmini, stringToByteArray, hex, rgb2bgr } from "../common/util";
|
2019-09-08 17:36:26 +00:00
|
|
|
|
|
|
|
// https://www.c64-wiki.com/wiki/C64
|
|
|
|
// http://www.zimmers.net/cbmpics/cbm/c64/vic-ii.txt
|
|
|
|
// http://www.zimmers.net/cbmpics/cbm/c64/c64prg.txt
|
|
|
|
// http://sta.c64.org/cbm64mem.html
|
|
|
|
// http://hitmen.c02.at/temp/palstuff/
|
|
|
|
|
|
|
|
const KEYBOARD_ROW_0 = 0;
|
|
|
|
const SWCHA = 8;
|
|
|
|
const SWCHB = 9;
|
|
|
|
const INPT0 = 10;
|
|
|
|
|
|
|
|
const C64_KEYCODE_MAP = makeKeycodeMap([
|
|
|
|
[Keys.A, INPT0+0, 0x80],
|
|
|
|
[Keys.B, INPT0+1, 0x80],
|
|
|
|
[Keys.SELECT, SWCHB, -0x02],
|
|
|
|
[Keys.START, SWCHB, -0x01],
|
|
|
|
[Keys.UP, SWCHA, -0x10],
|
|
|
|
[Keys.DOWN, SWCHA, -0x20],
|
|
|
|
[Keys.LEFT, SWCHA, -0x40],
|
|
|
|
[Keys.RIGHT, SWCHA, -0x80],
|
|
|
|
|
|
|
|
[Keys.P2_A, INPT0+2, 0x80],
|
|
|
|
[Keys.P2_B, INPT0+3, 0x80],
|
|
|
|
//[Keys.P2_SELECT, 1, 2],
|
|
|
|
//[Keys.P2_START, 1, 3],
|
|
|
|
[Keys.P2_UP, SWCHA, -0x01],
|
|
|
|
[Keys.P2_DOWN, SWCHA, -0x02],
|
|
|
|
[Keys.P2_LEFT, SWCHA, -0x04],
|
|
|
|
[Keys.P2_RIGHT, SWCHA, -0x08],
|
|
|
|
]);
|
|
|
|
|
|
|
|
const C64_KEYMATRIX_NOSHIFT = [
|
|
|
|
Keys.VK_DELETE, Keys.VK_ENTER, Keys.VK_RIGHT, Keys.VK_F7, Keys.VK_F1, Keys.VK_F3, Keys.VK_F5, Keys.VK_DOWN,
|
|
|
|
Keys.VK_3, Keys.VK_W, Keys.VK_A, Keys.VK_4, Keys.VK_Z, Keys.VK_S, Keys.VK_E, Keys.VK_SHIFT,
|
|
|
|
Keys.VK_5, Keys.VK_R, Keys.VK_D, Keys.VK_6, Keys.VK_C, Keys.VK_F, Keys.VK_T, Keys.VK_X,
|
|
|
|
Keys.VK_7, Keys.VK_Y, Keys.VK_G, Keys.VK_8, Keys.VK_B, Keys.VK_H, Keys.VK_U, Keys.VK_V,
|
|
|
|
Keys.VK_9, Keys.VK_I, Keys.VK_J, Keys.VK_0, Keys.VK_M, Keys.VK_K, Keys.VK_O, Keys.VK_N,
|
|
|
|
null/*Keys.VK_PLUS*/, Keys.VK_P, Keys.VK_L, Keys.VK_MINUS, Keys.VK_PERIOD, null/*Keys.VK_COLON*/, null/*Keys.VK_AT*/, Keys.VK_COMMA,
|
|
|
|
null/*Keys.VK_POUND*/, null/*TIMES*/, Keys.VK_SEMICOLON, Keys.VK_HOME, Keys.VK_SHIFT/*right*/, Keys.VK_EQUALS, Keys.VK_TILDE, Keys.VK_SLASH,
|
|
|
|
Keys.VK_1, Keys.VK_LEFT, Keys.VK_CONTROL, Keys.VK_2, Keys.VK_SPACE, Keys.VK_ALT, Keys.VK_Q, null/*STOP*/,
|
|
|
|
];
|
|
|
|
|
|
|
|
// CIA
|
|
|
|
// TODO: https://www.c64-wiki.com/wiki/CIA
|
|
|
|
|
|
|
|
class CIA {
|
|
|
|
regs = new Uint8Array(0x10);
|
|
|
|
|
|
|
|
reset() {
|
|
|
|
this.regs.fill(0);
|
|
|
|
}
|
|
|
|
read(a : number) : number {
|
|
|
|
return this.regs[a] | 0;
|
|
|
|
}
|
|
|
|
write(a : number, v : number) {
|
|
|
|
this.regs[a] = v;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// VIC-II chip
|
|
|
|
|
|
|
|
class VIC_II {
|
|
|
|
platform : C64;
|
|
|
|
scanline : number = 0;
|
|
|
|
regs = new Uint8Array(0x40); // 64 bytes
|
|
|
|
cram = new Uint8Array(0x400); // color RAM
|
|
|
|
pixels = new Uint8Array(numVisiblePixels); // output pixel buffer
|
|
|
|
cycle : number = 0;
|
|
|
|
vc : number = 0;
|
|
|
|
vcbase : number = 0;
|
|
|
|
rc : number = 0;
|
|
|
|
vmli : number = 0; // TODO: we don't use
|
|
|
|
|
|
|
|
constructor(platform) {
|
|
|
|
this.platform = platform;
|
|
|
|
}
|
|
|
|
reset() {
|
|
|
|
this.regs.fill(0);
|
|
|
|
this.cram.fill(0);
|
|
|
|
this.vc = 0;
|
|
|
|
this.vcbase = 0;
|
|
|
|
this.rc = 0;
|
|
|
|
this.vmli = 0;
|
|
|
|
}
|
|
|
|
read(a : number) : number {
|
|
|
|
switch (a) {
|
|
|
|
case 0x12: return this.scanline;
|
|
|
|
}
|
|
|
|
return this.regs[a] | 0;
|
|
|
|
}
|
|
|
|
write(a : number, v : number) {
|
|
|
|
this.regs[a] = v;
|
|
|
|
}
|
|
|
|
setScanline(sl : number) {
|
|
|
|
this.scanline = sl;
|
|
|
|
// interrupt?
|
|
|
|
if (sl == (this.regs[0x12] | ((this.regs[0x11]&0x80)<<1))) {
|
|
|
|
this.regs[0x19] |= 0x81;
|
|
|
|
}
|
|
|
|
// reset pixel clock
|
|
|
|
this.cycle = 0;
|
|
|
|
// reset VCBASE
|
|
|
|
if (sl < firstScanline) this.vcbase = 0;
|
|
|
|
// clear pixel buffer w/ background
|
|
|
|
this.pixels.fill(this.regs[0x21]);
|
|
|
|
}
|
|
|
|
saveState() {
|
|
|
|
return {
|
|
|
|
regs: this.regs.slice(0),
|
|
|
|
cram: this.cram.slice(0)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
loadState(s) {
|
|
|
|
for (let i=0; i<32; i++)
|
|
|
|
this.write(i, s.regs[i]);
|
|
|
|
this.cram.set(s.cram);
|
|
|
|
}
|
|
|
|
c_access() : number {
|
|
|
|
let vm = this.regs[0x18];
|
|
|
|
let vadr = (this.vc & 0x3ff) | ((vm & 0xf0) << 6);
|
|
|
|
//this.platform.profiler && this.platform.profiler.logRead(vadr);
|
|
|
|
return this.platform.readVRAMAddress(vadr) | (this.cram[vadr & 0x3ff] << 8);
|
|
|
|
}
|
|
|
|
g_access(data : number) : number {
|
|
|
|
let cb = this.regs[0x18];
|
|
|
|
let vadr = (this.rc & 7) | ((data & 0xff) << 3) | ((cb & 0xe) << 10);
|
|
|
|
this.vc = (this.vc + 1) & 0x3ff;
|
|
|
|
//this.vcbase = (this.vcbase + 1) & 0x3ff;
|
|
|
|
//this.platform.profiler && this.platform.profiler.logRead(vadr);
|
|
|
|
return this.platform.readVRAMAddress(vadr);
|
|
|
|
}
|
|
|
|
getx() : number {
|
|
|
|
// TODO: left border 0x1f, 0x18
|
|
|
|
return (this.cycle - 16)*8 + numBorderPixels;
|
|
|
|
}
|
|
|
|
clockPulse() {
|
|
|
|
switch (this.cycle) {
|
|
|
|
case 14:
|
|
|
|
this.vc = this.vcbase;
|
|
|
|
//console.log("VC ->",hex(this.vc));
|
|
|
|
this.vmli = 0;
|
|
|
|
break;
|
|
|
|
case 58:
|
|
|
|
if (this.rc == 7) {
|
|
|
|
this.vcbase = this.vc;
|
|
|
|
//console.log("VCBASE",hex(this.vc));
|
|
|
|
// TODO
|
|
|
|
}
|
|
|
|
this.rc = (this.rc + 1) & 7;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
let x = this.getx();
|
|
|
|
if (this.cycle >= 16 && this.cycle < 56) {
|
|
|
|
var cdata = this.c_access();
|
|
|
|
let gdata = this.g_access(cdata);
|
|
|
|
let fgcol = cdata >> 8;
|
|
|
|
let bgcol = this.regs[0x21];
|
|
|
|
for (let i=0; i<8; i++) {
|
|
|
|
this.pixels[x+i] = (gdata & 0x80) ? fgcol : bgcol;
|
|
|
|
gdata <<= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.cycle++;
|
|
|
|
}
|
|
|
|
doDMA() {
|
|
|
|
// TODO
|
|
|
|
//let bus = this.platform.bus;
|
|
|
|
//let profiler = this.platform.profiler;
|
|
|
|
if (true) { //this.isDMAEnabled()) {
|
|
|
|
}
|
|
|
|
return 0; //TODO
|
|
|
|
}
|
|
|
|
doInterrupt() : boolean {
|
|
|
|
return false; // TODO
|
|
|
|
}
|
|
|
|
static stateToLongString(state) : string {
|
|
|
|
let s = "";
|
|
|
|
s += dumpRAM(state.regs, 0, 64);
|
|
|
|
//s += "\nScanline: " + state.scanline;
|
|
|
|
//s += "\n DLL: $" + hex((state.regs[0x0c] << 8) + state.regs[0x10],4) + " @ $" + hex(state.dll,4);
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const cpuFrequency = 10227273; // NTSC
|
|
|
|
const linesPerFrame = 263; // (6567R8)
|
|
|
|
const firstScanline = 0x30;
|
|
|
|
const lastScanline = 0xf7;
|
|
|
|
const numVisibleLines = 235; // (6567R8)
|
|
|
|
const numScreenPixels = 320;
|
|
|
|
const numBorderPixels = 16;
|
|
|
|
const numVisiblePixels = numScreenPixels+numBorderPixels*2; // (6567R8)
|
|
|
|
const cpuClocksPerLine = 65; // 65*8 (6567R8)
|
|
|
|
const cpuClocksPreDMA = 7; // TODO
|
|
|
|
const audioOversample = 4;
|
|
|
|
const audioSampleRate = linesPerFrame*60*audioOversample;
|
|
|
|
|
|
|
|
export class C64 extends BasicMachine implements RasterFrameBased {
|
|
|
|
|
|
|
|
cpuFrequency = cpuFrequency;
|
|
|
|
canvasWidth = numVisiblePixels;
|
|
|
|
overscan = true;
|
|
|
|
numTotalScanlines = linesPerFrame;
|
|
|
|
numVisibleScanlines = numVisibleLines;
|
|
|
|
defaultROMSize = 0x6000;
|
|
|
|
cpuCyclesPerLine = 65; // 65*8 (6567R8)
|
|
|
|
cpuCyclesPreDMA = 7; // TODO
|
|
|
|
sampleRate = audioSampleRate;
|
|
|
|
|
|
|
|
cpu : MOS6502;
|
|
|
|
ram : Uint8Array;
|
|
|
|
rom : Uint8Array; // cartridge ROM
|
|
|
|
bios : Uint8Array;
|
|
|
|
vic : VIC_II = new VIC_II(this);
|
|
|
|
cia1 : CIA = new CIA();
|
|
|
|
cia2 : CIA = new CIA();
|
|
|
|
probeDMABus;
|
|
|
|
lastFrameCycles : number = 0;
|
|
|
|
|
|
|
|
enableKERNAL : boolean = true;
|
|
|
|
enableIO : boolean = true;
|
|
|
|
enableBASIC : boolean = true;
|
|
|
|
enableCART : boolean = true;
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
this.cpu = new MOS6502();
|
|
|
|
this.ram = new Uint8Array(0x10000); // 64 KB, of course
|
|
|
|
this.bios = new lzgmini().decode(stringToByteArray(atob(C64_BIOS_LZG))); // BASIC-CHAR-KERNAL ROMs
|
|
|
|
this.connectCPUMemoryBus(this);
|
|
|
|
this.probeDMABus = this.probeIOBus(this);
|
|
|
|
this.handler = newKeyboardHandler(this.inputs, C64_KEYCODE_MAP, this.getKeyboardFunction(), true);
|
|
|
|
}
|
|
|
|
|
|
|
|
read = newAddressDecoder([
|
|
|
|
[0x8000, 0x9fff, 0x1fff, (a) => { return this.enableCART ? (this.rom&&this.rom[a]) : this.ram[a|0x8000]; }], // CART ROM
|
|
|
|
[0xa000, 0xbfff, 0x1fff, (a) => { return this.enableBASIC ? this.bios[a] : this.ram[a|0xa000]; }], // BASIC ROM
|
|
|
|
[0xd000, 0xdfff, 0xfff, (a) => { return !this.enableIO ? this.bios[a + 0x2000] : this.readIO(a) }], // CHAR ROM
|
|
|
|
[0xe000, 0xffff, 0x1fff, (a) => { return this.enableKERNAL ? this.bios[a + 0x3000] : this.ram[a|0xe000]; }], // KERNAL ROM
|
|
|
|
[0x0000, 0xffff, 0xffff, (a) => { return this.ram[a]; }],
|
|
|
|
]);
|
|
|
|
|
|
|
|
write = newAddressDecoder([
|
|
|
|
[0x0000, 0x0001, 0xffff, (a,v) => { this.write6510(a,v); }],
|
|
|
|
[0xd000, 0xdfff, 0xfff, (a,v) => { this.writeIO(a,v); }],
|
|
|
|
[0x0000, 0xffff, 0xffff, (a,v) => { this.ram[a] = v; }],
|
|
|
|
]);
|
|
|
|
|
|
|
|
getKeyboardMap() { return null; /* TODO: C64_KEYCODE_MAP;*/ }
|
|
|
|
|
|
|
|
// http://map.grauw.nl/articles/keymatrix.php
|
|
|
|
// https://codebase64.org/doku.php?id=base:reading_the_keyboard
|
|
|
|
// http://www.c64os.com/post?p=45
|
|
|
|
// https://www.c64-wiki.com/wiki/Keyboard
|
|
|
|
|
|
|
|
getKeyboardFunction() {
|
|
|
|
return (o,key,code,flags) => {
|
|
|
|
//console.log(o,key,code,flags);
|
|
|
|
var keymap = C64_KEYMATRIX_NOSHIFT;
|
|
|
|
for (var i=0; i<keymap.length; i++) {
|
|
|
|
if (keymap[i] && keymap[i].c == key) {
|
|
|
|
let row = i >> 3;
|
|
|
|
let col = i & 7;
|
|
|
|
// is column selected?
|
|
|
|
if (flags & KeyFlags.KeyDown) {
|
|
|
|
this.inputs[KEYBOARD_ROW_0 + row] |= (1<<col);
|
|
|
|
} else if (flags & KeyFlags.KeyUp) {
|
|
|
|
this.inputs[KEYBOARD_ROW_0 + row] &= ~(1<<col);
|
|
|
|
}
|
|
|
|
//console.log(key, row, col, hex(this.inputs[KEYBOARD_ROW_0 + row]));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: https://www.c64-wiki.com/wiki/Zeropage
|
|
|
|
write6510(a:number, v:number) {
|
|
|
|
this.ram[a] = v;
|
|
|
|
switch (a) {
|
|
|
|
case 0:
|
|
|
|
this.enableBASIC = (v & 0x1) != 0; // LORAM
|
|
|
|
this.enableKERNAL = (v & 0x2) != 0; // HIRAM
|
|
|
|
this.enableIO = (v & 0x4) != 0; // CHAREN
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
writeIO(a:number, v:number) {
|
|
|
|
//this.profiler && this.profiler.logWrite(a+0xd000);
|
|
|
|
var page = (a>>8);
|
|
|
|
switch (page) {
|
|
|
|
case 0x0: case 0x1: case 0x2: case 0x3:
|
|
|
|
this.vic.write(a & 0x3f, v);
|
|
|
|
break;
|
|
|
|
case 0x8: case 0x9: case 0xa: case 0xb:
|
|
|
|
this.vic.cram[a & 0x3ff] = v;
|
|
|
|
break;
|
|
|
|
case 0xc:
|
|
|
|
this.cia1.write(a & 0xf, v);
|
|
|
|
break;
|
|
|
|
case 0xd:
|
|
|
|
this.cia2.write(a & 0xf, v);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return; //TODO
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
readIO(a:number) : number {
|
|
|
|
//this.profiler && this.profiler.logRead(a+0xd000);
|
|
|
|
var page = (a>>8);
|
|
|
|
switch (page) {
|
|
|
|
case 0x0: case 0x1: case 0x2: case 0x3: // VIC-II
|
|
|
|
return this.vic.read(a & 0x3f);
|
|
|
|
case 0x8: case 0x9: case 0xa: case 0xb:
|
|
|
|
return this.vic.cram[a & 0x3ff];
|
|
|
|
case 0xc:
|
|
|
|
switch (a & 0xf) {
|
|
|
|
// scan keyboard matrix for CIA 1
|
|
|
|
// CIA 1 regs: [00, 00, ff, 00] or [ff, 00, ff, 00]
|
|
|
|
// http://www.c64os.com/post?p=45
|
|
|
|
case 0x1:
|
|
|
|
let cols = 0;
|
|
|
|
for (let i=0; i<8; i++)
|
|
|
|
if ((this.cia1.regs[0] & (1<<i)) == 0)
|
|
|
|
cols |= this.inputs[KEYBOARD_ROW_0 + i];
|
|
|
|
//if (cols) console.log(this.cia1.regs[0], cols);
|
|
|
|
return cols ^ 0xff;
|
|
|
|
}
|
|
|
|
return this.cia1.read(a & 0xf);
|
|
|
|
case 0xd:
|
|
|
|
return this.cia2.read(a & 0xf);
|
|
|
|
default:
|
|
|
|
return 0; //TODO
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
readVRAMAddress(a:number) {
|
|
|
|
let bank = ~this.cia2.regs[0] & 3; // CIA 2 port A
|
|
|
|
a &= 0x3fff; // VIC II sees 14 bits
|
|
|
|
if ((a >= 0x1000 && a < 0x2000) && (bank == 0 || bank == 2)) return this.bios[0x1000 + a]; // CHAR ROM
|
|
|
|
else return this.ram[a];
|
|
|
|
}
|
|
|
|
|
2019-09-08 17:57:19 +00:00
|
|
|
advanceFrame(trap) : number {
|
2019-09-08 17:36:26 +00:00
|
|
|
var idata = this.pixels;
|
|
|
|
var iofs = 0;
|
|
|
|
var vicClocks = cpuClocksPreDMA;
|
|
|
|
var fc = 0;
|
|
|
|
this.probe.logNewFrame();
|
|
|
|
// visible lines
|
|
|
|
for (var sl=0; sl<linesPerFrame; sl++) {
|
|
|
|
this.vic.setScanline(sl);
|
|
|
|
// interrupt?
|
|
|
|
// TODO: https://www.c64-wiki.com/wiki/Raster_interrupt
|
|
|
|
if (this.vic.regs[0x19] & 0x1) {
|
|
|
|
this.vic.regs[0x19] &= 0x7e;
|
2019-09-08 17:57:19 +00:00
|
|
|
//TODO: this.cpu.IRQ();
|
2019-09-08 17:36:26 +00:00
|
|
|
//this.profiler && this.profiler.logInterrupt(0x1);
|
|
|
|
}
|
|
|
|
// is this scanline visible?
|
|
|
|
let visible = sl >= firstScanline && sl <= lastScanline;
|
|
|
|
// iterate CPU with free clocks
|
|
|
|
while (vicClocks > 0) {
|
|
|
|
// next CPU clock
|
|
|
|
vicClocks--;
|
|
|
|
if (trap && trap()) {
|
|
|
|
trap = null;
|
|
|
|
sl = 999;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (visible) this.vic.clockPulse(); // VIC first
|
|
|
|
this.advanceCPU();
|
|
|
|
fc++;
|
|
|
|
}
|
|
|
|
vicClocks += cpuClocksPerLine;
|
|
|
|
if (visible) {
|
|
|
|
// do DMA for scanline?
|
|
|
|
vicClocks -= this.vic.doDMA();
|
|
|
|
}
|
|
|
|
// copy line to frame buffer
|
|
|
|
if (idata && sl > firstScanline-24 && iofs < idata.length) {
|
|
|
|
for (var i=0; i<numVisiblePixels; i++) {
|
|
|
|
idata[iofs++] = COLORS_RGBA[this.vic.pixels[i]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.probe.logNewScanline();
|
|
|
|
}
|
|
|
|
return this.lastFrameCycles = fc;
|
|
|
|
}
|
|
|
|
|
|
|
|
getRasterX() { return this.lastFrameCycles % this.cpuCyclesPerLine; }
|
|
|
|
getRasterY() { return Math.floor(this.lastFrameCycles / this.cpuCyclesPerLine); }
|
|
|
|
|
|
|
|
loadROM(data) {
|
|
|
|
// BASIC stub?
|
|
|
|
if (data[0] == 0x01 && data[1] == 0x08) {
|
|
|
|
this.ram.set(data.slice(2), 0x801);
|
|
|
|
this.enableCART = false;
|
|
|
|
// hack BASIC interpreter loop (TODO?)
|
|
|
|
var prgstart = 0x80a; //TODO (this.debugSymbols && this.debugSymbols.symbolmap['__MAIN_START__']) || 0x80a;
|
|
|
|
this.bios[0x3f9b] = 0x4c;
|
|
|
|
this.bios[0x3f9c] = prgstart & 0xff;
|
|
|
|
this.bios[0x3f9d] = prgstart >> 8;
|
|
|
|
} else {
|
|
|
|
// assume cartridge ROM
|
|
|
|
this.rom = padBytes(data, this.defaultROMSize);
|
|
|
|
}
|
|
|
|
this.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
// BASIC (0x2000 bytes)
|
|
|
|
// CHAR (0x1000 bytes)
|
|
|
|
// KERNAL (0x2000 bytes)
|
|
|
|
loadBIOS(data) {
|
|
|
|
this.bios = padBytes(data, 0x5000);
|
|
|
|
this.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
reset() {
|
|
|
|
super.reset();
|
|
|
|
this.vic.reset();
|
|
|
|
this.cia1.reset();
|
|
|
|
this.cia2.reset();
|
|
|
|
this.write6510(0, 0xff);
|
|
|
|
this.inputs.fill(0x0);
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: don't log if profiler active
|
|
|
|
readConst(addr : number) {
|
|
|
|
return this.read(addr) | 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
loadState(state) {
|
|
|
|
this.cpu.loadState(state.c);
|
|
|
|
this.ram.set(state.ram);
|
|
|
|
this.vic.loadState(state.vic);
|
|
|
|
this.loadControlsState(state);
|
|
|
|
}
|
|
|
|
saveState() {
|
|
|
|
return {
|
|
|
|
c:this.getCPUState(),
|
|
|
|
ram:this.ram.slice(0),
|
|
|
|
vic:this.vic.saveState(),
|
|
|
|
inputs:this.inputs.slice(0)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
loadControlsState(state) {
|
|
|
|
this.inputs.set(state.inputs);
|
|
|
|
}
|
|
|
|
|
|
|
|
saveControlsState() {
|
|
|
|
return {
|
|
|
|
inputs:this.inputs.slice(0)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
getCPUState() {
|
|
|
|
return this.cpu.saveState();
|
|
|
|
}
|
|
|
|
|
|
|
|
getRasterScanline() {
|
|
|
|
return this.vic.scanline;
|
|
|
|
}
|
|
|
|
|
|
|
|
getDebugCategories() {
|
2019-09-08 17:57:19 +00:00
|
|
|
return ['CPU','Stack','VIC-II'];
|
2019-09-08 17:36:26 +00:00
|
|
|
}
|
|
|
|
getDebugInfo(category, state) {
|
|
|
|
switch (category) {
|
|
|
|
case 'VIC-II': return VIC_II.stateToLongString(state.vic);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const VIC_NTSC_RGB = [
|
|
|
|
0x000000,
|
|
|
|
0xFFFFFF,
|
|
|
|
0x880000,
|
|
|
|
0xAAFFEE,
|
|
|
|
0xCC44CC,
|
|
|
|
0x00CC55,
|
|
|
|
0x00CC55,
|
|
|
|
0xEEEE77,
|
|
|
|
0xDD8855,
|
|
|
|
0x664400,
|
|
|
|
0xFF7777,
|
|
|
|
0x333333,
|
|
|
|
0x777777,
|
|
|
|
0xAAFF66,
|
|
|
|
0x0088FF,
|
|
|
|
0xBBBBBB,
|
|
|
|
];
|
|
|
|
|
|
|
|
var COLORS_RGBA = new Uint32Array(256);
|
|
|
|
var COLORS_WEB = [];
|
|
|
|
for (var i=0; i<256; i++) {
|
|
|
|
COLORS_RGBA[i] = rgb2bgr(VIC_NTSC_RGB[i & 15]) | 0xff000000;
|
|
|
|
COLORS_WEB[i] = "#"+hex(VIC_NTSC_RGB[i & 15],6);
|
|
|
|
}
|
|
|
|
|
|
|
|
// bank-switching table
|
|
|
|
// TODO: https://www.c64-wiki.com/wiki/Bank_Switching
|
|
|
|
|
|
|
|
enum BankSwitchFlags {
|
|
|
|
LORAM=1, HIRAM=2, CHAREN=4, _GAME=8, _EXROM=16
|
|
|
|
};
|
|
|
|
|
|
|
|
enum Bank {
|
|
|
|
NONE, RAM, IO, CART_ROM_LO, CART_ROM_HI, CHAR_ROM, KERN_ROM, BASIC_ROM
|
|
|
|
};
|
|
|
|
|
|
|
|
function getBankMapping(flags:number, region:number) : Bank {
|
|
|
|
return BANK_TABLE[flags & 0x1f][region];
|
|
|
|
};
|
|
|
|
|
|
|
|
const BANK_TABLE : Bank[][] = [
|
|
|
|
[ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM ],
|
|
|
|
[ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM ],
|
|
|
|
[ Bank.RAM, Bank.RAM, Bank.RAM, Bank.CART_ROM_HI, Bank.RAM, Bank.CHAR_ROM, Bank.KERN_ROM ],
|
|
|
|
[ Bank.RAM, Bank.RAM, Bank.CART_ROM_LO, Bank.CART_ROM_HI, Bank.RAM, Bank.CHAR_ROM, Bank.KERN_ROM ],
|
|
|
|
[ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM ],
|
|
|
|
[ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.IO, Bank.RAM ],
|
|
|
|
[ Bank.RAM, Bank.RAM, Bank.RAM, Bank.CART_ROM_HI, Bank.RAM, Bank.IO, Bank.KERN_ROM ],
|
|
|
|
[ Bank.RAM, Bank.RAM, Bank.CART_ROM_LO, Bank.CART_ROM_HI, Bank.RAM, Bank.IO, Bank.KERN_ROM ],
|
|
|
|
[ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM ],
|
|
|
|
[ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.CHAR_ROM, Bank.RAM ],
|
|
|
|
[ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.CHAR_ROM, Bank.KERN_ROM ],
|
|
|
|
[ Bank.RAM, Bank.RAM, Bank.CART_ROM_LO, Bank.BASIC_ROM, Bank.RAM, Bank.CHAR_ROM, Bank.KERN_ROM ],
|
|
|
|
[ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM ],
|
|
|
|
[ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.IO, Bank.RAM ],
|
|
|
|
[ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.IO, Bank.KERN_ROM ],
|
|
|
|
[ Bank.RAM, Bank.RAM, Bank.CART_ROM_LO, Bank.BASIC_ROM, Bank.RAM, Bank.IO, Bank.KERN_ROM ],
|
|
|
|
[ Bank.RAM, Bank.NONE, Bank.CART_ROM_LO, Bank.NONE, Bank.NONE, Bank.IO, Bank.CART_ROM_HI ],
|
|
|
|
[ Bank.RAM, Bank.NONE, Bank.CART_ROM_LO, Bank.NONE, Bank.NONE, Bank.IO, Bank.CART_ROM_HI ],
|
|
|
|
[ Bank.RAM, Bank.NONE, Bank.CART_ROM_LO, Bank.NONE, Bank.NONE, Bank.IO, Bank.CART_ROM_HI ],
|
|
|
|
[ Bank.RAM, Bank.NONE, Bank.CART_ROM_LO, Bank.NONE, Bank.NONE, Bank.IO, Bank.CART_ROM_HI ],
|
|
|
|
[ Bank.RAM, Bank.NONE, Bank.CART_ROM_LO, Bank.NONE, Bank.NONE, Bank.IO, Bank.CART_ROM_HI ],
|
|
|
|
[ Bank.RAM, Bank.NONE, Bank.CART_ROM_LO, Bank.NONE, Bank.NONE, Bank.IO, Bank.CART_ROM_HI ],
|
|
|
|
[ Bank.RAM, Bank.NONE, Bank.CART_ROM_LO, Bank.NONE, Bank.NONE, Bank.IO, Bank.CART_ROM_HI ],
|
|
|
|
[ Bank.RAM, Bank.NONE, Bank.CART_ROM_LO, Bank.NONE, Bank.NONE, Bank.IO, Bank.CART_ROM_HI ],
|
|
|
|
[ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM ],
|
|
|
|
[ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.CHAR_ROM, Bank.RAM ],
|
|
|
|
[ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.CHAR_ROM, Bank.KERN_ROM ],
|
|
|
|
[ Bank.RAM, Bank.RAM, Bank.RAM, Bank.BASIC_ROM, Bank.RAM, Bank.CHAR_ROM, Bank.KERN_ROM ],
|
|
|
|
[ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM ],
|
|
|
|
[ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.IO, Bank.RAM ],
|
|
|
|
[ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.IO, Bank.KERN_ROM ],
|
|
|
|
[ Bank.RAM, Bank.RAM, Bank.RAM, Bank.BASIC_ROM, Bank.RAM, Bank.IO, Bank.KERN_ROM ],
|
|
|
|
];
|
|
|
|
|
|
|
|
// https://github.com/MEGA65/open-roms
|
|
|
|
var C64_BIOS_LZG = `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
|