2022-08-31 00:57:30 +00:00
|
|
|
import { newPOKEYAudio, TssChannelAdapter } from "../common/audio";
|
2022-09-03 03:22:39 +00:00
|
|
|
import { Machine } from "../common/baseplatform";
|
2022-08-31 00:57:30 +00:00
|
|
|
import { MOS6502 } from "../common/cpu/MOS6502";
|
2022-09-03 03:22:39 +00:00
|
|
|
import { AcceptsKeyInput, AcceptsPaddleInput, AcceptsROM, BasicScanlineMachine, FrameBased, Probeable, TrapCondition, VideoSource } from "../common/devices";
|
|
|
|
import { KeyFlags, Keys, makeKeycodeMap, newAddressDecoder, newKeyboardHandler } from "../common/emu";
|
|
|
|
import { hex } from "../common/util";
|
2022-02-21 15:35:52 +00:00
|
|
|
import { BaseWASIMachine } from "../common/wasmplatform";
|
2023-07-08 20:12:17 +00:00
|
|
|
import { ANTIC, MODE_LINES, MODE_SHIFT } from "./chips/antic";
|
2022-09-01 15:57:33 +00:00
|
|
|
import { CONSOL, GTIA, TRIG0 } from "./chips/gtia";
|
|
|
|
import { POKEY } from "./chips/pokey";
|
2022-02-21 15:35:52 +00:00
|
|
|
|
2022-08-31 00:57:30 +00:00
|
|
|
const ATARI8_KEYMATRIX_INTL_NOSHIFT = [
|
2022-09-03 20:34:57 +00:00
|
|
|
Keys.VK_L, Keys.VK_J, Keys.VK_SEMICOLON, Keys.VK_F4, Keys.VK_F5, Keys.VK_K, Keys.VK_BACK_SLASH, Keys.VK_TILDE,
|
2022-09-01 22:26:06 +00:00
|
|
|
Keys.VK_O, null, Keys.VK_P, Keys.VK_U, Keys.VK_ENTER, Keys.VK_I, Keys.VK_MINUS2, Keys.VK_EQUALS2,
|
2022-09-03 20:34:57 +00:00
|
|
|
Keys.VK_V, Keys.VK_F7, Keys.VK_C, Keys.VK_F6, Keys.VK_F4, Keys.VK_B, Keys.VK_X, Keys.VK_Z,
|
2022-08-31 00:57:30 +00:00
|
|
|
Keys.VK_4, null, Keys.VK_3, Keys.VK_6, Keys.VK_ESCAPE, Keys.VK_5, Keys.VK_2, Keys.VK_1,
|
|
|
|
Keys.VK_COMMA, Keys.VK_SPACE, Keys.VK_PERIOD, Keys.VK_N, null, Keys.VK_M, Keys.VK_SLASH, null/*invert*/,
|
|
|
|
Keys.VK_R, null, Keys.VK_E, Keys.VK_Y, Keys.VK_TAB, Keys.VK_T, Keys.VK_W, Keys.VK_Q,
|
2022-09-01 22:26:06 +00:00
|
|
|
Keys.VK_9, null, Keys.VK_0, Keys.VK_7, Keys.VK_BACK_SPACE, Keys.VK_8, null, null,
|
2022-08-31 00:57:30 +00:00
|
|
|
Keys.VK_F, Keys.VK_H, Keys.VK_D, null, Keys.VK_CAPS_LOCK, Keys.VK_G, Keys.VK_S, Keys.VK_A,
|
|
|
|
];
|
|
|
|
|
|
|
|
//TODO
|
|
|
|
var ATARI8_KEYCODE_MAP = makeKeycodeMap([
|
|
|
|
[Keys.UP, 0, 0x1],
|
|
|
|
[Keys.DOWN, 0, 0x2],
|
|
|
|
[Keys.LEFT, 0, 0x4],
|
|
|
|
[Keys.RIGHT, 0, 0x8],
|
2022-09-03 23:15:20 +00:00
|
|
|
[{ c: 16, n: "Shift", plyr: 0, button: 0 }, 2, 0x1],
|
2022-08-31 00:57:30 +00:00
|
|
|
/*
|
|
|
|
[Keys.P2_UP, 0, 0x10],
|
|
|
|
[Keys.P2_DOWN, 0, 0x20],
|
|
|
|
[Keys.P2_LEFT, 0, 0x40],
|
|
|
|
[Keys.P2_RIGHT, 0, 0x80],
|
|
|
|
[Keys.P2_A, 3, 0x1],
|
|
|
|
*/
|
2022-09-03 20:34:57 +00:00
|
|
|
[Keys.VK_F1, 3, 0x1], // START
|
|
|
|
[Keys.VK_F2, 3, 0x2], // SELECT
|
|
|
|
[Keys.VK_F3, 3, 0x4], // OPTION
|
2022-09-01 15:57:33 +00:00
|
|
|
]);
|
2022-08-31 00:57:30 +00:00
|
|
|
|
|
|
|
|
2023-02-07 19:18:50 +00:00
|
|
|
export class Atari800 extends BasicScanlineMachine implements AcceptsPaddleInput {
|
2022-08-31 00:57:30 +00:00
|
|
|
|
|
|
|
// http://www.ataripreservation.org/websites/freddy.offenga/megazine/ISSUE5-PALNTSC.html
|
|
|
|
cpuFrequency = 1789773;
|
|
|
|
numTotalScanlines = 262;
|
|
|
|
cpuCyclesPerLine = 114;
|
2022-09-03 03:22:39 +00:00
|
|
|
canvasWidth = 336;
|
2022-09-01 22:26:06 +00:00
|
|
|
numVisibleScanlines = 224;
|
2022-09-03 03:22:39 +00:00
|
|
|
aspectRatio = this.canvasWidth / this.numVisibleScanlines * 0.857;
|
2022-09-01 22:26:06 +00:00
|
|
|
firstVisibleScanline = 16;
|
2022-09-03 03:22:39 +00:00
|
|
|
firstVisibleClock = (44 - 6) * 2; // ... to 215 * 2
|
2022-08-31 00:57:30 +00:00
|
|
|
defaultROMSize = 0x8000;
|
|
|
|
overscan = true;
|
2022-09-04 23:43:13 +00:00
|
|
|
audioOversample = 2;
|
2022-08-31 00:57:30 +00:00
|
|
|
sampleRate = this.numTotalScanlines * 60 * this.audioOversample;
|
2022-09-03 23:15:20 +00:00
|
|
|
run_address = -1;
|
2022-08-31 00:57:30 +00:00
|
|
|
|
|
|
|
cpu: MOS6502;
|
|
|
|
ram: Uint8Array;
|
|
|
|
bios: Uint8Array;
|
|
|
|
bus;
|
2022-09-01 15:57:33 +00:00
|
|
|
audio_pokey;
|
2022-08-31 00:57:30 +00:00
|
|
|
audioadapter;
|
|
|
|
antic: ANTIC;
|
|
|
|
gtia: GTIA;
|
2022-09-01 15:57:33 +00:00
|
|
|
irq_pokey: POKEY;
|
2022-08-31 00:57:30 +00:00
|
|
|
inputs = new Uint8Array(4);
|
|
|
|
linergb = new Uint32Array(this.canvasWidth);
|
|
|
|
lastdmabyte = 0;
|
|
|
|
keycode = 0;
|
2022-09-01 15:57:33 +00:00
|
|
|
cart_80 = false;
|
|
|
|
cart_a0 = false;
|
2022-09-03 03:22:39 +00:00
|
|
|
xexdata = null;
|
2022-09-03 20:34:57 +00:00
|
|
|
keyboard_active = true;
|
2022-09-03 23:15:20 +00:00
|
|
|
d500 = new Uint8Array(0x100);
|
2022-08-31 00:57:30 +00:00
|
|
|
// TODO: save/load vars
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
this.cpu = new MOS6502();
|
|
|
|
this.ram = new Uint8Array(0x10000);
|
|
|
|
this.bios = new Uint8Array(0x2800);
|
|
|
|
this.bus = this.newBus();
|
|
|
|
this.connectCPUMemoryBus(this.bus);
|
|
|
|
// create support chips
|
2022-09-01 15:57:33 +00:00
|
|
|
this.antic = new ANTIC(this.readDMA.bind(this), this.antic_nmi.bind(this));
|
2022-08-31 00:57:30 +00:00
|
|
|
this.gtia = new GTIA();
|
2022-09-01 15:57:33 +00:00
|
|
|
this.irq_pokey = new POKEY(this.pokey_irq.bind(this), () => this.antic.h);
|
|
|
|
this.audio_pokey = newPOKEYAudio(1);
|
|
|
|
this.audioadapter = new TssChannelAdapter(this.audio_pokey.pokey1, this.audioOversample, this.sampleRate);
|
2022-08-31 00:57:30 +00:00
|
|
|
this.handler = newKeyboardHandler(
|
|
|
|
this.inputs, ATARI8_KEYCODE_MAP, this.getKeyboardFunction(), true);
|
2022-09-03 23:15:20 +00:00
|
|
|
}
|
2022-08-31 00:57:30 +00:00
|
|
|
newBus() {
|
|
|
|
return {
|
|
|
|
read: newAddressDecoder([
|
2022-09-01 15:57:33 +00:00
|
|
|
[0x0000, 0x7fff, 0xffff, (a) => { return this.ram[a]; }],
|
|
|
|
[0x8000, 0x9fff, 0xffff, (a) => { return this.cart_80 ? this.rom[a - 0x8000] : this.ram[a]; }],
|
|
|
|
[0xa000, 0xbfff, 0xffff, (a) => { return this.cart_a0 ? this.rom[a - 0x8000] : this.ram[a]; }],
|
2022-08-31 00:57:30 +00:00
|
|
|
[0xd000, 0xd0ff, 0x1f, (a) => { return this.gtia.readReg(a); }],
|
|
|
|
[0xd200, 0xd2ff, 0xf, (a) => { return this.readPokey(a); }],
|
|
|
|
[0xd300, 0xd3ff, 0xf, (a) => { return this.readPIA(a); }],
|
|
|
|
[0xd400, 0xd4ff, 0xf, (a) => { return this.antic.readReg(a); }],
|
2022-09-03 23:15:20 +00:00
|
|
|
[0xd500, 0xd5ff, 0xff, (a) => { return this.d500[a]; }],
|
2022-08-31 00:57:30 +00:00
|
|
|
[0xd800, 0xffff, 0xffff, (a) => { return this.bios[a - 0xd800]; }],
|
|
|
|
]),
|
|
|
|
write: newAddressDecoder([
|
2022-09-03 03:22:39 +00:00
|
|
|
[0x0000, 0xbffa, 0xffff, (a, v) => { this.ram[a] = v; }],
|
|
|
|
[0xbffb, 0xbfff, 0xffff, (a, v) => { this.ram[a] = v; this.initCartA(); }],
|
2022-08-31 00:57:30 +00:00
|
|
|
[0xd000, 0xd0ff, 0x1f, (a, v) => { this.gtia.setReg(a, v); }],
|
|
|
|
[0xd200, 0xd2ff, 0xf, (a, v) => { this.writePokey(a, v); }],
|
|
|
|
[0xd400, 0xd4ff, 0xf, (a, v) => { this.antic.setReg(a, v); }],
|
2022-09-03 03:22:39 +00:00
|
|
|
[0xd500, 0xd5ff, 0xff, (a, v) => { this.writeMapper(a, v); }],
|
2022-08-31 00:57:30 +00:00
|
|
|
]),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
loadBIOS(bios: Uint8Array) {
|
|
|
|
this.bios.set(bios);
|
|
|
|
}
|
|
|
|
|
|
|
|
reset() {
|
|
|
|
super.reset();
|
|
|
|
this.antic.reset();
|
|
|
|
this.gtia.reset();
|
|
|
|
this.keycode = 0;
|
2022-09-03 23:15:20 +00:00
|
|
|
//if (this.xexdata) this.cart_a0 = true; // TODO
|
2022-08-31 00:57:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
read(a) {
|
|
|
|
// TODO: lastdmabyte?
|
|
|
|
return this.bus.read(a);
|
|
|
|
}
|
|
|
|
// used by ANTIC
|
|
|
|
readDMA(a) {
|
|
|
|
let v = this.bus.read(a);
|
2022-09-03 20:34:57 +00:00
|
|
|
this.probe.logDMARead(a, v);
|
2022-08-31 00:57:30 +00:00
|
|
|
this.lastdmabyte = v;
|
|
|
|
return v;
|
|
|
|
}
|
|
|
|
readConst(a) {
|
2022-09-03 23:15:20 +00:00
|
|
|
return a < 0xd000 || a >= 0xd500 ? this.bus.read(a) : 0xff;
|
2022-08-31 00:57:30 +00:00
|
|
|
}
|
|
|
|
write(a, v) {
|
|
|
|
this.bus.write(a, v);
|
|
|
|
}
|
|
|
|
readPokey(a: number) {
|
2022-09-01 15:57:33 +00:00
|
|
|
switch (a & 0xf) {
|
2022-08-31 00:57:30 +00:00
|
|
|
case 9: // KBCODE
|
|
|
|
return this.keycode & 0xff;
|
|
|
|
case 15: // SKSTAT
|
|
|
|
return ((~this.keycode >> 6) & 0x4) | ((~this.keycode >> 3) & 0x8) | 0x12;
|
|
|
|
default:
|
2022-09-01 15:57:33 +00:00
|
|
|
return this.irq_pokey.read(a);
|
2022-08-31 00:57:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
readPIA(a: number) {
|
|
|
|
if (a == 0 || a == 1) { return ~this.inputs[a]; }
|
|
|
|
}
|
|
|
|
writePokey(a, v) {
|
2022-09-01 15:57:33 +00:00
|
|
|
this.audio_pokey.pokey1.setRegister(a, v);
|
|
|
|
this.irq_pokey.write(a, v);
|
2022-08-31 00:57:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
startScanline() {
|
2022-09-01 15:57:33 +00:00
|
|
|
// TODO: if (this.antic.h != 0) throw new Error(this.antic.h+"");
|
|
|
|
//if (this.cpu.isHalted()) throw new EmuHalt("CPU HALTED");
|
|
|
|
// set GTIA switch inputs
|
|
|
|
this.gtia.sync();
|
2022-09-01 22:26:06 +00:00
|
|
|
// TODO: trigger latching mode
|
2022-09-01 15:57:33 +00:00
|
|
|
for (let i = 0; i < 4; i++)
|
|
|
|
this.gtia.readregs[TRIG0 + i] = (~this.inputs[2] >> i) & 1;
|
2022-09-01 22:26:06 +00:00
|
|
|
// console switches
|
|
|
|
this.gtia.readregs[CONSOL] = ~this.inputs[3] & 0x7;
|
2022-09-01 15:57:33 +00:00
|
|
|
// advance POKEY audio
|
2022-08-31 00:57:30 +00:00
|
|
|
this.audio && this.audioadapter.generate(this.audio);
|
2022-09-01 15:57:33 +00:00
|
|
|
// advance POKEY IRQ timers
|
|
|
|
this.irq_pokey.advanceScanline();
|
2022-08-31 00:57:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
drawScanline() {
|
|
|
|
// TODO
|
2022-09-01 22:26:06 +00:00
|
|
|
let y = this.antic.v - this.firstVisibleScanline;
|
|
|
|
if (y >= 0 && y < this.numVisibleScanlines) {
|
|
|
|
this.pixels.set(this.linergb, y * this.canvasWidth);
|
2022-08-31 00:57:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
advanceCPU(): number {
|
|
|
|
// update ANTIC
|
|
|
|
if (this.antic.clockPulse()) {
|
2022-09-01 15:57:33 +00:00
|
|
|
// ANTIC DMA cycle, update GTIA
|
2022-09-03 18:38:45 +00:00
|
|
|
if (this.antic.h < 8)
|
|
|
|
this.gtia.updateGfx(this.antic.h - 1, this.antic.v, this.lastdmabyte); // HALT pin
|
2022-09-03 20:34:57 +00:00
|
|
|
if (this.antic.isWSYNC())
|
|
|
|
this.probe.logWait(0);
|
2022-08-31 00:57:30 +00:00
|
|
|
this.probe.logClocks(1);
|
|
|
|
} else {
|
|
|
|
super.advanceCPU();
|
|
|
|
}
|
|
|
|
// update GTIA
|
2022-09-01 22:26:06 +00:00
|
|
|
// get X coordinate within scanline
|
|
|
|
let xofs = this.antic.h * 4 - this.firstVisibleClock;
|
|
|
|
// GTIA tick functions
|
2022-08-31 00:57:30 +00:00
|
|
|
let gtiatick1 = () => {
|
|
|
|
this.gtia.clockPulse1();
|
|
|
|
this.linergb[xofs++] = this.gtia.rgb;
|
|
|
|
}
|
|
|
|
let gtiatick2 = () => {
|
|
|
|
this.gtia.clockPulse2();
|
|
|
|
this.linergb[xofs++] = this.gtia.rgb;
|
|
|
|
}
|
2022-09-01 22:26:06 +00:00
|
|
|
// tick 4 GTIA clocks for each CPU/ANTIC cycle
|
2022-09-03 03:22:39 +00:00
|
|
|
this.gtia.clockPulse4();
|
|
|
|
// correct for HSCROL -- bias antic +2, bias gtia -1
|
|
|
|
if ((this.antic.dliop & 0x10) && (this.antic.regs[4] & 1)) {
|
|
|
|
xofs += 2;
|
|
|
|
this.gtia.setBias(-1);
|
|
|
|
} else {
|
|
|
|
this.gtia.setBias(0);
|
|
|
|
}
|
2022-08-31 00:57:30 +00:00
|
|
|
let bp = MODE_SHIFT[this.antic.mode];
|
2022-09-03 03:22:39 +00:00
|
|
|
let odd = this.antic.h & 1;
|
|
|
|
if (bp < 8 || odd) { this.gtia.an = this.antic.shiftout(); }
|
2022-08-31 00:57:30 +00:00
|
|
|
gtiatick1();
|
|
|
|
if (bp == 1) { this.gtia.an = this.antic.shiftout(); }
|
|
|
|
gtiatick2();
|
|
|
|
if (bp <= 2) { this.gtia.an = this.antic.shiftout(); }
|
|
|
|
gtiatick1();
|
|
|
|
if (bp == 1) { this.gtia.an = this.antic.shiftout(); }
|
|
|
|
gtiatick2();
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
loadState(state: any) {
|
2022-09-03 03:22:39 +00:00
|
|
|
this.loadControlsState(state);
|
2022-08-31 00:57:30 +00:00
|
|
|
this.cpu.loadState(state.c);
|
|
|
|
this.ram.set(state.ram);
|
|
|
|
this.antic.loadState(state.antic);
|
|
|
|
this.gtia.loadState(state.gtia);
|
2022-09-01 15:57:33 +00:00
|
|
|
this.irq_pokey.loadState(state.pokey);
|
2022-08-31 00:57:30 +00:00
|
|
|
this.lastdmabyte = state.lastdmabyte;
|
2022-09-03 23:15:20 +00:00
|
|
|
this.cart_80 = state.cart_80;
|
|
|
|
this.cart_a0 = state.cart_a0;
|
2022-08-31 00:57:30 +00:00
|
|
|
}
|
|
|
|
saveState() {
|
|
|
|
return {
|
|
|
|
c: this.cpu.saveState(),
|
|
|
|
ram: this.ram.slice(0),
|
|
|
|
antic: this.antic.saveState(),
|
|
|
|
gtia: this.gtia.saveState(),
|
2022-09-01 15:57:33 +00:00
|
|
|
pokey: this.irq_pokey.saveState(),
|
2022-08-31 00:57:30 +00:00
|
|
|
inputs: this.inputs.slice(0),
|
|
|
|
lastdmabyte: this.lastdmabyte,
|
2022-09-03 03:22:39 +00:00
|
|
|
keycode: this.keycode,
|
2022-09-03 23:15:20 +00:00
|
|
|
cart_80: this.cart_80,
|
|
|
|
cart_a0: this.cart_a0,
|
2022-08-31 00:57:30 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
loadControlsState(state) {
|
|
|
|
this.inputs.set(state.inputs);
|
2022-09-07 17:28:24 +00:00
|
|
|
this.keycode = state.keycode;
|
2022-08-31 00:57:30 +00:00
|
|
|
}
|
|
|
|
saveControlsState() {
|
|
|
|
return {
|
2022-09-07 17:28:24 +00:00
|
|
|
inputs: this.inputs.slice(0),
|
|
|
|
keycode: this.keycode,
|
2022-08-31 00:57:30 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
getRasterScanline() {
|
|
|
|
return this.antic.v;
|
|
|
|
}
|
2022-09-01 22:26:06 +00:00
|
|
|
getRasterLineClock() {
|
|
|
|
return this.antic.h;
|
|
|
|
}
|
2022-08-31 00:57:30 +00:00
|
|
|
getDebugCategories() {
|
|
|
|
return ['CPU', 'Stack', 'ANTIC', 'GTIA', 'POKEY'];
|
|
|
|
}
|
|
|
|
getDebugInfo(category, state) {
|
|
|
|
switch (category) {
|
|
|
|
case 'ANTIC': return ANTIC.stateToLongString(state.antic);
|
|
|
|
case 'GTIA': return GTIA.stateToLongString(state.gtia);
|
2022-09-01 15:57:33 +00:00
|
|
|
case 'POKEY': return POKEY.stateToLongString(state.pokey);
|
2022-08-31 00:57:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
getKeyboardFunction() {
|
|
|
|
return (o, key, code, flags) => {
|
2022-09-03 20:34:57 +00:00
|
|
|
if (!this.keyboard_active) return false;
|
2022-08-31 00:57:30 +00:00
|
|
|
if (flags & (KeyFlags.KeyDown | KeyFlags.KeyUp)) {
|
2022-09-03 23:15:20 +00:00
|
|
|
//console.log(o, key, code, flags, hex(this.keycode));
|
2022-08-31 00:57:30 +00:00
|
|
|
var keymap = ATARI8_KEYMATRIX_INTL_NOSHIFT;
|
|
|
|
if (key == Keys.VK_F9.c) {
|
2022-09-01 15:57:33 +00:00
|
|
|
this.irq_pokey.generateIRQ(0x80); // break IRQ
|
2022-08-31 00:57:30 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
for (var i = 0; i < keymap.length; i++) {
|
|
|
|
if (keymap[i] && keymap[i].c == key) {
|
|
|
|
this.keycode = i;
|
|
|
|
if (flags & KeyFlags.Shift) { this.keycode |= 0x40; }
|
|
|
|
if (flags & KeyFlags.Ctrl) { this.keycode |= 0x80; }
|
|
|
|
if (flags & KeyFlags.KeyDown) {
|
|
|
|
this.keycode |= 0x100;
|
2022-09-01 15:57:33 +00:00
|
|
|
this.irq_pokey.generateIRQ(0x40); // key pressed IRQ
|
2022-08-31 00:57:30 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
2022-09-01 15:57:33 +00:00
|
|
|
pokey_irq() {
|
|
|
|
this.cpu.IRQ();
|
|
|
|
this.probe.logInterrupt(2);
|
|
|
|
}
|
|
|
|
antic_nmi() {
|
|
|
|
this.cpu.NMI();
|
|
|
|
this.probe.logInterrupt(1);
|
2022-08-31 00:57:30 +00:00
|
|
|
}
|
2022-09-01 15:57:33 +00:00
|
|
|
|
2022-09-03 03:22:39 +00:00
|
|
|
loadROM(rom: Uint8Array, title: string) {
|
2022-09-03 23:15:20 +00:00
|
|
|
if ((rom[0] == 0xff && rom[1] == 0xff) && !title?.endsWith('.rom')) {
|
|
|
|
// XEX file, chill out and wait for BIOS hook
|
2022-09-03 03:22:39 +00:00
|
|
|
this.xexdata = rom;
|
|
|
|
} else {
|
|
|
|
this.loadCartridge(rom);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
loadCartridge(rom: Uint8Array) {
|
2022-09-03 23:15:20 +00:00
|
|
|
// TODO: https://github.com/dmlloyd/atari800/blob/master/DOC/cart.txt
|
2022-09-03 03:22:39 +00:00
|
|
|
// strip off header
|
|
|
|
if (rom[0] == 0x43 && rom[1] == 0x41 && rom[2] == 0x52 && rom[3] == 0x54) {
|
|
|
|
rom = rom.slice(16);
|
|
|
|
}
|
|
|
|
if (rom.length != 0x1000 && rom.length != 0x2000 && rom.length != 0x4000 && rom.length != 0x8000)
|
|
|
|
throw new Error("Sorry, this platform can only load 4/8/16/32 KB cartridges at the moment.");
|
2022-08-31 00:57:30 +00:00
|
|
|
// TODO: support other than 8 KB carts
|
2022-09-01 15:57:33 +00:00
|
|
|
// support 4/8/16/32 KB carts
|
|
|
|
let rom2 = new Uint8Array(0x8000);
|
|
|
|
for (let i = 0; i <= rom2.length - rom.length; i += rom.length) {
|
|
|
|
rom2.set(rom, i);
|
|
|
|
}
|
2022-09-03 23:15:20 +00:00
|
|
|
this.run_address = rom2[0x7ffe] + rom2[0x7fff]*256;
|
2022-09-01 15:57:33 +00:00
|
|
|
this.cart_a0 = true; // TODO
|
2022-09-03 03:22:39 +00:00
|
|
|
this.cart_80 = rom.length == 0x4000;
|
2022-09-01 15:57:33 +00:00
|
|
|
super.loadROM(rom2);
|
2022-08-31 00:57:30 +00:00
|
|
|
}
|
2022-09-03 03:22:39 +00:00
|
|
|
|
2022-09-03 23:15:20 +00:00
|
|
|
writeMapper(addr: number, value: number) {
|
|
|
|
// TODO
|
2022-09-03 03:22:39 +00:00
|
|
|
if (addr == 0xff) {
|
|
|
|
if (value == 0x80) this.cart_80 = false;
|
|
|
|
if (value == 0xa0) this.cart_a0 = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
loadXEX(rom: Uint8Array) {
|
|
|
|
let ofs = 2;
|
2022-09-03 23:15:20 +00:00
|
|
|
let stub = this.d500;
|
|
|
|
let stubofs = 0; // stub routine
|
|
|
|
var runaddr = -1;
|
|
|
|
// load segments into RAM
|
2022-09-03 03:22:39 +00:00
|
|
|
while (ofs < rom.length) {
|
2022-09-03 23:15:20 +00:00
|
|
|
let start = rom[ofs + 0] + rom[ofs + 1] * 256;
|
|
|
|
let end = rom[ofs + 2] + rom[ofs + 3] * 256;
|
|
|
|
console.log('XEX', hex(ofs), hex(start), hex(end));
|
2022-09-03 03:22:39 +00:00
|
|
|
ofs += 4;
|
2022-09-03 23:15:20 +00:00
|
|
|
for (let i = start; i <= end; i++) {
|
2022-09-03 03:22:39 +00:00
|
|
|
this.ram[i] = rom[ofs++];
|
|
|
|
}
|
2022-09-03 23:15:20 +00:00
|
|
|
if (start == 0x2e0 && end == 0x2e1) {
|
|
|
|
runaddr = this.ram[0x2e0] + this.ram[0x2e1] * 256;
|
|
|
|
console.log('XEX run', hex(runaddr));
|
|
|
|
}
|
|
|
|
if (start == 0x2e2 && end == 0x2e3) {
|
|
|
|
var initaddr = this.ram[0x2e2] + this.ram[0x2e3] * 256;
|
|
|
|
console.log('XEX init', hex(initaddr));
|
|
|
|
stub[stubofs++] = 0x20;
|
|
|
|
stub[stubofs++] = initaddr & 0xff;
|
|
|
|
stub[stubofs++] = initaddr >> 8;
|
2022-09-03 03:22:39 +00:00
|
|
|
}
|
|
|
|
if (ofs > rom.length) throw new Error("Bad .XEX file format");
|
|
|
|
}
|
2022-09-03 23:15:20 +00:00
|
|
|
if (runaddr >= 0) {
|
|
|
|
// build stub routine at 0xd500
|
|
|
|
stub[stubofs++] = 0xa9; // lda #$a0
|
|
|
|
stub[stubofs++] = 0xa0;
|
|
|
|
stub[stubofs++] = 0x8d; // sta $d5ff (disable cart)
|
|
|
|
stub[stubofs++] = 0xff;
|
|
|
|
stub[stubofs++] = 0xd5;
|
|
|
|
stub[stubofs++] = 0x4c; // jmp runaddr
|
|
|
|
stub[stubofs++] = runaddr & 0xff;
|
|
|
|
stub[stubofs++] = runaddr >> 8;
|
|
|
|
// set DOSVEC to 0xd500
|
|
|
|
this.ram[0xa] = 0x00;
|
|
|
|
this.ram[0xb] = 0xd5;
|
|
|
|
this.run_address = 0xd500;
|
2022-09-03 03:22:39 +00:00
|
|
|
}
|
2022-09-03 23:15:20 +00:00
|
|
|
}
|
2022-09-03 03:22:39 +00:00
|
|
|
|
|
|
|
initCartA() {
|
2022-09-03 23:15:20 +00:00
|
|
|
if (this.cpu.getPC() == 0xf17f && this.xexdata) {
|
|
|
|
this.loadXEX(this.xexdata);
|
2022-09-03 03:22:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-07 19:18:50 +00:00
|
|
|
setPaddleInput(controller: number, value: number): void {
|
|
|
|
this.irq_pokey.pot_inputs[controller] = 255 - value;
|
|
|
|
}
|
|
|
|
|
2023-07-08 20:12:17 +00:00
|
|
|
getDebugDisplayList() {
|
|
|
|
let pc = this.antic.getDlistAddr();
|
|
|
|
const nextInsn = () => {
|
|
|
|
let b = this.read(pc);
|
|
|
|
pc = ((pc + 1) & 0x3ff) | (pc & ~0x3ff);
|
|
|
|
return b;
|
|
|
|
}
|
2023-07-11 03:52:03 +00:00
|
|
|
let dlist = [];
|
2023-07-08 20:12:17 +00:00
|
|
|
let y = 0;
|
|
|
|
for (let i=0; i<256 && y<240; i++) {
|
|
|
|
let pc0 = pc;
|
|
|
|
let op = nextInsn(); // get mode
|
|
|
|
let mode = op & 0xf;
|
2023-07-11 03:52:03 +00:00
|
|
|
let debugmsg = "" // "op=" + hex(op);
|
2023-07-08 20:12:17 +00:00
|
|
|
let jmp = false;
|
|
|
|
let lines;
|
|
|
|
if (mode == 0) {
|
|
|
|
lines = (((op >> 4) & 7) + 1);
|
|
|
|
debugmsg += " blank=" + lines;
|
|
|
|
} else {
|
|
|
|
lines = MODE_LINES[mode];
|
|
|
|
debugmsg += " mode=" + hex(mode);
|
|
|
|
debugmsg += " lines=" + lines;
|
|
|
|
jmp = (op & ~0x40) == 0x01; // JMP insn?
|
|
|
|
let lms = (op & 0x40) != 0 && (op & 0xf) != 0; // LMS insn?
|
2023-07-11 03:52:03 +00:00
|
|
|
if (op & 0x10) { debugmsg += " HSCROL"; }
|
|
|
|
if (op & 0x20) { debugmsg += " VSCROL"; }
|
|
|
|
if (op & 0x80) { debugmsg += " DLI"; }
|
2023-07-08 20:12:17 +00:00
|
|
|
if (jmp && (op & 0x40)) { debugmsg += " JVB"; }
|
|
|
|
else if (jmp) debugmsg += " JMP";
|
|
|
|
else if (lms) debugmsg += " LMS";
|
2023-07-11 03:52:03 +00:00
|
|
|
if (jmp || lms) {
|
2023-07-08 20:12:17 +00:00
|
|
|
let dlarg_lo = nextInsn();
|
|
|
|
let dlarg_hi = nextInsn();
|
|
|
|
debugmsg += " $" + hex(dlarg_hi) + "" + hex(dlarg_lo);
|
|
|
|
}
|
|
|
|
}
|
2023-07-11 03:52:03 +00:00
|
|
|
dlist.push("$"+hex(pc0) + " y=" + y + " " + debugmsg);
|
2023-07-08 20:12:17 +00:00
|
|
|
if (jmp) break;
|
|
|
|
y += lines;
|
|
|
|
}
|
|
|
|
return dlist;
|
|
|
|
}
|
|
|
|
|
2022-08-31 00:57:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export class Atari5200 extends Atari800 {
|
|
|
|
newBus() {
|
|
|
|
return {
|
|
|
|
read: newAddressDecoder([
|
|
|
|
[0x0000, 0x3fff, 0xffff, (a) => { return this.ram[a]; }],
|
|
|
|
[0x4000, 0xbfff, 0xffff, (a) => { return this.rom ? this.rom[a - 0x4000] : 0; }],
|
|
|
|
[0xc000, 0xcfff, 0x1f, (a) => { return this.gtia.readReg(a); }],
|
|
|
|
[0xd400, 0xd4ff, 0xf, (a) => { return this.antic.readReg(a); }],
|
|
|
|
[0xe800, 0xefff, 0xf, (a) => { return this.readPokey(a); }],
|
|
|
|
[0xf800, 0xffff, 0x7ff, (a) => { return this.bios[a]; }],
|
|
|
|
]),
|
|
|
|
write: newAddressDecoder([
|
|
|
|
[0x0000, 0x3fff, 0xffff, (a, v) => { this.ram[a] = v; }],
|
|
|
|
[0xc000, 0xcfff, 0x1f, (a, v) => { this.gtia.setReg(a, v); }],
|
|
|
|
[0xd400, 0xd4ff, 0xf, (a, v) => { this.antic.setReg(a, v); }],
|
|
|
|
[0xe800, 0xefff, 0xf, (a, v) => { this.writePokey(a, v); }],
|
|
|
|
]),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|