TITLE  JDOS.ASM
;
COMMENT *

        Purpose:        Perform 'DOS' functions from within most programs.
        Allows other programs to be executed then return to the interrupted
        program.

        Created:        11-MAR-1989             Richard B. Johnson

        Modified:
        12-MAR-1989     V1.01                   Richard B. Johnson
        Added sign-on screens using direct screen-writes.
        Added DOS version check. DOS V2.## does not work correctly.

        13-MAR-1989     V1.02                   Richard B. Johnson
        Added code to intercept interrupt vectors that the interrupted
        program might be using. If the interrupted program was using
        the timer-tick, the program would crash.

        Changed reserved word "WIDTH" to label "@WIDTH". Would produce a
        warning message under MASM but not under OPTASM.

        14-MAR-1989     V1.03                   Richard B. Johnson
        Added code to save the default interrupt table during initialization
        and use this default table during the spawned process. This should
        prevent possible reentry problems if spawned programs spawn other
        processes also.

        14-MAR-1989     V1.04                   Richard B. Johnson
        Added code to save the state of four possible 8250 UARTS including
        any enabled interrupts and the baud-rate. The UART status is restored
        before returning to the interrupted program. This allows communi-
        cations programs to be interrupted, other communications programs
        executed with different baud-rates, etc., then the return to the
        interrupted program without any errors.

        14-MAR-1989     V1.05                   Richard B. Johnson
        Added code to mask off the interrupt controller immediately
        upon return from the spawmed process. Some communications programs
        were leaving "hot" interrupts when they exited!! Since not even the
        stack is in a known place when the EXEC call returns, this could
        cause a crash. Symptom = "divide by zero error".

        16-MAR-1989     V1.06                   Richard B. Johnson
        Added code to search for the screen segment to accommodate COMPAQ
        "compatibles" that ALWAYS have the screen segment at B800H without
        regard for any standards.

        16-MAR-1989     V1.07                   Richard B. Johnson
        Fixed bug in code that failed to restore the interrupt controller
        mask after a return from the spawned process. No crashes were
        reported, but it's possible that a crash could occur if a program
        being executed failed to restore "hot" interrupt vectors when
        it was aborted. This might happen if the program being executed
        by the spawned process aborted because of a fatal error.

        Notice:         This program must be compiled as a '.COM' file!
        MASM JDOS;
        LINK JDOS;
        EXE2BIN JDOS.EXE JDOS.COM
        DEL JDOS.EXE
*
VERS    STRUC
        DB      'V1.07'                 ; Set version number here ONLY.
@VERS   DB      ' '
VERS    ENDS
;
TRUE    EQU     -1                      ; Set logicals
FALSE   EQU     NOT TRUE
TESTING EQU     FALSE                   ; Debugging conditional
CR      EQU     0DH
LF      EQU     0AH
INT_CTL EQU     21H                     ; Interrupt controller mask addr.
INT_RES EQU     20H                     ; Interrupt controller reset addr.
NOS_EOI EQU     20H                     ; Non-specific end-of-interrupt
MS_DOS  EQU     21H                     ; Operating system interrupt
VIDEO   EQU     10H                     ; Video BIOS code interrupr
ENVIR   EQU     2CH                     ; Address of environment segment
KB      EQU     1024                    ; One kb =
MEM     EQU     32                      ; Extra bytes of memory required
TICK1   EQU     08H                     ; Clock tick
TICK2   EQU     1CH                     ; User timer
SCR_LIN EQU     15                      ; Line to start
@WIDTH  EQU     28                      ; Width of the box
HEIGHT  EQU     6                       ; Height of the box
COL_SCR EQU     (80 - @WIDTH) / 2       ; Column to center
SCR_WRD EQU     80                      ; Words per line on the screen
LOCUS   EQU     (SCR_LIN * SCR_WRD * 2) + (COL_SCR * 2)
;
IN8250  STRUC
LIN_CTR DB      ?                       ; Line control register
DIVISOR DW      ?                       ; Divisor
INT_CTR DB      ?                       ; Interrupt control register
MOD_CTR DB      ?                       ; Modem control register
IN8250  ENDS
;
;       Possible UART base addresses
;
ADDR1   EQU     03F8H                   ; COM1
ADDR2   EQU     02F8H                   ; COM2
ADDR3   EQU     03E8H                   ; COM3
ADDR4   EQU     02E8H                   ; COM4
;
PSEG    SEGMENT PARA PUBLIC 'CODE'
START   EQU     $
        ASSUME CS:PSEG, DS:PSEG, ES:PSEG, SS:NOTHING
        ORG     100H
MAIN    PROC    NEAR
        JMP     INIT
MAIN    ENDP
;
;       Write the string addressed by SI to a box on the screen.
;
SIGNON  PROC    NEAR
        PUSH    ES
        MOV     ES,WORD PTR [SCR_SEG]   ; Pick up screen segment
        MOV     DI,LOCUS                ; Where to start the box
        MOV     CX,HEIGHT               ; Height of the box
BOX0:   PUSH    CX                      ; Save height
        MOV     AH,01110000B            ; Reverse video
        MOV     CX,@WIDTH               ; Width of the box
BOX1:   LODSB                           ; Get LOGO byte
        STOSW                           ; Write the word
        LOOP    BOX1                    ; Continue
        ADD     DI,(SCR_WRD - @WIDTH ) * 2 ; For next line
        POP     CX                      ; Restore height
        LOOP    BOX0
        POP     ES                      ; Restore segment
        RET
SIGNON  ENDP
;
;       This is the main clock interrupt entry point. We check to see if
;       [EFLAG] has been set. If not, we continue to old vector. If it has
;       been set, we check to see if the sub-process has already been spawned
;       by looking at [SPAWN]. If the process has not been spawned, we check
;       the interrupted program's CS. It must be below 640k and above our
;       CS or else a sub-process is not created.
;
ENTRY   PROC    FAR
        PUSH    BP
        MOV     BP,SP                   ; Set up index
        PUSH    AX                      ; Save a register
        PUSH    DS                      ; Save segment
;
        PUSH    CS
        POP     DS                      ; DS = CS
;
        CMP     BYTE PTR [SPAWN],0      ; See if we spawned the process
        JNZ     BYPASS                  ; Yes, continue
        CMP     BYTE PTR [EFLAG],0      ; Check entry flag
        JZ      BYPASS                  ; We don't want to do it
;
;       See what we interrupted. Must be between current CS and end of
;       640k for us to enter.
;
                                        ; BP = Pushed BP
                                        ; BP + 2 = IP
                                        ; BP + 4 = CS
                                        ; BP + 6 = FLAGS
        MOV     AX,CS                   ; Pick up our code-segment
        CMP     [BP+4],AX               ; Compare with interrupted segment
        JC      BYPASS                  ; Not the code to interrupt
        CMP     [BP+4],9000H            ; Check high limit
        JNC     BYPASS                  ; Not the code to interrupt
;
        MOV     BYTE PTR [SPAWN],0FFH   ; Set flag
        CALL    @LOCAL                  ; Call the local routine
        MOV     BYTE PTR [SPAWN],0      ; Reset the flag
        MOV     BYTE PTR [EFLAG],0      ; Reset rentry flag.
;
BYPASS: POP     DS                      ; Restore segment
        POP     AX                      ; Restore register
        POP     BP                      ; Restore index
        JMP     DWORD PTR CS:[OLD_CLK]  ; Continue
ENTRY   ENDP
;
;       Keyboard interrupt extension. If the call is to check status, we
;       ignore it and simply jump to the old interrupt vector. If the call
;       is to receive a character, we make the call ourselves, them check
;       the character. If the character is the one used to spawn the sub-
;       process, we set the sub-process flag, [EFLAG]. Then we substitute
;       a null character for the key-code and return.
;
LCL_KBD PROC    FAR
        CMP     BYTE PTR CS:[SPAWN],0   ; Already in the spawned process?
        JZ      ASK                     ; No, Check the request
NOCHR:  JMP     DWORD PTR CS:[OLD_KBD]  ; Continue
ASK:    CMP     AH,0                    ; Do we want a character?
        JNZ     NOCHR                   ; No, checking status
        PUSHF                           ; Dummy INT
        CALL    DWORD PTR CS:[OLD_KBD]  ; Go get the character
        CMP     AX,2B1CH                ; Character we want?
        JNZ     HOME                    ; Nope
        PUSH    BX                      ; Yes, beep
        PUSH    BP                      ; Is sometimes destroyed
        MOV     AX,0E07H                ; Sound the bell
        MOV     BX,7                    ; Normal attribute
        INT     VIDEO                   ; Video ROM BIOS
        POP     BP                      ; Restore registers
        POP     BX
        MOV     BYTE PTR CS:[EFLAG],0FFH ; ..and set the flag
        MOV     AX,0                    ; Substitute
HOME:   RET     2                       ; Return to caller
LCL_KBD ENDP
;
;       This is the local procedure to create a subprocess.
;
@LOCAL  PROC    NEAR
        MOV     AX,[BP+4]               ; BP = pushed BP
                                        ; BP + 2 = IP
                                        ; BP + 4 = CS
                                        ; BP + 6 = FLAGS
        MOV     WORD PTR [CS_SAV],AX    ; Save the caller's code-segment
        CLI                             ; No interrupts
        CLD                             ; Forwards
        MOV     WORD PTR [SS_SAV],SS    ; Save segment
        MOV     WORD PTR [SP_SAV],SP    ; Save pointer
        MOV     AX,CS                   ; Get our segment
        MOV     SS,AX                   ; Into stack segment
        MOV     SP,OFFSET STKTOP        ; Set up new stack
        STI                             ; Allow interrupts
        PUSH    BX                      ; Save all registers
        PUSH    CX
        PUSH    DX
        PUSH    SI
        PUSH    DI
        PUSH    ES
        MOV     DS,AX                   ; Fix up segments
        MOV     ES,AX
;
        MOV     AL,NOS_EOI
        OUT     INT_RES,AL              ; Reset hardware controller
;
        CALL    SAV_UAR                 ; Save UART parameters
        CALL    SAV_DIR                 ; Save the directory
        CALL    SAV_DTA                 ; Save data transfer area
        CALL    SAV_SCR                 ; Save screen data
        CALL    SAV_MEM                 ; Free up some memory
        CALL    CLR_SCR                 ; Clear the screen
        CALL    SAV_INT                 ; Save interrupt table

        MOV     BYTE PTR DS:[80H],0     ; Show no bytes typed
        MOV     WORD PTR [SP_NEW],SP    ; Save stack
        MOV     DX,OFFSET COMMAND       ; Point to COMMAND.COM
        MOV     BX,OFFSET BLOCK         ; Parameter block for the load
        MOV     AX,4B00H                ; Load/execute program
        INT     MS_DOS                  ; Doit-toit
        CLI                             ; Quiet while I fix
        MOV     AL,10011101B            ; Mask off all but required interrupts
;                  ||||||||_________ Timer
;                  |||||||__________ Keyboard
;                  ||||||___________ Reserved
;                  |||||____________ Async (2)
;                  ||||_____________ Async (1)
;                  |||______________ Hard drive
;                  ||_______________ Floppy drive
;                  |________________ Printer
;
        OUT     INT_CTL,AL              ; The interrupt controller
        MOV     AX,CS                   ; Restore all segments
        MOV     DS,AX
        MOV     ES,AX
        MOV     SS,AX
        MOV     SP,WORD PTR [SP_NEW]    ; Restore stack
        STI                             ; Allow interrupts
        CLD                             ; Could be messed up
;
        JNC     GOOD
        MOV     SI,OFFSET ERROR1        ; Point to string
        CALL    PROMPT                  ; Display to console
        MOV     AH,0                    ; Get response
        PUSHF
        CALL    DWORD PTR [OLD_KBD]     ; Wait for a response
;
GOOD:   CALL    RES_DIR                 ; Restore the directory
        CALL    RES_DTA                 ; Restore data transfer area
        CALL    RES_MEM                 ; Re-acquire the memory we used
        CALL    RES_SCR                 ; Restore screen
        CALL    RES_INT                 ; Restore interrupt table
        CALL    RES_UAR                 ; Restore UART parameters.
        POP     ES                      ; Restore all registers
        POP     DI
        POP     SI
        POP     DX
        POP     CX
        POP     BX
        CLI
        MOV     SS,WORD PTR [SS_SAV]
        MOV     SP,WORD PTR [SP_SAV]
        STI
        RET
@LOCAL  ENDP
;
;       Free up memory. Save memory contents in file VIRTUAL.MEM.
;
SAV_MEM PROC    NEAR
        MOV     DX,OFFSET VIRMEM        ; Point to code-data filename
        MOV     AX,3C00H                ; Create file function
        XOR     CH,CH                   ; File attributes
        MOV     CL,00000110B            ; Hidden/system
        INT     MS_DOS                  ; Create the file
        JNC     W1                      ; Good create
        MOV     DX,OFFSET VIRMEM        ; Point to filename
        MOV     SI,OFFSET ERROR5        ; Point to 'create'
        JMP     SHO_ERR                 ; Show the error, implied return
;
W1:     MOV     BX,AX                   ; File handle
        PUSH    DS                      ; Save segment
        MOV     DS,WORD PTR [CS_SAV]    ; Pick up code segment
WR_ALL: MOV     AX,4000H                ; Write to file
        MOV     CX,0FFFFH               ; 64k -1
        XOR     DX,DX                   ; Offset 0
        INT     MS_DOS                  ; Write the file
        CMP     AX,CX                   ; Write as requested?
        JNZ     WR_OK                   ; Exit, bad write
        MOV     AX,DS                   ; Pick up segment
        ADD     AX,1000H                ; Next segment
        MOV     DS,AX                   ; Index 64k
        CMP     AX,0A000H               ; Check limit
        JC      WR_ALL                  ; Continue
WR_OK:  POP     DS                      ; Restore segment
        JNC     W2                      ; Good write
        MOV     DX,OFFSET VIRMEM        ; Point to filename
        MOV     SI,OFFSET ERROR6        ; Point to 'write'
        JMP     SHO_ERR                 ; Show the error, implied return
;
W2:     MOV     AX,3E00H                ; Close file handle
        INT     MS_DOS
        JNC     W3                      ; Good close
        MOV     DX,OFFSET VIRMEM        ; Point to filename
        MOV     SI,OFFSET ERROR7        ; Point to 'close'
        JMP     SHO_ERR                 ; Show the error, implied return
;
W3:     MOV     BX,(100H SHR 4)         ; Release all memory above 100H
        PUSH    ES                      ; Save segment
        MOV     ES,WORD PTR [CS_SAV]    ; Caller's code-segment
        MOV     AX,4A00H                ; Modify memory
        INT     MS_DOS
        POP     ES                      ; Restore segment
        IF      TESTING
        JNC     MEM1
;
        MOV     SI,OFFSET ERROR2        ; Point to memory error message.
        CALL    PROMPT                  ; Desplay on the screen
        MOV     AH,0
        PUSHF
        CALL    DWORD PTR [OLD_KBD]     ; Wait for a response
        ENDIF
MEM1:   RET
SAV_MEM ENDP
;
;       Acquire memory. Restore it's contents from file VIRTUAL.MEM.
;
RES_MEM PROC    NEAR
        MOV     BX,0FFFFH               ; Get everything back.
        MOV     AX,4A00H                ; Modify memory
        PUSH    ES
        MOV     ES,WORD PTR [CS_SAV]    ; Pick up code segment
        INT     MS_DOS
        POP     ES
;
        MOV     DX,OFFSET VIRMEM        ; Point to code-data filename
        MOV     AX,3D00H                ; Open file for reading
        XOR     CX,CX                   ; File attributes
        INT     MS_DOS                  ; Open the file
        JNC     Z1                      ; Good open
        MOV     DX,OFFSET VIRMEM        ; Point to filename
        MOV     SI,OFFSET ERROR8        ; Point to 'open'
        JMP     SHO_ERR                 ; Show error, implied return
;
Z1:     PUSH    DS                      ; Save segment
        MOV     DS,WORD PTR [CS_SAV]    ; Pick up code segment
        MOV     BX,AX                   ; File handle
RD_ALL: MOV     CX,0FFFFH               ; 64k -1
        XOR     DX,DX                   ; Offset 0
        MOV     AX,3F00H                ; Read from file
        INT     MS_DOS                  ; Write the file
        JC      RD_DON                  ; Bad read
        CMP     CX,AX                   ; See if end of file
        JNZ     RD_DON                  ; No more bytes, all done
        MOV     AX,DS                   ; Pick up segment
        ADD     AX,1000H                ; Next 64k block
        MOV     DS,AX                   ; Update segment
        JMP     SHORT RD_ALL            ; Continue
RD_DON: POP     DS                      ; Restore segment
        JNC     Z2
        MOV     DX,OFFSET VIRMEM        ; Point to filename
        MOV     SI,OFFSET ERROR9        ; Point to 'read'
        JMP     SHO_ERR                 ; Show error, implied return
;
Z2:     MOV     AX,3E00H                ; Close file handle
        INT     MS_DOS
        JNC     Z3                      ; Good close
        MOV     DX,OFFSET VIRMEM        ; Point to filename
        MOV     SI,OFFSET ERROR7        ; Point to 'close'
        JMP     SHO_ERR                 ; Show error, implied return
;
Z3:     MOV     DX,OFFSET VIRMEM        ; Point to code-data filename
        MOV     AX,4100H                ; Delete the file
        INT     MS_DOS
        JNC     Z4                      ; Good delete
        MOV     DX,OFFSET VIRMEM        ; Point to filename
        MOV     SI,OFFSET ERROR10       ; Point to 'delete'
        JMP     SHO_ERR                 ; Show error, implied return
;
Z4:     RET
RES_MEM ENDP
;
;       Restore the DTA
;
RES_DTA PROC    NEAR
        PUSH    DS                      ; Save segment
        LDS     DX,DWORD PTR [DTA]      ; Get old DTA address
        MOV     AX,1A00H                ; Set DTA address
        INT     MS_DOS
        POP     DS                      ; Restore segment
        RET
RES_DTA ENDP
;
;       Save the DTA
;
SAV_DTA PROC    NEAR
        PUSH    ES                      ; Save segment
        MOV     AX,2F00H                ; Get DTA address
        INT     MS_DOS
        MOV     WORD PTR [DTA_OFF],BX   ; Save current DTA address
        MOV     WORD PTR [DTA_SEG],ES
        POP     ES                      ; Restore segment.
        RET
SAV_DTA ENDP
;
;       Save the interrupt table.
;
SAV_INT PROC    NEAR
        IN      AL,INT_CTL              ; Get interrupt controller mask
        MOV     BYTE PTR [OLD_MSK],AL   ; Save the mask
        OR      AL,00011100B            ; Reset IRQ3, IRQ4, Reserved
        OUT     INT_CTL,AL              ; Set the mask
;
;       Save present interrupt vectors.
;
        XOR     AX,AX                   ; Get a zero
        MOV     SI,AX                   ; Offset zero
        MOV     CX,KB                   ; 1k to move
;
        PUSH    DS
        MOV     DS,AX                   ; Point to interrupt table
    MOV DI,OFFSET TABL  ; Point to our data-space
        CLI
        REP     MOVSB                   ; Copy to our CS
        STI
        POP     DS                      ; Restore segment
;
;       Set the default interrupt vectors.
;
        XOR     AX,AX
        MOV     DI,AX                   ; Offset zero
        MOV     SI,OFFSET DEFAULT       ; Where the interrupt data is
        MOV     CX,KB                   ; 1k to replace
;
        PUSH    ES                      ; Save the segment
        MOV     ES,AX                   ; Zero the segment
        CLI
        REP     MOVSB
        STI
        POP     ES                      ; Restore segment
        RET
SAV_INT ENDP
;
;       Restore the interrupt table
;
RES_INT PROC    NEAR
        XOR     AX,AX
        MOV     DI,AX                   ; Offset zero
    MOV SI,OFFSET TABL  ; Where the interrupt data is
        MOV     CX,KB                   ; 1k to replace
;
        PUSH    ES                      ; Save the segment
        MOV     ES,AX                   ; Zero the segment
        CLI
        REP     MOVSB
        STI
        POP     ES                      ; Restore segment
;
        MOV     AL,BYTE PTR [OLD_MSK]   ; Get old controller mask
        OUT     INT_CTL,AL              ; Set interrupt controller mask
        RET
RES_INT ENDP
;
;       Clear the screen, write message.
;
CLR_SCR PROC    NEAR
        MOV     AL,BYTE PTR [SCR_MOD]   ; Get screen mode byte
        MOV     AH,0                    ; Set mode function
        INT     VIDEO                   ; That should clear the screen
        MOV     SI,OFFSET LOGO2         ; Point to 'EXIT' message
        CALL    SIGNON                  ; Print to screen
        RET
CLR_SCR ENDP
;
;       Save the default directory.
;
SAV_DIR PROC    NEAR
        MOV     AH,47H                  ; Get current directory function
        XOR     DL,DL                   ; Get default
        MOV     SI,OFFSET CUR_DIR       ; Where to copy the string
        INT     MS_DOS                  ; Put it there
        MOV     AH,19H                  ; Get current disk
        INT     MS_DOS                  ; From DOS
        ADD     AL,'A'                  ; Drive bias
        MOV     BYTE PTR [DRV],AL       ; Put in drive specifier
        RET
SAV_DIR ENDP
;
SAV_SCR PROC    NEAR
        MOV     AH,15                   ; Return video state
        INT     VIDEO                   ; Video BIOS
        MOV     BYTE PTR [SCR_MOD],AL   ; Current mode
        MOV     BYTE PTR [SCR_COL],AH   ; Number of columns
        MOV     BYTE PTR [SCR_PAG],BH   ; Current page
;
        MOV     AH,3                    ; Get cursor position
        INT     VIDEO                   ; Video BIOS
        MOV     WORD PTR [CUR_POS],DX   ; Save cursor position
        MOV     WORD PTR [CUR_TYP],CX   ; Save cursor type
;
        MOV     DX,OFFSET SCREEN        ; Point to screen-data filename
        MOV     AX,3C00H                ; Create file function
        XOR     CH,CH                   ; File attributes
        MOV     CL,00000110B            ; Hidden/system
        INT     MS_DOS                  ; Create the file
        JNC     Y1                      ; Good create
        MOV     DX,OFFSET SCREEN        ; Point to filename
        MOV     SI,OFFSET ERROR5        ; Point to 'create'
        JMP     SHO_ERR                 ; Show error, implied return
;
Y1:     MOV     BX,AX                   ; File handle
        MOV     CX,0FFFFH               ; 64k -1
        XOR     DX,DX                   ; Offset zero
        MOV     AX,4000H                ; Write to file
        PUSH    DS                      ; Save segment
        MOV     DS,WORD PTR [SCR_SEG]   ; Pick up screen segment
        INT     MS_DOS                  ; Write the file
        POP     DS                      ; Restore segment
        JNC     Y2                      ; Good write
        MOV     DX,OFFSET SCREEN        ; Point to filename
        MOV     SI,OFFSET ERROR6        ; Point to 'write'
        JMP     SHO_ERR                 ; Show error, implied return
;
Y2:     MOV     AX,3E00H                ; Close file handle
        INT     MS_DOS
        JNC     Y3                      ; Good close
        MOV     DX,OFFSET SCREEN        ; Point to filename
        MOV     SI,OFFSET ERROR7        ; Point to 'close'
        JMP     SHO_ERR                 ; Show error, implied return
Y3:     RET
SAV_SCR ENDP
;
;       Restore the default directory.
;
RES_DIR PROC    NEAR
        MOV     AH,0EH                  ; Change drive function
        MOV     DL,BYTE PTR [DRV]       ; Get saved drive letter
        SUB     DL,'A'                  ; Remove bias
        INT     MS_DOS                  ; Go do it
;
        MOV     AH,3BH                  ; Change directory function
        MOV     DX,OFFSET DRV           ; Full drive/path name
        INT     MS_DOS
        RET
RES_DIR ENDP
;
RES_SCR PROC    NEAR
        MOV     DX,OFFSET SCREEN        ; Point to screen-data filename
        MOV     AX,3D00H                ; Open file for reading
        XOR     CX,CX                   ; File attributes
        INT     MS_DOS                  ; Open the file
        JNC     X1                      ; Good open
        MOV     DX,OFFSET SCREEN        ; Filename
        MOV     SI,OFFSET ERROR8        ; Point to 'open'
        JMP     SHO_ERR                 ; Show the error, Implied return
;
X1:     MOV     BX,AX                   ; File handle
        MOV     CX,0FFFFH               ; 64k -1
        XOR     DX,DX                   ; Offset zero
        MOV     AX,3F00H                ; Read from file
        PUSH    DS                      ; Save segment
        MOV     DS,WORD PTR [SCR_SEG]   ; Pick up screen segment
        INT     MS_DOS                  ; Write the file
        POP     DS                      ; Restore segment
        JNC     X2                      ; Good read
        MOV     DX,OFFSET SCREEN        ; Filename
        MOV     SI,OFFSET ERROR9        ; Point to 'read'
        JMP     SHO_ERR                 ; Implied return
;
X2:     MOV     AX,3E00H                ; Close file handle
        INT     MS_DOS
        JNC     X3                      ; Good close
        MOV     DX,OFFSET SCREEN        ; Filename
        MOV     SI,OFFSET ERROR7        ; Point to 'close'
        JMP     SHO_ERR                 ; Implied return
;
X3:     MOV     DX,OFFSET SCREEN        ; Point to screen-data filename
        MOV     AX,4100H                ; Delete the file
        INT     MS_DOS
        JNC     X4                      ; Good delete
        MOV     DX,OFFSET SCREEN        ; Filename
        MOV     SI,OFFSET ERROR10       ; Point to 'delete'
        JMP     SHO_ERR                 ; Implied return
;
X4:     MOV     AH,5                    ; Select active page
        MOV     AL,BYTE PTR [SCR_PAG]   ; Get previous screen page
        INT     VIDEO                   ; Video BIOS
;
        MOV     AH,1                    ; Set cursor type
        MOV     CX,WORD PTR [CUR_TYP]   ; Get previous cursor type
        INT     VIDEO                   ; Video BIOS
;
        MOV     AH,2                    ; Set cursor position
        MOV     BH,BYTE PTR [SCR_PAG]   ; Page number.
        MOV     DX,WORD PTR [CUR_POS]   ; Get saved cursor position
        INT     VIDEO                   ; Video ROM BIOS
        RET
RES_SCR ENDP
;
PROMPT  PROC    NEAR
        LODSB                           ; Get memory byte
        TEST    AL,AL                   ; Check for a null
        JZ      PEXIT                   ; All done
        MOV     BX,7                    ; Page zero/normal attribute
        MOV     AH,14                   ; Dumb terminal mode
        INT     VIDEO                   ; Video BIOS
        JMP     SHORT PROMPT            ; Continue
PEXIT:  RET
PROMPT  ENDP
;
;       Upon entry, DX points to filename. Si points to string for open/close
;       etc. The error message is printed to the screen.
;
SHO_ERR PROC    NEAR
        PUSH    SI
        MOV     SI,OFFSET ERROR4        ; Point to "Can't"
        CALL    PROMPT                  ; Write to screen
        POP     SI                      ; Point to create/write/read, etc
        CALL    PROMPT                  ; Write to screen
        MOV     SI,OFFSET ERROR11       ; Point to "file"
        CALL    PROMPT                  ; Write to screen
        MOV     SI,DX                   ; Get file name
        JMP     PROMPT                  ; Write to screen, implied return.
SHO_ERR ENDP
;
;       Save UART parameters.
;
SAV_UAR PROC    NEAR
        MOV     DI,OFFSET UAR_PAR       ; Point to Uart parameter table
        MOV     DX,ADDR1                ; Pick up address of UART
        CALL    GET_PAR                 ; Get UART parameters
        MOV     DX,ADDR2                ; Pick up address of UART
        CALL    GET_PAR                 ; Get UART parameters
        MOV     DX,ADDR3                ; Pick up address of UART
        CALL    GET_PAR                 ; Get UART parameters
        MOV     DX,ADDR4                ; Pick up address of UART
        CALL    GET_PAR                 ; Get UART parameters
        RET
SAV_UAR ENDP
;
;       Get UART parameters. UART base-port is in DX. Storage for
;       parameters is pointed to by DI. After saving parameters, disable
;       any interrupt enable bits.
;
GET_PAR PROC    NEAR
        ADD     DX,3                    ; Offset to line control register
        IN      AL,DX                   ; Get line control bits
        MOV     BL,AL                   ; Save the bits
        STOSB                           ; Save in memory
        OR      AL,10000000B            ; Divisor access bit
        OUT     DX,AL                   ; Set divisor access bit
        SUB     DX,3                    ; Back to base port
        IN      AX,DX                   ; Get divisor
        STOSW                           ; Save in memory
        ADD     DX,3                    ; Offset to line control register
        MOV     AL,BL                   ; Get saved line-control bits
        OUT     DX,AL                   ; Reset divisor latch
        SUB     DX,2                    ; Set to interrupt control register
        IN      AL,DX                   ; Get the control bits
        STOSB                           ; Save in memory
        XOR     AL,AL                   ; Turn off interrupt control
        OUT     DX,AL                   ; Reset the bits
        ADD     DX,3                    ; Offset to modem control
        IN      AL,DX                   ; Save modem control bits
        STOSB                           ; Save in memory
        MOV     CX,6                    ; Registers to read
        SUB     DX,4                    ; Back to base port
RDALL1: IN      AL,DX                   ; Read port
        INC     DX                      ; Ready next
        LOOP    RDALL1                  ; Read all ports
        MOV     AL,NOS_EOI              ; Non-specific end-of-interrupt
        OUT     INT_RES,AL              ; Reset hardware controller.
        RET
GET_PAR ENDP
;
;       Restore UART parameters.
;
RES_UAR PROC    NEAR
        MOV     SI,OFFSET UAR_PAR       ; Point to Uart parameter table
        MOV     DX,ADDR1                ; Pick up address of UART
        CALL    SET_PAR                 ; Set UART parameters
        MOV     DX,ADDR2                ; Pick up address of UART
        CALL    SET_PAR                 ; Set UART parameters
        MOV     DX,ADDR3                ; Pick up address of UART
        CALL    SET_PAR                 ; Set UART parameters
        MOV     DX,ADDR4                ; Pick up address of UART
        CALL    SET_PAR                 ; Set UART parameters
        RET
RES_UAR ENDP
;
;       Set UART parameters. UART base-port is in DX. Storage for
;       parameters is pointed to by SI.
;
SET_PAR PROC    NEAR
        ADD     DX,3                    ; Line control register
        IN      AL,DX                   ; Get present control-bits
        OR      AL,10000000B            ; Set divisor latch access bit
        OUT     DX,AL                   ; To line-control register
        LODSB                           ; Get old line-control bits
        MOV     BL,AL                   ; Save for now
        SUB     DX,3                    ; Back to the base register
        LODSW                           ; Get saved divisor
        OUT     DX,AX                   ; Out the adjacent ports
        ADD     DX,3                    ; Back to line-control
        MOV     AL,BL                   ; Get saved control-bits
        AND     AL,01111111B            ; Verify DLAB is reset
        OUT     DX,AL                   ; Restore line-control
        SUB     DX,2                    ; Set to interrupt control register
        LODSB                           ; Get old bits
        OUT     DX,AL                   ; Restore
        ADD     DX,3                    ; Offset to modem control
        LODSB                           ; Get old bits
        OUT     DX,AL                   ; Restore
        MOV     CX,6                    ; Registers to read
        SUB     DX,4                    ; Back to base port
RDALL2: IN      AL,DX                   ; Read port
        INC     DX                      ; Ready next
        LOOP    RDALL2                  ; Read all ports
        MOV     AL,NOS_EOI              ; Non-specific end-of-interrupt
        OUT     INT_RES,AL              ; Reset hardware controller.
        RET
SET_PAR ENDP
;
ERROR1  DB      CR,LF,'Can''t load the command processor! <CR> ',0
ERROR2  DB      CR,LF,'Can''t release memory! <CR> ',0
ERROR4  DB      CR,LF,'Can''t ',0
ERROR5  DB      'create',0
ERROR6  DB      'write',0
ERROR7  DB      'close',0
ERROR8  DB      'open',0
ERROR9  DB      'read',0
ERROR10 DB      'delete',0
ERROR11 DB      ' file ',0
OLD_MSK DB      ?                       ; Old interrupt mask.
SPAWN   DB      0                       ; Flag for spawned sub-process
EFLAG   DB      0                       ; Entry flag
SCR_MOD DB      ?                       ; Screen mode
SCR_PAG DB      ?                       ; Screen page
SCR_COL DB      ?                       ; Columns on screen
CS_SAV  DW      ?                       ; Interrupted program's CS
SP_SAV  DW      ?                       ; Interrupted program's SP
SS_SAV  DW      ?                       ; Interrupted program's SS
SP_NEW  DW      ?                       ; Save SP for EXEC call
SCR_SEG DW      0B000H                  ; Segment of screen regen buffer
CUR_TYP DW      ?                       ; Cursor type
CUR_POS DW      ?                       ; Cursor position
DTA     LABEL   DWORD                   ; Data transfer area
DTA_OFF DW      ?                       ; Offset
DTA_SEG DW      ?                       ; Segment
OLD_KBD LABEL   DWORD                   ; Old keyboard vector
KBD_OFF DW      ?                       ; Offset
KBD_SEG DW      ?                       ; Segment
OLD_CLK LABEL   DWORD                   ; Old clock vector
CLK_OFF DW      ?                       ; Offset
CLK_SEG DW      ?                       ; Segment
;
COMSPEC DB      'COMSPEC='              ; Environment search string
COMLEN  EQU     $ - COMSPEC             ; It's length
COMMAND DB      65 DUP (0)              ; To copy command processor name
DRV     DB      '?:\'                   ; Current directory drive and root.
CUR_DIR DB      65 DUP (0)              ; Rest of the current directory string
SCREEN  DB      '\SCREEN.$$$',0         ; Screen data file.
VIRMEM  DB      '\VIRTUAL.MEM',0        ; Virtual memory file
;
;       Parameter block for EXEC function call
;
BLOCK   DW      0                       ; Use current environment
        DW      80H                     ; Offset of command line
CS0     DW      ?                       ; Segment of command line
        DW      5CH                     ; Offset of FCB #1
CS1     DW      ?                       ; Segment of FCB #1
        DW      6CH                     ; Offset of FCB #2
CS2     DW      ?                       ; Segment of FCB #2
;
UAR_PAR LABEL   BYTE                    ; Where the UART parameters are kept.
        IN8250  <>
        IN8250  <>
        IN8250  <>
        IN8250  <>
;
LOGO2   DB      28 DUP (' ')
STRTL2  DB      (28 - ( @VERS + 4 ) ) / 2 DUP (' ')
        DB      'JDOS '
        VERS <>
ENDL2   EQU     $ - STRTL2
        DB      28 - ENDL2 DUP (' ')
        DB      ' Resident Command Processor '
        DB      '           ACTIVE           '
        DB      '  Type EXIT ΔΩ to return   '
        DB      28 DUP (' ')
;
;       Put on a paragraph boundary.
;
        ORG     (( ($-START) + 16) AND 1111111111110000B)
        DB      32 DUP ('STACK   ')
;
STKTOP  LABEL   WORD
DEFAULT LABEL   BYTE                    ; For our default interrupt table
        ORG     $ + KB
BUFFER  LABEL   BYTE                    ; Misc buffer for environment parse
TABL   LABEL   BYTE            ; Saved interrupt table during spawn
        ORG     $ + KB
TOP     EQU     $
;
INIT    PROC    NEAR
        MOV     AX,3000H                ; Get DOS version number
        INT     MS_DOS                  ; From DOS
        CMP     AL,3                    ; Need verion 3+
        JNC     VERSOK                  ; Its okay
        MOV     SI,OFFSET ERROR12       ; Point to version error
EXITF:  CALL    PROMPT                  ; Write to screen, exit fatal
        MOV     AX,4C01H                ; Exit, ERRORLEVEL 1
        INT     MS_DOS                  ; To DOS
;
VERSOK: INT     11H                     ; Equipment check
        AND     AL,00110000B            ; Mask everything but video info
        CMP     AL,00100000B            ; See if color
        JNZ     DEFULT                  ; Default is mono
        MOV     WORD PTR [SCR_SEG],0B800H ; Color screen segment

DEFULT: CALL    CHK_SEG                 ; See if its writable memory
        JZ      SCR_OK                  ; Found writable memory
        MOV    WORD PTR [SCR_SEG],(0B000H - 100H); Start at lowest segment
SERCH0: ADD     WORD PTR [SCR_SEG],00100H ; Incr screen segment
        CMP     WORD PTR [SCR_SEG],0E000H ; Upper limit of search
        JC      SERCH1                  ; Not outside limits
        MOV     SI,OFFSET ERROR13       ; Point to "can't find screen"
        JMP     SHORT EXITF             ; Write to screen and exit fatal
SERCH1: CALL    CHK_SEG                 ; Check for writable memory
        JNZ     SERCH0                  ; Keep looking
;
SCR_OK: CALL    GET_CMD                 ; Get command processor
        JNC     INIT2                   ; Found
        MOV     SI,OFFSET ERROR1        ; Point to error message
        JMP     SHORT EXITF             ; Write to screen and exit fatal
;
INIT2:  MOV     WORD PTR [CS0],CS       ; Fix up parameter block
        MOV     WORD PTR [CS1],CS
        MOV     WORD PTR [CS2],CS
        MOV     AH,35H                  ; Get vector function
        MOV     AL,TICK1                ; Clock tick
        INT     MS_DOS                  ; Go get it
        MOV     WORD PTR [CLK_OFF],BX   ; Save the vector
        MOV     WORD PTR [CLK_SEG],ES
;
        MOV     AH,35H                  ; Get vector function
        MOV     AL,16H                  ; Kbd interrupt
        INT     MS_DOS                  ; Get the vector
        MOV     WORD PTR [KBD_OFF],BX   ; Save the vector
        MOV     WORD PTR [KBD_SEG],ES
;
        MOV     DI,BX                   ; ES:DI = old vector
        MOV     SI,OFFSET LCL_KBD       ; DS:SI = new vector
        MOV     CX,10H                  ; Check 16 bytes
        REPZ    CMPSB
        PUSH    CS
        POP     ES                      ; restore segment
        JNZ     NEW                     ; Never installed before
;
        MOV     SI,OFFSET ERROR3        ; Point to error message
        CALL    PROMPT                  ; Display to console
        MOV     AX,4C00H                ; Exit to DOS ERRORLEVEL 0
        INT     MS_DOS
;
NEW:    MOV     AH,25H                  ; Set vector function
        MOV     AL,TICK1                ; Vector to set
        MOV     DX,OFFSET ENTRY         ; Vector to patch
        INT     MS_DOS                  ; Patch the vector
;
        MOV     AH,25H                  ; Set vector function
        MOV     AL,16H                  ; Vector to set
        MOV     DX,OFFSET LCL_KBD       ; Vector to patch
        INT     MS_DOS                  ; Patch the vector
;
        PUSH    ES                      ; Save segment
        MOV     ES,WORD PTR DS:[ENVIR]  ; Get environment segment
        XOR     BX,BX                   ; Free it all up
        MOV     AX,4A00H                ; Modify memory
        INT     MS_DOS                  ; Call DOS
        POP     ES                      ; Restore segment
;
;       Save our default interrupt table.
;
        XOR     AX,AX                   ; Get a zero
        MOV     SI,AX                   ; Offset zero
        MOV     CX,KB                   ; 1k to move
;
        PUSH    DS
        MOV     DS,AX                   ; Point to interrupt table
        MOV     DI,OFFSET DEFAULT       ; Point to our data-space
        CLI
        REP     MOVSB                   ; Copy to our CS
        STI
        POP     DS                      ; Restore segment
;
        MOV     SI,OFFSET LOGO1         ; Point to 'installed' logo
        CALL    SIGNON                  ; Signon the message.
        MOV     AX,3100H                ; Keep process
        MOV     DX,OFFSET TOP           ; Last location to keep
        ADD     DX,MEM                  ; Memory we need
        SHR     DX,1                    ; Div/2
        SHR     DX,1                    ; Div/4
        SHR     DX,1                    ; Div/8
        SHR     DX,1                    ; Div/16
        INT     MS_DOS                  ; Exit to DOS
        JMP     $                       ; For fatal error abort
INIT    ENDP
;
;       Check screen segment for writable memory.
;
CHK_SEG PROC    NEAR
        PUSH    DS                      ; Save segment
        MOV     DS,WORD PTR [SCR_SEG]   ; Get screen segment
        MOV     AX,WORD PTR DS:[0]      ; Get word at address zero
        MOV     BX,AX                   ; Save memory word
        NOT     AX                      ; Invert
        NOT     WORD PTR DS:[0]         ; Invert memory word
        CMP     AX,WORD PTR DS:[0]      ; See if it went
        MOV     WORD PTR DS:[0],BX      ; Put original word back
        POP     DS                      ; Restore segment
        RET
CHK_SEG ENDP
;
;       Get boot command interpreter. Find out where COMMAND.COM is.
;
GET_CMD PROC    NEAR
        XOR     SI,SI                   ; Offset zero
        MOV     DI,OFFSET BUFFER        ; Where to copy the string
        MOV     AX,WORD PTR DS:[ENVIR]  ; Get environment segment
        PUSH    DS                      ; Save segment
        MOV     DS,AX                   ; Set environment segment
GET0:   LODSB                           ; Get environment byte
        TEST    AL,AL                   ; Check for a null
        JNZ     GET1                    ; Not a null
        CMP     BYTE PTR [SI],0         ; Next on a null too?
        JZ      GET3                    ; Yes, all done
GET1:   CMP     AL,'z'                  ; Check limits
        JA      GET2                    ; Not lower case
        CMP     AL,'a'                  ; Check lower limit
        JB      GET2                    ; Not lower case
        AND     AL,95                   ; Reset lower-case bits
GET2:   STOSB                           ; Save byte
        JMP     SHORT GET0              ; Continue until the double-null
GET3:   STOSB                           ; Final null
        POP     DS                      ; Restore segment
;
        MOV     CX,DI                   ; Get last pointer
        SUB     CX,OFFSET BUFFER        ; CX= length of environment strings
        MOV     BX,OFFSET COMSPEC       ; Substring to find
        MOV     DX,COMLEN               ; Length of the substring
        MOV     SI,OFFSET BUFFER        ; Where the string should be found.
        CALL    COMPARE                 ; Scan the string
        JNZ     GET10                   ; Not found
        MOV     DI,OFFSET COMMAND       ; Where to copy COMMAND.COM
        MOV     BX,SI                   ; Save location
GET4:   LODSB                           ; Get byte to transfer
        TEST    AL,AL                   ; Check for terminator
        JZ      GET5                    ; String is transferred
        STOSB                           ; Copy byte
        JMP     SHORT GET4              ; Continue
GET5:   RET                             ; No errors
GET10:  STC                             ; Show error
        RET
GET_CMD ENDP
;
;       Compares the substring addressed by DS:BX to the string addressed
;       by DS:SI. DX contains the substring length. CX contains the string
;       length. Returns ZF=TRUE if the string is found. SI points to one
;       character after the string if its found.
;
COMPARE PROC    NEAR
        PUSH    CX                      ; Save string length
COMP0:  PUSH    CX                      ; Save original string length
        MOV     CX,DX                   ; Get substring length
        MOV     DI,BX                   ; Get substring location
        REPZ    CMPSB                   ; Compare string/substring
        POP     CX                      ; Restore string length
        JZ      FOUND                   ; String was found
        LOOP    COMP0                   ; Not found, continue
        INC     CX                      ; Make NZ
FOUND:  POP     CX                      ; Restore string length
        RET
COMPARE ENDP
;
ERROR3  DB      CR,LF,'JDOS is already installed!',CR,LF,0
ERROR12 DB      CR,LF,'Need DOS version 3.0 or higher to execute!',CR,LF,0
ERROR13 DB      CR,LF,'Can''t find writable screen memory!',CR,LF,0
LOGO1   DB      28 DUP (' ')
STRTL   DB      (28 - ( @VERS + 4 ) ) / 2 DUP (' ')
        DB      'JDOS '
        VERS <>
ENDL    EQU     $ - STRTL
        DB      28 - ENDL DUP (' ')
        DB      ' Resident Command Processor '
        DB      '                            '
        DB      '    Use ^\ to activate.     '
        DB      28 DUP (' ')
PSEG    ENDS
        END     MAIN
