rk86 2.0.29 → 2.0.31
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 +2 -0
- package/package.json +2 -2
- package/rk86.js +212 -25
package/README.md
CHANGED
|
@@ -31,6 +31,8 @@ bunx rk86 --online CHESS.GAM # открыть файл в онлайн-э
|
|
|
31
31
|
--memory-to <адрес> конец области дампа памяти включительно (по умолчанию: 0xFFFF)
|
|
32
32
|
--screen <файл> сохранить экран 78x30 как текст при выходе
|
|
33
33
|
--input <seq> инъекция клавиш (через запятую): KeyA,Digit1,Enter,...
|
|
34
|
+
--color <0|1|2|3> режим цвета: 0=ч/б (по умолчанию), 1=Толкалин,
|
|
35
|
+
2=Акименко, 3=Апогей
|
|
34
36
|
--online открыть в онлайн-эмуляторе rk86.ru
|
|
35
37
|
```
|
|
36
38
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rk86",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.31",
|
|
4
4
|
"description": "Эмулятор Радио-86РК (Intel 8080) для терминала",
|
|
5
5
|
"bin": {
|
|
6
6
|
"rk86": "rk86.js"
|
|
@@ -18,6 +18,6 @@
|
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"repository": {
|
|
20
20
|
"type": "git",
|
|
21
|
-
"url": "https://github.com/begoon/rk86-js
|
|
21
|
+
"url": "https://github.com/begoon/rk86-js"
|
|
22
22
|
}
|
|
23
23
|
}
|
package/rk86.js
CHANGED
|
@@ -165,6 +165,7 @@ function preprocess(source) {
|
|
|
165
165
|
const out = [];
|
|
166
166
|
const stack = [];
|
|
167
167
|
let counter = 0;
|
|
168
|
+
let procCounter = 0;
|
|
168
169
|
let proc = null;
|
|
169
170
|
for (let i = 0;i < lines.length; i++) {
|
|
170
171
|
const line = lines[i];
|
|
@@ -223,7 +224,14 @@ function preprocess(source) {
|
|
|
223
224
|
regs.push(up);
|
|
224
225
|
}
|
|
225
226
|
}
|
|
226
|
-
|
|
227
|
+
const id = procCounter++;
|
|
228
|
+
proc = {
|
|
229
|
+
regs,
|
|
230
|
+
line: orig,
|
|
231
|
+
source: line,
|
|
232
|
+
exitLabel: `__proc_${id}_exit`,
|
|
233
|
+
returnUsed: false
|
|
234
|
+
};
|
|
227
235
|
out.push({ text: `${name}:`, orig });
|
|
228
236
|
for (const r of regs) {
|
|
229
237
|
out.push({ text: ` PUSH ${r}`, orig });
|
|
@@ -237,6 +245,9 @@ function preprocess(source) {
|
|
|
237
245
|
if (!proc) {
|
|
238
246
|
throw new AsmError(".endp without .proc", orig, line, firstNonSpaceCol(line));
|
|
239
247
|
}
|
|
248
|
+
if (proc.returnUsed) {
|
|
249
|
+
out.push({ text: `${proc.exitLabel}:`, orig });
|
|
250
|
+
}
|
|
240
251
|
out.push(...popsAndRet(proc.regs, orig));
|
|
241
252
|
proc = null;
|
|
242
253
|
continue;
|
|
@@ -245,7 +256,12 @@ function preprocess(source) {
|
|
|
245
256
|
if (!proc) {
|
|
246
257
|
throw new AsmError(".return outside .proc", orig, line, firstNonSpaceCol(line));
|
|
247
258
|
}
|
|
248
|
-
|
|
259
|
+
if (proc.regs.length === 0) {
|
|
260
|
+
out.push({ text: ` RET`, orig });
|
|
261
|
+
} else {
|
|
262
|
+
proc.returnUsed = true;
|
|
263
|
+
out.push({ text: ` JMP ${proc.exitLabel}`, orig });
|
|
264
|
+
}
|
|
249
265
|
continue;
|
|
250
266
|
}
|
|
251
267
|
out.push({ text: line, orig });
|
|
@@ -742,6 +758,7 @@ function countDb(operands) {
|
|
|
742
758
|
function asm(source) {
|
|
743
759
|
const pp = preprocess(source);
|
|
744
760
|
const symbols = new Map;
|
|
761
|
+
const pending = [];
|
|
745
762
|
let pc = 0;
|
|
746
763
|
let lastLabel = "";
|
|
747
764
|
let ended = false;
|
|
@@ -760,7 +777,7 @@ function asm(source) {
|
|
|
760
777
|
lastLabel = parts.label;
|
|
761
778
|
}
|
|
762
779
|
if (parts.isEqu) {
|
|
763
|
-
symbols
|
|
780
|
+
tryDefineEqu(symbols, pending, labelName, parts.operands[0], pc, lastLabel, orig, line);
|
|
764
781
|
continue;
|
|
765
782
|
}
|
|
766
783
|
symbols.set(labelName.toUpperCase(), pc);
|
|
@@ -800,6 +817,7 @@ function asm(source) {
|
|
|
800
817
|
throw new AsmError(e.message, orig, line, firstNonSpaceCol(line));
|
|
801
818
|
}
|
|
802
819
|
}
|
|
820
|
+
resolvePendingEqus(symbols, pending);
|
|
803
821
|
const sections = [];
|
|
804
822
|
let current = null;
|
|
805
823
|
const sectionNames = new Set;
|
|
@@ -861,6 +879,50 @@ function asm(source) {
|
|
|
861
879
|
}
|
|
862
880
|
return sections;
|
|
863
881
|
}
|
|
882
|
+
function isUnknownSymbolErr(e) {
|
|
883
|
+
return e instanceof Error && /^unknown symbol:/.test(e.message);
|
|
884
|
+
}
|
|
885
|
+
function tryDefineEqu(symbols, pending, name, expr, pc, lastLabel, orig, line) {
|
|
886
|
+
try {
|
|
887
|
+
symbols.set(name.toUpperCase(), evalExpr(expr, symbols, pc, lastLabel));
|
|
888
|
+
} catch (e) {
|
|
889
|
+
if (isUnknownSymbolErr(e)) {
|
|
890
|
+
pending.push({ name, expr, pc, lastLabel, orig, line });
|
|
891
|
+
} else {
|
|
892
|
+
throw e;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
function resolvePendingEqus(symbols, pending) {
|
|
897
|
+
while (pending.length > 0) {
|
|
898
|
+
let progress = false;
|
|
899
|
+
const next = [];
|
|
900
|
+
for (const p of pending) {
|
|
901
|
+
try {
|
|
902
|
+
symbols.set(p.name.toUpperCase(), evalExpr(p.expr, symbols, p.pc, p.lastLabel));
|
|
903
|
+
progress = true;
|
|
904
|
+
} catch (e) {
|
|
905
|
+
if (isUnknownSymbolErr(e)) {
|
|
906
|
+
next.push(p);
|
|
907
|
+
} else {
|
|
908
|
+
throw new AsmError(e.message, p.orig, p.line, firstNonSpaceCol(p.line));
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
if (!progress) {
|
|
913
|
+
const p = next[0];
|
|
914
|
+
try {
|
|
915
|
+
evalExpr(p.expr, symbols, p.pc, p.lastLabel);
|
|
916
|
+
} catch (e) {
|
|
917
|
+
throw new AsmError(e.message, p.orig, p.line, firstNonSpaceCol(p.line));
|
|
918
|
+
}
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
pending.length = 0;
|
|
922
|
+
pending.push(...next);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
var DATA_DIRECTIVES = new Set(["DB", "DW", "DS"]);
|
|
864
926
|
if (false) {}
|
|
865
927
|
|
|
866
928
|
// src/lib/terminal/rk86_terminal.ts
|
|
@@ -871,7 +933,7 @@ import { basename } from "path";
|
|
|
871
933
|
// packages/rk86/package.json
|
|
872
934
|
var package_default = {
|
|
873
935
|
name: "rk86",
|
|
874
|
-
version: "2.0.
|
|
936
|
+
version: "2.0.30",
|
|
875
937
|
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",
|
|
876
938
|
bin: {
|
|
877
939
|
rk86: "rk86.js"
|
|
@@ -889,10 +951,63 @@ var package_default = {
|
|
|
889
951
|
license: "MIT",
|
|
890
952
|
repository: {
|
|
891
953
|
type: "git",
|
|
892
|
-
url: "https://github.com/begoon/rk86-js
|
|
954
|
+
url: "https://github.com/begoon/rk86-js"
|
|
893
955
|
}
|
|
894
956
|
};
|
|
895
957
|
|
|
958
|
+
// src/lib/core/rk86_colors.ts
|
|
959
|
+
var COLOR_MODES = ["mono", "color1", "color2", "color3"];
|
|
960
|
+
var DEFAULT_COLOR_MODE = "color1";
|
|
961
|
+
function attrToRgb(mode, attrs) {
|
|
962
|
+
const hglt = (attrs & 1) !== 0;
|
|
963
|
+
const gpa0 = (attrs & 4) !== 0;
|
|
964
|
+
const gpa1 = (attrs & 8) !== 0;
|
|
965
|
+
switch (mode) {
|
|
966
|
+
case "color1": {
|
|
967
|
+
const rgb = (gpa1 ? 255 : 0) | (gpa0 ? 65280 : 0) | (hglt ? 16711680 : 0);
|
|
968
|
+
return rgb === 0 ? 12632256 : rgb;
|
|
969
|
+
}
|
|
970
|
+
case "color2":
|
|
971
|
+
return (gpa0 ? 0 : 16711680) | (gpa1 ? 0 : 65280) | (hglt ? 0 : 255);
|
|
972
|
+
case "color3":
|
|
973
|
+
return (gpa0 ? 0 : 255) | (gpa1 ? 0 : 65280) | (hglt ? 0 : 16711680);
|
|
974
|
+
case "mono":
|
|
975
|
+
default:
|
|
976
|
+
return 12632256;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
function hasCellOffset(mode) {
|
|
980
|
+
return mode !== "color3";
|
|
981
|
+
}
|
|
982
|
+
function rgbToAnsiBaseFg(rgb) {
|
|
983
|
+
const r = rgb >> 16 & 255;
|
|
984
|
+
const g = rgb >> 8 & 255;
|
|
985
|
+
const b = rgb & 255;
|
|
986
|
+
const palette = [
|
|
987
|
+
[30, 0, 0, 0],
|
|
988
|
+
[31, 255, 0, 0],
|
|
989
|
+
[32, 0, 255, 0],
|
|
990
|
+
[33, 255, 255, 0],
|
|
991
|
+
[34, 0, 0, 255],
|
|
992
|
+
[35, 255, 0, 255],
|
|
993
|
+
[36, 0, 255, 255],
|
|
994
|
+
[37, 255, 255, 255]
|
|
995
|
+
];
|
|
996
|
+
let best = 37;
|
|
997
|
+
let bestDist = Infinity;
|
|
998
|
+
for (const [code, pr, pg, pb] of palette) {
|
|
999
|
+
const dr = r - pr;
|
|
1000
|
+
const dg = g - pg;
|
|
1001
|
+
const db = b - pb;
|
|
1002
|
+
const dist = dr * dr + dg * dg + db * db;
|
|
1003
|
+
if (dist < bestDist) {
|
|
1004
|
+
bestDist = dist;
|
|
1005
|
+
best = code;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
return best;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
896
1011
|
// src/lib/core/hex.ts
|
|
897
1012
|
function hex(v, prefix) {
|
|
898
1013
|
return v.toString(16).toUpperCase();
|
|
@@ -2208,6 +2323,8 @@ class Memory {
|
|
|
2208
2323
|
return;
|
|
2209
2324
|
}
|
|
2210
2325
|
if (vg75_reg === 49152 && this.vg75_c001_00_cmd === 3) {
|
|
2326
|
+
this.machine.screen.set_char_height((byte & 15) + 1);
|
|
2327
|
+
this.machine.screen.underline_scanline = byte >> 4 & 15;
|
|
2211
2328
|
this.vg75_c001_00_cmd += 1;
|
|
2212
2329
|
return;
|
|
2213
2330
|
}
|
|
@@ -2301,6 +2418,9 @@ class Memory {
|
|
|
2301
2418
|
// src/lib/core/rk86_runner.ts
|
|
2302
2419
|
class Runner {
|
|
2303
2420
|
paused = false;
|
|
2421
|
+
turbo = false;
|
|
2422
|
+
hardware_id_enabled = false;
|
|
2423
|
+
stc_streak = 0;
|
|
2304
2424
|
tracer = null;
|
|
2305
2425
|
last_instructions = [];
|
|
2306
2426
|
previous_batch_time = 0;
|
|
@@ -2322,7 +2442,7 @@ class Runner {
|
|
|
2322
2442
|
this.machine.cpu.jump(63488);
|
|
2323
2443
|
}
|
|
2324
2444
|
interrupt(iff) {
|
|
2325
|
-
if (!this.sound)
|
|
2445
|
+
if (!this.sound || this.turbo)
|
|
2326
2446
|
return;
|
|
2327
2447
|
if (this.last_iff == iff)
|
|
2328
2448
|
return;
|
|
@@ -2347,8 +2467,11 @@ class Runner {
|
|
|
2347
2467
|
}
|
|
2348
2468
|
}
|
|
2349
2469
|
execute(options = {}) {
|
|
2350
|
-
const { terminate_address, on_terminate, exit_on_halt,
|
|
2470
|
+
const { terminate_address, on_terminate, exit_on_halt, on_halt, on_batch_complete } = options;
|
|
2471
|
+
if (options.turbo !== undefined)
|
|
2472
|
+
this.turbo = options.turbo;
|
|
2351
2473
|
clearTimeout(this.execute_timer);
|
|
2474
|
+
const turbo = this.turbo;
|
|
2352
2475
|
const bursts = turbo ? 100 : 1;
|
|
2353
2476
|
for (let burst = 0;burst < bursts; burst++) {
|
|
2354
2477
|
if (this.paused)
|
|
@@ -2365,10 +2488,21 @@ class Runner {
|
|
|
2365
2488
|
if (this.last_instructions.length > 5) {
|
|
2366
2489
|
this.last_instructions.shift();
|
|
2367
2490
|
}
|
|
2491
|
+
const opcode_pc = this.machine.cpu.pc;
|
|
2368
2492
|
this.machine.memory.invalidate_access_variables();
|
|
2369
2493
|
const instruction_ticks = this.machine.cpu.instruction();
|
|
2370
2494
|
batch_ticks += instruction_ticks;
|
|
2371
2495
|
this.total_ticks += instruction_ticks;
|
|
2496
|
+
if (this.hardware_id_enabled) {
|
|
2497
|
+
if (this.machine.memory.read_raw(opcode_pc) === 55) {
|
|
2498
|
+
if (++this.stc_streak >= 4) {
|
|
2499
|
+
this.stc_streak = 0;
|
|
2500
|
+
this.fire_hardware_id();
|
|
2501
|
+
}
|
|
2502
|
+
} else {
|
|
2503
|
+
this.stc_streak = 0;
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2372
2506
|
if (this.tracer) {
|
|
2373
2507
|
this.tracer("after");
|
|
2374
2508
|
if (this.paused)
|
|
@@ -2382,9 +2516,13 @@ class Runner {
|
|
|
2382
2516
|
on_terminate?.();
|
|
2383
2517
|
return;
|
|
2384
2518
|
}
|
|
2385
|
-
if (
|
|
2386
|
-
|
|
2387
|
-
|
|
2519
|
+
if (this.machine.memory.read_raw(this.machine.cpu.pc) === 118) {
|
|
2520
|
+
if (exit_on_halt) {
|
|
2521
|
+
on_terminate?.();
|
|
2522
|
+
return;
|
|
2523
|
+
}
|
|
2524
|
+
if (on_halt && on_halt())
|
|
2525
|
+
return;
|
|
2388
2526
|
}
|
|
2389
2527
|
}
|
|
2390
2528
|
const now = performance.now();
|
|
@@ -2407,6 +2545,13 @@ class Runner {
|
|
|
2407
2545
|
this.machine.cpu.jump(63488);
|
|
2408
2546
|
this.machine.keyboard.reset();
|
|
2409
2547
|
}
|
|
2548
|
+
fire_hardware_id() {
|
|
2549
|
+
const colorIdx = COLOR_MODES.indexOf(this.machine.screen.color_mode);
|
|
2550
|
+
this.machine.cpu.set_a(1);
|
|
2551
|
+
this.machine.cpu.set_b(colorIdx < 0 ? 0 : colorIdx);
|
|
2552
|
+
this.machine.cpu.set_c(this.turbo ? 1 : 0);
|
|
2553
|
+
this.machine.cpu.cf = 0;
|
|
2554
|
+
}
|
|
2410
2555
|
}
|
|
2411
2556
|
|
|
2412
2557
|
// src/lib/core/rk86_screen.ts
|
|
@@ -2427,6 +2572,9 @@ class Screen {
|
|
|
2427
2572
|
video_memory_base = 0;
|
|
2428
2573
|
video_memory_size = 0;
|
|
2429
2574
|
transparent_attr = false;
|
|
2575
|
+
char_height = 10;
|
|
2576
|
+
underline_scanline = 7;
|
|
2577
|
+
color_mode = DEFAULT_COLOR_MODE;
|
|
2430
2578
|
ready = false;
|
|
2431
2579
|
renderer;
|
|
2432
2580
|
constructor(machine) {
|
|
@@ -2486,6 +2634,11 @@ class Screen {
|
|
|
2486
2634
|
}
|
|
2487
2635
|
last_flip_ticks = 0;
|
|
2488
2636
|
tick_cursor(total_ticks, ticks_per_flip) {
|
|
2637
|
+
if (this.machine.runner.turbo) {
|
|
2638
|
+
this.cursor_state = true;
|
|
2639
|
+
this.last_flip_ticks = total_ticks;
|
|
2640
|
+
return;
|
|
2641
|
+
}
|
|
2489
2642
|
while (total_ticks - this.last_flip_ticks >= ticks_per_flip) {
|
|
2490
2643
|
this.cursor_state = !this.cursor_state;
|
|
2491
2644
|
this.last_flip_ticks += ticks_per_flip;
|
|
@@ -2502,7 +2655,7 @@ class Screen {
|
|
|
2502
2655
|
this.width = width;
|
|
2503
2656
|
this.height = height;
|
|
2504
2657
|
this.video_memory_size = width * height;
|
|
2505
|
-
this.machine.ui.update_screen_geometry(this.width, this.height);
|
|
2658
|
+
this.machine.ui.update_screen_geometry(this.width, this.height, this.char_height);
|
|
2506
2659
|
if (this.last_width === this.width && this.last_height === this.height)
|
|
2507
2660
|
return;
|
|
2508
2661
|
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}`);
|
|
@@ -2511,6 +2664,12 @@ class Screen {
|
|
|
2511
2664
|
if (this.last_video_memory_base !== -1)
|
|
2512
2665
|
this.ready = true;
|
|
2513
2666
|
}
|
|
2667
|
+
set_char_height(char_height) {
|
|
2668
|
+
if (this.char_height === char_height)
|
|
2669
|
+
return;
|
|
2670
|
+
this.char_height = char_height;
|
|
2671
|
+
this.machine.ui.update_screen_geometry(this.width, this.height, this.char_height);
|
|
2672
|
+
}
|
|
2514
2673
|
last_video_memory_base = -1;
|
|
2515
2674
|
set_video_memory(base) {
|
|
2516
2675
|
this.video_memory_base = base;
|
|
@@ -2848,16 +3007,17 @@ class TerminalRenderer {
|
|
|
2848
3007
|
const dim = "\x1B[2m";
|
|
2849
3008
|
const reset = "\x1B[0m";
|
|
2850
3009
|
const w = screen.width;
|
|
2851
|
-
const
|
|
3010
|
+
const mode = screen.color_mode;
|
|
2852
3011
|
let output = "\x1B[H";
|
|
2853
3012
|
output += `${dim}\u250C${"\u2500".repeat(w)}\u2510${reset}
|
|
2854
3013
|
`;
|
|
2855
3014
|
const transparent = screen.transparent_attr;
|
|
3015
|
+
const offset = hasCellOffset(mode) && !transparent;
|
|
2856
3016
|
const blinkOff = Math.floor(Date.now() / 320) % 2 === 1;
|
|
2857
3017
|
const FA_PENDING = -1;
|
|
2858
3018
|
let addr = screen.video_memory_base;
|
|
2859
3019
|
let frameStopped = false;
|
|
2860
|
-
let
|
|
3020
|
+
let latchedAttrs = 0;
|
|
2861
3021
|
let blink = false;
|
|
2862
3022
|
for (let y = 0;y < screen.height; y++) {
|
|
2863
3023
|
let line = `${dim}\u2502${reset}`;
|
|
@@ -2877,23 +3037,23 @@ class TerminalRenderer {
|
|
|
2877
3037
|
continue;
|
|
2878
3038
|
}
|
|
2879
3039
|
if (raw >= 240) {
|
|
2880
|
-
cells[cellCount++] = { ch: 0,
|
|
3040
|
+
cells[cellCount++] = { ch: 0, attrs: latchedAttrs, blink, isFA: false };
|
|
2881
3041
|
rowStopped = true;
|
|
2882
3042
|
if (raw >= 248)
|
|
2883
3043
|
frameStopped = true;
|
|
2884
3044
|
} else if (raw >= 192) {
|
|
2885
|
-
cells[cellCount++] = { ch: 0,
|
|
3045
|
+
cells[cellCount++] = { ch: 0, attrs: latchedAttrs, blink, isFA: false };
|
|
2886
3046
|
} else if (raw >= 128) {
|
|
2887
|
-
|
|
3047
|
+
latchedAttrs = raw;
|
|
2888
3048
|
blink = (raw & 2) !== 0;
|
|
2889
|
-
cells[cellCount++] = { ch: FA_PENDING,
|
|
3049
|
+
cells[cellCount++] = { ch: FA_PENDING, attrs: latchedAttrs, blink, isFA: true };
|
|
2890
3050
|
fifoFlag = true;
|
|
2891
3051
|
} else {
|
|
2892
|
-
cells[cellCount++] = { ch: raw,
|
|
3052
|
+
cells[cellCount++] = { ch: raw, attrs: latchedAttrs, blink, isFA: false };
|
|
2893
3053
|
}
|
|
2894
3054
|
}
|
|
2895
3055
|
while (cellCount < w)
|
|
2896
|
-
cells[cellCount++] = { ch: 0,
|
|
3056
|
+
cells[cellCount++] = { ch: 0, attrs: latchedAttrs, blink, isFA: false };
|
|
2897
3057
|
let fifoIdx = 0;
|
|
2898
3058
|
for (let x = 0;x < w; ++x) {
|
|
2899
3059
|
if (cells[x].ch === FA_PENDING) {
|
|
@@ -2910,6 +3070,7 @@ class TerminalRenderer {
|
|
|
2910
3070
|
for (let x = 0;x < w; x++) {
|
|
2911
3071
|
const raw = memory.read(addr + x);
|
|
2912
3072
|
let ch;
|
|
3073
|
+
let isFA = false;
|
|
2913
3074
|
if (rowStopped) {
|
|
2914
3075
|
ch = 0;
|
|
2915
3076
|
} else if (raw >= 240) {
|
|
@@ -2920,27 +3081,46 @@ class TerminalRenderer {
|
|
|
2920
3081
|
} else if (raw >= 192) {
|
|
2921
3082
|
ch = 0;
|
|
2922
3083
|
} else if (raw >= 128) {
|
|
2923
|
-
|
|
3084
|
+
latchedAttrs = raw;
|
|
2924
3085
|
blink = (raw & 2) !== 0;
|
|
2925
3086
|
ch = 0;
|
|
3087
|
+
isFA = true;
|
|
2926
3088
|
} else {
|
|
2927
3089
|
ch = raw;
|
|
2928
3090
|
}
|
|
2929
|
-
cells[x] = { ch,
|
|
3091
|
+
cells[x] = { ch, attrs: latchedAttrs, blink, isFA };
|
|
2930
3092
|
}
|
|
2931
3093
|
addr += w;
|
|
2932
3094
|
}
|
|
2933
|
-
let
|
|
3095
|
+
let prevAnsi = -1;
|
|
3096
|
+
let prevReverse = false;
|
|
3097
|
+
let prevUnderline = false;
|
|
2934
3098
|
for (let x = 0;x < w; x++) {
|
|
2935
3099
|
const cell = cells[x];
|
|
2936
3100
|
const ch = cell.blink && blinkOff ? 0 : cell.ch;
|
|
2937
3101
|
const glyph = rk86char(ch);
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
3102
|
+
let colorAttrs = cell.attrs;
|
|
3103
|
+
if (offset && x + 1 < w && cells[x + 1].isFA) {
|
|
3104
|
+
colorAttrs = cells[x + 1].attrs;
|
|
3105
|
+
}
|
|
3106
|
+
const ansi = rgbToAnsiBaseFg(attrToRgb(mode, colorAttrs));
|
|
3107
|
+
const reverse = !cell.isFA && (cell.attrs & 16) !== 0;
|
|
3108
|
+
const underline = !cell.isFA && (cell.attrs & 32) !== 0;
|
|
3109
|
+
if (ansi !== prevAnsi) {
|
|
3110
|
+
line += `\x1B[${ansi}m`;
|
|
3111
|
+
prevAnsi = ansi;
|
|
3112
|
+
}
|
|
3113
|
+
if (reverse !== prevReverse) {
|
|
3114
|
+
line += reverse ? `\x1B[7m` : `\x1B[27m`;
|
|
3115
|
+
prevReverse = reverse;
|
|
3116
|
+
}
|
|
3117
|
+
if (underline !== prevUnderline) {
|
|
3118
|
+
line += underline ? `\x1B[4m` : `\x1B[24m`;
|
|
3119
|
+
prevUnderline = underline;
|
|
2941
3120
|
}
|
|
2942
3121
|
if (x === screen.cursor_x && y === screen.cursor_y) {
|
|
2943
3122
|
line += `\x1B[4m${glyph}\x1B[24m`;
|
|
3123
|
+
prevUnderline = false;
|
|
2944
3124
|
} else {
|
|
2945
3125
|
line += glyph;
|
|
2946
3126
|
}
|
|
@@ -3106,6 +3286,8 @@ function printHelp() {
|
|
|
3106
3286
|
--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
|
|
3107
3287
|
--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,...
|
|
3108
3288
|
\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)
|
|
3289
|
+
--color <0|1|2|3> \u0440\u0435\u0436\u0438\u043C \u0446\u0432\u0435\u0442\u0430: 0=\u0447/\u0431 (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E), 1=\u0422\u043E\u043B\u043A\u0430\u043B\u0438\u043D,
|
|
3290
|
+
2=\u0410\u043A\u0438\u043C\u0435\u043D\u043A\u043E, 3=\u0410\u043F\u043E\u0433\u0435\u0439
|
|
3109
3291
|
--online \u043E\u0442\u043A\u0440\u044B\u0442\u044C \u0432 \u043E\u043D\u043B\u0430\u0439\u043D-\u044D\u043C\u0443\u043B\u044F\u0442\u043E\u0440\u0435 rk86.ru
|
|
3110
3292
|
|
|
3111
3293
|
\u041F\u0440\u0438\u043C\u0435\u0440\u044B:
|
|
@@ -3210,6 +3392,8 @@ async function main() {
|
|
|
3210
3392
|
const screenFile = arg(args, "--screen");
|
|
3211
3393
|
const snapshotFile = arg(args, "--snapshot");
|
|
3212
3394
|
const goViaMonitor = arg(args, "-G", undefined, addrRe, parseAddr);
|
|
3395
|
+
const colorIdx = arg(args, "--color", "0", /^[0-3]$/, (v) => parseInt(v, 10)) ?? 0;
|
|
3396
|
+
const colorMode = COLOR_MODES[colorIdx];
|
|
3213
3397
|
let inputSeq = arg(args, "--input");
|
|
3214
3398
|
if (goViaMonitor !== undefined) {
|
|
3215
3399
|
const hex2 = goViaMonitor.toString(16).toUpperCase();
|
|
@@ -3229,8 +3413,11 @@ async function main() {
|
|
|
3229
3413
|
const machine = machineBuilder;
|
|
3230
3414
|
machine.ui = new TerminalUI;
|
|
3231
3415
|
machine.memory = new Memory(machine);
|
|
3416
|
+
io.input = (port) => machine.memory.read(port | port << 8);
|
|
3417
|
+
io.output = (port, w8) => machine.memory.write(port | port << 8, w8);
|
|
3232
3418
|
machine.cpu = new I8080(machine);
|
|
3233
3419
|
machine.screen = new Screen(machine);
|
|
3420
|
+
machine.screen.color_mode = colorMode;
|
|
3234
3421
|
machine.tape = new Tape(machine);
|
|
3235
3422
|
machine.runner = new Runner(machine);
|
|
3236
3423
|
machine.memory.update_ruslat = machine.ui.update_ruslat;
|