rk86 2.0.12 → 2.0.15

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 (2) hide show
  1. package/package.json +1 -1
  2. package/rk86.js +475 -16
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rk86",
3
- "version": "2.0.12",
3
+ "version": "2.0.15",
4
4
  "description": "Эмулятор Радио-86РК (Intel 8080) для терминала",
5
5
  "bin": {
6
6
  "rk86": "rk86.js"
package/rk86.js CHANGED
@@ -1588,9 +1588,420 @@ var init_catalog_data = __esm(() => {
1588
1588
  ];
1589
1589
  });
1590
1590
 
1591
+ // node_modules/asm8080/dist/asm8.js
1592
+ var REG8 = {
1593
+ B: 0,
1594
+ C: 1,
1595
+ D: 2,
1596
+ E: 3,
1597
+ H: 4,
1598
+ L: 5,
1599
+ M: 6,
1600
+ A: 7
1601
+ };
1602
+ var REG_PAIR = {
1603
+ B: 0,
1604
+ D: 1,
1605
+ H: 2,
1606
+ SP: 3
1607
+ };
1608
+ var REG_PAIR_PUSH = {
1609
+ B: 0,
1610
+ D: 1,
1611
+ H: 2,
1612
+ PSW: 3
1613
+ };
1614
+ var IMPLIED = {
1615
+ NOP: 0,
1616
+ HLT: 118,
1617
+ RET: 201,
1618
+ XCHG: 235,
1619
+ EI: 251,
1620
+ DI: 243,
1621
+ CMA: 47,
1622
+ STC: 55,
1623
+ CMC: 63,
1624
+ DAA: 39,
1625
+ RLC: 7,
1626
+ RRC: 15,
1627
+ RAL: 23,
1628
+ RAR: 31,
1629
+ PCHL: 233,
1630
+ SPHL: 249,
1631
+ XTHL: 227,
1632
+ RNZ: 192,
1633
+ RZ: 200,
1634
+ RNC: 208,
1635
+ RC: 216,
1636
+ RPO: 224,
1637
+ RPE: 232,
1638
+ RP: 240,
1639
+ RM: 248
1640
+ };
1641
+ var ALU_REG = {
1642
+ ADD: 128,
1643
+ ADC: 136,
1644
+ SUB: 144,
1645
+ SBB: 152,
1646
+ ANA: 160,
1647
+ XRA: 168,
1648
+ ORA: 176,
1649
+ CMP: 184
1650
+ };
1651
+ var ALU_IMM = {
1652
+ ADI: 198,
1653
+ ACI: 206,
1654
+ SUI: 214,
1655
+ SBI: 222,
1656
+ ANI: 230,
1657
+ XRI: 238,
1658
+ ORI: 246,
1659
+ CPI: 254
1660
+ };
1661
+ var ADDR16 = {
1662
+ JMP: 195,
1663
+ JNZ: 194,
1664
+ JZ: 202,
1665
+ JNC: 210,
1666
+ JC: 218,
1667
+ JPO: 226,
1668
+ JPE: 234,
1669
+ JP: 242,
1670
+ JM: 250,
1671
+ CALL: 205,
1672
+ CNZ: 196,
1673
+ CZ: 204,
1674
+ CNC: 212,
1675
+ CC: 220,
1676
+ CPO: 228,
1677
+ CPE: 236,
1678
+ CP: 244,
1679
+ CM: 252,
1680
+ LDA: 58,
1681
+ STA: 50,
1682
+ LHLD: 42,
1683
+ SHLD: 34
1684
+ };
1685
+ function instrSize(m) {
1686
+ if (m in IMPLIED)
1687
+ return 1;
1688
+ if (m in ALU_REG)
1689
+ return 1;
1690
+ if (m === "MOV" || m === "INR" || m === "DCR")
1691
+ return 1;
1692
+ if (m === "PUSH" || m === "POP")
1693
+ return 1;
1694
+ if (m === "DAD" || m === "INX" || m === "DCX")
1695
+ return 1;
1696
+ if (m === "LDAX" || m === "STAX")
1697
+ return 1;
1698
+ if (m === "RST")
1699
+ return 1;
1700
+ if (m === "MVI")
1701
+ return 2;
1702
+ if (m in ALU_IMM)
1703
+ return 2;
1704
+ if (m === "IN" || m === "OUT")
1705
+ return 2;
1706
+ if (m === "LXI")
1707
+ return 3;
1708
+ if (m in ADDR16)
1709
+ return 3;
1710
+ throw new Error(`unknown mnemonic: ${m}`);
1711
+ }
1712
+ function stripComment(line) {
1713
+ let inQ = false;
1714
+ let qc = "";
1715
+ for (let i = 0;i < line.length; i++) {
1716
+ const c = line[i];
1717
+ if (inQ) {
1718
+ if (c === qc)
1719
+ inQ = false;
1720
+ } else if (c === '"' || c === "'") {
1721
+ inQ = true;
1722
+ qc = c;
1723
+ } else if (c === ";")
1724
+ return line.slice(0, i);
1725
+ }
1726
+ return line;
1727
+ }
1728
+ function splitOperands(s) {
1729
+ const r = [];
1730
+ let current = "";
1731
+ let inQ = false;
1732
+ let qc = "";
1733
+ for (const c of s) {
1734
+ if (inQ) {
1735
+ current += c;
1736
+ if (c === qc)
1737
+ inQ = false;
1738
+ } else if (c === '"' || c === "'") {
1739
+ inQ = true;
1740
+ qc = c;
1741
+ current += c;
1742
+ } else if (c === ",") {
1743
+ r.push(current.trim());
1744
+ current = "";
1745
+ } else
1746
+ current += c;
1747
+ }
1748
+ if (current.trim())
1749
+ r.push(current.trim());
1750
+ return r;
1751
+ }
1752
+ function parseLine(line) {
1753
+ let s = stripComment(line).trim();
1754
+ if (!s)
1755
+ return { operands: [] };
1756
+ let label;
1757
+ const ci = s.indexOf(":");
1758
+ if (ci > 0 && /^[A-Za-z_]\w*$/.test(s.slice(0, ci).trim())) {
1759
+ label = s.slice(0, ci).trim();
1760
+ s = s.slice(ci + 1).trim();
1761
+ }
1762
+ if (!s)
1763
+ return { label, operands: [] };
1764
+ const si = s.search(/\s/);
1765
+ const first = si < 0 ? s : s.slice(0, si);
1766
+ const rest = si < 0 ? "" : s.slice(si).trim();
1767
+ if (!label && rest) {
1768
+ const parts = rest.split(/\s+/);
1769
+ if (parts[0].toUpperCase() === "EQU") {
1770
+ return {
1771
+ label: first,
1772
+ mnemonic: "EQU",
1773
+ operands: [parts.slice(1).join(" ")],
1774
+ isEqu: true
1775
+ };
1776
+ }
1777
+ }
1778
+ return { label, mnemonic: first, operands: rest ? splitOperands(rest) : [] };
1779
+ }
1780
+ function evalAtom(s, symbols) {
1781
+ s = s.trim();
1782
+ if (s.length === 3 && s[0] === "'" && s[2] === "'")
1783
+ return s.charCodeAt(1);
1784
+ if (/^[0-9][0-9A-Fa-f]*[hH]$/.test(s))
1785
+ return parseInt(s.slice(0, -1), 16);
1786
+ if (/^[0-9]+$/.test(s))
1787
+ return parseInt(s, 10);
1788
+ const k = s.toUpperCase();
1789
+ if (symbols.has(k))
1790
+ return symbols.get(k);
1791
+ throw new Error(`unknown symbol: ${s}`);
1792
+ }
1793
+ function evalExpr(expr, symbols) {
1794
+ expr = expr.trim();
1795
+ const tokens = [];
1796
+ const ops = ["+"];
1797
+ let current = "";
1798
+ for (const c of expr) {
1799
+ if ((c === "+" || c === "-") && current.trim()) {
1800
+ tokens.push(current.trim());
1801
+ ops.push(c);
1802
+ current = "";
1803
+ } else {
1804
+ current += c;
1805
+ }
1806
+ }
1807
+ if (current.trim())
1808
+ tokens.push(current.trim());
1809
+ let r = 0;
1810
+ for (let i = 0;i < tokens.length; i++) {
1811
+ const v = evalAtom(tokens[i], symbols);
1812
+ r = ops[i] === "+" ? r + v : r - v;
1813
+ }
1814
+ return r & 65535;
1815
+ }
1816
+ function encode(m, ops, symbols) {
1817
+ if (m in IMPLIED)
1818
+ return [IMPLIED[m]];
1819
+ if (m in ALU_REG)
1820
+ return [ALU_REG[m] | REG8[ops[0].toUpperCase()]];
1821
+ if (m in ALU_IMM)
1822
+ return [ALU_IMM[m], evalExpr(ops[0], symbols) & 255];
1823
+ if (m in ADDR16) {
1824
+ const v = evalExpr(ops[0], symbols);
1825
+ return [ADDR16[m], v & 255, v >> 8 & 255];
1826
+ }
1827
+ if (m === "MOV")
1828
+ return [64 | REG8[ops[0].toUpperCase()] << 3 | REG8[ops[1].toUpperCase()]];
1829
+ if (m === "MVI") {
1830
+ const v = evalExpr(ops[1], symbols);
1831
+ return [6 | REG8[ops[0].toUpperCase()] << 3, v & 255];
1832
+ }
1833
+ if (m === "INR")
1834
+ return [4 | REG8[ops[0].toUpperCase()] << 3];
1835
+ if (m === "DCR")
1836
+ return [5 | REG8[ops[0].toUpperCase()] << 3];
1837
+ if (m === "LXI") {
1838
+ const v = evalExpr(ops[1], symbols);
1839
+ return [1 | REG_PAIR[ops[0].toUpperCase()] << 4, v & 255, v >> 8 & 255];
1840
+ }
1841
+ if (m === "DAD")
1842
+ return [9 | REG_PAIR[ops[0].toUpperCase()] << 4];
1843
+ if (m === "INX")
1844
+ return [3 | REG_PAIR[ops[0].toUpperCase()] << 4];
1845
+ if (m === "DCX")
1846
+ return [11 | REG_PAIR[ops[0].toUpperCase()] << 4];
1847
+ if (m === "PUSH")
1848
+ return [197 | REG_PAIR_PUSH[ops[0].toUpperCase()] << 4];
1849
+ if (m === "POP")
1850
+ return [193 | REG_PAIR_PUSH[ops[0].toUpperCase()] << 4];
1851
+ if (m === "LDAX")
1852
+ return [10 | REG_PAIR[ops[0].toUpperCase()] << 4];
1853
+ if (m === "STAX")
1854
+ return [2 | REG_PAIR[ops[0].toUpperCase()] << 4];
1855
+ if (m === "IN")
1856
+ return [219, evalExpr(ops[0], symbols) & 255];
1857
+ if (m === "OUT")
1858
+ return [211, evalExpr(ops[0], symbols) & 255];
1859
+ if (m === "RST") {
1860
+ const n = evalExpr(ops[0], symbols);
1861
+ return [199 | n << 3];
1862
+ }
1863
+ throw new Error(`cannot encode: ${m} ${ops.join(", ")}`);
1864
+ }
1865
+ function dbBytes(operands, symbols) {
1866
+ const out = [];
1867
+ for (const op of operands) {
1868
+ if (op.startsWith('"') && op.endsWith('"') || op.startsWith("'") && op.endsWith("'")) {
1869
+ for (const ch of op.slice(1, -1))
1870
+ out.push(ch.charCodeAt(0));
1871
+ } else {
1872
+ out.push(evalExpr(op, symbols) & 255);
1873
+ }
1874
+ }
1875
+ return out;
1876
+ }
1877
+ function dwBytes(operands, symbols) {
1878
+ const out = [];
1879
+ for (const op of operands) {
1880
+ const v = evalExpr(op, symbols) & 65535;
1881
+ out.push(v & 255, v >> 8 & 255);
1882
+ }
1883
+ return out;
1884
+ }
1885
+ function countDb(operands) {
1886
+ let n = 0;
1887
+ for (const op of operands) {
1888
+ if (op.startsWith('"') && op.endsWith('"') || op.startsWith("'") && op.endsWith("'"))
1889
+ n += op.length - 2;
1890
+ else
1891
+ n++;
1892
+ }
1893
+ return n;
1894
+ }
1895
+ function asm(source) {
1896
+ const lines = source.split(`
1897
+ `);
1898
+ const symbols = new Map;
1899
+ let pc = 0;
1900
+ for (const line of lines) {
1901
+ const parts = parseLine(line);
1902
+ if (parts.label) {
1903
+ if (parts.isEqu) {
1904
+ symbols.set(parts.label.toUpperCase(), evalExpr(parts.operands[0], symbols));
1905
+ continue;
1906
+ }
1907
+ symbols.set(parts.label.toUpperCase(), pc);
1908
+ }
1909
+ if (!parts.mnemonic)
1910
+ continue;
1911
+ const m = parts.mnemonic.toUpperCase();
1912
+ if (m === "EQU")
1913
+ continue;
1914
+ if (m === "ORG") {
1915
+ pc = evalExpr(parts.operands[0], symbols);
1916
+ continue;
1917
+ }
1918
+ if (m === "SECTION")
1919
+ continue;
1920
+ if (m === "END")
1921
+ break;
1922
+ if (m === "DB") {
1923
+ pc += countDb(parts.operands);
1924
+ continue;
1925
+ }
1926
+ if (m === "DW") {
1927
+ pc += parts.operands.length * 2;
1928
+ continue;
1929
+ }
1930
+ pc += instrSize(m);
1931
+ }
1932
+ const sections = [];
1933
+ let current = null;
1934
+ const sectionNames = new Set;
1935
+ for (const line of lines) {
1936
+ const parts = parseLine(line);
1937
+ if (parts.isEqu || !parts.mnemonic)
1938
+ continue;
1939
+ const m = parts.mnemonic.toUpperCase();
1940
+ if (m === "EQU")
1941
+ continue;
1942
+ if (m === "ORG") {
1943
+ if (current && current.data.length) {
1944
+ current.end = current.start + current.data.length - 1;
1945
+ sections.push(current);
1946
+ }
1947
+ const addr = evalExpr(parts.operands[0], symbols);
1948
+ current = { start: addr, end: addr, data: [] };
1949
+ continue;
1950
+ }
1951
+ if (m === "SECTION") {
1952
+ if (!current)
1953
+ throw new Error("SECTION before ORG");
1954
+ const name = parts.operands[0];
1955
+ if (!name)
1956
+ throw new Error("SECTION requires a name");
1957
+ if (sectionNames.has(name.toUpperCase()))
1958
+ throw new Error(`duplicate section name: ${name}`);
1959
+ sectionNames.add(name.toUpperCase());
1960
+ current.name = name;
1961
+ continue;
1962
+ }
1963
+ if (m === "END")
1964
+ break;
1965
+ if (!current)
1966
+ throw new Error("code before ORG");
1967
+ const bytes = m === "DB" ? dbBytes(parts.operands, symbols) : m === "DW" ? dwBytes(parts.operands, symbols) : encode(m, parts.operands, symbols);
1968
+ current.data.push(...bytes);
1969
+ }
1970
+ if (current && current.data.length) {
1971
+ current.end = current.start + current.data.length - 1;
1972
+ sections.push(current);
1973
+ }
1974
+ return sections;
1975
+ }
1976
+ if (false) {}
1977
+
1591
1978
  // src/lib/terminal/rk86_terminal.ts
1592
1979
  import { existsSync } from "fs";
1593
1980
  import { readFile } from "fs/promises";
1981
+ // packages/rk86/package.json
1982
+ var package_default = {
1983
+ name: "rk86",
1984
+ version: "2.0.14",
1985
+ description: "\u042D\u043C\u0443\u043B\u044F\u0442\u043E\u0440 \u0420\u0430\u0434\u0438\u043E-86\u0420\u041A (Intel 8080) \u0434\u043B\u044F \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0430",
1986
+ bin: {
1987
+ rk86: "rk86.js"
1988
+ },
1989
+ type: "module",
1990
+ keywords: [
1991
+ "rk86",
1992
+ "radio-86rk",
1993
+ "emulator",
1994
+ "intel-8080",
1995
+ "i8080",
1996
+ "retro"
1997
+ ],
1998
+ author: "Alexander Demin",
1999
+ license: "MIT",
2000
+ repository: {
2001
+ type: "git",
2002
+ url: "https://github.com/begoon/rk86-js-web"
2003
+ }
2004
+ };
1594
2005
 
1595
2006
  // src/lib/core/hex.ts
1596
2007
  function hex(v, prefix) {
@@ -3007,7 +3418,7 @@ class Runner {
3007
3418
  }
3008
3419
  }
3009
3420
  execute(options = {}) {
3010
- const { terminate_address, on_terminate, exit_on_halt } = options;
3421
+ const { terminate_address, on_terminate, exit_on_halt, armed } = options;
3011
3422
  clearTimeout(this.execute_timer);
3012
3423
  if (!this.paused) {
3013
3424
  let batch_ticks = 0;
@@ -3035,6 +3446,8 @@ class Runner {
3035
3446
  this.machine.ui.on_visualizer_hit(this.machine.memory.read_raw(this.machine.cpu.pc));
3036
3447
  }
3037
3448
  batch_instructions += 1;
3449
+ if (armed?.value === false)
3450
+ continue;
3038
3451
  if (terminate_address !== undefined && this.machine.cpu.pc === terminate_address) {
3039
3452
  on_terminate?.();
3040
3453
  return;
@@ -3624,10 +4037,12 @@ function printHelp() {
3624
4037
  \u0418\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D\u0438\u0435: bunx rk86 [\u043E\u043F\u0446\u0438\u0438] [\u0444\u0430\u0439\u043B]
3625
4038
 
3626
4039
  \u041E\u043F\u0446\u0438\u0438:
4040
+ -v \u0432\u0435\u0440\u0441\u0438\u044F
3627
4041
  -h \u0441\u043F\u0440\u0430\u0432\u043A\u0430
3628
4042
  -l \u0441\u043F\u0438\u0441\u043E\u043A \u0444\u0430\u0439\u043B\u043E\u0432 \u0438\u0437 \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0430
3629
4043
  -m <\u0444\u0430\u0439\u043B> \u043C\u043E\u043D\u0438\u0442\u043E\u0440 (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E: \u0432\u0441\u0442\u0440\u043E\u0435\u043D\u043D\u044B\u0439 mon32.bin)
3630
4044
  -p \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0444\u0430\u0439\u043B \u0431\u0435\u0437 \u0437\u0430\u043F\u0443\u0441\u043A\u0430
4045
+ -g <\u0430\u0434\u0440\u0435\u0441> \u0430\u0434\u0440\u0435\u0441 \u0437\u0430\u043F\u0443\u0441\u043A\u0430 (\u043D\u0435\u0441\u043E\u0432\u043C\u0435\u0441\u0442\u0438\u043C \u0441 -p)
3631
4046
  --exit-halt \u0432\u044B\u0445\u043E\u0434 \u043F\u0440\u0438 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0438 HLT
3632
4047
  --exit-address [\u0430\u0434\u0440\u0435\u0441] \u0432\u044B\u0445\u043E\u0434 \u043F\u0440\u0438 \u043F\u0435\u0440\u0435\u0445\u043E\u0434\u0435 \u043D\u0430 \u0430\u0434\u0440\u0435\u0441 (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E: 0xFFFE)
3633
4048
 
@@ -3639,6 +4054,8 @@ function printHelp() {
3639
4054
  bunx rk86 --exit-halt prog.bin \u0432\u044B\u0445\u043E\u0434 \u043F\u0440\u0438 HLT
3640
4055
  bunx rk86 --exit-address prog.bin \u0432\u044B\u0445\u043E\u0434 \u043F\u0440\u0438 JMP FFFEh
3641
4056
  bunx rk86 -l \u0441\u043F\u0438\u0441\u043E\u043A \u0438\u0437\u0432\u0435\u0441\u0442\u043D\u044B\u0445 \u0444\u0430\u0439\u043B\u043E\u0432
4057
+ bunx rk86 --exit-halt prog.asm \u0441\u043E\u0431\u0440\u0430\u0442\u044C \u0438 \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C .asm \u0444\u0430\u0439\u043B
4058
+ bunx rk86 -g 0x100 prog.bin \u0437\u0430\u043F\u0443\u0441\u043A \u0441 \u0430\u0434\u0440\u0435\u0441\u0430 100h
3642
4059
 
3643
4060
  \u0423\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435:
3644
4061
  Ctrl+C \u0432\u044B\u0445\u043E\u0434`);
@@ -3676,6 +4093,10 @@ function arg(args, name, defaultValue, matcher, convertor) {
3676
4093
  }
3677
4094
  async function main() {
3678
4095
  const args = process.argv.slice(2);
4096
+ if (flag(args, "-v") || flag(args, "--version")) {
4097
+ console.log(`rk86 ${package_default.version}`);
4098
+ process.exit(0);
4099
+ }
3679
4100
  if (flag(args, "-h") || flag(args, "--help")) {
3680
4101
  printHelp();
3681
4102
  process.exit(0);
@@ -3685,6 +4106,11 @@ async function main() {
3685
4106
  process.exit(0);
3686
4107
  }
3687
4108
  const loadOnly = flag(args, "-p");
4109
+ const goAddr = arg(args, "-g", undefined, /^0x[0-9a-fA-F]+$/i, (v) => parseInt(v, 16));
4110
+ if (loadOnly && goAddr !== undefined) {
4111
+ console.error("\u043E\u0448\u0438\u0431\u043A\u0430: -p \u0438 -g \u043D\u0435\u0441\u043E\u0432\u043C\u0435\u0441\u0442\u0438\u043C\u044B");
4112
+ process.exit(1);
4113
+ }
3688
4114
  const exitOnHalt = flag(args, "--exit-halt");
3689
4115
  const exitAddrValue = arg(args, "--exit-address", "0xFFFE", /^0x[0-9a-fA-F]+$/i, (v) => parseInt(v, 16));
3690
4116
  const exitAddr = exitAddrValue !== undefined;
@@ -3711,18 +4137,44 @@ async function main() {
3711
4137
  let entryPoint;
3712
4138
  let loadInfo = "";
3713
4139
  if (programFile) {
3714
- const content = await fetchFile(programFile);
3715
- const { ok, json } = parse(content);
3716
- if (ok) {
3717
- rk86_snapshot_restore(json, machine);
3718
- entryPoint = parseInt(json.cpu.pc);
3719
- loadInfo = `\u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043D: ${programFile} (PC=${hex16(entryPoint)})`;
4140
+ const ext = file_ext(programFile).toLowerCase();
4141
+ if (ext === "asm") {
4142
+ const source = await readFile(programFile, "utf-8");
4143
+ const sections = asm(source);
4144
+ if (sections.length === 0) {
4145
+ console.error("\u043E\u0448\u0438\u0431\u043A\u0430: \u0430\u0441\u0441\u0435\u043C\u0431\u043B\u0435\u0440 \u043D\u0435 \u0432\u0435\u0440\u043D\u0443\u043B \u0441\u0435\u043A\u0446\u0438\u0439");
4146
+ process.exit(1);
4147
+ }
4148
+ const lines = [];
4149
+ for (const section of sections) {
4150
+ const data = section.data;
4151
+ for (let i = 0;i < data.length; i++) {
4152
+ machine.memory.write(section.start + i, data[i]);
4153
+ }
4154
+ const name = section.name ? ` [${section.name}]` : "";
4155
+ lines.push(`${hex16(section.start)}-${hex16(section.end)}${name} (${data.length} \u0431\u0430\u0439\u0442)`);
4156
+ }
4157
+ entryPoint = goAddr ?? sections[0].start;
4158
+ loadInfo = `\u0441\u043E\u0431\u0440\u0430\u043D: ${programFile}
4159
+ ` + lines.join(`
4160
+ `) + `
4161
+ \u0437\u0430\u043F\u0443\u0441\u043A: G${hex16(entryPoint)}`;
3720
4162
  } else {
3721
- const file = parse_rk86_binary(programFile, content);
3722
- machine.memory.load_file(file);
3723
- entryPoint = file.entry;
3724
- loadInfo = `\u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043D: ${programFile} (${hex16(file.start)}-${hex16(file.end)}, G${hex16(file.entry)})`;
4163
+ const content = await fetchFile(programFile);
4164
+ const { ok, json } = parse(content);
4165
+ if (ok) {
4166
+ rk86_snapshot_restore(json, machine);
4167
+ entryPoint = parseInt(json.cpu.pc);
4168
+ loadInfo = `\u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043D: ${programFile} (PC=${hex16(entryPoint)})`;
4169
+ } else {
4170
+ const file = parse_rk86_binary(programFile, content);
4171
+ machine.memory.load_file(file);
4172
+ entryPoint = file.entry;
4173
+ loadInfo = `\u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043D: ${programFile} (${hex16(file.start)}-${hex16(file.end)}, G${hex16(file.entry)})`;
4174
+ }
3725
4175
  }
4176
+ if (goAddr !== undefined)
4177
+ entryPoint = goAddr;
3726
4178
  }
3727
4179
  process.stdout.write("\x1B[?25l");
3728
4180
  process.stdout.write("\x1B[2J");
@@ -3732,17 +4184,24 @@ async function main() {
3732
4184
  machine.screen.start(renderer);
3733
4185
  const onTerminate = exitOnHalt || exitAddr ? () => {
3734
4186
  renderer.update();
3735
- console.log();
3736
- console.log("\u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430 \u0440\u0430\u0431\u043E\u0442\u0443 \u043D\u0430", hex16(machine.cpu.pc));
3737
- process.exit(0);
4187
+ setTimeout(() => {
4188
+ console.log();
4189
+ console.log("\u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430 \u0440\u0430\u0431\u043E\u0442\u0443 \u043D\u0430", hex16(machine.cpu.pc));
4190
+ process.exit(0);
4191
+ }, 1000);
3738
4192
  } : undefined;
4193
+ const armed = { value: entryPoint === undefined };
3739
4194
  machine.runner.execute({
3740
4195
  terminate_address: exitAddr ? exitAddrValue : undefined,
3741
4196
  exit_on_halt: exitOnHalt,
3742
- on_terminate: onTerminate
4197
+ on_terminate: onTerminate,
4198
+ armed
3743
4199
  });
3744
4200
  if (entryPoint !== undefined && !loadOnly) {
3745
- setTimeout(() => machine.cpu.jump(entryPoint), 500);
4201
+ setTimeout(() => {
4202
+ machine.cpu.jump(entryPoint);
4203
+ armed.value = true;
4204
+ }, 500);
3746
4205
  }
3747
4206
  process.on("exit", () => {
3748
4207
  process.stdout.write("\x1B[?25h");