mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2026-10-25 00:02:16 +00:00
handle failed BIOS loads more gracefully
This commit is contained in:
1 parent
0988fa9136
commit
e109fd4f4f
4 files changed
+143
-4
No files matched your search
@@ -125,6 +125,11 @@ export abstract class BaseWASMMachine {
|
||||
this.reset(); // TODO?
|
||||
}
|
||||
loadBIOS(srcArray: Uint8Array) {
|
||||
// the machine's ROM layout is fixed, so a bigger image can't work;
|
||||
// TypedArray.set() would only throw a cryptic "source array is too long"
|
||||
if (srcArray.length > this.biosarr.length) {
|
||||
throw new Error(`BIOS too large: ${srcArray.length} bytes, but this machine's BIOS is only ${this.biosarr.length} bytes`);
|
||||
}
|
||||
this.biosarr.set(srcArray);
|
||||
// machine_init copied the ROMs out of biosarr, so a BIOS loaded after
|
||||
// that has to build a new machine (the old one is leaked)
|
||||
|
||||
+9
-1
@@ -1086,7 +1086,15 @@ async function loadBIOSFromProject() {
|
||||
var biosdata = await store.getItem(biospath);
|
||||
if (biosdata instanceof Uint8Array) {
|
||||
console.log('loading BIOS', biospath, biosdata.length + " bytes")
|
||||
platform.loadBIOS(biospath, biosdata);
|
||||
try {
|
||||
platform.loadBIOS(biospath, biosdata);
|
||||
} catch (e) {
|
||||
// A bad BIOS file must not lock us out of the IDE (and its file manager):
|
||||
// the platform keeps running with its built-in BIOS, so we warn and go on.
|
||||
console.log(e);
|
||||
alertError(`Could not load BIOS file "${biospath}": ${e.message || e + ''}<br><br>` +
|
||||
'The emulator will use its built-in BIOS. Delete or rename the file to fix this.');
|
||||
}
|
||||
} else {
|
||||
console.log('BIOS file must be binary')
|
||||
}
|
||||
|
||||
+80
-3
@@ -7,10 +7,26 @@
|
||||
|
||||
import { Machine } from "../common/baseplatform";
|
||||
import { Probeable, TrapCondition } from "../common/devices";
|
||||
import { KeyFlags } from "../common/emu";
|
||||
import { hex } from "../common/util";
|
||||
import { KeyFlags, dumpRAM } from "../common/emu";
|
||||
import { hex, lpad } from "../common/util";
|
||||
import { BaseWASMMachine } from "../common/wasmplatform";
|
||||
|
||||
// Offsets of the registers in the C structs that machine_get_debug_pointer()
|
||||
// returns, for the debug panes. The chips structs only hold integers, so the
|
||||
// offsets are the same in every WASM build of the machine.
|
||||
const VIA_PA = 0x00; // m6522_port_t { inpr, outr, ddr, pins }
|
||||
const VIA_PB = 0x0a;
|
||||
const VIA_T1 = 0x14; // m6522_timer_t { latch, counter, ... }
|
||||
const VIA_T2 = 0x1c;
|
||||
const VIA_IER = 0x24; // m6522_int_t { ier, ifr, pip }
|
||||
const VIA_IFR = 0x25;
|
||||
const VIA_ACR = 0x28;
|
||||
const VIA_PCR = 0x29;
|
||||
const VIA_SIZE = 56;
|
||||
// m6561_t starts with { uint64 pins, fetch_cb, user_data, bool debug_vis },
|
||||
// and pointers are 32 bits in WASM32, so regs[16] starts at byte 17.
|
||||
const VIC_REGS = 17;
|
||||
|
||||
export class VIC20_WASMMachine extends BaseWASMMachine implements Machine, Probeable {
|
||||
|
||||
numTotalScanlines = 312;
|
||||
@@ -139,12 +155,25 @@ export class VIC20_WASMMachine extends BaseWASMMachine implements Machine, Probe
|
||||
o:this.readConst(pc),
|
||||
}
|
||||
}
|
||||
getDebugStateOffset(index: number) {
|
||||
var p = this.exports.machine_get_debug_pointer(this.sys, index);
|
||||
return p - this.sys;
|
||||
}
|
||||
saveState() {
|
||||
this.exports.machine_save_state(this.sys, this.stateptr);
|
||||
// the debug panes want the chip registers at the moment of the save, so
|
||||
// they travel in the state instead of being read from the live machine
|
||||
let ram0 = this.getDebugStateOffset(5);
|
||||
let via1 = this.getDebugStateOffset(1);
|
||||
let via2 = this.getDebugStateOffset(2);
|
||||
let vic = this.getDebugStateOffset(3) + VIC_REGS;
|
||||
return {
|
||||
c:this.getCPUState(),
|
||||
state:this.statearr.slice(0),
|
||||
ram:this.statearr.slice(18640, 18640+0x200), // ZP and stack (TODO)
|
||||
ram:this.statearr.slice(ram0, ram0+0x200), // ZP and stack
|
||||
via1:this.statearr.slice(via1, via1+VIA_SIZE),
|
||||
via2:this.statearr.slice(via2, via2+VIA_SIZE),
|
||||
vic:this.statearr.slice(vic, vic+16),
|
||||
// the joystick mask lives on this object, not in the WASM state blob,
|
||||
// so it has to be saved explicitly or it leaks across a rewind
|
||||
joymask0: this.joymask0,
|
||||
@@ -161,6 +190,54 @@ export class VIC20_WASMMachine extends BaseWASMMachine implements Machine, Probe
|
||||
getVideoParams() {
|
||||
return {width:232, height:272, overscan:true, videoFrequency:50, aspect:1.5};
|
||||
}
|
||||
getDebugCategories() {
|
||||
return ['CPU','ZPRAM','Stack','VIA','VIC'];
|
||||
}
|
||||
// translate an address as the VIC-I sees it (14 bits, its own memory map)
|
||||
// into the CPU address of the same memory, or "ROM" for the character ROM
|
||||
vicAddressToCPU(addr: number) {
|
||||
if (addr < 0x2000) return 'ROM $' + hex(0x8000 + (addr & 0xfff), 4);
|
||||
return '$' + hex((addr - 0x2000) & 0xffff, 4);
|
||||
}
|
||||
// format one VIA, in the style of the C64's CIA pane
|
||||
viaToString(name: string, m: Uint8Array) {
|
||||
let t1 = m[VIA_T1+2] + m[VIA_T1+3]*256;
|
||||
let t2 = m[VIA_T2+2] + m[VIA_T2+3]*256;
|
||||
let s = `${name}\n`;
|
||||
s += ` A: Out ${hex(m[VIA_PA+1])} In ${hex(m[VIA_PA+0])} DDR ${hex(m[VIA_PA+2])}`;
|
||||
s += ` T1 ${hex(t1, 4)}\n`;
|
||||
s += ` B: Out ${hex(m[VIA_PB+1])} In ${hex(m[VIA_PB+0])} DDR ${hex(m[VIA_PB+2])}`;
|
||||
s += ` T2 ${hex(t2, 4)}\n`;
|
||||
s += ` ACR ${hex(m[VIA_ACR])} PCR ${hex(m[VIA_PCR])}`;
|
||||
s += ` IER ${hex(m[VIA_IER])} IFR ${hex(m[VIA_IFR])}\n`;
|
||||
return s;
|
||||
}
|
||||
getDebugInfo(category: string, state: any) {
|
||||
switch (category) {
|
||||
case 'VIA': {
|
||||
let s = '';
|
||||
s += this.viaToString('VIA 1 ($9110) joystick, cassette, NMI', state.via1);
|
||||
s += this.viaToString('VIA 2 ($9120) keyboard, cassette, IRQ', state.via2);
|
||||
return s;
|
||||
}
|
||||
case 'VIC': {
|
||||
let m = state.vic;
|
||||
// screen memory is at A13..A9 from $9005 bits 4-7 and $9002 bit 7
|
||||
// (A8 must be 0, the screen needs a 512 byte page)
|
||||
let screen = ((m[5] & 0xf0) << 6) | ((m[2] & 0x80) << 2);
|
||||
// character memory is the $9005 low nibble, in 1K steps
|
||||
let charmem = (m[5] & 0x0f) << 10;
|
||||
let s = 'Mode: TEXT\n';
|
||||
s += `Raster: ${lpad(this.getRasterY(), 3)}`;
|
||||
s += ` Border ${m[15] & 7} Background ${(m[15]>>4) & 0xf} Aux ${(m[14]>>4) & 0xf}`;
|
||||
s += ` Volume ${m[14] & 0xf}\n`;
|
||||
s += `Screen: ${this.vicAddressToCPU(screen)} `;
|
||||
s += `Char: ${this.vicAddressToCPU(charmem)}\n`;
|
||||
s += dumpRAM(m, 0x9000, 16);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
setKeyInput(key: number, code: number, flags: number): void {
|
||||
// TODO: handle shifted keys
|
||||
if (key == 16 || key == 17 || key == 18 || key == 224) return; // meta keys
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import assert from "assert";
|
||||
import { describe, it } from "mocha";
|
||||
import * as fs from "fs";
|
||||
|
||||
// .gitmodules lives at the repo root, which is the cwd for `npm test`.
|
||||
const GITMODULES = '.gitmodules';
|
||||
|
||||
interface Submodule { name: string, path: string, url: string }
|
||||
|
||||
function parseGitmodules(text: string): Submodule[] {
|
||||
var out: Submodule[] = [];
|
||||
var cur: any = null;
|
||||
for (var line of text.split('\n')) {
|
||||
var m;
|
||||
if ((m = line.match(/^\s*\[\s*submodule\s+"([^"]+)"\s*\]/))) {
|
||||
cur = { name: m[1] };
|
||||
out.push(cur);
|
||||
} else if (cur && (m = line.match(/^\s*(\w+)\s*=\s*(.*?)\s*$/))) {
|
||||
cur[m[1]] = m[2];
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// git resolves a relative URL ("./chips") against the superproject's remote,
|
||||
// so "./chips" in a repo hosted at github.com/org/repo becomes
|
||||
// github.com/org/repo/chips -- a 404 that breaks every CI clone.
|
||||
function isRelativeURL(url: string) {
|
||||
return url.startsWith('./') || url.startsWith('../') || !/^[a-z][a-z0-9+.-]*:\/\/|^[^@\/]+@[^:\/]+:./i.test(url);
|
||||
}
|
||||
|
||||
describe('submodules', () => {
|
||||
it('.gitmodules exists and lists submodules', function () {
|
||||
if (!fs.existsSync(GITMODULES)) this.skip();
|
||||
var subs = parseGitmodules(fs.readFileSync(GITMODULES, 'utf8'));
|
||||
assert.ok(subs.length > 0, 'no submodules parsed from .gitmodules');
|
||||
});
|
||||
|
||||
it('uses absolute URLs that do not depend on the superproject remote', function () {
|
||||
if (!fs.existsSync(GITMODULES)) this.skip();
|
||||
var subs = parseGitmodules(fs.readFileSync(GITMODULES, 'utf8'));
|
||||
for (var sub of subs) {
|
||||
assert.ok(sub.path, `${sub.name}: missing path`);
|
||||
assert.ok(sub.url, `${sub.name}: missing url`);
|
||||
assert.ok(!isRelativeURL(sub.url),
|
||||
`${sub.name}: relative url "${sub.url}" resolves against the superproject remote and breaks CI; use e.g. https://github.com/8bitworkshop/${sub.name}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user