ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/vt102/vt102
Revision: 1.20
Committed: Wed Dec 3 08:17:16 2014 UTC (9 years, 5 months ago) by root
Branch: MAIN
Changes since 1.19: +4 -1 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 #!/opt/bin/perl
2    
3 root 1.2 #
4     # Copyright(C) 2014 Marc Alexander Lehmann <vt102@schmorp.de>
5     #
6     # vt102 is free software; you can redistribute it and/or modify it under
7     # the terms of the GNU General Public License as published by the Free
8     # Software Foundation; either version 3, or (at your option) any later
9     # version.
10     #
11     # vt102 is distributed in the hope that it will be useful, but WITHOUT
12     # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13     # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14     # for more details.
15     #
16    
17     # If this file contains embedded ROMs, the above copyright notice does
18     # not apply to them.
19    
20 root 1.9 use strict;
21     #use common::sense;
22 root 1.1
23 root 1.3 my $VT102 = 1;
24 root 1.10 my $VT131 = 0;
25     my $AVO = 1;
26 root 1.1
27 root 1.13 shift, ($VT102 = 0), ($AVO = 0) if $ARGV[0] =~ /^-?-vt100$/;
28     shift, ($VT102 = 0) if $ARGV[0] =~ /^-?-vt100\+avo$/;
29     shift if $ARGV[0] =~ /^-?-vt102$/;
30     shift, ($VT131 = 1) if $ARGV[0] =~ /^-?-vt131$/;
31 root 1.10
32 root 1.19 # vt100 wps = word processing roms
33     # vt101 = vt102 - avo, but custom rom? really?
34     # vt103 = vt100 + tu58 tape drive
35     # vt125 = vt100 + gpo graphics processor
36     # vt132 = vt100 + avo, stp
37     # vt180 = vt100 + z80 cp/m
38    
39 root 1.10 if ($ARGV[0] =~ /^-/) {
40     die <<EOF;
41    
42 root 1.19 VT102, A VT100/102/131 SIMULATOR
43 root 1.10
44     Usage:
45    
46     $0 [option] [program [args]]
47    
48     Examples:
49    
50     $0 bash
51     $0 telnet towel.blinkenlights.nl
52     $0 curl http://artscene.textfiles.com/vt100/trekvid.vt
53     $0 curl http://artscene.textfiles.com/vt100/surf.vt # in 3d!
54    
55     Option can be one of:
56    
57     --vt100
58     --vt100+avo
59     --vt102
60     --vt131
61    
62     Non-obvious special keys are:
63    
64     SET UP Home
65     BACKSPACE Rubout
66     CAPS LOCK Prior/PgUp
67     NO SCROLL Next/PgDown
68     BREAK End
69    
70     Set-Up Guide:
71    
72     http://vt100.net/docs/vt102-ug/chapter3.html#S3.6
73    
74     Author:
75    
76     Marc Lehmann <vt102\@schmorp.de>
77    
78     EOF
79     }
80    
81 root 1.1 #############################################################################
82 root 1.10 # ROM/hardware init
83 root 1.1
84 root 1.15 my $PTY; # the pty we allocated, if any
85     my $KBD = 1;
86    
87 root 1.10 my $ROMS = do {
88 root 1.1 binmode DATA;
89     local $/;
90     <DATA>
91     };
92    
93 root 1.10 0x6801 == length $ROMS or die "corrupted rom image";
94 root 1.1
95     my @M = (0xff) x 65536; # main memory, = (0xff) x 65536;
96    
97     # populate mem with rom contents
98     if ($VT102) {
99 root 1.10 @M[0x0000 .. 0x1fff] = unpack "C*", substr $ROMS, 0x2000, 0x2000;
100     @M[0x8000 .. 0x9fff] = unpack "C*", substr $ROMS, 0x4000, 0x2000;
101     @M[0xa000 .. 0xa7ff] = unpack "C*", substr $ROMS, 0x6000, 0x0800 if $VT131;
102 root 1.1 } else {
103 root 1.10 @M[0x0000 .. 0x1fff] = unpack "C*", substr $ROMS, 0x0000, 0x2000;
104 root 1.1 }
105    
106     #############################################################################
107 root 1.10 # 8085 CPU registers and I/O support
108 root 1.1
109     # 8080/8085 registers
110 root 1.16 my ($A, $B, $C, $D, $E, $H, $L);
111 root 1.1 my ($PC, $SP, $IFF, $FA, $FZ, $FS, $FP, $FC);
112    
113     my $RST = 0; # 8080 pending interrupts
114     my $INTMASK = 7; # 8085 half interrupts
115     my $INTPEND = 0; # 8085 half interrupts
116    
117     my $CLK; # rather inexact clock
118    
119     #############################################################################
120 root 1.10 # the dreaded NVR1400 chip. not needed to get it going, but provided anyway
121 root 1.1
122     # nvram
123     my @NVR = (0x3fff) x 100; # vt102: 214e accum, 214f only lower 8 bit used, first 44 bytes
124     my $NVRADDR;
125     my $NVRDATA;
126     my $NVRLATCH;
127    
128     my @NVRCMD = (
129 root 1.5 sub { $NVRDATA = ($NVRDATA << 1) + $_[1]; }, # 0 accept data
130     sub { $NVRADDR = ($NVRADDR << 1) + $_[1]; }, # 1 accept addr
131     sub { ($NVRDATA <<= 1) & 0x4000 }, # 2 shift out
132     undef, # 3 not used, will barf
133     sub { $NVR[$_[0]] = $NVRDATA & 0x3fff; }, # 4 write
134     sub { $NVR[$_[0]] = 0x3fff; }, # 5 erase
135     sub { $NVRDATA = $NVR[$_[0]]; }, # 6 read
136     sub { }, # 7 standby
137 root 1.1 );
138    
139 root 1.15 my @NVR_BITIDX;
140     $NVR_BITIDX[1 << $_] = 9 - $_ for 0..9;
141 root 1.1
142     # the nvr1400 state machine. what a monster
143     sub nvr() {
144 root 1.15 my $a1 = $NVR_BITIDX[(~$NVRADDR ) & 0x3ff];
145     my $a0 = $NVR_BITIDX[(~$NVRADDR >> 10) & 0x3ff];
146 root 1.1
147     # printf "NVR %02x A %020b %d %d D %02x\n", $NVRLATCH, $NVRADDR & 0xfffff, $a1, $a0, $NVRDATA;
148    
149     $NVRCMD[($NVRLATCH >> 1) & 7]($a1 * 10 + $a0, $NVRLATCH & 1)
150     }
151    
152     #############################################################################
153 root 1.10 # I/O ports - output
154 root 1.1
155 root 1.6 my $DC11_REVERSE = 0;
156 root 1.1
157     my $XON = 1; # false if terminal wants us to pause
158     my $PUSARTCMD;
159    
160     my @KXMIT; # current scan queue
161     my %KXMIT; # currently pressed keys
162     my @KQUEUE; # key event queue
163     my $KXCNT; # count for debouncew
164     my @PUSARTRECV;
165     my $KSTATUS;
166    
167     sub out_00 { # pusartdata
168     # handle xon/xoff, but also pass it through
169     if ($_[0] == 0x13) {
170     $XON = 0;
171 root 1.3 return;#d#
172 root 1.1 } elsif ($_[0] == 0x11) {
173     $XON = 1;
174 root 1.3 return;#d#
175 root 1.1 }
176    
177     syswrite $PTY, chr $_[0];
178    
179 root 1.3 $INTPEND |= 1;
180 root 1.1 }
181    
182     sub out_01 {
183     $PUSARTCMD = shift;
184    
185     $INTPEND |= 1 if $PUSARTCMD & 0x01; # VT102, 5.5 txrdy
186 root 1.3 $INTPEND |= 2 if $PUSARTCMD & 0x04 && !@PUSARTRECV; # VT102, 6.5 rxrdy, needed for some reason
187 root 1.1 }
188    
189     sub out_02 { } # baudrate generator
190    
191     sub out_23 { } # unknown
192     sub out_27 { } # unknown
193     sub out_2f { } # unknown, connected to in 0f
194    
195     sub out_42 { } # brightness
196    
197     sub out_62 {
198     $NVRLATCH = shift;
199     }
200    
201 root 1.6 sub out_a2 {
202     my $dc11 = 0x0f & shift;
203    
204     $DC11_REVERSE = 1 if $dc11 == 0b1010;
205     $DC11_REVERSE = 0 if $dc11 == 0b1011;
206     }
207    
208 root 1.1 sub out_c2 { } # unknown
209 root 1.6 sub out_d2 { } # 0..3 == 80c/132c/60hz/50hz
210 root 1.1
211     sub out_82 {
212     # keyboard
213    
214 root 1.10 # CLICK STARTSCAN ONLINE LOCKED | CTS DSR INS L1
215     # CLICK STARTSCAN ONLINE LOCKED | L1 L2 L3 L4
216 root 1.1 $KSTATUS = $_[0];
217    
218     # start new scan unless scan in progress
219     if (($_[0] & 0x40) && !@KXMIT) {
220     # do not reply with keys in locked mode
221     # or during post (0xff),
222     # mostly to skip init and not fail POST,
223     # and to send startup keys only when terminal is ready
224     unless (($_[0] & 0x10) || ($_[0] == 0xff) || ($VT102 && $INTMASK == 0x07)) {
225     if ($KXCNT <= 0 && @KQUEUE) {
226     my $c = shift @KQUEUE;
227    
228     if ($c < 0) { # key up
229     delete $KXMIT{-$c};
230     $KXCNT = 10;
231     } elsif ($c > 0) { # key down
232     undef $KXMIT{$c};
233     $KXCNT = 10;
234     } else { # delay
235     $KXCNT = 100;
236     }
237     }
238    
239     --$KXCNT;
240     @KXMIT = sort keys %KXMIT;
241     }
242    
243     $RST |= 1;
244     }
245     }
246    
247     #############################################################################
248 root 1.10 # I/O ports - input
249 root 1.1
250     my $NVRBIT;
251 root 1.10 my $LBA6; # twice the frequenxy of LBA7
252 root 1.1
253     sub in_00 { # pusart data
254 root 1.3 # interrupt not generated here, because infinite
255     # speed does not go well with the vt102.
256 root 1.1
257     shift @PUSARTRECV
258     }
259    
260     sub in_01 { # pusart status
261     # DSR SYNDET FE OE | PE TXEMPTY RXRDY TXRDY
262     0x85 + (@PUSARTRECV && 0x02)
263     }
264    
265     sub in_22 { # modem buffer(?)
266     # wild guess: -CTS -SPDI -RI -CD 0 0 0 0
267     0x20
268     }
269    
270 root 1.10 sub in_0f { 0xff } # vt102 unknown, connected to out 2f
271 root 1.1
272     sub in_42 { # flag buffer
273 root 1.10 ++$LBA6;
274 root 1.1
275 root 1.10 $NVRBIT = nvr ? 0x20 : 0x00 if ($LBA6 & 0x3) == 0x2;
276 root 1.1
277     # KBD_XMITEMPTY LBA7 NVRDATA ODDFIELD - OPTION !GFX !AVO PUSART_TXRDY
278    
279     my $f = 0x85 | $NVRBIT;
280    
281     $f |= 0x02 unless $AVO;
282 root 1.10 $f |= 0x40 if $LBA6 & 0x2;
283 root 1.1
284     $f
285     }
286    
287     sub in_82 { # tbmt keyboard uart
288     return 0x7f unless @KXMIT;
289    
290     $RST |= 1;
291     shift @KXMIT
292     }
293    
294 root 1.10 sub in_03 { 0xff } # vt102 unknown, printer uart input?
295     sub in_0b { 0xff } # vt102 unknown
296     sub in_17 { 0xff } # vt102 unknown, printer status clear by reading?
297     sub in_1b { 0xff } # vt102 unknown
298 root 1.1
299     #############################################################################
300 root 1.10 # 8085 cpu opcodes and flag handling
301 root 1.1
302 root 1.17 my $x; # dummy scratchpad for opcodes
303    
304 root 1.3 sub sf { # set flags (ZSC - AP not implemented)
305 root 1.10 $FS = $_[0] & 0x080;
306     $FZ = !($_[0] & 0x0ff);
307     $FC = $_[0] & 0x100;
308    
309     $_[0] &= 0xff;
310     }
311 root 1.1
312 root 1.10 sub sf8 { # set flags (ZSC - AP not implemented)
313     $FS = $_[0] & 0x080;
314     $FZ = !($_[0] & 0x0ff);
315     $FC = 0;
316 root 1.1 }
317    
318 root 1.3 sub sf_nc { # set flags except carry
319 root 1.1 $FS = $_[0] & 0x080;
320     $FZ = ($_[0] & 0x0ff) == 0;
321    
322 root 1.10 $_[0] &= 0xff;
323 root 1.1 }
324    
325 root 1.10 my @op = map { sprintf "status(); die 'unknown op %02x'", $_ } 0x00 .. 0xff;
326 root 1.1
327     my @reg = qw($B $C $D $E $H $L $M[$H*256+$L] $A);
328     my @cc = ('if !$FZ', 'if $FZ', 'if !$FC', 'if $FC', ';die', ';die', 'if !$FS', 'if $FS'); # die == unimplemented $FP parity
329    
330 root 1.10 $op[0x00] = '';
331    
332 root 1.1 # mov r,r / r,M / M,r
333     for my $s (0..7) {
334     for my $d (0..7) {
335 root 1.10 $op[0x40 + $d * 8 + $s] = "$reg[$d] = $reg[$s]"; # mov
336 root 1.1 }
337     }
338    
339 root 1.10 $op[0x76] = 'die "HLT"'; # hlt (mov m,m)
340    
341     # mvi r / M
342     $op[0x06 + $_ * 8] = "$reg[$_] = IMM8" for 0..7;
343 root 1.1
344     $op[0x01] = '($B, $C) = (IMM16 >> 8, IMM16 & 0xff)'; # lxi
345     $op[0x11] = '($D, $E) = (IMM16 >> 8, IMM16 & 0xff)'; # lxi
346     $op[0x21] = '($H, $L) = (IMM16 >> 8, IMM16 & 0xff)'; # lxi
347 root 1.10 $op[0x31] = '$SP = IMM16' ; # lxi
348 root 1.1
349     $op[0x02] = '$M[$B * 256 + $C] = $A'; # stax
350     $op[0x12] = '$M[$D * 256 + $E] = $A'; # stax
351     $op[0x32] = '$M[IMM16 ] = $A'; # sta
352    
353 root 1.10 $op[0x0a] = '$A = $M[$B * 256 + $C]'; # ldax b
354     $op[0x1a] = '$A = $M[$D * 256 + $E]'; # ldax d
355     $op[0x3a] = '$A = $M[IMM16]'; # lda
356    
357     $op[0x22] = '($M[IMM16], $M[IMM16 + 1]) = ($L, $H)'; # shld
358     $op[0x2a] = '($L, $H) = ($M[IMM16], $M[IMM16 + 1])'; # lhld
359    
360 root 1.1 sub inxdcx($$$) {
361 root 1.10 $x = $_[0] * 256 + $_[1] + $_[2];
362     ($_[0], $_[1]) = (($x >> 8) & 0xff, $x & 0xff);
363 root 1.1 }
364    
365     $op[0x03] = 'inxdcx $B, $C, 1'; # inx
366     $op[0x13] = 'inxdcx $D, $E, 1'; # inx
367     $op[0x23] = 'inxdcx $H, $L, 1'; # inx
368     $op[0x33] = '$SP++' ; # inx
369     $op[0x0b] = 'inxdcx $B, $C, -1'; # dcx
370     $op[0x1b] = 'inxdcx $D, $E, -1'; # dcx
371     $op[0x2b] = 'inxdcx $H, $L, -1'; # dcx
372     $op[0x3b] = '--$SP' ; # dcx
373    
374     # "no carry" doesn't seem to be needed for vt100 - optimize?
375 root 1.10 $op[0x04 + $_ * 8] = "sf_nc ++$reg[$_]" for 0..7; # inr
376     $op[0x05 + $_ * 8] = "sf_nc --$reg[$_]" for 0..7; # dcr
377    
378     $op[0x07] = ' $FC = $A & 0x80; $A = (($A << 1) + ($FC && 0x01)) & 0xff '; # rlc
379     $op[0x17] = ' ($FC, $A) = ($A & 0x80, (($A << 1) + ($FC && 0x01)) & 0xff)'; # ral
380    
381     $op[0x0f] = ' $FC = $A & 0x01; $A = ($A >> 1) | ($FC && 0x80) '; # rrc
382     $op[0x1f] = ' ($FC, $A) = ($A & 0x01, ($A >> 1) | ($FC && 0x80))'; # rar
383 root 1.1
384 root 1.10 $op[0x2f] = '$A ^= 0xff'; # cma
385 root 1.1
386     # getting this insn wrong (its the only 16 bit insn to modify flags)
387     # wasted three of my best days with mindless vt102 rom reverse engineering
388     sub dad {
389     $x = $H * 256 + $L + $_[0];
390    
391     ($H, $L) = (($x >> 8) & 0xff, $x & 0xff);
392     $FC = $x > 0xffff;
393     }
394    
395     $op[0x09] = 'dad $B * 256 + $C'; # dad
396     $op[0x19] = 'dad $D * 256 + $E'; # dad
397     $op[0x29] = 'dad $H * 256 + $L'; # dad
398     $op[0x39] = 'dad $SP '; # dad
399    
400 root 1.10 $op[0x80 + $_] = 'sf $A += + ' . $reg[$_] for 0..7; # add
401     $op[0x88 + $_] = 'sf $A += ($FC && 1) + ' . $reg[$_] for 0..7; # adc
402     $op[0x90 + $_] = 'sf $A -= + ' . $reg[$_] for 0..7; # sub
403     $op[0x98 + $_] = 'sf $A -= ($FC && 1) + ' . $reg[$_] for 0..7; # sbb
404     $op[0xa0 + $_] = 'sf8 $A &= ' . $reg[$_] for 0..7; # ana
405     $op[0xa8 + $_] = 'sf8 $A ^= ' . $reg[$_] for 0..7; # xra
406     $op[0xb0 + $_] = 'sf8 $A |= ' . $reg[$_] for 0..7; # ora
407     $op[0xb8 + $_] = 'sf $x = $A - ' . $reg[$_] for 0..7; # cmp
408     # possible todo: optimize ora a, maybe xra a
409 root 1.1
410 root 1.10 $op[0xc6] = 'sf $A += IMM8'; # adi
411 root 1.19 # ce ACI NYI, apparently unused
412 root 1.10 $op[0xd6] = 'sf $A -= IMM8'; # sui
413 root 1.19 # de SBI NYI, apparently unused
414 root 1.10 $op[0xe6] = 'sf8 $A &= IMM8'; # ani
415     $op[0xee] = 'sf8 $A ^= IMM8'; # xri
416     $op[0xf6] = 'sf8 $A |= IMM8'; # ori
417     $op[0xfe] = 'sf $A - IMM8'; # cpi
418 root 1.1
419 root 1.10 $op[0xc5] = 'PUSH $B; PUSH $C';
420     $op[0xd5] = 'PUSH $D; PUSH $E';
421     $op[0xe5] = 'PUSH $H; PUSH $L';
422     $op[0xf5] = 'PUSH $A; PUSH +($FS && 0x80) | ($FZ && 0x40) | ($FA && 0x10) | ($FP && 0x04) | ($FC && 0x01)'; # psw
423 root 1.1
424     $op[0xc1] = '($C, $B) = (POP, POP)'; # pop
425     $op[0xd1] = '($E, $D) = (POP, POP)'; # pop
426     $op[0xe1] = '($L, $H) = (POP, POP)'; # pop
427 root 1.10 $op[0xf1] = '($x, $A) = (POP, POP); ($FS, $FZ, $FA, $FP, $FC) = ($x & 0x80, $x & 0x40, $x & 0x10, $x & 0x04, $x & 0x01)'; # pop psw
428 root 1.1
429     $op[0xc2 + $_ * 8] = 'BRA IMM16 ' . $cc[$_] for 0..7; # jcc
430     $op[0xc3] = 'JMP IMM16'; # jmp
431    
432     $op[0xc4 + $_ * 8] = '(PUSH PC >> 8), (PUSH PC & 0xff), (BRA IMM16) ' . $cc[$_] for 0..7; # ccc
433     $op[0xcd] = '(PUSH PC >> 8), (PUSH PC & 0xff), (BRA IMM16)'; # call
434    
435 root 1.10 $op[0xc0 + $_ * 8] = 'BRA POP + POP * 256 ' . $cc[$_] for 0..7; # rcc
436     $op[0xc9] = 'JMP POP + POP * 256'; # ret
437    
438 root 1.1 $op[0xc7 + $_ * 8] = "JMP $_ * 8" for 0..7; # rst
439    
440 root 1.10 $op[0xe9] = 'JMP $H * 256 + $L'; # pchl
441 root 1.19 # f9 SPHL NYI, apparently unused
442 root 1.1
443 root 1.10 $op[0x37] = '$FC = 1 '; # stc
444     $op[0x3f] = '$FC = !$FC'; # cmc
445 root 1.1
446     $op[0xd3] = 'OUT'; # out
447     $op[0xdb] = 'IN'; # in
448    
449 root 1.10 $op[0xeb] = '($D, $E, $H, $L) = ($H, $L, $D, $E)'; # xchg
450    
451 root 1.19 # e3 xthl NYI # @ 917b in e69, hl <-> (sp)
452 root 1.1
453 root 1.10 $op[0x20] = '$A = $INTPEND * 16 + $INTMASK + ($IFF && 8)'; # rim (incomplete)
454     $op[0x30] = '$INTMASK = $A & 7 if $A & 8'; # sim (incomplete)
455 root 1.1
456     $op[0xf3] = '$IFF = 0'; # DI
457     $op[0xfb] = '$IFF = 1'; # EI
458    
459 root 1.10 # yeah, the fucking setup screens actually use daa...
460     $op[0x27] = '
461     my ($h, $l);
462    
463     ($h, $l) = ($A >> 4, $A & 15);
464    
465     if ($l > 9 || $FA) {
466     sf $A += 6;
467     ($h, $l) = ($A >> 4, $A & 15);
468     }
469    
470     if ($h > 9 || $FC) {
471     $h += 6;
472     $A = ($h * 16 + $l) & 0xff;
473     }
474     '; # daa, almost certainly borked, also, acarry not set by sf
475 root 1.1
476     #############################################################################
477 root 1.10 # print cpu status for debugging purposes
478 root 1.1
479     # print cpu status, for debugging
480     sub status {
481     my $PC = shift || $PC;
482    
483     printf "%04x/%04x A=%02x BC=%02x:%02x DE=%02x:%02x HL=%02x:%02x ZSCAP=%s: %02x %s\n",
484     $PC, $SP,
485     $A, $B, $C, $D, $E, $H, $L,
486     ($FZ ? "1" : "0")
487     . ($FS ? "1" : "0")
488     . ($FC ? "1" : "0")
489     . ($FA ? "1" : "0")
490     . ($FP ? "1" : "0"),
491 root 1.10 $M[$PC], $op[$M[$PC]];
492 root 1.1 }
493    
494     #############################################################################
495 root 1.10 # video emulation
496 root 1.1
497 root 1.14 binmode STDOUT;
498    
499 root 1.10 my @CHARMAP = (
500 root 1.5 " " , "\x{29eb}", "\x{2592}", "\x{2409}",
501     "\x{240c}", "\x{240d}", "\x{240a}", "\x{00b0}",
502     "\x{00b1}", "\x{2424}", "\x{240b}", "\x{2518}",
503     "\x{2510}", "\x{250c}", "\x{2514}", "\x{253c}",
504     "\x{23ba}", "\x{23bb}", "\x{2500}", "\x{23bc}",
505     "\x{23bd}", "\x{251c}", "\x{2524}", "\x{2534}",
506     "\x{252c}", "\x{2502}", "\x{2264}", "\x{2265}",
507     "\x{03c0}", "\x{2260}", "\x{00a3}", "\x{00b7}",
508 root 1.1 (map chr, 0x020 .. 0x7e),
509     );
510    
511 root 1.10 utf8::encode $_ for @CHARMAP;
512 root 1.1
513 root 1.10 my @SGR; # sgr sequences for attributes
514 root 1.6
515     for (0x00 .. 0xff) {
516     my $sgr = "";
517    
518 root 1.10 # ~1 sgr 5 blink
519     # ~2 sgr 4 underline
520     # ~4 sgr 1 bold
521     # 0x80 in attr, sgr 7, reversed
522    
523 root 1.6 $sgr .= ";5" unless $_ & 0x01;
524     $sgr .= ";4" unless $_ & 0x02;
525     $sgr .= ";1" unless $_ & 0x04;
526     $sgr .= ";7" if $_ & 0x80;
527    
528 root 1.10 $SGR[$_] = "\e[${sgr}m";
529 root 1.6 }
530    
531 root 1.10 my @LED = $VT102
532     ? qw(L1 INSERT DSR CTS LOCKED LOCAL SCAN BEEP)
533     : qw(L4 L3 L2 L1 LOCKED LOCAL SCAN BEEP);
534    
535     # display screen
536     sub display {
537 root 1.1 my $i = 0x2000;
538    
539 root 1.10 my $leds = join " ", map $KSTATUS & 2**$_ ? "\e[7m$LED[$_]\e[m" : "$LED[$_]", reverse 0 .. $#LED;
540    
541     my $scr = sprintf "\e[H--- LED [ %s ] CLK %d\e[K\n", $leds, $CLK;
542 root 1.6
543     $scr .= "\e[?5" . ($DC11_REVERSE ? "h" : "l");
544 root 1.5
545 root 1.1 line:
546 root 1.6 for my $y (0 .. 25) { # ntsc, two vblank delay lines, up to 24 text lines
547     my $prev_sgr;
548    
549 root 1.9 $scr .= sprintf "%2d \xe2\x94\x82", $y;
550 root 1.1
551 root 1.6 for (0..139) {
552     my $c = $M[$i];
553 root 1.1
554     if ($c == 0x7f) { # also 0xff, but the firmware avoids that
555 root 1.9 $scr .= "\e[m\xe2\x94\x82\e[K\n";
556 root 1.1
557 root 1.6 my $a1 = $M[$i + 1];
558     my $a0 = $M[$i + 2];
559 root 1.1
560     $i = 0x2000 + (($a1 * 256 + $a0) & 0xfff);
561    
562     next line;
563     }
564    
565 root 1.10 my $sgr = $SGR[ ($M[$i++ + 0x1000] & 15) | ($c & 0x80)];
566 root 1.6
567     $scr .= $prev_sgr = $sgr if $sgr ne $prev_sgr;
568    
569 root 1.10 $scr .= $CHARMAP[$c & 0x7f];
570 root 1.1 }
571    
572 root 1.6 $scr .= "\e[K\nvideo overflow\e[K\n";
573 root 1.1 last;
574     }
575    
576 root 1.10 $scr .= "\e[m\e[J";
577 root 1.1
578     syswrite STDOUT, $scr;
579     }
580    
581     #############################################################################
582 root 1.10 # keyboard handling
583 root 1.1
584 root 1.7 # 0x080 shift, 0x100 ctrl
585 root 1.1 my %KEYMAP = (
586 root 1.8 "\t" => 0x3a,
587     "\r" => 0x64,
588     "\n" => 0x44,
589    
590     "\x00" => 0x77 | 0x100, # CTRL-SPACE
591     "\x1c" => 0x45 | 0x100, # CTRL-\
592     "\x1d" => 0x14 | 0x100, # CTRL-]
593     "\x1e" => 0x24 | 0x100, # CTRL-~
594     "\x1f" => 0x75 | 0x100, # CTRL-?
595    
596     # hardcoded rxvt keys
597     "\e" => 0x2a, # ESC
598     "\e[3~" => 0x03, # DC
599     "\e[5~" => 0x7e, # CAPS LOCK (prior)
600     "\e[6~" => 0x6a, # NO SCROLL (next)
601     "\e[A" => 0x30, # UP
602     "\e[B" => 0x22, # DOWN
603     "\e[C" => 0x10, # RIGHT
604     "\e[D" => 0x20, # LEFT
605     "\e[a" => 0x30 | 0x080, # UP
606     "\e[b" => 0x22 | 0x080, # DOWN
607     "\e[c" => 0x10 | 0x080, # RIGHT
608     "\e[d" => 0x20 | 0x080, # LEFT
609     "\e[7~" => 0x7b, # SETUP (home)
610     "\e[8~" => 0x23, # BREAK (end)
611     "\e[8\$" => 0x23 | 0x080, # SHIFT BREAK / DISCONNECT (shift-end)
612     "\x7f" => 0x33, # BACKSPACE
613    
614     "\e[11~" => 0x32, # F1
615     "\e[11~" => 0x42, # F2
616     "\e[11~" => 0x31, # F3
617     "\e[11~" => 0x41, # F4
618 root 1.1 );
619    
620 root 1.10 @KEYMAP{map chr, 0x20 .. 0x40, 0x5b .. 0x7e} = unpack "C*", pack "H*",
621     "779ad5a9a8b8a755a6b5b6b466256575" . "351a3929283837273626d656e634e5f5" . "b9" # 20..40
622     . "154514b7a5" . "244a6879591949485816574746766706" . "050a185a0817780969077a95c594a4"; # 5b..7e
623 root 1.1
624 root 1.7 $KEYMAP{"\x1f" & $_} ||= $KEYMAP{$_} | 0x100 for "a" .. "z"; # ctrl
625 root 1.10 $KEYMAP{"\x20" ^ $_} ||= $KEYMAP{$_} | 0x080 for "a" .. "z"; # shift
626 root 1.1
627     my $KEYMATCH = join "|", map quotemeta, reverse sort keys %KEYMAP;
628     $KEYMATCH = qr{^($KEYMATCH)}s;
629    
630 root 1.10 my %KMOD;
631    
632 root 1.1 sub key {
633     my ($key) = @_;
634    
635 root 1.10 push @KQUEUE, -0x7c if !($key & 0x100) && delete $KMOD{0x7c}; # ctrl-up
636     push @KQUEUE, -0x7d if !($key & 0x080) && delete $KMOD{0x7d}; # shift-up
637 root 1.1
638 root 1.10 push @KQUEUE, 0x7c if $key & 0x100 && !$KMOD{0x7c}++; # ctrl-down
639     push @KQUEUE, 0x7d if $key & 0x080 && !$KMOD{0x7d}++; # shift-down
640 root 1.1
641     $key &= 0x7f;
642     push @KQUEUE, $key, -$key;
643     }
644    
645     my $STDIN_BUF;
646    
647     sub stdin_parse {
648     key $KEYMAP{$1}
649     while $STDIN_BUF =~ s/$KEYMATCH//;
650    
651     # skip input we can't decipher
652     substr $STDIN_BUF, 0, 1, "";
653     }
654    
655     if ($KBD) {
656     system "stty -icanon -icrnl -inlcr -echo min 1 time 0";
657     eval q{ sub END { system "stty sane" } };
658     $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = sub { exit 1 };
659     }
660    
661     #############################################################################
662 root 1.10 # initial key input, to set up online mode etc.
663     # could be done via nvram defaults
664    
665     @KQUEUE = (
666     0x7b, -0x7b, # setup
667     0, # delay
668     0x28, -0x28, # 4, toggle local/online
669     0x38, -0x38, # 5, setup b
670     0, # delay
671     (0x10, -0x10) x 2, # cursor right
672     0x37, -0x37, # 6 toggle soft scroll
673     (0x10, -0x10) x 1, # cursor right
674     0x37, -0x37, # 6 toggle autorepeat off
675     (0x10, -0x10) x 8, # cursor right
676     0x37, -0x37, # 6 toggle keyclick
677     (0x10, -0x10) x 1, # cursor right
678     $VT102 ? () : (0x37, -0x37), # 6 toggle ansi/vt52
679     (0x10, -0x10) x 7, # cursor right
680     0x37, -0x37, # 6 toggle wrap around
681     0x7b, -0x7b, # leave setup
682     );
683    
684     #############################################################################
685     # process/pty management
686    
687     require IO::Pty;
688     $PTY = IO::Pty->new;
689    
690     my $slave = $PTY->slave;
691    
692     $PTY->set_winsize (24, 80);
693    
694     unless (fork) {
695     $ENV{TERM} = $VT102 ? "vt102" : "vt100";
696    
697     close $PTY;
698    
699     open STDIN , "<&", $slave;
700     open STDOUT, ">&", $slave;
701     open STDERR, ">&", $slave;
702    
703     system "stty ixoff erase ^H";
704    
705     $PTY->make_slave_controlling_terminal;
706     $PTY->close_slave;
707    
708     @ARGV = "sh" unless @ARGV;
709     exec @ARGV;
710     }
711    
712     $PTY->close_slave;
713    
714     #############################################################################
715     # the actual hardware simulator
716 root 1.1
717     my @ICACHE; # compiled instruction cache
718    
719     while () {
720     # execute extended basic blocks
721     $PC = ($ICACHE[$PC] ||= do {
722     my $pc = $PC;
723    
724     my $insn = "";
725    
726     # the jit compiler
727 root 1.10 for (0..31) {
728 root 1.1 my $imm;
729     my $op = $op[$M[$pc++]];
730    
731     for ($op) {
732     s/\bPUSH\b/\$M[--\$SP] =/g; # push byte to stack
733     s/\bPOP\b/\$M[\$SP++]/g; # pop byte from stack
734    
735     s/\bIMM16\b/$imm \/\/= $M[$pc++] + $M[$pc++] * 256/xge; # 16 bit insn immediate
736     s/\bIMM8\b /$imm \/\/= $M[$pc++] /xge; # 8 bit insn immediate
737    
738     s/\bPC\b/$pc/ge; # PC at end of insn
739     s/\bBRA\b/return/g; # conditional jump
740     s/\bJMP\b(.*)/$1\x00/sg; # unconditional jump
741    
742     s/\bIN\b/ sprintf "\$A = in_%02x", $M[$pc++]/xge;
743     s/\bOUT\b/sprintf "out_%02x \$A ", $M[$pc++]/xge;
744     }
745    
746     $insn .= "$op;\n";
747     }
748    
749    
750 root 1.12 $insn .= $pc;
751 root 1.1 $insn =~ s/\x00.*$//s;
752    
753     eval "use integer; sub { $insn }" or die "$insn: $@"
754     })->();
755    
756     ++$CLK;
757    
758 root 1.18 # things we do from time to time only
759 root 1.1 unless ($CLK & 0xf) {
760     # do I/O
761    
762 root 1.10 unless ($CLK & 0xfff) {
763 root 1.1
764     # pty/serial I/O
765 root 1.10 unless ((@PUSARTRECV >= 128) || @KQUEUE || !$PTY) {
766 root 1.1 my $rin = ""; (vec $rin, fileno $PTY, 1) = 1;
767    
768     if (select $rin, undef, undef, 0) {
769     sysread $PTY, my $buf, 256;
770 root 1.20
771     # linux don't do cs7 and/or parity anymore, so we need to filter
772     # out xoff characters to avoid freezes.
773     push @PUSARTRECV, grep { ($_ & 0x7f) != 0x13 } unpack "C*", $buf;
774 root 1.1 }
775     }
776    
777     # keyboard input
778     if ($KBD) {
779     while (select my $rin = "\x01", undef, undef, 0) {
780     sysread STDIN, $STDIN_BUF, 1, length $STDIN_BUF
781     or last;
782     }
783    
784     stdin_parse if length $STDIN_BUF;
785     }
786     }
787    
788     # kick off various interrupts
789    
790 root 1.3 $RST |= 2 if @PUSARTRECV && $XON; # VT100, but works on vt102, too (probably not used on real hardware though)
791     #$INTPEND |= 2 if @PUSARTRECV && $XON; # VT102, 6.5 rxrdy
792 root 1.1
793 root 1.3 # kick off vertical retrace form time to time
794 root 1.5 unless ($CLK & 0x1ff) {
795 root 1.1 $RST |= 4; # vertical retrace
796     }
797    
798     # handle video hardware
799 root 1.10 unless ($CLK & 0x3fff) {
800     display;
801 root 1.1 }
802     }
803 root 1.4
804     # the interrupt logic
805 root 1.10 if (($RST || ($INTPEND & ~$INTMASK)) && $IFF) {
806 root 1.4 # rst 1 kbd data available
807     # rst 2 pusart xmit+recv flag
808     # rst 4 vertical retrace
809     # 5.5 vt125 mb7 trans ready (serial send?)
810     # 6.5 vt125 mb7 read ready (something modem?)
811     # 7.5 vt125 mb7 vblank h(?)
812     # trap vt125 mbi init h(?)
813     my $vec;
814    
815 root 1.17 my $pend = $INTPEND & ~$INTMASK;
816 root 1.10
817 root 1.17 if ($pend & 1) { $vec = 0x2c; $INTPEND &= ~1;
818     } elsif ($pend & 2) { $vec = 0x34; $INTPEND &= ~2;
819     } elsif ($pend & 4) { $vec = 0x3c; $INTPEND &= ~4;
820 root 1.10 # } elsif ($RST ) { $vec = $RST * 8; $RST = 0; # the vt102 firmware doesn't like combined interrupts
821 root 1.4 } elsif ($RST & 1) { $vec = 0x08; $RST &= ~1; # separate is better for vt102
822     } elsif ($RST & 2) { $vec = 0x10; $RST &= ~2;
823     } elsif ($RST & 4) { $vec = 0x20; $RST &= ~4;
824     } else {
825     die;
826     }
827    
828     $M[--$SP] = $PC >> 8;
829     $M[--$SP] = $PC & 0xff;
830     $PC = $vec;
831    
832     $IFF = 0;
833     }
834 root 1.1 }
835    
836 root 1.10 #############################################################################
837     # roms in the data section + one newline
838     #
839     # vt100 @ 0x0000+0x0800 23-032E2
840     # vt100 @ 0x0800+0x0800 23-061E2
841     # vt100 @ 0x1000+0x0800 23-033E2
842     # vt100 @ 0x1800+0x0800 23-034E2
843     #
844 root 1.11 # vt102 @ 0x0000+0x2000 23-226E4
845     # vt102 @ 0x8000+0x2000 23-225E4
846 root 1.10 #
847     # vt131 @ 0xa000+0x0800 23-280E2
848     #
849    
850 root 1.1 __DATA__
851     1N ;0>b/BWog<Gӂ,O$ O[xI<ӂ,Bt@`+6+|v#~ʐ|¡0ڡq~ʨ|¡0Ҩ,ڥ#€€yOtͤ[zW>/2!b>>>ӂ xӂ:h zW͢uۂG|g$>% !h w-!h >-4!j pO:{ y:! u:x!_yA[>y >yA[>?y@ :x!žyA[P>>O[>>[Î:!ʵyA>>OlyAPÇ!:!S!h ~ ~ >%: O͓: Ô!20!2!!!yAG~"&=w< w:!/!!A:!Ey2!~1N ! ~eBi<2!͢:P =2S!~6ʘ!!6
852     2!0* w4ʘ> 2! ~î!N ̓/2!! "R !"" 
853     ! ͋!0Ãppp pp!"-!>52,!>2[ 2v!!"I!>2s >2 B>2y >2!2!&l" >@:X!:!͔>2!b:!K>G:| !p ^!p" :!x:! u>
854     Ӣ:[! Ӣ:!c w w#‹:!>Ÿ>{́kB͈͇!w!~6 :!ʮ2D!î:O:!:8>'y7:!y !!>D! NDy<wG: 'qx<o61,@ 7~G>G>w70)1!2@3#4$5%6^7&8*9(-_=+`~[{]};:/?'",<.>\| poytwq][iure1`-9743=08652 Ѱ
855     \lkgfa';jhds .,nbx/m vczN> Ӣ:e !Q >>2e 6>2[ :V!>2[ :U!=/2z [
856     !Z 'wWӢzӢz>2e
857     :V!8:U!=2z !x ~O52F!!,!~=i65>Ӣ!!~jӢw>@2H!! 4:!B:X!:!b:!!{ uG:{ x©:y xک ڲ*@!!
858     2>2y 
859     G>xb O͈ByF sO! V#zozz2 V: Gzy_ Oz@y$#$: O:B!A! :S!A6U * q|gpx2 ! q! :S!hy2 :!@m462B!:!…! ~#‡n& V! ~<w+@0§~z:h _!A{2g W!n ~!j #6 #{>2r !G:r < 2r :!@{:P!!_#!s 56:r y!n ~C#8vyj !n ~[
860     f#}rQ
861     lNG:g x2g 2P!{j,C_:D!A{WW{!v_Ny1 xpy{aOx0!K~###NxyA[? {AAO:! 2T!S:P!!n A#!n ~8#+A:P!w>2r !h Vw#r#w#L:!@>2G!{*j:d:0!W:1!_>=zħ;>>ܧ>b>ܧz >oz2 QW>>Wʞ _{
862     >!_^#V 8 A U U U K <2 :!*{!|!r!>w!r!~w!{!F#\!~0 # $ !x ~w! ~562 6K :! K ! V:V!s Ϳxr6:!ʘ ͎>2!4:!‰ 2 =2 2} 2!! 
863     0ҽ !} O~ʻ q2~ !
864     :} :! !P
865     !
866     G:! x( 2 )1 #!{
867     !:~ O#
868     ##
869     ~#fo!"@!A
870     B
871     C
872     D
873     F
874     G H
875     I J
876     K
877     Y Z = > 1W < ]` cER M 1W [
878     H DU 7h 8q = > Z NF OF 345J67` 8 !"@!>H2~ !"0!!
879     :} !
880     :!!0!:K!_
881     ~!
882     G :!#
883     DBCHSArfSxydc q n JAKlm hg ! "@!G ڲ!Q!. ! "@!~B 6x 2R!! "@!6x PO 2 Ϳ:R!] 2 
884     62!ͭPLPÉ >2!ͭL2U!Ϳx2V!y2P BH>ê! :U!G~ʸ ! 56:! ͎>2!5:! 2 -!x!6!x!6!!~ w:!& &Hl" " 2 & &l" !E!x# ~wG7) w:~ G!J ##~ȸ9 #~! 44AHB012!y ~
885     6!
886     !! w ! ! ͋6! xʗ     2 ! ~w>õ :!» >õ >! w>2 :0!!r!~w>Ϳ :! 6+6/6?#>1͹ BG/Ox y0w#6:!x!r!2 ~w>Ϳ :!x) 0w#6~w>Ϳ : G:!J :U!Ox<z ͻ : <z 6B͎>͈Bo _0d͙ ʈ w#
887     ͙ ʓ w#{0w#{0 _x>E2!́>E2 2!w#6;#!r!w!\!6#6[#Bx  #w#/w!!w#} #  Wz #}> :S!y2 6: OW!!ozW>8:!!{ GB!!~6+~>xmB:!!s!~y*t!:r!ډÀ!~#fo7    !\ FT]w# ©!C!5~O!r!~w:!y2D!x?2s!!"t!xG:!!{ x“!"t!2q!<2s!!q!~4!\!oFx2s!W:!+z֑a!r!~ w!C!~4!\ _or>N:! 2:![>>2D!zW!!~u+~u#rJ~^:!:!y!!xʞ/w2!wy!!w#w! :{ ̈î*N |g"N :P G| gW]>6#<2!!U!V#^xz{{O:+!Vwxo z2+!>2!6͑:V!/:U!=>͑:U!=/:V!=2z !V!~+!z ~6*V *X r#s*y!*u r#s!V "X "u 2W!͍"N!p#zƒ!Q :e ʧy܈͓ÎyÑ!p" 2Q 2e 2Z Ӣ>Ӣj>ͪ!"|W]/ :!w#> x:!2 2 2 >2 ɯ2Q :e !" Ϳ 666#"T 6p#6y6 >2 !" 6#T]zw#sy6! ">s#r# =Y2U!Ϳx2V!<2+!* >~* |g"N !!w,<:z ʠ͈Ñ:e °:Q Ù:[ !Z 'ʙ:!!! ɷ! ~#ng"N ͐BK
888     ͐͐BKzgk"N |<͙"u (͝:P W#"u zp#q`i"y!;! H|<R͙"X F#Nq͝:P G#"X xF#N*N s+r*N "V ͙p#q<G! ~#ng͙Ä͙V#^#| gͰPͰ0Ͱ͑!!: o~w͍~͕:S!! ! " :0!2v!!r!~w>Ϳ :v!2͹ :!<͹ 1 x͹ :!z ͻ :!z ͻ >1͹ :!z 6!W!~͑!!: o~w͍:P Gw#k>2!͕2W!6: ! ò~#fopG: =! W͆͐:P _#~揰w:P T]~#:P o$: !!o~:!!&?!
889     xA */WGN?f_ m8!!pv! v! !!pHv!v! k!!ṕv!@v!@v!B^#V#~#O/wxw͓̪YB!D!~ʢ>!!!E!##6#6ӂ!t 4:w :e !Q *-!+|"-!:!2!!! "-!* F:Y!w:Z!wtx=
890     =: G*N!): G:W!:P $G* |W]>6#02 2 xj=U=UjJ! ~Gw͆wt
891     : J! ~O>G͆wt qw>p͕t:P G)Æ͎Ϳ:0!ʶ=W:1!x<=_xz!U!r#sKBy<_! xP~w+~w+>W~w+x2U!y2V!!!~#͆~#fo# H#n g#! "-!2!* : Gw|g: w:P =_:{ {: !!wt2T!!W!~{{=_{2S!!S! ڌ~:!¡#ʡ8 2T!: 2!*N!" ~2 G|g~2 * pG!"@!2/!2K!2} !0!w# !!6x@<wGx:0O!/!~G>w0!!K!N /!
892     w2<G;xC2~ â
893     !} L>w!3"@!b
894     ;!{!32!y2!z~2!̮ͣ:!ʞw:!O#:!<qʬby@>c+!" !!͋!q" !{!'6# ͋>02x Waitpn P :U!Ϳ:V!! %:S!"! W:!
895     :0!5<_~DD7w6!"0!>2!:!O!0!~`=Gyi:U!Gy:V!yHͿxA~x2 #~ʈ=Gy•:P =Ø:S!ڝx2 6@(ü@(ͨw>0bBB>-bBB>/b!!BB>%bBBw#B>/b!!) o"!:!
896     -
897     !!#s#<6/!!_6"!!>
898     _6"BW!!BbBj~b#b*!))!> )>/!!BʓB™~b#“BʨB®>+b>/b!;BB+|BB>)b>/b C! K :`0!͐x!2w!:{ 2{ a͎*@!"!!"@!!"C!"r!* "!* " !ͺ"N!2 6*!"@!ͺ:P 6#=q! !~2 >#w6*!" 2!2!Bʡ:v! 
899     "@AR2!2 !:!w c ER: #w#Tx2 : R !!~ w2D!K !Z"~w2e"2!5Y!!: (W+wY2 ~w:!:!:i xSrTÁR†[͢+AYK >A>=> !"@!ɘa{͔!"@!*N !!| gͺ6#|p}:P _T{'{!S">q͊>qP͊>q͈6#ͺ|p}:P G>1w#OyI<'P0>͟>2!!S"|e0o" 
900     ʆw#wp6#T]zw#s# ‹6T]#zpw#sSET-UP ATO EXIT PRESS "SET-UP"ͺ>N##Bw# 6# yO6# K:!aV:!a͟= >1w< @` T SPEED R SPEED O ͋yOO| Ë 50 75 110 134 150 200 300 600 1200 1800 2000 2400 3600 4800 960019200!!S{+>:!G:!2X! :!??2!B!!~w>n1:!G:!  G:!ð2!:!>> 2| :!M>n): BB  6ɷy2[!ÔB2Y!>2[!>2Z!k2[!2Z!>2Y!k!{!w#"!G̓!"@!*!G:{!O}p#"!qK ð >!N O:!yP~#fo| gyo $pxկ2!x2X!y!uH#|&>:X!>%2!76yO xGz{͡6e>G2!bxʓ%xʝyOy"Gxʳ>x@ʽyO/ >x yOy1" vvvv vvvvvvvkvvvvvn [#!ǜ I}Ȅc 1"͜i:d =2,!! ~6ʰ!D06
901     0*!w2!4>2J0> ­2y!:y!̆I>2(" 2 !> '͊Ϳ:B!%!`! j0!"0 0>2w!!80(0<2f !""!!", >52+ >2!>2*!2{ &l"~ :w!B!G! M_ppp ppw# _>>@:x!:/!:@!b!.!F!~w#w#w2?0—f͹:v!ʮ>:&0!p>!p " : 0>
902     <Ӣ:0! Ӣɻ؀> Ӣ:| :W0®!e ~<w#~:.!:-!=!f ~G#'w<++~wx:.!8:-!=a!I ~S5!L G~w!!4!g ~Ӣ~Ӣ2!H0>w![0~~6É=w‰LÌ!+ 5§65>Ӣ!D0~ʧӢw!X0>w!X0>w2l :w!B:/!:@!b{+::(0:B!COg8G-:| x:#0-x -|-:| :y!|:| yҶ:$0yyy:70t:60t:H!yË:70ʑ!z!y‘ !F!~wy2H!:!0ʵ:70y!<!y2H!!~ NDy<wG: qy=o6ƀ`2:!>>wۂG!N0:M0w:w 6~9x|Fg$>%-!w [x\2N0\!w ~O4>[!x yoxw:X0ʀ k!~ ^#V!2!I0~6ĩ uk:B!`::0´!F0~6Ȅ OyĜẙ:!: !:K `:B02B0!H0>w`!L F:
903     :B!><w :00OuG:"02:B0;>G!L ~Gwx!+!6!y!ӂ:e œ:H0œ*, +|q", Ü!{ ~/w!! ", *!!0!>w >w:H0ʬ:L L!m FT]w# »!K 5x25!!"6!xG:B!=uG:"0W:S0:R0uͲzzÈ!)"6!23!<25!:B!@dFx/25!xG:y!͈:5!o!z!x~#w>25!!3!~4!M o:70:%0>W:!0ʚ:70z֑!K ~4!m _or>¾:C0¾:"0šuW:"0>>>G:B02B0=280:B!1zW!;!~+~#rJ:70:!0:y!y!} x6/w2=!wy!:!w#w+*I!~e:F! e!B0~w:!B:!U!B0~wʆG:B!x!y!!!_:!¡{=:!0:T0½{A{{{{A>?}{@:T0{AP{>O}ù{>[:G0{ A{>OAP!:y!:B0!w ~ ~ :G!CE2G!:!0/!y!:70:} qy2=!:;0 yʾR:;0ʬ:y!!~!"!v> 2;0:y!!~!"!Avf!;0>>
904     wy!_^&^tsrgö S<2!:S0*O!|!4!>w!4!~w!O!FL #~/ $ :S0!I ~G w?2!K :;0 n :90n y2J { :"0K !!V:.!ʏ >r:S0: 0;>2V0!e 4#w?,ɯ2!2 !<2R0!z>>B>0W>>> Ӣ>Ӣ>Ӣ> Ӣog<Gӂ,
905     $
906     x
907     )
908     v"
909     >&  @&@+6+|0
910     #yӂ~M
911     |E
912     0J
913     p~^
914     |V
915     0[
916     ,=
917     $|=
918     |@:
919     xG+
920     z@2B!Oʎ
921     zW>2W0>0>>¤
922     >ӂ xš
923     ӂ:w
924     zW2W0
925     :/ W:0 _> z >2B0>ƒ> >> >ă>  z >
926     z2 2B0qW>>W9:/ 1 =W:0 ; x<=_xz!-!r#sKBy<_! xc P~w͢ X ~w͢ c >ʀ W~w͢ u x2-!y2.!! ~ ˘#  #n g#-Ͳ!x = = :!G*! :!G:Q0:d G*!%͊ 2!2! *!~2!9Ͳ!x8 =# =# 8 à !!~GwT w :!̃ !!~O>GT w  q͊ k 5w>p :Q0:d z G T ͊ !7:+07z:-!ç :.!!! :,!þ 2/ !!W:/ <_~  w!"/ >2*!:!O!/ ~ = >Gy :-!Gy:.! > x2!#~ =2! _5 !å!SO:B!"| y K ~:} 0!h 0~#fo:E0| js:v y! AC]DM,PRST2I0!!~J!A!~6 fi:| *>!" !!~ /6!)!~2!#~2!:E!2/ :Z0?=x2 !!2C02!:O02O0*E "? !"~!"!"!""""2<!2<02{!2@02A02F02^0w;*? "E S!"4!2K 2B02:02P0!=!~w:/ 2E!: !ʉ?ʉx2Z0f* ">!*!")!/"!2C02!:d =2,!͟?{
927     :!x
928     k!y!~ w>2F!ɯ2R0>0!
929     "~w2"{_!:!>W! 0Ͱw:!ʹ HI%2ʥ4%:!0::!:702} 2:!ƒ!10yƒ!204%Í!3040
930     !v!~/w2*!2)!—ÐJ?{O>2F!͹ͥ%ÚK AY=Y Y>2C0!n
931     !e!6#="õ:E0!40%(!500U>2F!!70Q:60]0:E0U:B!p:70!60:70ʆ>p0:60晃_ҙ2z!!2/ 2 !!w!{ ºAG~:v =w< w :C0í :C0ú !E0~~w0:
932     "A:
933     "B~<#w:A!2A!*b 8!{!| g!"}!">4>P4 >>"2I!n"p"l"͎55:d G=2,!>1w#Oy§<'Ү0œI/6#5|p}:d _{ͬ T{͍ {͍/p"x"Ͳ*!~2!!"0" |eo
934     0w#!p6#T]zw#s# 56T]#zpw#s/>52,!2!2E0!z"p"x"͎W5I*:G:̟ڞ*ʟ!|!:w#s#6.#r!!">!!(>!!p50p56/#s#r5p"l"/>>0p51p##! 0S{Ͱ>w# x8##:E0G!!:400_:10H4_:20H͒xG!!:502_:30H:70ș:B!²:70²>õ:600_O0_6__p#p#O0SET-UP ATO EXIT PRESS "SET-UP"MODEMPRINTERT=T/R=P=:K!O!10~#G~2/!!@!~bd w8:/!ʁ:#0@ʁ:&0>ʌ<G:40¦:/!¦>G:40!##~2x!!0!: 066:30:50!##~/:30O! /~##~'/h!O!w#"!G0!(h*!G:O!OC}dCp#"!q/>K 2C0!% x2!:!<2!s u>G#…:"0ʅY:!O7!,!Ҩ*b | gyoҧ$pɬzB@ʮB@µ> bB@B@>b!!B@B@>bB@Bw#B@>b!!) o"/ tzB@&B@-> b>b*/ ))!>)K>͵B@^B@e> b>b:. G:@!Ox
935     
936     !!6#”6!!_6/ _6B@¬!!B@ʸ#B@~bø!;B@B@+|G!"? 2. 2!2!!/ w# ! !6x@<wGx:=0=O!. ~9G9;>wx/ !!N .
937     wY<G;xj2!~!!s>w!Z0Ҍ!!O~ʊq2!:B!!:!G:!0x(!)#![:!0!!3ͅ!0:!O~#fo鯆#7##AsBsCsDsFhGmHsIJsKsVWYZ]Ȅ^ؕ_=7><<A5D{ ER H^MNOZc7 8[k=7><3g4k5!6c8!"? !"/ 2!:B!:;0#:y!#: !:!:!i!/ :!_¹~#¹ÈR!~!"!v[v: !Gv!/ :!O ;v#~ͺ :!Gv:!Gv> 2;0f!:!͟: !!ͅH!/ :!OT F!"? !=!~w2:0# T!=!~w?HA B C D H LeMPf r$ y
938     }t|J K cgNm#nqi^h֓lѓn:B!!L ~w:!G!N#~#ȸy!!44ɯ2R0!"? !!!!!! _!.x;FKPX2!!!~w>R>R>!!w>2!>2:0: !!xs?!H Ȅؕ:/ !4!~w>ͭ:!0®6+6/6?#6:B!:B!:p#6:/ >!4!w:/ >>ͭ:J0>3>0w#>ͭ6?#61#: !x6!4!~w:=!2=!>ͭ:!G:!<:-!Ox<x6;#:!<x6:=!2=!>ͭ6?#61#60#:(0>t>w_0d͗ʆw#
939     ͗ʑw#{0w#{0_x>2U0—!4!w!M 6#6[#!nx 2. !x P2!:. 2!>2^0!!:-! 2^052^0: 0-;>2V04#6?2^012^0Ͱ>=2T0!!0~w:B!I:"0&\&l"!"!2!&o&l"!xʝʕ!~#ʍ#Á:*02*0!+0~wɯ2*0!+0~w:B!:70x=>260͌:y!!!:S0:5!bG>2S0:!x O:B!L}*? O:B!(l!;0~ʫ:(0ąW:"0O:S0«!~!"!: !ʫ~s:y!«Avl:!06 y‰Xl[6 [lvAvl!", 2{ >2H0:(0ą͂l:00Ğ:O01:(0ąK :90{ 0:.!G:!$:S00R 'a:!dS͂d͘:B!Rl*!q|gpx2!y2!!!:,!h:"02O0l6 l4 :(0ąnjS:B!+njʖ2P0S6 nj:00͟*!y0:!GyO |]W +~#w+üy2!!V#zozz2!~G!("x2=y#22==2=%y_2_O2*L!~#F#2%H:S0W:(0ąW*!~怱O|g~G:!G:!O:=!:@0:A0!~ ~#ʤn& V! ~<w+ƀpš:R0/7ɯvV12 vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv*b |g"b <2V0:d G| gW]>6#"3!-!V#^xz@{8{O:* Vwxo zI2* P:-!G:.!v,:-!=ɀ :Q0:d ʅG! >q#–=~w# =P:.!G:-!v=1:.!=a!.!~+!K0~6*h *j *8!*G !h "j "G 2K0|gr#s+:d 0r#s52Q0Ș"!p#z0!e ~My2;y:K0`2P:e lY:f !g 'Y"/G G:y!ʜ:F!ҁ!K!:70³x@ʳzʼr8x*
940     !+"
941     !)ʁG:F!؁!.傰G:G! :$0ҁ*I!~ҁ͸>}>}!:$0:G!G&>G- :L Wx=>Wx2L :y!:!h*I!_{]zz{hz*I!~#t##x/##Gˆ+y/##O+NxڨGyҙOzA#Ìz Ұ+#܂:70ł:60#^#V#؂:70^#V##"I!FxȂ2G!:F!2F!:70x /2| xO:@!2@!b:&0#5#xPD^L~W~_"
942     !*I!~j:F! j!B0~w:!b!B0~w:?02?0:$0ʓ> >:%025!2K 2R0:!º!"? :$0ɯ25!2K ͌:70!:!6:!!!"? 2=!2B02:!2;!2R02T02002G0I&l"~ :Q09͟>! :!o~w͎Ș:d G0|W]>6#S2Q0oPo0o9͟! :!o~wȘ~:,!!!!!"!:)0*R!!"!vSvo:z!Gv:{!:^0R9{2!>
943     2J !F06>
944     2J >
945     2J 2!: !{!~!"!B*:{!fR9>2!!!"!2"::0j!9}"!"w#s#r#<<[+V+^:"Œͳ…ʅ:y!ʑf6 >Ó>2{!ͿR:y!«:"0ʲ!B0~w΅܅:#0a>@""n>2Y0:)0O=""""tya*C!"!*!"C!:!=O:-!=y@%=:!=0"":!O:-!=7k:!kO:!O:!k:!@:!0""t:.!@"":-!@"":""!͟*!"|!>Ґ/ 2""!:)0 G:"ņ*"*|!5ņ5}ņ<O*"?:"~:"~:"O:!W:":":#0 !!v1:)0
946     :"LJ:)0(!!6:"0(#:.!(4:z!Gv2{!9g:(0>g:"gŇvú2'":"x:"/Áͅʖ:(0ʖ~ʑ –+ †Ňͅʺ:(0ʺ:":"v:"ćLJ:"vx:"0 v:90:J Gv
947     v:B!Oڞ*|!+#"|!*!#Vvry:'"F0 %/OxJ Ó EO[Ó!"~͠Ғ:!͠Ҏ*1!^#V#F#:"q:!qͧqPCv*|! ԧyO|Ɉ~¹_Ɉ#~#³׈Yy yx<ɯ<7YG!("w*1!^#V#~#7"L!:+0:+01:601 +}o|g:!8˘]T<H!=!~w:B02B0!=!~w:B02B0*!#~<<ƒO+#~”O=>;#op*!~#q< ҽ*!}~>7ʺ2A02@0:!W:"!!~#"~#~#:"0x2!!)"1!:B!X:B![!#~#v(vFvz2"AB012:==#_`abcdefgh i
948     j k l mnopqrstuvwxyz{|}~:(0:B!::w !(0~ʣ~w!4!~@w:/ G´͟:w :-!2!!!4:.!<Ȋ2!:-!2!ݖnjSɯ2!:-!2!ݖ :Q0:d G~w# ͟͟:!_4{ͬ(*!:!O% GS + C yZYͿŕͫƋniiDMz"-!ͫƋniiDMN"-!ͫnjʽ6 S[¯x΋:!!-!ߋ!.!O~njj"-!Ͱݖ͟:-!@<@~<%~#x G~#/ݖnjjN"-![ݖ͟:.!@=@ 6 9ͳx<ʒ:.!҅G!-!N:!Wxҕ7r#~pG*!"!~2!|g~2!2!:-!W:!OÌ:.!?͟*!:S0:+0㌯ͳͳ %:*0G :+0~͟:!O*!͌~<5͌D##!!~O4 !!~A=OGq ͟:!W:!_:,!r:.!}}{ͬѦ^ڝ:.!!!ʝ>ʝʝ4!!FxʯҴPYôyڪͻSz2!{2!:!!.!ٍ:,!!!njͲ:!O*!% G#~+< # T]7T]##~ #ng |W]:\0 0  0:W0 0 
949     0 0  0ɷz! ~#ng"b ! Û PY`izgk"b |<¢!"G õ+:d 0#|g"G `i"8!>2K0! َ |<!"j F#N+:d 0#F#N+|g"j *b "h !p#qú<G! ~#ng!!F#N:d o*$#| g!M0~:w D>w2[0!:B!F:w _A y!x #n b!@ x ʚ#~‹w>2D  †!!A ~W# ©:D ŏ=2D : 0:]0=2]0>2]0z!w ~2v w# 폯2M0:M0!L ~@w?6yyʣ IO?:w :B0!]ͅ>2S0:!>ӹj:w p:(0!! ܐ
950     />NWSŐԐӺ АC S:w LÏ:w <!00~w!!6:y!:)0y!!~:.!4:w Ȅ:w А:y!А͡:{!f:-!!!~5!!~:.!4:,!!!H!!7!!S06:!>:w G !Ȅ:w !90~w:B!Uy:!®:!02P0:70y!z!:%0y :C0y}:"0>
951     }Ny}>2]0:!0>2+!?O:w G:B!’y{#>’yWWy_:&0{E0C1E!_Ny ڲxpʲy{tat Ox£òx0ʈ!ǜ~#ʇ#}Nxʲy ʣ?ʣ{ڬҬyOy’>’!ky’#¸!L傯2y!Bђx2/!y!b#|뒯7yOxGݒz.TG@y0O{ _xJ{_{y2@!b"O_/ >y oy@ʀ>x!:50yœ#!G³#|¡7x¯¯yOœzW҆ؓ: !!"?!HBͅ: !!/?!9INxco\ϔ֔! U
952      ݔ!G0p1!01"0!!p 91 01 0!v!p—1 01%01"0:701B0100:00G!L ~w1"0:"0G2"01#01#01)01(01(01+01)01)01(0:B!1(0!"C!(!L ~w^#V#~#O/wxwŕ6 ͷͲnj[6 S!!yO*!}|W]#~+w# o2O0~w|g~w*!~2!|g~2!!!!5~<4#ŕ5+6ŕ͟nj·:-!G:!ҕ:!: !╯290: !> 2;0ɯ2;0 :<0: !!~!"!~G#9::0:{!1"!"~/oV+^+<<.u]2@0:{!YR*|!x\fG:50xj#!l 6#xoF*!~<<…w#x ! !ʗ6ɶ6Gx!<0ʵ>ø>w_0d͗ɖv
953     ͗Ֆv{0Gv*!~2!~2!>2*!Ͳ [>2*!͟ ͟:!C:!!*!Cw 2P02O0:Q0<:d ==2,!C:d =2,!!,!N!!FxTq^:!^2O0:!}x}:P0}< 2P0:!2!*!0"!~2!G|g~2!*!pͲ*!:!w|g:!w!", 2{ >2H09!p"h ! "j >2K0͎!"|W]0 :U02!w#=< >E2!>6E#  2U02!2!>2!!""!!I">0ͭ0ͣ0#6p#6ͭ>2 !" x2.!<2* ˘|g"b 2-!! w,<wͣ2d ͥ ͎!!~<:!2Ó:v!>P>ͣ06#zw#s5_bk)))))))"pG:!=! 8W˘:d _# ~揰w:d ##T]0-~+$:d o$:!! o~:V0? 50 75 110 134 150 200 300 600 1200 1800 2000 2400 3600 4800 9600192007M 7S 7O7E87N8O8E<8N 7M7S7O
954     7E7N8O 8E8NFDX AFDX BFDX CHDX AHDX *HDX B ETXEOT CR DC3 FF YWs9N4/'
955      '#. 7ZlBB˃SyL'. ZzfktB.b
956     ..".yƒ#A.њb.S.š.#.#A."B.bƒLa.A2V1GG#.#.WꛖL@.mF1FFb"b.&
957    
958     .y'.ݛŃЛ.dx.& .Л" &%&5>LGxWdkͨwͨ/w!e!6#}vnͨwͨ ڐWzʁÚ#}v>‡ :,!ڢy2!:!OW!e!oһ$zW>0)1!2@3#4$5%6^7&8*9(-_=+`~[{]};:/?'",<.>\| poytwq][iure1`-9743=08652 Ѱ
959     \lkgfa';jhds .,nbx/m vczT=* :B!4
960     !O!,ͳʄmÝ
961     ! 0>2ͳʥ†">02I y>c}" 2. y2N!~2/ z#ͫ:/ w:N!O:/ O#:. <õ!N!蝾:&0> 2 !{!>!q{" ÎWaitp!O!'6#'M_! 0R_:B!7N 
962 root 1.10 L8>2\0͍2\0:y!ʟ25!24!̞D!;!~G6̞:70xˆ!F!~ɞ˞w:5!ڞ:4!ឯ͆;:<!Bʾ:B!.ڇ!!~#;:!;D!!"!F#ux‡2A0:{!4R*|!x7fÇ:%0H:l :5!S*6!:4!k!wf##]~#fo:K 2\0 )şZşG>2\0:70ʫx!z!«!F!~ w!B0~w:40x·!l 62\0+2\012 vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv^y5k6!
963     "~ACŠ>Bw2"!z"pv"x">Cw2"!n"pv"l"!!~< N[>2,!NAp##!(0S{}ˠ>w# »xE##ö6T]#zpw#sX6#N|p}Hpv"x"7:
964     "C*C!(0:!@W ==}wï2C!2D!z5*b Q!{!| gH:d G| gW]>6#k<|eoozW>Ê