home *** CD-ROM | disk | FTP | other *** search
/ C!T ROM 5 / ctrom5b.zip / ctrom5b / DOS / UTILITY / DIVERSEN / STFKEY41 / STUFF321.ASM < prev    next >
Assembly Source File  |  1992-07-09  |  52KB  |  2,088 lines

  1.    PAGE 80,132
  2.    TITLE "StuffIt, Delayed keyboard stuffer. (C) Terje Mathisen 1989-92"
  3.  
  4. VerNr   EQU 321h
  5. VerStr  EQU '3.21'
  6.  
  7.         LOCALS                  ; Use TASM features for easier development.
  8.         NOJUMPS
  9.  
  10. TicksPrDay      EQU 1573041
  11. TicksPrHour     EQU 65543
  12.  
  13. BIOS SEGMENT AT 40h
  14.         ORG 1Ah
  15. BufferHead      dw ?
  16. BufferTail      dw ?
  17. BufferStart     dw 16 dup (?)
  18. BufferEnd       LABEL word
  19.  
  20.         ORG 49h
  21. VideoMode       db ?
  22. CrtWidth        dw ?
  23.  
  24.         ORG 4Eh
  25. CurrStart       dw ?
  26. Cursor          dw ?
  27.  
  28.         ORG 6Ch
  29. BIOS_Timer      dw 2 dup (?)
  30.  
  31.         ORG 80h
  32. KbdBufferStart  dw ?
  33. KbdBufferEnd    dw ?
  34.  
  35. BIOS ENDS
  36.  
  37. BOOT SEGMENT AT 0F000h
  38.         ORG 0FFF0h
  39. RebootLocation  LABEL FAR
  40. BOOT ENDS
  41.  
  42. ; To reduce the resident size of StuffIt, the script is compressed into tokens
  43. ; using the following algorithm:
  44. ;
  45. ; 0 and 224 are used as the character part of function keys. These codes are
  46. ; always followed by a the function key scan code.
  47. ;
  48. ; 254 is an escape character, followed by a char:scan pair. This is also used
  49. ; if we need to enter Chr(254) or Chr(255)
  50. ;
  51. ; 255 is the lead-in for an extended function code. It will be followed by
  52. ; one of these codes:
  53. ;
  54. ; Codes for extended functions, i.e not normal keys:
  55.  
  56. REBOOT_CODE     = 0
  57. ATTIME_CODE     = 1
  58. DELTATIME_CODE  = 2
  59. FIND_CODE       = 3
  60. PROMPT_CODE     = 4
  61. PRTSCRN_CODE    = 5
  62. BREAK_CODE      = 6
  63.  
  64. ; Use 255 to signal that the following is an extended function
  65.  
  66. EXTENDED_CODE   = 255
  67.  
  68. ; Use 254 as lead-in for keys that need both char & scan.
  69.  
  70. GETWORD_CODE    = 254
  71.  
  72. ; All other codes (1-221,223-253) are presumed to be single characters to
  73. ; place in the kbd buffer. All of them will receive the same scan code (2).
  74. ; If your application cannot accept this, you must use the {c} or [c] syntax,
  75. ; where <c> is any character. This will be translated to the US std enh kbd
  76. ; char:scan pair.
  77.  
  78.  
  79. CODE SEGMENT PARA PUBLIC 'code'
  80.         ASSUME CS:CODE,DS:NOTHING,ES:NOTHING
  81.         ORG 0
  82. PspStart label byte
  83.  
  84.         ORG 5Ch
  85. ResidentSize    dw ?
  86.  
  87. LowStart label byte                     ; Small size TSR
  88.  
  89.         ORG 80h
  90. CommandLen      db ?
  91. CommandLine     LABEL BYTE
  92.  
  93.         ORG 100h
  94. start:
  95.         jmp init
  96.  
  97. Semafor equ 'ST'                        ; Stuffit
  98.  
  99. ;LowStart label byte                     ; Move this label to decrease size!
  100. HighStart label byte
  101.  
  102. MoveDown EQU HighStart - LowStart       ; Relocation factor for resident
  103.                                         ; part of StuffIt
  104.  
  105. Int2F   proc far
  106.         cmp ax,0E000h
  107.          je @@maybe
  108.  
  109. @@chain:
  110. ;       jmp [OldInt2F]
  111.         db 0EAh
  112. OldInt2F dd ?
  113.  
  114. @@maybe:
  115.         cmp dx,Semafor                  ; Be safe, insist on semafor in DX
  116.  
  117. current = $
  118.         org $-2
  119. Semafor_1 label word
  120.         org current
  121.  
  122.          jne @@chain
  123.  
  124. @@We_Are_Here:
  125.         mov al,0FFh
  126.         mov dx,cs
  127.         mov bx,VerNr
  128.  
  129.         iret
  130.  
  131. Int2F   endp
  132.  
  133.  
  134. ; Here comes the actual, INT 8, code, which will run on every timer tick to
  135. ; execute the tokenized script.
  136. ;
  137. ; To reduce the performance overhead of having StuffIt loaded, I use a dirty
  138. ; trick: Self-modifying code.
  139. ;
  140. ; When the script has finished, the total overhead is reduced to just
  141. ; 3 instructions: PUSHF / CALL (FAR IMMEDIATE) OldTimer / IRET
  142.  
  143. PUSH_AX_OPCODE EQU  50h                 ; Opcode for PUSH AX, used when active
  144. IRET_OPCODE    EQU 0CFh                 ; Opcode for IRET, used when disabled
  145.  
  146. MyTimer PROC FAR
  147.         pushf
  148. ;       call [OldTimer]                 ; Call the old timer code first, to
  149.         db 09Ah                         ; do it's stuff and re-enable the HW.
  150. OldTimer dw ?,?
  151.  
  152. SelfModify label byte                   ; This will be IRET when idle
  153.         push ax                         ; PUSH AX = 50h, IRET = 0CFh
  154.  
  155. ; The [active] flag is initialized to -1. This way I can use an INC
  156. ; instruction to detect the first entry into this code. Multiple
  157. ; invocations will jump directly to the exit code, with very little
  158. ; overhead. (A total of only 7 instructions and 1 short jump.)
  159.  
  160.         inc byte ptr [cs:active-MoveDown] ;  INC from -1 to 0
  161.          jnz Already_Active
  162.  
  163.         STI
  164.         CLD
  165.  
  166.         push bx
  167.         push cx
  168.  
  169.         push dx
  170.         push si
  171.         push di
  172.         push ds
  173.         push es
  174.  
  175.         push cs
  176.         pop ds
  177.         ASSUME DS:CODE
  178.  
  179.         mov es,[BiosSeg-MoveDown]       ; I store 40h in a memory variable and
  180.                                         ; load ES from it, as this saves one
  181.                                         ; instruction vs MOV AX,40/MOV ES,AX
  182.         ASSUME ES:BIOS
  183.  
  184.         call word ptr [StuffMode-MoveDown] ; State machine, call the current
  185.                                         ; state handler.
  186.  
  187.         pop es
  188.         pop ds
  189.         pop di
  190.         pop si
  191.         pop dx
  192.  
  193.         pop cx
  194.         pop bx
  195.  
  196.         ASSUME CS:CODE,DS:NOTHING,ES:NOTHING
  197.  
  198.         CLI
  199.  
  200. Already_Active:
  201.  
  202.         dec byte ptr [cs:active-MoveDown]
  203.  
  204.         pop ax
  205.         iret
  206. MyTimer ENDP
  207.  
  208.         ASSUME CS:CODE,DS:CODE,ES:BIOS
  209.  
  210. StuffFinished:
  211.         mov [SelfModify-MoveDown],IRET_OPCODE   ; Disable by self-modifying
  212.                                         ; May be re-enabled by a later
  213.                                         ; invocation of StuffIt.
  214.         ret
  215.  
  216. NextKey proc near
  217.         mov si,[StuffPtr-MoveDown]
  218. GetNext:
  219.         cmp si,[StuffEnd-MoveDown]
  220.          jae StuffFinished
  221.  
  222.         lodsb
  223.         cmp al,GETWORD_CODE
  224.          ja Extended                    ; Extended function
  225.          je @@GetBoth                   ; 254 => char, scan follows
  226.  
  227.         mov ah,2                        ; Simulate scan = 2 for normal chars
  228.         cmp al,224                      ; Character for Enh.Kbd new keys
  229.          je @@GetScan
  230.  
  231.         or al,al
  232.          jne stuff
  233.  
  234. @@GetScan:
  235.         mov ah,[si]
  236.         inc si
  237.          jmp short stuff
  238.  
  239. @@GetBoth:
  240.         lodsw
  241. Stuff:
  242.         CLI
  243.         mov di,[BufferTail]
  244.         stos word ptr [BIOS:di]
  245.         cmp di, OFFSET BufferEnd
  246.  
  247. BufferEndAddr label word
  248.  
  249.          jb @@1
  250.         mov di, OFFSET BufferStart
  251. BufferStartAddr label word
  252. @@1:
  253.         cmp di,[BufferHead]
  254.          je @@Overflow
  255.         mov [BufferTail],di
  256.         STI
  257. StuffOK:
  258.         mov [StuffPtr-MoveDown],si
  259.          jmp GetNext
  260.  
  261. @@OverFlow:
  262.         STI
  263.         ret
  264. NextKey ENDP
  265.  
  266. ExtendedTable label word
  267.   dw Reboot      - MoveDown
  268.   dw AbsTime     - MoveDown
  269.   dw DeltaTime   - MoveDown
  270.   dw StartFind   - MoveDown
  271.   dw StartPrompt - MoveDown
  272.   dw PrtScrn     - MoveDown
  273.   dw CtrlBreak   - MoveDown
  274.  
  275. NrOfCodes = ($ - offset ExtendedTable) SHR 1
  276.  
  277. Extended PROC near
  278.         lodsb                           ; Get function code!
  279.         cmp al,NrOfCodes
  280.          jae StuffFinished              ; Program Error! Abort the script!
  281.  
  282.         cbw                             ; All codes are <= 127, so CBW is OK!
  283.         mov bx,ax
  284.         shl bx,1
  285.         jmp ExtendedTable[bx - MoveDown] ; Jump to function handler
  286.  
  287. Extended endp
  288.  
  289. Reboot proc near
  290.         mov word ptr [BIOS: 72h],1234h  ; == Warm Boot (CtrlAltDel)
  291.         jmp RebootLocation              ; == F000:FFF0
  292. Reboot endp
  293.  
  294. PrtScrn proc near
  295.         int     5
  296.         jmp     GetNext
  297. PrtScrn endp
  298.  
  299. CtrlBreak proc near
  300.         int     1Bh
  301.         xor     ax,ax
  302.         jmp     Stuff
  303. CtrlBreak endp
  304.  
  305. AbsTime:                                ; Both Abs time & Delta time land here
  306. DeltaTime:                              ; ---- " ----
  307.  
  308. GetTime proc near
  309.  
  310.         cmp al, DELTATIME_CODE
  311.         lodsw                           ; Next 3 bytes is # of ticks
  312.         mov dl,[si]
  313.          jz @@TimeOk                    ; Delta time, so wait # of ticks
  314.  
  315. ; Wait until time equal: Calculate remaining ticks
  316.  
  317.         sub ax,[Bios_Timer]
  318.         sbb dl,BYTE PTR [Bios_Timer+2]
  319.          jae @@TimeOk
  320.  
  321.         add ax,TicksPrDay AND 0FFFFh
  322.         adc dl,TicksPrDay Shr 16
  323.  
  324. @@TimeOK:
  325.         inc si
  326.         mov [StuffPtr-MoveDown],si               ; Point to next byte
  327.  
  328.         sub dh,dh                       ; Fill top of DX with 0
  329.         or ax,dx
  330.          jz @@WaitZero                  ; Special case, wait for empty kbd
  331.  
  332.         mov [CountLow-MoveDown],ax
  333.         mov [CountHigh-MoveDown],dl
  334.         mov [StuffMode-MoveDown], OFFSET CountDown - MoveDown
  335.         ret
  336.  
  337. @@WaitZero:
  338.         mov [StuffMode-MoveDown], OFFSET WaitEmpty - Movedown
  339.         ret
  340. GetTime endp
  341.  
  342. CountDown proc near
  343.         DEC [CountLow-MoveDown]
  344.          jnz NoChange
  345.         SUB [CountHigh-MoveDown],1
  346.          jae NoChange
  347.  
  348. StartNextKey:
  349.         mov [StuffMode-MoveDown], OFFSET NextKey - MoveDown
  350. NoChange:
  351.         ret
  352. CountDown endp
  353.  
  354. WaitEmpty proc near
  355.         mov ax,[BufferHead]
  356.         cmp ax,[BufferTail]
  357.          je StartNextKey
  358.  
  359.         ret
  360. WaitEmpty endp
  361.  
  362. StartPrompt:
  363.  
  364.         mov [StuffMode-MoveDown], offset ScanPrompt - MoveDown
  365.         mov [StuffPtr-MoveDown],si
  366.  
  367. ; si -> dx:BYTE, dy:BYTE, count:WORD, len:BYTE, attr:BYTE, st:BYTE * len
  368.  
  369. ScanPrompt:                             ; Find start posn'n
  370.  
  371.         mov si,[StuffPtr-MoveDown]
  372.  
  373.         lodsw                           ; AL = dx, AH = dy
  374.         mov cx,[Cursor]                 ; CL = X,  CH = y
  375.         sub cl,al
  376.          jae @@1
  377.         sub cl,cl
  378. @@1:
  379.         sub ch,ah
  380.          jae @@2
  381.         sub ch,ch
  382. @@2:
  383.         mov al,byte ptr [CrtWidth]
  384.         mul ch                          ; AX = Y-offset
  385.         sub ch,ch
  386.         add ax,cx
  387.         shl ax,1                        ; AX = Start of scan
  388.  
  389.          jmp short Scan1
  390.  
  391. StartFind:
  392.         mov [StuffMode-MoveDown], offset ScanText - MoveDown
  393.         mov [StuffPtr-MoveDown],si
  394.  
  395. ; si -> start:WORD, count:WORD, len:BYTE, attr:BYTE, st:BYTE * len
  396.  
  397. ScanText proc near
  398.  
  399.         mov si,[StuffPtr-MoveDown]
  400.         lodsw                           ; starting offset in screen
  401.  
  402. Scan1:
  403.         mov di,ax
  404.         lodsw
  405.         mov cx, ax                      ; # of char cells to search
  406.  
  407.         lodsw                           ; AL = len, AH = attr
  408.         mov dl,al
  409.         xor dh,dh                       ; Text len
  410.  
  411.         dec dx                          ; Skip first char in length
  412.  
  413.         mov bx, 0B000h
  414.         cmp [VideoMode],7
  415.          je @@1
  416.         cmp [VideoMode],3
  417.          ja @@done
  418.         mov bh,0B8h
  419. @@1:
  420.         mov es,bx                       ; ES -> video segment
  421. ASSUME ES:NOTHING
  422.  
  423.         inc si                          ; Skip first char
  424.         cmp ah,255                      ; ATTR = 255 -> no attr
  425.          je @@FindChar
  426.  
  427. @@FindCharAttr:
  428.         mov al,[si-1]                   ; First char to match
  429.   repne scasw
  430.          jne @@done
  431.         or dx,dx                        ; Remaining length = 0
  432.          jz @@found
  433.  
  434.         push cx
  435.         push si
  436.         push di
  437.         mov cx,dx
  438. @@l2:
  439.         lodsb
  440.         scasw
  441.          loope @@l2
  442.  
  443.         pop di
  444.         pop si
  445.         pop cx
  446.          je @@found                     ; Yes, all chars match!
  447.          jcxz @@done                    ; No more room!
  448.          jne @@FindCharAttr             ;  this BUG was found by rbabcock!
  449.  
  450. @@FindChar:
  451.         mov al,[si-1]
  452.         dec di
  453. @@l3:
  454.          jcxz @@done                    ; No more room to start in!
  455. @@l31:
  456.         inc di
  457.         scasb
  458.          loopne @@l31
  459.  
  460.          jne @@done
  461.  
  462.         or dx,dx
  463.          jz @@found
  464.  
  465.         push cx
  466.         push si
  467.         push di
  468.         mov cx,dx
  469. @@l4:
  470.         inc di
  471.         cmpsb
  472.          loope @@l4
  473.  
  474.         pop di
  475.         pop si
  476.         pop cx
  477.          jne @@l3
  478.  
  479. @@found:
  480.         add si,dx                       ; Point after text to search for
  481.         mov [StuffMode-MoveDown], offset NextKey - MoveDown
  482.         mov [StuffPtr-MoveDown],si
  483. @@done:
  484.         ret
  485. ScanText Endp
  486.  
  487.         ALIGN 2
  488.  
  489. BiosSeg         dw 40h
  490.  
  491. FirstByteToCopy label byte
  492.  
  493. StuffPtr        dw OFFSET StuffBuffer - MoveDown
  494. StuffEnd        dw ?
  495. StuffMode       dw OFFSET NextKey - MoveDown
  496.  
  497. CountLow        dw ?
  498. CountHigh       db ?                    ; Use 24 bits for tick counter
  499.  
  500. active          db -1
  501.  
  502. ResidentEnd     EQU $
  503.  
  504. StuffBuffer     LABEL byte
  505.  
  506. StartMsg db 'StuffIt V',VerStr,' (C) Terje Mathisen 1989,1991,1992',13,10,'$'
  507.  
  508. SyntaxMsg label byte
  509.  
  510. db 'Syntax: Stuffit <commands>',13,10
  511. db '  +|=[[hh:]mm:]ss   : Delay for(+) or until(=) a specified time.',13,10
  512. db '                         +45 will wait for 45 seconds.',13,10
  513. db '                         =14:: will wait until 2pm.',13,10
  514. db '                         +0 will wait until the kbd buffer is empty.',13,10
  515. db '  <character code>  : Stuff a character code. 27=<Esc>,13=<CR> etc.',13,10
  516. db '  @<scan code>      : Stuff a given scan code (char=0). @68=F10,@73=PgUp etc.',13,10
  517. db '  <char>:<scan>     : Specify both character and scan. 43:74 = <Num+>',13,10
  518. db "  'TEXT'",' or "TEXT"  : Stuff all the characters in TEXT',13,10
  519. db '  Fx,y,n[,attr],"TEXT" : Find "TEXT" in an area starting at (X,Y),',13,10
  520. db "  (or {Find}x,y etc)     and being (N) char's long. (Top left = 1,1)",13,10
  521. db '                         Ignore text attributes, unless <attr> is specified.',13,10
  522. db '  Pdx,dy,n[,attr],"TEXT" : Find "TEXT", starting at',13,10
  523. db "  (or {Prompt}dx, etc)     CursorX-DX,CursorY-DY. OBS! Negative coordinates!",13,10
  524. db '  !                 : Reboot. (=0 ! will reboot at midnight.)',13,10
  525. db '  /F:FileName       : Read commands from <FileName>.',13,10
  526. db '  /B:nnnn           : Allocate room for <nnnn> bytes in TSR(512 default).',13,10
  527. db '  /R                : Remove (Unload) StuffIt from RAM.',13,10
  528. db ' {KeyName}|[KeyName]: Stuff <KeyName>. ({F1},{^Left},{Home} etc.)',13,10
  529. db '  /L                : List all mnemonic key names.',13,10
  530. db '  /Sab              : Use "ab" as signature. Default is "ST"',13,10
  531. db '  /E                : Use Expanded kbd buffer pointers at 40:80 & 40:82',13,10
  532. db '$'
  533.  
  534. ErrorMsg1 db 'SYNTAX ERROR ON LINE $'
  535.  
  536. ErrorMsg2 label byte
  537. CRLF db 13,10,'$'
  538.  
  539. UpdateMsg       db 'Resident copy updated!',13,10,'$'
  540.  
  541. StayResMsg      db 'Resident code loaded!',13,10,'$'
  542.  
  543. RemovedMsg      db 'Resident code removed from RAM!',13,10,'$'
  544.  
  545. WrongVerMsg db 'A different version of StuffIt is already resident!',13,10,'$'
  546.  
  547. NotRemovedMsg  label byte
  548.  db 'Resident code cannot be removed, as another '
  549.  db   'program is using the',13,10
  550.  db 'Timer (Int 8) and/or the Multiplex (Int 2F) '
  551.  db   'vector. Please remove all',13,10
  552.  db 'programs loaded after StuffIt, '
  553.  db   'and retry the operation.',13,10,'$'
  554.  
  555. RamErrMsg label byte
  556.  db 'Not enough RAM! (Need at least 128 kB to initialize program.)',13,10,'$'
  557.  
  558. FileErrMsg label byte
  559.  db 'Error reading input file!',13,10,'$'
  560.  
  561. ResidentToSmallMsg label byte
  562.  db 'Resident buffer to small! Try to remove it (/R) and reload.',13,10,'$'
  563.  
  564. ;FirstBlock      dw ?
  565. ;SecondBlock     dw ?
  566.  
  567.         JUMPS        ; Allow inefficient code in transient part of code!
  568.                      ; This makes it more readable.
  569.  
  570. Init proc near
  571. ASSUME DS:CODE, ES:CODE
  572.  
  573.         mov     ax,OFFSET StuffBuffer - MoveDown + 512 + 15
  574.         and     ax,0FFF0h
  575.         mov     [ResidentSize], ax
  576.  
  577. ; Start by relocating the program into a second segment
  578.  
  579. ;        mov     [FirstBlock],CS         ; Save segment addr
  580.  
  581.         mov     ah,4Ah
  582.         mov     bx,2000h                ; Realloc to 128 kB
  583. ;       mov     es,[FirstBlock]
  584.         int     21h
  585.         mov     dx, offset RamErrMsg
  586.          jc     ErrorMessage
  587.  
  588. ;        mov     ah,48h
  589. ;        mov     bx,1000h
  590. ;        int     21h                     ; Alloc second 64kB block
  591. ;        mov     dx, offset RamErrMsg
  592. ;         jc     ErrorMessage
  593.  
  594.         mov     ax,cs
  595.         add     ax,1000h                ; Point after 1st 64kB
  596.  
  597.         mov     es,ax
  598. ;        mov     [SecondBlock],es
  599.         sub     si,si
  600.         mov     di,si
  601.         mov     cx,(OFFSET ProgramEnd - OFFSET PspStart + 1) Shr 1
  602.         cld
  603.         rep     movsw
  604.  
  605.         push    es
  606.         mov     ax, OFFSET Continue
  607.         push    ax
  608.         retf
  609.  
  610. ;        pop     cs                      ; Jump into second copy of program!
  611. Continue:
  612.         push    ds
  613.         push    cs
  614.         pop     ds
  615.         pop     es                      ; DS=CS = SecondBlock, ES=FirstBlock
  616.  
  617. ; Move resident part of code as low as possible:
  618.  
  619.         mov     si, OFFSET HighStart
  620.         mov     di, OFFSET LowStart
  621.         mov     cx, OFFSET ResidentEnd - OFFSET HighStart
  622.         rep     movsb
  623.  
  624.         mov dx, OFFSET StartMsg
  625.         mov ah,9
  626.         int 21h
  627.  
  628.         call Parse
  629.  
  630.         cmp di, OFFSET StuffBuffer - MoveDown
  631.         mov dx, offset SyntaxMsg
  632.          je ErrorMessage                ; No parameters!
  633.  
  634.         push    es
  635.         pop     ds                      ; DS,ES,SS = FirstBlock
  636.  
  637.         mov [StuffEnd-MoveDown],di
  638. ;       mov [StuffMode-MoveDown], OFFSET NextKey - MoveDown
  639. ;       mov [StuffPtr-MoveDown], OFFSET StuffBuffer - MoveDown
  640.  
  641. ;       mov [active-MoveDown],-1                 ; Initialize [active] flag
  642. ;       mov [BiosSeg-MoveDown],40h               ; Fast load of ES: when resident
  643.  
  644.         call TestSecond                 ; Don't return if second copy!
  645.  
  646. ; Get old int 2F interrupt
  647.  
  648.         mov ax,352Fh
  649.         int 21h
  650.         mov WORD PTR [OldInt2F-MoveDown],BX
  651.         mov WORD PTR [OldInt2F+2-MoveDown],ES
  652.  
  653. ; Get old timer interrupt
  654.         mov ax,3508h
  655.         int 21h
  656.         mov WORD PTR [OldTimer-MoveDown],BX
  657.         mov Word Ptr [OldTimer+2-Movedown],ES
  658.  
  659. ; Enter our routine first
  660.  
  661.         mov ax,252Fh
  662.         mov dx, OFFSET Int2F - MoveDown
  663.         int 21h
  664.  
  665.         mov ax,2508h
  666.         mov dx, OFFSET MyTimer - MoveDown
  667.         int 21h
  668.  
  669.         mov ES, [DS:2Ch]
  670.         mov ah,49h
  671.         int 21h
  672.         mov word ptr [DS:2Ch],0         ; Signal no environment!
  673.  
  674.         push ds
  675.         push cs
  676.         pop ds
  677.         mov dx, OFFSET StayResMsg
  678.         mov ah,9
  679.         int 21h
  680.         pop ds
  681.  
  682.         mov cx,5
  683. @@CloseLoop:
  684.         mov bx,cx
  685.         dec bx
  686.         mov ah,3Eh
  687.         int 21h
  688.          loop @@CloseLoop
  689.  
  690.         push ss
  691.         pop ds
  692.         mov dx, [DS:ResidentSize]
  693.         cmp dx, [DS:Stuffend-MoveDown]
  694.          ja @@OK
  695.         mov dx, [DS:StuffEnd-MoveDown] ; DX = MAX(ResidentSize, StuffEnd)
  696.         mov [DS:ResidentSize],dx
  697. @@OK:
  698.         add dx,15
  699.         mov cl,4
  700.         shr dx,cl
  701.         mov ax,3100h
  702.         int 21h                         ; Go TSR with first block
  703.  
  704. Init Endp
  705.  
  706. FindFirst proc near
  707.  
  708.         mov dx,Semafor
  709. current = $
  710.         org $-2
  711. Semafor_2 label word
  712.         org current
  713.  
  714.         mov ax,0E000h
  715.         xor bx,bx
  716.         int 2Fh
  717.  
  718.         cmp al,0FFh
  719.          jne @@done
  720.  
  721.         cmp bx, VerNr
  722.          je @@done
  723.  
  724.         mov dx, offset WrongVerMsg
  725.          jmp ErrorMessage
  726.  
  727. @@done:
  728.         ret                             ; Return ZERO if found
  729.  
  730. FindFirst endp
  731.  
  732. TestSecond proc near
  733.         call FindFirst
  734.          jne @@NotFound
  735.  
  736.         mov es,dx                       ; Save segment
  737.  
  738. ; This is the second copy! ES -> to first copy
  739. ; Test if enough room in resident program:
  740.  
  741.         mov ax,[StuffEnd-MoveDown]
  742.         cmp ax,[ES:ResidentSize]
  743.         mov dx, OFFSET ResidentToSmallMsg
  744.          ja ErrorMessage
  745.  
  746. ; Will now move all data into first copy, including pointers and StuffMode
  747.  
  748.         mov [ES:SelfModify-MoveDown], IRET_OPCODE ; Stop resident program
  749.         mov [DS:SelfModify-MoveDown], IRET_OPCODE ; Stop this version!
  750.  
  751.         mov si, offset FirstByteToCopy - MoveDown
  752.         mov di, si
  753.         mov cx, ax                      ; [StuffEnd]
  754.         sub cx,si
  755.         rep movsb
  756.  
  757.         mov [ES:SelfModify-MoveDown], PUSH_AX_OPCODE ; Restart resident version
  758.  
  759.         mov dx, OFFSET UpdateMsg
  760.         mov ah,9
  761.         int 21h
  762.  
  763.         mov ax,4C00h
  764.         int 21h
  765.  
  766. @@NotFound:
  767. ParseFinish:
  768.         ret
  769.  
  770. TestSecond ENDP
  771.  
  772. LocalSyntax:
  773.         jmp Syntax
  774.  
  775. EOF     EQU 26
  776. LF      EQU 10                  ; Use LineFeed to count lines
  777.  
  778. LineNr  dw      ?
  779.  
  780. Parse Proc near
  781.         cld
  782.         mov si, OFFSET CommandLine
  783.  
  784.         mov bl,[si-1]
  785.         sub bh,bh
  786.         mov word ptr [si+bx],13 + (EOF * 256) ; EOF is ending marker
  787.  
  788. RestartParse:
  789.         mov di, OFFSET StuffBuffer - MoveDown
  790.  
  791.         mov     [LineNr],0
  792. NewLine:
  793.         inc     [LineNr]
  794.  
  795. ParseNextChar:
  796.         lodsb
  797.  
  798.         cmp al,EOF
  799.          je ParseFinish
  800.  
  801.         cmp     al,LF
  802.          je     NewLine                 ; LF marks the end of one line
  803.  
  804.         cmp     al,';'                  ; ';' makes the rest of the line a
  805.          je     @@Comment               ; comment
  806.  
  807.         cmp     al,' '
  808.          jbe    ParseNextChar
  809.  
  810.         cmp al,'0'
  811.          jb @@NotDigit
  812.         cmp al,'9'
  813.          ja @@NotDigit
  814. @@Digit:
  815.         dec si
  816.         mov bx,255
  817.         call GetNumber
  818.         cmp al,':'
  819.         mov al,bl
  820.         mov ah,2
  821.          jne @@NotTwo
  822.  
  823.         push ax
  824.         mov bx,255
  825.         call GetNumber
  826.         cmp al,':'
  827.          je LocalSyntax
  828.  
  829.         pop ax
  830.         mov ah,bl
  831.  
  832. @@NotTwo:
  833.         Call SaveChar
  834.          jmp ParseNextChar
  835.  
  836. @@Comment:
  837.         lodsb
  838.  
  839.         cmp     al,EOF
  840.          je     ParseFinish
  841.  
  842.         cmp     al,13
  843.          je     ParseNextChar
  844.  
  845.         cmp     al,LF
  846.          jne    @@Comment
  847.  
  848.         jmp     NewLine
  849.  
  850. @@NotDigit:
  851.         cmp al,'/'
  852.          je ParseOption
  853.         cmp al,'-'
  854.          je ParseOption
  855.  
  856.         cmp al,'@'
  857.         jne @@NotFunc
  858.         mov bx,255
  859.         call GetNumber
  860.         mov ah,bl
  861.         xor al,al
  862.         stosw
  863.          jmp ParseNextChar
  864.  
  865. @@NotFunc:
  866.         cmp al,"'"
  867.          je @@Quote
  868.         cmp al,'"'
  869.          jne @@NotQuote
  870. @@Quote:
  871.         mov bl,al                       ; Save starting quote char
  872. @@2:
  873.         lodsb
  874.         cmp al,13                       ; Missing last quote
  875.          je Syntax
  876.         cmp al,bl                       ; Ending quote?
  877.          je ParseNextChar               ; Yes, restart
  878.         mov ah,2                        ; Assume scan = 2
  879.         call SaveChar
  880.          jmp @@2
  881.  
  882. @@NotQuote:
  883.         cmp al,'+'
  884.          jne @@3
  885.          jmp DeTime
  886. @@3:
  887.         cmp al,'='
  888.          jne @@4
  889.          jmp AtTime
  890.  
  891. @@4:
  892. ; Use ! to signal Reboot
  893.         cmp al,'!'
  894.          je SignalReboot
  895.  
  896.         cmp al,'f'
  897.          je FindNear
  898.         cmp al,'F'
  899.          je FindNear
  900.  
  901.         cmp al,'p'
  902.          je PromptNear
  903.         cmp al,'P'
  904.          je PromptNear
  905.  
  906.         cmp  al,'{'                     ; Start of token!
  907.          je  StartToken
  908.         cmp  al,'['                     ; Start of token!
  909.          je  StartToken
  910.  
  911.  
  912. ; Fall into syntax error!
  913. Parse endp
  914.  
  915. Syntax Proc near
  916.         push    cs
  917.         pop     ds
  918.  
  919.         mov     dx, OFFSET SyntaxMsg
  920.         mov     ah,9
  921.         int     21h
  922.  
  923.         mov     dx, OFFSET ErrorMsg1
  924.         mov     ah,9
  925.         int     21h
  926.  
  927.         mov     ax,[LineNr]
  928.         xor     cx,cx
  929.         call    PrintAX
  930.         mov     dx, OFFSET ErrorMsg2
  931.  
  932. ErrorMessage:
  933.         push cs
  934.         pop ds
  935.  
  936.         mov     ah,9
  937.         int     21h
  938.  
  939.         mov ax,4C01h
  940.         int 21h
  941. Syntax Endp
  942.  
  943. StartToken:
  944.         push    es di
  945.  
  946.         push    ds
  947.         pop     es
  948.  
  949.         call    GetToken        ; Return token, with orig. case if a single
  950.         call    FindToken       ; letter, then look it up.
  951.         pop     di es
  952.          jc     Syntax
  953.  
  954.         cmp     al,EXTENDED_CODE
  955.          je     @@SaveExtended
  956.  
  957.         call    SaveChar
  958.         jmp     ParseNextChar
  959.  
  960. @@SaveExtended:
  961.         cmp     ah,ATTIME_CODE
  962.          je     AtTime
  963.         cmp     ah,DELTATIME_CODE
  964.          je     DeTime
  965.         cmp     ah,FIND_CODE
  966.          je     FindNear
  967.         cmp     ah,PROMPT_CODE
  968.          je     PromptNear
  969. ;        cmp     ah,PRTSCRN_CODE
  970. ;         je     @@Save2
  971. ;        cmp     ah,REBOOT_CODE
  972. ;         je     @@Save2
  973. @@Save2:
  974.         stosw
  975.         jmp     ParseNextChar
  976.  
  977. FindNear:
  978.         jmp FindText
  979.  
  980. PromptNear:
  981.         jmp PromptText
  982.  
  983. SignalReBoot:
  984.         mov ax,EXTENDED_CODE + (REBOOT_CODE * 256)
  985.         stosw
  986.          jmp ParseNextChar
  987.  
  988. ReadFile proc near
  989.  
  990.         cmp byte ptr [si],':'
  991.          jne @@Skip
  992.         inc si
  993. @@Skip:
  994.         mov dx,si
  995. @@Next:
  996.         lodsb
  997.         cmp al,' '
  998.          ja @@Next
  999.         dec si
  1000.  
  1001.         mov ax,3D00h                    ; Open file for Read_Only
  1002.         mov byte ptr [si],al            ; Make ASCIIZ filename
  1003.  
  1004.         int 21h
  1005.         mov dx, OFFSET FileErrMsg
  1006.          jc ErrorMessage
  1007.  
  1008.         mov bx,ax
  1009.         mov ah,3Fh                      ; Read File
  1010.         mov dx, OFFSET ProgramEnd
  1011.         mov si,dx
  1012.         mov cx, - (OFFSET ProgramEnd)   ; Max Size in segment
  1013.         int 21h
  1014.         mov dx, OFFSET FileErrMsg
  1015.          jc ErrorMessage
  1016.  
  1017.         add si, ax
  1018.         mov byte ptr [si],EOF
  1019.         sub si, ax                      ; Point back to start of filebuffer
  1020.  
  1021.         mov ah,3Eh
  1022.         int 21h                         ; Close this file
  1023.  
  1024.          jmp RestartParse               ; Parse file buffer!
  1025. ReadFile endp
  1026.  
  1027. SetBufferSize proc near
  1028.  
  1029.         cmp byte ptr [si],':'
  1030.          jne @@Skip
  1031.         inc si
  1032. @@Skip:
  1033.         mov bx,-( OFFSET StuffBuffer - MoveDown) + 32; Max text buffer
  1034.         call GetNumber
  1035.         add bx,OFFSET StuffBuffer - MoveDown + 15
  1036.         and bx,0FFF0h
  1037.         mov [ES:ResidentSize],bx
  1038.          jmp ParseNextChar
  1039.  
  1040. SetBufferSize endp
  1041.  
  1042. SetSemafor proc    near
  1043.         lodsw
  1044.         mov [es:Semafor_1 - MoveDown],ax
  1045.         mov [Semafor_2],ax
  1046.          jmp ParseNextChar
  1047. SetSemafor endp
  1048.  
  1049. UseExpandedBuffer proc near
  1050.         push ds
  1051.         mov ds,[BiosSeg]
  1052.  
  1053.         mov ax,[DS:KbdBufferStart]      ; Keyboard Buffer start address
  1054.         mov [ES:BufferStartAddr-2-MoveDown],ax
  1055.         mov ax,[DS:KbdBufferEnd]        ; Keyboard Buffer end address
  1056.         mov [ES:BufferEndAddr-2-MoveDown],ax
  1057.  
  1058.         pop ds
  1059.          jmp ParseNextChar
  1060. UseExpandedBuffer endp
  1061.  
  1062. ParseOption Proc near
  1063.         lodsb
  1064.         cmp al,'a'
  1065.          jb @@Upper
  1066.         cmp al,'z'
  1067.          ja @@Upper
  1068.         sub al,'a'-'A'
  1069. @@Upper:
  1070.         cmp al,'B'
  1071.          je SetBufferSize
  1072.         cmp al,'F'
  1073.          je ReadFile
  1074.         cmp al,'L'
  1075.          je ListKeys
  1076.         cmp al,'S'
  1077.          je SetSemafor
  1078.         cmp al,'E'
  1079.          je UseExpandedBuffer
  1080.         cmp al,'R'
  1081.          jne Syntax
  1082. @@Remove:
  1083.         call FindFirst
  1084.          jnz Syntax                     ; First copy, nothing to remove!
  1085.  
  1086.         mov ax,3508h
  1087.         int 21h
  1088.         cmp bx, OFFSET MyTimer - MoveDown
  1089.          jne @@CannotRemove
  1090.         mov ax, es
  1091.         cmp ax, dx
  1092.          jne @@CannotRemove             ; Other TSR has timer vector!
  1093.  
  1094.         mov ax,352Fh
  1095.         int 21h
  1096.         cmp bx, OFFSET Int2F - MoveDown
  1097.          jne @@CannotRemove
  1098.         mov ax, es
  1099.         cmp ax, dx
  1100.          jne @@CannotRemove             ; Other TSR has Int 2F vector!
  1101.  
  1102. ; OK to remove previous copy from RAM
  1103. ; First, restore timer vector
  1104.  
  1105.         push ds
  1106.         mov dx,[word ptr ES:OldTimer-MoveDown]
  1107.         mov ds,[word ptr ES:OldTimer+2-MoveDown]
  1108.         mov ax,2508h
  1109.         int 21h
  1110.  
  1111. ; Then, retore Int 2F vector
  1112.  
  1113.         mov dx,[word ptr ES:OldInt2F-MoveDown]
  1114.         mov ds,[word ptr ES:OldInt2F+2-MoveDown]
  1115.         mov ax,252Fh
  1116.         int 21h
  1117.  
  1118.         pop ds
  1119.  
  1120. ; Next, release the memory segment
  1121. ; ES -> to previos copy!
  1122.  
  1123.         mov ah,49h
  1124.         int 21h
  1125.  
  1126.         mov dx, OFFSET RemovedMsg
  1127.         mov ah,9
  1128.         int 21h
  1129.  
  1130.         mov ax,4C00h
  1131.         int 21h
  1132.  
  1133. @@CannotRemove:
  1134.         mov dx, OFFSET NotRemovedMsg
  1135.         mov ah,9
  1136.         int 21h
  1137.  
  1138.         mov ax,4C01h
  1139.         int 21h
  1140.  
  1141. ParseOption Endp
  1142.  
  1143. hour    dw ?
  1144. min     dw ?
  1145. sec     dw ?
  1146.  
  1147. DeTime Proc near
  1148.         mov ax,EXTENDED_CODE + (DELTATIME_CODE * 256)
  1149.          jmp short ParseTime
  1150. AtTime:
  1151.         mov ax,EXTENDED_CODE + (ATTIME_CODE * 256)
  1152.  
  1153. ParseTime:
  1154.         stosw                           ; Save marker for time
  1155.  
  1156. ; FIX BUG found by davidgb. HOUR and Min MUST be initialized to ZERO!
  1157.  
  1158.         xor ax,ax
  1159.         mov [hour],ax
  1160.         mov [min],ax
  1161.  
  1162. ; END OF bug-fix
  1163.  
  1164.         mov bx,59                       ; Max value
  1165.         call GetNumber
  1166.         cmp al,':'
  1167.          jne @@SaveSec
  1168.  
  1169.         mov [min],bx
  1170.         mov bx,59
  1171.         call GetNumber
  1172.         cmp al,':'
  1173.          jne @@SaveSec
  1174.  
  1175.         xchg bx,[min]
  1176.         mov [hour],bx
  1177.         mov bx,59
  1178.         call GetNumber
  1179.         cmp al,':'
  1180.          je NearError
  1181.  
  1182. @@SaveSec:
  1183.         mov [sec],bx
  1184.  
  1185. ; Now convert hour:min:sec into Timer ticks:
  1186. ; Ticks= (hour*TicksPrHour) + (((min*60)+sec) * 34829 + 956) DIV 1913
  1187. ; 34829 / 1913 is 18.206482, which is the closest possible result to
  1188. ; the true value of 18.206493 Ticks/second, using only 16 bit mul and div.
  1189.  
  1190.         mov ax,(TicksPrHour - 65536)
  1191.         mul [hour]
  1192.         add dx,[hour]                   ; DX:AX = hour*TicksPrHour
  1193.         push ax                         ; Save DX:AX
  1194.         push dx                         ; --- " ---
  1195.  
  1196.         mov al,60
  1197.         mul [byte ptr min]
  1198.         add ax,[sec]                    ; AX has # of seconds
  1199.  
  1200.         mov dx,34829
  1201.         mul dx
  1202.         add ax,1913 Shr 1               ; Add 1913/2 to get automatic rounding
  1203.         adc dx,0                        ; of fractional timer ticks
  1204.         mov bx,ax
  1205.         mov ax,dx
  1206.         xor dx,dx
  1207.         mov cx,1913
  1208.         div cx
  1209.         xchg ax,bx
  1210.         div cx                          ; BX:AX = Ticks in (min*60+sec)
  1211.  
  1212.         pop dx
  1213.         pop cx                          ; DX:CX = Ticks in hours
  1214.  
  1215.         add ax,cx
  1216.         adc dx,bx                       ; DX:AX = Total Ticks
  1217.  
  1218.         stosw                           ; Save Low word of count
  1219.         mov al,dl
  1220.         stosb                           ; Save high byte of count
  1221.  
  1222.          jmp ParseNextChar
  1223. DeTime Endp
  1224.  
  1225. PromptText:
  1226.         mov ax,EXTENDED_CODE + (PROMPT_CODE * 256)
  1227.         stosw
  1228.         mov bx,127
  1229.         call GetNumber
  1230.         cmp al,','
  1231.          jne NearError
  1232.         mov al,bl                       ; Save X value
  1233.         stosb
  1234.  
  1235.         mov bx,60                       ; 0<y<=60
  1236.         call GetNumber
  1237.         cmp al,','
  1238.          jne NearError
  1239.         mov al,bl
  1240.         stosb                           ; Save Y value
  1241.  
  1242.          jmp short Text1
  1243.  
  1244. NearError:
  1245.         jmp syntax
  1246.  
  1247. FindText proc near
  1248.         mov ax,EXTENDED_CODE + (FIND_CODE * 256) ; 255 + 3 -> flag for Find Text
  1249.         stosw
  1250.  
  1251.         mov bx,132                      ; 0<x<=132
  1252.         call GetNumber
  1253.         cmp al,','
  1254.          jne GetError
  1255.         sub bl,1
  1256.          jb GetError
  1257.         mov cx,bx                       ; Save X value
  1258.  
  1259.         mov bx,60                       ; 0<y<=60
  1260.         call GetNumber
  1261.         cmp al,','
  1262.          jne GetError
  1263.         sub bl,1
  1264.          jb GetError
  1265.  
  1266.         mov al,80
  1267.         mul bl
  1268.         add ax, cx
  1269.         shl ax, 1
  1270.         stosw                           ; Save X,Y as starting offset
  1271.  
  1272. Text1:
  1273.         mov bx,(132*60)                 ; Get length to search in
  1274.         call GetNumber
  1275.         cmp al,','
  1276.          jne NearError
  1277.         or bx,bx
  1278.          jz NearError                   ; Count must be > 0
  1279.  
  1280.         mov ax,bx
  1281.         stosw                           ; Save buffer length
  1282.  
  1283.         mov bx,255
  1284.         cmp byte ptr [si],'"'
  1285.          je @@SkipAttr
  1286.         cmp byte ptr [si],"'"
  1287.          je @@SkipAttr
  1288.  
  1289.         call GetNumber
  1290.         cmp al,','
  1291.          jne NearError
  1292.  
  1293. @@SkipAttr:
  1294.         mov ah,bl
  1295.         lodsb
  1296.         cmp al,'"'
  1297.          je @@1
  1298.         cmp al,"'"
  1299.          jne GetError
  1300. @@1:
  1301.         mov bx,di                       ; Save current pos for len
  1302.         stosw                           ; Store len + attr
  1303.  
  1304.         mov ah,al
  1305.         xor cx,cx
  1306. @@2:
  1307.         lodsb
  1308.         cmp al,EOF
  1309.          je GetError
  1310.         cmp al,13
  1311.          je GetError
  1312.         cmp al,ah
  1313.          je @@3
  1314.  
  1315.         inc cx                          ; INC len
  1316.         stosb                           ; Save Text
  1317.         jmp @@2
  1318.  
  1319. @@3:
  1320.         mov [ES:bx],cl                  ; Save actual length!
  1321.  
  1322.         jmp ParseNextChar
  1323.  
  1324. FindText endp
  1325.  
  1326.  
  1327. GetError:
  1328.         jmp syntax
  1329.  
  1330. GetNumber proc near
  1331. ; input: SI -> first char to convert, BX = max value
  1332.         push cx                         ; Use as temp buffer
  1333.         push dx                         ; For mul
  1334.         push di                         ; For sign flag
  1335.  
  1336.         sub cx,cx
  1337.         mov ah,ch
  1338.         mov di,cx                       ; Zero DI -> Positive
  1339.  
  1340.         cmp byte ptr [si],'-'
  1341.          jne @@GetLoop
  1342.         dec di
  1343.         inc si
  1344.  
  1345. @@GetLoop:
  1346.         lodsb
  1347.         cmp al,' '
  1348.          jbe @@GetEnd
  1349.         cmp al,':'
  1350.          je @@GetEnd
  1351.         cmp al,','
  1352.          je @@GetEnd
  1353.  
  1354.         sub al,'0'
  1355.          jb GetError
  1356.         cmp al,9
  1357.          ja GetError
  1358.  
  1359. ; Valid decimal digit!
  1360.         xchg ax,cx
  1361.         mov dx,10
  1362.         mul dx
  1363.         add cx,ax
  1364.          jmp @@GetLoop
  1365. @@GetEnd:
  1366.         cmp al,EOF
  1367.          jne @@1
  1368.         dec si                          ; Prepare to reload AL
  1369. @@1:
  1370.         cmp cx,bx                       ; Valid value?
  1371.          ja GetError
  1372.         mov bx,cx
  1373.  
  1374.         or di,di
  1375.          jz @@done
  1376.  
  1377.         neg bx                          ; return -BX
  1378.  
  1379. @@done:
  1380.         pop di
  1381.         pop dx
  1382.         pop cx
  1383.  
  1384.         ret
  1385. GetNumber endp
  1386.  
  1387. SaveChar proc near
  1388. ; AL, AH = char, scan to save in StuffBuffer
  1389.  
  1390.         or al,al
  1391.          je @@Save2
  1392.         cmp al,224
  1393.          je @@Save2
  1394.  
  1395.         cmp al,254
  1396.          jae @@Save3
  1397.         cmp ah,2                        ; Scan for normal chars
  1398.          jne @@Save3
  1399.  
  1400.         mov ah,14
  1401.         cmp al,8
  1402.          je @@Save3
  1403.         mov ah,15
  1404.         cmp al,9
  1405.          je @@Save3
  1406.         mov ah,28
  1407.         cmp al,13
  1408.          je @@Save3
  1409.         mov ah,1
  1410.         cmp al,27
  1411.          je @@Save3
  1412.  
  1413. ; Normal character, store just the char itself
  1414.  
  1415.         stosb
  1416.         ret
  1417.  
  1418. @@Save2:
  1419.         stosw
  1420.         ret
  1421.  
  1422. @@Save3:
  1423.         mov byte ptr [es:di],254
  1424.         inc di
  1425.         stosw
  1426.         ret
  1427. SaveChar endp
  1428.  
  1429. MaxTokenSize = 8
  1430.  
  1431. CurToken   db MaxTokenSize dup (0)
  1432.  
  1433. TokenTable      label byte
  1434.  
  1435. T_S     struc
  1436. tname   db      MaxTokenSize dup (0)
  1437. tchr    db      ?
  1438. tscn    db      ?
  1439.         ends
  1440.  
  1441.         T_S     <"ESC",27,1>
  1442.  
  1443. TokenRecSize    = $ - offset TokenTable
  1444.  
  1445.         T_S     <"aEsc",0,1>
  1446.  
  1447.         T_S     <"^@",0,3>
  1448.         T_S     <"^A",1,30>
  1449.         T_S     <"^B",2,48>
  1450.         T_S     <"^C",3,46>
  1451.         T_S     <"^D",4,32>
  1452.         T_S     <"^E",5,18>
  1453.         T_S     <"^F",6,33>
  1454.         T_S     <"^G",7,34>
  1455.         T_S     <"^H",8,35>
  1456.         T_S     <"^I",9,23>
  1457.         T_S     <"^J",10,36>
  1458.         T_S     <"^K",11,37>
  1459.         T_S     <"^L",12,38>
  1460.         T_S     <"^M",13,50>
  1461.         T_S     <"^N",14,49>
  1462.         T_S     <"^O",15,24>
  1463.         T_S     <"^P",16,25>
  1464.         T_S     <"^Q",17,16>
  1465.         T_S     <"^R",18,19>
  1466.         T_S     <"^S",19,31>
  1467.         T_S     <"^T",20,20>
  1468.         T_S     <"^U",21,22>
  1469.         T_S     <"^V",22,47>
  1470.         T_S     <"^W",23,17>
  1471.         T_S     <"^X",24,45>
  1472.         T_S     <"^Y",25,21>
  1473.         T_S     <"^Z",26,44>
  1474.         T_S     <"^[",27,26>
  1475.         T_S     <"^\",28,43>
  1476.         T_S     <"^]",29,27>
  1477.         T_S     <"^^",30,7>
  1478.         T_S     <"^_",31,12>
  1479.  
  1480.         T_S     <"NUL",0,3>
  1481.         T_S     <"SOH",1,30>
  1482.         T_S     <"STX",2,48>
  1483.         T_S     <"ETX",3,46>
  1484.         T_S     <"EOT",4,32>
  1485.         T_S     <"ENQ",5,18>
  1486.         T_S     <"ACK",6,33>
  1487.         T_S     <"BEL",7,34>
  1488. ;        T_S     <"BS",8,35>            ; Defined as BS key
  1489.         T_S     <"HT",9,23>             ; HT = ^H = TAB
  1490.         T_S     <"LF",10,36>
  1491.         T_S     <"VT",11,37>
  1492.         T_S     <"FF",12,38>
  1493. ;        T_S     <"CR",13,50>           ;  --"--     CR
  1494.         T_S     <"SO",14,49>
  1495.         T_S     <"SI",15,24>
  1496.         T_S     <"DLE",16,25>
  1497.         T_S     <"DC1",17,16>
  1498.         T_S     <"DC2",18,19>
  1499.         T_S     <"DC3",19,31>
  1500.         T_S     <"DC4",20,20>
  1501.         T_S     <"NAK",21,22>
  1502.         T_S     <"SYN",22,47>
  1503.         T_S     <"ETB",23,17>
  1504.         T_S     <"CAN",24,45>
  1505.         T_S     <"EM",25,21>
  1506.         T_S     <"SUB",26,44>
  1507. ;        T_S     <"ESC",27,26>          ;  --"--     ESC
  1508.         T_S     <"FS",28,43>
  1509.         T_S     <"GS",29,27>
  1510.         T_S     <"RS",30,7>
  1511.         T_S     <"US",31,12>
  1512.  
  1513. FirstChar       label byte              ; Use to translate characters into
  1514.                                         ; char:scan pairs
  1515.  
  1516.         T_S     <" ",32,57>
  1517.         T_S     <"!",33,2>
  1518.         T_S     <'"',34,40>
  1519.         T_S     <"#",35,4>
  1520.         T_S     <"$",36,5>
  1521.         T_S     <"%",37,6>
  1522.         T_S     <"&",38,8>
  1523.         T_S     <"'",39,40>
  1524.         T_S     <"(",40,10>
  1525.         T_S     <")",41,11>
  1526.         T_S     <"*",42,9>
  1527.         T_S     <"+",43,13>
  1528.         T_S     <",",44,51>
  1529.         T_S     <"-",45,12>
  1530.         T_S     <".",46,52>
  1531.         T_S     <"/",47,53>
  1532.  
  1533.         T_S     <"0",48,11>
  1534.         T_S     <"1",49,2>
  1535.         T_S     <"2",50,3>
  1536.         T_S     <"3",51,4>
  1537.         T_S     <"4",52,5>
  1538.         T_S     <"5",53,6>
  1539.         T_S     <"6",54,7>
  1540.         T_S     <"7",55,8>
  1541.         T_S     <"8",56,9>
  1542.         T_S     <"9",57,10>
  1543.         T_S     <":",58,39>
  1544.         T_S     <";",59,39>
  1545.         T_S     <"<",60,51>
  1546.         T_S     <"=",61,13>
  1547.         T_S     <">",62,52>
  1548.         T_S     <"?",63,53>
  1549.  
  1550.         T_S     <"@",'@',3>
  1551.         T_S     <"A",'A',30>
  1552.         T_S     <"B",'B',48>
  1553.         T_S     <"C",'C',46>
  1554.         T_S     <"D",'D',32>
  1555.         T_S     <"E",'E',18>
  1556.         T_S     <"F",'F',33>
  1557.         T_S     <"G",'G',34>
  1558.         T_S     <"H",'H',35>
  1559.         T_S     <"I",'I',23>
  1560.         T_S     <"J",'J',36>
  1561.         T_S     <"K",'K',37>
  1562.         T_S     <"L",'L',38>
  1563.         T_S     <"M",'M',50>
  1564.         T_S     <"N",'N',49>
  1565.         T_S     <"O",'O',24>
  1566.         T_S     <"P",'P',25>
  1567.         T_S     <"Q",'Q',16>
  1568.         T_S     <"R",'R',19>
  1569.         T_S     <"S",'S',31>
  1570.         T_S     <"T",'T',20>
  1571.         T_S     <"U",'U',22>
  1572.         T_S     <"V",'V',47>
  1573.         T_S     <"W",'W',17>
  1574.         T_S     <"X",'X',45>
  1575.         T_S     <"Y",'Y',21>
  1576.         T_S     <"Z",'Z',44>
  1577.         T_S     <"[",'[',26>
  1578.         T_S     <"\",'\',43>
  1579.         T_S     <"]",']',27>
  1580.         T_S     <"^",'^',7>
  1581.         T_S     <"_",'_',12>
  1582.  
  1583.         T_S     <"`",'`',41>
  1584.         T_S     <"a",'a',30>
  1585.         T_S     <"b",'b',48>
  1586.         T_S     <"c",'c',46>
  1587.         T_S     <"d",'d',32>
  1588.         T_S     <"e",'e',18>
  1589.         T_S     <"f",'f',33>
  1590.         T_S     <"g",'g',34>
  1591.         T_S     <"h",'h',35>
  1592.         T_S     <"i",'i',23>
  1593.         T_S     <"j",'j',36>
  1594.         T_S     <"k",'k',37>
  1595.         T_S     <"l",'l',38>
  1596.         T_S     <"m",'m',50>
  1597.         T_S     <"n",'n',49>
  1598.         T_S     <"o",'o',24>
  1599.         T_S     <"p",'p',25>
  1600.         T_S     <"q",'q',16>
  1601.         T_S     <"r",'r',19>
  1602.         T_S     <"s",'s',31>
  1603.         T_S     <"t",'t',20>
  1604.         T_S     <"u",'u',22>
  1605.         T_S     <"v",'v',47>
  1606.         T_S     <"w",'w',17>
  1607.         T_S     <"x",'x',45>
  1608.         T_S     <"y",'y',21>
  1609.         T_S     <"z",'z',44>
  1610.         T_S     <"{",'{',26>
  1611.         T_S     <"|",'|',43>
  1612.         T_S     <"}",'}',27>
  1613.         T_S     <"~",'~',7>
  1614.  
  1615.         T_S     <"æ",'æ',40>
  1616.         T_S     <"¢",'¢',39>
  1617.         T_S     <"å",'å',26>
  1618.         T_S     <"Æ",'Æ',40>
  1619.         T_S     <"¥",'¥',39>
  1620.         T_S     <"Å",'Å',26>
  1621.  
  1622. LastChar        label byte
  1623.  
  1624.         T_S     <"a1",0,120>
  1625.         T_S     <"a2",0,121>
  1626.         T_S     <"a3",0,122>
  1627.         T_S     <"a4",0,123>
  1628.         T_S     <"a5",0,124>
  1629.         T_S     <"a6",0,125>
  1630.         T_S     <"a7",0,126>
  1631.         T_S     <"a8",0,127>
  1632.         T_S     <"a9",0,128>
  1633.         T_S     <"a0",0,129>
  1634.         T_S     <"a-",0,130>
  1635.         T_S     <"a=",0,131>
  1636.  
  1637.         T_S     <"BS",8,14>
  1638.         T_S     <"aBS",0,14>
  1639.         T_S     <"^BS",127,14>
  1640.  
  1641.         T_S     <"Tab",9,15>
  1642.         T_S     <"sTab",0,15>
  1643.         T_S     <"^Tab",0,148>
  1644.  
  1645.         T_S     <"CR",13,28>
  1646.         T_S     <"^CR",10,28>
  1647.         T_S     <"aCR",0,28>
  1648.  
  1649.         T_S     <"aQ",0,16>
  1650.         T_S     <"aW",0,17>
  1651.         T_S     <"aE",0,18>
  1652.         T_S     <"aR",0,19>
  1653.         T_S     <"aT",0,20>
  1654.         T_S     <"aY",0,21>
  1655.         T_S     <"aU",0,22>
  1656.         T_S     <"aI",0,23>
  1657.         T_S     <"aO",0,24>
  1658.         T_S     <"aP",0,25>
  1659.         T_S     <"a[",0,26>
  1660.         T_S     <"a]",0,27>
  1661.  
  1662.         T_S     <"aA",0,30>
  1663.         T_S     <"aS",0,31>
  1664.         T_S     <"aD",0,32>
  1665.         T_S     <"aF",0,33>
  1666.         T_S     <"aG",0,34>
  1667.         T_S     <"aH",0,35>
  1668.         T_S     <"aJ",0,36>
  1669.         T_S     <"aK",0,37>
  1670.         T_S     <"aL",0,38>
  1671.         T_S     <"a;",0,39>
  1672.         T_S     <"a'",0,40>
  1673.         T_S     <"a`",0,41>
  1674.  
  1675.         T_S     <"a\",0,43>
  1676.         T_S     <"aZ",0,44>
  1677.         T_S     <"aX",0,45>
  1678.         T_S     <"aC",0,46>
  1679.         T_S     <"aV",0,47>
  1680.         T_S     <"aB",0,48>
  1681.         T_S     <"aN",0,49>
  1682.         T_S     <"aM",0,50>
  1683.         T_S     <"a,",0,51>
  1684.         T_S     <"a.",0,52>
  1685.         T_S     <"a/",0,53>
  1686.  
  1687.         T_S     <"a*",0,55>
  1688.  
  1689.         T_S     <"F1",0,59>
  1690.         T_S     <"F2",0,60>
  1691.         T_S     <"F3",0,61>
  1692.         T_S     <"F4",0,62>
  1693.         T_S     <"F5",0,63>
  1694.         T_S     <"F6",0,64>
  1695.         T_S     <"F7",0,65>
  1696.         T_S     <"F8",0,66>
  1697.         T_S     <"F9",0,67>
  1698.         T_S     <"F10",0,68>
  1699.  
  1700.         T_S     <"n/",'/',224>
  1701.         T_S     <"n*",'*',55>
  1702.         T_S     <"n7",'7',74>
  1703.         T_S     <"n8",'8',72>
  1704.         T_S     <"n9",'9',73>
  1705.         T_S     <"n-",'-',74>
  1706.         T_S     <"n4",'4',75>
  1707.         T_S     <"n5",'5',76>
  1708.         T_S     <"n6",'6',77>
  1709.         T_S     <"n+",'+',78>
  1710.         T_S     <"n1",'1',79>
  1711.         T_S     <"n2",'2',80>
  1712.         T_S     <"n3",'3',81>
  1713.         T_S     <"n0",'0',82>
  1714.         T_S     <"n.",'.',83>
  1715.         T_S     <"Enter",13,224>
  1716.  
  1717.         T_S     <"Home",0,71>
  1718.         T_S     <"Up"  ,0,72>
  1719.         T_S     <"PgUp",0,73>
  1720.         T_S     <"Left",0,75>
  1721.         T_S     <"Right",0,77>
  1722.         T_S     <"End" ,0,79>
  1723.         T_S     <"Down",0,80>
  1724.         T_S     <"PgDn",0,81>
  1725.         T_S     <"Ins" ,0,82>
  1726.         T_S     <"Del" ,0,83>
  1727.  
  1728.         T_S     <"^n/",0,149>
  1729.         T_S     <"^n*",0,150>
  1730.         T_S     <"^Home",0,119>
  1731.         T_S     <"^Up"  ,0,141>
  1732.         T_S     <"^PgUp",0,132>
  1733.         T_S     <"^n-",0,142>
  1734.         T_S     <"^Left",0,115>
  1735.         T_S     <"^n5",0,143>
  1736.         T_S     <"^Right",0,116>
  1737.         T_S     <"^n+",0,144>
  1738.         T_S     <"^End" ,0,117>
  1739.         T_S     <"^Down",0,145>
  1740.         T_S     <"^PgDn",0,118>
  1741.         T_S     <"^Ins" ,0,146>
  1742.         T_S     <"^Del" ,0,147>
  1743.         T_S     <"^Enter",10,224>
  1744.  
  1745.         T_S     <"an/",0,164>
  1746.         T_S     <"an*",0,55>
  1747.         T_S     <"an-",0,74>
  1748.         T_S     <"an+",0,78>
  1749.         T_S     <"aEnter",10,224>
  1750.  
  1751.         T_S     <"eHome",224,71>
  1752.         T_S     <"eUp"  ,224,72>
  1753.         T_S     <"ePgUp",224,73>
  1754.         T_S     <"eLeft",224,75>
  1755.         T_S     <"eRight",224,77>
  1756.         T_S     <"eEnd" ,224,79>
  1757.         T_S     <"eDown",224,80>
  1758.         T_S     <"ePgDn",224,81>
  1759.         T_S     <"eIns" ,224,82>
  1760.         T_S     <"eDel" ,224,83>
  1761.  
  1762.         T_S     <"^eHome",224,119>
  1763.         T_S     <"^eUp"  ,224,141>
  1764.         T_S     <"^ePgUp",224,132>
  1765.         T_S     <"^eLeft",224,115>
  1766.         T_S     <"^eRight",224,116>
  1767.         T_S     <"^eEnd" ,224,117>
  1768.         T_S     <"^eDown",224,145>
  1769.         T_S     <"^ePgDn",224,118>
  1770.         T_S     <"^eIns" ,224,146>
  1771.         T_S     <"^eDel" ,224,147>
  1772.  
  1773.         T_S     <"aeHome",0,151>
  1774.         T_S     <"aeUp"  ,0,152>
  1775.         T_S     <"aePgUp",0,153>
  1776.         T_S     <"aeLeft",0,155>
  1777.         T_S     <"aeRight",0,157>
  1778.         T_S     <"aeEnd" ,0,159>
  1779.         T_S     <"aeDown",0,160>
  1780.         T_S     <"aePgDn",0,161>
  1781.         T_S     <"aeIns" ,0,162>
  1782.         T_S     <"aeDel" ,0,163>
  1783.  
  1784.         T_S     <"sF1",0,84>
  1785.         T_S     <"sF2",0,85>
  1786.         T_S     <"sF3",0,86>
  1787.         T_S     <"sF4",0,87>
  1788.         T_S     <"sF5",0,88>
  1789.         T_S     <"sF6",0,89>
  1790.         T_S     <"sF7",0,90>
  1791.         T_S     <"sF8",0,91>
  1792.         T_S     <"sF9",0,92>
  1793.         T_S     <"sF10",0,93>
  1794.  
  1795.         T_S     <"^F1",0,94>
  1796.         T_S     <"^F2",0,95>
  1797.         T_S     <"^F3",0,96>
  1798.         T_S     <"^F4",0,97>
  1799.         T_S     <"^F5",0,98>
  1800.         T_S     <"^F6",0,99>
  1801.         T_S     <"^F7",0,100>
  1802.         T_S     <"^F8",0,101>
  1803.         T_S     <"^F9",0,102>
  1804.         T_S     <"^F10",0,103>
  1805.  
  1806.         T_S     <"aF1",0,104>
  1807.         T_S     <"aF2",0,105>
  1808.         T_S     <"aF3",0,106>
  1809.         T_S     <"aF4",0,107>
  1810.         T_S     <"aF5",0,108>
  1811.         T_S     <"aF6",0,109>
  1812.         T_S     <"aF7",0,110>
  1813.         T_S     <"aF8",0,111>
  1814.         T_S     <"aF9",0,112>
  1815.         T_S     <"aF10",0,113>
  1816.  
  1817.         T_S     <"^PrtScrn",0,114>
  1818.  
  1819.         T_S     <"F11",0,133>
  1820.         T_S     <"F12",0,134>
  1821.         T_S     <"sF11",0,135>
  1822.         T_S     <"sF12",0,136>
  1823.         T_S     <"^F11",0,137>
  1824.         T_S     <"^F12",0,138>
  1825.         T_S     <"aF11",0,139>
  1826.         T_S     <"aF12",0,140>
  1827.  
  1828. ; Special functions with full names:
  1829.  
  1830.         T_S     <"PrtScrn",255,PRTSCRN_CODE>
  1831.         T_S     <"Boot"   ,255,REBOOT_CODE>
  1832.         T_S     <"^aDel"  ,255,REBOOT_CODE>     ; Ctrl-Alt-Del is an alias
  1833.                                                 ; for Boot!
  1834.         T_S     <"AtTime" ,255,ATTIME_CODE>
  1835.         T_S     <"Wait"   ,255,DELTATIME_CODE>
  1836.         T_S     <"Find"   ,255,FIND_CODE>
  1837.         T_S     <"Prompt" ,255,PROMPT_CODE>
  1838.         T_S     <"^Break" ,255,BREAK_CODE>
  1839.  
  1840. EndToken        label byte
  1841.  
  1842. GetToken        proc                    ; AL = '{', SI -> next char
  1843.  
  1844.         push    cx di
  1845.  
  1846.         mov     ah,'}'                  ; Assume '{ for start of token
  1847.         cmp     al,'{'
  1848.          je     @@l1
  1849.         mov     ah,']'                  ; No, so it must be '['
  1850. @@l1:
  1851.         mov     di, offset CurToken
  1852.         mov     cx, MaxTokenSize
  1853.         lodsb                           ; Must be at least one char,
  1854. @@next:                                 ; so {{} is OK!
  1855.         stosb
  1856.         dec     cx
  1857.          jz     @@full
  1858.         lodsb
  1859.         cmp     al,' '
  1860.          jbe    @@MissingBrace
  1861.         cmp     al,ah
  1862.          jne    @@next
  1863.  
  1864.         mov     al,0
  1865.         rep     stosb
  1866. @@full:
  1867.  
  1868.         cmp     CurToken[1],0           ; Length = 1?
  1869.          je     @@done                  ; Yes, so leave alone!
  1870.  
  1871.         mov     di, offset CurToken
  1872.         mov     cx, MaxTokenSize
  1873. @@load:
  1874.         mov     al,[di]
  1875.         cmp     al,'a'
  1876.          jb     @@upper
  1877.         cmp     al,'z'
  1878.          ja     @@upper
  1879.         sub     al,'a'-'A'
  1880.         mov     [di],al
  1881. @@upper:
  1882.         inc     di
  1883.          loop   @@load
  1884. @@done:
  1885.         pop     di cx
  1886.         ret
  1887.  
  1888. @@MissingBrace:
  1889.         jmp     Syntax
  1890. GetToken        endp
  1891.  
  1892. TokenUpper      db 0
  1893.  
  1894. FindToken proc near             ; bx => token
  1895. ; Return CLC (and AX) if found, STC if not found
  1896.  
  1897.         push    cx dx si di
  1898.  
  1899.         cmp     [TokenUpper],0
  1900.          jne    @@TokenUpper
  1901.         call    UpcaseToken             ; Converts token table to uppercase!
  1902.         mov     [TokenUpper],1
  1903.  
  1904. @@TokenUpper:
  1905.         mov     dx, offset TokenTable - TokenRecSize
  1906.  
  1907. @@testtoken:
  1908.         add     dx, TokenRecSize
  1909.         cmp     dx, offset EndToken
  1910.         cmc
  1911.          jc     @@done
  1912.  
  1913.         mov     si, offset CurToken
  1914.         mov     di,dx
  1915.         mov     cx, MaxTokenSize SHR 1
  1916.    repe cmpsw
  1917.          jne     @@TestToken
  1918.  
  1919.         mov     ax,[di]
  1920.         clc
  1921. @@done:
  1922.         pop     di si dx cx
  1923.         ret
  1924. FindToken       endp
  1925.  
  1926. UpcaseToken     proc
  1927.         push    cx dx di
  1928.  
  1929.         mov     dx, offset TokenTable
  1930.  
  1931. @@testtoken:
  1932.         mov     di,dx
  1933.         cmp     byte ptr [di+1],0       ; Token length = 1?
  1934.          je     @@end                   ; Yes, so skip this!
  1935.  
  1936.         mov     cx, MaxTokenSize
  1937.  
  1938. @@upcase:
  1939.         mov     al,[di]
  1940.         or      al,al
  1941.          jz     @@end
  1942.         cmp     al,'a'
  1943.          jb     @@upper
  1944.         cmp     al,'z'
  1945.          ja     @@upper
  1946.         sub     al,'a'-'A'
  1947. @@upper:
  1948.         stosb
  1949.          loop   @@upcase
  1950. @@end:
  1951.         add     dx, TokenRecSize
  1952.         cmp     dx, offset EndToken
  1953.          jb     @@testtoken
  1954.  
  1955. @@done:
  1956.         pop     di dx cx
  1957.         ret
  1958. UpcaseToken     endp
  1959.  
  1960. ListKeys        proc    near
  1961.         push    bx cx dx si di
  1962.  
  1963.         mov     si, offset TokenTable
  1964.  
  1965. @@print3:
  1966.         mov     di, 3
  1967.  
  1968. @@testtoken:
  1969.         cmp     si, offset EndToken
  1970.          jae    @@done
  1971.  
  1972.         mov     bx,'[' + ']' * 256
  1973.         cmp     byte ptr [si],'{'
  1974.          je     @@UseBrackets
  1975.         cmp     byte ptr [si],'}'
  1976.          je     @@UseBrackets
  1977.  
  1978.         mov     bx,'{' + '}' * 256      ; Use '{}' pair for all others
  1979.  
  1980. @@UseBrackets:
  1981.         mov     dl,bl
  1982.         mov     ah,2
  1983.         int     21h
  1984.  
  1985.         mov     cx, MaxTokenSize
  1986. @@nextchar:
  1987.         mov     al,[si]
  1988.         or      al,al
  1989.          jz     @@zero
  1990.         inc     si
  1991.         mov     dl,al
  1992.         mov     ah,2
  1993.         int     21h
  1994.          loop   @@nextchar
  1995. @@zero:
  1996.         mov     dl,bh
  1997.         mov     ah,2
  1998.         int     21h
  1999.  
  2000.         add     si,cx
  2001.         call    Space
  2002.  
  2003.         lodsb                   ; Character value
  2004.         xor     ah,ah
  2005.         mov     cx,3
  2006.         call    PrintAX
  2007.         mov     dl,':'
  2008.         mov     ah,2
  2009.         int     21h
  2010.         lodsb                   ; Character value
  2011.         xor     ah,ah
  2012.         xor     cx,cx
  2013.         call    PrintAX
  2014.         dec     di
  2015.          jz     @@Eoln
  2016.  
  2017.         add     cx,8
  2018.         call    Space
  2019.          jmp    @@testtoken
  2020. @@Eoln:
  2021.         mov     dx, offset CRLF
  2022.         mov     ah,9
  2023.         int     21h
  2024.          jmp    @@print3
  2025. @@done:
  2026.         pop     di si dx cx bx
  2027.  
  2028.         mov     ax,4C00h
  2029.         int     21h
  2030.  
  2031.         ret
  2032. ListKeys        endp
  2033.  
  2034. PrintAX proc    near
  2035.         push    dx si di
  2036.  
  2037.         mov     si,ax
  2038.         mov     bx,10
  2039.         mov     di,cx
  2040.         xor     cx,cx
  2041. @@next:
  2042.         mov     ax,si
  2043.         xor     dx,dx
  2044.         div     bx
  2045.         mov     si,ax
  2046.         mov     ah,2
  2047.         add     dl,'0'
  2048.         push    dx
  2049.         inc     cx
  2050.         or      si,si
  2051.          jnz    @@next
  2052.  
  2053.         sub     di,cx
  2054.          jbe    @@popDL
  2055.  
  2056.         xchg    cx,di
  2057.         call    Space
  2058.         xchg    cx,di
  2059. @@popDL:
  2060.         pop     dx
  2061.         mov     ah,2
  2062.         int     21h
  2063.          loop   @@popDL
  2064.  
  2065.         mov     cx,di
  2066.         pop     di si dx
  2067.         ret
  2068. PrintAX endp
  2069.  
  2070. Space   proc
  2071.  
  2072.          jcxz   @@done
  2073. @@space:
  2074.         mov     dl,' '
  2075.         mov     ah,2
  2076.         int     21h
  2077.          loop   @@space
  2078. @@done:
  2079.         ret
  2080.  
  2081. Space   endp
  2082.  
  2083. ProgramEnd label byte
  2084.  
  2085. CODE ENDS
  2086.         END start
  2087.  
  2088.