jsbeeb 1.12.0 → 1.13.1

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.
package/README.md CHANGED
@@ -4,8 +4,9 @@
4
4
 
5
5
  [![jsbeeb](public/images/jsbeeb-example.png)](https://bbc.xania.org/)
6
6
 
7
- A BBC Micro emulator written in JavaScript and running in modern browsers. Emulates a 32K BBC B (with sideways RAM)
8
- and a 128K BBC Master, along with a number of different peripherals.
7
+ A BBC Micro and other 8-bit Acorn emulator written in JavaScript and running in modern browsers. Emulates a 32K BBC B
8
+ (with sideways RAM), a 128K BBC Master, and an Acorn Atom (with AtoMMC2 SD card interface), along with a number of
9
+ different peripherals.
9
10
 
10
11
  ## Table of Contents
11
12
 
@@ -179,6 +180,14 @@ sudo rpm -i out/dist/jsbeeb-1.0.1.x86_64.rpm
179
180
  - (mostly internal use) `logFdcCommands`, `logFdcStateChanges` - turn on logging in the disc controller.
180
181
  - `audioDebug` - show audio queue stats chart.
181
182
 
183
+ ### Atom-specific parameters
184
+
185
+ - `model=Atom` - select the Acorn Atom (MMC) model. Other Atom variants: `Atom-Tape`, `Atom-Tape-FP`, `Atom-DOS`.
186
+ - `mmc=XXX` - load an MMC/SD card image (ZIP) for the Atom.
187
+
188
+ Atom models can also be selected automatically by hostname: any hostname starting with `atom` (e.g. `atom.xania.org`)
189
+ defaults to the Atom model.
190
+
182
191
  ## Patches
183
192
 
184
193
  Patches can be applied by making a `patch=P` URL parameter. `P` is a sequence of semicolon-separated patches of the form
@@ -258,6 +267,11 @@ Cheers to [Ed Spittles](https://github.com/BigEd) for testing various interrupt
258
267
 
259
268
  Thanks to Chris Jordan for his thorough testing, bug reports, ideas and help.
260
269
 
270
+ Huge thanks to [Andrew Hague](https://github.com/CommanderCoder) (CommanderCoder) for the Acorn Atom emulation
271
+ support. Andrew developed the original Atom implementation including the MC6847 video chip, 8255 PPIA, AtoMMC2 SD card
272
+ interface, Atom keyboard mapping, tape support, and speaker output. His work in [PR #505](https://github.com/mattgodbolt/jsbeeb/pull/505)
273
+ was incrementally merged and refined into the codebase.
274
+
261
275
  A lot of the early development used the amazing [Visual 6502](http://visual6502.org/) as reference for intra-instruction
262
276
  timings. Amazing stuff.
263
277
 
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "name": "jsbeeb",
8
8
  "description": "Emulate a BBC Micro",
9
9
  "repository": "git@github.com:mattgodbolt/jsbeeb.git",
10
- "version": "1.12.0",
10
+ "version": "1.13.1",
11
11
  "//": "If you change the version of Node, it must also be updated at the top of the Dockerfile.",
12
12
  "engines": {
13
13
  "node": ">=22.12.0"
@@ -36,21 +36,21 @@
36
36
  "devDependencies": {
37
37
  "@eslint/js": "^10.0.1",
38
38
  "@vitest/coverage-v8": "^4.0.18",
39
- "eslint": "^10.0.0",
39
+ "eslint": "^10.2.0",
40
40
  "eslint-config-prettier": "^10.1.8",
41
41
  "eslint-plugin-prettier": "^5.5.5",
42
- "globals": "^17.3.0",
42
+ "globals": "^17.5.0",
43
43
  "husky": "^9.1.7",
44
- "jsdom": "^29.0.0",
45
- "lint-staged": "^16.2.7",
44
+ "jsdom": "^29.0.2",
45
+ "lint-staged": "^16.4.0",
46
46
  "npm-run-all2": "^8.0.4",
47
47
  "pixelmatch": "^7.1.0",
48
- "prettier": "^3.8.1",
49
- "vite": "^8.0.3",
48
+ "prettier": "^3.8.2",
49
+ "vite": "^8.0.8",
50
50
  "vitest": "^4.0.10"
51
51
  },
52
52
  "optionalDependencies": {
53
- "electron": "^41.1.1",
53
+ "electron": "^41.2.0",
54
54
  "electron-builder": "^26.8.1",
55
55
  "electron-store": "^11.0.2"
56
56
  },
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/src/6502.js CHANGED
@@ -10,6 +10,8 @@ import { TouchScreen } from "./touchscreen.js";
10
10
  import { TeletextAdaptor } from "./teletext_adaptor.js";
11
11
  import { Filestore } from "./filestore.js";
12
12
  import { FakeRelayNoise } from "./relaynoise.js";
13
+ import { AtomPPIA } from "./ppia.js";
14
+ import { AtomMMC2 } from "./mmc.js";
13
15
 
14
16
  const signExtend = utils.signExtend;
15
17
 
@@ -1245,53 +1247,69 @@ export class Cpu6502 extends Base6502 {
1245
1247
 
1246
1248
  reset(hard) {
1247
1249
  if (hard) {
1248
- // On the Master, opcodes executing from 0xc000 - 0xdfff can optionally have their memory accesses
1249
- // redirected to shadow RAM.
1250
- this.memStatOffsetByIFetchBank = this.model.isMaster ? (1 << 0xc) | (1 << 0xd) : 0x0000;
1251
- if (!this.model.isTest) {
1252
- for (let i = 0; i < 128; ++i) this.memStat[i] = this.memStat[256 + i] = 1;
1253
- for (let i = 128; i < 256; ++i) this.memStat[i] = this.memStat[256 + i] = 2;
1254
- for (let i = 0; i < 128; ++i) this.memLook[i] = this.memLook[256 + i] = 0;
1255
- for (let i = 128; i < 192; ++i) this.memLook[i] = this.memLook[256 + i] = this.romOffset - 0x8000;
1256
- for (let i = 192; i < 256; ++i) this.memLook[i] = this.memLook[256 + i] = this.osOffset - 0xc000;
1257
-
1258
- for (let i = 0xfc; i < 0xff; ++i) this.memStat[i] = this.memStat[256 + i] = 0;
1259
- } else {
1260
- // Test sets everything as RAM.
1261
- for (let i = 0; i < 256; ++i) {
1262
- this.memStat[i] = this.memStat[256 + i] = 1;
1263
- this.memLook[i] = this.memLook[256 + i] = 0;
1264
- }
1265
- }
1266
- // DRAM content is not guaranteed to contain any particular
1267
- // value on start up, so we choose values that help avoid
1268
- // bugs in various games.
1269
- for (let i = 0; i < this.romOffset; ++i) {
1270
- if (i < 0x100) {
1271
- // For Clogger.
1272
- this.ramRomOs[i] = 0x00;
1273
- } else {
1274
- // For Eagle Empire.
1275
- this.ramRomOs[i] = 0xff;
1276
- }
1277
- }
1278
- this.videoDisplayPage = 0;
1279
- if (this.config.printerPort) this.uservia.ca2changecallback = this.config.printerPort.outputStrobe;
1280
-
1281
- this.sysvia.reset();
1282
- this.uservia.reset();
1283
- this.acia.reset();
1284
- this.serial.reset();
1285
- this.ddNoise.spinDown();
1286
- this.fdc.powerOnReset();
1287
- this.adconverter.reset();
1288
-
1289
- this.touchScreen = new TouchScreen(this.scheduler);
1290
- if (this.model.hasTeletextAdaptor) this.teletextAdaptor = new TeletextAdaptor(this);
1291
- if (this.econet) this.filestore = new Filestore(this, this.econet);
1250
+ this.setupMemoryMap();
1251
+ this.resetPeripherals(hard);
1292
1252
  } else {
1293
1253
  this.fdc.reset();
1294
1254
  }
1255
+ this.resetCpuState(hard);
1256
+ }
1257
+
1258
+ // Override in subclasses for different memory maps.
1259
+ setupMemoryMap() {
1260
+ // On the Master, opcodes executing from 0xc000 - 0xdfff can optionally have their memory accesses
1261
+ // redirected to shadow RAM.
1262
+ this.memStatOffsetByIFetchBank = this.model.isMaster ? (1 << 0xc) | (1 << 0xd) : 0x0000;
1263
+ if (!this.model.isTest) {
1264
+ for (let i = 0; i < 128; ++i) this.memStat[i] = this.memStat[256 + i] = 1;
1265
+ for (let i = 128; i < 256; ++i) this.memStat[i] = this.memStat[256 + i] = 2;
1266
+ for (let i = 0; i < 128; ++i) this.memLook[i] = this.memLook[256 + i] = 0;
1267
+ for (let i = 128; i < 192; ++i) this.memLook[i] = this.memLook[256 + i] = this.romOffset - 0x8000;
1268
+ for (let i = 192; i < 256; ++i) this.memLook[i] = this.memLook[256 + i] = this.osOffset - 0xc000;
1269
+
1270
+ for (let i = 0xfc; i < 0xff; ++i) this.memStat[i] = this.memStat[256 + i] = 0;
1271
+ } else {
1272
+ // Test sets everything as RAM.
1273
+ for (let i = 0; i < 256; ++i) {
1274
+ this.memStat[i] = this.memStat[256 + i] = 1;
1275
+ this.memLook[i] = this.memLook[256 + i] = 0;
1276
+ }
1277
+ }
1278
+ // DRAM content is not guaranteed to contain any particular
1279
+ // value on start up, so we choose values that help avoid
1280
+ // bugs in various games.
1281
+ for (let i = 0; i < this.romOffset; ++i) {
1282
+ if (i < 0x100) {
1283
+ // For Clogger.
1284
+ this.ramRomOs[i] = 0x00;
1285
+ } else {
1286
+ // For Eagle Empire.
1287
+ this.ramRomOs[i] = 0xff;
1288
+ }
1289
+ }
1290
+ this.videoDisplayPage = 0;
1291
+ }
1292
+
1293
+ // Override in subclasses for different peripheral sets.
1294
+ // Only called on hard reset.
1295
+ resetPeripherals() {
1296
+ if (this.config.printerPort) this.uservia.ca2changecallback = this.config.printerPort.outputStrobe;
1297
+
1298
+ this.sysvia.reset();
1299
+ this.uservia.reset();
1300
+ this.acia.reset();
1301
+ this.serial.reset();
1302
+ this.ddNoise.spinDown();
1303
+ this.fdc.powerOnReset();
1304
+ this.adconverter.reset();
1305
+
1306
+ this.touchScreen = new TouchScreen(this.scheduler);
1307
+ if (this.model.hasTeletextAdaptor) this.teletextAdaptor = new TeletextAdaptor(this);
1308
+ if (this.econet) this.filestore = new Filestore(this, this.econet);
1309
+ }
1310
+
1311
+ // Universal CPU state reset. Shared by all machine types.
1312
+ resetCpuState(hard) {
1295
1313
  this.tube.reset(hard);
1296
1314
  if (hard) {
1297
1315
  this.targetCycles = 0;
@@ -1495,3 +1513,285 @@ export class Cpu6502 extends Base6502 {
1495
1513
  this.debugger.setCpu(this);
1496
1514
  }
1497
1515
  }
1516
+
1517
+ // Acorn Atom memory map:
1518
+ // 0x0000-0x7FFF RAM (32KB, or 40KB with extensions up to 0x9FFF)
1519
+ // 0x0A00-0x0AFF FDC (8271) -- hole in RAM region
1520
+ // 0x8000-0x9FFF Video RAM (shared with MC6847 VDG)
1521
+ // 0xA000-0xAFFF Banked ROM/RAM (Branquart, 16 x 4KB banks via latch at 0xBFFF)
1522
+ // 0xB000-0xB003 PPIA (8255)
1523
+ // 0xB400-0xB40F AtomMMC
1524
+ // 0xB800-0xB80F VIA (6522)
1525
+ // 0xBFFF Bank select latch (write-only)
1526
+ // 0xC000-0xEFFF ROM (BASIC, FP, DOS in 4KB blocks)
1527
+ // 0xF000-0xFFFF OS kernel ROM (4KB)
1528
+ //
1529
+ // Branquart banking: bits 0-3 of the latch at 0xBFFF select which 4KB
1530
+ // bank is visible at 0xA000-0xAFFF. Banks can contain ROM or RAM.
1531
+ // The latch is write-only; bit 6 is a lock bit.
1532
+
1533
+ // Storage for Branquart banks starts after the BBC ROM area.
1534
+ const BranquartOffset = 128 * 1024 + 17 * 16 * 16384;
1535
+ const BranquartBankSize = 0x1000; // 4KB per bank
1536
+ const NumBranquartBanks = 16;
1537
+ const AtomRomBlockSize = 0x1000; // 4KB
1538
+
1539
+ export class AtomCpu6502 extends Cpu6502 {
1540
+ constructor(model, options) {
1541
+ super(model, options);
1542
+
1543
+ // Atom peripherals
1544
+ this.atomppia = new AtomPPIA(this, this.config.keyLayout, this.scheduler);
1545
+ this.atommc = new AtomMMC2(this);
1546
+
1547
+ // Branquart bank selection
1548
+ this.branquartLatch = 0;
1549
+ // Track which banks are ROM (false) vs RAM (true) for write protection.
1550
+ this.branquartRam = new Array(NumBranquartBanks).fill(true);
1551
+
1552
+ // Expand ramRomOs to include Branquart bank storage.
1553
+ const expandedSize = BranquartOffset + NumBranquartBanks * BranquartBankSize;
1554
+ if (this.ramRomOs.length < expandedSize) {
1555
+ const expanded = new Uint8Array(expandedSize);
1556
+ expanded.set(this.ramRomOs);
1557
+ this.ramRomOs = expanded;
1558
+ }
1559
+
1560
+ // Atom runs at 1 MHz
1561
+ this.peripheralCyclesPerSecond = 1 * 1000 * 1000;
1562
+ // reset() and debugger.setCpu() are called by initialise() after loadOs().
1563
+ }
1564
+
1565
+ // Select which Branquart bank is visible at 0xA000-0xAFFF by
1566
+ // updating the memLook table. This keeps reads on the fast path.
1567
+ selectBranquartBank(bank) {
1568
+ bank &= 0x0f;
1569
+ const offset = BranquartOffset + bank * BranquartBankSize - 0xa000;
1570
+ for (let i = 0xa0; i < 0xb0; ++i) {
1571
+ this.memLook[i] = this.memLook[256 + i] = offset;
1572
+ }
1573
+ // ROM banks are read-only, RAM banks are writable
1574
+ const stat = this.branquartRam[bank] ? 1 : 2;
1575
+ for (let i = 0xa0; i < 0xb0; ++i) {
1576
+ this.memStat[i] = this.memStat[256 + i] = stat;
1577
+ }
1578
+ }
1579
+
1580
+ setupMemoryMap() {
1581
+ this.memStatOffsetByIFetchBank = 0; // no shadow RAM on Atom
1582
+
1583
+ // 0x0000-0x9FFF: RAM (including video RAM at 0x8000)
1584
+ for (let i = 0; i < 0xa0; ++i) {
1585
+ this.memStat[i] = this.memStat[256 + i] = 1;
1586
+ this.memLook[i] = this.memLook[256 + i] = 0;
1587
+ }
1588
+
1589
+ // 0xA000-0xAFFF: Branquart banked region (set up via selectBranquartBank)
1590
+ this.branquartLatch = 0;
1591
+ this.selectBranquartBank(0);
1592
+
1593
+ // 0xB000-0xBFFF: Device I/O (PPIA, MMC, VIA, bank latch)
1594
+ for (let i = 0xb0; i < 0xc0; ++i) {
1595
+ this.memStat[i] = this.memStat[256 + i] = 0;
1596
+ }
1597
+
1598
+ // 0xC000-0xEFFF: ROM (4KB blocks loaded into romOffset area)
1599
+ for (let i = 0xc0; i < 0xf0; ++i) {
1600
+ this.memStat[i] = this.memStat[256 + i] = 2;
1601
+ this.memLook[i] = this.memLook[256 + i] = this.romOffset - 0xa000;
1602
+ }
1603
+
1604
+ // 0xF000-0xFFFF: OS kernel ROM
1605
+ for (let i = 0xf0; i < 0x100; ++i) {
1606
+ this.memStat[i] = this.memStat[256 + i] = 2;
1607
+ this.memLook[i] = this.memLook[256 + i] = this.osOffset - 0xf000;
1608
+ }
1609
+
1610
+ // FDC at 0x0A00 (device hole in RAM, if model uses FDC)
1611
+ if (this.model.Fdc) {
1612
+ this.memStat[0x0a] = this.memStat[256 + 0x0a] = 0;
1613
+ }
1614
+
1615
+ // Randomise video RAM and seed bytes
1616
+ for (let i = 8; i < 13; ++i) this.ramRomOs[i] = (256 * Math.random()) | 0;
1617
+ for (let i = 0x8000; i < 0x9000; ++i) this.ramRomOs[i] = (256 * Math.random()) | 0;
1618
+
1619
+ this.videoDisplayPage = 0;
1620
+ }
1621
+
1622
+ resetPeripherals() {
1623
+ super.resetPeripherals();
1624
+ this.atomppia.reset();
1625
+ this.atommc.reset(true);
1626
+ }
1627
+
1628
+ readDevice(addr) {
1629
+ addr &= 0xffff;
1630
+ switch (addr & ~0x0003) {
1631
+ case 0x0a00:
1632
+ case 0x0a04:
1633
+ return this.fdc.read(addr);
1634
+ case 0xb000:
1635
+ case 0xb004:
1636
+ return this.atomppia.read(addr);
1637
+ case 0xb008:
1638
+ case 0xb00c:
1639
+ return 0x00;
1640
+ case 0xb400:
1641
+ case 0xb404:
1642
+ case 0xb408:
1643
+ case 0xb40c:
1644
+ return this.atommc.read(addr);
1645
+ case 0xb800:
1646
+ case 0xb804:
1647
+ case 0xb808:
1648
+ case 0xb80c:
1649
+ return this.uservia.read(addr);
1650
+ }
1651
+ return addr >>> 8; // open bus
1652
+ }
1653
+
1654
+ writeDevice(addr, b) {
1655
+ addr &= 0xffff;
1656
+ b |= 0;
1657
+ switch (addr & ~0x0003) {
1658
+ case 0x0a00:
1659
+ case 0x0a04:
1660
+ return this.fdc.write(addr, b);
1661
+ case 0xb000:
1662
+ case 0xb004:
1663
+ case 0xb008:
1664
+ case 0xb00c:
1665
+ return this.atomppia.write(addr, b);
1666
+ case 0xb400:
1667
+ case 0xb404:
1668
+ case 0xb408:
1669
+ case 0xb40c:
1670
+ return this.atommc.write(addr, b);
1671
+ case 0xb800:
1672
+ case 0xb804:
1673
+ case 0xb808:
1674
+ case 0xb80c:
1675
+ return this.uservia.write(addr, b);
1676
+ case 0xbffc:
1677
+ if (addr === 0xbfff) {
1678
+ this.branquartLatch = b & 0xff;
1679
+ this.selectBranquartBank(b & 0x0f);
1680
+ }
1681
+ return;
1682
+ }
1683
+ }
1684
+
1685
+ writemem(addr, b) {
1686
+ addr &= 0xffff;
1687
+ b |= 0;
1688
+ if (this._debugWrite) this._debugWrite(addr, b);
1689
+ const statOffset = this.memStatOffset + (addr >>> 8);
1690
+ const stat = this.memStat[statOffset];
1691
+ if (stat === 1) {
1692
+ // Notify VDG of CPU address bus activity for snow effect
1693
+ if (this.video.video6847) {
1694
+ this.video.video6847.cpuAddrAccess(addr);
1695
+ }
1696
+ const offset = this.memLook[statOffset];
1697
+ this.ramRomOs[offset + addr] = b;
1698
+ return;
1699
+ }
1700
+ if (stat === 2) return; // ROM: write ignored
1701
+ // Device page
1702
+ this.writeDevice(addr, b);
1703
+ }
1704
+
1705
+ // Atom ROMs are 4KB, not 16KB like BBC.
1706
+ // OS layout: osOffset holds 4KB kernel at 0xF000.
1707
+ // Extra ROMs (BASIC, FP, DOS) are 4KB blocks at romOffset+0..romOffset+0x5000
1708
+ // mapped at 0xC000, 0xD000, 0xE000 etc.
1709
+ async loadOs(os) {
1710
+ const extraRoms = Array.prototype.slice.call(arguments, 1).concat(this.config.extraRoms);
1711
+ const bankRoms = this.model.banks || [];
1712
+ os = "roms/" + os;
1713
+ console.log(`Loading Atom OS from ${os}`);
1714
+ const data = await utils.loadData(os);
1715
+ const len = data.length;
1716
+
1717
+ if (len < AtomRomBlockSize || len % AtomRomBlockSize !== 0) {
1718
+ throw new Error(`Broken Atom ROM file (length=${len})`);
1719
+ }
1720
+
1721
+ // Clear the OS area and load the kernel at osOffset (mapped to 0xF000)
1722
+ this.ramRomOs.fill(0x00, this.osOffset, this.osOffset + 0x4000);
1723
+ this.ramRomOs.set(data.subarray(0, AtomRomBlockSize), this.osOffset);
1724
+
1725
+ // Load extra ROMs (BASIC, FP, DOS) into 4KB blocks.
1726
+ // romIndex counts down from 5: slot 4 maps to 0xE000 (romOffset+0x4000),
1727
+ // slot 3 to 0xD000 (romOffset+0x3000), slot 2 to 0xC000 (romOffset+0x2000).
1728
+ let romIndex = 5;
1729
+ const awaiting = [];
1730
+ for (const rom of extraRoms) {
1731
+ romIndex--;
1732
+ if (romIndex < 2)
1733
+ throw new Error("Too many extra ROMs for Atom (max 3 addressable slots at 0xC000-0xEFFF)");
1734
+ if (rom !== "") {
1735
+ awaiting.push(this.loadRom(rom, this.romOffset + romIndex * AtomRomBlockSize));
1736
+ }
1737
+ }
1738
+
1739
+ // Load Branquart bank ROMs (max 16 banks)
1740
+ const numBanks = Math.min(bankRoms.length, NumBranquartBanks);
1741
+ for (let bankIndex = 0; bankIndex < numBanks; bankIndex++) {
1742
+ if (bankRoms[bankIndex] !== "") {
1743
+ awaiting.push(this.loadRom(bankRoms[bankIndex], BranquartOffset + bankIndex * BranquartBankSize));
1744
+ this.branquartRam[bankIndex] = false; // mark as ROM
1745
+ }
1746
+ }
1747
+
1748
+ return await Promise.all(awaiting);
1749
+ }
1750
+
1751
+ // Override loadRom to accept 4KB ROMs
1752
+ async loadRom(name, offset) {
1753
+ const data = await utils.loadData("roms/" + name);
1754
+ const len = data.length;
1755
+ if (len !== 16384 && len !== 8192 && len !== 4096) {
1756
+ throw new Error(`Broken ROM file ${name} (length=${len})`);
1757
+ }
1758
+ this.ramRomOs.set(data, offset);
1759
+ }
1760
+
1761
+ // Atom key layout goes through PPIA, not SysVia
1762
+ updateKeyLayout() {
1763
+ this.atomppia.setKeyLayout(this.config.keyLayout);
1764
+ }
1765
+
1766
+ snapshotState(options) {
1767
+ const state = super.snapshotState(options);
1768
+ // Atom-specific state
1769
+ state.branquartLatch = this.branquartLatch;
1770
+ state.branquartRam = [...this.branquartRam];
1771
+ // Save Branquart bank contents
1772
+ state.branquartBanks = this.ramRomOs.slice(
1773
+ BranquartOffset,
1774
+ BranquartOffset + NumBranquartBanks * BranquartBankSize,
1775
+ );
1776
+ state.atomppia = this.atomppia.snapshotState();
1777
+ return state;
1778
+ }
1779
+
1780
+ // No-op: the Atom doesn't have BBC sideways ROM banking.
1781
+ // This prevents super.restoreState() from corrupting the Atom memory map.
1782
+ romSelect() {}
1783
+
1784
+ restoreState(state) {
1785
+ super.restoreState(state);
1786
+
1787
+ // Restore Atom-specific state after the parent has handled
1788
+ // CPU registers, memory, cycle tracking, and sub-components.
1789
+ if (state.branquartRam) this.branquartRam = [...state.branquartRam];
1790
+ if (state.branquartBanks) {
1791
+ this.ramRomOs.set(state.branquartBanks, BranquartOffset);
1792
+ }
1793
+ this.branquartLatch = state.branquartLatch || 0;
1794
+ this.selectBranquartBank(this.branquartLatch & 0x0f);
1795
+ if (state.atomppia) this.atomppia.restoreState(state.atomppia);
1796
+ }
1797
+ }