;========================================================
; MODEX.ASM - A Complete Mode X Library
;
; Version 1.04 Release, 3 May 1993, By Matt Pritchard
; With considerable input from Michael Abrash
;
; The following information is donated to the public domain in
; the hopes that save other programmers much frustration.
;
; If you do use this code in a product, it would be nice if
; you include a line like "Mode X routines by Matt Pritchard"
; in the credits.
;
; =========================================================
;
; All of this code is designed to be assembled with MASM 5.10a
; but TASM 3.0 could be used as well.
;
; The routines contained are designed for use in a MEDIUM model
; program.  All Routines are FAR, and is assumed that a DGROUP
; data segment exists and that DS will point to it on entry.
;
; For all routines, the AX, BX, CX, DX, ES and FLAGS registers
; will not be preserved, while the DS, BP, SI and DI registers
; will be preserved.
;
; Unless specifically noted, All Parameters are assumed to be
; "PASSED BY VALUE".  That is, the actual value is placed on
; the stack.  When a reference is passed it is assumed to be
; a near pointer to a variable in the DGROUP segment.
;
; Routines that return a single 16-Bit integer value will
; return that value in the AX register.
;
; This code will *NOT* run on an 8086/8088 because 80286+
; specific instructions are used.   If you have an 8088/86
; and VGA, you can buy an 80386-40 motherboard for about
; $160 and move into the 90's.
;
; This code is reasonably optimized: Most drawing loops have
; been unrolled once and memory references are minimized by
; keeping stuff in registers when possible.
;
; Error Trapping varies by Routine.  No Clipping is performed
; so the caller should verify that all coordinates are valid.
;
; Several Macros are used to simplify common 2 or 3 instruction
; sequences.  Several Single letter Text Constants also
; simplify common assembler expressions like "WORD PTR".
;
; ------------------ Mode X Variations ------------------
;
;  Mode #  Screen Size    Max Pages   Aspect Ratio (X:Y)
;
;    0      320 x 200      4 Pages         1.2:1
;    1      320 x 400      2 Pages         2.4:1
;    2      360 x 200      3 Pages        1.35:1
;    3      360 x 400      1 Page          2.7:1
;    4      320 x 240      3 Pages           1:1
;    5      320 x 480      1 Page            2:1
;    6      360 x 240      3 Pages       1.125:1
;    7      360 x 480      1 Page         2.25:1
;
; -------------------- The Legal Stuff ------------------
;
; No warranty, either written or implied, is made as to
; the accuracy and usability of this code product.  Use
; at your own risk.  Batteries not included.  Pepperoni
; and extra cheese available for an additional charge.
;
; ----------------------- The Author --------------------
;
; Matt Pritchard is a paid programmer who'd rather be
; writing games.  He can be reached at: P.O. Box 140264,
; Irving, TX  75014  USA.  Michael Abrash is a living
; god, who now works for Bill Gates (Microsoft).
;
; -------------------- Revision History -----------------
; 4-12-93: v1.02 - SET_POINT & READ_POINT now saves DI
;          SET_MODEX now saves SI
; 5-3-93:  v1.04 - added LOAD_DAC_REGISTERS and
;          READ_DAC_REGISTERS.  Expanded CLR Macro
;          to handle multiple registers
;
 
    PAGE    255, 132
 
    .MODEL Medium
    .286
 
    ; ===== MACROS =====
 
    ; Macro to OUT a 16 bit value to an I/O port
 
OUT_16 MACRO Register, Value
    IFDIFI <Register>, <DX>         ; If DX not setup
        MOV     DX, Register        ; then Select Register
    ENDIF
    IFDIFI <Value>, <AX>            ; If AX not setup
        MOV     AX, Value           ; then Get Data Value
    ENDIF
        OUT     DX, AX              ; Set I/O Register(s)
ENDM
 
    ; Macro to OUT a 8 bit value to an I/O Port
 
OUT_8 MACRO Register, Value
    IFDIFI <Register>, <DX>         ; If DX not setup
        MOV     DX, Register        ; then Select Register
    ENDIF
    IFDIFI <Value>, <AL>            ; If AL not Setup
        MOV     AL, Value           ; then Get Data Value
    ENDIF
        OUT     DX, AL              ; Set I/O Register
ENDM
 
    ; macros to PUSH and POP multiple registers
 
PUSHx MACRO R1, R2, R3, R4, R5, R6, R7, R8
    IFNB <R1>
        PUSH    R1              ; Save R1
        PUSHx   R2, R3, R4, R5, R6, R7, R8
    ENDIF
ENDM
 
POPx MACRO R1, R2, R3, R4, R5, R6, R7, R8
    IFNB <R1>
        POP     R1              ; Restore R1
        POPx    R2, R3, R4, R5, R6, R7, R8
    ENDIF
ENDM
 
    ; Macro to Clear Registers to 0
 
CLR MACRO Register, R2, R3, R4, R5, R6
    IFNB <Register>
        XOR     Register, Register      ; Set Register = 0
        CLR R2, R3, R4, R5, R6
    ENDIF
ENDM
 
    ; Macros to Decrement Counter & Jump on Condition
 
LOOPx MACRO Register, Destination
    DEC     Register                ; Counter--
    JNZ     Destination             ; Jump if not 0
ENDM
 
LOOPjz MACRO Register, Destination
    DEC     Register                ; Counter--
    JZ      Destination             ; Jump if 0
ENDM
 
 
    ; ===== General Constants =====
 
    False   EQU 0
    True    EQU -1
    nil     EQU 0
 
    b       EQU BYTE PTR
    w       EQU WORD PTR
    d       EQU DWORD PTR
    o       EQU OFFSET
    f       EQU FAR PTR
    s       EQU SHORT
    ?x4     EQU <?,?,?,?>
    ?x3     EQU <?,?,?>
 
    ; ===== VGA Register Values =====
 
    VGA_Segment     EQU 0A000h  ; Vga Memory Segment
 
    ATTRIB_Ctrl     EQU 03C0h   ; VGA Attribute Controller
    GC_Index        EQU 03CEh   ; VGA Graphics Controller
    SC_Index        EQU 03C4h   ; VGA Sequencer Controller
    SC_Data         EQU 03C5h   ; VGA Sequencer Data Port
    CRTC_Index      EQU 03D4h   ; VGA CRT Controller
    CRTC_Data       EQU 03D5h   ; VGA CRT Controller Data
    MISC_OUTPUT     EQU 03C2h   ; VGA Misc Register
    INPUT_1         EQU 03DAh   ; Input Status #1 Register
 
    DAC_WRITE_ADDR  EQU 03C8h   ; VGA DAC Write Addr Register
    DAC_READ_ADDR   EQU 03C7h   ; VGA DAC Read Addr Register
    PEL_DATA_REG    EQU 03C9h   ; VGA DAC/PEL data Register R/W
 
    PIXEL_PAN_REG   EQU 033h    ; Attrib Index: Pixel Pan Reg
    MAP_MASK        EQU 002h    ; Sequ Index: Write Map Mask reg
    READ_MAP        EQU 004h    ; GC Index: Read Map Register
    START_DISP_HI   EQU 00Ch    ; CRTC Index: Display Start Hi
    START_DISP_LO   EQU 00Dh    ; CRTC Index: Display Start Lo
 
    MAP_MASK_PLANE1 EQU 00102h  ; Map Register + Plane 1
    MAP_MASK_PLANE2 EQU 01102h  ; Map Register + Plane 1
    ALL_PLANES_ON   EQU 00F02h  ; Map Register + All Bit Planes
 
    CHAIN4_OFF      EQU 00604h  ; Chain 4 mode Off
    ASYNC_RESET     EQU 00100h  ; (A)synchronous Reset
    SEQU_RESTART    EQU 00300h  ; Sequencer Restart
 
    LATCHES_ON      EQU 00008h  ; Bit Mask + Data from Latches
    LATCHES_OFF     EQU 0FF08h  ; Bit Mask + Data from CPU
 
    VERT_RETRACE    EQU 08h     ; INPUT_1: Vertical Retrace Bit
    PLANE_BITS      EQU 03h     ; Bits 0-1 of Xpos = Plane #
    ALL_PLANES      EQU 0Fh     ; All Bit Planes Selected
    CHAR_BITS       EQU 0Fh     ; Bits 0-3 of Character Data
 
    GET_CHAR_PTR    EQU 01130h  ; VGA BIOS Func: Get Char Set
    ROM_8x8_Lo      EQU 03h     ; ROM 8x8 Char Set Lo Pointer
    ROM_8x8_Hi      EQU 04h     ; ROM 8x8 Char Set Hi Pointer
 
    ; Constants Specific for these routines
 
    NUM_MODES       EQU 8       ; # of Mode X Variations
 
    ; Specific Mode Data Table format...
 
Mode_Data_Table STRUC
    M_MiscR         DB  ?       ; Value of MISC_OUTPUT register
    M_Pages         DB  ?       ; Maximum Possible # of pages
    M_XSize         DW  ?       ; X Size Displayed on screen
    M_YSize         DW  ?       ; Y Size Displayed on screen
    M_XMax          DW  ?       ; Maximum Possible X Size
    M_YMax          DW  ?       ; Maximum Possible Y Size
    M_CRTC          DW  ?       ; Table of CRTC register values
Mode_Data_Table ENDS
 
    ; ===== DGROUP STORAGE NEEDED (42 BYTES) =====
 
    .DATA?
 
SCREEN_WIDTH    DW  0       ; Width of a line in Bytes
SCREEN_HEIGHT   DW  0       ; Vertical Height in Pixels
 
LAST_PAGE       DW  0       ; # of Display Pages
PAGE_ADDR       DW  4 DUP (0)   ; Offsets to start of each page
 
PAGE_SIZE       DW  0       ; Size of Page in Addr Bytes
 
DISPLAY_PAGE    DW  0       ; Page # currently displayed
ACTIVE_PAGE     DW  0       ; Page # currently active
 
CURRENT_PAGE    DW  0       ; Offset of current Page
CURRENT_SEGMENT DW  0       ; Segment of VGA memory
 
CURRENT_XOFFSET DW  0       ; Current Display X Offset
CURRENT_YOFFSET DW  0       ; Current Display Y Offset
 
CURRENT_MOFFSET DW  0       ; Current Start Offset
 
MAX_XOFFSET     DW  0       ; Current Display X Offset
MAX_YOFFSET     DW  0       ; Current Display Y Offset
 
CHARSET_LOW     DW  0, 0    ; Far Ptr to Char Set: 0-127
CHARSET_HI      DW  0, 0    ; Far Ptr to Char Set: 128-255
 
    .CODE
 
    ; ===== DATA TABLES =====
 
    ; Data Tables, Put in Code Segment for Easy Access
    ; (Like when all the other Segment Registers are in
    ; use!!) and reduced DGROUP requirements...
 
    ; Bit Mask Tables for Left/Right/Character Masks
 
Left_Clip_Mask      DB  0FH, 0EH, 0CH, 08H
 
Right_Clip_Mask     DB  01H, 03H, 07H, 0FH
 
    ; Bit Patterns for converting character fonts
 
Char_Plane_Data     DB  00H,08H,04H,0CH,02H,0AH,06H,0EH
                    DB  01H,09H,05H,0DH,03H,0BH,07H,0FH
 
        ; CRTC Register Values for Various Configurations
 
MODE_Single_Line:       ; CRTC Setup Data for 400/480 Line modes
        DW  04009H      ; Cell Height (1 Scan Line)
        DW  00014H      ; Dword Mode off
        DW  0E317H      ; turn on Byte Mode
        DW  nil         ; End of CRTC Data for 400/480 Line Mode
 
MODE_Double_Line:       ; CRTC Setup Data for 200/240 Line modes
        DW  04109H      ; Cell Height (2 Scan Lines)
        DW  00014H      ; Dword Mode off
        DW  0E317H      ; turn on Byte Mode
        DW  nil         ; End of CRTC Data for 200/240 Line Mode
 
MODE_320_Wide:          ; CRTC Setup Data for 320 Horz Pixels
        DW  05F00H      ; Horz total
        DW  04F01H      ; Horz Displayed
        DW  05002H      ; Start Horz Blanking
        DW  08203H      ; End Horz Blanking
        DW  05404H      ; Start H Sync
        DW  08005H      ; End H Sync
        DW  nil         ; End of CRTC Data for 320 Horz pixels
 
MODE_360_Wide:          ; CRTC Setup Data for 360 Horz Pixels
        DW  06B00H      ; Horz total
        DW  05901H      ; Horz Displayed
        DW  05A02H      ; Start Horz Blanking
        DW  08E03H      ; End Horz Blanking
        DW  05E04H      ; Start H Sync
        DW  08A05H      ; End H Sync
        DW  nil         ; End of CRTC Data for 360 Horz pixels
 
MODE_200_Tall:
MODE_400_Tall:          ; CRTC Setup Data for 200/400 Line modes
        DW  0BF06H      ; Vertical Total
        DW  01F07H      ; Overflow
        DW  09C10H      ; V Sync Start
        DW  08E11H      ; V Sync End/Prot Cr0 Cr7
        DW  08F12H      ; Vertical Displayed
        DW  09615H      ; V Blank Start
        DW  0B916H      ; V Blank End
        DW  nil         ; End of CRTC Data for 200/400 Lines
 
MODE_240_Tall:
MODE_480_Tall:          ; CRTC Setup Data for 240/480 Line modes
        DW  00D06H      ; Vertical Total
        DW  03E07H      ; Overflow
        DW  0EA10H      ; V Sync Start
        DW  08C11H      ; V Sync End/Prot Cr0 Cr7
        DW  0DF12H      ; Vertical Displayed
        DW  0E715H      ; V Blank Start
        DW  00616H      ; V Blank End
        DW  nil         ; End of CRTC Data for 240/480 Lines
 
        ; Table of Display Mode Tables
 
MODE_TABLE:
        DW  o MODE_320x200, o MODE_320x400
        DW  o MODE_360x200, o MODE_360x400
        DW  o MODE_320x240, o MODE_320x480
        DW  o MODE_360x240, o MODE_360x480
 
        ; Table of Display Mode Components
 
MODE_320x200:           ; Data for 320 by 200 Pixels
 
        DB  063h        ; 400 scan Lines & 25 Mhz Clock
        DB  4           ; Maximum of 4 Pages
        DW  320, 200    ; Displayed Pixels (X,Y)
        DW  1302, 816   ; Max Possible X and Y Sizes
 
        DW  o MODE_320_Wide, o MODE_200_Tall
        DW  o MODE_Double_Line, nil
 
MODE_320x400:           ; Data for 320 by 400 Pixels
 
        DB  063h        ; 400 scan Lines & 25 Mhz Clock
        DB  2           ; Maximum of 2 Pages
        DW  320, 400    ; Displayed Pixels X,Y
        DW  648, 816    ; Max Possible X and Y Sizes
 
        DW  o MODE_320_Wide, o MODE_400_Tall
        DW  o MODE_Single_Line, nil
 
MODE_360x240:           ; Data for 360 by 240 Pixels
 
        DB  0E7h        ; 480 scan Lines & 28 Mhz Clock
        DB  3           ; Maximum of 3 Pages
        DW  360, 240    ; Displayed Pixels X,Y
        DW  1092, 728   ; Max Possible X and Y Sizes
 
        DW  o MODE_360_Wide, o MODE_240_Tall
        DW  o MODE_Double_Line , nil
 
MODE_360x480:           ; Data for 360 by 480 Pixels
 
        DB  0E7h        ; 480 scan Lines & 28 Mhz Clock
        DB  1           ; Only 1 Page Possible
        DW  360, 480    ; Displayed Pixels X,Y
        DW  544, 728    ; Max Possible X and Y Sizes
 
        DW  o MODE_360_Wide, o MODE_480_Tall
        DW  o MODE_Single_Line , nil
 
MODE_320x240:           ; Data for 320 by 240 Pixels
 
        DB  0E3h        ; 480 scan Lines & 25 Mhz Clock
        DB  3           ; Maximum of 3 Pages
        DW  320, 240    ; Displayed Pixels X,Y
        DW  1088, 818   ; Max Possible X and Y Sizes
 
        DW  o MODE_320_Wide, o MODE_240_Tall
        DW  o MODE_Double_Line, nil
 
MODE_320x480:           ; Data for 320 by 480 Pixels
 
        DB  0E3h        ; 480 scan Lines & 25 Mhz Clock
        DB  1           ; Only 1 Page Possible
        DW  320, 480    ; Displayed Pixels X,Y
        DW  540, 818    ; Max Possible X and Y Sizes
 
        DW  o MODE_320_WIDE, o MODE_480_Tall
        DW  o MODE_Single_Line, nil
 
MODE_360x200:           ; Data for 360 by 200 Pixels
 
        DB  067h        ; 400 scan Lines & 28 Mhz Clock
        DB  3           ; Maximum of 3 Pages
        DW  360, 200    ; Displayed Pixels (X,Y)
        DW  1302, 728   ; Max Possible X and Y Sizes
 
        DW  o MODE_360_Wide, MODE_200_Tall
        DW  o MODE_Double_Line, nil
 
MODE_360x400:           ; Data for 360 by 400 Pixels
 
        DB  067h        ; 400 scan Lines & 28 Mhz Clock
        DB  1           ; Maximum of 1 Pages
        DW  360, 400    ; Displayed Pixels X,Y
        DW  648, 816    ; Max Possible X and Y Sizes
 
        DW  o MODE_360_Wide, MODE_400_Tall
        DW  o MODE_Single_Line, nil
 
 
    ; ===== MODE X SETUP ROUTINES =====
 
;======================================================
;SET_VGA_MODEX% (ModeType%, MaxXPos%, MaxYpos%, Pages%)
;======================================================
;
; Sets Up the specified version of Mode X.  Allows for
; the setup of multiple video pages, and a virtual
; screen which can be larger than the displayed screen
; (which can then be scrolled a pixel at a time)
;
; ENTRY: ModeType = Desired Screen Resolution (0-7)
;
;     0 =  320 x 200, 4 Pages max,   1.2:1 Aspect Ratio
;     1 =  320 x 400, 2 Pages max,   2.4:1 Aspect Ratio
;     2 =  360 x 200, 3 Pages max,  1.35:1 Aspect Ratio
;     3 =  360 x 400, 1 Page  max,   2.7:1 Aspect Ratio
;     4 =  320 x 240, 3 Pages max,     1:1 Aspect Ratio
;     5 =  320 x 480, 1 Page  max,     2:1 Aspect Ratio
;     6 =  360 x 240, 3 Pages max, 1.125:1 Aspect Ratio
;     7 =  360 x 480, 1 Page  max,  2.25:1 Aspect Ratio
;
;        MaxXpos = The Desired Virtual Screen Width
;        MaxYpos = The Desired Virtual Screen Height
;        Pages   = The Desired # of Video Pages
;
; EXIT:  AX = Success Flag:   0 = Failure / -1= Success
;
 
SVM_STACK   STRUC
    SVM_Table   DW  ?   ; Offset of Mode Info Table
                DW  ?x4 ; DI, SI, DS, BP
                DD  ?   ; Caller
    SVM_Pages   DW  ?   ; # of Screen Pages desired
    SVM_Ysize   DW  ?   ; Vertical Screen Size Desired
    SVM_Xsize   DW  ?   ; Horizontal Screen Size Desired
    SVM_Mode    DW  ?   ; Display Resolution Desired
SVM_STACK   ENDS
 
    PUBLIC  SET_VGA_MODEX
 
SET_VGA_MODEX   PROC    FAR
 
    PUSHx   BP, DS, SI, DI      ; Preserve Important Registers
    SUB     SP, 2               ; Allocate workspace
    MOV     BP, SP              ; Set up Stack Frame
 
    ; Check Legality of Mode Request....
 
    MOV     BX, [BP].SVM_Mode   ; Get Requested Mode #
    CMP     BX, NUM_MODES       ; Is it 0..7?
    JAE     @SVM_BadModeSetup   ; If Not, Error out
 
    SHL     BX, 1                   ; Scale BX
    MOV     SI, w MODE_TABLE[BX]    ; CS:SI -> Mode Info
    MOV     [BP].SVM_Table, SI      ; Save ptr for later use
 
    ; Check # of Requested Display Pages
 
    MOV     CX, [BP].SVM_Pages  ; Get # of Requested Pages
    CLR     CH                  ; Set Hi Word = 0!
    CMP     CL, CS:[SI].M_Pages ; Check # Pages for mode
    JA      @SVM_BadModeSetup   ; Report Error if too Many Pages
    JCXZ    @SVM_BadModeSetup   ; Report Error if 0 Pages
 
    ; Check Validity of X Size
 
    AND     [BP].SVM_XSize, 0FFF8h  ; X size Mod 8 Must = 0
 
    MOV     AX, [BP].SVM_XSize  ; Get Logical Screen Width
    CMP     AX, CS:[SI].M_XSize ; Check against Displayed X
    JB      @SVM_BadModeSetup   ; Report Error if too small
    CMP     AX, CS:[SI].M_XMax  ; Check against Max X
    JA      @SVM_BadModeSetup   ; Report Error if too big
 
    ; Check Validity of Y Size
 
    MOV     BX, [BP].SVM_YSize  ; Get Logical Screen Height
    CMP     BX, CS:[SI].M_YSize ; Check against Displayed Y
    JB      @SVM_BadModeSetup   ; Report Error if too small
    CMP     BX, CS:[SI].M_YMax  ; Check against Max Y
    JA      @SVM_BadModeSetup   ; Report Error if too big
 
    ; Enough memory to Fit it all?
 
    SHR     AX, 2               ; # of Bytes:Line = XSize/4
    MUL     CX                  ; AX = Bytes/Line * Pages
    MUL     BX                  ; DX:AX = Total VGA mem needed
    JNO     @SVM_Continue       ; Exit if Total Size > 256K
 
    DEC     DX                  ; Was it Exactly 256K???
    OR      DX, AX              ; (DX = 1, AX = 0000)
    JZ      @SVM_Continue       ; if so, it's valid...
 
@SVM_BadModeSetup:
 
    CLR     AX                  ; Return Value = False
    JMP     @SVM_Exit           ; Normal Exit
 
@SVM_Continue:
 
    MOV     AX, 13H             ; Start with Mode 13H
    INT     10H                 ; Let BIOS Set Mode
 
    OUT_16  SC_INDEX, CHAIN4_OFF            ; Disable Chain 4 Mode
    OUT_16  SC_INDEX, ASYNC_RESET           ; (A)synchronous Reset
    OUT_8   MISC_OUTPUT, CS:[SI].M_MiscR    ; Set New Timing/Size
    OUT_16  SC_INDEX, SEQU_RESTART          ; Restart Sequencer ...
 
    OUT_8   CRTC_INDEX, 11H     ; Select Vert Retrace End Register
    INC     DX                  ; Point to Data
    IN      AL, DX              ; Get Value, Bit 7 = Protect
    AND     AL, 7FH             ; Mask out Write Protect
    OUT     DX, AL              ; And send it back
 
    MOV     DX, CRTC_INDEX      ; Vga Crtc Registers
    ADD     SI, M_CRTC          ; SI -> CRTC Parameter Data
 
    ; Load Tables of CRTC Parameters from List of Tables
 
@SVM_Setup_Table:
 
    MOV     DI, CS:[SI]         ; Get Pointer to CRTC Data Tbl
    ADD     SI, 2               ; Point to next Ptr Entry
    OR      DI, DI              ; A nil Ptr means that we have
    JZ      @SVM_Set_Data       ; finished CRTC programming
 
@SVM_Setup_CRTC:
    MOV     AX, CS:[DI]         ; Get CRTC Data from Table
    ADD     DI, 2               ; Advance Pointer
    OR      AX, AX              ; At End of Data Table?
    JZ      @SVM_Setup_Table    ; If so, Exit & get next Table
 
    OUT     DX, AX              ; Reprogram VGA CRTC reg
    JMP     s @SVM_Setup_CRTC   ; Process Next Table Entry
 
    ; Initialize Page & Scroll info, DI = 0
 
@SVM_Set_Data:
    MOV     DISPLAY_PAGE, DI    ; Display Page = 0
    MOV     ACTIVE_PAGE, DI     ; Active Page = 0
    MOV     CURRENT_PAGE, DI    ; Current Page (Offset) = 0
    MOV     CURRENT_XOFFSET, DI ; Horz Scroll Index = 0
    MOV     CURRENT_YOFFSET, DI ; Vert Scroll Index = 0
    MOV     CURRENT_MOFFSET, DI ; Memory Scroll Index = 0
 
    MOV     AX, VGA_SEGMENT     ; Segment for VGA memory
    MOV     CURRENT_SEGMENT, AX ; Save for Future LES's
 
    ; Set Logical Screen Width, X Scroll and Our Data
 
    MOV     SI, [BP].SVM_Table  ; Get Saved Ptr to Mode Info
    MOV     AX, [BP].SVM_Xsize  ; Get Display Width
 
    MOV     CX, AX              ; CX = Logical Width
    SUB     CX, CS:[SI].M_XSize ; CX = Max X Scroll Value
    MOV     MAX_XOFFSET, CX     ; Set Maximum X Scroll
 
    SHR     AX, 2               ; Bytes = Pixels / 4
    MOV     SCREEN_WIDTH, AX    ; Save Width in Pixels
 
    SHR     AX, 1               ; Offset Value = Bytes / 2
    MOV     AH, 13h             ; CRTC Offset Register Index
    XCHG    AL, AH              ; Switch format for OUT
    OUT     DX, AX              ; Set VGA CRTC Offset Reg
 
    ; Setup Data table, Y Scroll, Misc for Other Routines
 
    MOV     AX, [BP].SVM_Ysize  ; Get Logical Screen Height
 
    MOV     CX, AX              ; CX = Logical Height
    SUB     BX, CS:[SI].M_YSize ; CX = Max Y Scroll Value
    MOV     MAX_YOFFSET, CX     ; Set Maximum Y Scroll
 
    MOV     SCREEN_HEIGHT, AX   ; Save Height in Pixels
    MUL     SCREEN_WIDTH        ; AX = Page Size in Bytes,
    MOV     PAGE_SIZE, AX       ; Save Page Size
 
    MOV     CX, [BP].SVM_Pages  ; Get # of Pages
    MOV     LAST_PAGE, CX       ; Save # of Pages
 
    CLR     BX                  ; Page # = 0
    MOV     DX, BX              ; Page 0 Offset = 0
 
@SVM_Set_Pages:
 
    MOV     PAGE_ADDR[BX], DX   ; Set Page #(BX) Offset
    ADD     BX, 2               ; Page#++
    ADD     DX, AX              ; Compute Addr of Next Page
    LOOPx   CX, @SVM_Set_Pages  ; Loop until all Pages Set
 
    ; Clear VGA Memory
 
    OUT_16  SC_INDEX, ALL_PLANES_ON ; Select All Planes
    LES     DI, d CURRENT_PAGE      ; -> Start of VGA memory
 
    CLR     AX                  ; AX = 0
    CLD                         ; Block Xfer Forwards
    MOV     CX, 8000H           ; 32K * 4 * 2 = 256K
    REP     STOSW               ; Clear dat memory!
 
    ; Setup Font Pointers
 
    MOV     BH, ROM_8x8_Lo      ; Ask for 8x8 Font, 0-127
    MOV     AX, GET_CHAR_PTR    ; Service to Get Pointer
    INT     10h                 ; Call VGA BIOS
 
    MOV     CHARSET_LOW, BP     ; Save Char Set Offset
    MOV     CHARSET_LOW+2, ES   ; Save Char Set Segment
 
    MOV     BH, ROM_8x8_Hi      ; Ask for 8x8 Font, 128-255
    MOV     AX, GET_CHAR_PTR    ; Service to Get Pointer
    INT     10h                 ; Call VGA BIOS
 
    MOV     CHARSET_HI, BP      ; Save Char Set Offset
    MOV     CHARSET_HI+2, ES    ; Save Char Set Segment
 
    MOV     AX, True            ; Return Success Code
 
@SVM_EXIT:
    ADD     SP, 2               ; Deallocate workspace
    POPx    DI, SI, DS, BP      ; Restore Saved Registers
    RET     8                   ; Exit & Clean Up Stack
 
SET_VGA_MODEX   ENDP
 
 
;==================
;SET_MODEX% (Mode%)
;==================
;
; Quickie Mode Set - Sets Up Mode X to Default Configuration
;
; ENTRY: ModeType = Desired Screen Resolution (0-7)
;        (See SET_VGA_MODEX for list)
;
; EXIT:  AX = Success Flag:   0 = Failure / -1= Success
;
 
SM_STACK    STRUC
                DW  ?,? ; BP, SI
                DD  ?   ; Caller
    SM_Mode     DW  ?   ; Desired Screen Resolution
SM_STACK    ENDS
 
    PUBLIC  SET_MODEX
 
SET_MODEX   PROC    FAR
 
    PUSHx   BP, SI              ; Preserve Important registers
    MOV     BP, SP              ; Set up Stack Frame
 
    CLR     AX                  ; Assume Failure
    MOV     BX, [BP].SM_Mode    ; Get Desired Mode #
    CMP     BX, NUM_MODES       ; Is it a Valid Mode #?
    JAE     @SMX_Exit           ; If Not, don't Bother
 
    PUSH    BX                  ; Push Mode Parameter
 
    SHL     BX, 1                   ; Scale BX to word Index
    MOV     SI, w MODE_TABLE[BX]    ; CS:SI -> Mode Info
 
    PUSH    CS:[SI].M_XSize     ; Push Default X Size
    PUSH    CS:[SI].M_Ysize     ; Push Default Y size
    MOV     AL, CS:[SI].M_Pages ; Get Default # of Pages
    CLR     AH                  ; Hi Byte = 0
    PUSH    AX                  ; Push # Pages
 
    CALL    f SET_VGA_MODEX     ; Set up Mode X!
 
@SMX_Exit:
    POPx    SI, BP              ; Restore Registers
    RET     2                   ; Exit & Clean Up Stack
 
SET_MODEX   ENDP
 
 
    ; ===== BASIC GRAPHICS PRIMITIVES =====
 
;============================
;CLEAR_VGA_SCREEN (ColorNum%)
;============================
;
; Clears the active display page
;
; ENTRY: ColorNum = Color Value to fill the page with
;
; EXIT:  No meaningful values returned
;
 
CVS_STACK   STRUC
                DW  ?,? ; DI, BP
                DD  ?   ; Caller
    CVS_COLOR   DB  ?,? ; Color to Set Screen to
CVS_STACK   ENDS
 
    PUBLIC  CLEAR_VGA_SCREEN
 
CLEAR_VGA_SCREEN    PROC    FAR
 
    PUSHx   BP, DI              ; Preserve Important Registers
    MOV     BP, SP              ; Set up Stack Frame
 
    OUT_16  SC_INDEX, ALL_PLANES_ON ; Select All Planes
    LES     DI, d CURRENT_PAGE      ; Point to Active VGA Page
 
    MOV     AL, [BP].CVS_COLOR  ; Get Color
    MOV     AH, AL              ; Copy for Word Write
    CLD                         ; Block fill Forwards
 
    MOV     CX, PAGE_SIZE       ; Get Size of Page
    SHR     CX, 1               ; Divide by 2 for Words
    REP     STOSW               ; Block Fill VGA memory
 
    POPx    DI, BP              ; Restore Saved Registers
    RET     2                   ; Exit & Clean Up Stack
 
CLEAR_VGA_SCREEN    ENDP
 
 
;===================================
;SET_POINT (Xpos%, Ypos%, ColorNum%)
;===================================
;
; Plots a single Pixel on the active display page
;
; ENTRY: Xpos     = X position to plot pixel at
;        Ypos     = Y position to plot pixel at
;        ColorNum = Color to plot pixel with
;
; EXIT:  No meaningful values returned
;
 
SP_STACK    STRUC
                DW  ?,? ; BP, DI
                DD  ?   ; Caller
    SETP_Color  DB  ?,? ; Color of Point to Plot
    SETP_Ypos   DW  ?   ; Y pos of Point to Plot
    SETP_Xpos   DW  ?   ; X pos of Point to Plot
SP_STACK    ENDS
 
        PUBLIC SET_POINT
 
SET_POINT   PROC    FAR
 
    PUSHx   BP, DI              ; Preserve Registers
    MOV     BP, SP              ; Set up Stack Frame
 
    LES     DI, d CURRENT_PAGE  ; Point to Active VGA Page
 
    MOV     AX, [BP].SETP_Ypos  ; Get Line # of Pixel
    MUL     SCREEN_WIDTH        ; Get Offset to Start of Line
 
    MOV     BX, [BP].SETP_Xpos  ; Get Xpos
    MOV     CX, BX              ; Copy to extract Plane # from
    SHR     BX, 2               ; X offset (Bytes) = Xpos/4
    ADD     BX, AX              ; Offset = Width*Ypos + Xpos/4
 
    MOV     AX, MAP_MASK_PLANE1 ; Map Mask & Plane Select Register
    AND     CL, PLANE_BITS      ; Get Plane Bits
    SHL     AH, CL              ; Get Plane Select Value
    OUT_16  SC_Index, AX        ; Select Plane
 
    MOV     AL,[BP].SETP_Color  ; Get Pixel Color
    MOV     ES:[DI+BX], AL      ; Draw Pixel
 
    POPx    DI, BP              ; Restore Saved Registers
    RET     6                   ; Exit and Clean up Stack
 
SET_POINT        ENDP
 
 
;==========================
;READ_POINT% (Xpos%, Ypos%)
;==========================
;
; Read the color of a pixel from the Active Display Page
;
; ENTRY: Xpos = X position of pixel to read
;        Ypos = Y position of pixel to read
;
; EXIT:  AX   = Color of Pixel at (Xpos, Ypos)
;
 
RP_STACK    STRUC
            DW  ?,? ; BP, DI
            DD  ?   ; Caller
    RP_Ypos DW  ?   ; Y pos of Point to Read
    RP_Xpos DW  ?   ; X pos of Point to Read
RP_STACK    ENDS
 
        PUBLIC  READ_POINT
 
READ_POINT      PROC    FAR
 
    PUSHx   BP, DI              ; Preserve Registers
    MOV     BP, SP              ; Set up Stack Frame
 
    LES     DI, d CURRENT_PAGE  ; Point to Active VGA Page
 
    MOV     AX, [BP].RP_Ypos    ; Get Line # of Pixel
    MUL     SCREEN_WIDTH        ; Get Offset to Start of Line
 
    MOV     BX, [BP].RP_Xpos    ; Get Xpos
    MOV     CX, BX
    SHR     BX, 2               ; X offset (Bytes) = Xpos/4
    ADD     BX, AX              ; Offset = Width*Ypos + Xpos/4
 
    MOV     AL, READ_MAP        ; GC Read Mask Register
    MOV     AH, CL              ; Get Xpos
    AND     AH, PLANE_BITS      ; & mask out Plane #
    OUT_16  GC_INDEX, AX        ; Select Plane to read in
 
    CLR     AH                  ; Clear Return Value Hi byte
    MOV     AL, ES:[DI+BX]      ; Get Color of Pixel
 
    POPx    DI, BP              ; Restore Saved Registers
    RET     4                   ; Exit and Clean up Stack
 
READ_POINT        ENDP
 
 
;======================================================
;FILL_BLOCK (Xpos1%, Ypos1%, Xpos2%, Ypos2%, ColorNum%)
;======================================================
;
; Fills a rectangular block on the active display Page
;
; ENTRY: Xpos1    = Left X position of area to fill
;        Ypos1    = Top Y position of area to fill
;        Xpos2    = Right X position of area to fill
;        Ypos2    = Bottom Y position of area to fill
;        ColorNum = Color to fill area with
;
; EXIT:  No meaningful values returned
;
 
FB_STACK    STRUC
                DW  ?x4 ; DS, DI, SI, BP
                DD  ?   ; Caller
    FB_Color    DB  ?,? ; Fill Color
    FB_Ypos2    DW  ?   ; Y pos of Lower Right Pixel
    FB_Xpos2    DW  ?   ; X pos of Lower Right Pixel
    FB_Ypos1    DW  ?   ; Y pos of Upper Left Pixel
    FB_Xpos1    DW  ?   ; X pos of Upper Left Pixel
FB_STACK    ENDS
 
        PUBLIC    FILL_BLOCK
 
FILL_BLOCK  PROC    FAR
 
    PUSHx   BP, DS, SI, DI      ; Preserve Important Registers
    MOV     BP, SP              ; Set up Stack Frame
 
    LES     DI, d CURRENT_PAGE  ; Point to Active VGA Page
    CLD                         ; Direction Flag = Forward
 
    OUT_8   SC_INDEX, MAP_MASK  ; Set up for Plane Select
 
    ; Validate Pixel Coordinates
    ; If necessary, Swap so X1 <= X2, Y1 <= Y2
 
    MOV     AX, [BP].FB_Ypos1   ; AX = Y1   is Y1< Y2?
    MOV     BX, [BP].FB_Ypos2   ; BX = Y2
    CMP     AX, BX
    JLE     @FB_NOSWAP1
 
    MOV     [BP].FB_Ypos1, BX   ; Swap Y1 and Y2 and save Y1
    XCHG    AX, BX              ; on stack for future use
 
@FB_NOSWAP1:
    SUB     BX, AX              ; Get Y width
    INC     BX                  ; Add 1 to avoid 0 value
    MOV     [BP].FB_Ypos2, BX   ; Save in Ypos2
 
    MUL     SCREEN_WIDTH        ; Mul Y1 by Bytes per Line
    ADD     DI, AX              ; DI = Start of Line Y1
 
    MOV     AX, [BP].FB_Xpos1   ; Check X1 <= X2
    MOV     BX, [BP].FB_Xpos2   ;
    CMP     AX, BX
    JLE     @FB_NOSWAP2         ; Skip Ahead if Ok
 
    MOV     [BP].FB_Xpos2, AX   ; Swap X1 AND X2 and save X2
    XCHG    AX, BX              ; on stack for future use
 
    ; All our Input Values are in order, Now determine
    ; How many full "bands" 4 pixels wide (aligned) there
    ; are, and if there are partial bands (<4 pixels) on
    ; the left and right edges.
 
@FB_NOSWAP2:
    MOV     DX, AX              ; DX = X1 (Pixel Position)
    SHR     DX, 2               ; DX/4 = Bytes into Line
    ADD     DI, DX              ; DI = Addr of Upper-Left Corner
 
    MOV     CX, BX              ; CX = X2 (Pixel Position)
    SHR     CX, 2               ; CX/4 = Bytes into Line
 
    CMP     DX, CX              ; Start and end in same band?
    JNE     @FB_NORMAL          ; if not, check for l & r edges
    JMP     @FB_ONE_BAND_ONLY   ; if so, then special processing
 
@FB_NORMAL:
    SUB     CX, DX              ; CX = # bands -1
    MOV     SI, AX              ; SI = PLANE#(X1)
    AND     SI, PLANE_BITS      ; if Left edge is aligned then
    JZ      @FB_L_PLANE_FLUSH   ; no special processing..
 
    ; Draw "Left Edge" vertical strip of 1-3 pixels...
 
    OUT_8   SC_Data, Left_Clip_Mask[SI] ; Set Left Edge Plane Mask
 
    MOV     SI, DI              ; SI = Copy of Start Addr (UL)
 
    MOV     DX, [BP].FB_Ypos2   ; Get # of Lines to draw
    MOV     AL, [BP].FB_Color   ; Get Fill Color
    MOV     BX, SCREEN_WIDTH    ; Get Vertical increment Value
 
@FB_LEFT_LOOP:
    MOV     ES:[SI], AL         ; Fill in Left Edge Pixels
    ADD     SI, BX              ; Point to Next Line (Below)
    LOOPjz  DX, @FB_LEFT_CONT   ; Exit loop if all Lines Drawn
 
    MOV     ES:[SI], AL         ; Fill in Left Edge Pixels
    ADD     SI, BX              ; Point to Next Line (Below)
    LOOPx   DX, @FB_LEFT_LOOP   ; loop until left strip is drawn
 
@FB_LEFT_CONT:
 
    INC     DI                  ; Point to Middle (or Right) Block
    DEC     CX                  ; Reset CX instead of JMP @FB_RIGHT
 
@FB_L_PLANE_FLUSH:
    INC     CX                  ; Add in Left band to middle block
 
    ; DI = Addr of 1st middle Pixel (band) to fill
    ; CX = # of Bands to fill -1
 
@FB_RIGHT:
    MOV     SI, [BP].FB_Xpos2   ; Get Xpos2
    AND     SI, PLANE_BITS      ; Get Plane values
    CMP     SI, 0003            ; Plane = 3?
    JE      @FB_R_EDGE_FLUSH    ; Hey, add to middle
 
    ; Draw "Right Edge" vertical strip of 1-3 pixels...
 
    OUT_8   SC_Data, Right_Clip_Mask[SI]    ; Right Edge Plane Mask
 
    MOV     SI, DI              ; Get Addr of Left Edge
    ADD     SI, CX              ; Add Width-1 (Bands)
    DEC     SI                  ; To point to top of Right Edge
 
    MOV     DX, [BP].FB_Ypos2   ; Get # of Lines to draw
    MOV     AL, [BP].FB_Color   ; Get Fill Color
    MOV     BX, SCREEN_WIDTH    ; Get Vertical increment Value
 
@FB_RIGHT_LOOP:
    MOV     ES:[SI], AL         ; Fill in Right Edge Pixels
    ADD     SI, BX              ; Point to Next Line (Below)
    LOOPjz  DX, @FB_RIGHT_CONT  ; Exit loop if all Lines Drawn
 
    MOV     ES:[SI], AL         ; Fill in Right Edge Pixels
    ADD     SI, BX              ; Point to Next Line (Below)
    LOOPx   DX, @FB_RIGHT_LOOP  ; loop until left strip is drawn
 
@FB_RIGHT_CONT:
 
    DEC     CX                  ; Minus 1 for Middle bands
    JZ      @FB_EXIT            ; Uh.. no Middle bands...
 
@FB_R_EDGE_FLUSH:
 
    ; DI = Addr of Upper Left block to fill
    ; CX = # of Bands to fill in (width)
 
    OUT_8   SC_Data, ALL_PLANES ; Write to All Planes
 
    MOV     DX, SCREEN_WIDTH    ; DX = DI Increment
    SUB     DX, CX              ;  = Screen_Width-# Planes Filled
 
    MOV     BX, CX              ; BX = Quick Refill for CX
    MOV     SI, [BP].FB_Ypos2   ; SI = # of Line to Fill
    MOV     AL, [BP].FB_Color   ; Get Fill Color
 
@FB_MIDDLE_LOOP:
    REP     STOSB               ; Fill in entire line
 
    MOV     CX, BX              ; Recharge CX (Line Width)
    ADD     DI, DX              ; Point to start of Next Line
    LOOPx   SI, @FB_MIDDLE_LOOP ; Loop until all lines drawn
 
    JMP     s @FB_EXIT          ; Outa here
 
@FB_ONE_BAND_ONLY:
    MOV     SI, AX                  ; Get Left Clip Mask, Save X1
    AND     SI, PLANE_BITS          ; Mask out Row #
    MOV     AL, Left_Clip_Mask[SI]  ; Get Left Edge Mask
    MOV     SI, BX                  ; Get Right Clip Mask, Save X2
    AND     SI, PLANE_BITS          ; Mask out Row #
    AND     AL, Right_Clip_Mask[SI] ; Get Right Edge Mask byte
 
    OUT_8   SC_Data, AL         ; Clip For Left & Right Masks
 
    MOV     CX, [BP].FB_Ypos2   ; Get # of Lines to draw
    MOV     AL, [BP].FB_Color   ; Get Fill Color
    MOV     BX, SCREEN_WIDTH    ; Get Vertical increment Value
 
@FB_ONE_LOOP:
    MOV     ES:[DI], AL         ; Fill in Pixels
    ADD     DI, BX              ; Point to Next Line (Below)
    LOOPjz  CX, @FB_EXIT        ; Exit loop if all Lines Drawn
 
    MOV     ES:[DI], AL         ; Fill in Pixels
    ADD     DI, BX              ; Point to Next Line (Below)
    LOOPx   CX, @FB_ONE_LOOP    ; loop until left strip is drawn
 
@FB_EXIT:
    POPx    DI, SI, DS, BP      ; Restore Saved Registers
    RET     10                  ; Exit and Clean up Stack
 
FILL_BLOCK   ENDP
 
 
;=====================================================
;DRAW_LINE (Xpos1%, Ypos1%, Xpos2%, Ypos2%, ColorNum%)
;=====================================================
;
; Draws a Line on the active display page
;
; ENTRY: Xpos1    = X position of first point on line
;        Ypos1    = Y position of first point on line
;        Xpos2    = X position of last point on line
;        Ypos2    = Y position of last point on line
;        ColorNum = Color to draw line with
;
; EXIT:  No meaningful values returned
;
 
DL_STACK    STRUC
                DW  ?x3 ; DI, SI, BP
                DD  ?   ; Caller
    DL_ColorF   DB  ?,? ; Line Draw Color
    DL_Ypos2    DW  ?   ; Y pos of last point
    DL_Xpos2    DW  ?   ; X pos of last point
    DL_Ypos1    DW  ?   ; Y pos of first point
    DL_Xpos1    DW  ?   ; X pos of first point
DL_STACK    ENDS
 
        PUBLIC DRAW_LINE
 
DRAW_LINE   PROC    FAR
 
    PUSHx   BP, SI, DI          ; Preserve Important Registers
    MOV     BP, SP              ; Set up Stack Frame
    CLD                         ; Direction Flag = Forward
 
    OUT_8   SC_INDEX, MAP_MASK  ; Set up for Plane Select
    MOV     CH, [BP].DL_ColorF  ; Save Line Color in CH
 
    ; Check Line Type
 
    MOV     SI, [BP].DL_Xpos1   ; AX = X1   is X1< X2?
    MOV     DI, [BP].DL_Xpos2   ; DX = X2
    CMP     SI, DI              ; Is X1 < X2
    JE      @DL_VLINE           ; If X1=X2, Draw Vertical Line
    JL      @DL_NOSWAP1         ; If X1 < X2, don't swap
 
    XCHG    SI, DI              ; X2 IS > X1, SO SWAP THEM
 
@DL_NOSWAP1:
 
    ; SI = X1, DI = X2
 
    MOV     AX, [BP].DL_Ypos1   ; AX = Y1   is Y1 <> Y2?
    CMP     AX, [BP].DL_Ypos2   ; Y1 = Y2?
    JE      @DL_HORZ            ; If so, Draw a Horizontal Line
 
    JMP     @DL_BREZHAM         ; Diagonal line... go do it...
 
    ; This Code draws a Horizontal Line in Mode X where:
    ; SI = X1, DI = X2, and AX = Y1/Y2
 
@DL_HORZ:
 
    MUL     SCREEN_WIDTH        ; Offset = Ypos * Screen_Width
    MOV     DX, AX              ; CX = Line offset into Page
 
    MOV     AX, SI                  ; Get Left edge, Save X1
    AND     SI, PLANE_BITS          ; Mask out Row #
    MOV     BL, Left_Clip_Mask[SI]  ; Get Left Edge Mask
    MOV     CX, DI                  ; Get Right edge, Save X2
    AND     DI, PLANE_BITS          ; Mask out Row #
    MOV     BH, Right_Clip_Mask[DI] ; Get Right Edge Mask byte
 
    SHR     AX, 2               ; Get X1 Byte # (=X1/4)
    SHR     CX, 2               ; Get X2 Byte # (=X2/4)
 
    LES     DI, d CURRENT_PAGE  ; Point to Active VGA Page
    ADD     DI, DX              ; Point to Start of Line
    ADD     DI, AX              ; Point to Pixel X1
 
    SUB     CX, AX              ; CX = # Of Bands (-1) to set
    JNZ     @DL_LONGLN          ; jump if longer than one segment
 
    AND     BL, BH              ; otherwise, merge clip masks
 
@DL_LONGLN:
 
    OUT_8   SC_Data, BL         ; Set the Left Clip Mask
 
    MOV     AL, [BP].DL_ColorF  ; Get Line Color
    MOV     BL, AL              ; BL = Copy of Line Color
    STOSB                       ; Set Left (1-4) Pixels
 
    JCXZ    @DL_EXIT            ; Done if only one Line Segment
 
    DEC     CX                  ; CX = # of Middle Segments
    JZ      @DL_XRSEG           ; If no middle segments....
 
    ; Draw Middle Segments
 
    OUT_8   DX, ALL_PLANES      ; Write to ALL Planes
 
    MOV     AL, BL              ; Get Color from BL
    REP     STOSB               ; Draw Middle (4 Pixel) Segments
 
@DL_XRSEG:
    OUT_8   DX, BH              ; Select Planes for Right Clip Mask
    MOV     AL, BL              ; Get Color Value
    STOSB                       ; Draw Right (1-4) Pixels
 
    JMP     s @DL_EXIT          ; We Are Done...
 
 
    ; This Code Draws A Vertical Line.  On entry:
    ; CH = Line Color, SI & DI = X1
 
@DL_VLINE:
 
    MOV     AX, [BP].DL_Ypos1   ; AX = Y1
    MOV     SI, [BP].DL_Ypos2   ; SI = Y2
    CMP     AX, SI              ; Is Y1 < Y2?
    JLE     @DL_NOSWAP2         ; if so, Don't Swap them
 
    XCHG    AX, SI              ; Ok, NOW Y1 < Y2
 
@DL_NOSWAP2:
 
    SUB     SI, AX              ; SI = Line Height (Y2-Y1+1)
    INC     SI
 
    ; AX = Y1, DI = X1, Get offset into Page into AX
 
    MUL     SCREEN_WIDTH        ; Offset = Y1 (AX) * Screen Width
    MOV     DX, DI              ; Copy Xpos into DX
    SHR     DI, 2               ; DI = Xpos/4
    ADD     AX, DI              ; DI = Xpos/4 + ScreenWidth * Y1
 
    LES     DI, d CURRENT_PAGE  ; Point to Active VGA Page
    ADD     DI, AX              ; Point to Pixel X1, Y1
 
    ;Select Plane
 
    MOV     CL, DL              ; CL = Save X1
    AND     CL, PLANE_BITS      ; Get X1 MOD 4 (Plane #)
    MOV     AX, MAP_MASK_PLANE1 ; Code to set Plane #1
    SHL     AH, CL              ; Change to Correct Plane #
    OUT_16  SC_Index, AX        ; Select Plane
 
    MOV     AL, CH              ; Get Saved Color
    MOV     BX, SCREEN_WIDTH    ; Get Offset to Advance Line By
 
@DL_VLoop:
    MOV     ES:[DI], AL         ; Draw Single Pixel
    ADD     DI, BX              ; Point to Next Line
    LOOPjz  SI, @DL_EXIT        ; Lines--, Exit if done
 
    MOV     ES:[DI], AL         ; Draw Single Pixel
    ADD     DI, BX              ; Point to Next Line
    LOOPx   SI, @DL_VLoop       ; Lines--, Loop until Done
 
@DL_EXIT:
 
    JMP     @DL_EXIT2           ; Done!
 
    ; This code Draws a diagonal line in Mode X
 
@DL_BREZHAM:
    LES     DI, d CURRENT_PAGE  ; Point to Active VGA Page
 
    MOV     AX, [BP].DL_Ypos1   ; get Y1 value
    MOV     BX, [BP].DL_Ypos2   ; get Y2 value
    MOV     CX, [BP].DL_Xpos1   ; Get Starting Xpos
 
    CMP     BX, AX              ; Y2-Y1 is?
    JNC     @DL_DeltaYOK        ; if Y2>=Y1 then goto...
 
    XCHG    BX, AX              ; Swap em...
    MOV     CX, [BP].DL_Xpos2   ; Get New Starting Xpos
 
@DL_DeltaYOK:
    MUL     SCREEN_WIDTH        ; Offset = SCREEN_WIDTH * Y1
 
    ADD     DI, AX              ; DI -> Start of Line Y1 on Page
    MOV     AX, CX              ; AX = Xpos (X1)
    SHR     AX, 2               ; /4 = Byte Offset into Line
    ADD     DI, AX              ; DI = Starting pos (X1,Y1)
 
    MOV     AL, 11h             ; Staring Mask
    AND     CL, PLANE_BITS      ; Get Plane #
    SHL     AL, CL              ; and shift into place
    MOV     AH, [BP].DL_ColorF  ; Color in Hi Bytes
 
    PUSH    AX                  ; Save Mask,Color...
 
    MOV     AH, AL              ; Plane # in AH
    MOV     AL, MAP_MASK        ; Select Plane Register
    OUT_16  SC_Index, AX        ; Select initial plane
 
    MOV     AX, [BP].DL_Xpos1   ; get X1 value
    MOV     BX, [BP].DL_Ypos1   ; get Y1 value
    MOV     CX, [BP].DL_Xpos2   ; get X2 value
    MOV     DX, [BP].DL_Ypos2   ; get Y2 value
 
    MOV     BP, SCREEN_WIDTH    ; Use BP for Line width to
                                ; to avoid extra memory access
 
    SUB     DX, BX              ; figure Delta_Y
    JNC     @DL_DeltaYOK2       ; jump if Y2 >= Y1
 
    ADD     BX, DX              ; put Y2 into Y1
    NEG     DX                  ; abs(Delta_Y)
    XCHG    AX, CX              ; and exchange X1 and X2
 
@DL_DeltaYOK2:
    MOV     BX, 08000H          ; seed for fraction accumulator
 
    SUB     CX, AX              ; figure Delta_X
    JC      @DL_DrawLeft        ; if negative, go left
 
    JMP     @DL_DrawRight       ; Draw Line that slopes right
 
@DL_DrawLeft:
 
    NEG     CX                  ; abs(Delta_X)
 
    CMP     CX, DX              ; is Delta_X < Delta_Y?
    JB      @DL_SteepLeft       ; yes, so go do steep line
                                ; (Delta_Y iterations)
 
    ; Draw a Shallow line to the left in Mode X
 
@DL_ShallowLeft:
    CLR     AX                  ; zero low word of Delta_Y * 10000h
    SUB     AX, DX              ; DX:AX <- DX * 0FFFFh
    SBB     DX, 0               ; include carry
    DIV     CX                  ; divide by Delta_X
 
    MOV     SI, BX              ; SI = Accumulator
    MOV     BX, AX              ; BX = Add fraction
    POP     AX                  ; Get Color, Bit mask
    MOV     DX, SC_Data         ; Sequence controller data register
    INC     CX                  ; Inc Delta_X so we can unroll loop
 
    ; Loop (x2) to Draw Pixels, Move Left, and Maybe Down...
 
@DL_SLLLoop:
    MOV     ES:[DI], AH         ; set first pixel, plane data set up
    LOOPjz  CX, @DL_SLLExit     ; Delta_X--, Exit if done
 
    ADD     SI, BX              ; add numerator to accumulator
    JNC     @DL_SLLL2nc         ; move down on carry
 
    ADD     DI, BP              ; Move Down one line...
 
@DL_SLLL2nc:
    DEC     DI                  ; Left one addr
    ROR     AL, 1               ; Move Left one plane, back on 0 1 2
    CMP     AL, 87h             ; wrap?, if AL <88 then Carry set
    ADC     DI, 0               ; Adjust Address: DI = DI + Carry
    OUT     DX, AL              ; Set up New Bit Plane mask
 
    MOV     ES:[DI], AH         ; set pixel
    LOOPjz  CX, @DL_SLLExit     ; Delta_X--, Exit if done
 
    ADD     SI, BX              ; add numerator to accumulator,
    JNC     @DL_SLLL3nc         ; move down on carry
 
    ADD     DI, BP              ; Move Down one line...
 
@DL_SLLL3nc:                    ; Now move left a pixel...
    DEC     DI                  ; Left one addr
    ROR     AL, 1               ; Move Left one plane, back on 0 1 2
    CMP     AL, 87h             ; Wrap?, if AL <88 then Carry set
    ADC     DI, 0               ; Adjust Address: DI = DI + Carry
    OUT     DX, AL              ; Set up New Bit Plane mask
    JMP     s @DL_SLLLoop       ; loop until done
 
@DL_SLLExit:
    JMP     @DL_EXIT2           ; and exit
 
    ; Draw a steep line to the left in Mode X
 
@DL_SteepLeft:
    CLR     AX                  ; zero low word of Delta_Y * 10000h
    XCHG    DX, CX              ; Delta_Y switched with Delta_X
    DIV     CX                  ; divide by Delta_Y
 
    MOV     SI, BX              ; SI = Accumulator
    MOV     BX, AX              ; BX = Add Fraction
    POP     AX                  ; Get Color, Bit mask
    MOV     DX, SC_Data         ; Sequence controller data register
    INC     CX                  ; Inc Delta_Y so we can unroll loop
 
    ; Loop (x2) to Draw Pixels, Move Down, and Maybe left
 
@DL_STLLoop:
 
    MOV     ES:[DI], AH         ; set first pixel
    LOOPjz  CX, @DL_STLExit     ; Delta_Y--, Exit if done
 
    ADD     SI, BX              ; add numerator to accumulator
    JNC     @DL_STLnc2          ; No carry, just move down!
 
    DEC     DI                  ; Move Left one addr
    ROR     AL, 1               ; Move Left one plane, back on 0 1 2
    CMP     AL, 87h             ; Wrap?, if AL <88 then Carry set
    ADC     DI, 0               ; Adjust Address: DI = DI + Carry
    OUT     DX, AL              ; Set up New Bit Plane mask
 
@DL_STLnc2:
    ADD     DI, BP              ; advance to next line.
 
    MOV     ES:[DI], AH         ; set pixel
    LOOPjz  CX, @DL_STLExit     ; Delta_Y--, Exit if done
 
    ADD     SI, BX              ; add numerator to accumulator
    JNC     @DL_STLnc3          ; No carry, just move down!
 
    DEC     DI                  ; Move Left one addr
    ROR     AL, 1               ; Move Left one plane, back on 0 1 2
    CMP     AL, 87h             ; Wrap?, if AL <88 then Carry set
    ADC     DI, 0               ; Adjust Address: DI = DI + Carry
    OUT     DX, AL              ; Set up New Bit Plane mask
 
@DL_STLnc3:
    ADD     DI, BP              ; advance to next line.
    JMP     s @DL_STLLoop       ; Loop until done
 
@DL_STLExit:
    JMP     @DL_EXIT2           ; and exit
 
    ; Draw a line that goes to the Right...
 
@DL_DrawRight:
    CMP     CX, DX              ; is Delta_X < Delta_Y?
    JB      @DL_SteepRight      ; yes, so go do steep line
                                ; (Delta_Y iterations)
 
    ; Draw a Shallow line to the Right in Mode X
 
@DL_ShallowRight:
    CLR     AX                  ; zero low word of Delta_Y * 10000h
    SUB     AX, DX              ; DX:AX <- DX * 0FFFFh
    SBB     DX, 0               ; include carry
    DIV     CX                  ; divide by Delta_X
 
    MOV     SI, BX              ; SI = Accumulator
    MOV     BX, AX              ; BX = Add Fraction
    POP     AX                  ; Get Color, Bit mask
    MOV     DX, SC_Data         ; Sequence controller data register
    INC     CX                  ; Inc Delta_X so we can unroll loop
 
    ; Loop (x2) to Draw Pixels, Move Right, and Maybe Down...
 
@DL_SLRLoop:
    MOV     ES:[DI], AH         ; set first pixel, mask is set up
    LOOPjz  CX, @DL_SLRExit     ; Delta_X--, Exit if done..
 
    ADD     SI, BX              ; add numerator to accumulator
    JNC     @DL_SLR2nc          ; don't move down if carry not set
 
    ADD     DI, BP              ; Move Down one line...
 
@DL_SLR2nc:                     ; Now move right a pixel...
    ROL     AL, 1               ; Move Right one addr if Plane = 0
    CMP     AL, 12h             ; Wrap? if AL >12 then Carry not set
    ADC     DI, 0               ; Adjust Address: DI = DI + Carry
    OUT     DX, AL              ; Set up New Bit Plane mask
 
    MOV     ES:[DI], AH         ; set pixel
    LOOPjz  CX, @DL_SLRExit     ; Delta_X--, Exit if done..
 
    ADD     SI, BX              ; add numerator to accumulator
    JNC     @DL_SLR3nc          ; don't move down if carry not set
 
    ADD     DI, BP              ; Move Down one line...
 
@DL_SLR3nc:
    ROL     AL, 1               ; Move Right one addr if Plane = 0
    CMP     AL, 12h             ; Wrap? if AL >12 then Carry not set
    ADC     DI, 0               ; Adjust Address: DI = DI + Carry
    OUT     DX, AL              ; Set up New Bit Plane mask
    JMP     s @DL_SLRLoop       ; loop till done
 
@DL_SLRExit:
    JMP     @DL_EXIT2           ; and exit
 
    ; Draw a Steep line to the Right in Mode X
 
@DL_SteepRight:
    CLR     AX                  ; zero low word of Delta_Y * 10000h
    XCHG    DX, CX              ; Delta_Y switched with Delta_X
    DIV     CX                  ; divide by Delta_Y
 
    MOV     SI, BX              ; SI = Accumulator
    MOV     BX, AX              ; BX = Add Fraction
    POP     AX                  ; Get Color, Bit mask
    MOV     DX, SC_Data         ; Sequence controller data register
    INC     CX                  ; Inc Delta_Y so we can unroll loop
 
    ; Loop (x2) to Draw Pixels, Move Down, and Maybe Right
 
@STRLoop:
    MOV     ES:[DI], AH         ; set first pixel, mask is set up
    LOOPjz  CX, @DL_EXIT2       ; Delta_Y--, Exit if Done
 
    ADD     SI, BX              ; add numerator to accumulator
    JNC     @STRnc2             ; if no carry then just go down...
 
    ROL     AL, 1               ; Move Right one addr if Plane = 0
    CMP     AL, 12h             ; Wrap? if AL >12 then Carry not set
    ADC     DI, 0               ; Adjust Address: DI = DI + Carry
    OUT     DX, AL              ; Set up New Bit Plane mask
 
@STRnc2:
    ADD     DI, BP              ; advance to next line.
 
    MOV     ES:[DI], AH         ; set pixel
    LOOPjz  CX, @DL_EXIT2       ; Delta_Y--, Exit if Done
 
    ADD     SI, BX              ; add numerator to accumulator
    JNC     @STRnc3             ; if no carry then just go down...
 
    ROL     AL, 1               ; Move Right one addr if Plane = 0
    CMP     AL, 12h             ; Wrap? if AL >12 then Carry not set
    ADC     DI, 0               ; Adjust Address: DI = DI + Carry
    OUT     DX, AL              ; Set up New Bit Plane mask
 
@STRnc3:
    ADD     DI, BP              ; advance to next line.
    JMP     s @STRLoop          ; loop till done
 
@DL_EXIT2:
    POPx    DI, SI, BP          ; Restore Saved Registers
    RET     10                  ; Exit and Clean up Stack
 
DRAW_LINE        ENDP
 
 
    ; ===== DAC COLOR REGISTER ROUTINES =====
 
;=================================================
;SET_DAC_REGISTER (Register%, Red%, Green%, Blue%)
;=================================================
;
; Sets a single (RGB) Vga Palette Register
;
; ENTRY: Register = The DAC # to modify (0-255)
;        Red      = The new Red Intensity (0-63)
;        Green    = The new Green Intensity (0-63)
;        Blue     = The new Blue Intensity (0-63)
;
; EXIT:  No meaningful values returned
;
 
SDR_STACK   STRUC
                    DW  ?   ; BP
                    DD  ?   ; Caller
    SDR_Blue        DB  ?,? ; Blue Data Value
    SDR_Green       DB  ?,? ; Green Data Value
    SDR_Red         DB  ?,? ; Red Data Value
    SDR_Register    DB  ?,? ; Palette Register #
SDR_STACK   ENDS
 
    PUBLIC  SET_DAC_REGISTER
 
SET_DAC_REGISTER    PROC    FAR
 
    PUSH    BP                  ; Save BP
    MOV     BP, SP              ; Set up Stack Frame
 
    ; Select which DAC Register to modify
 
    OUT_8   DAC_WRITE_ADDR, [BP].SDR_Register
 
    MOV     DX, PEL_DATA_REG    ; Dac Data Register
    OUT_8   DX, [BP].SDR_Red    ; Set Red Intensity
    OUT_8   DX, [BP].SDR_Green  ; Set Green Intensity
    OUT_8   DX, [BP].SDR_Blue   ; Set Blue Intensity
 
    POP     BP                  ; Restore Registers
    RET     8                   ; Exit & Clean Up Stack
 
SET_DAC_REGISTER    ENDP
 
;====================================================
;GET_DAC_REGISTER (Register%, &Red%, &Green%, &Blue%)
;====================================================
;
; Reads the RGB Values of a single Vga Palette Register
;
; ENTRY: Register = The DAC # to read (0-255)
;        Red      = Offset to Red Variable in DS
;        Green    = Offset to Green Variable in DS
;        Blue     = Offset to Blue Variable in DS
;
; EXIT:  The values of the integer variables Red,
;        Green, and Blue are set to the values
;        taken from the specified DAC register.
;
 
GDR_STACK   STRUC
                    DW  ?   ; BP
                    DD  ?   ; Caller
    GDR_Blue        DW  ?   ; Addr of Blue Data Value in DS
    GDR_Green       DW  ?   ; Addr of Green Data Value in DS
    GDR_Red         DW  ?   ; Addr of Red Data Value in DS
    GDR_Register    DB  ?,? ; Palette Register #
GDR_STACK   ENDS
 
    PUBLIC  GET_DAC_REGISTER
 
GET_DAC_REGISTER    PROC    FAR
 
    PUSH    BP                  ; Save BP
    MOV     BP, SP              ; Set up Stack Frame
 
    ; Select which DAC Register to read in
 
    OUT_8   DAC_READ_ADDR, [BP].GDR_Register
 
    MOV     DX, PEL_DATA_REG    ; Dac Data Register
    CLR     AX                  ; Clear AX
 
    IN      AL, DX              ; Read Red Value
    MOV     BX, [BP].GDR_Red    ; Get Address of Red%
    MOV     [BX], AX            ; *Red% = AX
 
    IN      AL, DX              ; Read Green Value
    MOV     BX, [BP].GDR_Green  ; Get Address of Green%
    MOV     [BX], AX            ; *Green% = AX
 
    IN      AL, DX              ; Read Blue Value
    MOV     BX, [BP].GDR_Blue   ; Get Address of Blue%
    MOV     [BX], AX            ; *Blue% = AX
 
    POP     BP                  ; Restore Registers
    RET     8                   ; Exit & Clean Up Stack
 
GET_DAC_REGISTER    ENDP
 
 
;===========================================================
;LOAD_DAC_REGISTERS (SEG PalData, StartReg%, EndReg%, Sync%)
;===========================================================
;
; Sets a Block of Vga Palette Registers
;
; ENTRY: PalData  = Far Pointer to Block of palette data
;        StartReg = First Register # in range to set (0-255)
;        EndReg   = Last Register # in Range to set (0-255)
;        Sync     = Wait for Vertical Retrace Flag (Boolean)
;
; EXIT:  No meaningful values returned
;
; NOTES: PalData is a linear array of 3 byte Palette values
;        in the order: Red  (0-63), Green (0-63), Blue (0-63)
;
 
LDR_STACK   STRUC
                    DW  ?x3 ; BP, DS, SI
                    DD  ?   ; Caller
    LDR_Sync        DW  ?   ; Vertical Sync Flag
    LDR_EndReg      DB  ?,? ; Last Register #
    LDR_StartReg    DB  ?,? ; First Register #
    LDR_PalData     DD  ?   ; Far Ptr to Palette Data
LDR_STACK   ENDS
 
    PUBLIC  LOAD_DAC_REGISTERS
 
LOAD_DAC_REGISTERS  PROC    FAR
 
    PUSHx   BP, DS, SI          ; Save Registers
    mov     BP, SP              ; Set up Stack Frame
 
    mov     AX, [BP].LDR_Sync   ; Get Vertical Sync Flag
    or      AX, AX              ; is Sync Flag = 0?
    jz      @LDR_Load           ; if so, skip call
 
    call    f SYNC_DISPLAY      ; wait for vsync
 
    ; Determine register #'s, size to copy, etc
 
@LDR_Load:
 
    lds     SI, [BP].LDR_PalData    ; DS:SI -> Palette Data
    mov     DX, DAC_WRITE_ADDR      ; DAC register # selector
 
    CLR     AX, BX                  ; Clear for byte loads
    mov     AL, [BP].LDR_StartReg   ; Get Start Register
    mov     BL, [BP].LDR_EndReg     ; Get End Register
 
    sub     BX, AX              ; BX = # of DAC registers -1
    inc     BX                  ; BX = # of DAC registers
    mov     CX, BX              ; CX = # of DAC registers
    add     CX, BX              ; CX =  "   " * 2
    add     CX, BX              ; CX =  "   " * 3
    cld                         ; Block OUTs forward
    out     DX, AL              ; set up correct register #
 
    ; Load a block of DAC Registers
 
    mov     DX, PEL_DATA_REG    ; Dac Data Register
 
    rep     outsb               ; block set DAC registers
 
    POPx    SI, DS, BP          ; Restore Registers
    ret     10                  ; Exit & Clean Up Stack
 
LOAD_DAC_REGISTERS  ENDP
 
 
;====================================================
;READ_DAC_REGISTERS (SEG PalData, StartReg%, EndReg%)
;====================================================
;
; Reads a Block of Vga Palette Registers
;
; ENTRY: PalData  = Far Pointer to block to store palette data
;        StartReg = First Register # in range to read (0-255)
;        EndReg   = Last Register # in Range to read (0-255)
;
; EXIT:  No meaningful values returned
;
; NOTES: PalData is a linear array of 3 byte Palette values
;        in the order: Red  (0-63), Green (0-63), Blue (0-63)
;
 
RDR_STACK   STRUC
                    DW  ?x3 ; BP, ES, DI
                    DD  ?   ; Caller
    RDR_EndReg      DB  ?,? ; Last Register #
    RDR_StartReg    DB  ?,? ; First Register #
    RDR_PalData     DD  ?   ; Far Ptr to Palette Data
RDR_STACK   ENDS
 
    PUBLIC  READ_DAC_REGISTERS
 
READ_DAC_REGISTERS  PROC    FAR
 
    PUSHx   BP, ES, DI          ; Save Registers
    mov     BP, SP              ; Set up Stack Frame
 
    ; Determine register #'s, size to copy, etc
 
    les     DI, [BP].RDR_PalData    ; ES:DI -> Palette Buffer
    mov     DX, DAC_READ_ADDR       ; DAC register # selector
 
    CLR     AX, BX                  ; Clear for byte loads
    mov     AL, [BP].RDR_StartReg   ; Get Start Register
    mov     BL, [BP].RDR_EndReg     ; Get End Register
 
    sub     BX, AX              ; BX = # of DAC registers -1
    inc     BX                  ; BX = # of DAC registers
    mov     CX, BX              ; CX = # of DAC registers
    add     CX, BX              ; CX =  "   " * 2
    add     CX, BX              ; CX =  "   " * 3
    cld                         ; Block INs forward
 
    ; Read a block of DAC Registers
 
    out     DX, AL              ; set up correct register #
    mov     DX, PEL_DATA_REG    ; Dac Data Register
 
    rep     insb                ; block read DAC registers
 
    POPx    DI, ES, BP          ; Restore Registers
    ret     8                   ; Exit & Clean Up Stack
 
READ_DAC_REGISTERS  ENDP
 
 
    ; ===== PAGE FLIPPING AND SCROLLING ROUTINES =====
 
;=========================
;SET_ACTIVE_PAGE (PageNo%)
;=========================
;
; Sets the active display Page to be used for future drawing
;
; ENTRY: PageNo = Display Page to make active
;        (values: 0 to Number of Pages - 1)
;
; EXIT:  No meaningful values returned
;
 
SAP_STACK   STRUC
                DW  ?   ; BP
                DD  ?   ; Caller
    SAP_Page    DW  ?   ; Page # for Drawing
SAP_STACK   ENDS
 
    PUBLIC  SET_ACTIVE_PAGE
 
SET_ACTIVE_PAGE PROC    FAR
 
    PUSH    BP                  ; Preserve Registers
    MOV     BP, SP              ; Set up Stack Frame
 
    MOV     BX, [BP].SAP_Page   ; Get Desired Page #
    CMP     BX, LAST_PAGE       ; Is Page # Valid?
    JAE     @SAP_Exit           ; IF Not, Do Nothing
 
    MOV     ACTIVE_PAGE, BX     ; Set Active Page #
 
    SHL     BX, 1               ; Scale Page # to Word
    MOV     AX, PAGE_ADDR[BX]   ; Get offset to Page
 
    MOV     CURRENT_PAGE, AX    ; And set for future LES's
 
@SAP_Exit:
    POP     BP                  ; Restore Registers
    RET     2                   ; Exit and Clean up Stack
 
SET_ACTIVE_PAGE ENDP
 
 
;================
;GET_ACTIVE_PAGE%
;================
;
; Returns the Video Page # currently used for Drawing
;
; ENTRY: No Parameters are passed
;
; EXIT:  AX = Current Video Page used for Drawing
;
 
    PUBLIC  GET_ACTIVE_PAGE
 
GET_ACTIVE_PAGE PROC    FAR
 
    MOV     AX, ACTIVE_PAGE     ; Get Active Page #
    RET                         ; Exit and Clean up Stack
 
GET_ACTIVE_PAGE ENDP
 
 
;===============================
;SET_DISPLAY_PAGE (DisplayPage%)
;===============================
;
; Sets the currently visible display page.
; When called this routine syncronizes the display
; to the vertical blank.
;
; ENTRY: PageNo = Display Page to show on the screen
;        (values: 0 to Number of Pages - 1)
;
; EXIT:  No meaningful values returned
;
 
SDP_STACK   STRUC
                DW  ?       ; BP
                DD  ?       ; Caller
    SDP_Page    DW  ?       ; Page # to Display...
SDP_STACK   ENDS
 
    PUBLIC  SET_DISPLAY_PAGE
 
SET_DISPLAY_PAGE    PROC    FAR
 
    PUSH    BP                  ; Preserve Registers
    MOV     BP, SP              ; Set up Stack Frame
 
    MOV     BX, [BP].SDP_Page   ; Get Desired Page #
    CMP     BX, LAST_PAGE       ; Is Page # Valid?
    JAE     @SDP_Exit           ; IF Not, Do Nothing
 
    MOV     DISPLAY_PAGE, BX    ; Set Display Page #
 
    SHL     BX, 1               ; Scale Page # to Word
    MOV     CX, PAGE_ADDR[BX]   ; Get offset in memory to Page
    ADD     CX, CURRENT_MOFFSET ; Adjust for any scrolling
 
    ; Wait if we are currently in a Vertical Retrace
 
    MOV     DX, INPUT_1         ; Input Status #1 Register
 
@DP_WAIT0:
    IN      AL, DX              ; Get VGA status
    AND     AL, VERT_RETRACE    ; In Display mode yet?
    JNZ     @DP_WAIT0           ; If Not, wait for it
 
    ; Set the Start Display Address to the new page
 
    MOV     DX, CRTC_Index      ; We Change the VGA Sequencer
 
    MOV     AL, START_DISP_LO   ; Display Start Low Register
    MOV     AH, CL              ; Low 8 Bits of Start Addr
    OUT     DX, AX              ; Set Display Addr Low
 
    MOV     AL, START_DISP_HI   ; Display Start High Register
    MOV     AH, CH              ; High 8 Bits of Start Addr
    OUT     DX, AX              ; Set Display Addr High
 
    ; Wait for a Vertical Retrace to smooth out things
 
    MOV     DX, INPUT_1         ; Input Status #1 Register
 
@DP_WAIT1:
    IN      AL, DX              ; Get VGA status
    AND     AL, VERT_RETRACE    ; Vertical Retrace Start?
    JZ      @DP_WAIT1           ; If Not, wait for it
 
    ; Now Set Display Starting Address
 
 
@SDP_Exit:
    POP     BP                  ; Restore Registers
    RET     2                   ; Exit and Clean up Stack
 
SET_DISPLAY_PAGE    ENDP
 
 
;=================
;GET_DISPLAY_PAGE%
;=================
;
; Returns the Video Page # currently displayed
;
; ENTRY: No Parameters are passed
;
; EXIT:  AX = Current Video Page being displayed
;
 
    PUBLIC  GET_DISPLAY_PAGE
 
GET_DISPLAY_PAGE    PROC    FAR
 
    MOV     AX, DISPLAY_PAGE    ; Get Display Page #
    RET                         ; Exit & Clean Up Stack
 
GET_DISPLAY_PAGE    ENDP
 
 
;=======================================
;SET_WINDOW (DisplayPage%, Xpos%, Ypos%)
;=======================================
;
; Since a Logical Screen can be larger than the Physical
; Screen, Scrolling is possible.  This routine sets the
; Upper Left Corner of the Screen to the specified Pixel.
; Also Sets the Display page to simplify combined page
; flipping and scrolling.  When called this routine
; syncronizes the display to the vertical blank.
;
; ENTRY: DisplayPage = Display Page to show on the screen
;        Xpos        = # of pixels to shift screen right
;        Ypos        = # of lines to shift screen down
;
; EXIT:  No meaningful values returned
;
 
SW_STACK    STRUC
                DW  ?   ; BP
                DD  ?   ; Caller
    SW_Ypos     DW  ?   ; Y pos of UL Screen Corner
    SW_Xpos     DW  ?   ; X pos of UL Screen Corner
    SW_Page     DW  ?   ; (new) Display Page
SW_STACK    ENDS
 
        PUBLIC SET_WINDOW
 
SET_WINDOW  PROC    FAR
 
    PUSH    BP                  ; Preserve Registers
    MOV     BP, SP              ; Set up Stack Frame
 
    ; Check if our Scroll Offsets are Valid
 
    MOV     BX, [BP].SW_Page    ; Get Desired Page #
    CMP     BX, LAST_PAGE       ; Is Page # Valid?
    JAE     @SW_Exit            ; IF Not, Do Nothing
 
    MOV     AX, [BP].SW_Ypos    ; Get Desired Y Offset
    CMP     AX, MAX_YOFFSET     ; Is it Within Limits?
    JA      @SW_Exit            ; if not, exit
 
    MOV     CX, [BP].SW_Xpos    ; Get Desired X Offset
    CMP     CX, MAX_XOFFSET     ; Is it Within Limits?
    JA      @SW_Exit            ; if not, exit
 
    ; Compute proper Display start address to use
 
    MUL     SCREEN_WIDTH        ; AX = YOffset * Line Width
    SHR     CX, 2               ; CX / 4 = Bytes into Line
    ADD     AX, CX              ; AX = Offset of Upper Left Pixel
 
    MOV     CURRENT_MOFFSET, AX ; Save Offset Info
 
    MOV     DISPLAY_PAGE, BX    ; Set Current Page #
    SHL     BX, 1               ; Scale Page # to Word
    ADD     AX, PAGE_ADDR[BX]   ; Get offset in VGA to Page
    MOV     BX, AX              ; BX = Desired Display Start
 
    MOV     DX, INPUT_1         ; Input Status #1 Register
 
    ; Wait if we are currently in a Vertical Retrace
 
@SW_WAIT0:
    IN      AL, DX              ; Get VGA status
    AND     AL, VERT_RETRACE    ; In Display mode yet?
    JNZ     @SW_WAIT0           ; If Not, wait for it
 
    ; Set the Start Display Address to the new window
 
    MOV     DX, CRTC_Index      ; We Change the VGA Sequencer
    MOV     AL, START_DISP_LO   ; Display Start Low Register
    MOV     AH, BL              ; Low 8 Bits of Start Addr
    OUT     DX, AX              ; Set Display Addr Low
 
    MOV     AL, START_DISP_HI   ; Display Start High Register
    MOV     AH, BH              ; High 8 Bits of Start Addr
    OUT     DX, AX              ; Set Display Addr High
 
    ; Wait for a Vertical Retrace to smooth out things
 
    MOV     DX, INPUT_1         ; Input Status #1 Register
 
@SW_WAIT1:
    IN      AL, DX              ; Get VGA status
    AND     AL, VERT_RETRACE    ; Vertical Retrace Start?
    JZ      @SW_WAIT1           ; If Not, wait for it
 
    ; Now Set the Horizontal Pixel Pan values
 
    OUT_8   ATTRIB_Ctrl, PIXEL_PAN_REG  ; Select Pixel Pan Register
 
    MOV     AX, [BP].SW_Xpos    ; Get Desired X Offset
    AND     AL, 03              ; Get # of Pixels to Pan (0-3)
    SHL     AL, 1               ; Shift for 256 Color Mode
    OUT     DX, AL              ; Fine tune the display!
 
@SW_Exit:
    POP     BP                  ; Restore Saved Registers
    RET     6                   ; Exit and Clean up Stack
 
SET_WINDOW        ENDP
 
 
;=============
;GET_X_OFFSET%
;=============
;
; Returns the X coordinate of the Pixel currently display
; in the upper left corner of the display
;
; ENTRY: No Parameters are passed
;
; EXIT:  AX = Current Horizontal Scroll Offset
;
 
    PUBLIC  GET_X_OFFSET
 
GET_X_OFFSET    PROC    FAR
 
    MOV     AX, CURRENT_XOFFSET ; Get current horz offset
    RET                         ; Exit & Clean Up Stack
 
GET_X_OFFSET    ENDP
 
 
;=============
;GET_Y_OFFSET%
;=============
;
; Returns the Y coordinate of the Pixel currently display
; in the upper left corner of the display
;
; ENTRY: No Parameters are passed
;
; EXIT:  AX = Current Vertical Scroll Offset
;
 
    PUBLIC  GET_Y_OFFSET
 
GET_Y_OFFSET    PROC    FAR
 
    MOV     AX, CURRENT_YOFFSET ; Get current vertical offset
    RET                         ; Exit & Clean Up Stack
 
GET_Y_OFFSET    ENDP
 
 
;============
;SYNC_DISPLAY
;============
;
; Pauses the computer until the next Vertical Retrace starts
;
; ENTRY: No Parameters are passed
;
; EXIT:  No meaningful values returned
;
 
    PUBLIC  SYNC_DISPLAY
 
SYNC_DISPLAY    PROC    FAR
 
    MOV     DX, INPUT_1         ; Input Status #1 Register
 
    ; Wait for any current retrace to end
 
@SD_WAIT0:
    IN      AL, DX              ; Get VGA status
    AND     AL, VERT_RETRACE    ; In Display mode yet?
    JNZ     @SD_WAIT0           ; If Not, wait for it
 
    ; Wait for the start of the next vertical retrace
 
@SD_WAIT1:
    IN      AL, DX              ; Get VGA status
    AND     AL, VERT_RETRACE    ; Vertical Retrace Start?
    JZ      @SD_WAIT1           ; If Not, wait for it
 
    RET                         ; Exit & Clean Up Stack
 
SYNC_DISPLAY    ENDP
 
 
    ; ===== TEXT DISPLAY ROUTINES =====
 
;==================================================
;GPRINTC (CharNum%, Xpos%, Ypos%, ColorF%, ColorB%)
;==================================================
;
; Draws an ASCII Text Character using the currently selected
; 8x8 font on the active display page.  It would be a simple
; exercise to make this routine process variable height fonts.
;
; ENTRY: CharNum = ASCII character # to draw
;        Xpos    = X position to draw Character at
;        Ypos    = Y position of to draw Character at
;        ColorF  = Color to draw text character in
;        ColorB  = Color to set background to
;
; EXIT:  No meaningful values returned
;
 
GPC_STACK   STRUC
    GPC_Width   DW  ?   ; Screen Width-1
    GPC_Lines   DB  ?,? ; Scan lines to Decode
    GPC_T_SETS  DW  ?   ; Saved Charset Segment
    GPC_T_SETO  DW  ?   ; Saved Charset Offset
                DW  ?x4 ; DI, SI, DS, BP
                DD  ?   ; Caller
    GPC_ColorB  DB  ?,? ; Background Color
    GPC_ColorF  DB  ?,? ; Text Color
    GPC_Ypos    DW  ?   ; Y Position to Print at
    GPC_Xpos    DW  ?   ; X position to Print at
    GPC_Char    DB  ?,? ; Character to Print
GPC_STACK   ENDS
 
        PUBLIC GPRINTC
 
GPRINTC     PROC    FAR
 
    PUSHx   BP, DS, SI, DI      ; Preserve Important Registers
    SUB     SP, 8               ; Allocate WorkSpace on Stack
    MOV     BP, SP              ; Set up Stack Frame
 
    LES     DI, d CURRENT_PAGE  ; Point to Active VGA Page
 
    MOV     AX, SCREEN_WIDTH    ; Get Logical Line Width
    MOV     BX, AX              ; BX = Screen Width
    DEC     BX                  ;    = Screen Width-1
    MOV     [BP].GPC_Width, BX  ; Save for later use
 
    MUL     [BP].GPC_Ypos       ; Start of Line = Ypos * Width
    ADD     DI, AX              ; DI -> Start of Line Ypos
 
    MOV     AX, [BP].GPC_Xpos   ; Get Xpos of Character
    MOV     CX, AX              ; Save Copy of Xpos
    SHR     AX, 2               ; Bytes into Line = Xpos/4
    ADD     DI, AX              ; DI -> (Xpos, Ypos)
 
    ;Get Source ADDR of Character Bit Map  & Save
 
    MOV     AL, [BP].GPC_Char   ; Get Character #
    TEST    AL, 080h            ; Is Hi Bit Set?
    JZ      @GPC_LowChar        ; Nope, use low char set ptr
 
    AND     AL, 07Fh            ; Mask Out Hi Bit
    MOV     BX, CHARSET_HI      ; BX = Char Set Ptr:Offset
    MOV     DX, CHARSET_HI+2    ; DX = Char Set Ptr:Segment
    JMP     s @GPC_Set_Char     ; Go Setup Character Ptr
 
@GPC_LowChar:
 
    MOV     BX, CHARSET_LOW     ; BX = Char Set Ptr:Offset
    MOV     DX, CHARSET_LOW+2   ; DX = Char Set Ptr:Segment
 
@GPC_Set_Char:
    MOV     [BP].GPC_T_SETS, DX ; Save Segment on Stack
 
    MOV     AH, 0               ; Valid #'s are 0..127
    SHL     AX, 3               ; * 8 Bytes Per Bitmap
    ADD     BX, AX              ; BX = Offset of Selected char
    MOV     [BP].GPC_T_SETO, BX ; Save Offset on Stack
 
    AND     CX, PLANE_BITS      ; Get Plane #
    MOV     CH, ALL_PLANES      ; Get Initial Plane mask
    SHL     CH, CL              ; And shift into position
    AND     CH, ALL_PLANES      ; And mask to lower nibble
 
    MOV     AL, 04              ; 4-Plane # = # of initial
    SUB     AL, CL              ; shifts to align bit mask
    MOV     CL, AL              ; Shift Count for SHL
 
    ;Get segment of character map
 
    OUT_8   SC_Index, MAP_MASK  ; Setup Plane selections
    INC     DX                  ; DX -> SC_Data
 
    MOV     AL, 08              ; 8 Lines to Process
    MOV     [BP].GPC_Lines, AL  ; Save on Stack
 
    MOV     DS, [BP].GPC_T_SETS ; Point to character set
 
@GPC_DECODE_CHAR_BYTE:
 
    MOV     SI, [BP].GPC_T_SETO ; Get DS:SI = String
 
    MOV     BH, [SI]            ; Get Bit Map
    INC     SI                  ; Point to Next Line
    MOV     [BP].GPC_T_SETO, SI ; And save new Pointer...
 
    CLR     AX                  ; Clear AX
 
    CLR     BL                      ; Clear BL
    ROL     BX, CL                  ; BL holds left edge bits
    MOV     SI, BX                  ; Use as Table Index
    AND     SI, CHAR_BITS           ; Get Low Bits
    MOV     AL, Char_Plane_Data[SI] ; Get Mask in AL
    JZ      @GPC_NO_LEFT1BITS       ; Skip if No Pixels to set
 
    MOV     AH, [BP].GPC_ColorF ; Get Foreground Color
    OUT     DX, AL              ; Set up Screen Mask
    MOV     ES:[DI], AH         ; Write Foreground color
 
@GPC_NO_LEFT1BITS:
    XOR     AL, CH              ; Invert mask for Background
    JZ      @GPC_NO_LEFT0BITS   ; Hey, no need for this
 
    MOV     AH, [BP].GPC_ColorB ; Get background Color
    OUT     DX, AL              ; Set up Screen Mask
    MOV     ES:[DI], AH         ; Write Foreground color
 
    ;Now Do Middle/Last Band
 
@GPC_NO_LEFT0BITS:
    INC     DI                  ; Point to next Byte
    ROL     BX, 4               ; Shift 4 bits
 
    MOV     SI, BX                  ; Make Lookup Pointer
    AND     SI, CHAR_BITS           ; Get Low Bits
    MOV     AL, Char_Plane_Data[SI] ; Get Mask in AL
    JZ      @GPC_NO_MIDDLE1BITS     ; Skip if no pixels to set
 
    MOV     AH, [BP].GPC_ColorF ; Get Foreground Color
    OUT     DX, AL              ; Set up Screen Mask
    MOV     ES:[DI], AH         ; Write Foreground color
 
@GPC_NO_MIDDLE1BITS:
    XOR     AL, ALL_PLANES      ; Invert mask for Background
    JZ      @GPC_NO_MIDDLE0BITS ; Hey, no need for this
 
    MOV     AH, [BP].GPC_ColorB ; Get background Color
    OUT     DX, AL              ; Set up Screen Mask
    MOV     ES:[DI], AH         ; Write Foreground color
 
@GPC_NO_MIDDLE0BITS:
    XOR     CH, ALL_PLANES      ; Invert Clip Mask
    CMP     CL, 4               ; Aligned by 4?
    JZ      @GPC_NEXT_LINE      ; If so, Exit now..
 
    INC     DI                  ; Point to next Byte
    ROL     BX, 4               ; Shift 4 bits
 
    MOV     SI, BX                  ; Make Lookup Pointer
    AND     SI, CHAR_BITS           ; Get Low Bits
    MOV     AL, Char_Plane_Data[SI] ; Get Mask in AL
    JZ      @GPC_NO_RIGHT1BITS      ; Skip if No Pixels to set
 
    MOV     AH, [BP].GPC_ColorF ; Get Foreground Color
    OUT     DX, AL              ; Set up Screen Mask
    MOV     ES:[DI], AH         ; Write Foreground color
 
@GPC_NO_RIGHT1BITS:
 
    XOR     AL, CH              ; Invert mask for Background
    JZ      @GPC_NO_RIGHT0BITS  ; Hey, no need for this
 
    MOV     AH, [BP].GPC_ColorB ; Get background Color
    OUT     DX, AL              ; Set up Screen Mask
    MOV     ES:[DI], AH         ; Write Foreground color
 
@GPC_NO_RIGHT0BITS:
    DEC     DI                  ; Adjust for Next Line Advance
 
@GPC_NEXT_LINE:
    ADD     DI, [BP].GPC_Width  ; Point to Next Line
    XOR     CH, CHAR_BITS       ; Flip the Clip mask back
 
    DEC     [BP].GPC_Lines      ; Count Down Lines
    JZ      @GPC_EXIT           ; Ok... Done!
 
    JMP     @GPC_DECODE_CHAR_BYTE   ; Again! Hey!
 
@GPC_EXIT:
    ADD     SP, 08              ; Deallocate stack workspace
    POPx    DI, SI, DS, BP      ; Restore Saved Registers
    RET     10                  ; Exit and Clean up Stack
 
GPRINTC  ENDP
 
 
;==========================================
;TGPRINTC (CharNum%, Xpos%, Ypos%, ColorF%)
;==========================================
;
; Transparently draws an ASCII Text Character using the
; currently selected 8x8 font on the active display page.
;
; ENTRY: CharNum = ASCII character # to draw
;        Xpos    = X position to draw Character at
;        Ypos    = Y position of to draw Character at
;        ColorF  = Color to draw text character in
;
; EXIT:  No meaningful values returned
;
 
TGP_STACK   STRUC
    TGP_Width   DW  ?   ; Screen Width-1
    TGP_Lines   DB  ?,? ; Scan lines to Decode
    TGP_T_SETS  DW  ?   ; Saved Charset Segment
    TGP_T_SETO  DW  ?   ; Saved Charset Offset
                DW  ?x4 ; DI, SI, DS, BP
                DD  ?   ; Caller
    TGP_ColorF  DB  ?,? ; Text Color
    TGP_Ypos    DW  ?   ; Y Position to Print at
    TGP_Xpos    DW  ?   ; X position to Print at
    TGP_Char    DB  ?,? ; Character to Print
TGP_STACK   ENDS
 
        PUBLIC TGPRINTC
 
TGPRINTC    PROC    FAR
 
    PUSHx   BP, DS, SI, DI      ; Preserve Important Registers
    SUB     SP, 8               ; Allocate WorkSpace on Stack
    MOV     BP, SP              ; Set up Stack Frame
 
    LES     DI, d CURRENT_PAGE  ; Point to Active VGA Page
 
    MOV     AX, SCREEN_WIDTH    ; Get Logical Line Width
    MOV     BX, AX              ; BX = Screen Width
    DEC     BX                  ;    = Screen Width-1
    MOV     [BP].TGP_Width, BX  ; Save for later use
 
    MUL     [BP].TGP_Ypos       ; Start of Line = Ypos * Width
    ADD     DI, AX              ; DI -> Start of Line Ypos
 
    MOV     AX, [BP].TGP_Xpos   ; Get Xpos of Character
    MOV     CX, AX              ; Save Copy of Xpos
    SHR     AX, 2               ; Bytes into Line = Xpos/4
    ADD     DI, AX              ; DI -> (Xpos, Ypos)
 
    ;Get Source ADDR of Character Bit Map  & Save
 
    MOV     AL, [BP].TGP_Char   ; Get Character #
    TEST    AL, 080h            ; Is Hi Bit Set?
    JZ      @TGP_LowChar        ; Nope, use low char set ptr
 
    AND     AL, 07Fh            ; Mask Out Hi Bit
    MOV     BX, CHARSET_HI      ; BX = Char Set Ptr:Offset
    MOV     DX, CHARSET_HI+2    ; DX = Char Set Ptr:Segment
    JMP     s @TGP_Set_Char     ; Go Setup Character Ptr
 
@TGP_LowChar:
 
    MOV     BX, CHARSET_LOW     ; BX = Char Set Ptr:Offset
    MOV     DX, CHARSET_LOW+2   ; DX = Char Set Ptr:Segment
 
@TGP_Set_Char:
    MOV     [BP].TGP_T_SETS, DX ; Save Segment on Stack
 
    MOV     AH, 0               ; Valid #'s are 0..127
    SHL     AX, 3               ; * 8 Bytes Per Bitmap
    ADD     BX, AX              ; BX = Offset of Selected char
    MOV     [BP].TGP_T_SETO, BX ; Save Offset on Stack
 
    AND     CX, PLANE_BITS      ; Get Plane #
    MOV     CH, ALL_PLANES      ; Get Initial Plane mask
    SHL     CH, CL              ; And shift into position
    AND     CH, ALL_PLANES      ; And mask to lower nibble
 
    MOV     AL, 04              ; 4-Plane # = # of initial
    SUB     AL, CL              ; shifts to align bit mask
    MOV     CL, AL              ; Shift Count for SHL
 
    ;Get segment of character map
 
    OUT_8   SC_Index, MAP_MASK  ; Setup Plane selections
    INC     DX                  ; DX -> SC_Data
 
    MOV     AL, 08              ; 8 Lines to Process
    MOV     [BP].TGP_Lines, AL  ; Save on Stack
 
    MOV     DS, [BP].TGP_T_SETS ; Point to character set
 
@TGP_DECODE_CHAR_BYTE:
 
    MOV     SI, [BP].TGP_T_SETO ; Get DS:SI = String
 
    MOV     BH, [SI]            ; Get Bit Map
    INC     SI                  ; Point to Next Line
    MOV     [BP].TGP_T_SETO, SI ; And save new Pointer...
 
    MOV     AH, [BP].TGP_ColorF ; Get Foreground Color
 
    CLR     BL                      ; Clear BL
    ROL     BX, CL                  ; BL holds left edge bits
    MOV     SI, BX                  ; Use as Table Index
    AND     SI, CHAR_BITS           ; Get Low Bits
    MOV     AL, Char_Plane_Data[SI] ; Get Mask in AL
    JZ      @TGP_NO_LEFT1BITS       ; Skip if No Pixels to set
 
    OUT     DX, AL              ; Set up Screen Mask
    MOV     ES:[DI], AH         ; Write Foreground color
 
    ;Now Do Middle/Last Band
 
@TGP_NO_LEFT1BITS:
 
    INC     DI                  ; Point to next Byte
    ROL     BX, 4               ; Shift 4 bits
 
    MOV     SI, BX                  ; Make Lookup Pointer
    AND     SI, CHAR_BITS           ; Get Low Bits
    MOV     AL, Char_Plane_Data[SI] ; Get Mask in AL
    JZ      @TGP_NO_MIDDLE1BITS     ; Skip if no pixels to set
 
    OUT     DX, AL              ; Set up Screen Mask
    MOV     ES:[DI], AH         ; Write Foreground color
 
@TGP_NO_MIDDLE1BITS:
    XOR     CH, ALL_PLANES      ; Invert Clip Mask
    CMP     CL, 4               ; Aligned by 4?
    JZ      @TGP_NEXT_LINE      ; If so, Exit now..
 
    INC     DI                  ; Point to next Byte
    ROL     BX, 4               ; Shift 4 bits
 
    MOV     SI, BX                  ; Make Lookup Pointer
    AND     SI, CHAR_BITS           ; Get Low Bits
    MOV     AL, Char_Plane_Data[SI] ; Get Mask in AL
    JZ      @TGP_NO_RIGHT1BITS      ; Skip if No Pixels to set
 
    OUT     DX, AL              ; Set up Screen Mask
    MOV     ES:[DI], AH         ; Write Foreground color
 
@TGP_NO_RIGHT1BITS:
 
    DEC     DI                  ; Adjust for Next Line Advance
 
@TGP_NEXT_LINE:
    ADD     DI, [BP].TGP_Width  ; Point to Next Line
    XOR     CH, CHAR_BITS       ; Flip the Clip mask back
 
    DEC     [BP].TGP_Lines      ; Count Down Lines
    JZ      @TGP_EXIT           ; Ok... Done!
 
    JMP     @TGP_DECODE_CHAR_BYTE   ; Again! Hey!
 
@TGP_EXIT:
    ADD     SP, 08              ; Deallocate stack workspace
    POPx    DI, SI, DS, BP      ; Restore Saved Registers
    RET     8                   ; Exit and Clean up Stack
 
TGPRINTC    ENDP
 
 
;===============================================================
;PRINT_STR (SEG String, MaxLen%, Xpos%, Ypos%, ColorF%, ColorB%)
;===============================================================
;
; Routine to quickly Print a null terminated ASCII string on the
; active display page up to a maximum length.
;
; ENTRY: String  = Far Pointer to ASCII string to print
;        MaxLen  = # of characters to print if no null found
;        Xpos    = X position to draw Text at
;        Ypos    = Y position of to draw Text at
;        ColorF  = Color to draw text in
;        ColorB  = Color to set background to
;
; EXIT:  No meaningful values returned
;
 
PS_STACK    STRUC
                DW  ?x4 ; DI, SI, DS, BP
                DD  ?   ; Caller
    PS_ColorB   DW  ?   ; Background Color
    PS_ColorF   DW  ?   ; Text Color
    PS_Ypos     DW  ?   ; Y Position to Print at
    PS_Xpos     DW  ?   ; X position to Print at
    PS_Len      DW  ?   ; Maximum Length of string to print
    PS_Text     DW  ?,? ; Far Ptr to Text String
PS_STACK    ENDS
 
        PUBLIC  PRINT_STR
 
PRINT_STR   PROC    FAR
 
    PUSHx   BP, DS, SI, DI      ; Preserve Important Registers
    MOV     BP, SP              ; Set up Stack Frame
 
@PS_Print_It:
 
    MOV     CX, [BP].PS_Len     ; Get Remaining text Length
    JCXZ    @PS_Exit            ; Exit when out of text
 
    LES     DI, d [BP].PS_Text  ; ES:DI -> Current Char in Text
    MOV     AL, ES:[DI]         ; AL = Text Character
    AND     AX, 00FFh           ; Clear High Word
    JZ      @PS_Exit            ; Exit if null character
 
    DEC     [BP].PS_Len         ; Remaining Text length--
    INC     [BP].PS_Text        ; Point to Next text char
 
    ; Set up Call to GPRINTC
 
    PUSH    AX                  ; Set Character Parameter
    MOV     BX, [BP].PS_Xpos    ; Get Xpos
    PUSH    BX                  ; Set Xpos Parameter
    ADD     BX, 8               ; Advance 1 Char to Right
    MOV     [BP].PS_Xpos, BX    ; Save for next time through
 
    MOV     BX, [BP].PS_Ypos    ; Get Ypos
    PUSH    BX                  ; Set Ypos Parameter
 
    MOV     BX, [BP].PS_ColorF  ; Get Text Color
    PUSH    BX                  ; Set ColorF Parameter
 
    MOV     BX, [BP].PS_ColorB  ; Get Background Color
    PUSH    BX                  ; Set ColorB Parameter
 
    CALL    f GPRINTC           ; Print Character!
    JMP     s @PS_Print_It      ; Process next character
 
@PS_Exit:
    POPx    DI, SI, DS, BP      ; Restore Saved Registers
    RET     14                  ; Exit and Clean up Stack
 
PRINT_STR  ENDP
 
 
;================================================================
;TPRINT_STR (SEG String, MaxLen%, Xpos%, Ypos%, ColorF%, ColorB%)
;================================================================
;
; Routine to quickly transparently Print a null terminated ASCII
; string on the active display page up to a maximum length.
;
; ENTRY: String  = Far Pointer to ASCII string to print
;        MaxLen  = # of characters to print if no null found
;        Xpos    = X position to draw Text at
;        Ypos    = Y position of to draw Text at
;        ColorF  = Color to draw text in
;
; EXIT:  No meaningful values returned
;
 
TPS_STACK   STRUC
                DW  ?x4 ; DI, SI, DS, BP
                DD  ?   ; Caller
    TPS_ColorF  DW  ?   ; Text Color
    TPS_Ypos    DW  ?   ; Y Position to Print at
    TPS_Xpos    DW  ?   ; X position to Print at
    TPS_Len     DW  ?   ; Maximum Length of string to print
    TPS_Text    DW  ?,? ; Far Ptr to Text String
TPS_STACK   ENDS
 
        PUBLIC  TPRINT_STR
 
TPRINT_STR  PROC    FAR
 
    PUSHx   BP, DS, SI, DI      ; Preserve Important Registers
    MOV     BP, SP              ; Set up Stack Frame
 
@TPS_Print_It:
 
    MOV     CX, [BP].TPS_Len    ; Get Remaining text Length
    JCXZ    @TPS_Exit           ; Exit when out of text
 
    LES     DI, d [BP].TPS_Text ; ES:DI -> Current Char in Text
    MOV     AL, ES:[DI]         ; AL = Text Character
    AND     AX, 00FFh           ; Clear High Word
    JZ      @TPS_Exit           ; Exit if null character
 
    DEC     [BP].TPS_Len        ; Remaining Text length--
    INC     [BP].TPS_Text       ; Point to Next text char
 
    ; Set up Call to TGPRINTC
 
    PUSH    AX                  ; Set Character Parameter
    MOV     BX, [BP].TPS_Xpos   ; Get Xpos
    PUSH    BX                  ; Set Xpos Parameter
    ADD     BX, 8               ; Advance 1 Char to Right
    MOV     [BP].TPS_Xpos, BX   ; Save for next time through
 
    MOV     BX, [BP].TPS_Ypos   ; Get Ypos
    PUSH    BX                  ; Set Ypos Parameter
 
    MOV     BX, [BP].TPS_ColorF ; Get Text Color
    PUSH    BX                  ; Set ColorF Parameter
 
    CALL    f TGPRINTC          ; Print Character!
    JMP     s @TPS_Print_It     ; Process next character
 
@TPS_Exit:
    POPx    DI, SI, DS, BP      ; Restore Saved Registers
    RET     12                  ; Exit and Clean up Stack
 
TPRINT_STR  ENDP
 
 
;===========================================
;SET_DISPLAY_FONT(SEG FontData, FontNumber%)
;===========================================
;
; Allows the user to specify their own font data for
; wither the lower or upper 128 characters.
;
; ENTRY: FontData   = Far Pointer to Font Bitmaps
;        FontNumber = Which half of set this is
;                   = 0, Lower 128 characters
;                   = 1, Upper 128 characters
;
; EXIT:  No meaningful values returned
;
 
SDF_STACK   STRUC
                DW  ?   ; BP
                DD  ?   ; Caller
    SDF_Which   DW  ?   ; Hi Table/Low Table Flag
    SDF_Font    DD  ?   ; Far Ptr to Font Table
SDF_STACK   ENDS
 
    PUBLIC  SET_DISPLAY_FONT
 
SET_DISPLAY_FONT    PROC    FAR
 
    PUSH    BP                  ; Preserve Registers
    MOV     BP, SP              ; Set up Stack Frame
 
    LES     DI, [BP].SDF_Font   ; Get Far Ptr to Font
 
    MOV     SI, o CHARSET_LOW   ; Assume Lower 128 chars
    TEST    [BP].SDF_Which, 1   ; Font #1 selected?
    JZ      @SDF_Set_Font       ; If not, skip ahead
 
    MOV     SI, o CHARSET_HI    ; Ah, really it's 128-255
 
@SDF_Set_Font:
    MOV     [SI], DI            ; Set Font Pointer Offset
    MOV     [SI+2], ES          ; Set Font Pointer Segment
 
    POP     BP                  ; Restore Registers
    RET     6                   ; We are Done.. Outa here
 
SET_DISPLAY_FONT    ENDP
 
 
    ; ===== BITMAP (SPRITE) DISPLAY ROUTINES =====
 
;======================================================
;DRAW_BITMAP (SEG Image, Xpos%, Ypos%, Width%, Height%)
;======================================================
;
; Draws a variable sized Graphics Bitmap such as a
; picture or an Icon on the current Display Page in
; Mode X.  The Bitmap is stored in a linear byte array
; corresponding to (0,0) (1,0), (2,0) .. (Width, Height)
; This is the same linear manner as mode 13h graphics.
;
; ENTRY: Image  = Far Pointer to Bitmap Data
;        Xpos   = X position to Place Upper Left pixel at
;        Ypos   = Y position to Place Upper Left pixel at
;        Width  = Width of the Bitmap in Pixels
;        Height = Height of the Bitmap in Pixels
;
; EXIT:  No meaningful values returned
;
 
DB_STACK    STRUC
    DB_LineO    DW  ?   ; Offset to Next Line
    DB_PixCount DW  ?   ; (Minimum) # of Pixels/Line
    DB_Start    DW  ?   ; Addr of Upper Left Pixel
    DB_PixSkew  DW  ?   ; # of bytes to Adjust EOL
    DB_SkewFlag DW  ?   ; Extra Pix on Plane Flag
                DW  ?x4 ; DI, SI, DS, BP
                DD  ?   ; Caller
    DB_Height   DW  ?   ; Height of Bitmap in Pixels
    DB_Width    DW  ?   ; Width of Bitmap in Pixels
    DB_Ypos     DW  ?   ; Y position to Draw Bitmap at
    DB_Xpos     DW  ?   ; X position to Draw Bitmap at
    DB_Image    DD  ?   ; Far Pointer to Graphics Bitmap
DB_STACK    ENDS
 
        PUBLIC    DRAW_BITMAP
 
DRAW_BITMAP PROC    FAR
 
    PUSHx   BP, DS, SI, DI      ; Preserve Important Registers
    SUB     SP, 10              ; Allocate workspace
    MOV     BP, SP              ; Set up Stack Frame
 
    LES     DI, d CURRENT_PAGE  ; Point to Active VGA Page
    CLD                         ; Direction Flag = Forward
 
    MOV     AX, [BP].DB_Ypos    ; Get UL Corner Ypos
    MUL     SCREEN_WIDTH        ; AX = Offset to Line Ypos
 
    MOV     BX, [BP].DB_Xpos    ; Get UL Corner Xpos
    MOV     CL, BL              ; Save Plane # in CL
    SHR     BX, 2               ; Xpos/4 = Offset Into Line
 
    ADD     DI, AX              ; ES:DI -> Start of Line
    ADD     DI, BX              ; ES:DI -> Upper Left Pixel
    MOV     [BP].DB_Start, DI   ; Save Starting Addr
 
    ; Compute line to line offset
 
    MOV     BX, [BP].DB_Width   ; Get Width of Image
    MOV     DX, BX              ; Save Copy in DX
    SHR     BX, 2               ; /4 = width in bands
    MOV     AX, SCREEN_WIDTH    ; Get Screen Width
    SUB     AX, BX              ; - (Bitmap Width/4)
 
    MOV     [BP].DB_LineO, AX       ; Save Line Width offset
    MOV     [BP].DB_PixCount, BX    ; Minimum # pix to copy
 
    AND     DX, PLANE_BITS          ; Get "partial band" size (0-3)
    MOV     [BP].DB_PixSkew, DX     ; Also End of Line Skew
    MOV     [BP].DB_SkewFlag, DX    ; Save as Flag/Count
 
    AND     CX, PLANE_BITS      ; CL = Starting Plane #
    MOV     AX, MAP_MASK_PLANE2 ; Plane Mask & Plane Select
    SHL     AH, CL              ; Select correct Plane
    OUT_16  SC_Index, AX        ; Select Plane...
    MOV     BH, AH              ; BH = Saved Plane Mask
    MOV     BL, 4               ; BL = Planes to Copy
 
@DB_COPY_PLANE:
 
    LDS     SI, [BP].DB_Image   ; DS:SI-> Source Image
    MOV     DX, [BP].DB_Height  ; # of Lines to Copy
    MOV     DI, [BP].DB_Start   ; ES:DI-> Dest pos
 
@DB_COPY_LINE:
    MOV     CX, [BP].DB_PixCount    ; Min # to copy
 
    TEST    CL, 0FCh            ; 16+PixWide?
    JZ      @DB_COPY_REMAINDER  ; Nope...
 
    ; Pixel Copy loop has been unrolled to x4
 
@DB_COPY_LOOP:
    MOVSB                       ; Copy Bitmap Pixel
    ADD     SI, 3               ; Skip to Next Byte in same plane
    MOVSB                       ; Copy Bitmap Pixel
    ADD     SI, 3               ; Skip to Next Byte in same plane
    MOVSB                       ; Copy Bitmap Pixel
    ADD     SI, 3               ; Skip to Next Byte in same plane
    MOVSB                       ; Copy Bitmap Pixel
    ADD     SI, 3               ; Skip to Next Byte in same plane
 
    SUB     CL, 4               ; Pixels to Copy=-4
    TEST    CL, 0FCh            ; 4+ Pixels Left?
    JNZ     @DB_COPY_LOOP       ; if so, do another block
 
@DB_COPY_REMAINDER:
    JCXZ    @DB_NEXT_LINE       ; Any Pixels left on line
 
@DB_COPY2:
    MOVSB                       ; Copy Bitmap Pixel
    ADD     SI,3                ; Skip to Next Byte in same plane
    LOOPx   CX, @DB_COPY2       ; Pixels to Copy--, Loop until done
 
@DB_NEXT_LINE:
 
    ; any Partial Pixels? (some planes only)
 
    OR      CX, [BP].DB_SkewFlag    ; Get Skew Count
    JZ      @DB_NEXT2               ; if no partial pixels
 
    MOVSB                       ; Copy Bitmap Pixel
    DEC     DI                  ; Back up to align
    DEC     SI                  ; Back up to align
 
@DB_NEXT2:
    ADD     SI, [BP].DB_PixSkew ; Adjust Skew
    ADD     DI, [BP].DB_LineO   ; Set to Next Display Line
    LOOPx   DX, @DB_COPY_LINE   ; Lines to Copy--, Loop if more
 
    ; Copy Next Plane....
 
    DEC     BL                  ; Planes to Go--
    JZ      @DB_Exit            ; Hey! We are done
 
    ROL     BH, 1               ; Next Plane in line...
    OUT_8   SC_Data, BH         ; Select Plane
 
    CMP     AL, 12h             ; Carry Set if AL=11h
    ADC     [BP].DB_Start, 0    ; Screen Addr =+Carry
    INC     w [BP].DB_Image     ; Start @ Next Byte
 
    SUB     [BP].DB_SkewFlag, 1 ; Reduce Planes to Skew
    ADC     [BP].DB_SkewFlag, 0 ; Back to 0 if it was -1
 
    JMP     s @DB_COPY_PLANE    ; Go Copy the Next Plane
 
@DB_Exit:
    ADD     SP, 10              ; Deallocate workspace
    POPx    DI, SI, DS, BP      ; Restore Saved Registers
    RET     12                  ; Exit and Clean up Stack
 
DRAW_BITMAP   ENDP
 
 
;=======================================================
;TDRAW_BITMAP (SEG Image, Xpos%, Ypos%, Width%, Height%)
;=======================================================
;
; Transparently Draws a variable sized Graphics Bitmap
; such as a picture or an Icon on the current Display Page
; in Mode X.  Pixels with a value of 0 are not drawn,
; leaving the previous "background" contents intact.
;
; The Bitmap format is the same as for the DRAW_BITMAP function.
;
; ENTRY: Image  = Far Pointer to Bitmap Data
;        Xpos   = X position to Place Upper Left pixel at
;        Ypos   = Y position to Place Upper Left pixel at
;        Width  = Width of the Bitmap in Pixels
;        Height = Height of the Bitmap in Pixels
;
; EXIT:  No meaningful values returned
;
 
TB_STACK    STRUC
    TB_LineO    DW  ?   ; Offset to Next Line
    TB_PixCount DW  ?   ; (Minimum) # of Pixels/Line
    TB_Start    DW  ?   ; Addr of Upper Left Pixel
    TB_PixSkew  DW  ?   ; # of bytes to Adjust EOL
    TB_SkewFlag DW  ?   ; Extra Pix on Plane Flag
                DW  ?x4 ; DI, SI, DS, BP
                DD  ?   ; Caller
    TB_Height   DW  ?   ; Height of Bitmap in Pixels
    TB_Width    DW  ?   ; Width of Bitmap in Pixels
    TB_Ypos     DW  ?   ; Y position to Draw Bitmap at
    TB_Xpos     DW  ?   ; X position to Draw Bitmap at
    TB_Image    DD  ?   ; Far Pointer to Graphics Bitmap
TB_STACK    ENDS
 
        PUBLIC    TDRAW_BITMAP
 
TDRAW_BITMAP    PROC    FAR
 
    PUSHx   BP, DS, SI, DI      ; Preserve Important Registers
    SUB     SP, 10              ; Allocate workspace
    MOV     BP, SP              ; Set up Stack Frame
 
    LES     DI, d CURRENT_PAGE  ; Point to Active VGA Page
    CLD                         ; Direction Flag = Forward
 
    MOV     AX, [BP].TB_Ypos    ; Get UL Corner Ypos
    MUL     SCREEN_WIDTH        ; AX = Offset to Line Ypos
 
    MOV     BX, [BP].TB_Xpos    ; Get UL Corner Xpos
    MOV     CL, BL              ; Save Plane # in CL
    SHR     BX, 2               ; Xpos/4 = Offset Into Line
 
    ADD     DI, AX              ; ES:DI -> Start of Line
    ADD     DI, BX              ; ES:DI -> Upper Left Pixel
    MOV     [BP].TB_Start, DI   ; Save Starting Addr
 
    ; Compute line to line offset
 
    MOV     BX, [BP].TB_Width   ; Get Width of Image
    MOV     DX, BX              ; Save Copy in DX
    SHR     BX, 2               ; /4 = width in bands
    MOV     AX, SCREEN_WIDTH    ; Get Screen Width
    SUB     AX, BX              ; - (Bitmap Width/4)
 
    MOV     [BP].TB_LineO, AX       ; Save Line Width offset
    MOV     [BP].TB_PixCount, BX    ; Minimum # pix to copy
 
    AND     DX, PLANE_BITS          ; Get "partial band" size (0-3)
    MOV     [BP].TB_PixSkew, DX     ; Also End of Line Skew
    MOV     [BP].TB_SkewFlag, DX    ; Save as Flag/Count
 
    AND     CX, PLANE_BITS      ; CL = Starting Plane #
    MOV     AX, MAP_MASK_PLANE2 ; Plane Mask & Plane Select
    SHL     AH, CL              ; Select correct Plane
    OUT_16  SC_Index, AX        ; Select Plane...
    MOV     BH, AH              ; BH = Saved Plane Mask
    MOV     BL, 4               ; BL = Planes to Copy
 
@TB_COPY_PLANE:
 
    LDS     SI, [BP].TB_Image   ; DS:SI-> Source Image
    MOV     DX, [BP].TB_Height  ; # of Lines to Copy
    MOV     DI, [BP].TB_Start   ; ES:DI-> Dest pos
 
    ; Here AH is set with the value to be considered
    ; "Transparent".  It can be changed!
 
    MOV     AH, 0               ; Value to Detect 0
 
@TB_COPY_LINE:
    MOV     CX, [BP].TB_PixCount    ; Min # to copy
 
    TEST    CL, 0FCh            ; 16+PixWide?
    JZ      @TB_COPY_REMAINDER  ; Nope...
 
    ; Pixel Copy loop has been unrolled to x4
 
@TB_COPY_LOOP:
    LODSB                       ; Get Pixel Value in AL
    ADD     SI, 3               ; Skip to Next Byte in same plane
    CMP     AL, AH              ; It is "Transparent"?
    JE      @TB_SKIP_01         ; Skip ahead if so
    MOV     ES:[DI], AL         ; Copy Pixel to VGA screen
 
@TB_SKIP_01:
    LODSB                       ; Get Pixel Value in AL
    ADD     SI, 3               ; Skip to Next Byte in same plane
    CMP     AL, AH              ; It is "Transparent"?
    JE      @TB_SKIP_02         ; Skip ahead if so
    MOV     ES:[DI+1], AL       ; Copy Pixel to VGA screen
 
@TB_SKIP_02:
    LODSB                       ; Get Pixel Value in AL
    ADD     SI, 3               ; Skip to Next Byte in same plane
    CMP     AL, AH              ; It is "Transparent"?
    JE      @TB_SKIP_03         ; Skip ahead if so
    MOV     ES:[DI+2], AL       ; Copy Pixel to VGA screen
 
@TB_SKIP_03:
    LODSB                       ; Get Pixel Value in AL
    ADD     SI, 3               ; Skip to Next Byte in same plane
    CMP     AL, AH              ; It is "Transparent"?
    JE      @TB_SKIP_04         ; Skip ahead if so
    MOV     ES:[DI+3], AL       ; Copy Pixel to VGA screen
 
@TB_SKIP_04:
    ADD     DI, 4               ; Adjust Pixel Write Location
    SUB     CL, 4               ; Pixels to Copy=-4
    TEST    CL, 0FCh            ; 4+ Pixels Left?
    JNZ     @TB_COPY_LOOP       ; if so, do another block
 
@TB_COPY_REMAINDER:
    JCXZ    @TB_NEXT_LINE       ; Any Pixels left on line
 
@TB_COPY2:
    LODSB                       ; Get Pixel Value in AL
    ADD     SI, 3               ; Skip to Next Byte in same plane
    CMP     AL, AH              ; It is "Transparent"?
    JE      @TB_SKIP_05         ; Skip ahead if so
    MOV     ES:[DI], AL         ; Copy Pixel to VGA screen
 
@TB_SKIP_05:
    INC     DI                  ; Advance Dest Addr
    LOOPx   CX, @TB_COPY2       ; Pixels to Copy--, Loop until done
 
@TB_NEXT_LINE:
 
    ; any Partial Pixels? (some planes only)
 
    OR      CX, [BP].TB_SkewFlag    ; Get Skew Count
    JZ      @TB_NEXT2               ; if no partial pixels
 
    LODSB                       ; Get Pixel Value in AL
    DEC     SI                  ; Backup to Align
    CMP     AL, AH              ; It is "Transparent"?
    JE      @TB_NEXT2           ; Skip ahead if so
    MOV     ES:[DI], AL         ; Copy Pixel to VGA screen
 
@TB_NEXT2:
    ADD     SI, [BP].TB_PixSkew ; Adjust Skew
    ADD     DI, [BP].TB_LineO   ; Set to Next Display Line
    LOOPx   DX, @TB_COPY_LINE   ; Lines to Copy--, Loop if More
 
    ;Copy Next Plane....
 
    DEC     BL                  ; Planes to Go--
    JZ      @TB_Exit            ; Hey! We are done
 
    ROL     BH, 1               ; Next Plane in line...
    OUT_8   SC_Data, BH         ; Select Plane
 
    CMP     AL, 12h             ; Carry Set if AL=11h
    ADC     [BP].TB_Start, 0    ; Screen Addr =+Carry
    INC     w [BP].TB_Image     ; Start @ Next Byte
 
    SUB     [BP].TB_SkewFlag, 1 ; Reduce Planes to Skew
    ADC     [BP].TB_SkewFlag, 0 ; Back to 0 if it was -1
 
    JMP     @TB_COPY_PLANE      ; Go Copy the next Plane
 
@TB_Exit:
    ADD     SP, 10              ; Deallocate workspace
    POPx    DI, SI, DS, BP      ; Restore Saved Registers
    RET     12                  ; Exit and Clean up Stack
 
TDRAW_BITMAP    ENDP
 
 
    ; ==== VIDEO MEMORY to VIDEO MEMORY COPY ROUTINES =====
 
;==================================
;COPY_PAGE (SourcePage%, DestPage%)
;==================================
;
; Duplicate on display page onto another
;
; ENTRY: SourcePage = Display Page # to Duplicate
;        DestPage   = Display Page # to hold copy
;
; EXIT:  No meaningful values returned
;
 
CP_STACK    STRUC
                DW  ?x4 ; DI, SI, DS, BP
                DD  ?   ; Caller
    CP_DestP    DW  ?   ; Page to hold copied image
    CP_SourceP  DW  ?   ; Page to Make copy from
CP_STACK    ENDS
 
        PUBLIC    COPY_PAGE
 
COPY_PAGE   PROC    FAR
 
    PUSHx   BP, DS, SI, DI      ; Preserve Important Registers
    MOV     BP, SP              ; Set up Stack Frame
    CLD                         ; Block Xfer Forwards
 
    ; Make sure Page #'s are valid
 
    MOV     AX, [BP].CP_SourceP ; Get Source Page #
    CMP     AX, LAST_PAGE       ; is it > Max Page #?
    JAE     @CP_Exit            ; if so, abort
 
    MOV     BX, [BP].CP_DestP   ; Get Destination Page #
    CMP     BX, LAST_PAGE       ; is it > Max Page #?
    JAE     @CP_Exit            ; if so, abort
 
    CMP     AX, BX              ; Pages #'s the same?
    JE      @CP_Exit            ; if so, abort
 
    ; Setup DS:SI and ES:DI to Video Pages
 
    SHL     BX, 1               ; Scale index to Word
    MOV     DI, PAGE_ADDR[BX]   ; Offset to Dest Page
 
    MOV     BX, AX              ; Index to Source page
    SHL     BX, 1               ; Scale index to Word
    MOV     SI, PAGE_ADDR[BX]   ; Offset to Source Page
 
    MOV     CX, PAGE_SIZE       ; Get size of Page
    MOV     AX, CURRENT_SEGMENT ; Get Video Mem Segment
    MOV     ES, AX              ; ES:DI -> Dest Page
    MOV     DS, AX              ; DS:SI -> Source Page
 
    ; Setup VGA registers for Mem to Mem copy
 
    OUT_16  GC_Index, LATCHES_ON    ; Data from Latches = on
    OUT_16  SC_Index, ALL_PLANES_ON ; Copy all Planes
 
    ; Note.. Do *NOT* use MOVSW or MOVSD - they will
    ; Screw with the latches which are 8 bits x 4
 
    REP     MOVSB               ; Copy entire Page!
 
    ; Reset VGA for normal memory access
 
    OUT_16  GC_Index, LATCHES_OFF   ; Data from Latches = off
 
@CP_Exit:
    POPx    DI, SI, DS, BP      ; Restore Saved Registers
    RET     4                   ; Exit and Clean up Stack
 
COPY_PAGE   ENDP
 
 
;==========================================================================
;COPY_BITMAP (SourcePage%, X1%, Y1%, X2%, Y2%, DestPage%, DestX1%, DestY1%)
;==========================================================================
;
; Copies a Bitmap Image from one Display Page to Another
; This Routine is Limited to copying Images with the same
; Plane Alignment.  To Work: (X1 MOD 4) must = (DestX1 MOD 4)
; Copying an Image to the Same Page is supported, but results
; may be defined when the when the rectangular areas
; (X1, Y1) - (X2, Y2) and (DestX1, DestY1) -
; (DestX1+(X2-X1), DestY1+(Y2-Y1)) overlap...
; No Paramter checking to done to insure that
; X2 >= X1 and Y2 >= Y1.  Be Careful...
;
; ENTRY: SourcePage = Display Page # with Source Image
;        X1         = Upper Left Xpos of Source Image
;        Y1         = Upper Left Ypos of Source Image
;        X2         = Lower Right Xpos of Source Image
;        Y2         = Lower Right Ypos of Source Image
;        DestPage   = Display Page # to copy Image to
;        DestX1     = Xpos to Copy UL Corner of Image to
;        DestY1     = Ypos to Copy UL Corner of Image to
;
; EXIT:  AX = Success Flag:   0 = Failure / -1= Success
;
 
CB_STACK    STRUC
    CB_Height   DW  ?   ; Height of Image in Lines
    CB_Width    DW  ?   ; Width of Image in "bands"
                DW  ?x4 ; DI, SI, DS, BP
                DD  ?   ; Caller
    CB_DestY1   DW  ?   ; Destination Ypos
    CB_DestX1   DW  ?   ; Destination Xpos
    CB_DestP    DW  ?   ; Page to Copy Bitmap To
    CB_Y2       DW  ?   ; LR Ypos of Image
    CB_X2       DW  ?   ; LR Xpos of Image
    CB_Y1       DW  ?   ; UL Ypos of Image
    CB_X1       DW  ?   ; UL Xpos of Image
    CB_SourceP  DW  ?   ; Page containing Source Bitmap
CB_STACK    ENDS
 
        PUBLIC    COPY_BITMAP
 
COPY_BITMAP PROC    FAR
 
    PUSHx   BP, DS, SI, DI      ; Preserve Important Registers
    SUB     SP, 4               ; Allocate WorkSpace on Stack
    MOV     BP, SP              ; Set up Stack Frame
 
    ; Prep Registers (and keep jumps short!)
 
    MOV     ES, CURRENT_SEGMENT ; ES -> VGA Ram
    CLD                         ; Block Xfer Forwards
 
    ; Make sure Parameters are valid
 
    MOV     BX, [BP].CB_SourceP ; Get Source Page #
    CMP     BX, LAST_PAGE       ; is it > Max Page #?
    JAE     @CB_Abort           ; if so, abort
 
    MOV     CX, [BP].CB_DestP   ; Get Destination Page #
    CMP     CX, LAST_PAGE       ; is it > Max Page #?
    JAE     @CB_Abort           ; if so, abort
 
    MOV     AX, [BP].CB_X1      ; Get Source X1
    XOR     AX, [BP].CB_DestX1  ; Compare Bits 0-1
    AND     AX, PLANE_BITS      ; Check Plane Bits
    JNZ     @CB_Abort           ; They should cancel out
 
    ; Setup for Copy processing
 
    OUT_8   SC_INDEX, MAP_MASK      ; Set up for Plane Select
    OUT_16  GC_Index, LATCHES_ON    ; Data from Latches = on
 
    ; Compute Info About Images, Setup ES:SI & ES:DI
 
    MOV     AX, [BP].CB_Y2      ; Height of Bitmap in lines
    SUB     AX, [BP].CB_Y1      ; is Y2 - Y1 + 1
    INC     AX                  ; (add 1 since were not 0 based)
    MOV     [BP].CB_Height, AX  ; Save on Stack for later use
 
    MOV     AX, [BP].CB_X2      ; Get # of "Bands" of 4 Pixels
    MOV     DX, [BP].CB_X1      ; the Bitmap Occupies as X2-X1
    SHR     AX, 2               ; Get X2 Band (X2 / 4)
    SHR     DX, 2               ; Get X1 Band (X1 / 4)
    SUB     AX, DX              ; AX = # of Bands - 1
    INC     AX                  ; AX = # of Bands
    MOV     [BP].CB_Width, AX   ; Save on Stack for later use
 
    SHL     BX, 1               ; Scale Source Page to Word
    MOV     SI, PAGE_ADDR[BX]   ; SI = Offset of Source Page
    MOV     AX, [BP].CB_Y1      ; Get Source Y1 Line
    MUL     SCREEN_WIDTH        ; AX = Offset to Line Y1
    ADD     SI, AX              ; SI = Offset to Line Y1
    MOV     AX, [BP].CB_X1      ; Get Source X1
    SHR     AX, 2               ; X1 / 4 = Byte offset
    ADD     SI, AX              ; SI = Byte Offset to (X1,Y1)
 
    MOV     BX, CX              ; Dest Page Index to BX
    SHL     BX, 1               ; Scale Source Page to Word
    MOV     DI, PAGE_ADDR[BX]   ; DI = Offset of Dest Page
    MOV     AX, [BP].CB_DestY1  ; Get Dest Y1 Line
    MUL     SCREEN_WIDTH        ; AX = Offset to Line Y1
    ADD     DI, AX              ; DI = Offset to Line Y1
    MOV     AX, [BP].CB_DestX1  ; Get Dest X1
    SHR     AX, 2               ; X1 / 4 = Byte offset
    ADD     DI, AX              ; DI = Byte Offset to (D-X1,D-Y1)
 
    MOV     CX, [BP].CB_Width   ; CX = Width of Image (Bands)
    DEC     CX                  ; CX = 1?
    JE      @CB_Only_One_Band   ; 0 Means Image Width of 1 Band
 
    MOV     BX, [BP].CB_X1      ; Get Source X1
    AND     BX, PLANE_BITS      ; Aligned? (bits 0-1 = 00?)
    JZ      @CB_Check_Right     ; if so, check right alignment
    JNZ     @CB_Left_Band       ; not aligned? well..
 
@CB_Abort:
    CLR     AX                  ; Return False (Failure)
    JMP     @CB_Exit            ; and Finish Up
 
    ; Copy when Left & Right Clip Masks overlap...
 
@CB_Only_One_Band:
    MOV     BX, [BP].CB_X1          ; Get Left Clip Mask
    AND     BX, PLANE_BITS          ; Mask out Row #
    MOV     AL, Left_Clip_Mask[BX]  ; Get Left Edge Mask
    MOV     BX, [BP].CB_X2          ; Get Right Clip Mask
    AND     BX, PLANE_BITS          ; Mask out Row #
    AND     AL, Right_Clip_Mask[BX] ; Get Right Edge Mask byte
 
    OUT_8   SC_Data, AL         ; Clip For Left & Right Masks
 
    MOV     CX, [BP].CB_Height  ; CX = # of Lines to Copy
    MOV     DX, SCREEN_WIDTH    ; DX = Width of Screen
    CLR     BX                  ; BX = Offset into Image
 
@CB_One_Loop:
    MOV     AL, ES:[SI+BX]      ; Load Latches
    MOV     ES:[DI+BX], AL      ; Unload Latches
    ADD     BX, DX              ; Advance Offset to Next Line
    LOOPjz  CX, @CB_One_Done    ; Exit Loop if Finished
 
    MOV     AL, ES:[SI+BX]      ; Load Latches
    MOV     ES:[DI+BX], AL      ; Unload Latches
    ADD     BX, DX              ; Advance Offset to Next Line
    LOOPx   CX, @CB_One_Loop    ; Loop until Finished
 
@CB_One_Done:
    JMP     @CB_Finish          ; Outa Here!
 
    ; Copy Left Edge of Bitmap
 
@CB_Left_Band:
 
    OUT_8   SC_Data, Left_Clip_Mask[BX] ; Set Left Edge Plane Mask
 
    MOV     CX, [BP].CB_Height  ; CX = # of Lines to Copy
    MOV     DX, SCREEN_WIDTH    ; DX = Width of Screen
    CLR     BX                  ; BX = Offset into Image
 
@CB_Left_Loop:
    MOV     AL, ES:[SI+BX]      ; Load Latches
    MOV     ES:[DI+BX], AL      ; Unload Latches
    ADD     BX, DX              ; Advance Offset to Next Line
    LOOPjz  CX, @CB_Left_Done   ; Exit Loop if Finished
 
    MOV     AL, ES:[SI+BX]      ; Load Latches
    MOV     ES:[DI+BX], AL      ; Unload Latches
    ADD     BX, DX              ; Advance Offset to Next Line
    LOOPx   CX, @CB_Left_Loop   ; Loop until Finished
 
@CB_Left_Done:
    INC     DI                  ; Move Dest Over 1 band
    INC     SI                  ; Move Source Over 1 band
    DEC     [BP].CB_Width       ; Band Width--
 
    ; Determine if Right Edge of Bitmap needs special copy
 
@CB_Check_Right:
    MOV     BX, [BP].CB_X2      ; Get Source X2
    AND     BX, PLANE_BITS      ; Aligned? (bits 0-1 = 11?)
    CMP     BL, 03h             ; Plane = 3?
    JE      @CB_Copy_Middle     ; Copy the Middle then!
 
    ; Copy Right Edge of Bitmap
 
@CB_Right_Band:
 
    OUT_8   SC_Data, Right_Clip_Mask[BX]    ; Set Right Edge Plane Mask
 
    DEC     [BP].CB_Width       ; Band Width--
    MOV     CX, [BP].CB_Height  ; CX = # of Lines to Copy
    MOV     DX, SCREEN_WIDTH    ; DX = Width of Screen
    MOV     BX, [BP].CB_Width   ; BX = Offset to Right Edge
 
@CB_Right_Loop:
    MOV     AL, ES:[SI+BX]      ; Load Latches
    MOV     ES:[DI+BX], AL      ; Unload Latches
    ADD     BX, DX              ; Advance Offset to Next Line
    LOOPjz  CX, @CB_Right_Done  ; Exit Loop if Finished
 
    MOV     AL, ES:[SI+BX]      ; Load Latches
    MOV     ES:[DI+BX], AL      ; Unload Latches
    ADD     BX, DX              ; Advance Offset to Next Line
    LOOPx   CX, @CB_Right_Loop  ; Loop until Finished
 
@CB_Right_Done:
 
    ; Copy the Main Block of the Bitmap
 
@CB_Copy_Middle:
 
    MOV     CX, [BP].CB_Width   ; Get Width Remaining
    JCXZ    @CB_Finish          ; Exit if Done
 
    OUT_8   SC_Data, ALL_PLANES ; Copy all Planes
 
    MOV     DX, SCREEN_WIDTH    ; Get Width of Screen minus
    SUB     DX, CX              ; Image width (for Adjustment)
    MOV     AX, [BP].CB_Height  ; AX = # of Lines to Copy
    MOV     BX, CX              ; BX = Quick REP reload count
    MOV     CX, ES              ; Move VGA Segment
    MOV     DS, CX              ; Into DS
 
    ; Actual Copy Loop.  REP MOVSB does the work
 
@CB_Middle_Copy:
    MOV     CX, BX              ; Recharge Rep Count
    REP     MOVSB               ; Move Bands
    LOOPjz  AX, @CB_Finish      ; Exit Loop if Finished
 
    ADD     SI, DX              ; Adjust DS:SI to Next Line
    ADD     DI, DX              ; Adjust ES:DI to Next Line
 
    MOV     CX, BX              ; Recharge Rep Count
    REP     MOVSB               ; Move Bands
 
    ADD     SI, DX              ; Adjust DS:SI to Next Line
    ADD     DI, DX              ; Adjust ES:DI to Next Line
    LOOPx   AX, @CB_Middle_Copy ; Copy Lines until Done
 
@CB_Finish:
    OUT_16  GC_Index, LATCHES_OFF   ; Data from Latches = on
 
@CB_Exit:
    ADD     SP, 04              ; Deallocate stack workspace
    POPx    DI, SI, DS, BP      ; Restore Saved Registers
    RET     16                  ; Exit and Clean up Stack
 
COPY_BITMAP ENDP
 
    END                         ; End of Code Segment
