mirror of
https://github.com/whscullin/apple2js.git
synced 2024-01-12 14:14:38 +00:00
Flesh out some state stuff (#59)
Get save and restore state limping along to nearly as well as before I refactored and broke everything.
This commit is contained in:
parent
983026aa1d
commit
afc5280ac2
49
js/apple2.ts
49
js/apple2.ts
|
@ -1,9 +1,9 @@
|
||||||
import Apple2IO from './apple2io';
|
import Apple2IO from './apple2io';
|
||||||
// import * as gl from './gl';
|
|
||||||
import {
|
import {
|
||||||
HiresPage,
|
HiresPage,
|
||||||
LoresPage,
|
LoresPage,
|
||||||
VideoModes,
|
VideoModes,
|
||||||
|
VideoModesState,
|
||||||
} from './videomodes';
|
} from './videomodes';
|
||||||
import {
|
import {
|
||||||
HiresPage2D,
|
HiresPage2D,
|
||||||
|
@ -15,9 +15,11 @@ import {
|
||||||
LoresPageGL,
|
LoresPageGL,
|
||||||
VideoModesGL,
|
VideoModesGL,
|
||||||
} from './gl';
|
} from './gl';
|
||||||
import CPU6502, { PageHandler, CpuState } from './cpu6502';
|
import ROM from './roms/rom';
|
||||||
import MMU from './mmu';
|
import { Apple2IOState } from './apple2io';
|
||||||
import RAM from './ram';
|
import CPU6502, { CpuState } from './cpu6502';
|
||||||
|
import MMU, { MMUState } from './mmu';
|
||||||
|
import RAM, { RAMState } from './ram';
|
||||||
import { debug } from './util';
|
import { debug } from './util';
|
||||||
|
|
||||||
import SYMBOLS from './symbols';
|
import SYMBOLS from './symbols';
|
||||||
|
@ -29,13 +31,17 @@ interface Options {
|
||||||
enhanced: boolean,
|
enhanced: boolean,
|
||||||
e: boolean,
|
e: boolean,
|
||||||
gl: boolean,
|
gl: boolean,
|
||||||
rom: PageHandler,
|
rom: ROM,
|
||||||
canvas: HTMLCanvasElement,
|
canvas: HTMLCanvasElement,
|
||||||
tick: () => void,
|
tick: () => void,
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
cpu: CpuState,
|
cpu: CpuState,
|
||||||
|
vm: VideoModesState,
|
||||||
|
io: Apple2IOState,
|
||||||
|
mmu?: MMUState,
|
||||||
|
ram?: RAMState[],
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Apple2 implements Restorable<State> {
|
export class Apple2 implements Restorable<State> {
|
||||||
|
@ -57,7 +63,8 @@ export class Apple2 implements Restorable<State> {
|
||||||
private vm: VideoModes;
|
private vm: VideoModes;
|
||||||
|
|
||||||
private io: Apple2IO;
|
private io: Apple2IO;
|
||||||
private mmu: MMU;
|
private mmu: MMU | undefined;
|
||||||
|
private ram: [RAM, RAM, RAM] | undefined;
|
||||||
|
|
||||||
private tick: () => void;
|
private tick: () => void;
|
||||||
|
|
||||||
|
@ -85,17 +92,19 @@ export class Apple2 implements Restorable<State> {
|
||||||
this.mmu = new MMU(this.cpu, this.vm, this.gr, this.gr2, this.hgr, this.hgr2, this.io, options.rom);
|
this.mmu = new MMU(this.cpu, this.vm, this.gr, this.gr2, this.hgr, this.hgr2, this.io, options.rom);
|
||||||
this.cpu.addPageHandler(this.mmu);
|
this.cpu.addPageHandler(this.mmu);
|
||||||
} else {
|
} else {
|
||||||
const ram1 = new RAM(0x00, 0x03);
|
this.ram = [
|
||||||
const ram2 = new RAM(0x0C, 0x1F);
|
new RAM(0x00, 0x03),
|
||||||
const ram3 = new RAM(0x60, 0xBF);
|
new RAM(0x0C, 0x1F),
|
||||||
|
new RAM(0x60, 0xBF)
|
||||||
|
];
|
||||||
|
|
||||||
this.cpu.addPageHandler(ram1);
|
this.cpu.addPageHandler(this.ram[0]);
|
||||||
this.cpu.addPageHandler(this.gr);
|
this.cpu.addPageHandler(this.gr);
|
||||||
this.cpu.addPageHandler(this.gr2);
|
this.cpu.addPageHandler(this.gr2);
|
||||||
this.cpu.addPageHandler(ram2);
|
this.cpu.addPageHandler(this.ram[1]);
|
||||||
this.cpu.addPageHandler(this.hgr);
|
this.cpu.addPageHandler(this.hgr);
|
||||||
this.cpu.addPageHandler(this.hgr2);
|
this.cpu.addPageHandler(this.hgr2);
|
||||||
this.cpu.addPageHandler(ram3);
|
this.cpu.addPageHandler(this.ram[2]);
|
||||||
this.cpu.addPageHandler(this.io);
|
this.cpu.addPageHandler(this.io);
|
||||||
this.cpu.addPageHandler(options.rom);
|
this.cpu.addPageHandler(options.rom);
|
||||||
}
|
}
|
||||||
|
@ -184,6 +193,10 @@ export class Apple2 implements Restorable<State> {
|
||||||
getState(): State {
|
getState(): State {
|
||||||
const state: State = {
|
const state: State = {
|
||||||
cpu: this.cpu.getState(),
|
cpu: this.cpu.getState(),
|
||||||
|
vm: this.vm.getState(),
|
||||||
|
io: this.io.getState(),
|
||||||
|
mmu: this.mmu?.getState(),
|
||||||
|
ram: this.ram?.map(bank => bank.getState()),
|
||||||
};
|
};
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
|
@ -191,6 +204,18 @@ export class Apple2 implements Restorable<State> {
|
||||||
|
|
||||||
setState(state: State) {
|
setState(state: State) {
|
||||||
this.cpu.setState(state.cpu);
|
this.cpu.setState(state.cpu);
|
||||||
|
this.vm.setState(state.vm);
|
||||||
|
this.io.setState(state.io);
|
||||||
|
if (this.mmu && state.mmu) {
|
||||||
|
this.mmu.setState(state.mmu);
|
||||||
|
}
|
||||||
|
if (this.ram) {
|
||||||
|
this.ram.forEach((bank, idx) => {
|
||||||
|
if (state.ram) {
|
||||||
|
bank.setState(state.ram[idx]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import CPU6502, { PageHandler } from './cpu6502';
|
import CPU6502, { PageHandler } from './cpu6502';
|
||||||
import { Card, Memory, TapeData, byte } from './types';
|
import { Card, Memory, TapeData, byte, Restorable } from './types';
|
||||||
import { debug } from './util';
|
import { debug } from './util';
|
||||||
|
|
||||||
type slot = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
type slot = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
||||||
|
@ -25,8 +25,9 @@ interface Annunciators {
|
||||||
3: boolean,
|
3: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
export interface Apple2IOState {
|
||||||
annunciators: Annunciators;
|
annunciators: Annunciators;
|
||||||
|
cards: Array<any | null>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SampleListener = (sample: number[]) => void;
|
export type SampleListener = (sample: number[]) => void;
|
||||||
|
@ -65,8 +66,8 @@ const LOC = {
|
||||||
ACCEL: 0x74, // CPU Speed control
|
ACCEL: 0x74, // CPU Speed control
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class Apple2IO implements PageHandler {
|
export default class Apple2IO implements PageHandler, Restorable<Apple2IOState> {
|
||||||
private _slot: Card[] = [];
|
private _slot: Array<Card | null> = new Array(7).fill(null);
|
||||||
private _auxRom: Memory | null = null;
|
private _auxRom: Memory | null = null;
|
||||||
|
|
||||||
private _khz = 1023;
|
private _khz = 1023;
|
||||||
|
@ -139,6 +140,7 @@ export default class Apple2IO implements PageHandler {
|
||||||
_access(off: byte, val?: byte): byte | undefined {
|
_access(off: byte, val?: byte): byte | undefined {
|
||||||
let result: number | undefined = 0;
|
let result: number | undefined = 0;
|
||||||
const now = this.cpu.getCycles();
|
const now = this.cpu.getCycles();
|
||||||
|
const writeMode = val === undefined;
|
||||||
const delta = now - this._trigger;
|
const delta = now - this._trigger;
|
||||||
switch (off) {
|
switch (off) {
|
||||||
case LOC.CLRTEXT:
|
case LOC.CLRTEXT:
|
||||||
|
@ -256,7 +258,9 @@ export default class Apple2IO implements PageHandler {
|
||||||
result = this._key;
|
result = this._key;
|
||||||
break;
|
break;
|
||||||
case LOC.STROBE: // C01x
|
case LOC.STROBE: // C01x
|
||||||
|
if (off === LOC.STROBE || writeMode) {
|
||||||
this._key &= 0x7f;
|
this._key &= 0x7f;
|
||||||
|
}
|
||||||
if (this._buffer.length > 0) {
|
if (this._buffer.length > 0) {
|
||||||
let val = this._buffer.shift() as string;
|
let val = this._buffer.shift() as string;
|
||||||
if (val == '\n') {
|
if (val == '\n') {
|
||||||
|
@ -264,7 +268,10 @@ export default class Apple2IO implements PageHandler {
|
||||||
}
|
}
|
||||||
this._key = val.charCodeAt(0) | 0x80;
|
this._key = val.charCodeAt(0) | 0x80;
|
||||||
}
|
}
|
||||||
result = (this._keyDown ? 0x80 : 0x00) | this._key;
|
result = this._key & 0x7f;
|
||||||
|
if (off === LOC.STROBE) {
|
||||||
|
result |= this._keyDown ? 0x80 : 0x00;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case LOC.TAPEOUT: // C02x
|
case LOC.TAPEOUT: // C02x
|
||||||
this._phase = -this._phase;
|
this._phase = -this._phase;
|
||||||
|
@ -401,14 +408,17 @@ export default class Apple2IO implements PageHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getState() {
|
getState(): Apple2IOState {
|
||||||
|
// TODO vet more potential state
|
||||||
return {
|
return {
|
||||||
annunciators: this._annunciators[0]
|
annunciators: this._annunciators,
|
||||||
|
cards: this._slot.map((card) => card ? card.getState() : null)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
setState(state: State) {
|
setState(state: Apple2IOState) {
|
||||||
this._annunciators = state.annunciators;
|
this._annunciators = state.annunciators;
|
||||||
|
state.cards.map((cardState, idx) => this._slot[idx]?.setState(cardState));
|
||||||
}
|
}
|
||||||
|
|
||||||
setSlot(slot: slot, card: Card) {
|
setSlot(slot: slot, card: Card) {
|
||||||
|
@ -453,7 +463,7 @@ export default class Apple2IO implements PageHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setTape(tape: TapeData) { // TODO(flan): Needs typing.
|
setTape(tape: TapeData) {
|
||||||
debug('Tape length: ' + tape.length);
|
debug('Tape length: ' + tape.length);
|
||||||
this._tape = tape;
|
this._tape = tape;
|
||||||
this._tapeOffset = -1;
|
this._tapeOffset = -1;
|
||||||
|
@ -470,9 +480,7 @@ export default class Apple2IO implements PageHandler {
|
||||||
tick() {
|
tick() {
|
||||||
this._tick();
|
this._tick();
|
||||||
for (let idx = 0; idx < 8; idx++) {
|
for (let idx = 0; idx < 8; idx++) {
|
||||||
if (this._slot[idx]) {
|
this._slot[idx]?.tick?.();
|
||||||
this._slot[idx].tick?.();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
24
js/base64.ts
24
js/base64.ts
|
@ -121,3 +121,27 @@ export function base64_decode(data: string | null | undefined): memory | undefin
|
||||||
|
|
||||||
return new Uint8Array(tmp_arr);
|
return new Uint8Array(tmp_arr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DATA_URL_PREFIX = 'data:application/octet-stream;base64,';
|
||||||
|
|
||||||
|
export function base64_json_parse(json: string) {
|
||||||
|
const reviver = (_key: string, value: any) => {
|
||||||
|
if (typeof value ==='string' && value.startsWith(DATA_URL_PREFIX)) {
|
||||||
|
return base64_decode(value.slice(DATA_URL_PREFIX.length));
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
return JSON.parse(json, reviver);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function base64_json_stringify(json: any) {
|
||||||
|
const replacer = (_key: string, value: any) => {
|
||||||
|
if (value instanceof Uint8Array) {
|
||||||
|
return DATA_URL_PREFIX + base64_encode(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
return JSON.stringify(json, replacer);
|
||||||
|
}
|
||||||
|
|
17
js/canvas.ts
17
js/canvas.ts
|
@ -9,7 +9,6 @@
|
||||||
* implied warranty.
|
* implied warranty.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { base64_decode, base64_encode } from './base64';
|
|
||||||
import { byte, memory, Memory } from './types';
|
import { byte, memory, Memory } from './types';
|
||||||
import { allocMemPages } from './util';
|
import { allocMemPages } from './util';
|
||||||
import {
|
import {
|
||||||
|
@ -468,16 +467,16 @@ export class LoresPage2D implements LoresPage {
|
||||||
page: this.page,
|
page: this.page,
|
||||||
mono: this._monoMode,
|
mono: this._monoMode,
|
||||||
buffer: [
|
buffer: [
|
||||||
base64_encode(this._buffer[0]),
|
new Uint8Array(this._buffer[0]),
|
||||||
base64_encode(this._buffer[1])
|
new Uint8Array(this._buffer[1]),
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
setState(state: GraphicsState) {
|
setState(state: GraphicsState) {
|
||||||
this.page = state.page;
|
this.page = state.page;
|
||||||
this._monoMode = state.mono;
|
this._monoMode = state.mono;
|
||||||
this._buffer[0] = base64_decode(state.buffer[0]);
|
this._buffer[0] = new Uint8Array(state.buffer[0]);
|
||||||
this._buffer[1] = base64_decode(state.buffer[1]);
|
this._buffer[1] = new Uint8Array(state.buffer[1]);
|
||||||
|
|
||||||
this.refresh();
|
this.refresh();
|
||||||
}
|
}
|
||||||
|
@ -879,8 +878,8 @@ export class HiresPage2D implements HiresPage {
|
||||||
page: this.page,
|
page: this.page,
|
||||||
mono: this._monoMode,
|
mono: this._monoMode,
|
||||||
buffer: [
|
buffer: [
|
||||||
base64_encode(this._buffer[0]),
|
new Uint8Array(this._buffer[0]),
|
||||||
base64_encode(this._buffer[1])
|
new Uint8Array(this._buffer[1]),
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -888,8 +887,8 @@ export class HiresPage2D implements HiresPage {
|
||||||
setState(state: GraphicsState) {
|
setState(state: GraphicsState) {
|
||||||
this.page = state.page;
|
this.page = state.page;
|
||||||
this._monoMode = state.mono;
|
this._monoMode = state.mono;
|
||||||
this._buffer[0] = base64_decode(state.buffer[0]);
|
this._buffer[0] = new Uint8Array(state.buffer[0]);
|
||||||
this._buffer[1] = base64_decode(state.buffer[1]);
|
this._buffer[1] = new Uint8Array(state.buffer[1]);
|
||||||
|
|
||||||
this.refresh();
|
this.refresh();
|
||||||
}
|
}
|
||||||
|
|
|
@ -372,6 +372,13 @@ export default function CFFA() {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getState() {
|
||||||
|
// TODO CFFA State
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
|
||||||
|
setState(_) {},
|
||||||
|
|
||||||
// Assign a raw disk image to a drive. Must be 2mg or raw PO image.
|
// Assign a raw disk image to a drive. Must be 2mg or raw PO image.
|
||||||
|
|
||||||
setBinary: function(drive, name, ext, rawData) {
|
setBinary: function(drive, name, ext, rawData) {
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
* implied warranty.
|
* implied warranty.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { base64_decode, base64_encode } from '../base64';
|
import { base64_decode, base64_encode} from '../base64';
|
||||||
import { bit, byte, DiskFormat, MemberOf, memory, nibble, rom } from '../types';
|
import { bit, byte, DiskFormat, MemberOf, memory, nibble, rom } from '../types';
|
||||||
import { debug, toHex } from '../util';
|
import { debug, toHex } from '../util';
|
||||||
import { Disk, jsonDecode, jsonEncode, readSector } from '../formats/format_utils';
|
import { Disk, jsonDecode, jsonEncode, readSector } from '../formats/format_utils';
|
||||||
|
@ -151,6 +151,7 @@ type DriveNumber = MemberOf<typeof DRIVE_NUMBERS>;
|
||||||
interface Callbacks {
|
interface Callbacks {
|
||||||
driveLight: (drive: DriveNumber, on: boolean) => void;
|
driveLight: (drive: DriveNumber, on: boolean) => void;
|
||||||
dirty: (drive: DriveNumber, dirty: boolean) => void;
|
dirty: (drive: DriveNumber, dirty: boolean) => void;
|
||||||
|
label: (drive: DriveNumber, name: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Common information for Nibble and WOZ disks. */
|
/** Common information for Nibble and WOZ disks. */
|
||||||
|
@ -159,6 +160,8 @@ interface BaseDrive {
|
||||||
format: DiskFormat,
|
format: DiskFormat,
|
||||||
/** Current disk volume number. */
|
/** Current disk volume number. */
|
||||||
volume: byte,
|
volume: byte,
|
||||||
|
/** Displayed disk name */
|
||||||
|
name: string,
|
||||||
/** Quarter track position of read/write head. */
|
/** Quarter track position of read/write head. */
|
||||||
track: byte,
|
track: byte,
|
||||||
/** Position of the head on the track. */
|
/** Position of the head on the track. */
|
||||||
|
@ -195,7 +198,8 @@ function isNibbleDrive(drive: Drive): drive is NibbleDrive {
|
||||||
interface DriveState {
|
interface DriveState {
|
||||||
format: DiskFormat,
|
format: DiskFormat,
|
||||||
volume: byte,
|
volume: byte,
|
||||||
tracks: string[],
|
name: string,
|
||||||
|
tracks: memory[],
|
||||||
track: byte,
|
track: byte,
|
||||||
head: byte,
|
head: byte,
|
||||||
phase: Phase,
|
phase: Phase,
|
||||||
|
@ -217,7 +221,8 @@ function getDriveState(drive: Drive): DriveState {
|
||||||
const result: DriveState = {
|
const result: DriveState = {
|
||||||
format: drive.format,
|
format: drive.format,
|
||||||
volume: drive.volume,
|
volume: drive.volume,
|
||||||
tracks: [] as string[],
|
name: drive.name,
|
||||||
|
tracks: [],
|
||||||
track: drive.track,
|
track: drive.track,
|
||||||
head: drive.head,
|
head: drive.head,
|
||||||
phase: drive.phase,
|
phase: drive.phase,
|
||||||
|
@ -228,7 +233,7 @@ function getDriveState(drive: Drive): DriveState {
|
||||||
throw Error('No tracks.');
|
throw Error('No tracks.');
|
||||||
}
|
}
|
||||||
for (let idx = 0; idx < drive.tracks.length; idx++) {
|
for (let idx = 0; idx < drive.tracks.length; idx++) {
|
||||||
result.tracks.push(base64_encode(drive.tracks[idx]));
|
result.tracks.push(new Uint8Array(drive.tracks[idx]));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -238,6 +243,7 @@ function setDriveState(state: DriveState) {
|
||||||
const result: Drive = {
|
const result: Drive = {
|
||||||
format: state.format,
|
format: state.format,
|
||||||
volume: state.volume,
|
volume: state.volume,
|
||||||
|
name: state.name,
|
||||||
tracks: [] as memory[],
|
tracks: [] as memory[],
|
||||||
track: state.track,
|
track: state.track,
|
||||||
head: state.head,
|
head: state.head,
|
||||||
|
@ -246,8 +252,9 @@ function setDriveState(state: DriveState) {
|
||||||
dirty: state.dirty
|
dirty: state.dirty
|
||||||
};
|
};
|
||||||
for (let idx = 0; idx < state.tracks.length; idx++) {
|
for (let idx = 0; idx < state.tracks.length; idx++) {
|
||||||
result.tracks!.push(base64_decode(state.tracks[idx]));
|
result.tracks!.push(new Uint8Array(state.tracks[idx]));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,6 +267,7 @@ export default class DiskII {
|
||||||
{ // Drive 1
|
{ // Drive 1
|
||||||
format: 'dsk',
|
format: 'dsk',
|
||||||
volume: 254,
|
volume: 254,
|
||||||
|
name: 'Disk 1',
|
||||||
tracks: [],
|
tracks: [],
|
||||||
track: 0,
|
track: 0,
|
||||||
head: 0,
|
head: 0,
|
||||||
|
@ -270,6 +278,7 @@ export default class DiskII {
|
||||||
{ // Drive 2
|
{ // Drive 2
|
||||||
format: 'dsk',
|
format: 'dsk',
|
||||||
volume: 254,
|
volume: 254,
|
||||||
|
name: 'Disk 2',
|
||||||
tracks: [],
|
tracks: [],
|
||||||
track: 0,
|
track: 0,
|
||||||
head: 0,
|
head: 0,
|
||||||
|
@ -521,9 +530,7 @@ export default class DiskII {
|
||||||
this.offTimeout = window.setTimeout(() => {
|
this.offTimeout = window.setTimeout(() => {
|
||||||
this.debug('Drive Off');
|
this.debug('Drive Off');
|
||||||
this.on = false;
|
this.on = false;
|
||||||
if (this.callbacks.driveLight) {
|
|
||||||
this.callbacks.driveLight(this.drive, false);
|
this.callbacks.driveLight(this.drive, false);
|
||||||
}
|
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -538,17 +545,15 @@ export default class DiskII {
|
||||||
this.debug('Drive On');
|
this.debug('Drive On');
|
||||||
this.on = true;
|
this.on = true;
|
||||||
this.lastCycles = this.io.cycles();
|
this.lastCycles = this.io.cycles();
|
||||||
if (this.callbacks.driveLight) {
|
|
||||||
this.callbacks.driveLight(this.drive, true);
|
this.callbacks.driveLight(this.drive, true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LOC.DRIVE1: // 0x0a
|
case LOC.DRIVE1: // 0x0a
|
||||||
this.debug('Disk 1');
|
this.debug('Disk 1');
|
||||||
this.drive = 1;
|
this.drive = 1;
|
||||||
this.cur = this.drives[this.drive - 1];
|
this.cur = this.drives[this.drive - 1];
|
||||||
if (this.on && this.callbacks.driveLight) {
|
if (this.on) {
|
||||||
this.callbacks.driveLight(2, false);
|
this.callbacks.driveLight(2, false);
|
||||||
this.callbacks.driveLight(1, true);
|
this.callbacks.driveLight(1, true);
|
||||||
}
|
}
|
||||||
|
@ -557,7 +562,7 @@ export default class DiskII {
|
||||||
this.debug('Disk 2');
|
this.debug('Disk 2');
|
||||||
this.drive = 2;
|
this.drive = 2;
|
||||||
this.cur = this.drives[this.drive - 1];
|
this.cur = this.drives[this.drive - 1];
|
||||||
if (this.on && this.callbacks.driveLight) {
|
if (this.on) {
|
||||||
this.callbacks.driveLight(1, false);
|
this.callbacks.driveLight(1, false);
|
||||||
this.callbacks.driveLight(2, true);
|
this.callbacks.driveLight(2, true);
|
||||||
}
|
}
|
||||||
|
@ -685,9 +690,11 @@ export default class DiskII {
|
||||||
this.on = state.on;
|
this.on = state.on;
|
||||||
this.drive = state.drive;
|
this.drive = state.drive;
|
||||||
for (const d of DRIVE_NUMBERS) {
|
for (const d of DRIVE_NUMBERS) {
|
||||||
this.drives[d - 1] = setDriveState(state.drives[d - 1]);
|
const idx = d - 1;
|
||||||
|
this.drives[idx] = setDriveState(state.drives[idx]);
|
||||||
|
this.callbacks.label(d, state.drives[idx].name);
|
||||||
this.callbacks.driveLight(d, this.on);
|
this.callbacks.driveLight(d, this.on);
|
||||||
this.callbacks.dirty(d, this.drives[d - 1].dirty);
|
this.callbacks.dirty(d, this.drives[idx].dirty);
|
||||||
}
|
}
|
||||||
this.cur = this.drives[this.drive - 1];
|
this.cur = this.drives[this.drive - 1];
|
||||||
}
|
}
|
||||||
|
@ -777,6 +784,7 @@ export default class DiskII {
|
||||||
|
|
||||||
Object.assign(cur, newDisk);
|
Object.assign(cur, newDisk);
|
||||||
this.updateDirty(this.drive, false);
|
this.updateDirty(this.drive, false);
|
||||||
|
this.callbacks.label(this.drive, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
getJSON(drive: DriveNumber, pretty: boolean) {
|
getJSON(drive: DriveNumber, pretty: boolean) {
|
||||||
|
@ -831,6 +839,7 @@ export default class DiskII {
|
||||||
|
|
||||||
Object.assign(cur, disk);
|
Object.assign(cur, disk);
|
||||||
this.updateDirty(drive, true);
|
this.updateDirty(drive, true);
|
||||||
|
this.callbacks.label(this.drive, name);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -115,5 +115,12 @@ export default function NoSlotClock(rom)
|
||||||
_access(off);
|
_access(off);
|
||||||
rom.write(page, off, val);
|
rom.write(page, off, val);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getState() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
|
||||||
|
setState(_) {
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,10 @@ export default function Parallel(io, cbs) {
|
||||||
read: function(page, off) {
|
read: function(page, off) {
|
||||||
return rom[off];
|
return rom[off];
|
||||||
},
|
},
|
||||||
write: function() {}
|
write: function() {},
|
||||||
|
getState() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
setState(_) {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
* implied warranty.
|
* implied warranty.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { base64_decode, base64_encode } from '../base64';
|
|
||||||
import { allocMem, debug } from '../util';
|
import { allocMem, debug } from '../util';
|
||||||
import { rom } from '../roms/cards/ramfactor';
|
import { rom } from '../roms/cards/ramfactor';
|
||||||
|
|
||||||
|
@ -140,14 +139,14 @@ export default function RAMFactor(io, size) {
|
||||||
return {
|
return {
|
||||||
loc: _loc,
|
loc: _loc,
|
||||||
firmware: _firmware,
|
firmware: _firmware,
|
||||||
mem: base64_encode(mem)
|
mem: new Uint8Array(mem)
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
setState: function(state) {
|
setState: function(state) {
|
||||||
_loc = state.loc;
|
_loc = state.loc;
|
||||||
_firmware = state.firmware;
|
_firmware = state.firmware;
|
||||||
mem = base64_decode(state.mem);
|
mem = new Uint8Array(state.mem);
|
||||||
|
|
||||||
_ramhi = (_loc >> 16) & 0xff;
|
_ramhi = (_loc >> 16) & 0xff;
|
||||||
_rammid = (_loc >> 8) & 0xff;
|
_rammid = (_loc >> 8) & 0xff;
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
* implied warranty.
|
* implied warranty.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { base64_decode } from '../base64';
|
|
||||||
import { debug, toHex } from '../util';
|
import { debug, toHex } from '../util';
|
||||||
import { rom } from '../roms/cards/smartport';
|
import { rom } from '../roms/cards/smartport';
|
||||||
|
|
||||||
|
@ -21,7 +20,6 @@ export default function SmartPort(io, cpu, options ) {
|
||||||
var BLOCK_LO = 0x46;
|
var BLOCK_LO = 0x46;
|
||||||
// var BLOCK_HI = 0x47;
|
// var BLOCK_HI = 0x47;
|
||||||
|
|
||||||
|
|
||||||
var disks = [];
|
var disks = [];
|
||||||
|
|
||||||
function _init() {
|
function _init() {
|
||||||
|
@ -36,7 +34,7 @@ export default function SmartPort(io, cpu, options ) {
|
||||||
function decodeDisk(unit, disk) {
|
function decodeDisk(unit, disk) {
|
||||||
disks[unit] = [];
|
disks[unit] = [];
|
||||||
for (var idx = 0; idx < disk.blocks.length; idx++) {
|
for (var idx = 0; idx < disk.blocks.length; idx++) {
|
||||||
disks[unit][idx] = base64_decode(disk.blocks[idx]);
|
disks[unit][idx] = new Uint8Array(disk.blocks[idx]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -413,9 +411,21 @@ export default function SmartPort(io, cpu, options ) {
|
||||||
},
|
},
|
||||||
|
|
||||||
getState: function() {
|
getState: function() {
|
||||||
|
return {
|
||||||
|
disks: disks.map(
|
||||||
|
(disk) => disk.map(
|
||||||
|
(block) => new Uint8Array(block)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
setState: function() {
|
setState: function(state) {
|
||||||
|
disks = state.disks.map(
|
||||||
|
(disk) => disk.map(
|
||||||
|
(block) => new Uint8Array(block)
|
||||||
|
)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
setBinary: function (drive, name, fmt, data) {
|
setBinary: function (drive, name, fmt, data) {
|
||||||
|
|
|
@ -150,6 +150,10 @@ export default function Thunderclock()
|
||||||
},
|
},
|
||||||
ioSwitch: function thunderclock_ioSwitch(off, val) {
|
ioSwitch: function thunderclock_ioSwitch(off, val) {
|
||||||
return _access(off, val);
|
return _access(off, val);
|
||||||
}
|
},
|
||||||
|
getState() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
setState(_) {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -256,6 +256,29 @@ export default function Videoterm(_io) {
|
||||||
return _imageData;
|
return _imageData;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
},
|
||||||
|
|
||||||
|
getState() {
|
||||||
|
return {
|
||||||
|
curReg: _curReg,
|
||||||
|
startPos: _startPos,
|
||||||
|
cursorPos: _cursorPos,
|
||||||
|
bank: _bank,
|
||||||
|
buffer: new Uint8Array(_buffer),
|
||||||
|
regs: [..._regs],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
setState(state) {
|
||||||
|
_curReg = state.curReg;
|
||||||
|
_startPos = state.startPos;
|
||||||
|
_cursorPos = state.cursorPos;
|
||||||
|
_bank = state.bank;
|
||||||
|
_buffer = new Uint8Array(_buffer);
|
||||||
|
_regs = [...state.regs];
|
||||||
|
|
||||||
|
_shouldRefresh = true;
|
||||||
|
_dirty = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
18
js/gl.ts
18
js/gl.ts
|
@ -9,7 +9,6 @@
|
||||||
* implied warranty.
|
* implied warranty.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { base64_decode, base64_encode } from './base64';
|
|
||||||
import { byte, memory, Memory, Restorable } from './types';
|
import { byte, memory, Memory, Restorable } from './types';
|
||||||
import { allocMemPages } from './util';
|
import { allocMemPages } from './util';
|
||||||
|
|
||||||
|
@ -340,16 +339,16 @@ export class LoresPageGL implements LoresPage {
|
||||||
page: this.page,
|
page: this.page,
|
||||||
mono: this._monoMode,
|
mono: this._monoMode,
|
||||||
buffer: [
|
buffer: [
|
||||||
base64_encode(this._buffer[0]),
|
new Uint8Array(this._buffer[0]),
|
||||||
base64_encode(this._buffer[1])
|
new Uint8Array(this._buffer[1]),
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
setState(state: GraphicsState) {
|
setState(state: GraphicsState) {
|
||||||
this.page = state.page;
|
this.page = state.page;
|
||||||
this._buffer[0] = base64_decode(state.buffer[0]);
|
this._buffer[0] = new Uint8Array(state.buffer[0]);
|
||||||
this._buffer[1] = base64_decode(state.buffer[1]);
|
this._buffer[1] = new Uint8Array(state.buffer[1]);
|
||||||
|
|
||||||
this.refresh();
|
this.refresh();
|
||||||
}
|
}
|
||||||
|
@ -646,16 +645,16 @@ export class HiresPageGL implements Memory, Restorable<GraphicsState> {
|
||||||
page: this.page,
|
page: this.page,
|
||||||
mono: this._monoMode,
|
mono: this._monoMode,
|
||||||
buffer: [
|
buffer: [
|
||||||
base64_encode(this._buffer[0]),
|
new Uint8Array(this._buffer[0]),
|
||||||
base64_encode(this._buffer[1])
|
new Uint8Array(this._buffer[1]),
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
setState(state: GraphicsState) {
|
setState(state: GraphicsState) {
|
||||||
this.page = state.page;
|
this.page = state.page;
|
||||||
this._buffer[0] = base64_decode(state.buffer[0]);
|
this._buffer[0] = new Uint8Array(state.buffer[0]);
|
||||||
this._buffer[1] = base64_decode(state.buffer[1]);
|
this._buffer[1] = new Uint8Array(state.buffer[1]);
|
||||||
|
|
||||||
this.refresh();
|
this.refresh();
|
||||||
}
|
}
|
||||||
|
@ -914,6 +913,7 @@ export class VideoModesGL implements VideoModes {
|
||||||
this._grs[1].setState(state.grs[1]);
|
this._grs[1].setState(state.grs[1]);
|
||||||
this._hgrs[0].setState(state.hgrs[0]);
|
this._hgrs[0].setState(state.hgrs[0]);
|
||||||
this._hgrs[1].setState(state.hgrs[1]);
|
this._hgrs[1].setState(state.hgrs[1]);
|
||||||
|
this._refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
mono(on: boolean) {
|
mono(on: boolean) {
|
||||||
|
|
105
js/mmu.ts
105
js/mmu.ts
|
@ -10,9 +10,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import CPU6502 from './cpu6502';
|
import CPU6502 from './cpu6502';
|
||||||
import RAM from './ram';
|
import RAM, { RAMState } from './ram';
|
||||||
|
import ROM, { ROMState } from './roms/rom';
|
||||||
import { debug, toHex } from './util';
|
import { debug, toHex } from './util';
|
||||||
import { byte, Memory } from './types';
|
import { byte, Memory, Restorable } from './types';
|
||||||
import Apple2IO from './apple2io';
|
import Apple2IO from './apple2io';
|
||||||
import { HiresPage, LoresPage, VideoModes } from './videomodes';
|
import { HiresPage, LoresPage, VideoModes } from './videomodes';
|
||||||
|
|
||||||
|
@ -138,10 +139,18 @@ class Switches implements Memory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AuxRom {
|
class AuxRom implements Memory {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly mmu: MMU,
|
private readonly mmu: MMU,
|
||||||
private readonly rom: Memory) { }
|
private readonly rom: ROM) { }
|
||||||
|
|
||||||
|
start() {
|
||||||
|
return 0xc1;
|
||||||
|
}
|
||||||
|
|
||||||
|
end() {
|
||||||
|
return 0xcf;
|
||||||
|
}
|
||||||
|
|
||||||
read(page: byte, off: byte) {
|
read(page: byte, off: byte) {
|
||||||
if (page == 0xc3) {
|
if (page == 0xc3) {
|
||||||
|
@ -160,38 +169,33 @@ class AuxRom {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
export interface MMUState {
|
||||||
interface State {
|
bank1: boolean
|
||||||
bank1: this._bank1,
|
readbsr: boolean
|
||||||
readbsr: this._readbsr,
|
writebsr: boolean
|
||||||
writebsr: this._writebsr,
|
prewrite: boolean
|
||||||
prewrite: this._prewrite,
|
|
||||||
|
|
||||||
intcxrom: this._intcxrom,
|
intcxrom: boolean
|
||||||
slot3rom: this._slot3rom,
|
slot3rom: boolean
|
||||||
intc8rom: this._intc8rom,
|
intc8rom: boolean
|
||||||
|
|
||||||
auxRamRead: this._auxRamRead,
|
auxRamRead: boolean
|
||||||
auxRamWrite: this._auxRamWrite,
|
auxRamWrite: boolean
|
||||||
altzp: this._altzp,
|
altzp: boolean
|
||||||
|
|
||||||
_80store: this._80store,
|
_80store: boolean
|
||||||
page2: this._page2,
|
page2: boolean
|
||||||
hires: this._hires,
|
hires: boolean
|
||||||
|
|
||||||
mem00_01: [this.mem00_01[0].getState(), this.mem00_01[1].getState()],
|
mem00_01: [RAMState, RAMState]
|
||||||
mem02_03: [this.mem02_03[0].getState(), this.mem02_03[1].getState()],
|
mem02_03: [RAMState, RAMState]
|
||||||
mem0C_1F: [this.mem0C_1F[0].getState(), this.mem0C_1F[1].getState()],
|
mem0C_1F: [RAMState, RAMState]
|
||||||
mem60_BF: [this.mem60_BF[0].getState(), this.mem60_BF[1].getState()],
|
mem60_BF: [RAMState, RAMState]
|
||||||
memD0_DF: [
|
memD0_DF: [ROMState, RAMState, RAMState, RAMState, RAMState]
|
||||||
this.memD0_DF[0].getState(), this.memD0_DF[1].getState(),
|
memE0_FF: [ROMState, RAMState, RAMState]
|
||||||
this.memD0_DF[2].getState(), this.memD0_DF[3].getState()
|
}
|
||||||
],
|
|
||||||
memE0_FF: [this.memE0_FF[0].getState(), this.memE0_FF[1].getState()]
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default class MMU implements Memory {
|
export default class MMU implements Memory, Restorable<MMUState> {
|
||||||
private _readPages = new Array(0x100);
|
private _readPages = new Array(0x100);
|
||||||
private _writePages = new Array(0x100);
|
private _writePages = new Array(0x100);
|
||||||
private _pages = new Array(0x100);
|
private _pages = new Array(0x100);
|
||||||
|
@ -235,12 +239,15 @@ export default class MMU implements Memory {
|
||||||
private mem60_BF = [new RAM(0x60, 0xBF), new RAM(0x60, 0xBF)];
|
private mem60_BF = [new RAM(0x60, 0xBF), new RAM(0x60, 0xBF)];
|
||||||
private memC0_C0 = [this.switches];
|
private memC0_C0 = [this.switches];
|
||||||
private memC1_CF = [this.io, this.auxRom];
|
private memC1_CF = [this.io, this.auxRom];
|
||||||
private memD0_DF = [
|
private memD0_DF: [ROM, RAM, RAM, RAM, RAM] = [
|
||||||
this.rom,
|
this.rom,
|
||||||
new RAM(0xD0, 0xDF), new RAM(0xD0, 0xDF),
|
new RAM(0xD0, 0xDF), new RAM(0xD0, 0xDF),
|
||||||
new RAM(0xD0, 0xDF), new RAM(0xD0, 0xDF)
|
new RAM(0xD0, 0xDF), new RAM(0xD0, 0xDF)
|
||||||
];
|
];
|
||||||
private memE0_FF = [this.rom, new RAM(0xE0, 0xFF), new RAM(0xE0, 0xFF)];
|
private memE0_FF: [ROM, RAM, RAM] = [
|
||||||
|
this.rom,
|
||||||
|
new RAM(0xE0, 0xFF), new RAM(0xE0, 0xFF)
|
||||||
|
];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly cpu: CPU6502,
|
private readonly cpu: CPU6502,
|
||||||
|
@ -250,8 +257,7 @@ export default class MMU implements Memory {
|
||||||
private readonly hires1: HiresPage,
|
private readonly hires1: HiresPage,
|
||||||
private readonly hires2: HiresPage,
|
private readonly hires2: HiresPage,
|
||||||
private readonly io: Apple2IO,
|
private readonly io: Apple2IO,
|
||||||
// TODO(flan): Better typing.
|
private readonly rom: ROM) {
|
||||||
private readonly rom: any) {
|
|
||||||
/*
|
/*
|
||||||
* Initialize read/write banks
|
* Initialize read/write banks
|
||||||
*/
|
*/
|
||||||
|
@ -332,7 +338,7 @@ export default class MMU implements Memory {
|
||||||
}
|
}
|
||||||
|
|
||||||
_initSwitches() {
|
_initSwitches() {
|
||||||
this._bank1 = true;
|
this._bank1 = false;
|
||||||
this._readbsr = false;
|
this._readbsr = false;
|
||||||
this._writebsr = false;
|
this._writebsr = false;
|
||||||
this._prewrite = false;
|
this._prewrite = false;
|
||||||
|
@ -352,7 +358,7 @@ export default class MMU implements Memory {
|
||||||
this._iouDisable = true;
|
this._iouDisable = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_debug(..._args: any) {
|
_debug(..._args: any[]) {
|
||||||
// debug.apply(this, arguments);
|
// debug.apply(this, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -813,6 +819,9 @@ export default class MMU implements Memory {
|
||||||
}
|
}
|
||||||
|
|
||||||
public read(page: byte, off: byte) {
|
public read(page: byte, off: byte) {
|
||||||
|
if (page === 0xff && off === 0xfc && this._intcxrom) {
|
||||||
|
this._initSwitches();
|
||||||
|
}
|
||||||
return this._readPages[page].read(page, off);
|
return this._readPages[page].read(page, off);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -823,8 +832,8 @@ export default class MMU implements Memory {
|
||||||
public resetVB() {
|
public resetVB() {
|
||||||
this._vbEnd = this.cpu.getCycles() + 1000;
|
this._vbEnd = this.cpu.getCycles() + 1000;
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
public getState(): State {
|
public getState(): MMUState {
|
||||||
return {
|
return {
|
||||||
bank1: this._bank1,
|
bank1: this._bank1,
|
||||||
readbsr: this._readbsr,
|
readbsr: this._readbsr,
|
||||||
|
@ -848,14 +857,21 @@ export default class MMU implements Memory {
|
||||||
mem0C_1F: [this.mem0C_1F[0].getState(), this.mem0C_1F[1].getState()],
|
mem0C_1F: [this.mem0C_1F[0].getState(), this.mem0C_1F[1].getState()],
|
||||||
mem60_BF: [this.mem60_BF[0].getState(), this.mem60_BF[1].getState()],
|
mem60_BF: [this.mem60_BF[0].getState(), this.mem60_BF[1].getState()],
|
||||||
memD0_DF: [
|
memD0_DF: [
|
||||||
this.memD0_DF[0].getState(), this.memD0_DF[1].getState(),
|
this.memD0_DF[0].getState(),
|
||||||
this.memD0_DF[2].getState(), this.memD0_DF[3].getState()
|
this.memD0_DF[1].getState(),
|
||||||
|
this.memD0_DF[2].getState(),
|
||||||
|
this.memD0_DF[3].getState(),
|
||||||
|
this.memD0_DF[4].getState()
|
||||||
],
|
],
|
||||||
memE0_FF: [this.memE0_FF[0].getState(), this.memE0_FF[1].getState()]
|
memE0_FF: [
|
||||||
|
this.memE0_FF[0].getState(),
|
||||||
|
this.memE0_FF[1].getState(),
|
||||||
|
this.memE0_FF[2].getState()
|
||||||
|
]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public setState(state: State) {
|
public setState(state: MMUState) {
|
||||||
this._readbsr = state.readbsr;
|
this._readbsr = state.readbsr;
|
||||||
this._writebsr = state.writebsr;
|
this._writebsr = state.writebsr;
|
||||||
this._bank1 = state.bank1;
|
this._bank1 = state.bank1;
|
||||||
|
@ -885,10 +901,11 @@ export default class MMU implements Memory {
|
||||||
this.memD0_DF[1].setState(state.memD0_DF[1]);
|
this.memD0_DF[1].setState(state.memD0_DF[1]);
|
||||||
this.memD0_DF[2].setState(state.memD0_DF[2]);
|
this.memD0_DF[2].setState(state.memD0_DF[2]);
|
||||||
this.memD0_DF[3].setState(state.memD0_DF[3]);
|
this.memD0_DF[3].setState(state.memD0_DF[3]);
|
||||||
|
this.memD0_DF[4].setState(state.memD0_DF[4]);
|
||||||
this.memE0_FF[0].setState(state.memE0_FF[0]);
|
this.memE0_FF[0].setState(state.memE0_FF[0]);
|
||||||
this.memE0_FF[1].setState(state.memE0_FF[1]);
|
this.memE0_FF[1].setState(state.memE0_FF[1]);
|
||||||
|
this.memE0_FF[2].setState(state.memE0_FF[2]);
|
||||||
|
|
||||||
this._updateBanks();
|
this._updateBanks();
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
27
js/ram.ts
27
js/ram.ts
|
@ -9,24 +9,19 @@
|
||||||
* implied warranty.
|
* implied warranty.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { base64_decode, base64_encode } from './base64';
|
import { byte, memory, Memory, Restorable } from './types';
|
||||||
import { byte, memory, Memory } from './types';
|
|
||||||
import { allocMemPages } from './util';
|
import { allocMemPages } from './util';
|
||||||
|
|
||||||
export interface State {
|
export interface RAMState {
|
||||||
/** Start of memory region. */
|
/** Copy of contents. */
|
||||||
start: byte;
|
mem: memory;
|
||||||
/** End of memory region. */
|
|
||||||
end: byte;
|
|
||||||
/** Base64-encoded contents. */
|
|
||||||
mem: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents RAM from the start page `sp` to end page `ep`. The memory
|
* Represents RAM from the start page `sp` to end page `ep`. The memory
|
||||||
* is addressed by `page` and `offset`.
|
* is addressed by `page` and `offset`.
|
||||||
*/
|
*/
|
||||||
export default class RAM implements Memory {
|
export default class RAM implements Memory, Restorable<RAMState> {
|
||||||
private start_page: byte;
|
private start_page: byte;
|
||||||
private end_page: byte;
|
private end_page: byte;
|
||||||
private mem: memory;
|
private mem: memory;
|
||||||
|
@ -54,17 +49,13 @@ export default class RAM implements Memory {
|
||||||
this.mem[(page - this.start_page) << 8 | offset] = val;
|
this.mem[(page - this.start_page) << 8 | offset] = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getState(): State {
|
public getState(): RAMState {
|
||||||
return {
|
return {
|
||||||
start: this.start_page,
|
mem: new Uint8Array(this.mem)
|
||||||
end: this.end_page,
|
|
||||||
mem: base64_encode(this.mem)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public setState(state: State) {
|
public setState(state: RAMState) {
|
||||||
this.start_page = state.start;
|
this.mem = new Uint8Array(state.mem);
|
||||||
this.end_page = state.end;
|
|
||||||
this.mem = base64_decode(state.mem);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { PageHandler } from '../cpu6502';
|
import { PageHandler } from '../cpu6502';
|
||||||
import { byte, rom } from '../types';
|
import { Restorable, byte, rom } from '../types';
|
||||||
|
|
||||||
export default class ROM implements PageHandler {
|
export type ROMState = null;
|
||||||
|
|
||||||
|
export default class ROM implements PageHandler, Restorable<ROMState> {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly startPage: byte,
|
private readonly startPage: byte,
|
||||||
|
@ -24,4 +26,9 @@ export default class ROM implements PageHandler {
|
||||||
}
|
}
|
||||||
write() {
|
write() {
|
||||||
}
|
}
|
||||||
|
getState() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
setState(_state: null) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ export interface Memory {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* An interface card */
|
/* An interface card */
|
||||||
export interface Card extends Memory {
|
export interface Card extends Memory, Restorable {
|
||||||
/* Reset the card */
|
/* Reset the card */
|
||||||
reset(): void;
|
reset(): void;
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ export interface DiskIIDrive extends Drive {
|
||||||
|
|
||||||
export type TapeData = Array<[duration: number, high: boolean]>;
|
export type TapeData = Array<[duration: number, high: boolean]>;
|
||||||
|
|
||||||
export interface Restorable<T> {
|
export interface Restorable<T = any> {
|
||||||
getState(): T;
|
getState(): T;
|
||||||
setState(state: T): void;
|
setState(state: T): void;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import MicroModal from 'micromodal';
|
import MicroModal from 'micromodal';
|
||||||
|
|
||||||
|
import { base64_json_parse, base64_json_stringify } from '../base64';
|
||||||
import Audio from './audio';
|
import Audio from './audio';
|
||||||
import DriveLights from './drive_lights';
|
import DriveLights from './drive_lights';
|
||||||
import { DISK_FORMATS } from '../types';
|
import { DISK_FORMATS } from '../types';
|
||||||
|
@ -301,13 +302,11 @@ function doLoadLocalDisk(drive, file) {
|
||||||
|
|
||||||
if (this.result.byteLength >= 800 * 1024) {
|
if (this.result.byteLength >= 800 * 1024) {
|
||||||
if (_smartPort.setBinary(drive, name, ext, this.result)) {
|
if (_smartPort.setBinary(drive, name, ext, this.result)) {
|
||||||
driveLights.label(drive, name);
|
|
||||||
focused = false;
|
focused = false;
|
||||||
initGamepad();
|
initGamepad();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (_disk2.setBinary(drive, name, ext, this.result)) {
|
if (_disk2.setBinary(drive, name, ext, this.result)) {
|
||||||
driveLights.label(drive, name);
|
|
||||||
focused = false;
|
focused = false;
|
||||||
initGamepad();
|
initGamepad();
|
||||||
}
|
}
|
||||||
|
@ -362,12 +361,10 @@ export function doLoadHTTP(drive, _url) {
|
||||||
var name = decodeURIComponent(fileParts.join('.'));
|
var name = decodeURIComponent(fileParts.join('.'));
|
||||||
if (data.byteLength >= 800 * 1024) {
|
if (data.byteLength >= 800 * 1024) {
|
||||||
if (_smartPort.setBinary(drive, name, ext, data)) {
|
if (_smartPort.setBinary(drive, name, ext, data)) {
|
||||||
driveLights.label(drive, name);
|
|
||||||
initGamepad();
|
initGamepad();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (_disk2.setBinary(drive, name, ext, data)) {
|
if (_disk2.setBinary(drive, name, ext, data)) {
|
||||||
driveLights.label(drive, name);
|
|
||||||
initGamepad();
|
initGamepad();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -503,7 +500,6 @@ function loadDisk(drive, disk) {
|
||||||
disk_cur_cat[drive] = category;
|
disk_cur_cat[drive] = category;
|
||||||
disk_cur_name[drive] = name;
|
disk_cur_name[drive] = name;
|
||||||
|
|
||||||
driveLights.label(drive, name);
|
|
||||||
_disk2.setDisk(drive, disk);
|
_disk2.setDisk(drive, disk);
|
||||||
initGamepad(disk.gamepad);
|
initGamepad(disk.gamepad);
|
||||||
}
|
}
|
||||||
|
@ -635,7 +631,7 @@ function _keydown(evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
|
|
||||||
var key = keyboard.mapKeyEvent(evt);
|
var key = keyboard.mapKeyEvent(evt);
|
||||||
if (key != 0xff) {
|
if (key !== 0xff) {
|
||||||
io.keyDown(key);
|
io.keyDown(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -666,9 +662,11 @@ function _keydown(evt) {
|
||||||
} else if (evt.keyCode === 114) { // F3
|
} else if (evt.keyCode === 114) { // F3
|
||||||
io.keyDown(0x1b);
|
io.keyDown(0x1b);
|
||||||
} else if (evt.keyCode === 117) { // F6 Quick Save
|
} else if (evt.keyCode === 117) { // F6 Quick Save
|
||||||
_apple2.getState();
|
window.localStorage.state = base64_json_stringify(_apple2.getState());
|
||||||
} else if (evt.keyCode === 120) { // F9 Quick Restore
|
} else if (evt.keyCode === 120) { // F9 Quick Restore
|
||||||
_apple2.setState();
|
if (window.localStorage.state) {
|
||||||
|
_apple2.setState(base64_json_parse(window.localStorage.state));
|
||||||
|
}
|
||||||
} else if (evt.keyCode == 16) { // Shift
|
} else if (evt.keyCode == 16) { // Shift
|
||||||
keyboard.shiftKey(true);
|
keyboard.shiftKey(true);
|
||||||
} else if (evt.keyCode == 20) { // Caps lock
|
} else if (evt.keyCode == 20) { // Caps lock
|
||||||
|
|
|
@ -15,20 +15,6 @@ export default function DriveLights()
|
||||||
document.querySelector('#disk-label' + drive).innerText = label;
|
document.querySelector('#disk-label' + drive).innerText = label;
|
||||||
}
|
}
|
||||||
return document.querySelector('#disk-label' + drive).innerText;
|
return document.querySelector('#disk-label' + drive).innerText;
|
||||||
},
|
|
||||||
getState: function() {
|
|
||||||
return {
|
|
||||||
disks: [
|
|
||||||
this.label(1),
|
|
||||||
this.label(2)
|
|
||||||
]
|
|
||||||
};
|
|
||||||
},
|
|
||||||
setState: function(state) {
|
|
||||||
if (state && state.disks) {
|
|
||||||
this.label(1, state.disks[0].label);
|
|
||||||
this.label(2, state.disks[1].label);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -210,7 +210,7 @@ export default function KeyBoard(cpu, io, e) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mapKeyEvent: function keyboard_mapKeyEvent(evt) {
|
mapKeyEvent: function keyboard_mapKeyEvent(evt) {
|
||||||
var code = evt.keyCode, key = '\xff';
|
var code = evt.keyCode, key = 0xff;
|
||||||
|
|
||||||
if (evt.key in uiKitMap) {
|
if (evt.key in uiKitMap) {
|
||||||
key = uiKitMap[evt.key];
|
key = uiKitMap[evt.key];
|
||||||
|
@ -230,7 +230,7 @@ export default function KeyBoard(cpu, io, e) {
|
||||||
|
|
||||||
if (key == 0x7F && evt.shiftKey && evt.ctrlKey) {
|
if (key == 0x7F && evt.shiftKey && evt.ctrlKey) {
|
||||||
cpu.reset();
|
cpu.reset();
|
||||||
key = '\xff';
|
key = 0xff;
|
||||||
}
|
}
|
||||||
|
|
||||||
return key;
|
return key;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Memory, Restorable, byte } from './types';
|
import { Memory, Restorable, byte, memory } from './types';
|
||||||
|
|
||||||
export type bank = 0 | 1;
|
export type bank = 0 | 1;
|
||||||
export type pageNo = 1 | 2;
|
export type pageNo = 1 | 2;
|
||||||
|
@ -19,7 +19,7 @@ export interface Region {
|
||||||
export interface GraphicsState {
|
export interface GraphicsState {
|
||||||
page: byte;
|
page: byte;
|
||||||
mono: boolean;
|
mono: boolean;
|
||||||
buffer: string[];
|
buffer: memory[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VideoModesState {
|
export interface VideoModesState {
|
||||||
|
|
60
test/js/base64.test.ts
Normal file
60
test/js/base64.test.ts
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
/** @fileoverview Test for base64.ts. */
|
||||||
|
|
||||||
|
import {
|
||||||
|
base64_encode,
|
||||||
|
base64_decode,
|
||||||
|
base64_json_parse,
|
||||||
|
base64_json_stringify,
|
||||||
|
} from '../../js/base64';
|
||||||
|
|
||||||
|
describe('base64', () => {
|
||||||
|
let memory: Uint8Array;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
memory = new Uint8Array([1,2,3,4,5,6]);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('base64_encode', () => {
|
||||||
|
it('encodes Uint8Arrays', () => {
|
||||||
|
expect(base64_encode(memory)).toEqual('AQIDBAUG');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('base64_decode', () => {
|
||||||
|
it('encodes Uint8Arrays', () => {
|
||||||
|
expect(base64_decode('AQIDBAUG')).toEqual(memory);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('base64_json_parse', () => {
|
||||||
|
it('handles structures with Uint8Arrays', () => {
|
||||||
|
expect(base64_json_parse(`\
|
||||||
|
{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": {
|
||||||
|
"biff": "data:application/octet-stream;base64,AQIDBAUG"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)).toEqual({
|
||||||
|
foo: 'bar',
|
||||||
|
baz: {
|
||||||
|
biff: memory
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('base64_json_stringify', () => {
|
||||||
|
it('handles structures with Uint8Arrays', () => {
|
||||||
|
expect(base64_json_stringify({
|
||||||
|
foo: 'bar',
|
||||||
|
baz: {
|
||||||
|
biff: memory
|
||||||
|
}
|
||||||
|
})).toEqual(
|
||||||
|
'{"foo":"bar","baz":{"biff":"data:application/octet-stream;base64,AQIDBAUG"}}'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user