mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2025-01-30 12:35:18 +00:00
sms: made into classes, more vdp fixed (still need to pass vdptest); debug fn bound to platform
This commit is contained in:
parent
23f32b2dd3
commit
2dd5d88d55
@ -83,7 +83,7 @@ export interface Platform {
|
||||
runToPC?(pc:number) : void;
|
||||
runUntilReturn?() : void;
|
||||
stepBack?() : void;
|
||||
runEval?(evalfunc/* : DebugEvalCondition*/) : void;
|
||||
runEval?(evalfunc : DebugEvalCondition) : void;
|
||||
|
||||
getOpcodeMetadata?(opcode:number, offset:number) : OpcodeMetadata; //TODO
|
||||
getSP?() : number;
|
||||
@ -113,9 +113,9 @@ export interface MemoryBus {
|
||||
write : (address:number, value:number) => void;
|
||||
}
|
||||
|
||||
type DebugCondition = () => boolean;
|
||||
type DebugEvalCondition = (c:CpuState) => boolean;
|
||||
type BreakpointCallback = (EmuState) => void;
|
||||
export type DebugCondition = () => boolean;
|
||||
export type DebugEvalCondition = (c:CpuState) => boolean;
|
||||
export type BreakpointCallback = (s:EmuState) => void;
|
||||
|
||||
export interface EmuRecorder {
|
||||
frameRequested() : boolean;
|
||||
|
@ -47,182 +47,294 @@ var SG1000_KEYCODE_MAP = makeKeycodeMap([
|
||||
[Keys.VK_1, 1, 0x10],
|
||||
]);
|
||||
|
||||
/// standard emulator
|
||||
class SG1000Platform extends BaseZ80Platform {
|
||||
|
||||
const _SG1000Platform = function(mainElement, isSMS:boolean) {
|
||||
cpuFrequency = 3579545; // MHz
|
||||
canvasWidth = 304;
|
||||
numTotalScanlines = 262;
|
||||
numVisibleScanlines = 240;
|
||||
cpuCyclesPerLine;
|
||||
|
||||
const cpuFrequency = 3579545; // MHz
|
||||
const canvasWidth = 304;
|
||||
const numTotalScanlines = 262;
|
||||
const numVisibleScanlines = 240;
|
||||
const cpuCyclesPerLine = Math.round(cpuFrequency / 60 / numTotalScanlines);
|
||||
cpu;
|
||||
ram : RAM;
|
||||
membus;
|
||||
iobus;
|
||||
rom = new Uint8Array(0);
|
||||
video;
|
||||
vdp;
|
||||
timer;
|
||||
audio;
|
||||
psg;
|
||||
inputs = new Uint8Array(4);
|
||||
mainElement : HTMLElement;
|
||||
|
||||
var cpu, ram, membus, iobus, rom;
|
||||
var video, vdp, timer;
|
||||
var audio, psg;
|
||||
var inputs = new Uint8Array(4);
|
||||
|
||||
class SG1000Platform extends BaseZ80Platform implements Platform {
|
||||
isSMS = false; // TODO: remove
|
||||
currentScanline : number;
|
||||
|
||||
currentScanline;
|
||||
constructor(mainElement : HTMLElement) {
|
||||
super();
|
||||
this.mainElement = mainElement;
|
||||
}
|
||||
|
||||
getPresets() { return SG1000_PRESETS; }
|
||||
|
||||
start() {
|
||||
var ramSize = isSMS ? 0x2000 : 0x400;
|
||||
ram = new RAM(ramSize);
|
||||
membus = {
|
||||
read: newAddressDecoder([
|
||||
[0xc000, 0xffff, ramSize-1, function(a) { return ram.mem[a]; }],
|
||||
[0x0000, 0xbfff, 0xffff, function(a) { return rom ? rom[a] : 0; }],
|
||||
]),
|
||||
write: newAddressDecoder([
|
||||
[0xc000, 0xffff, ramSize-1, function(a,v) { ram.mem[a] = v; }],
|
||||
]),
|
||||
isContended: function() { return false; },
|
||||
};
|
||||
iobus = {
|
||||
read: (addr:number) => {
|
||||
addr &= 0xff;
|
||||
//console.log('IO read', hex(addr,4));
|
||||
switch (addr & 0xc1) {
|
||||
case 0x40: return isSMS ? this.currentScanline : 0;
|
||||
case 0x80: return vdp.readData();
|
||||
case 0x81: return vdp.readStatus();
|
||||
case 0xc0: return inputs[0] ^ 0xff;
|
||||
case 0xc1: return inputs[1] ^ 0xff;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
write: (addr:number, val:number) => {
|
||||
addr &= 0xff;
|
||||
val &= 0xff;
|
||||
//console.log('IO write', hex(addr,4), hex(val,2));
|
||||
switch (addr & 0xc1) {
|
||||
case 0x80: return vdp.writeData(val);
|
||||
case 0x81: return vdp.writeAddress(val);
|
||||
case 0x40:
|
||||
case 0x41: return psg.setData(val);
|
||||
}
|
||||
}
|
||||
};
|
||||
cpu = this.newCPU(membus, iobus);
|
||||
video = new RasterVideo(mainElement,canvasWidth,numVisibleScanlines,{overscan:true});
|
||||
video.create();
|
||||
audio = new MasterAudio();
|
||||
psg = new SN76489_Audio(audio);
|
||||
var cru = {
|
||||
setVDPInterrupt: (b) => {
|
||||
if (b) {
|
||||
cpu.nonMaskableInterrupt();
|
||||
} else {
|
||||
// TODO: reset interrupt?
|
||||
}
|
||||
getPresets() { return SG1000_PRESETS; }
|
||||
|
||||
newRAM() {
|
||||
return new RAM(0x400);
|
||||
}
|
||||
|
||||
newMembus() {
|
||||
var ramSize = this.ram.mem.length;
|
||||
return {
|
||||
read: newAddressDecoder([
|
||||
[0xc000, 0xffff, ramSize-1, (a) => { return this.ram.mem[a]; }],
|
||||
[0x0000, 0xbfff, 0xffff, (a) => { return this.rom[a]; }],
|
||||
]),
|
||||
write: newAddressDecoder([
|
||||
[0xc000, 0xffff, ramSize-1, (a,v) => { this.ram.mem[a] = v; }],
|
||||
]),
|
||||
isContended: () => { return false; },
|
||||
};
|
||||
}
|
||||
|
||||
newIOBus() {
|
||||
return {
|
||||
read: (addr:number) => {
|
||||
addr &= 0xff;
|
||||
//console.log('IO read', hex(addr,4));
|
||||
switch (addr & 0xc1) {
|
||||
case 0x40: return this.isSMS ? this.currentScanline : 0;
|
||||
case 0x80: return this.vdp.readData();
|
||||
case 0x81: return this.vdp.readStatus();
|
||||
case 0xc0: return this.inputs[0] ^ 0xff;
|
||||
case 0xc1: return this.inputs[1] ^ 0xff;
|
||||
}
|
||||
};
|
||||
var vdpclass = isSMS ? SMSVDP : TMS9918A;
|
||||
vdp = new vdpclass(video.getFrameData(), cru, true); // true = 4 sprites/line
|
||||
setKeyboardFromMap(video, inputs, SG1000_KEYCODE_MAP);
|
||||
timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
||||
}
|
||||
|
||||
readAddress(addr) {
|
||||
return membus.read(addr);
|
||||
}
|
||||
|
||||
advance(novideo : boolean) {
|
||||
for (var sl=0; sl<numTotalScanlines; sl++) {
|
||||
this.currentScanline = sl;
|
||||
this.runCPU(cpu, cpuCyclesPerLine);
|
||||
vdp.drawScanline(sl);
|
||||
}
|
||||
video.updateFrame();
|
||||
}
|
||||
|
||||
loadROM(title, data) {
|
||||
if (data.length < 0xc000) {
|
||||
rom = padBytes(data, 0xc000);
|
||||
} else {
|
||||
switch (data.length) {
|
||||
case 0x10000:
|
||||
case 0x20000:
|
||||
case 0x40000:
|
||||
rom = data;
|
||||
break;
|
||||
default:
|
||||
throw "Unknown rom size: $" + hex(data.length);
|
||||
return 0;
|
||||
},
|
||||
write: (addr:number, val:number) => {
|
||||
addr &= 0xff;
|
||||
val &= 0xff;
|
||||
//console.log('IO write', hex(addr,4), hex(val,2));
|
||||
switch (addr & 0xc1) {
|
||||
case 0x80: return this.vdp.writeData(val);
|
||||
case 0x81: return this.vdp.writeAddress(val);
|
||||
case 0x40:
|
||||
case 0x41: return this.psg.setData(val);
|
||||
}
|
||||
}
|
||||
this.reset();
|
||||
}
|
||||
|
||||
loadState(state) {
|
||||
cpu.loadState(state.c);
|
||||
ram.mem.set(state.b);
|
||||
vdp.restoreState(state.vdp);
|
||||
inputs.set(state.in);
|
||||
}
|
||||
saveState() {
|
||||
return {
|
||||
c:this.getCPUState(),
|
||||
b:ram.mem.slice(0),
|
||||
vdp:vdp.getState(),
|
||||
in:inputs.slice(0),
|
||||
};
|
||||
}
|
||||
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();
|
||||
cpu.setTstates(0);
|
||||
vdp.reset();
|
||||
psg.reset();
|
||||
}
|
||||
|
||||
getDebugCategories() {
|
||||
return super.getDebugCategories().concat(['VDP']);
|
||||
}
|
||||
getDebugInfo(category, state) {
|
||||
switch (category) {
|
||||
case 'VDP': return this.vdpStateToLongString(state.vdp);
|
||||
default: return super.getDebugInfo(category, state);
|
||||
};
|
||||
}
|
||||
|
||||
newVDP(frameData, cru, flicker) {
|
||||
return new TMS9918A(frameData, cru, flicker);
|
||||
}
|
||||
|
||||
start() {
|
||||
this.cpuCyclesPerLine = Math.round(this.cpuFrequency / 60 / this.numTotalScanlines);
|
||||
this.ram = this.newRAM();
|
||||
this.membus = this.newMembus();
|
||||
this.iobus = this.newIOBus();
|
||||
this.cpu = this.newCPU(this.membus, this.iobus);
|
||||
this.video = new RasterVideo(this.mainElement, this.canvasWidth, this.numVisibleScanlines, {overscan:true});
|
||||
this.video.create();
|
||||
this.audio = new MasterAudio();
|
||||
this.psg = new SN76489_Audio(this.audio);
|
||||
var cru = {
|
||||
setVDPInterrupt: (b) => {
|
||||
if (b) {
|
||||
this.cpu.nonMaskableInterrupt();
|
||||
} else {
|
||||
// TODO: reset interrupt?
|
||||
}
|
||||
}
|
||||
};
|
||||
this.vdp = this.newVDP(this.video.getFrameData(), cru, true); // true = 4 sprites/line
|
||||
setKeyboardFromMap(this.video, this.inputs, SG1000_KEYCODE_MAP); // TODO
|
||||
this.timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
||||
}
|
||||
|
||||
readAddress(addr) {
|
||||
return this.membus.read(addr);
|
||||
}
|
||||
|
||||
advance(novideo : boolean) {
|
||||
for (var sl=0; sl<this.numTotalScanlines; sl++) {
|
||||
this.currentScanline = sl;
|
||||
this.runCPU(this.cpu, this.cpuCyclesPerLine);
|
||||
this.vdp.drawScanline(sl);
|
||||
}
|
||||
vdpStateToLongString(ppu) {
|
||||
return vdp.getRegsString();
|
||||
this.video.updateFrame();
|
||||
}
|
||||
|
||||
loadROM(title, data) {
|
||||
this.rom = padBytes(data, 0xc000);
|
||||
this.reset();
|
||||
}
|
||||
|
||||
loadState(state) {
|
||||
this.cpu.loadState(state.c);
|
||||
this.ram.mem.set(state.b);
|
||||
this.vdp.restoreState(state.vdp);
|
||||
this.inputs.set(state.in);
|
||||
}
|
||||
saveState() {
|
||||
return {
|
||||
c:this.getCPUState(),
|
||||
b:this.ram.mem.slice(0),
|
||||
vdp:this.vdp.getState(),
|
||||
in:this.inputs.slice(0),
|
||||
};
|
||||
}
|
||||
loadControlsState(state) {
|
||||
this.inputs.set(state.in);
|
||||
}
|
||||
saveControlsState() {
|
||||
return {
|
||||
in:this.inputs.slice(0)
|
||||
};
|
||||
}
|
||||
getCPUState() {
|
||||
return this.cpu.saveState();
|
||||
}
|
||||
|
||||
isRunning() {
|
||||
return this.timer && this.timer.isRunning();
|
||||
}
|
||||
pause() {
|
||||
this.timer.stop();
|
||||
this.audio.stop();
|
||||
}
|
||||
resume() {
|
||||
this.timer.start();
|
||||
this.audio.start();
|
||||
}
|
||||
reset() {
|
||||
this.cpu.reset();
|
||||
this.cpu.setTstates(0);
|
||||
this.vdp.reset();
|
||||
this.psg.reset();
|
||||
}
|
||||
|
||||
getDebugCategories() {
|
||||
return super.getDebugCategories().concat(['VDP']);
|
||||
}
|
||||
getDebugInfo(category, state) {
|
||||
switch (category) {
|
||||
case 'VDP': return this.vdpStateToLongString(state.vdp);
|
||||
default: return super.getDebugInfo(category, state);
|
||||
}
|
||||
}
|
||||
return new SG1000Platform();
|
||||
vdpStateToLongString(ppu) {
|
||||
return this.vdp.getRegsString();
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
const _SMSPlatform = function(mainElement) {
|
||||
this.__proto__ = new (_SG1000Platform as any)(mainElement, true);
|
||||
class SMSPlatform extends SG1000Platform {
|
||||
|
||||
isSMS = true;
|
||||
|
||||
cartram : RAM = new RAM(0);
|
||||
pagingRegisters = new Uint8Array(4);
|
||||
romPageMask : number;
|
||||
// TODO: hide bottom scanlines
|
||||
|
||||
reset() {
|
||||
super.reset();
|
||||
this.pagingRegisters.set([0,0,1,2]);
|
||||
}
|
||||
|
||||
newVDP(frameData, cru, flicker) {
|
||||
return new SMSVDP(frameData, cru, flicker);
|
||||
}
|
||||
|
||||
newRAM() { return new RAM(0x2000); }
|
||||
|
||||
getPagedROM(a:number, reg:number) {
|
||||
//if (!(a&0xff)) console.log(hex(a), reg, this.pagingRegisters[reg], this.romPageMask);
|
||||
return this.rom[a + ((this.pagingRegisters[reg] & this.romPageMask) << 14)]; // * $4000
|
||||
}
|
||||
|
||||
newMembus() {
|
||||
return {
|
||||
read: newAddressDecoder([
|
||||
[0xc000, 0xffff, 0x1fff, (a) => { return this.ram.mem[a]; }],
|
||||
[0x0000, 0x03ff, 0x3ff, (a) => { return this.rom[a]; }],
|
||||
[0x0400, 0x3fff, 0x3fff, (a) => { return this.getPagedROM(a,1); }],
|
||||
[0x4000, 0x7fff, 0x3fff, (a) => { return this.getPagedROM(a,2); }],
|
||||
[0x8000, 0xbfff, 0x3fff, (a) => {
|
||||
var reg0 = this.pagingRegisters[0]; // RAM select?
|
||||
if (reg0 & 0x8) {
|
||||
return this.cartram.mem[(reg0 & 0x4) ? a+0x4000 : a];
|
||||
} else {
|
||||
return this.getPagedROM(a,3);
|
||||
}
|
||||
}],
|
||||
]),
|
||||
write: newAddressDecoder([
|
||||
[0xc000, 0xfffb, 0x1fff, (a,v) => {
|
||||
this.ram.mem[a] = v;
|
||||
}],
|
||||
[0xfffc, 0xffff, 0x3, (a,v) => {
|
||||
this.pagingRegisters[a] = v;
|
||||
this.ram.mem[a+0x1ffc] = v;
|
||||
}],
|
||||
[0x8000, 0xbfff, 0x3fff, (a,v) => {
|
||||
var reg0 = this.pagingRegisters[0]; // RAM select?
|
||||
if (reg0 & 0x8) {
|
||||
if (this.cartram.mem.length == 0)
|
||||
this.cartram = new RAM(0x8000); // create cartridge RAM lazily
|
||||
this.cartram.mem[(reg0 & 0x4) ? a+0x4000 : a] = v;
|
||||
}
|
||||
}],
|
||||
]),
|
||||
isContended: () => { return false; },
|
||||
};
|
||||
}
|
||||
|
||||
loadROM(title, data) {
|
||||
if (data.length <= 0xc000) {
|
||||
this.rom = padBytes(data, 0xc000);
|
||||
this.romPageMask = 3; // only pages 0, 1, 2
|
||||
} else {
|
||||
switch (data.length) {
|
||||
case 0x10000:
|
||||
case 0x20000:
|
||||
case 0x40000:
|
||||
case 0x80000:
|
||||
this.rom = data;
|
||||
this.romPageMask = (data.length >> 14) - 1; // div $4000
|
||||
break;
|
||||
default:
|
||||
throw "Unknown rom size: $" + hex(data.length);
|
||||
}
|
||||
}
|
||||
//console.log("romPageMask: " + hex(this.romPageMask));
|
||||
this.reset();
|
||||
}
|
||||
|
||||
loadState(state) {
|
||||
super.loadState(state);
|
||||
this.pagingRegisters.set(state.pr);
|
||||
this.cartram.mem.set(state.cr);
|
||||
}
|
||||
saveState() {
|
||||
var state = super.saveState();
|
||||
state['pr'] = this.pagingRegisters.slice(0);
|
||||
state['cr'] = this.cartram.mem.slice(0);
|
||||
return state;
|
||||
}
|
||||
getDebugInfo(category, state) {
|
||||
switch (category) {
|
||||
case 'CPU':
|
||||
return super.getDebugInfo(category, state) +
|
||||
"\nBank Regs: " + this.pagingRegisters + "\n";
|
||||
default: return super.getDebugInfo(category, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
PLATFORMS['sms-sg1000-libcv'] = _SG1000Platform;
|
||||
PLATFORMS['sms-sms-libcv'] = _SMSPlatform;
|
||||
PLATFORMS['sms-sg1000-libcv'] = SG1000Platform;
|
||||
PLATFORMS['sms-sms-libcv'] = SMSPlatform;
|
||||
|
10
src/ui.ts
10
src/ui.ts
@ -7,7 +7,7 @@ import * as bootstrap from "bootstrap";
|
||||
import { CodeProject } from "./project";
|
||||
import { WorkerResult, WorkerOutput, VerilogOutput, SourceFile, WorkerError, FileData } from "./workertypes";
|
||||
import { ProjectWindows } from "./windows";
|
||||
import { Platform, Preset, DebugSymbols } from "./baseplatform";
|
||||
import { Platform, Preset, DebugSymbols, DebugEvalCondition } from "./baseplatform";
|
||||
import { PLATFORMS } from "./emu";
|
||||
import * as Views from "./views";
|
||||
import { createNewPersistentStore } from "./store";
|
||||
@ -716,7 +716,7 @@ function runToCursor() {
|
||||
if (platform.runToPC) {
|
||||
platform.runToPC(pc);
|
||||
} else {
|
||||
platform.runEval(function(c) {
|
||||
platform.runEval((c) => {
|
||||
return c.PC == pc;
|
||||
});
|
||||
}
|
||||
@ -748,7 +748,7 @@ function resetAndDebug() {
|
||||
platform.reset();
|
||||
setupBreakpoint("reset");
|
||||
if (platform.runEval)
|
||||
platform.runEval(function(c) { return true; }); // break immediately
|
||||
platform.runEval((c) => { return true; }); // break immediately
|
||||
else
|
||||
; // TODO???
|
||||
} else {
|
||||
@ -760,9 +760,9 @@ var lastBreakExpr = "c.PC == 0x6000";
|
||||
function _breakExpression() {
|
||||
var exprs = window.prompt("Enter break expression", lastBreakExpr);
|
||||
if (exprs) {
|
||||
var fn = new Function('c', 'return (' + exprs + ');');
|
||||
var fn = new Function('c', 'return (' + exprs + ');').bind(platform);
|
||||
setupBreakpoint();
|
||||
platform.runEval(fn);
|
||||
platform.runEval(fn as DebugEvalCondition);
|
||||
lastBreakExpr = exprs;
|
||||
}
|
||||
}
|
||||
|
@ -91,19 +91,14 @@ export class TMS9918A {
|
||||
RGBA(204, 204, 204),
|
||||
RGBA(255, 255, 255)
|
||||
];
|
||||
|
||||
this.reset();
|
||||
}
|
||||
|
||||
reset() {
|
||||
|
||||
var i;
|
||||
for (i = 0; i < this.ram.length; i++) {
|
||||
this.ram[i] = 0;
|
||||
}
|
||||
for (i = 0; i < this.registers.length; i++) {
|
||||
this.registers[i] = 0;
|
||||
}
|
||||
this.ram.fill(0);
|
||||
this.registers.fill(0);
|
||||
|
||||
this.addressRegister = 0;
|
||||
this.statusRegister = 0;
|
||||
|
||||
@ -499,6 +494,7 @@ export class TMS9918A {
|
||||
writeData(i:number) {
|
||||
this.ram[this.addressRegister++] = i;
|
||||
this.addressRegister &= this.ramMask;
|
||||
this.latch = false;
|
||||
this.redrawRequired = true;
|
||||
}
|
||||
|
||||
@ -516,6 +512,7 @@ export class TMS9918A {
|
||||
var i = this.prefetchByte;
|
||||
this.prefetchByte = this.ram[this.addressRegister++];
|
||||
this.addressRegister &= this.ramMask;
|
||||
this.latch = false;
|
||||
return i;
|
||||
}
|
||||
|
||||
@ -678,6 +675,13 @@ export class SMSVDP extends TMS9918A {
|
||||
registers = new Uint8Array(16); // 8 more registers (actually only 5)
|
||||
vramUntwiddled = new Uint8Array(0x8000);
|
||||
|
||||
reset() {
|
||||
super.reset();
|
||||
this.writeToCRAM = false;
|
||||
this.cram.fill(0);
|
||||
this.cpalette.fill(0);
|
||||
this.vramUntwiddled.fill(0);
|
||||
}
|
||||
updateMode(reg0:number, reg1:number) {
|
||||
if (reg0 & 0x04) {
|
||||
this.screenMode = TMS9918A_Mode.MODE4;
|
||||
@ -697,11 +701,12 @@ export class SMSVDP extends TMS9918A {
|
||||
}
|
||||
setVDPWriteRegister(i:number) {
|
||||
super.setVDPWriteRegister(i);
|
||||
this.writeToCRAM = false;
|
||||
//this.writeToCRAM = false; // TODO?
|
||||
this.ramMask = 0x3fff;
|
||||
}
|
||||
setVDPWriteCommand3(i:number) {
|
||||
this.writeToCRAM = true;
|
||||
//this.addressRegister &= 0x1f; // TODO?
|
||||
}
|
||||
writeData(i:number) {
|
||||
if (this.writeToCRAM) {
|
||||
@ -715,7 +720,18 @@ export class SMSVDP extends TMS9918A {
|
||||
super.writeData(i);
|
||||
this.writeTwiddled(oldAddress, i);
|
||||
}
|
||||
this.latch = false;
|
||||
}
|
||||
readData() : number {
|
||||
if (this.writeToCRAM) {
|
||||
var palindex = this.addressRegister++ & (this.cram.length-1);
|
||||
this.addressRegister &= this.ramMask;
|
||||
return this.cram[palindex];
|
||||
} else {
|
||||
return super.readData();
|
||||
}
|
||||
}
|
||||
|
||||
writeTwiddled(vdp_addr:number, val:number) {
|
||||
var planarBase = vdp_addr & 0x3ffc;
|
||||
var twiddledBase = planarBase * 2;
|
||||
|
Loading…
x
Reference in New Issue
Block a user