rk86 2.0.16 → 2.0.19
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 +166 -38
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,KeyF,Digit8,Digit0,Digit0,Comma,KeyF,Digit8,KeyF,KeyF,Enter" \
|
|
66
|
+
--timeout 10 \
|
|
67
|
+
--screen out.txt
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
После выхода `out.txt` содержит приглашение `-->DF800,F8FF` и 16 строк шестнадцатеричного
|
|
71
|
+
дампа `F800..F8FF`.
|
|
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.18",
|
|
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"
|
|
@@ -3707,16 +3707,19 @@ class Runner {
|
|
|
3707
3707
|
init_sound(enabled) {
|
|
3708
3708
|
if (enabled && this.sound == null && this.sound_factory) {
|
|
3709
3709
|
this.sound = this.sound_factory();
|
|
3710
|
-
|
|
3710
|
+
this.machine.log("\u0437\u0432\u0443\u043A \u0432\u043A\u043B\u044E\u0447\u0435\u043D");
|
|
3711
3711
|
} else if (!enabled) {
|
|
3712
3712
|
this.sound = null;
|
|
3713
|
-
|
|
3713
|
+
this.machine.log("\u0437\u0432\u0443\u043A \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D");
|
|
3714
3714
|
}
|
|
3715
3715
|
}
|
|
3716
3716
|
execute(options = {}) {
|
|
3717
|
-
const { terminate_address, on_terminate, exit_on_halt,
|
|
3717
|
+
const { terminate_address, on_terminate, exit_on_halt, on_batch_complete, turbo } = options;
|
|
3718
3718
|
clearTimeout(this.execute_timer);
|
|
3719
|
-
|
|
3719
|
+
const bursts = turbo ? 100 : 1;
|
|
3720
|
+
for (let burst = 0;burst < bursts; burst++) {
|
|
3721
|
+
if (this.paused)
|
|
3722
|
+
break;
|
|
3720
3723
|
let batch_ticks = 0;
|
|
3721
3724
|
let batch_instructions = 0;
|
|
3722
3725
|
while (batch_ticks < this.TICK_PER_MS) {
|
|
@@ -3742,8 +3745,6 @@ class Runner {
|
|
|
3742
3745
|
this.machine.ui.on_visualizer_hit(this.machine.memory.read_raw(this.machine.cpu.pc));
|
|
3743
3746
|
}
|
|
3744
3747
|
batch_instructions += 1;
|
|
3745
|
-
if (armed?.value === false)
|
|
3746
|
-
continue;
|
|
3747
3748
|
if (terminate_address !== undefined && this.machine.cpu.pc === terminate_address) {
|
|
3748
3749
|
on_terminate?.();
|
|
3749
3750
|
return;
|
|
@@ -3758,8 +3759,10 @@ class Runner {
|
|
|
3758
3759
|
this.previous_batch_time = now;
|
|
3759
3760
|
this.instructions_per_millisecond = batch_instructions / elapsed;
|
|
3760
3761
|
this.ticks_per_millisecond = batch_ticks / elapsed;
|
|
3762
|
+
this.machine.screen.tick_cursor(this.total_ticks, this.FREQ * (this.machine.screen.cursor_rate / 1000));
|
|
3763
|
+
on_batch_complete?.();
|
|
3761
3764
|
}
|
|
3762
|
-
this.execute_timer = setTimeout(() => this.execute(options), 10);
|
|
3765
|
+
this.execute_timer = setTimeout(() => this.execute(options), turbo ? 0 : 10);
|
|
3763
3766
|
}
|
|
3764
3767
|
pause() {
|
|
3765
3768
|
this.paused = true;
|
|
@@ -3845,9 +3848,15 @@ class Screen {
|
|
|
3845
3848
|
start(renderer) {
|
|
3846
3849
|
this.renderer = renderer;
|
|
3847
3850
|
this.renderer.connect(this.machine);
|
|
3848
|
-
this.flip_cursor();
|
|
3849
3851
|
this.render_loop();
|
|
3850
3852
|
}
|
|
3853
|
+
last_flip_ticks = 0;
|
|
3854
|
+
tick_cursor(total_ticks, ticks_per_flip) {
|
|
3855
|
+
while (total_ticks - this.last_flip_ticks >= ticks_per_flip) {
|
|
3856
|
+
this.cursor_state = !this.cursor_state;
|
|
3857
|
+
this.last_flip_ticks += ticks_per_flip;
|
|
3858
|
+
}
|
|
3859
|
+
}
|
|
3851
3860
|
render_loop() {
|
|
3852
3861
|
if (this.ready)
|
|
3853
3862
|
this.renderer.update();
|
|
@@ -3862,7 +3871,7 @@ class Screen {
|
|
|
3862
3871
|
this.machine.ui.update_screen_geometry(this.width, this.height);
|
|
3863
3872
|
if (this.last_width === this.width && this.last_height === this.height)
|
|
3864
3873
|
return;
|
|
3865
|
-
|
|
3874
|
+
this.machine.log(`\u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D \u0440\u0430\u0437\u043C\u0435\u0440 \u044D\u043A\u0440\u0430\u043D\u0430: ${width} x ${height}`);
|
|
3866
3875
|
this.last_width = this.width;
|
|
3867
3876
|
this.last_height = this.height;
|
|
3868
3877
|
if (this.last_video_memory_base !== -1)
|
|
@@ -3874,7 +3883,7 @@ class Screen {
|
|
|
3874
3883
|
this.machine.ui.update_video_memory_address(this.video_memory_base);
|
|
3875
3884
|
if (this.last_video_memory_base === this.video_memory_base)
|
|
3876
3885
|
return;
|
|
3877
|
-
|
|
3886
|
+
this.machine.log(`\u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430 \u0432\u0438\u0434\u0435\u043E\u043F\u0430\u043C\u044F\u0442\u044C \u0441 \u0430\u0434\u0440\u0435\u0441\u0430`, `${hex16(this.video_memory_base)}`, `\u0440\u0430\u0437\u043C\u0435\u0440\u043E\u043C ${hex16(this.video_memory_size)}`);
|
|
3878
3887
|
this.last_video_memory_base = this.video_memory_base;
|
|
3879
3888
|
if (this.last_width !== -1)
|
|
3880
3889
|
this.ready = true;
|
|
@@ -3883,13 +3892,28 @@ class Screen {
|
|
|
3883
3892
|
this.cursor_x = x;
|
|
3884
3893
|
this.cursor_y = y;
|
|
3885
3894
|
}
|
|
3886
|
-
flip_cursor() {
|
|
3887
|
-
this.cursor_state = !this.cursor_state;
|
|
3888
|
-
setTimeout(() => this.flip_cursor(), this.cursor_rate);
|
|
3889
|
-
}
|
|
3890
3895
|
}
|
|
3891
3896
|
|
|
3892
3897
|
// src/lib/core/rk86_snapshot.ts
|
|
3898
|
+
function rk86_snapshot(machine, version) {
|
|
3899
|
+
const { screen, cpu, keyboard, memory } = machine;
|
|
3900
|
+
const h16 = (n) => "0x" + hex16(n);
|
|
3901
|
+
const snapshot = {
|
|
3902
|
+
id: "rk86",
|
|
3903
|
+
created: new Date().toISOString(),
|
|
3904
|
+
format: "1",
|
|
3905
|
+
emulator: "rk86.ru",
|
|
3906
|
+
version,
|
|
3907
|
+
start: h16(0),
|
|
3908
|
+
end: h16(65535),
|
|
3909
|
+
boot: { keyboard: [] },
|
|
3910
|
+
cpu: cpu.export(),
|
|
3911
|
+
keyboard: keyboard.export(),
|
|
3912
|
+
screen: screen.export(),
|
|
3913
|
+
memory: memory.export()
|
|
3914
|
+
};
|
|
3915
|
+
return JSON.stringify(snapshot, null, 4);
|
|
3916
|
+
}
|
|
3893
3917
|
function rk86_snapshot_restore(snapshot, machine, keys_injector) {
|
|
3894
3918
|
try {
|
|
3895
3919
|
const json = typeof snapshot === "string" ? JSON.parse(snapshot) : snapshot;
|
|
@@ -4220,6 +4244,11 @@ class TerminalRenderer {
|
|
|
4220
4244
|
process.stdout.write(output);
|
|
4221
4245
|
}
|
|
4222
4246
|
}
|
|
4247
|
+
|
|
4248
|
+
class HeadlessRenderer {
|
|
4249
|
+
connect(_machine) {}
|
|
4250
|
+
update() {}
|
|
4251
|
+
}
|
|
4223
4252
|
var KEY_MAP = {
|
|
4224
4253
|
a: "KeyA",
|
|
4225
4254
|
b: "KeyB",
|
|
@@ -4339,8 +4368,19 @@ function printHelp() {
|
|
|
4339
4368
|
-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)
|
|
4340
4369
|
-p \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0444\u0430\u0439\u043B \u0431\u0435\u0437 \u0437\u0430\u043F\u0443\u0441\u043A\u0430
|
|
4341
4370
|
-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)
|
|
4371
|
+
-G <\u0430\u0434\u0440\u0435\u0441> \u0437\u0430\u043F\u0443\u0441\u043A \u0447\u0435\u0440\u0435\u0437 \u043A\u043E\u043C\u0430\u043D\u0434\u0443 G \u043C\u043E\u043D\u0438\u0442\u043E\u0440\u0430 (\u0438\u043D\u044A\u0435\u043A\u0446\u0438\u044F \u043A\u043B\u0430\u0432\u0438\u0448)
|
|
4342
4372
|
--exit-halt \u0432\u044B\u0445\u043E\u0434 \u043F\u0440\u0438 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0438 HLT
|
|
4343
4373
|
--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)
|
|
4374
|
+
--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)
|
|
4375
|
+
--turbo \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435 \u0431\u0435\u0437 \u043E\u0433\u0440\u0430\u043D\u0438\u0447\u0435\u043D\u0438\u044F \u0441\u043A\u043E\u0440\u043E\u0441\u0442\u0438 (\u0434\u043B\u044F \u0430\u0432\u0442\u043E\u0442\u0435\u0441\u0442\u043E\u0432)
|
|
4376
|
+
--timeout <\u0441\u0435\u043A> \u0432\u044B\u0445\u043E\u0434 \u043F\u043E \u0442\u0430\u0439\u043C\u0430\u0443\u0442\u0443
|
|
4377
|
+
--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
|
|
4378
|
+
--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)
|
|
4379
|
+
--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)
|
|
4380
|
+
--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
|
|
4381
|
+
--snapshot <\u0444\u0430\u0439\u043B> \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C \u0441\u043D\u0438\u043C\u043E\u043A \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F (JSON) \u043F\u0440\u0438 \u0432\u044B\u0445\u043E\u0434\u0435
|
|
4382
|
+
--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,...
|
|
4383
|
+
\u0442\u043E\u043A\u0435\u043D *N \u0437\u0430\u0434\u0430\u0451\u0442 \u043F\u0430\u0443\u0437\u0443 N \u043C\u0441 (\u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 *200)
|
|
4344
4384
|
|
|
4345
4385
|
\u041F\u0440\u0438\u043C\u0435\u0440\u044B:
|
|
4346
4386
|
bunx rk86 \u0437\u0430\u043F\u0443\u0441\u043A \u043C\u043E\u043D\u0438\u0442\u043E\u0440\u0430
|
|
@@ -4411,13 +4451,32 @@ async function main() {
|
|
|
4411
4451
|
const exitAddrValue = arg(args, "--exit-address", "0xFFFE", /^0x[0-9a-fA-F]+$/i, (v) => parseInt(v, 16));
|
|
4412
4452
|
const exitAddr = exitAddrValue !== undefined;
|
|
4413
4453
|
const monitorFile_ = arg(args, "-m");
|
|
4454
|
+
const headless = flag(args, "--headless");
|
|
4455
|
+
const turbo = flag(args, "--turbo");
|
|
4456
|
+
const timeoutSec = arg(args, "--timeout", undefined, /^\d+(\.\d+)?$/, parseFloat);
|
|
4457
|
+
const memoryFile = arg(args, "--memory");
|
|
4458
|
+
const addrRe = /^(0x)?[0-9a-fA-F]+$/i;
|
|
4459
|
+
const parseAddr = (v) => parseInt(v.toLowerCase().startsWith("0x") ? v.slice(2) : v, 16) & 65535;
|
|
4460
|
+
const memoryFrom = arg(args, "--memory-from", undefined, addrRe, parseAddr) ?? 0;
|
|
4461
|
+
const memoryTo = arg(args, "--memory-to", undefined, addrRe, parseAddr) ?? 65535;
|
|
4462
|
+
const screenFile = arg(args, "--screen");
|
|
4463
|
+
const snapshotFile = arg(args, "--snapshot");
|
|
4464
|
+
const goViaMonitor = arg(args, "-G", undefined, addrRe, parseAddr);
|
|
4465
|
+
let inputSeq = arg(args, "--input");
|
|
4466
|
+
if (goViaMonitor !== undefined) {
|
|
4467
|
+
const hex2 = goViaMonitor.toString(16).toUpperCase();
|
|
4468
|
+
const keys = [...hex2].map((c) => c >= "0" && c <= "9" ? `Digit${c}` : `Key${c}`);
|
|
4469
|
+
const gSeq = ["KeyG", ...keys, "Enter"].join(",");
|
|
4470
|
+
inputSeq = inputSeq ? `${inputSeq},${gSeq}` : gSeq;
|
|
4471
|
+
}
|
|
4414
4472
|
const programFile = args[0];
|
|
4415
4473
|
const keyboard = new Keyboard;
|
|
4416
4474
|
const io = new IO;
|
|
4417
4475
|
const machineBuilder = {
|
|
4418
4476
|
font: rk86_font_image(),
|
|
4419
4477
|
keyboard,
|
|
4420
|
-
io
|
|
4478
|
+
io,
|
|
4479
|
+
log: (...args2) => console.log(...args2)
|
|
4421
4480
|
};
|
|
4422
4481
|
const machine = machineBuilder;
|
|
4423
4482
|
machine.ui = new TerminalUI;
|
|
@@ -4472,35 +4531,104 @@ async function main() {
|
|
|
4472
4531
|
if (goAddr !== undefined)
|
|
4473
4532
|
entryPoint = goAddr;
|
|
4474
4533
|
}
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4534
|
+
if (!headless) {
|
|
4535
|
+
process.stdout.write("\x1B[?25l");
|
|
4536
|
+
process.stdout.write("\x1B[2J");
|
|
4537
|
+
setupKeyboard(keyboard);
|
|
4538
|
+
} else {
|
|
4539
|
+
process.on("SIGINT", () => doExit(null));
|
|
4540
|
+
}
|
|
4541
|
+
const renderer = headless ? new HeadlessRenderer : Object.assign(new TerminalRenderer, { loadInfo });
|
|
4480
4542
|
machine.screen.start(renderer);
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4543
|
+
let exiting = false;
|
|
4544
|
+
const doExit = async (message) => {
|
|
4545
|
+
if (exiting)
|
|
4546
|
+
return;
|
|
4547
|
+
exiting = true;
|
|
4548
|
+
if (screenFile)
|
|
4549
|
+
await writeFile(screenFile, dumpScreen(machine));
|
|
4550
|
+
if (memoryFile)
|
|
4551
|
+
await writeFile(memoryFile, new Uint8Array(machine.memory.buf.slice(memoryFrom, memoryTo + 1)));
|
|
4552
|
+
if (snapshotFile)
|
|
4553
|
+
await writeFile(snapshotFile, rk86_snapshot(machine, package_default.version));
|
|
4554
|
+
if (!headless)
|
|
4555
|
+
process.stdout.write("\x1B[?25h");
|
|
4556
|
+
if (message !== null && !headless) {
|
|
4484
4557
|
console.log();
|
|
4485
|
-
console.log(
|
|
4486
|
-
|
|
4487
|
-
|
|
4558
|
+
console.log(message);
|
|
4559
|
+
}
|
|
4560
|
+
process.exit(0);
|
|
4561
|
+
};
|
|
4562
|
+
const onTerminate = exitOnHalt || exitAddr ? () => {
|
|
4563
|
+
if (!headless)
|
|
4564
|
+
renderer.update();
|
|
4565
|
+
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
4566
|
} : undefined;
|
|
4489
|
-
const
|
|
4567
|
+
const armDelayMs = 500;
|
|
4568
|
+
if (entryPoint !== undefined && !loadOnly) {
|
|
4569
|
+
setTimeout(() => {
|
|
4570
|
+
machine.cpu.jump(entryPoint);
|
|
4571
|
+
}, armDelayMs);
|
|
4572
|
+
}
|
|
4573
|
+
const tickEvents = [];
|
|
4574
|
+
if (inputSeq) {
|
|
4575
|
+
const keys = inputSeq.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
4576
|
+
const TICKS_PER_MS = machine.runner.FREQ / 1000;
|
|
4577
|
+
const settleMs = armDelayMs + 1000;
|
|
4578
|
+
const keyDownMs = 50;
|
|
4579
|
+
const keyGapMs = 50;
|
|
4580
|
+
let t = settleMs * TICKS_PER_MS;
|
|
4581
|
+
for (const token of keys) {
|
|
4582
|
+
if (token.startsWith("*")) {
|
|
4583
|
+
const delayMs = parseInt(token.slice(1), 10);
|
|
4584
|
+
if (!Number.isFinite(delayMs) || delayMs < 0) {
|
|
4585
|
+
console.error(`\u043D\u0435\u0432\u0435\u0440\u043D\u0430\u044F \u0437\u0430\u0434\u0435\u0440\u0436\u043A\u0430 \u0432 --input: ${token}`);
|
|
4586
|
+
process.exit(1);
|
|
4587
|
+
}
|
|
4588
|
+
t += delayMs * TICKS_PER_MS;
|
|
4589
|
+
continue;
|
|
4590
|
+
}
|
|
4591
|
+
const code = token;
|
|
4592
|
+
tickEvents.push({ at_ticks: t, action: () => keyboard.onkeydown(code) });
|
|
4593
|
+
t += keyDownMs * TICKS_PER_MS;
|
|
4594
|
+
tickEvents.push({ at_ticks: t, action: () => keyboard.onkeyup(code) });
|
|
4595
|
+
t += keyGapMs * TICKS_PER_MS;
|
|
4596
|
+
}
|
|
4597
|
+
}
|
|
4490
4598
|
machine.runner.execute({
|
|
4491
4599
|
terminate_address: exitAddr ? exitAddrValue : undefined,
|
|
4492
4600
|
exit_on_halt: exitOnHalt,
|
|
4493
4601
|
on_terminate: onTerminate,
|
|
4494
|
-
|
|
4602
|
+
turbo,
|
|
4603
|
+
on_batch_complete: () => {
|
|
4604
|
+
const now = machine.runner.total_ticks;
|
|
4605
|
+
while (tickEvents.length > 0 && tickEvents[0].at_ticks <= now) {
|
|
4606
|
+
tickEvents.shift().action();
|
|
4607
|
+
}
|
|
4608
|
+
}
|
|
4495
4609
|
});
|
|
4496
|
-
if (
|
|
4497
|
-
setTimeout(() => {
|
|
4498
|
-
machine.cpu.jump(entryPoint);
|
|
4499
|
-
armed.value = true;
|
|
4500
|
-
}, 500);
|
|
4610
|
+
if (timeoutSec !== undefined) {
|
|
4611
|
+
setTimeout(() => doExit(`\u0432\u044B\u0445\u043E\u0434 \u043F\u043E \u0442\u0430\u0439\u043C\u0430\u0443\u0442\u0443 ${timeoutSec}\u0441`), timeoutSec * 1000);
|
|
4501
4612
|
}
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
}
|
|
4613
|
+
}
|
|
4614
|
+
function dumpScreen(machine) {
|
|
4615
|
+
const { memory, screen } = machine;
|
|
4616
|
+
const lines = [];
|
|
4617
|
+
let addr = screen.video_memory_base;
|
|
4618
|
+
for (let y = 0;y < screen.height; y++) {
|
|
4619
|
+
let line = "";
|
|
4620
|
+
for (let x = 0;x < screen.width; x++) {
|
|
4621
|
+
const byte = memory.read_raw(addr++) & 127;
|
|
4622
|
+
if (byte === 0 || byte === 9 || byte === 10 || byte === 13) {
|
|
4623
|
+
line += ".";
|
|
4624
|
+
} else {
|
|
4625
|
+
line += rk86char(byte);
|
|
4626
|
+
}
|
|
4627
|
+
}
|
|
4628
|
+
lines.push(line);
|
|
4629
|
+
}
|
|
4630
|
+
return lines.join(`\r
|
|
4631
|
+
`) + `\r
|
|
4632
|
+
`;
|
|
4505
4633
|
}
|
|
4506
4634
|
main();
|