jsbeeb 1.22.4 → 1.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/README.md +2 -2
  2. package/package.json +16 -11
  3. package/src/6502.js +39 -42
  4. package/src/6502.opcodes.js +198 -123
  5. package/src/6847.js +4 -5
  6. package/src/6847_fontdata.js +0 -2
  7. package/src/acia.js +2 -3
  8. package/src/app/app.js +0 -1
  9. package/src/app/args.js +0 -2
  10. package/src/app/electron.js +4 -6
  11. package/src/app-bench.js +1 -3
  12. package/src/archive.js +243 -0
  13. package/src/basic-loader.js +0 -7
  14. package/src/basic-tokenise.js +19 -6
  15. package/src/bbc-palette.js +0 -2
  16. package/src/bbcdiscs.js +0 -2
  17. package/src/bem-snapshot.js +1 -3
  18. package/src/binary.js +175 -0
  19. package/src/cmos.js +0 -2
  20. package/src/ddnoise.js +0 -1
  21. package/src/debounce.js +7 -0
  22. package/src/disc-surface.js +0 -2
  23. package/src/disc.js +10 -9
  24. package/src/fake6502.js +7 -23
  25. package/src/fdc.js +9 -51
  26. package/src/filestore.js +10 -17
  27. package/src/hex.js +38 -0
  28. package/src/index.js +11 -0
  29. package/src/intel-fdc.js +18 -20
  30. package/src/jsbeeb.css +33 -0
  31. package/src/{utils_atom.js → keymap-atom.js} +2 -18
  32. package/src/{utils.js → keymap.js} +47 -610
  33. package/src/loader.js +63 -0
  34. package/src/machine-session.js +37 -177
  35. package/src/machine-spec.js +59 -0
  36. package/src/main.js +69 -184
  37. package/src/media-resolver.js +68 -0
  38. package/src/mmc.js +3 -2
  39. package/src/models.js +40 -4
  40. package/src/music5000.js +0 -2
  41. package/src/ppia.js +3 -4
  42. package/src/printer.js +7 -14
  43. package/src/relaynoise.js +0 -1
  44. package/src/rewind.js +0 -2
  45. package/src/sample-player.js +2 -3
  46. package/src/serial.js +0 -2
  47. package/src/snapshot-helpers.js +0 -2
  48. package/src/snapshot.js +0 -2
  49. package/src/soundchip.js +1 -1
  50. package/src/state-utils.js +0 -2
  51. package/src/sth.js +2 -4
  52. package/src/tapes.js +10 -4
  53. package/src/teletext.js +2 -3
  54. package/src/teletext_adaptor.js +2 -4
  55. package/src/teletext_data.js +0 -2
  56. package/src/test-machine.js +307 -0
  57. package/src/touchscreen.js +2 -4
  58. package/src/tube.js +5 -6
  59. package/src/typist.js +103 -0
  60. package/src/uef-snapshot.js +0 -2
  61. package/src/url-params.js +4 -5
  62. package/src/vdu-capture.js +113 -0
  63. package/src/via.js +3 -4
  64. package/src/video-filters/pal-composite.js +0 -2
  65. package/src/video-filters/passthrough-filter.js +0 -2
  66. package/src/video-filters/pixel-grid.js +0 -2
  67. package/src/video-filters/shader-program.js +0 -2
  68. package/src/video-filters/xbr-filter.js +0 -2
  69. package/src/video.js +12 -11
  70. package/src/wd-fdc.js +20 -20
  71. package/src/web/analogue-inputs.js +6 -8
  72. package/src/web/analytics.js +13 -0
  73. package/src/web/audio-handler.js +2 -2
  74. package/src/{audio-utils.js → web/audio-utils.js} +0 -2
  75. package/src/web/autoboot.js +10 -13
  76. package/src/{canvas.js → web/canvas.js} +4 -5
  77. package/src/web/config.js +42 -72
  78. package/src/web/console-surface.js +25 -0
  79. package/src/web/debug.js +8 -49
  80. package/src/web/disc-visualiser.js +0 -2
  81. package/src/web/display.js +21 -4
  82. package/src/{dom-utils.js → web/dom-utils.js} +1 -11
  83. package/src/web/drives.js +1 -1
  84. package/src/web/emulation-loop.js +54 -33
  85. package/src/web/front-panel.js +13 -12
  86. package/src/{gamepads.js → web/gamepads.js} +2 -6
  87. package/src/web/google-drive-picker.js +14 -9
  88. package/src/{google-drive.js → web/google-drive.js} +9 -10
  89. package/src/web/hfe-picker.js +5 -4
  90. package/src/web/icons.js +42 -0
  91. package/src/web/keyboard-setup.js +29 -40
  92. package/src/{keyboard.js → web/keyboard.js} +18 -119
  93. package/src/web/layout.js +4 -0
  94. package/src/web/local-disc.js +44 -0
  95. package/src/web/machine.js +27 -20
  96. package/src/web/media-loader.js +38 -147
  97. package/src/{microphone-input.js → web/microphone-input.js} +1 -1
  98. package/src/web/modals.js +6 -9
  99. package/src/web/page-actions.js +88 -0
  100. package/src/web/quick-settings.js +16 -15
  101. package/src/web/reporting.js +0 -7
  102. package/src/{rewind-thumbnail.js → web/rewind-thumbnail.js} +0 -2
  103. package/src/web/rewind-ui.js +29 -29
  104. package/src/web/run-controls.js +39 -0
  105. package/src/web/settings.js +64 -135
  106. package/src/web/snapshot-ui.js +10 -16
  107. package/src/{speech-output.js → web/speech-output.js} +0 -2
  108. package/src/web/sth-picker.js +9 -7
  109. package/src/web/url-state.js +20 -6
  110. package/src/econet/L3FS.dat +0 -0
  111. package/src/econet/scsi.dat +0 -0
  112. package/tests/test-machine.js +0 -609
@@ -1,181 +1,4 @@
1
- "use strict";
2
- // Minimal ZIP extractor. Supports methods 0 (stored) and 8 (deflate); other
3
- // methods (bzip2, lzma, etc.) will throw an error with the method number.
4
-
5
- import { inflate as pakoInflate, inflateRaw as pakoInflateRaw, ungzip as pakoUngzip } from "pako";
6
-
7
- const ZipLocalHeaderSig = 0x04034b50;
8
- const ZipCentralDirSig = 0x02014b50;
9
- const ZipEocdSig = 0x06054b50;
10
- const ZipMethodStored = 0;
11
- const ZipMethodDeflate = 8;
12
-
13
- function readU16(buf, off) {
14
- return buf[off] | (buf[off + 1] << 8);
15
- }
16
-
17
- function readU32(buf, off) {
18
- return (buf[off] | (buf[off + 1] << 8) | (buf[off + 2] << 16) | (buf[off + 3] << 24)) >>> 0;
19
- }
20
-
21
- function isValidEocd(buf, off) {
22
- const commentLen = readU16(buf, off + 20);
23
- if (off + 22 + commentLen !== buf.length) return false;
24
- const cdOff = readU32(buf, off + 16);
25
- const cdSize = readU32(buf, off + 12);
26
- return cdOff <= off && cdSize <= off - cdOff;
27
- }
28
-
29
- // Find the End of Central Directory record by scanning backwards.
30
- // EOCD is in the last 65557 bytes (22-byte fixed record + up to 65535-byte comment).
31
- function findEocd(buf) {
32
- const searchStart = Math.max(0, buf.length - 65557);
33
- for (let i = buf.length - 22; i >= searchStart; i--) {
34
- if (readU32(buf, i) === ZipEocdSig && isValidEocd(buf, i)) return i;
35
- }
36
- throw new Error("Not a ZIP file: EOCD not found");
37
- }
38
-
39
- function inflateWith(inflater, data, context) {
40
- if (!(data instanceof Uint8Array)) data = new Uint8Array(data);
41
- try {
42
- return inflater(data);
43
- } catch (cause) {
44
- throw new Error(`${context}: ${cause.message || cause}`, { cause });
45
- }
46
- }
47
-
48
- export function inflate(data) {
49
- return inflateWith(pakoInflate, data, "Unable to inflate");
50
- }
51
-
52
- // Extract all files from a ZIP archive. Returns {filename: Uint8Array}.
53
- export async function unzip(buf) {
54
- if (!(buf instanceof Uint8Array)) buf = new Uint8Array(buf);
55
- const eocdOff = findEocd(buf);
56
- const cdOff = readU32(buf, eocdOff + 16);
57
- const cdCount = readU16(buf, eocdOff + 10);
58
- const decoder = new TextDecoder();
59
-
60
- const files = Object.create(null);
61
- let pos = cdOff;
62
- for (let i = 0; i < cdCount; i++) {
63
- if (pos + 46 > buf.length || readU32(buf, pos) !== ZipCentralDirSig)
64
- throw new Error("Bad central directory entry");
65
- const flags = readU16(buf, pos + 8);
66
- if (flags & 0x0001) throw new Error("Encrypted ZIP entries are not supported");
67
- const method = readU16(buf, pos + 10);
68
- const expectedCrc = readU32(buf, pos + 16);
69
- const compressedSize = readU32(buf, pos + 20);
70
- const nameLen = readU16(buf, pos + 28);
71
- const extraLen = readU16(buf, pos + 30);
72
- const commentLen = readU16(buf, pos + 32);
73
- const localHeaderOff = readU32(buf, pos + 42);
74
- const name = decoder.decode(buf.subarray(pos + 46, pos + 46 + nameLen));
75
- pos += 46 + nameLen + extraLen + commentLen;
76
-
77
- // Read local file header to find actual data offset.
78
- if (readU32(buf, localHeaderOff) !== ZipLocalHeaderSig) throw new Error(`Bad local file header for ${name}`);
79
- const localNameLen = readU16(buf, localHeaderOff + 26);
80
- const localExtraLen = readU16(buf, localHeaderOff + 28);
81
- const dataOff = localHeaderOff + 30 + localNameLen + localExtraLen;
82
- const dataEnd = dataOff + compressedSize;
83
- if (dataEnd > buf.length) throw new Error(`Truncated ZIP entry data for ${name}`);
84
- const raw = buf.subarray(dataOff, dataEnd);
85
-
86
- if (method === ZipMethodStored) {
87
- files[name] = raw.slice();
88
- } else if (method === ZipMethodDeflate) {
89
- files[name] = inflateWith(pakoInflateRaw, raw, `Unable to inflate ZIP entry ${name}`);
90
- } else {
91
- throw new Error(`Unsupported ZIP compression method ${method} for ${name}`);
92
- }
93
- if (crc32(files[name]) >>> 0 !== expectedCrc) throw new Error(`Corrupt ZIP entry ${name}: CRC32 mismatch`);
94
- }
95
- return files;
96
- }
97
-
98
- // Standard CRC-32/ISO-HDLC.
99
- const Crc32Table = new Uint32Array(256).map((_, index) => {
100
- let crc = index;
101
- for (let bit = 0; bit < 8; ++bit) crc = crc & 1 ? (crc >>> 1) ^ 0xedb88320 : crc >>> 1;
102
- return crc;
103
- });
104
-
105
- export function crc32(data) {
106
- let crc = 0xffffffff;
107
- for (let i = 0; i < data.length; ++i) crc = (crc >>> 8) ^ Crc32Table[(crc ^ data[i]) & 0xff];
108
- return ~crc;
109
- }
110
-
111
- // Create a ZIP blob from an array of {name: string, data: Uint8Array} entries.
112
- // Uses stored method (no compression) with CRC-32 for maximum compatibility.
113
- export function createZipBlob(files) {
114
- const encoder = new TextEncoder();
115
- const localHeaders = [];
116
- const centralHeaders = [];
117
- let offset = 0;
118
-
119
- for (const { name, data } of files) {
120
- const nameBytes = encoder.encode(name);
121
- const crc = crc32(data);
122
- // Local file header (30 + nameLen + data)
123
- const local = new Uint8Array(30 + nameBytes.length + data.length);
124
- const lv = new DataView(local.buffer);
125
- lv.setUint32(0, 0x04034b50, true); // signature
126
- lv.setUint16(4, 20, true); // version needed
127
- lv.setUint16(8, 0, true); // method: stored
128
- lv.setUint32(14, crc, true); // CRC-32
129
- lv.setUint32(18, data.length, true); // compressed size
130
- lv.setUint32(22, data.length, true); // uncompressed size
131
- lv.setUint16(26, nameBytes.length, true); // name length
132
- local.set(nameBytes, 30);
133
- local.set(data, 30 + nameBytes.length);
134
- localHeaders.push(local);
135
-
136
- // Central directory entry (46 + nameLen)
137
- const central = new Uint8Array(46 + nameBytes.length);
138
- const cv = new DataView(central.buffer);
139
- cv.setUint32(0, 0x02014b50, true); // signature
140
- cv.setUint16(4, 20, true); // version made by
141
- cv.setUint16(6, 20, true); // version needed
142
- cv.setUint16(10, 0, true); // method: stored
143
- cv.setUint32(16, crc, true); // CRC-32
144
- cv.setUint32(20, data.length, true); // compressed size
145
- cv.setUint32(24, data.length, true); // uncompressed size
146
- cv.setUint16(28, nameBytes.length, true); // name length
147
- cv.setUint32(42, offset, true); // local header offset
148
- central.set(nameBytes, 46);
149
- centralHeaders.push(central);
150
-
151
- offset += local.length;
152
- }
153
-
154
- const cdOffset = offset;
155
- let cdSize = 0;
156
- for (const c of centralHeaders) cdSize += c.length;
157
-
158
- // End of central directory (22 bytes)
159
- const eocd = new Uint8Array(22);
160
- const ev = new DataView(eocd.buffer);
161
- ev.setUint32(0, 0x06054b50, true); // signature
162
- ev.setUint16(8, files.length, true); // entries on this disc
163
- ev.setUint16(10, files.length, true); // total entries
164
- ev.setUint32(12, cdSize, true); // central directory size
165
- ev.setUint32(16, cdOffset, true); // central directory offset
166
-
167
- return new Blob([...localHeaders, ...centralHeaders, eocd], { type: "application/zip" });
168
- }
169
-
170
- export function debounce(fn, wait) {
171
- let timeout;
172
- return function (...args) {
173
- clearTimeout(timeout);
174
- timeout = setTimeout(() => fn.apply(this, args), wait);
175
- };
176
- }
177
-
178
- export const runningInNode = typeof window === "undefined";
1
+ import { runningInNode } from "./loader.js";
179
2
 
180
3
  export function isFirefox() {
181
4
  // With thanks to http://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
@@ -183,12 +6,6 @@ export function isFirefox() {
183
6
  return typeof InstallTrigger !== "undefined"; // Firefox 1.0+
184
7
  }
185
8
 
186
- export function parseAddr(s) {
187
- if (s[0] === "$" || s[0] === "&") return parseInt(s.substring(1), 16);
188
- if (s.indexOf("0x") === 0) return parseInt(s.substring(2), 16);
189
- return parseInt(s, 16);
190
- }
191
-
192
9
  export const userKeymap = [];
193
10
 
194
11
  export const BBC = {
@@ -440,6 +257,13 @@ export function stringToBBCKeys(str) {
440
257
  */
441
258
  export const keyCodes = {
442
259
  UNDEFINED: 0,
260
+ SEMICOLON: 186,
261
+ HASH: 222,
262
+ APOSTROPHE: 192,
263
+ MUTE: 173,
264
+ MINUS: 189,
265
+ EQUALS: 187,
266
+ BACK_QUOTE: 223,
443
267
  BACKSPACE: 8,
444
268
  TAB: 9,
445
269
  CLEAR: 12,
@@ -553,7 +377,7 @@ export const keyCodes = {
553
377
  CTRL_RIGHT: 261, // hack, jsbeeb only
554
378
  };
555
379
 
556
- function detectKeyboardLayout() {
380
+ export function detectKeyboardLayout() {
557
381
  if (runningInNode) {
558
382
  return "UK";
559
383
  }
@@ -567,41 +391,47 @@ function detectKeyboardLayout() {
567
391
  return "UK"; // Default guess of UK
568
392
  }
569
393
 
570
- const isUKlayout = detectKeyboardLayout() === "UK";
571
-
572
- if (isFirefox()) {
573
- keyCodes.SEMICOLON = 59;
574
- // #~ key (not on US keyboard)
575
- keyCodes.HASH = 163;
576
- keyCodes.APOSTROPHE = 222;
577
- keyCodes.BACK_QUOTE = 192;
578
- // Firefox doesn't return a keycode for this
579
- keyCodes.MUTE = -1;
580
- keyCodes.MINUS = 173;
581
- keyCodes.EQUALS = 61;
582
- } else {
583
- // Chrome
584
- // TODO: check other browsers
585
- // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.keyCode
586
- keyCodes.SEMICOLON = 186;
587
- // #~ key (not on US keyboard)
588
- keyCodes.HASH = isUKlayout ? 222 : 223;
589
- keyCodes.APOSTROPHE = isUKlayout ? 192 : 222;
590
- keyCodes.MUTE = 173;
591
- keyCodes.MINUS = 189;
592
- keyCodes.EQUALS = 187;
593
- keyCodes.BACK_QUOTE = isUKlayout ? 223 : 192;
594
- }
394
+ /**
395
+ * Adjusts keyCodes for the browser this runs in. The table starts as Chrome on a
396
+ * UK keyboard, which is also what node sees, so only the differences are applied.
397
+ */
398
+ export function adaptKeyCodesToBrowser() {
399
+ const isUKlayout = detectKeyboardLayout() === "UK";
400
+ if (isFirefox()) {
401
+ keyCodes.SEMICOLON = 59;
402
+ // #~ key (not on US keyboard)
403
+ keyCodes.HASH = 163;
404
+ keyCodes.APOSTROPHE = 222;
405
+ keyCodes.BACK_QUOTE = 192;
406
+ // Firefox doesn't return a keycode for this
407
+ keyCodes.MUTE = -1;
408
+ keyCodes.MINUS = 173;
409
+ keyCodes.EQUALS = 61;
410
+ } else {
411
+ // Chrome
412
+ // TODO(#1065) check other browsers
413
+ // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.keyCode
414
+ keyCodes.SEMICOLON = 186;
415
+ // #~ key (not on US keyboard)
416
+ keyCodes.HASH = isUKlayout ? 222 : 223;
417
+ keyCodes.APOSTROPHE = isUKlayout ? 192 : 222;
418
+ keyCodes.MUTE = 173;
419
+ keyCodes.MINUS = 189;
420
+ keyCodes.EQUALS = 187;
421
+ keyCodes.BACK_QUOTE = isUKlayout ? 223 : 192;
422
+ }
595
423
 
596
- // Swap APOSTROPHE and BACK_QUOTE keys around for Mac users. They are the opposite to what jsbeeb expects.
597
- // Swap them to what jsbeeb expects, and tidy up the hash key to prevent duplicate key mappings.
598
- if (!runningInNode && window.navigator.userAgent.indexOf("Mac") !== -1) {
599
- keyCodes.BACK_QUOTE = 192;
600
- keyCodes.APOSTROPHE = 222;
601
- keyCodes.HASH = 223;
424
+ // Swap APOSTROPHE and BACK_QUOTE keys around for Mac users. They are the opposite to what jsbeeb expects.
425
+ // Swap them to what jsbeeb expects, and tidy up the hash key to prevent duplicate key mappings.
426
+ if (!runningInNode && navigator.userAgent.indexOf("Mac") !== -1) {
427
+ keyCodes.BACK_QUOTE = 192;
428
+ keyCodes.APOSTROPHE = 222;
429
+ keyCodes.HASH = 223;
430
+ }
602
431
  }
603
432
 
604
433
  export function getKeyMap(keyLayout) {
434
+ const isUKlayout = detectKeyboardLayout() === "UK";
605
435
  const keys2 = [];
606
436
 
607
437
  // shift pressed
@@ -911,7 +741,7 @@ export function getKeyMap(keyLayout) {
911
741
  // no keycode for NUMPADENTER, small hack in main.js/keyCode()
912
742
  map(keyCodes.NUMPADENTER, BBC.NUMPADENTER);
913
743
 
914
- // TODO: "game" mapping
744
+ // TODO(#748) "game" mapping
915
745
  // eg Master Dunjunz needs # Del 3 , * Enter
916
746
  // https://web.archive.org/web/20080305042238/http://bbc.nvg.org/doc/games/Dunjunz-docs.txt
917
747
 
@@ -923,396 +753,3 @@ export function getKeyMap(keyLayout) {
923
753
 
924
754
  return keys2;
925
755
  }
926
-
927
- export function replaceOrAddExtension(name, newExt) {
928
- const lastDot = name.lastIndexOf(".");
929
- if (lastDot === -1) {
930
- return name + newExt;
931
- }
932
- return name.substring(0, lastDot) + newExt;
933
- }
934
-
935
- export function hexbyte(value) {
936
- return ((value >>> 4) & 0xf).toString(16) + (value & 0xf).toString(16);
937
- }
938
-
939
- export function hexword(value) {
940
- return hexbyte(value >>> 8) + hexbyte(value & 0xff);
941
- }
942
-
943
- export function hd(reader, start, end, opts) {
944
- opts = opts || {};
945
- const width = opts.width || 16;
946
- const gap = opts.gap === undefined ? 8 : opts.gap;
947
- const res = [];
948
- let str = "";
949
- let j = 0;
950
- for (let i = start; i < end; ++i) {
951
- str += " ";
952
- str += hexbyte(reader(i));
953
- if (++j === gap) str += " ";
954
- if (j === width) {
955
- res.push(str);
956
- str = "";
957
- j = 0;
958
- }
959
- }
960
- if (str) res.push(str);
961
- let joined = "";
962
- for (let i = 0; i < res.length; ++i) {
963
- joined += hexword(start + i * width) + " :" + res[i] + "\n";
964
- }
965
- return joined;
966
- }
967
-
968
- export function signExtend(val) {
969
- return val >= 128 ? val - 256 : val;
970
- }
971
-
972
- export function noop() {}
973
-
974
- export function bench() {
975
- for (let j = 0; j < 10; ++j) {
976
- let res = 0;
977
- const start = Date.now();
978
- for (let i = 0; i < 4096 * 1024; ++i) {
979
- res += signExtend(i & 0xff);
980
- }
981
- const tt = Date.now() - start;
982
- console.log(res, tt);
983
- }
984
- }
985
-
986
- export function noteEvent(category, type, label) {
987
- if (
988
- !runningInNode &&
989
- (window.location.host.endsWith(".godbolt.org") || window.location.host.endsWith(".xania.org"))
990
- ) {
991
- // Only note events on the public site
992
- /*global gtag*/
993
- gtag("event", category, { type, label });
994
- }
995
- console.log("event noted:", category, type, label);
996
- }
997
-
998
- let baseUrl = "";
999
-
1000
- export function setBaseUrl(url) {
1001
- baseUrl = url;
1002
- }
1003
-
1004
- export function uint8ArrayToString(array) {
1005
- let str = "";
1006
- for (let i = 0; i < array.length; ++i) str += String.fromCharCode(array[i]);
1007
- return str;
1008
- }
1009
-
1010
- export function stringToUint8Array(str) {
1011
- if (str instanceof Uint8Array) return str;
1012
- const len = str.length;
1013
- const array = new Uint8Array(len);
1014
- for (let i = 0; i < len; ++i) array[i] = str.charCodeAt(i) & 0xff;
1015
- return array;
1016
- }
1017
-
1018
- function loadDataHttp(url) {
1019
- return new Promise(function (resolve, reject) {
1020
- const request = new XMLHttpRequest();
1021
- request.open("GET", baseUrl + url, true);
1022
- request.overrideMimeType("text/plain; charset=x-user-defined");
1023
- request.onload = function () {
1024
- if (request.status !== 200) {
1025
- reject(new Error("Unable to load " + url + ", http code " + request.status));
1026
- return;
1027
- }
1028
- if (typeof request.response !== "string") {
1029
- resolve(request.response);
1030
- } else {
1031
- resolve(stringToUint8Array(request.response));
1032
- }
1033
- };
1034
- request.onerror = function () {
1035
- reject(new Error("A network error occurred loading " + url));
1036
- };
1037
- request.send(null);
1038
- });
1039
- }
1040
-
1041
- let _nodeBasePath = null;
1042
-
1043
- export function setNodeBasePath(basePath) {
1044
- _nodeBasePath = basePath;
1045
- }
1046
-
1047
- async function loadDataNode(url) {
1048
- if (typeof readbuffer !== "undefined") {
1049
- // d8 shell
1050
- /*global readbuffer*/
1051
- return new Uint8Array(readbuffer(url));
1052
- } else if (typeof read !== "undefined") {
1053
- // SpiderMonkey shell
1054
- /*global read*/
1055
- return read(url, "binary");
1056
- } else {
1057
- // Node
1058
- const fs = await import("fs");
1059
- const nodePath = await import("path");
1060
- if (_nodeBasePath) {
1061
- const publicPath = nodePath.join(_nodeBasePath, "public", url);
1062
- if (fs.existsSync(publicPath)) return fs.readFileSync(publicPath);
1063
- return fs.readFileSync(nodePath.join(_nodeBasePath, url));
1064
- }
1065
- if (url[0] === "/") url = "." + url;
1066
- if (fs.existsSync("public/" + url)) return fs.readFileSync("public/" + url);
1067
- return fs.readFileSync(url);
1068
- }
1069
- }
1070
-
1071
- export function loadData(url) {
1072
- if (runningInNode) {
1073
- return loadDataNode(url);
1074
- } else {
1075
- return loadDataHttp(url);
1076
- }
1077
- }
1078
-
1079
- export function readInt32(data, offset) {
1080
- return (data[offset + 3] << 24) | (data[offset + 2] << 16) | (data[offset + 1] << 8) | data[offset + 0];
1081
- }
1082
-
1083
- export function readInt16(data, offset) {
1084
- return (data[offset + 1] << 8) | data[offset + 0];
1085
- }
1086
-
1087
- const tempBuf = new ArrayBuffer(4);
1088
- const tempBuf8 = new Uint8Array(tempBuf);
1089
- const tempBufF32 = new Float32Array(tempBuf);
1090
-
1091
- export function readFloat32(data, offset) {
1092
- tempBuf8[0] = data[offset];
1093
- tempBuf8[1] = data[offset + 1];
1094
- tempBuf8[2] = data[offset + 2];
1095
- tempBuf8[3] = data[offset + 3];
1096
- return tempBufF32[0];
1097
- }
1098
-
1099
- export async function ungzip(data) {
1100
- return inflateWith(pakoUngzip, data, "Unable to ungzip");
1101
- }
1102
-
1103
- export class DataStream {
1104
- constructor(name, data) {
1105
- this.name = name;
1106
- this.pos = 0;
1107
- this.data = stringToUint8Array(data);
1108
- if (!this.data) {
1109
- throw new Error("No data in " + name);
1110
- }
1111
- this.end = this.data.length;
1112
- }
1113
-
1114
- static async create(name, data) {
1115
- const raw = stringToUint8Array(data);
1116
- if (raw && raw.length > 4 && raw[0] === 0x1f && raw[1] === 0x8b) {
1117
- console.log("Ungzipping " + name);
1118
- data = await ungzip(raw);
1119
- }
1120
- return new DataStream(name, data);
1121
- }
1122
-
1123
- bytesLeft() {
1124
- return this.end - this.pos;
1125
- }
1126
-
1127
- eof() {
1128
- return this.bytesLeft() === 0;
1129
- }
1130
-
1131
- advance(distance) {
1132
- if (this.bytesLeft() < distance) throw new RangeError("EOF in " + this.name);
1133
- this.pos += distance;
1134
- return this.pos - distance;
1135
- }
1136
-
1137
- readFloat32(pos) {
1138
- if (pos === undefined) pos = this.advance(4);
1139
- return readFloat32(this.data, pos);
1140
- }
1141
-
1142
- readInt32(pos) {
1143
- if (pos === undefined) pos = this.advance(4);
1144
- return readInt32(this.data, pos);
1145
- }
1146
-
1147
- readInt16(pos) {
1148
- if (pos === undefined) pos = this.advance(2);
1149
- return readInt16(this.data, pos);
1150
- }
1151
-
1152
- readByte(pos) {
1153
- if (pos === undefined) pos = this.advance(1);
1154
- return this.data[pos];
1155
- }
1156
-
1157
- readNulString(pos, maxLength) {
1158
- if (!maxLength) maxLength = 1024;
1159
- let posToUse = pos === undefined ? this.pos : pos;
1160
- let result = "";
1161
- let c;
1162
- while ((c = this.readByte(posToUse++)) !== 0 && --maxLength) {
1163
- result += String.fromCharCode(c);
1164
- }
1165
- if (maxLength === 0) return "";
1166
- if (pos === undefined) this.pos = posToUse;
1167
- return result;
1168
- }
1169
-
1170
- substream(posOrLength, length) {
1171
- let pos;
1172
- if (length === undefined) {
1173
- length = posOrLength;
1174
- pos = this.advance(length);
1175
- } else {
1176
- pos = posOrLength;
1177
- if (pos + length >= this.end) throw new RangeError("EOF in " + this.name);
1178
- }
1179
- return new DataStream(this.name + ".sub", this.data.subarray(pos, pos + length));
1180
- }
1181
-
1182
- seek(to) {
1183
- if (to >= this.end) throw new RangeError("Seek out of range in " + this.name);
1184
- this.pos = to;
1185
- }
1186
- }
1187
-
1188
- export function makeFast32(u32) {
1189
- // Firefox is ~5% faster with signed 32-bit arrays. Chrome is the same speed
1190
- // either way, so here we unconditionally wrap all u32 buffers as i32.
1191
- // Having a function do this makes it easy to test u32 vs i32, and means we
1192
- // keep the rest of the code using u32 (which makes more sense to me).
1193
- return new Int32Array(u32.buffer);
1194
- }
1195
-
1196
- const knownDiscExtensions = {
1197
- uef: true,
1198
- ssd: true,
1199
- dsd: true,
1200
- adf: true,
1201
- adm: true,
1202
- adl: true,
1203
- hfe: true,
1204
- };
1205
-
1206
- const knownRomExtensions = {
1207
- rom: true,
1208
- };
1209
-
1210
- async function unzipImage(data, knownExtensions) {
1211
- console.log("Attempting to unzip");
1212
-
1213
- let files;
1214
- try {
1215
- files = await unzip(data);
1216
- } catch (e) {
1217
- throw new Error("Error unzipping " + e.message, { cause: e });
1218
- }
1219
-
1220
- let uncompressed = null;
1221
- let loadedFile;
1222
- const ignored = [];
1223
-
1224
- for (const [filename, fileData] of Object.entries(files)) {
1225
- const match = filename.match(/.*\.([a-z]+)/i);
1226
- if (!match || !knownExtensions[match[1].toLowerCase()]) {
1227
- console.log("Skipping file", filename);
1228
- continue;
1229
- }
1230
- if (uncompressed) {
1231
- console.log("Ignoring", filename, "as already found a file");
1232
- ignored.push(filename);
1233
- continue;
1234
- }
1235
- loadedFile = filename;
1236
- uncompressed = fileData;
1237
- }
1238
-
1239
- if (!uncompressed) {
1240
- throw new Error("Couldn't find any compatible files in the archive");
1241
- }
1242
-
1243
- console.log("Unzipped '" + loadedFile + "'");
1244
- return { data: uncompressed, name: loadedFile, ignored };
1245
- }
1246
-
1247
- /**
1248
- * @param {Uint8Array|number[]} data a ZIP archive
1249
- * @returns {Promise<{data: Uint8Array, name: string, ignored: string[]}>} the one file loaded, and the
1250
- * names of any other loadable files the archive held
1251
- */
1252
- export async function unzipDiscImage(data) {
1253
- return unzipImage(data, knownDiscExtensions);
1254
- }
1255
-
1256
- export async function unzipRomImage(data) {
1257
- return unzipImage(data, knownRomExtensions);
1258
- }
1259
-
1260
- export function resizeUint8Array(array, byteSize) {
1261
- const newArray = new Uint8Array(byteSize);
1262
- newArray.set(array.subarray(0, byteSize));
1263
- return newArray;
1264
- }
1265
-
1266
- export class Fifo {
1267
- constructor(capacity) {
1268
- this._buffer = new Uint8Array(capacity);
1269
- this._size = 0;
1270
- this._wPtr = 0;
1271
- this._rPtr = 0;
1272
- }
1273
-
1274
- /** @returns {number} */
1275
- get size() {
1276
- return this._size;
1277
- }
1278
-
1279
- /** @returns {boolean} */
1280
- get full() {
1281
- return this._size === this._buffer.length;
1282
- }
1283
-
1284
- /** @returns {boolean} */
1285
- get empty() {
1286
- return this._size === 0;
1287
- }
1288
-
1289
- clear() {
1290
- this._size = 0;
1291
- this._wPtr = 0;
1292
- this._rPtr = 0;
1293
- }
1294
-
1295
- /** @type {Number} b */
1296
- put(b) {
1297
- if (this.full) return;
1298
- this._buffer[this._wPtr % this._buffer.length] = b;
1299
- this._wPtr++;
1300
- this._size++;
1301
- }
1302
-
1303
- /** @returns {Number} */
1304
- get() {
1305
- if (this.empty) return;
1306
- const res = this._buffer[this._rPtr % this._buffer.length];
1307
- this._rPtr++;
1308
- this._size--;
1309
- return res;
1310
- }
1311
-
1312
- /** @returns {number[]} pending bytes, oldest first */
1313
- toArray() {
1314
- const result = [];
1315
- for (let i = 0; i < this._size; ++i) result.push(this._buffer[(this._rPtr + i) % this._buffer.length]);
1316
- return result;
1317
- }
1318
- }