;EXECWSWP.ASM
;  Swap memory and exec another program, keeping child process's video
;  output to a specified portion of the screen
;  Copyright (c) 1988,1990 TurboPower Software
;  May be used freely as long as due credit is given
;
;-----------------------------------------------------------------------------
DATA    SEGMENT WORD PUBLIC
        EXTRN   BytesSwapped:DWORD      ;Bytes to swap to EMS/disk
        EXTRN   EmsAllocated:BYTE       ;True when EMS allocated for swap
        EXTRN   FileAllocated:BYTE      ;True when file allocated for swap
        EXTRN   EmsHandle:WORD          ;Handle of EMS allocation block
        EXTRN   FrameSeg:WORD           ;Segment of EMS page frame
        EXTRN   FileHandle:WORD         ;Handle of DOS swap file
        EXTRN   SwapName:BYTE           ;ASCIIZ name of swap file
        EXTRN   PrefixSeg:WORD          ;Base segment of program
        EXTRN   NewInt10Addr:WORD;      ;Hold addr on our int 10 handler
        EXTRN   NewInt21Addr:WORD;      ;Hold addr on our int 21 handler
        EXTRN   NewInt29Addr:WORD;      ;Hold addr on our int 29 handler
DATA    ENDS
;-----------------------------------------------------------------------------
CODE    SEGMENT WORD PUBLIC
        ASSUME  CS:CODE,DS:DATA
        PUBLIC  ExecWinWithSwap, FirstToSave
        PUBLIC  AllocateSwapFile, DeallocateSwapFile
        PUBLIC  DefaultDrive, DiskFree
        PUBLIC  EmsInstalled, EmsPageFrame
        PUBLIC  AllocateEmsPages, DeallocateEmsHandle
        PUBLIC  InstallInt21, RestoreInt21, CheckCursor
        PUBLIC  UpdateCsVars
;-----------------------------------------------------------------------------
FileAttr        EQU     6               ;Swap file attribute (hidden+system)
EmsPageSize     EQU     16384           ;Size of EMS page
FileBlockSize   EQU     32768           ;Size of a file block
StkSize         EQU     128             ;Bytes in temporary stack
lo              EQU     (WORD PTR 0)    ;Convenient typecasts
hi              EQU     (WORD PTR 2)
ofst            EQU     (WORD PTR 0)
segm            EQU     (WORD PTR 2)
col             EQU     (BYTE PTR 0)
row             EQU     (BYTE PTR 1)

;-----------------------------------------------------------------------------
;Variables in CS
EmsDevice       DB      'EMMXXXX0',0    ;Name of EMS device driver
UsedEms         DB      0               ;1 if swapping to EMS, 0 if to file
BytesSwappedCS  DD      0               ;Bytes to move during a swap
EmsHandleCS     DW      0               ;EMS handle
FrameSegCS      DW      0               ;Segment of EMS page window
FileHandleCS    DW      0               ;DOS file handle
PrefixSegCS     DW      0               ;Segment of base of program
Status          DW      0               ;ExecSwap status code
LeftToSwap      DD      0               ;Bytes left to move
SaveSP          DW      0               ;Original stack pointer
SaveSS          DW      0               ;Original stack segment
PathPtr         DD      0               ;Pointer to program to execute
CmdPtr          DD      0               ;Pointer to command line to execute
ParasWeHave     DW      0               ;Paragraphs allocated to process
CmdLine         DB      128 DUP(0)      ;Terminated command line passed to DOS
Path            DB      64 DUP(0)       ;Terminated path name passed to DOS
FileBlock1      DB      16 DUP(0)       ;FCB passed to DOS
FileBlock2      DB      16 DUP(0)       ;FCB passed to DOS
EnvironSeg      DW      0               ;Segment of environment for child
CmdLinePtr      DD      0               ;Pointer to terminated command line
FilePtr1        DD      0               ;Pointer to FCB file
FilePtr2        DD      0               ;Pointer to FCB file
OrigInt10       DD      0               ;Previous $10 vector
OrigInt21       DD      0               ;Previous $21 vector
OrigInt29       DD      0               ;Previous $29 vector
WindPos         DW      0               ;Cursor position in window
WindLo          DW      0               ;Top left corner of window
WindHi          DW      0               ;   and bottom right corner.
WindAttr        DB      0               ;Attribute with which to
Old1B           DD      0               ;Saves old 1B
Old23           DD      0               ;Saves old 23
IRetOp          DB      0CFh            ;IRET instruction of break ints
TempStack       DB      StkSize DUP(0)  ;Temporary stack
StackTop        LABEL   WORD            ;Initial top of stack

;-----------------------------------------------------------------------------
;Macros
MovSeg          MACRO Dest,Src          ;Set one segment register to another
        PUSH    Src
        POP     Dest
                ENDM

MovMem          MACRO Dest,Src          ;Move from memory to memory via AX
        MOV     AX,Src
        MOV     Dest,AX
                ENDM

InitSwapCount   MACRO                   ;Initialize counter for bytes to swap
        MovMem  LeftToSwap.lo,BytesSwappedCS.lo
        MovMem  LeftToSwap.hi,BytesSwappedCS.hi
                ENDM

SetSwapCount    MACRO BlkSize           ;Return CX = bytes to move this block
        LOCAL   FullBlk                 ;...and reduce total bytes left to move
        MOV     CX,BlkSize              ;Assume we'll write a full block
        CMP     LeftToSwap.hi,0         ;Is high word still non-zero?
        JNZ     FullBlk                 ;Jump if so
        CMP     LeftToSwap.lo,BlkSize   ;Low word still a block or more?
        JAE     FullBlk                 ;Jump if so
        MOV     CX,LeftToSwap.lo        ;Otherwise, move what's left
FullBlk:SUB     LeftToSwap.lo,CX        ;Reduce number left to move
        SBB     LeftToSwap.hi,0
                ENDM

NextBlock       MACRO SegReg, BlkSize   ;Point SegReg to next block to move
        MOV     AX,SegReg
        ADD     AX,BlkSize/16           ;Add paragraphs to next segment
        MOV     SegReg,AX               ;Next block to move
        MOV     AX,LeftToSwap.lo
        OR      AX,LeftToSwap.hi        ;Bytes left to move?
                ENDM

EmsCall         MACRO FuncAH            ;Call EMM and prepare to check result
        MOV     AH,FuncAH               ;Set up function
        INT     67h
        OR      AH,AH                   ;Error code in AH
                ENDM

DosCallAH       MACRO FuncAH            ;Call DOS subfunction AH
        MOV     AH,FuncAH
        INT     21h
                ENDM

DosCallAX       MACRO FuncAX            ;Call DOS subfunction AX
        MOV     AX,FuncAX
        INT     21h
                ENDM

InitSwapFile    MACRO
        MOV     BX,FileHandleCS         ;BX = handle of swap file
        XOR     CX,CX
        XOR     DX,DX                   ;Start of file
        DosCallAX 4200h                 ;DOS file seek
                ENDM

HaltWithError   MACRO Level             ;Halt if non-recoverable error occurs
        MOV     AL,Level                ;Set errorlevel
        DosCallAH 4Ch
                ENDM

MoveFast        MACRO                   ;Move CX bytes from DS:SI to ES:DI
        CLD                             ;Forward
        SHR     CX,1                    ;Convert to words
        REP     MOVSW                   ;Move the words
        RCL     CX,1                    ;Get the odd byte, if any
        REP     MOVSB                   ;Move it
                ENDM

SetTempStack    MACRO                   ;Switch to temporary stack
        MOV     AX,OFFSET StackTop      ;Point to top of stack
        MOV     BX,CS                   ;Temporary stack in this code segment
        CLI                             ;Interrupts off
        MOV     SS,BX                   ;Change stack
        MOV     SP,AX
        STI                             ;Interrupts on
                ENDM

ToDos           MACRO                   ;Transfer control to DOS
        JMP     DWORD PTR OrigInt21
                ENDM

Video           MACRO
        PUSHF                           ;Fake int 10
        CALL    DWORD PTR OrigInt10
                ENDM

ToApp           MACRO                   ;Transfer control back to caller
        CLC                             ;Clear error flag
        RET     2                       ;Return with flags intact
                ENDM

;-----------------------------------------------------------------------------
;procedure CheckCursor
;
CheckCursor PROC NEAR
        PUSH    BP
        MOV     BP,SP                   ;Set up stack frame

        MOV     AH,3
        XOR     BH,BH
        INT     10h                     ;Get cursor position
        MOV     CX,WindLo
        CMP     DH,CH                   ;Row above minimum?
        JAE     CC1                     ;Jump if so
        MOV     DH,CH                   ;Force to minimum
CC1:    CMP     DL,CL                   ;Col above minimum?
        JAE     CC2                     ;Jump is so
        MOV     DL,CL                   ;Force to minimum
CC2:    MOV     CX, WindHi
        CMP     DH,CH                   ;Row below maximum?
        JBE     CC3                     ;Jump if so
        MOV     DH,CH                   ;Force to maximum
CC3:    CMP     DL,CL                   ;Col below maximum?
        JBE     CC4                     ;Jump if so
        MOV     DL,CL                   ;Force to maximum
CC4:    MOV     WindPos,DX              ;Save current position
        MOV     AH,2
        XOR     BH,BH
        INT     10h                     ;Make it so

        POP     BP
        RET                             ;Return
CheckCursor ENDP

;----------------------------------------------------------------------------
;procedure InstallInt21
;
InstallInt21 PROC NEAR
        PUSH    BP
        MOV     BP,SP                   ;Set up stack frame

;Get the current Int21 vector
        DosCallAX 3521h                 ;ES:BX = vector
        MOV     OrigInt21.ofst, BX      ;Save as pointer in OrigInt21
        MOV     OrigInt21.segm, ES

;Get the current Int10 vector
        DosCallAX 3510h                 ;ES:BX = vector
        MOV     OrigInt10.ofst, BX      ;Save as pointer in OrigInt10
        MOV     OrigInt10.segm, ES

;Get the current Int29 vector
        DosCallAX 3529h                 ;ES:BX = vector
        MOV     OrigInt29.ofst, BX      ;Save as pointer in OrigInt29
        MOV     OrigInt29.segm, ES

;Install our Int21 handler
        PUSH    DS
        MOV     DX, SEG NewInt21        ;Segment of our handler
        MOV     DS, DX
        MOV     DX, OFFSET NewInt21     ;Offset of our handler
        DosCallAX 2521h                 ;Set it
        POP     DS

        MOV     NewInt21Addr, OFFSET NewInt21
        MOV     NewInt21Addr+2, SEG NewInt21    ;Show addr to caller

;Install our Int10 handler
        PUSH    DS
        MOV     DX, SEG NewInt10        ;Segment of our handler
        MOV     DS, DX
        MOV     DX, OFFSET NewInt10     ;Offset of our handler
        DosCallAX 2510h                 ;Set it
        POP     DS

        MOV     NewInt10Addr, OFFSET NewInt10
        MOV     NewInt10Addr+2, SEG NewInt10    ;Show addr to caller

;Install our Int29 handler
        PUSH    DS
        MOV     DX, SEG NewInt29        ;Segment of our handler
        MOV     DS, DX
        MOV     DX, OFFSET NewInt29     ;Offset of our handler
        DosCallAX 2529h                 ;Set it
        POP     DS

        MOV     NewInt29Addr, OFFSET NewInt29
        MOV     NewInt29Addr+2, SEG NewInt29    ;Show addr to caller

        POP     BP
        RET                             ;Return
InstallInt21 ENDP

;-----------------------------------------------------------------------------
;procedure RestoreInt21
;
RestoreInt21 PROC NEAR
        PUSH    BP
        MOV     BP,SP                   ;Set up stack frame

        PUSH    DS
        LDS     DX, OrigInt21           ;DS:DX = OrigInt21
        DosCallAX 2521h                 ;Restore it
        LDS     DX, OrigInt10           ;DS:DX = OrigInt10
        DosCallAX 2510h                 ;Restore it
        LDS     DX, OrigInt29           ;DS:DX = OrigInt29
        DosCallAX 2529h                 ;Restore it
        POP     DS

        POP     BP
        RET                             ;Return
RestoreInt21 ENDP

;-----------------------------------------------------------------------------
;procedure UpdateCsVars(Xlo, Ylo, Xhi, Yhi, Attr : Byte);
;
UpdateCsVars PROC NEAR
        PUSH    BP
        MOV     BP,SP                   ;Set up stack frame

;Move passed values to CS variables
        MOV     AX,[BP+12]
        DEC     AX
        MOV     BYTE PTR WindLo.Col, AL ;Lo(WindLo) = Xlo
        MOV     AX,[BP+10]
        DEC     AX
        MOV     BYTE PTR WindLo.Row, AL ;Hi(WindLo) = Ylo
        MOV     AX,[BP+8]
        DEC     AX
        MOV     BYTE PTR WindHi.Col,AL  ;Lo(WindHi) = Xhi
        MOV     AX, [BP+6]
        DEC     AX
        MOV     BYTE PTR WindHi.Row, AL ;Hi(WindHi) = Yhi
        MOV     AX,[BP+4]
        MOV     WindAttr, AL            ;Get attribute

        POP     BP
        RET     10
UpdateCsVars ENDP

;-----------------------------------------------------------------------------
;function ExecWithSwap(Path, CmdLine : string;
;                      Xlo, Ylo, Xhi, Yhi, Attr : Byte ) : Word;
ExecWinWithSwap PROC FAR
        PUSH    BP
        MOV     BP,SP                   ;Set up stack frame

;Move variables to CS where we can easily access them later
        MOV     Status,1                ;Assume failure
        LES     DI,[BP+20]              ;ES:DI -> Path
        MOV     PathPtr.ofst,DI
        MOV     PathPtr.segm,ES         ;PathPtr -> path to execute
        LES     DI,[BP+16]              ;ES:DI -> CmdLine
        MOV     CmdPtr.ofst,DI
        MOV     CmdPtr.segm,ES          ;CmdPtr -> command line string

        PUSH    [BP+14]
        PUSH    [BP+12]
        PUSH    [BP+10]
        PUSH    [BP+8]
        PUSH    [BP+6]
        CALL    UpdateCsVars            ;Transfer parms to CS vars

;Make sure cursor is within window
        CALL    CheckCursor

;Continue with swapping
        MOV     SaveSP,SP               ;Save stack position
        MOV     SaveSS,SS
        MovMem  BytesSwappedCS.lo,BytesSwapped.lo
        MovMem  BytesSwappedCS.hi,BytesSwapped.hi
        MovMem  EmsHandleCS,EmsHandle
        MovMem  FrameSegCS,FrameSeg
        MovMem  FileHandleCS,FileHandle
        MovMem  PrefixSegCS,PrefixSeg
        InitSwapCount                   ;Initialize bytes LeftToSwap

;Install our Int21 handler
        CALL    InstallInt21

;Point break vectors somewhere harmless
        PUSH    DS
        PUSH    CS
        POP     DS
        ASSUME  DS:CODE

        MOV     AX,351Bh                ;Save old vectors
        INT     21h
        MOV     Old1B.ofst,BX
        MOV     Old1B.segm,ES
        MOV     AX,3523h
        INT     21h
        MOV     Old23.ofst,BX
        MOV     Old23.segm,ES

        MOV     AX,251Bh                ;Force breaks to IRET
        MOV     DX,OFFSET IretOp
        INT     21h
        MOV     AL,23h
        INT     21h
        POP     DS
        ASSUME  DS:DATA

;Check for swapping to EMS or file
        CMP     EmsAllocated,0          ;Check flag for EMS method
        JZ      NotEms                  ;Jump if EMS not used
        JMP     WriteE                  ;Swap to EMS
NotEms: CMP     FileAllocated,0         ;Check flag for swap file method
        JNZ     WriteF                  ;Swap to file
        JMP     ESDone                  ;Exit if no swapping method set

;Write to swap file
WriteF: MovSeg  DS,CS                   ;DS = CS
        InitSwapFile                    ;Seek to start of swap file
        JNC     EF0                     ;Jump if success
        JMP     ESDone                  ;Exit if error
EF0:    SetSwapCount FileBlockSize      ;CX = bytes to write
        MOV     DX,OFFSET FirstToSave   ;DS:DX -> start of region to save
        DosCallAH 40h                   ;File write
        JC      EF1                     ;Jump if write error
        CMP     AX,CX                   ;All bytes written?
        JZ      EF2                     ;Jump if so
EF1:    JMP     ESDone                  ;Exit if error
EF2:    NextBlock DS,FileBlockSize      ;Point DS to next block to write
        JNZ     EF0                     ;Loop if bytes left to write
        MOV     UsedEms,0               ;Flag we used swap file for swapping
        DosCallAH 45h                   ;DUP handle
        JC      EF2a                    ;Jump if failed
        MOV     BX,AX                   ;Prepare to close dup'ed handle
        DosCallAH 3Eh                   ;Flush swap file
EF2a:   JMP     SwapDone                ;Done swapping out

;Write to EMS
WriteE: MOV     ES,FrameSeg             ;ES -> page window
        MOV     DX,EmsHandle            ;DX = handle of our EMS block
        EmsCall 47h                     ;Save EMS context
        XOR     BX,BX                   ;BX = initial logical page
        MovSeg  DS,CS                   ;DS = CS
EE0:    XOR     AL,AL                   ;Physical page 0
        EmsCall 44h                     ;Map physical page
        JZ      EE1                     ;Jump if success
        JMP     ESDone                  ;Exit if error
EE1:    SetSwapCount EmsPageSize        ;CX = Bytes to move
        XOR     DI,DI                   ;ES:DI -> base of EMS page
        MOV     SI,OFFSET FirstToSave   ;DS:SI -> region to save
        MoveFast                        ;Move CX bytes from DS:SI to ES:DI
        INC     BX                      ;Next logical page
        NextBlock DS,EmsPageSize        ;Point DS to next page to move
        JNZ     EE0                     ;Loop if bytes left to move
        EmsCall 48h                     ;Restore EMS context
        MOV     UsedEms,1               ;Flag we used EMS for swapping

;Shrink memory allocated to this process
SwapDone:MOV    AX,PrefixSegCS
        MOV     ES,AX                   ;ES = segment of our memory block
        DEC     AX
        MOV     DS,AX                   ;DS = segment of memory control block
        MOV     CX,DS:[0003h]           ;CX = current paragraphs owned
        MOV     ParasWeHave,CX          ;Save current paragraphs owned
        SetTempStack                    ;Switch to temporary stack
        MOV     AX,OFFSET FirstToSave+15
        MOV     CL,4
        SHR     AX,CL                   ;Convert offset to paragraphs
        ADD     BX,AX
        SUB     BX,PrefixSegCS          ;BX = new paragraphs to keep
        DosCallAH 4Ah                   ;SetBlock
        JNC     EX0                     ;Jump if successful
        JMP     EX5                     ;Swap back and exit

;Set up parameters and call DOS Exec
EX0:    MOV     AX,ES:[002Ch]           ;Get environment segment
        MOV     EnvironSeg,AX
        MovSeg  ES,CS                   ;ES = CS
        LDS     SI,PathPtr              ;DS:SI -> path to execute
        MOV     DI,OFFSET Path          ;ES:DI -> local ASCIIZ copy
        CLD
        LODSB                           ;Read current length
        CMP     AL,63                   ;Truncate if exceeds space set aside
        JB      EX1
        MOV     AL,63
EX1:    MOV     CL,AL
        XOR     CH,CH                   ;CX = bytes to copy
        REP     MOVSB
        XOR     AL,AL
        STOSB                           ;ASCIIZ terminate
        LDS     SI,CmdPtr               ;DS:SI -> Command line to pass
        MOV     DI,OFFSET CmdLine       ;ES:DI -> Local terminated copy
        LODSB
        CMP     AL,126                  ;Truncate command if exceeds space
        JB      EX2
        MOV     AL,126
EX2:    STOSB
        MOV     CL,AL
        XOR     CH,CH                   ;CX = bytes to copy
        REP     MOVSB
        MOV     AL,0DH                  ;Terminate with ^M
        STOSB
        MovSeg  DS,CS                   ;DS = CS
        MOV     SI,OFFSET CmdLine
        MOV     CmdLinePtr.ofst,SI
        MOV     CmdLinePtr.segm,DS      ;Store pointer to command line
        INC     SI
        MOV     DI,OFFSET FileBlock1
        MOV     FilePtr1.ofst,DI
        MOV     FilePtr1.segm,ES        ;Store pointer to filename 1, if any
        DosCallAX 2901h                 ;Parse FCB
        MOV     DI,OFFSET FileBlock2
        MOV     FilePtr2.ofst,DI
        MOV     FilePtr2.segm,ES        ;Store pointer to filename 2, if any
        DosCallAX 2901h                 ;Parse FCB
        MOV     DX,OFFSET Path
        MOV     BX,OFFSET EnvironSeg
        DosCallAX 4B00h                 ;Exec
        JC      EX3                     ;Jump if error in DOS call
        XOR     AX,AX                   ;Return zero for success
EX3:    MOV     Status,AX               ;Save DOS error code

;Set up temporary stack and reallocate original memory block
        SetTempStack                    ;Set up temporary stack
        MOV     ES,PrefixSegCS
        MOV     BX,ParasWeHave
        DosCallAH 4Ah                   ;SetBlock
        JNC     EX4                     ;Jump if no error
        HaltWithError 0FFh              ;Must halt if failure here
EX4:    InitSwapCount                   ;Initialize LeftToSwap

;Check which swap method is in use
EX5:    CMP     UsedEms,0
        JZ      ReadF                   ;Jump to read back from file
        JMP     ReadE                   ;Read back from EMS

;Read back from swap file
ReadF:  MovSeg  DS,CS                   ;DS = CS
        InitSwapFile                    ;Seek to start of swap file
        JNC     EF3                     ;Jump if we succeeded
        HaltWithError 0FEh              ;Must halt if failure here
EF3:    SetSwapCount FileBlockSize      ;CX = bytes to read
        MOV     DX,OFFSET FirstToSave   ;DS:DX -> start of region to restore
        DosCallAH 3Fh                   ;Read file
        JNC     EF4                     ;Jump if no error
        HaltWithError 0FEh              ;Must halt if failure here
EF4:    CMP     AX,CX
        JZ      EF5                     ;Jump if full block read
        HaltWithError 0FEh              ;Must halt if failure here
EF5:    NextBlock DS,FileBlockSize      ;Point DS to next page to read
        JNZ     EF3                     ;Jump if bytes left to read
        JMP     ESDone                  ;We're done

;Copy back from EMS
ReadE:  MOV     DX,EmsHandleCS          ;DX = handle of our EMS block
        EmsCall 47h                     ;Save EMS context for next time
        MOV     DS,FrameSegCS           ;DS -> page window
        XOR     BX,BX                   ;BX = initial logical page
        MovSeg  ES,CS                   ;ES = CS
EE3:    XOR     AL,AL                   ;Physical page 0
        EmsCall 44h                     ;Map physical page
        JZ      EE4                     ;Jump if success
        HaltWithError 0FDh              ;Must halt if failure here
EE4:    SetSwapCount EmsPageSize        ;CX = Bytes to move
        XOR     SI,SI                   ;DS:SI -> base of EMS page
        MOV     DI,OFFSET FirstToSave   ;ES:DI -> region to restore
        MoveFast                        ;Move CX bytes from DS:SI to ES:DI
        INC     BX                      ;Next logical page
        NextBlock ES,EmsPageSize        ;Point ES to next page to move
        JNZ     EE3                     ;Jump if so
        EmsCall 48h                     ;Restore EMS context

ESDone: CLI                             ;Switch back to original stack
        MOV     SS,SaveSS
        MOV     SP,SaveSP
        STI
        CALL    RestoreInt21            ;Restore original int21

;Restore the break vectors;
        MOV     AX,251Bh                ;Restore break vectors
        MOV     DX,Old1B.ofst
        MOV     DS,Old1B.segm
        INT     21h
        MOV     AX,2523h                ;Restore break vectors
        MOV     DX,Old23.ofst
        MOV     DS,Old23.segm
        INT     21h

        MOV     AX,SEG DATA
        MOV     DS,AX                   ;Restore DS
        MOV     AX,Status               ;Return status
        POP     BP
        RET     18                      ;Remove parameters and return
ExecWinWithSwap ENDP

;----------------------------------------------------------------------
; Handle interrupt 29 to trap output calls
NewInt29 PROC   FAR
        JMP     TtyOut
NewInt29 ENDP

;----------------------------------------------------------------------
; Handle interrupt 10 to trap output calls
NewInt10 PROC   FAR
        CMP     AH,0Eh
        JZ      TtyOut
        CMP     AH,09h
        JZ      MultOut
        CMP     AH,0Ah
        JZ      MultOut
        CMP     AH,02h
        JZ      SetCurPos
        JMP     DWORD PTR OrigInt10
NewInt10 ENDP

;-----------
TtyOut: PUSH    AX
        CALL    WriteChar
        POP     AX
        IRET

;-----------
MultOut:
        PUSH    AX
        PUSH    CX
MultOut1:
        CALL    WriteChar
        LOOP    MultOut1
        POP     CX
        POP     AX
        IRET

;-----------
SetCurPos:
        PUSH    AX
        PUSH    DS
        MOV     AX,SEG DATA
        MOV     DS,AX
        CMP     DL,WindLo.Col
        JAE     SetOKCLo
        MOV     DL,WindLo.Col
SetOKCLo:
        CMP     DL,WindHi.Col
        JBE     SetOKCHi
        MOV     DL,WindHi.Col
SetOKCHi:
        CMP     DH,WindLo.Row
        JAE     SetOKRLo
        MOV     DH,WindLo.Row
SetOKRLo:
        CMP     DH,WindHi.Row
        JBE     SetOKRHi
        MOV     DH,WindHi.Row
SetOKRHi:
        MOV     WindPos,dx
        POP     DS
        POP     AX
        JMP     DWORD PTR OrigInt10

;---------------------------------------------------------------------------
;procedure NewInt21
; Handle interrupt 21 to trap output calls
NewInt21        PROC FAR
        STI
        CMP     AH,2                    ;Just get functions that go to StdOut
        JZ      DispOut
        CMP     AH,6
        JZ      DirectOut
        CMP     AH,9
        JZ      StringOut
        CMP     AH,40h                  ;Or maybe to StdErr
        JZ      BlockOut
        ToDos

;-----------
DispOut:                                ;DOS function 2
        PUSH    AX
        MOV     AL,DL                   ;Character to write in AL
        CALL    WriteChar               ;Write via video BIOS
        POP     AX
        ToApp                           ;Return successfully

;-----------
DirectOut:                              ;DOS function 6
        CMP     DL,0FFh                 ;Console input?
        JNZ     DispOut                 ;Jump if not
        ToDos                           ;Else transfer to DOS

;------------
StringOut:                              ;DOS function 9
        PUSH    AX                      ;Save AX
        PUSH    BX                      ;Save string index
        MOV     BX,DX                   ;DS:BX -> string
StringOut1:
        MOV     AL,[BX]                 ;AL = next character to write
        CMP     AL,'$'                  ;Terminator?
        JZ      StringOut2              ;Exit if so
        CALL    WriteChar               ;Write it
        INC     BX                      ;Next character
        JMP     StringOut1              ;Loop
StringOut2:
        POP     BX
        POP     AX
        ToApp                           ;Back to application

;------------
BlockOut:                               ;DOS function 40h
        CMP     BX,1                    ;To StdOut?
        JZ      BlockOut1               ;Jump if so
        CMP     BX,2                    ;To StdErr?
        JZ      BlockOut1               ;Jump if so
        ToDos                           ;Else let DOS handle it
BlockOut1:
        JCXZ    BlockOut3               ;Get out if none to write
        PUSH    AX
        PUSH    BX
        PUSH    CX                      ;Save loop counter
        MOV     BX,DX                   ;DS:BX -> stuff to write
BlockOut2:
        MOV     AL,[BX]                 ;Next character to write
        CALL    WriteChar               ;Write it
        INC     BX                      ;Next index
        LOOP    BlockOut2               ;Loop for all the characters
        POP     CX
        POP     BX
        POP     AX
        MOV     AX,CX                   ;Wrote all the characters
BlockOut3:
        ToApp                           ;Back to application

NewInt21 ENDP

;------------
; Write a character to current position via BIOS
; Entry: AL is character to write
; Must preserve all but AX
WriteChar  proc    near
        PUSH    BP                   ;some versions of int 10 BIOS trash BP
        PUSH    BX
        PUSH    CX
        PUSH    DX
        PUSH    DS

        CMP     AL,7                 ;Bell character?
        JZ      BiosWriteDone        ;Don't write

        MOV     DX,WindPos           ;Current cursor pos in DX

        CMP     AL,8                 ;Backspace?
        JNZ     NotBack
        JMP     BackSpace

NotBack:
        CMP     AL,9                 ;Tab?
        JZ      Tab
        CMP     AL,10                ;Line feed?
        JZ      LineFeed
        CMP     AL,13                ;Carriage return?
        JZ      Carriage

        CALL    WriteOne             ;Write one normal character

BiosSetCursor:                          ;Position cursor
        XOR     BH,BH
        MOV     AH,2
        Video
        MOV     WindPos,DX           ;Save new cursor position

BiosWriteDone:
        POP     DS
        POP     DX
        POP     CX
        POP     BX
        POP     BP
        RET

Carriage:
        MOV     dl,WindLo.col        ;Move to left edge
        JMP     BiosSetCursor

LineFeed:
        CMP     dh,WindHi.row        ;Room to increment row?
        JB      LineFeed1
        MOV     ax,0601h             ;Scroll up one line
        MOV     cx,WindLo
        MOV     dx,WindHi
        MOV     bh,WindAttr
        Video
        JMP     BiosWriteDone
LineFeed1:
        INC     dh                   ;Increment row
        JMP     BiosSetCursor        ;Set cursor

Tab:    MOV     cl,dl
        SUB     cl,WindLo.Col        ;Characters beyond left edge
        ADD     cl,8
        AND     cl,0F8h              ;To next tab stop
        ADD     cl,WindLo.Col        ;Window coords
        SUB     cl,dl                ;Spaces to write
        XOR     ch,ch                ;CX = spaces to write
Tab1:   MOV     al,20h               ;Write spaces
        PUSH    cx
        CALL    WriteOne             ;One at a time
        XOR     bh,bh
        MOV     ah,2
        Video
        MOV     WindPos,dx           ;Save new cursor position
        POP     CX
        LOOP    Tab1                 ;Do all of them
        JMP     BiosWriteDone

BackSpace:
        CMP     DL,WindLo.col        ;Beyond left edge?
        JBE     BiosWriteDone        ;Exit if not
        DEC     DL                   ;One left
        XOR     BH,BH
        MOV     AH,2                 ;Position cursor
        Video
        MOV     WindPos,dx
        MOV     CX,1                 ;Write character
        MOV     BL,WindAttr
        MOV     AX,0920h             ;Write a space
        Video
        JMP     BiosWriteDone        ;Done now

WriteChar  endp

;---------------
; Write one character and update cursor variable
WriteOne PROC NEAR
        MOV     CX,1                 ;Write character
        MOV     BL,WindAttr
        XOR     BH,BH
        MOV     AH,9
        Video

        CMP     DL,WindHi.col        ;Below right border?
        JB      IncCol               ;If so, just increment column
        CMP     DH,WindHi.row        ;Room for CR/LF?
        JB      IncRow               ;Jump if so

        MOV     AX,0601h             ;Scroll up one line
        MOV     CX,WindLo
        MOV     DX,WindHi
        MOV     BH,WindAttr
        Video
        DEC     DH                   ;Compensate for inc to follow

IncRow: INC     DH                   ;Next row
        MOV     DL,WindLo.col        ;First col
        DEC     DL                   ;Compensate for inc to follow

IncCol: INC     DL                   ;Increment column
        RET
WriteOne ENDP

;-----------------------------------------------------------------------------
;Label marks first location to swap
        EVEN
FirstToSave:
;-----------------------------------------------------------------------------
;function AllocateSwapFile : Boolean;
AllocateSwapFile PROC NEAR
        MOV     CX,FileAttr             ;Attribute for swap file
        MOV     DX,OFFSET SwapName+1    ;DS:DX -> ASCIIZ swap name
        DosCallAH 3Ch                   ;Create file
        MOV     FileHandle,AX           ;Save handle assuming success
        MOV     AL,0                    ;Assume failure
        JC      ASDone                  ;Failed if carry set
        INC     AL                      ;Return true for success
ASDone: RET
AllocateSwapFile ENDP
;-----------------------------------------------------------------------------
;procedure DeallocateSwapFile;
DeallocateSwapFile PROC NEAR
        MOV     BX,FileHandle           ;Handle of swap file
        DosCallAH 3Eh                   ;Close file
        XOR     CX,CX                   ;Normal attribute
        MOV     DX,OFFSET SwapName+1    ;DS:DX -> ASCIIZ swap name
        DosCallAX 4301h                 ;Set file attribute
        DosCallAH 41h                   ;Delete file
        RET
DeallocateSwapFile ENDP
;-----------------------------------------------------------------------------
;function EmsInstalled : Boolean;
EmsInstalled    PROC FAR
        PUSH    DS
        MovSeg  DS,CS                   ;DS = CS
        MOV     DX,OFFSET EmsDevice     ;DS:DX -> EMS driver name
        DosCallAX 3D02h                 ;Open for read/write
        POP     DS
        MOV     BX,AX                   ;Save handle in case one returned
        MOV     AL,0                    ;Assume FALSE
        JC      EIDone
        DosCallAH 3Eh                   ;Close file
        MOV     AL,1                    ;Return TRUE
EIDone: RET
EmsInstalled    ENDP
;-----------------------------------------------------------------------------
;function EmsPageFrame : Word;
EmsPageFrame    PROC FAR
        EmsCall 41h                     ;Get page frame
        MOV     AX,BX                   ;AX = segment
        JZ      EPDone                  ;Done if Error = 0
        XOR     AX,AX                   ;Else segment = 0
EPDone: RET
EmsPageFrame    ENDP
;-----------------------------------------------------------------------------
;function AllocateEmsPages(NumPages : Word) : Word;
AllocateEmsPages PROC FAR
        MOV     BX,SP                   ;Set up stack frame
        MOV     BX,SS:[BX+4]            ;BX = NumPages
        EmsCall 43h                     ;Allocate EMS
        MOV     AX,DX                   ;Assume success
        JZ      APDone                  ;Done if not 0
        MOV     AX,0FFFFh               ;$FFFF for failure
APDone: RET     2                       ;Remove parameter and return
AllocateEmsPages ENDP
;-----------------------------------------------------------------------------
;procedure DeallocateEmsHandle(Handle : Word);
DeallocateEmsHandle PROC FAR
        MOV     BX,SP                   ;Set up stack frame
        MOV     DX,SS:[BX+4]            ;DX = Handle
        EmsCall 45h                     ;Deallocate EMS
        RET     2                       ;Remove parameter and return
DeallocateEmsHandle ENDP
;-----------------------------------------------------------------------------
;function DefaultDrive : Char;
DefaultDrive    PROC FAR
        DosCallAH 19h                   ;Get default drive
        ADD     AL,'A'                  ;Convert to character
        RET
DefaultDrive    ENDP
;-----------------------------------------------------------------------------
;function DiskFree(Drive : Byte) : LongInt;
DiskFree        PROC FAR
        MOV     BX,SP                   ;Set up stack frame
        MOV     DL,SS:[BX+4]            ;DL = Drive to check
        DosCallAH 36h                   ;Get disk space
        MOV     DX,AX                   ;Return 0FFFFFFFFh for failure
        CMP     AX,0FFFFh               ;Bad drive number?
        JZ      DFDone                  ;Jump if so
        MUL     CX                      ;AX = bytes/cluster
        MUL     BX                      ;DX:AX = bytes free
DFDone: RET     2                       ;Remove parameter and return
DiskFree        ENDP
;-----------------------------------------------------------------------------
CODE    ENDS
        END
