Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | 2x 2x 24x 34x 13x 13x 13x 2x 30x 30x 30x 60x 9x 9x 800x 800x 800x 800x 800x 8x 9x 9x 9x 9x 9x 30x 30x 30x 21x 1x 20x 1x 19x 1x 18x 4x 2x 16x 13x 3594x 185x 3409x 17x 3267x 175x 175x 175x 3092x | import { typedArrayToBase64, base64ToTypedArray } from "./state-utils.js";
import { findModel } from "./models.js";
const SnapshotFormat = "jsbeeb-snapshot";
const SnapshotVersion = 3;
/**
* Whether a snapshot was taken on a machine with a second processor fitted.
* Nothing before version 3 captured tube state, so those snapshots are always host-only.
*/
export function hasCoProcessor(snapshot) {
return !!snapshot.coProcessor;
}
/**
* Check if two model names resolve to the same model (accounting for
* synonyms and old names). Used to decide whether a page reload is
* needed when loading a snapshot saved under a different model name.
*/
export function isSameModel(nameA, nameB) {
if (nameA === nameB) return true;
const a = findModel(nameA);
const b = findModel(nameB);
return a !== null && a === b;
}
// Map of TypedArray constructor names for deserialization
const TypedArrayConstructors = {
Uint8Array,
Uint16Array,
Uint32Array,
Int32Array,
Float32Array,
Float64Array,
};
/**
* Create a snapshot of the emulator state for save-to-file.
* Disc track pulse data is stripped — on restore, the discs are reloaded
* from the source references in the `media` field. (The in-memory rewind
* path uses cpu.snapshotState() directly, which retains full disc data.)
* @param {import('./6502.js').Cpu6502} cpu
* @param {object} model - the model definition object
* @param {object} [media] - optional media source references (disc1, disc2)
* @returns {object} snapshot object
*/
export function createSnapshot(cpu, model, media) {
const state = cpu.snapshotState();
// Strip clean disc track data from the save-to-file snapshot.
// The FDC/drive mechanical state is kept; only clean tracks
// (which can be reloaded from the disc image) are removed.
// Dirty tracks (written since disc load) are kept as an overlay.
Eif (state.fdc && state.fdc.drives) {
for (const drive of state.fdc.drives) {
if (drive.disc) {
const dirtyTracks = {};
for (const key of Object.keys(drive.disc.tracks)) {
const [sideStr, trackNumStr] = key.split(":");
const isSideUpper = sideStr === "true";
const trackNum = parseInt(trackNumStr, 10);
const dirtyKey = trackNum | (isSideUpper ? 0x100 : 0);
if (drive.disc._everDirtyTracks && drive.disc._everDirtyTracks.has(dirtyKey)) {
dirtyTracks[key] = drive.disc.tracks[key];
}
}
drive.disc.tracks = {};
drive.disc.dirtyTracks = dirtyTracks;
// Clean up internal-only fields not needed in serialized state
delete drive.disc._everDirtyTracks;
delete drive.disc._originalImageData;
delete drive.disc._originalImageCrc32;
}
}
}
const snapshot = {
format: SnapshotFormat,
version: SnapshotVersion,
model: model.name,
// The model name cannot distinguish a Turbo from a plain Master: the co-processor is
// emulation config rather than part of the model.
coProcessor: cpu.hasTube,
timestamp: new Date().toISOString(),
state,
};
Iif (media) snapshot.media = media;
return snapshot;
}
/**
* Restore emulator state from a snapshot.
* @param {import('./6502.js').Cpu6502} cpu
* @param {object} model - the current model definition
* @param {object} snapshot
* @throws {Error} if the model doesn't match
*/
export function restoreSnapshot(cpu, model, snapshot) {
if (snapshot.format !== SnapshotFormat) {
throw new Error(`Unknown snapshot format: ${snapshot.format}`);
}
if (snapshot.version > SnapshotVersion) {
throw new Error(`Snapshot version ${snapshot.version} is newer than supported version ${SnapshotVersion}`);
}
if (!isSameModel(snapshot.model, model.name)) {
throw new Error(`Model mismatch: snapshot is for "${snapshot.model}" but current model is "${model.name}"`);
}
if (hasCoProcessor(snapshot) !== cpu.hasTube) {
const fitted = (yes) => (yes ? "with a second processor" : "without a second processor");
throw new Error(
`Co-processor mismatch: snapshot was taken ${fitted(hasCoProcessor(snapshot))} ` +
`but this machine is ${fitted(cpu.hasTube)}`,
);
}
cpu.restoreState(snapshot.state);
}
/**
* Serialize a snapshot to a JSON string, converting TypedArrays to base64.
* @param {object} snapshot
* @returns {string} JSON string
*/
export function snapshotToJSON(snapshot) {
return JSON.stringify(snapshot, (key, value) => {
if (ArrayBuffer.isView(value) && !(value instanceof DataView)) {
return {
__typedArray: true,
type: value.constructor.name,
data: typedArrayToBase64(value),
};
}
return value;
});
}
/**
* Deserialize a snapshot from a JSON string, converting base64 back to TypedArrays.
* @param {string} json
* @returns {object} snapshot object
*/
export function snapshotFromJSON(json) {
return JSON.parse(json, (key, value) => {
if (value && value.__typedArray) {
const Constructor = TypedArrayConstructors[value.type];
Iif (!Constructor) {
throw new Error(`Unknown TypedArray type: ${value.type}`);
}
return base64ToTypedArray(value.data, Constructor);
}
return value;
});
}
|