rk86 2.0.16 → 2.0.17
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 +71 -13
- package/package.json +1 -1
- package/rk86.js +95 -17
package/README.md
CHANGED
|
@@ -5,24 +5,33 @@
|
|
|
5
5
|
## Запуск
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
bunx rk86
|
|
8
|
+
bunx rk86 # встроенный монитор mon32
|
|
9
|
+
bunx rk86 CHESS.GAM # загрузить и запустить файл из текущей директории
|
|
10
|
+
bunx rk86 -p CHESS.GAM # загрузить без запуска
|
|
11
|
+
bunx rk86 -g 0x100 prog.bin # запуск с адреса 0x100
|
|
12
|
+
bunx rk86 -m mon16.bin # другой монитор
|
|
13
|
+
bunx rk86 prog.asm # собрать и запустить .asm (i8080)
|
|
14
|
+
bunx rk86 -l # список файлов встроенного каталога
|
|
9
15
|
```
|
|
10
16
|
|
|
11
|
-
## С программой
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
bunx rk86 CHESS.GAM
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
Файл программы загружается из текущей директории.
|
|
18
|
-
|
|
19
17
|
## Опции
|
|
20
18
|
|
|
21
19
|
```
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
20
|
+
-v версия
|
|
21
|
+
-h справка
|
|
22
|
+
-l список файлов из каталога
|
|
23
|
+
-m <файл> монитор (по умолчанию: встроенный mon32.bin)
|
|
24
|
+
-p загрузить файл без запуска
|
|
25
|
+
-g <адрес> адрес запуска (несовместим с -p)
|
|
26
|
+
--exit-halt выход при выполнении HLT
|
|
27
|
+
--exit-address [адрес] выход при переходе на адрес (по умолчанию: 0xFFFE)
|
|
28
|
+
--headless без отображения экрана (для автотестов)
|
|
29
|
+
--timeout <сек> выход по таймауту
|
|
30
|
+
--memory <файл> сохранить память в файл при выходе
|
|
31
|
+
--memory-from <адрес> начало области дампа памяти (по умолчанию: 0x0000)
|
|
32
|
+
--memory-to <адрес> конец области дампа памяти включительно (по умолчанию: 0xFFFF)
|
|
33
|
+
--screen <файл> сохранить экран 78x30 как текст при выходе
|
|
34
|
+
--input <seq> инъекция клавиш (через запятую): KeyA,Digit1,Enter,...
|
|
26
35
|
```
|
|
27
36
|
|
|
28
37
|
## Управление
|
|
@@ -30,6 +39,55 @@ bunx rk86 CHESS.GAM
|
|
|
30
39
|
- Клавиатура работает через stdin (raw mode)
|
|
31
40
|
- `Ctrl+C` — выход
|
|
32
41
|
|
|
42
|
+
## Безголовый режим (headless) и автотесты
|
|
43
|
+
|
|
44
|
+
Флаг `--headless` отключает вывод на терминал и настройку stdin — удобно для
|
|
45
|
+
автоматизированных e2e-проверок. Обычно комбинируется с `--input`, `--timeout`,
|
|
46
|
+
`--exit-halt` и `--screen`/`--memory` для анализа результата.
|
|
47
|
+
|
|
48
|
+
Формат `--input`: через запятую указываются WebKit-коды клавиш
|
|
49
|
+
(`KeyA`..`KeyZ`, `Digit0`..`Digit9`, `Enter`, `Space`, `Comma`, `Period`,
|
|
50
|
+
`Backspace`, `ArrowLeft`, `F1`..`F10`, и т.д.). Инъекция начинается после того,
|
|
51
|
+
как эмулятор инициализируется (как при подгрузке файла — с небольшой задержкой),
|
|
52
|
+
клавиши нажимаются по одной с минимальной паузой.
|
|
53
|
+
|
|
54
|
+
Формат файла `--screen`: 30 строк по 78 символов, разделитель `\r\n`.
|
|
55
|
+
Байты `\0`, `\t`, `\n`, `\r` заменяются на `.`, остальные символы с кодами <0x20
|
|
56
|
+
рисуются псевдографикой (как на реальном РК-86).
|
|
57
|
+
|
|
58
|
+
### Пример 1. Дамп памяти через команду монитора `D`
|
|
59
|
+
|
|
60
|
+
Монитор `mon32` печатает дамп памяти с адреса 0 до FFh, через 10 секунд
|
|
61
|
+
эмулятор завершается по таймауту и содержимое экрана сохраняется в файл:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
bunx rk86 --headless \
|
|
65
|
+
--input "KeyD,Digit0,Comma,KeyF,KeyF,Enter" \
|
|
66
|
+
--timeout 10 \
|
|
67
|
+
--screen out.txt
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
После выхода `out.txt` содержит приглашение `-->D0,FF` и 16 строк шестнадцатеричного
|
|
71
|
+
дампа `0000..00F0`.
|
|
72
|
+
|
|
73
|
+
### Пример 2. Запись HLT и запуск через команды `M` / `G`
|
|
74
|
+
|
|
75
|
+
Командой `M` мы помещаем байт `76h` (опкод HLT) в ячейку `0000`, выходим из режима
|
|
76
|
+
редактирования (`.`), затем командой `G 0` запускаем с адреса 0. На первой же
|
|
77
|
+
инструкции срабатывает HLT и `--exit-halt` завершает эмулятор:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
bunx rk86 --headless \
|
|
81
|
+
--exit-halt \
|
|
82
|
+
--input "KeyM,Enter,Digit7,Digit6,Enter,Period,KeyG,Digit0,Enter" \
|
|
83
|
+
--timeout 15 \
|
|
84
|
+
--screen out.txt \
|
|
85
|
+
--memory mem.bin --memory-from 0x0000 --memory-to 0x0000
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
После выхода `mem.bin` содержит один байт `76h`, а `out.txt` — скриншот с
|
|
89
|
+
приглашениями `-->M` и `-->G0`.
|
|
90
|
+
|
|
33
91
|
## Требования
|
|
34
92
|
|
|
35
93
|
- [Bun](https://bun.sh) runtime
|
package/package.json
CHANGED
package/rk86.js
CHANGED
|
@@ -2273,11 +2273,11 @@ if (false) {}
|
|
|
2273
2273
|
|
|
2274
2274
|
// src/lib/terminal/rk86_terminal.ts
|
|
2275
2275
|
import { existsSync } from "fs";
|
|
2276
|
-
import { readFile } from "fs/promises";
|
|
2276
|
+
import { readFile, writeFile } from "fs/promises";
|
|
2277
2277
|
// packages/rk86/package.json
|
|
2278
2278
|
var package_default = {
|
|
2279
2279
|
name: "rk86",
|
|
2280
|
-
version: "2.0.
|
|
2280
|
+
version: "2.0.16",
|
|
2281
2281
|
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",
|
|
2282
2282
|
bin: {
|
|
2283
2283
|
rk86: "rk86.js"
|
|
@@ -4220,6 +4220,11 @@ class TerminalRenderer {
|
|
|
4220
4220
|
process.stdout.write(output);
|
|
4221
4221
|
}
|
|
4222
4222
|
}
|
|
4223
|
+
|
|
4224
|
+
class HeadlessRenderer {
|
|
4225
|
+
connect(_machine) {}
|
|
4226
|
+
update() {}
|
|
4227
|
+
}
|
|
4223
4228
|
var KEY_MAP = {
|
|
4224
4229
|
a: "KeyA",
|
|
4225
4230
|
b: "KeyB",
|
|
@@ -4341,6 +4346,13 @@ function printHelp() {
|
|
|
4341
4346
|
-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)
|
|
4342
4347
|
--exit-halt \u0432\u044B\u0445\u043E\u0434 \u043F\u0440\u0438 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0438 HLT
|
|
4343
4348
|
--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)
|
|
4349
|
+
--headless \u0431\u0435\u0437 \u043E\u0442\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u044F \u044D\u043A\u0440\u0430\u043D\u0430 (\u0434\u043B\u044F \u0430\u0432\u0442\u043E\u0442\u0435\u0441\u0442\u043E\u0432)
|
|
4350
|
+
--timeout <\u0441\u0435\u043A> \u0432\u044B\u0445\u043E\u0434 \u043F\u043E \u0442\u0430\u0439\u043C\u0430\u0443\u0442\u0443
|
|
4351
|
+
--memory <\u0444\u0430\u0439\u043B> \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C \u043F\u0430\u043C\u044F\u0442\u044C \u0432 \u0444\u0430\u0439\u043B \u043F\u0440\u0438 \u0432\u044B\u0445\u043E\u0434\u0435
|
|
4352
|
+
--memory-from <\u0430\u0434\u0440\u0435\u0441> \u043D\u0430\u0447\u0430\u043B\u043E \u043E\u0431\u043B\u0430\u0441\u0442\u0438 \u0434\u0430\u043C\u043F\u0430 \u043F\u0430\u043C\u044F\u0442\u0438 (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E: 0x0000)
|
|
4353
|
+
--memory-to <\u0430\u0434\u0440\u0435\u0441> \u043A\u043E\u043D\u0435\u0446 \u043E\u0431\u043B\u0430\u0441\u0442\u0438 \u0434\u0430\u043C\u043F\u0430 \u043F\u0430\u043C\u044F\u0442\u0438 \u0432\u043A\u043B\u044E\u0447\u0438\u0442\u0435\u043B\u044C\u043D\u043E (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E: 0xFFFF)
|
|
4354
|
+
--screen <\u0444\u0430\u0439\u043B> \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C \u044D\u043A\u0440\u0430\u043D 78x30 \u043A\u0430\u043A \u0442\u0435\u043A\u0441\u0442 \u043F\u0440\u0438 \u0432\u044B\u0445\u043E\u0434\u0435
|
|
4355
|
+
--input <seq> \u0438\u043D\u044A\u0435\u043A\u0446\u0438\u044F \u043A\u043B\u0430\u0432\u0438\u0448 (\u0447\u0435\u0440\u0435\u0437 \u0437\u0430\u043F\u044F\u0442\u0443\u044E): KeyA,Digit1,Enter,...
|
|
4344
4356
|
|
|
4345
4357
|
\u041F\u0440\u0438\u043C\u0435\u0440\u044B:
|
|
4346
4358
|
bunx rk86 \u0437\u0430\u043F\u0443\u0441\u043A \u043C\u043E\u043D\u0438\u0442\u043E\u0440\u0430
|
|
@@ -4411,6 +4423,15 @@ async function main() {
|
|
|
4411
4423
|
const exitAddrValue = arg(args, "--exit-address", "0xFFFE", /^0x[0-9a-fA-F]+$/i, (v) => parseInt(v, 16));
|
|
4412
4424
|
const exitAddr = exitAddrValue !== undefined;
|
|
4413
4425
|
const monitorFile_ = arg(args, "-m");
|
|
4426
|
+
const headless = flag(args, "--headless");
|
|
4427
|
+
const timeoutSec = arg(args, "--timeout", undefined, /^\d+(\.\d+)?$/, parseFloat);
|
|
4428
|
+
const memoryFile = arg(args, "--memory");
|
|
4429
|
+
const addrRe = /^(0x)?[0-9a-fA-F]+$/i;
|
|
4430
|
+
const parseAddr = (v) => parseInt(v.toLowerCase().startsWith("0x") ? v.slice(2) : v, 16) & 65535;
|
|
4431
|
+
const memoryFrom = arg(args, "--memory-from", undefined, addrRe, parseAddr) ?? 0;
|
|
4432
|
+
const memoryTo = arg(args, "--memory-to", undefined, addrRe, parseAddr) ?? 65535;
|
|
4433
|
+
const screenFile = arg(args, "--screen");
|
|
4434
|
+
const inputSeq = arg(args, "--input");
|
|
4414
4435
|
const programFile = args[0];
|
|
4415
4436
|
const keyboard = new Keyboard;
|
|
4416
4437
|
const io = new IO;
|
|
@@ -4472,19 +4493,36 @@ async function main() {
|
|
|
4472
4493
|
if (goAddr !== undefined)
|
|
4473
4494
|
entryPoint = goAddr;
|
|
4474
4495
|
}
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4496
|
+
if (!headless) {
|
|
4497
|
+
process.stdout.write("\x1B[?25l");
|
|
4498
|
+
process.stdout.write("\x1B[2J");
|
|
4499
|
+
setupKeyboard(keyboard);
|
|
4500
|
+
} else {
|
|
4501
|
+
process.on("SIGINT", () => doExit(null));
|
|
4502
|
+
}
|
|
4503
|
+
const renderer = headless ? new HeadlessRenderer : Object.assign(new TerminalRenderer, { loadInfo });
|
|
4480
4504
|
machine.screen.start(renderer);
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4505
|
+
let exiting = false;
|
|
4506
|
+
const doExit = async (message) => {
|
|
4507
|
+
if (exiting)
|
|
4508
|
+
return;
|
|
4509
|
+
exiting = true;
|
|
4510
|
+
if (screenFile)
|
|
4511
|
+
await writeFile(screenFile, dumpScreen(machine));
|
|
4512
|
+
if (memoryFile)
|
|
4513
|
+
await writeFile(memoryFile, new Uint8Array(machine.memory.buf.slice(memoryFrom, memoryTo + 1)));
|
|
4514
|
+
if (!headless)
|
|
4515
|
+
process.stdout.write("\x1B[?25h");
|
|
4516
|
+
if (message !== null && !headless) {
|
|
4484
4517
|
console.log();
|
|
4485
|
-
console.log(
|
|
4486
|
-
|
|
4487
|
-
|
|
4518
|
+
console.log(message);
|
|
4519
|
+
}
|
|
4520
|
+
process.exit(0);
|
|
4521
|
+
};
|
|
4522
|
+
const onTerminate = exitOnHalt || exitAddr ? () => {
|
|
4523
|
+
if (!headless)
|
|
4524
|
+
renderer.update();
|
|
4525
|
+
setTimeout(() => doExit(`\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)}`), headless ? 0 : 1000);
|
|
4488
4526
|
} : undefined;
|
|
4489
4527
|
const armed = { value: entryPoint === undefined };
|
|
4490
4528
|
machine.runner.execute({
|
|
@@ -4493,14 +4531,54 @@ async function main() {
|
|
|
4493
4531
|
on_terminate: onTerminate,
|
|
4494
4532
|
armed
|
|
4495
4533
|
});
|
|
4534
|
+
const armDelayMs = 500;
|
|
4496
4535
|
if (entryPoint !== undefined && !loadOnly) {
|
|
4497
4536
|
setTimeout(() => {
|
|
4498
4537
|
machine.cpu.jump(entryPoint);
|
|
4499
4538
|
armed.value = true;
|
|
4500
|
-
},
|
|
4539
|
+
}, armDelayMs);
|
|
4501
4540
|
}
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4541
|
+
if (inputSeq) {
|
|
4542
|
+
const keys = inputSeq.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
4543
|
+
const settleMs = armDelayMs + 1000;
|
|
4544
|
+
const keyDownMs = 50;
|
|
4545
|
+
const keyGapMs = 50;
|
|
4546
|
+
setTimeout(() => {
|
|
4547
|
+
const pressNext = (i) => {
|
|
4548
|
+
if (i >= keys.length)
|
|
4549
|
+
return;
|
|
4550
|
+
const code = keys[i];
|
|
4551
|
+
keyboard.onkeydown(code);
|
|
4552
|
+
setTimeout(() => {
|
|
4553
|
+
keyboard.onkeyup(code);
|
|
4554
|
+
setTimeout(() => pressNext(i + 1), keyGapMs);
|
|
4555
|
+
}, keyDownMs);
|
|
4556
|
+
};
|
|
4557
|
+
pressNext(0);
|
|
4558
|
+
}, settleMs);
|
|
4559
|
+
}
|
|
4560
|
+
if (timeoutSec !== undefined) {
|
|
4561
|
+
setTimeout(() => doExit(`\u0432\u044B\u0445\u043E\u0434 \u043F\u043E \u0442\u0430\u0439\u043C\u0430\u0443\u0442\u0443 ${timeoutSec}\u0441`), timeoutSec * 1000);
|
|
4562
|
+
}
|
|
4563
|
+
}
|
|
4564
|
+
function dumpScreen(machine) {
|
|
4565
|
+
const { memory, screen } = machine;
|
|
4566
|
+
const lines = [];
|
|
4567
|
+
let addr = screen.video_memory_base;
|
|
4568
|
+
for (let y = 0;y < screen.height; y++) {
|
|
4569
|
+
let line = "";
|
|
4570
|
+
for (let x = 0;x < screen.width; x++) {
|
|
4571
|
+
const byte = memory.read_raw(addr++) & 127;
|
|
4572
|
+
if (byte === 0 || byte === 9 || byte === 10 || byte === 13) {
|
|
4573
|
+
line += ".";
|
|
4574
|
+
} else {
|
|
4575
|
+
line += rk86char(byte);
|
|
4576
|
+
}
|
|
4577
|
+
}
|
|
4578
|
+
lines.push(line);
|
|
4579
|
+
}
|
|
4580
|
+
return lines.join(`\r
|
|
4581
|
+
`) + `\r
|
|
4582
|
+
`;
|
|
4505
4583
|
}
|
|
4506
4584
|
main();
|