; ========================================================
; 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.
;
; Protected Mode modification by John McCarthy
; John McCarthy thanks Matt Pritchard for providing this code,
; and hopes Matt Pritchard is not offended by the changes.
;
; ========================================================
;
; 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.
;
; JM - revision, this code will *NOT* run on 80286 because of
; 80368+ code is used (and protected mode).
;
; 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
;
; Revisions by John McCarthy - sometime throughout June/93:
;
; - Protected mode addressing for all routines.
; - Conditional assembley for routines not wanted by user
; - Bios text addressing removed - see set_vga_modex
; - File assembles for protected mode, variables and routines made public
; - Mode03 routine added to return to DOS mode.  Must be used in conjunction
;      with TRAN's protected mode header.
; - Also added routines to turn off the screen to avoid that screen flicker
;      when changing into xmode.
;
; A note from John McCarthy:
;
;  When converting to protected mode, loop's, rep's, stosb's and such
;  must have the high word of the ecx,edi,esi registers set.  I have tested
;  the routines now that they have been converted and they seem to all work
;  fine, but if you find they don't jive, please send me a message/letter and
;  I will get right on it.
;
;  These routines originally came with all kinds of extra files for font
;  editing, pallet editing, C stuff and demos.  I have only supplied the
;  critical files from Matt to cut down on .zip file size.
;

x_fill_block    equ 1       ; small routines are automatically included
x_draw_line     equ 1       ; which x mode routines should be included
x_set_point     equ 1       ; when assembled; 1 = enabled
x_read_point    equ 1
x_gprintc       equ 0
x_tgprintc      equ 1
x_set_window    equ 0
x_print_str     equ 0
x_tprint_str    equ 0
x_set_font      equ 0
x_draw_bitmap   equ 0
x_tdraw_bitmap  equ 1
x_copy_page     equ 0
x_copy_bitmap   equ 0

           .386p
code32     segment para public use32
           assume cs:code32, ds:code32

           include pmode.inc     ; protected mode externals
           include macros.inc    ; guess...
           include equ.inc       ; list of constants

; xmode routines by matt prichard
; modified for 386 protected mode by john mccarthy
; protected mode header courtesy of TRAN

; ===== data tables =====

; bit mask tables for left/right/character masks

           public left_clip_mask
           public right_clip_mask

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:
        dd  o mode_320x200, o mode_320x400
        dd  o mode_360x200, o mode_360x400
        dd  o mode_320x240, o mode_320x480
        dd  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

        dd  o mode_320_wide, o mode_200_tall
        dd  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

        dd  o mode_320_wide, o mode_400_tall
        dd  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

        dd  o mode_360_wide, o mode_240_tall
        dd  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

        dd  o mode_360_wide, o mode_480_tall
        dd  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

        dd  o mode_320_wide, o mode_240_tall
        dd  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

        dd  o mode_320_wide, o mode_480_tall
        dd  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

        dd  o mode_360_wide, o mode_200_tall
        dd  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

        dd  o mode_360_wide, o mode_400_tall
        dd  o mode_single_line, nil

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          dd  ?       ; table of crtc register values
mode_data_table ends

; specific x mode data table format...

screen_width    dw  0        ; actual width of a line in bytes
screen_height   dw  0        ; actual vertical height in pixels

last_page       dw  0        ; # of display pages
page_addr       dd  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    dd  0        ; address of current page

current_xoffset dw  0        ; current display x offset
current_yoffset dw  0        ; current display y offset

current_moffset dd  0        ; current start offset

max_xoffset     dw  0        ; current display x offset
max_yoffset     dw  0        ; current display y offset

charset_low     dd  0        ; far ptr to char set: 0-127
charset_hi      dd  0        ; far ptr to char set: 128-255

        public  screen_width
        public  screen_height

        public  last_page
        public  page_addr

        public  page_size

        public  display_page
        public  active_page

        public  current_page

        public  current_xoffset
        public  current_yoffset

        public  current_moffset

        public  max_xoffset
        public  max_yoffset

        public  charset_low
        public  charset_hi

    ; ===== 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   dd  ?   ; offset of mode info table
                dd  ?x3 ; edi, esi, ebp
                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:
    push    ebp esi edi         ; preserve important registers
    sub     esp, 4              ; allocate workspace
    mov     ebp, esp            ; set up stack frame

; check legality of mode request....

    mov     bx, [ebp].svm_mode  ; get requested mode #

    cmp     bx, num_modes       ; is it 0..7?
    jae     @svm_badmodesetup   ; if not, error out

    shl     bx, 2                  ; scale bx
    mov     esi, d mode_table[bx]  ; si -> mode info
    mov     [ebp].svm_table, esi   ; save ptr for later use

; check # of requested display pages

    mov     cx, [ebp].svm_pages ; get # of requested pages

    clr     ch                  ; set hi word = 0!
    cmp     cl, [esi].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     [ebp].svm_xsize, 0fff8h  ; x size mod 8 must = 0

    mov     ax, [ebp].svm_xsize ; get logical screen width

    cmp     ax, [esi].m_xsize   ; check against displayed x
    jb      @svm_badmodesetup   ; report error if too small
    cmp     ax, [esi].m_xmax    ; check against max x
    ja      @svm_badmodesetup   ; report error if too big

; check validity of y size

    mov     bx, [ebp].svm_ysize ; get logical screen height
    cmp     bx, [esi].m_ysize   ; check against displayed y
    jb      @svm_badmodesetup   ; report error if too small
    cmp     bx, [esi].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     v86r_ax,13h         ; start with mode 13h
    mov     al,10h              ; bios int 10h
    int     33h                 ; let v86 handler call bios int 10h

    call turn_screen_off        ; prevent flicker

    out_16  sc_index, chain4_off        ; disable chain 4 mode
    out_16  sc_index, async_reset       ; (a)synchronous reset
    out_8   misc_output, [esi].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     esi, m_crtc         ; si -> crtc parameter data

; load tables of crtc parameters from list of tables

@svm_setup_table:

    mov     edi, [esi]          ; get pointer to crtc data tbl
    add     esi, 4              ; point to next ptr entry
    or      edi, edi            ; a nil ptr means that we have
    jz      @svm_set_data       ; finished crtc programming

@svm_setup_crtc:
    mov     ax, [edi]           ; get crtc data from table
    add     edi, 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, edi = 0

@svm_set_data:
    mov     display_page, di    ; display page = 0
    mov     active_page, di     ; active page = 0
    mov     current_xoffset, di ; horz scroll index = 0
    mov     current_yoffset, di ; vert scroll index = 0
    mov     current_moffset,edi ; memory scroll index = 0

    @rlp    esi, vga_segment    ; segment for vga memory
    mov     current_page, esi

; set logical screen width, x scroll and our data

    mov     esi, [ebp].svm_table ; get saved ptr to mode info
    mov     ax, [ebp].svm_xsize ; get display width

    mov     cx, ax              ; cx = logical width
    sub     cx, [esi].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, [ebp].svm_ysize ; get logical screen height

    mov     cx, ax              ; cx = logical height
    sub     bx, [esi].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, [ebp].svm_pages ; get # of pages
    mov     last_page, cx       ; save # of pages

    clr     bx                  ; page # = 0
    @rlp    edx, vga_segment    ; page 0 offset = 0a0000h
    movzx   eax,ax              ; clear top word of eax

@svm_set_pages:

    mov     page_addr[bx], edx  ; set page #(bx) offset
    add     bx, 4               ; page#++
    add     edx, eax            ; 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
    mov     edi, current_page   ; -> start of vga memory

    clr     ax                  ; ax = 0
    cld                         ; block xfer forwards
    mov     ecx, 8000h          ; 32k * 4 * 2 = 256k
    rep     stosw               ; clear dat memory!

; setup font pointers, in protected mode.  you must shl 8 then add but
; i don't know how to correctly convert segmented code to flat memory.
;
;   mov     v86r_bh, rom_8x8_lo    ; ask for 8x8 font, 0-127
;   mov     v86r_ax, get_char_ptr  ; service to get pointer
;   mov     al,10h
;   int     33h                 ; call vga bios through v86 handler
;
;   mov     charset_low, bp     ; save char set offset
;   mov     charset_low+2, es   ; save char set segment
;
;   mov     v86r_bh, rom_8x8_hi    ; ask for 8x8 font, 128-255
;   mov     v86r_ax, get_char_ptr  ; service to get pointer
;   mov     al,10h
;   int     33h                 ; call vga bios
;
;   mov     charset_hi, bp      ; save char set offset
;   mov     charset_hi+2, es    ; save char set segment

    call turn_screen_on         ; prevent flicker

    mov     ax, true            ; return success code

@svm_exit:
    add     esp, 4              ; deallocate workspace
    pop     edi esi ebp         ; restore saved registers
    ret     8                   ; exit & clean up stack

;==================
;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
                dd  ?,? ; ebp, esi
                dd  ?   ; caller
    sm_mode     dw  ?   ; desired screen resolution
sm_stack    ends

    public  set_modex

set_modex:
    push    ebp esi             ; preserve important registers
    mov     ebp, esp            ; set up stack frame

    clr     ax                  ; assume failure
    mov     bx, [ebp].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, 2                 ; scale bx to dword indexer
    mov     esi, d mode_table[bx] ; esi -> mode info

    push    [esi].m_xsize       ; push default x size
    push    [esi].m_ysize       ; push default y size
    mov     al, [esi].m_pages   ; get default # of pages
    clr     ah                  ; hi byte = 0
    push    ax                  ; push # pages

    call    set_vga_modex       ; set up mode x!

@smx_exit:
    pop     esi ebp             ; restore registers
    ret     2                   ; exit & clean up stack

; ===== 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
                dd  ?,? ; edi, ebp
                dd  ?   ; caller
    cvs_color   db  ?,? ; color to set screen to
cvs_stack   ends

    public  clear_vga_screen

clear_vga_screen:

    push    ebp edi             ; preserve important registers
    mov     ebp, esp            ; set up stack frame

    out_16  sc_index, all_planes_on ; select all planes
    mov     edi, current_page   ; point to active vga page

    mov     al, [ebp].cvs_color ; get color
    mov     ah, al              ; copy for dword write
    mov     bp,ax
    shl     eax,16
    mov     ax,bp
    cld                         ; block fill forwards

    movzx   ecx, page_size      ; get size of page
    shr     cx, 2               ; divide by 4 for dwords
    rep     stosd               ; block fill vga memory

    pop     edi ebp             ; restore saved registers
    ret     2                   ; exit & clean up stack

;===================================
;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
                dd  ?,? ; ebp, edi
                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:

    push    ebp edi             ; preserve registers
    mov     ebp, esp            ; set up stack frame

    mov     edi, current_page   ; point to active vga page

    mov     ax, [ebp].setp_ypos ; get line # of pixel
    mul     screen_width        ; get offset to start of line

    clr     ebx                 ; wipe high word
    mov     bx, [ebp].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,[ebp].setp_color ; get pixel color
    mov     [edi+ebx], al       ; draw pixel

    pop     edi ebp             ; restore saved registers
    ret     6                   ; exit and clean up stack

;==========================
;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
            dd  ?,? ; ebp, edi
            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:

    push    ebp edi             ; preserve registers
    mov     ebp, esp            ; set up stack frame

    mov     edi, current_page   ; point to active vga page

    mov     ax, [ebp].rp_ypos   ; get line # of pixel
    mul     screen_width        ; get offset to start of line

    clr     ebx                 ; wipe high word
    mov     bx, [ebp].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     eax                 ; clear return value hi byte
    mov     al, [edi+ebx]       ; get color of pixel

    pop     edi ebp             ; restore saved registers
    ret     4                   ; exit and clean up stack

    if x_fill_block eq 1

;======================================================
;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
                dd  ?x3 ; edi, esi, ebp
                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:

    push    ebp esi edi         ; preserve important registers
    mov     ebp, esp            ; set up stack frame

    mov     edi, 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

    clr     eax
    clr     ecx
    mov     ax, [ebp].fb_ypos1  ; ax = y1   is y1< y2?
    mov     bx, [ebp].fb_ypos2  ; bx = y2
    cmp     ax, bx
    jle     @fb_noswap1

    mov     [ebp].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     [ebp].fb_ypos2, bx  ; save in ypos2

    mul     screen_width        ; mul y1 by bytes per line
    add     edi, eax            ; di = start of line y1

    mov     ax, [ebp].fb_xpos1  ; check x1 <= x2
    mov     bx, [ebp].fb_xpos2  ;
    cmp     ax, bx
    jle     @fb_noswap2         ; skip ahead if ok

    mov     [ebp].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:
    movzx   edx, ax             ; dx = x1 (pixel position)
    shr     edx, 2              ; dx/4 = bytes into line
    add     edi, edx            ; di = addr of upper-left corner

    movzx   ecx, 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     esi, edi            ; si = copy of start addr (ul)

    mov     dx, [ebp].fb_ypos2  ; get # of lines to draw
    mov     al, [ebp].fb_color  ; get fill color
    movzx   ebx, screen_width   ; get vertical increment value

@fb_left_loop:
    mov     [esi], al           ; fill in left edge pixels
    add     esi, ebx            ; point to next line (below)
    loopjz  dx, @fb_left_cont   ; exit loop if all lines drawn

    mov     [esi], al           ; fill in left edge pixels
    add     esi, ebx            ; point to next line (below)
    loopx   dx, @fb_left_loop   ; loop until left strip is drawn

@fb_left_cont:

    inc     edi                 ; 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, [ebp].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     esi, edi            ; get addr of left edge
    add     esi, ecx            ; add width-1 (bands)
    dec     esi                 ; to point to top of right edge

    mov     dx, [ebp].fb_ypos2  ; get # of lines to draw
    mov     al, [ebp].fb_color  ; get fill color
    mov     bx, screen_width    ; get vertical increment value

@fb_right_loop:
    mov     [esi], al           ; fill in right edge pixels
    add     esi, ebx            ; point to next line (below)
    loopjz  dx, @fb_right_cont  ; exit loop if all lines drawn

    mov     [esi], al            ; fill in right edge pixels
    add     esi, ebx            ; 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     ebx, ecx            ; bx = quick refill for cx
    mov     si, [ebp].fb_ypos2  ; si = # of line to fill
    mov     al, [ebp].fb_color  ; get fill color

@fb_middle_loop:
    rep     stosb               ; fill in entire line

    mov     ecx, ebx            ; recharge cx (line width)
    add     edi, edx            ; 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, [ebp].fb_ypos2  ; get # of lines to draw
    mov     al, [ebp].fb_color  ; get fill color
    clr     ebx                 ; wipe high word
    mov     bx, screen_width    ; get vertical increment value

@fb_one_loop:
    mov     [edi], al           ; fill in pixels
    add     edi, ebx            ; point to next line (below)
    loopjz  cx, @fb_exit        ; exit loop if all lines drawn

    mov     [edi], al           ; fill in pixels
    add     edi, ebx            ; point to next line (below)
    loopx   cx, @fb_one_loop    ; loop until left strip is drawn

@fb_exit:
    pop     edi esi ebp         ; restore saved registers
    ret     10                  ; exit and clean up stack

    endif
    if x_draw_line eq 1

;=====================================================
;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
                dd  ?x3 ; edi, esi, ebp
                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:

    push    ebp esi edi         ; preserve important registers
    mov     ebp, esp            ; set up stack frame
    cld                         ; direction flag = forward

    out_8   sc_index, map_mask  ; set up for plane select
    mov     ch, [ebp].dl_colorf ; save line color in ch

; check line type

    mov     si, [ebp].dl_xpos1  ; ax = x1   is x1< x2?
    mov     di, [ebp].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, [ebp].dl_ypos1  ; ax = y1   is y1 <> y2?
    cmp     ax, [ebp].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)

    movzx   eax, ax             ; zero high words for add
    movzx   edx, dx
    movzx   ecx, cx

    mov     edi, current_page   ; point to active vga page
    add     edi, edx            ; point to start of line
    add     edi, eax            ; 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, [ebp].dl_colorf ; get line color
    mov     bl, al              ; bl = copy of line color
    stosb                       ; set left (1-4) pixels

    jcxz    @dl_exit3           ; 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
@dl_exit3:
    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, [ebp].dl_ypos1  ; ax = y1
    mov     si, [ebp].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

    movzx   eax, ax
    mov     edi, current_page   ; point to active vga page
    add     edi, eax            ; 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
    movzx   ebx, bx

@dl_vloop:
    mov     [edi], al           ; draw single pixel
    add     edi, ebx            ; point to next line
    loopjz  si, @dl_exit        ; lines--, exit if done

    mov     [edi], al           ; draw single pixel
    add     edi, ebx            ; 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:
    mov     edi, current_page   ; point to active vga page

    mov     ax, [ebp].dl_ypos1  ; get y1 value
    mov     bx, [ebp].dl_ypos2  ; get y2 value
    mov     cx, [ebp].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, [ebp].dl_xpos2  ; get new starting xpos

@dl_deltayok:
    mul     screen_width        ; offset = screen_width * y1
    movzx   eax, ax

    add     edi, eax            ; di -> start of line y1 on page
    mov     ax, cx              ; ax = xpos (x1)
    shr     ax, 2               ; /4 = byte offset into line
    add     edi, eax            ; 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, [ebp].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, [ebp].dl_xpos1  ; get x1 value
    mov     bx, [ebp].dl_ypos1  ; get y1 value
    mov     cx, [ebp].dl_xpos2  ; get x2 value
    mov     dx, [ebp].dl_ypos2  ; get y2 value

    movzx   ebp, 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     [edi], 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     edi, ebp            ; move down one line...

@dl_slll2nc:
    dec     edi                 ; 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     edi, 0              ; adjust address: di = di + carry
    out     dx, al              ; set up new bit plane mask

    mov     [edi], 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     edi, ebp            ; move down one line...

@dl_slll3nc:                    ; now move left a pixel...
    dec     edi                 ; 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     edi, 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     [edi], 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     edi                 ; 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     edi, 0              ; adjust address: di = di + carry
    out     dx, al              ; set up new bit plane mask

@dl_stlnc2:
    add     edi, ebp            ; advance to next line.

    mov     [edi], 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     edi                 ; 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     edi, 0              ; adjust address: di = di + carry
    out     dx, al              ; set up new bit plane mask

@dl_stlnc3:
    add     edi, ebp            ; 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     [edi], 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     edi, ebp            ; 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     edi, 0              ; adjust address: di = di + carry
    out     dx, al              ; set up new bit plane mask

    mov     [edi], 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     edi, ebp            ; 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     edi, 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     [edi], 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     edi, 0              ; adjust address: di = di + carry
    out     dx, al              ; set up new bit plane mask

@strnc2:
    add     edi, ebp            ; advance to next line.

    mov     [edi], 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     edi, 0              ; adjust address: di = di + carry
    out     dx, al              ; set up new bit plane mask

@strnc3:
    add     edi, ebp            ; advance to next line.
    jmp     s @strloop          ; loop till done

@dl_exit2:
    pop     edi esi ebp         ; restore saved registers
    ret     10                  ; exit and clean up stack

    endif

; ===== 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
                    dd  ?   ; ebp
                    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:

    push    ebp                 ; save bp
    mov     ebp, esp            ; set up stack frame

; select which dac register to modify

    out_8   dac_write_addr, [ebp].sdr_register

    mov     dx, pel_data_reg    ; dac data register
    out_8   dx, [ebp].sdr_red   ; set red intensity
    out_8   dx, [ebp].sdr_green ; set green intensity
    out_8   dx, [ebp].sdr_blue  ; set blue intensity

    pop     ebp                 ; restore registers
    ret     8                   ; exit & clean up stack

;====================================================
;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
                    dd  ?   ; ebp
                    dd  ?   ; caller
    gdr_blue        dd  ?   ; addr of blue data value (where to put)
    gdr_green       dd  ?   ; addr of green data value
    gdr_red         dd  ?   ; addr of red data value
    gdr_register    db  ?,? ; palette register #
gdr_stack   ends

    public  get_dac_register

get_dac_register:

    push    ebp                 ; save bp
    mov     ebp, esp            ; set up stack frame

; select which dac register to read in

    out_8   dac_read_addr, [ebp].gdr_register

    mov     dx, pel_data_reg    ; dac data register
    clr     ax                  ; clear ax

    in      al, dx              ; read red value
    mov     ebx, [ebp].gdr_red  ; get address of red%
    mov     [ebx], ax           ; *red% = ax

    in      al, dx              ; read green value
    mov     ebx, [ebp].gdr_green; get address of green%
    mov     [ebx], ax           ; *green% = ax

    in      al, dx              ; read blue value
    mov     ebx, [ebp].gdr_blue ; get address of blue%
    mov     [ebx], ax           ; *blue% = ax

    pop     ebp                 ; restore registers
    ret     14                  ; exit & clean up stack

;===========================================================
;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 lifar array of 3 byte palette values
;        in the order: red  (0-63), green (0-63), blue (0-63)
;

ldr_stack   struc
                    dd  ?,? ; ebp, esi
                    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:

    push    ebp esi             ; save registers
    mov     ebp, esp            ; set up stack frame

    mov     ax, [ebp].ldr_sync  ; get vertical sync flag
    or      ax, ax              ; is sync flag = 0?
    jz      @ldr_load           ; if so, skip call

    call    sync_display        ; wait for vsync

; determine register #'s, size to copy, etc

@ldr_load:

    mov     esi, [ebp].ldr_paldata  ; esi -> palette data
    mov     dx, dac_write_addr      ; dac register # selector

    clr     ax, bx                  ; clear for byte loads
    mov     al, [ebp].ldr_startreg  ; get start register
    mov     bl, [ebp].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
    movzx   ecx,cx

    rep     outsb               ; block set dac registers

    pop     esi ebp             ; restore registers
    ret     10                  ; exit & clean up stack

;====================================================
;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 lifar array of 3 byte palette values
;        in the order: red  (0-63), green (0-63), blue (0-63)
;

rdr_stack   struc
                    dd  ?,? ; ebp, edi
                    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:

    push    ebp edi             ; save registers
    mov     ebp, esp            ; set up stack frame

; determine register #'s, size to copy, etc

    mov     edi, [ebp].rdr_paldata  ; edi -> palette buffer
    mov     dx, dac_read_addr       ; dac register # selector

    clr     ax, bx                  ; clear for byte loads
    mov     al, [ebp].rdr_startreg  ; get start register
    mov     bl, [ebp].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
    movzx   ecx,cx

    rep     insb                ; block read dac registers

    pop     edi ebp             ; restore registers
    ret     8                   ; exit & clean up stack

; ===== 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
                dd  ?   ; ebp
                dd  ?   ; caller
    sap_page    dw  ?   ; page # for drawing
sap_stack   ends

    public  set_active_page

set_active_page:

    push    ebp                 ; preserve registers
    mov     ebp, esp            ; set up stack frame

    mov     bx, [ebp].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, 2               ; scale page # to dword
    mov     eax, page_addr[bx]  ; get offset to page

    mov     current_page, eax   ; and set for future mov's

@sap_exit:
    pop     ebp                 ; restore registers
    ret     2                   ; exit and clean up stack

;================
;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:

    mov     ax, active_page     ; get active page #
    ret                         ; exit and clean up stack

;===============================
;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
                dd  ?       ; ebp
                dd  ?       ; caller
    sdp_page    dw  ?       ; page # to display...
sdp_stack   ends

    public  set_display_page

set_display_page:

    push    ebp                 ; preserve registers
    mov     ebp, esp            ; set up stack frame

    mov     bx, [ebp].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, 2               ; scale page # to dword
    mov     ecx, page_addr[bx]   ; get offset in memory to page
    add     ecx, current_moffset ; adjust for any scrolling
    add     ecx,_code32a        ; adjust for protected mode

; 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

@sdp_exit:
    pop     ebp                 ; restore registers
    ret     2                   ; exit and clean up stack

;=================
;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:

    mov     ax, display_page    ; get display page #
    ret                         ; exit & clean up stack

    if x_set_window eq 1

;=======================================
;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
                dd  ?   ; ebp
                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:

    push    ebp                 ; preserve registers
    mov     ebp, esp            ; set up stack frame

; check if our scroll offsets are valid

    mov     bx, [ebp].sw_page   ; get desired page #
    cmp     bx, last_page       ; is page # valid?
    jae     @sw_exit            ; if not, do nothing

    mov     ax, [ebp].sw_ypos   ; get desired y offset
    cmp     ax, max_yoffset     ; is it within limits?
    ja      @sw_exit            ; if not, exit

    mov     cx, [ebp].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
    movzx   eax, ax

    mov     current_moffset, eax ; save offset info

    mov     display_page, bx    ; set current page #
    shl     bx, 2               ; scale page # to dword
    add     eax, page_addr[bx]  ; get offset in vga to page
    add     eax,_code32a        ; adjust for protected mode segment
    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, [ebp].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     ebp                 ; restore saved registers
    ret     6                   ; exit and clean up stack

;=============
;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:

    mov     ax, current_xoffset ; get current horz offset
    ret                         ; exit & clean up stack

;=============
;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:

    mov     ax, current_yoffset ; get current vertical offset
    ret                         ; exit & clean up stack

    endif

;============
;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:

    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

; ===== text display routines =====

    if x_gprintc eq 1

;==================================================
;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   dd  ?   ; screen width-1
    gpc_lines   db  ?,? ; scan lines to decode
    gpc_t_sets  dd  ?   ; saved charset segment
                dd  ?x3 ; edi, esi, ebp
                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:

    push    ebp esi edi         ; preserve important registers
    sub     esp, 10             ; allocate workspace on stack
    mov     ebp, esp            ; set up stack frame

    mov     edi, current_page   ; point to active vga page

    movzx   eax, screen_width   ; get logical line width
    mov     ebx, eax            ; bx = screen width
    dec     bx                  ;    = screen width-1
    mov     [ebp].gpc_width,ebx ; save for later use

    mul     [ebp].gpc_ypos      ; start of line = ypos * width
    add     edi, eax            ; di -> start of line ypos

    movzx   eax, [ebp].gpc_xpos ; get xpos of character
    mov     cx, ax              ; save copy of xpos
    shr     ax, 2               ; bytes into line = xpos/4
    add     edi, eax            ; di -> (xpos, ypos)

;get source addr of character bit map  & save

    mov     al, [ebp].gpc_char  ; get character #
    test    al, 080h            ; is hi bit set?
    jz      @gpc_lowchar        ; nope, use low char set ptr

    mov     ebx, charset_hi     ; bx = char set ptr:offset
    jmp     s @gpc_set_char     ; go setup character ptr

@gpc_lowchar:

    mov     ebx, charset_low    ; bx = char set ptr:offset

@gpc_set_char:
    and     eax, 07fh           ; mask out hi bits
    shl     ax, 3               ; * 8 bytes per bitmap
    add     ebx, eax            ; bx = offset of selected char
    mov     [ebp].gpc_t_sets, ebx ; save segment 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     [ebp].gpc_lines, al ; save on stack

@gpc_decode_char_byte:

    mov     esi, [ebp].gpc_t_sets ; get esi = string

    mov     bh, [esi]           ; get bit map
    inc     esi                 ; point to next line
    mov     [ebp].gpc_t_sets, esi ; and save new pointer...

    clr     eax                 ; 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, [ebp].gpc_colorf ; get foreground color
    out     dx, al               ; set up screen mask
    mov     [edi], 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, [ebp].gpc_colorb ; get background color
    out     dx, al               ; set up screen mask
    mov     [edi], ah            ; write foreground color

;now do middle/last band

@gpc_no_left0bits:
    inc     edi                 ; 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, [ebp].gpc_colorf ; get foreground color
    out     dx, al               ; set up screen mask
    mov     [edi], 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, [ebp].gpc_colorb ; get background color
    out     dx, al               ; set up screen mask
    mov     [edi], 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     edi                 ; 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, [ebp].gpc_colorf ; get foreground color
    out     dx, al               ; set up screen mask
    mov     [edi], 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, [ebp].gpc_colorb ; get background color
    out     dx, al               ; set up screen mask
    mov     [edi], ah            ; write foreground color

@gpc_no_right0bits:
    dec     edi                  ; adjust for next line advance

@gpc_next_line:
    add     edi, [ebp].gpc_width  ; point to next line
    xor     ch, char_bits       ; flip the clip mask back

    dec     [ebp].gpc_lines     ; count down lines
    jz      @gpc_exit           ; ok... done!

    jmp     @gpc_decode_char_byte   ; again! hey!

@gpc_exit:
    add     esp, 10             ; deallocate stack workspace
    pop     edi esi ebp         ; restore saved registers
    ret     10                  ; exit and clean up stack

    endif
    if x_tgprintc eq 1

;==========================================
;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
;

tpc_stack   struc
    tpc_width   dd  ?   ; screen width-1
    tpc_lines   db  ?,? ; scan lines to decode
    tpc_t_sets  dd  ?   ; saved charset segment
                dd  ?x3 ; edi, esi, ebp
                dd  ?   ; caller
    tpc_colorf  db  ?,? ; text color
    tpc_ypos    dw  ?   ; y position to print at
    tpc_xpos    dw  ?   ; x position to print at
    tpc_char    db  ?,? ; character to print
tpc_stack   ends

        public tgprintc

tgprintc:

    push    ebp esi edi         ; preserve important registers
    sub     esp, 10             ; allocate workspace on stack
    mov     ebp, esp            ; set up stack frame

    mov     edi, current_page   ; point to active vga page

    movzx   eax, screen_width   ; get logical line width
    mov     ebx, eax            ; bx = screen width
    dec     bx                  ;    = screen width-1
    mov     [ebp].tpc_width,ebx ; save for later use

    mul     [ebp].tpc_ypos      ; start of line = ypos * width
    add     edi, eax            ; di -> start of line ypos

    movzx   eax, [ebp].tpc_xpos ; get xpos of character
    mov     cx, ax              ; save copy of xpos
    shr     ax, 2               ; bytes into line = xpos/4
    add     edi, eax            ; di -> (xpos, ypos)

;get source addr of character bit map  & save

    mov     al, [ebp].tpc_char  ; get character #
    test    al, 080h            ; is hi bit set?
    jz      @tpc_lowchar        ; nope, use low char set ptr

    mov     ebx, charset_hi     ; bx = char set ptr:offset
    jmp     s @tpc_set_char     ; go setup character ptr

@tpc_lowchar:

    mov     ebx, charset_low    ; bx = char set ptr:offset

@tpc_set_char:
    and     eax, 07fh           ; mask out hi bits
    shl     ax, 3               ; * 8 bytes per bitmap
    add     ebx, eax            ; bx = offset of selected char
    mov     [ebp].tpc_t_sets, ebx ; save segment 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     [ebp].tpc_lines, al ; save on stack

@tpc_decode_char_byte:

    mov     esi, [ebp].tpc_t_sets ; get esi = string

    mov     bh, [esi]           ; get bit map
    inc     esi                 ; point to next line
    mov     [ebp].tpc_t_sets, esi ; and save new pointer...

    clr     eax                 ; 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      @tpc_no_left1bits       ; skip if no pixels to set

    mov     ah, [ebp].tpc_colorf ; get foreground color
    out     dx, al               ; set up screen mask
    mov     [edi], ah            ; write foreground color

;now do middle/last band

@tpc_no_left1bits:
    inc     edi                 ; 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      @tpc_no_middle1bits     ; skip if no pixels to set

    mov     ah, [ebp].tpc_colorf ; get foreground color
    out     dx, al               ; set up screen mask
    mov     [edi], ah            ; write foreground color

@tpc_no_middle1bits:
    xor     ch, all_planes      ; invert clip mask
    cmp     cl, 4               ; aligned by 4?
    jz      @tpc_next_line      ; if so, exit now..

    inc     edi                 ; 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      @tpc_no_right1bits      ; skip if no pixels to set

    mov     ah, [ebp].tpc_colorf ; get foreground color
    out     dx, al               ; set up screen mask
    mov     [edi], ah            ; write foreground color

@tpc_no_right1bits:
    dec     edi                  ; adjust for next line advance

@tpc_next_line:
    add     edi, [ebp].tpc_width  ; point to next line
    xor     ch, char_bits       ; flip the clip mask back

    dec     [ebp].tpc_lines     ; count down lines
    jz      @tpc_exit           ; ok... done!

    jmp     @tpc_decode_char_byte   ; again! hey!

@tpc_exit:
    add     esp, 10             ; deallocate stack workspace
    pop     edi esi ebp         ; restore saved registers
    ret     8                   ; exit and clean up stack

    endif
    if x_gprintc eq 1

;===============================================================
;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
                dd  ?x3 ; edi, esi, ebp
                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     dd  ?   ; far ptr to text string
ps_stack    ends

        public  print_str

print_str:

    push    ebp esi edi         ; preserve important registers
    mov     ebp, esp            ; set up stack frame

@ps_print_it:

    mov     cx, [ebp].ps_len    ; get remaining text length
    jcxz    @ps_exit            ; exit when out of text

    mov     edi, [ebp].ps_text  ; edi -> current char in text
    mov     al, [edi]           ; al = text character
    and     ax, 00ffh           ; clear high word
    jz      @ps_exit            ; exit if null character

    dec     [ebp].ps_len        ; remaining text length--
    inc     [ebp].ps_text       ; point to next text char

; set up call to gprintc

    push    ax                  ; set character parameter
    mov     bx, [ebp].ps_xpos   ; get xpos
    push    bx                  ; set xpos parameter
    add     bx, 8               ; advance 1 char to right
    mov     [ebp].ps_xpos, bx   ; save for next time through

    mov     bx, [ebp].ps_ypos   ; get ypos
    push    bx                  ; set ypos parameter

    mov     bx, [ebp].ps_colorf ; get text color
    push    bx                  ; set colorf parameter

    mov     bx, [ebp].ps_colorb ; get background color
    push    bx                  ; set colorb parameter

    call    gprintc             ; print character!
    jmp     s @ps_print_it      ; process next character

@ps_exit:
    pop     edi esi ebp         ; restore saved registers
    ret     14                  ; exit and clean up stack

    endif
    if x_tgprintc eq 1

;================================================================
;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
                dd  ?x3 ; edi, esi, ebp
                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    dd  ?   ; far ptr to text string
tps_stack   ends

        public  tprint_str

tprint_str:

    push    ebp esi edi         ; preserve important registers
    mov     ebp, esp            ; set up stack frame

@ts_print_it:

    mov     cx, [ebp].tps_len   ; get remaining text length
    jcxz    @ts_exit            ; exit when out of text

    mov     edi, [ebp].tps_text ; edi -> current char in text
    mov     al, [edi]           ; al = text character
    and     ax, 00ffh           ; clear high word
    jz      @ts_exit            ; exit if null character

    dec     [ebp].tps_len       ; remaining text length--
    inc     [ebp].tps_text      ; point to next text char

; set up call to tgprintc

    push    ax                  ; set character parameter
    mov     bx, [ebp].tps_xpos  ; get xpos
    push    bx                  ; set xpos parameter
    add     bx, 8               ; advance 1 char to right
    mov     [ebp].tps_xpos, bx  ; save for next time through

    mov     bx, [ebp].tps_ypos  ; get ypos
    push    bx                  ; set ypos parameter

    mov     bx, [ebp].tps_colorf ; get text color
    push    bx                  ; set colorf parameter

    call    tgprintc            ; print character!
    jmp     s @ts_print_it      ; process next character

@ts_exit:
    pop     edi esi ebp         ; restore saved registers
    ret     12                  ; exit and clean up stack

    endif

;===========================================
;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
                dd  ?   ; ebp
                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:

    push    ebp                 ; preserve registers
    mov     ebp, esp            ; set up stack frame

    mov     edi, [ebp].sdf_font ; get far ptr to font

    mov     esi, o charset_low  ; assume lower 128 chars
    test    [ebp].sdf_which, 1  ; font #1 selected?
    jz      @sdf_set_font       ; if not, skip ahead

    mov     esi, o charset_hi   ; ah, really it's 128-255

@sdf_set_font:
    mov     [esi], edi          ; set font pointer offset

    pop     ebp                 ; restore registers
    ret     6                   ; we are done.. outa here

; ===== bitmap (sprite) display routines =====

;======================================================
;draw_bitmap (seg image, xpos%, ypos% )
;======================================================
;
; 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 lifar byte array
; corresponding to (0,0) (1,0), (2,0) .. (width, height)
; this is the same lifar 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  - ommitted
;        height = height of the bitmap in pixels - ommitted
;
; exit:  no meaningful values returned
;
; routine has been modified so that first two words of bitmap define
; bitmap x and y size
;

db_stack    struc
    db_lineo    dw  ?   ; offset to next line
    db_pixcount dw  ?   ; (minimum) # of pixels/line
    db_start    dd  ?   ; addr of upper left pixel
    db_pixskew  dw  ?   ; # of bytes to adjust eol
    db_skewflag dw  ?   ; extra pix on plane flag
    db_height   dw  ?   ; height of bitmap in pixels
                dd  ?x3 ; edi, esi, ebp
                dd  ?   ; caller
    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:

    push    ebp esi edi         ; preserve important registers
    sub     esp, 14             ; allocate workspace
    mov     ebp, esp            ; set up stack frame

    mov     edi, current_page   ; point to active vga page
    cld                         ; direction flag = forward

    movzx   eax, [ebp].db_ypos  ; get ul corner ypos
    mul     screen_width        ; ax = offset to line ypos

    movzx   ebx, [ebp].db_xpos  ; get ul corner xpos
    mov     cl, bl              ; save plane # in cl
    shr     bx, 2               ; xpos/4 = offset into line

    add     edi, eax            ; edi -> start of line
    add     edi, ebx            ; edi -> upper left pixel
    mov     [ebp].db_start, edi ; save starting addr

; compute line to line offset

    mov     esi, [ebp].db_image ; esi-> source image
    lodsw                       ; get x width
    mov     bx,ax
    lodsw
    mov     [ebp].db_height,ax
    add     [ebp].db_image,4

    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     [ebp].db_lineo, ax      ; save line width offset
    mov     [ebp].db_pixcount, bx   ; minimum # pix to copy

    and     dx, plane_bits          ; get "partial band" size (0-3)
    mov     [ebp].db_pixskew, dx    ; also end of line skew
    mov     [ebp].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:

    mov     esi, [ebp].db_image ; esi-> source image
    mov     dx, [ebp].db_height ; # of lines to copy
    mov     edi, [ebp].db_start ; edi-> dest pos

@db_copy_line:
    mov     cx, [ebp].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     esi, 3              ; skip to next byte in same plane
    movsb                       ; copy bitmap pixel
    add     esi, 3              ; skip to next byte in same plane
    movsb                       ; copy bitmap pixel
    add     esi, 3              ; skip to next byte in same plane
    movsb                       ; copy bitmap pixel
    add     esi, 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     esi,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, [ebp].db_skewflag   ; get skew count
    jz      @db_next2               ; if no partial pixels

    movsb                       ; copy bitmap pixel
    dec     edi                 ; back up to align
    dec     esi                 ; back up to align

@db_next2:
    movzx   eax, [ebp].db_pixskew ; adjust skew
    add     esi, eax
    movzx   eax, [ebp].db_lineo   ; set to next display line
    add     edi, eax
    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     [ebp].db_start, 0   ; screen addr =+carry
    inc     w [ebp].db_image    ; start @ next byte

    sub     [ebp].db_skewflag, 1 ; reduce planes to skew
    adc     [ebp].db_skewflag, 0 ; back to 0 if it was -1

    jmp     f @db_copy_plane    ; go copy the next plane

@db_exit:
    add     esp, 14             ; deallocate workspace
    pop     edi esi ebp         ; restore saved registers
    ret     8                   ; exit and clean up stack

;=======================================================
;tdraw_bitmap (seg image, xpos%, ypos%)
;=======================================================
;
; 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   - ommitted
;        height = height of the bitmap in pixels  - ommitted
;
; exit:  no meaningful values returned
;
; routine has been modified so that first two words of bitmap define
; bitmap x and y size
;

tb_stack    struc
    tb_lineo    dw  ?   ; offset to next line
    tb_pixcount dw  ?   ; (minimum) # of pixels/line
    tb_start    dd  ?   ; addr of upper left pixel
    tb_pixskew  dw  ?   ; # of bytes to adjust eol
    tb_skewflag dw  ?   ; extra pix on plane flag
    tb_height   dw  ?   ; height of bitmap in pixels
                dd  ?x3 ; edi, esi, ebp
                dd  ?   ; caller
    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:

    push    ebp esi edi         ; preserve important registers
    sub     esp, 14             ; allocate workspace
    mov     ebp, esp            ; set up stack frame

    mov     edi, current_page   ; point to active vga page
    cld                         ; direction flag = forward

    movzx   eax, [ebp].tb_ypos  ; get ul corner ypos
    mul     screen_width        ; ax = offset to line ypos

    movzx   ebx, [ebp].tb_xpos  ; get ul corner xpos
    mov     cl, bl              ; save plane # in cl
    shr     bx, 2               ; xpos/4 = offset into line

    add     edi, eax            ; edi -> start of line
    add     edi, ebx            ; edi -> upper left pixel
    mov     [ebp].tb_start, edi ; save starting addr

; compute line to line offset

    mov     esi, [ebp].tb_image ; esi-> source image
    lodsw                       ; get x width
    mov     bx,ax
    lodsw
    mov     [ebp].tb_height,ax
    add     [ebp].tb_image,4

    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     [ebp].tb_lineo, ax      ; save line width offset
    mov     [ebp].tb_pixcount, bx   ; minimum # pix to copy

    and     dx, plane_bits          ; get "partial band" size (0-3)
    mov     [ebp].tb_pixskew, dx    ; also end of line skew
    mov     [ebp].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:

    mov     esi, [ebp].tb_image  ; esi-> source image
    mov     dx, [ebp].tb_height  ; # of lines to copy
    mov     edi, [ebp].tb_start  ; edi-> dest pos

; here ah is set with the value to be considered
; "transparent".  it can be changed!

@tb_copy_line:
    mov     ah, 0               ; value to detect 0
    mov     cx, [ebp].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     esi, 3              ; skip to next byte in same plane
    cmp     al, ah              ; it is "transparent"?
    je      @tb_skip_01         ; skip ahead if so
    mov     [edi], al           ; copy pixel to vga screen

@tb_skip_01:
    lodsb                       ; get pixel value in al
    add     esi, 3              ; skip to next byte in same plane
    cmp     al, ah              ; it is "transparent"?
    je      @tb_skip_02         ; skip ahead if so
    mov     [edi+1], al         ; copy pixel to vga screen

@tb_skip_02:
    lodsb                       ; get pixel value in al
    add     esi, 3              ; skip to next byte in same plane
    cmp     al, ah              ; it is "transparent"?
    je      @tb_skip_03         ; skip ahead if so
    mov     [edi+2], al         ; copy pixel to vga screen

@tb_skip_03:
    lodsb                       ; get pixel value in al
    add     esi, 3              ; skip to next byte in same plane
    cmp     al, ah              ; it is "transparent"?
    je      @tb_skip_04         ; skip ahead if so
    mov     [edi+3], al         ; copy pixel to vga screen

@tb_skip_04:
    add     edi, 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     esi, 3              ; skip to next byte in same plane
    cmp     al, ah              ; it is "transparent"?
    je      @tb_skip_05         ; skip ahead if so
    mov     [edi], al           ; copy pixel to vga screen

@tb_skip_05:
    inc     edi                 ; advance dest addr
    loopx   cx, @tb_copy2       ; pixels to copy--, loop until done

@tb_next_line:

; any partial pixels? (some planes only)

    or      cx, [ebp].tb_skewflag   ; get skew count
    jz      @tb_next2               ; if no partial pixels

    lodsb                       ; get pixel value in al
    dec     esi                 ; backup to align
    cmp     al, ah              ; it is "transparent"?
    je      @tb_next2           ; skip ahead if so
    mov     [edi], al           ; copy pixel to vga screen

@tb_next2:
    movzx   eax, [ebp].tb_pixskew ; adjust skew
    add     esi, eax
    movzx   eax, [ebp].tb_lineo   ; set to next display line
    add     edi, eax
    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     [ebp].tb_start, 0   ; screen addr =+carry
    inc     w [ebp].tb_image    ; start @ next byte

    sub     [ebp].tb_skewflag, 1 ; reduce planes to skew
    adc     [ebp].tb_skewflag, 0 ; back to 0 if it was -1

    jmp     @tb_copy_plane      ; go copy the next plane

@tb_exit:
    add     esp, 14             ; deallocate workspace
    pop     edi esi ebp         ; restore saved registers
    ret     8                   ; exit and clean up stack

; ==== 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
                dd  ?x3 ; edi, esi, ebp
                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:

    push    ebp esi edi         ; preserve important registers
    mov     ebp, esp            ; set up stack frame
    cld                         ; block xfer forwards

; make sure page #'s are valid

    mov     ax, [ebp].cp_sourcep ; get source page #
    cmp     ax, last_page       ; is it > max page #?
    jae     @cp_exit            ; if so, abort

    mov     bx, [ebp].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 esi and edi to video pages

    shl     bx, 2               ; scale index to dword
    mov     edi, page_addr[ebx] ; offset to dest page

    mov     bx, ax              ; index to source page
    shl     bx, 2               ; scale index to dword
    mov     esi, page_addr[ebx] ; offset to source page

    movzx   ecx, page_size      ; get size of 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:
    pop     edi esi ebp         ; restore saved registers
    ret     4                   ; exit and clean up stack

;==========================================================================
;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"
                dd  ?x3 ; edi, esi, ebp
                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:

    push    ebp esi edi         ; preserve important registers
    sub     esp, 4              ; allocate workspace on stack
    mov     ebp, esp            ; set up stack frame

; prep registers (and keep jumps short!)

    cld                         ; block xfer forwards

; make sure parameters are valid

    mov     bx, [ebp].cb_sourcep ; get source page #
    cmp     bx, last_page       ; is it > max page #?
    jae     @cb_abort           ; if so, abort

    mov     cx, [ebp].cb_destp  ; get destination page #
    cmp     cx, last_page       ; is it > max page #?
    jae     @cb_abort           ; if so, abort

    mov     ax, [ebp].cb_x1     ; get source x1
    xor     ax, [ebp].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 esi & edi

    mov     ax, [ebp].cb_y2     ; height of bitmap in lines
    sub     ax, [ebp].cb_y1     ; is y2 - y1 + 1
    inc     ax                  ; (add 1 since were not 0 based)
    mov     [ebp].cb_height, ax ; save on stack for later use

    mov     ax, [ebp].cb_x2     ; get # of "bands" of 4 pixels
    mov     dx, [ebp].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     [ebp].cb_width, ax  ; save on stack for later use

    shl     bx, 2               ; scale source page to dword
    mov     esi, page_addr[bx]  ; si = offset of source page
    mov     ax, [ebp].cb_y1     ; get source y1 line
    mul     screen_width        ; ax = offset to line y1
    movzx   eax, ax
    add     esi, eax            ; si = offset to line y1
    mov     ax, [ebp].cb_x1     ; get source x1
    shr     ax, 2               ; x1 / 4 = byte offset
    add     esi, eax            ; si = byte offset to (x1,y1)

    mov     bx, cx              ; dest page index to bx
    shl     bx, 2               ; scale source page to dword
    mov     edi, page_addr[bx]  ; di = offset of dest page
    mov     ax, [ebp].cb_desty1 ; get dest y1 line
    mul     screen_width        ; ax = offset to line y1
    movzx   eax, ax
    add     edi, eax            ; di = offset to line y1
    mov     ax, [ebp].cb_destx1 ; get dest x1
    shr     ax, 2               ; x1 / 4 = byte offset
    add     edi, eax            ; di = byte offset to (d-x1,d-y1)

    mov     cx, [ebp].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, [ebp].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, [ebp].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, [ebp].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, [ebp].cb_height ; cx = # of lines to copy
    movzx   edx, screen_width   ; dx = width of screen
    clr     ebx                 ; bx = offset into image

@cb_one_loop:
    mov     al, [esi+ebx]       ; load latches
    mov     [edi+ebx], al       ; unload latches
    add     bx, dx              ; advance offset to next line
    loopjz  cx, @cb_one_done    ; exit loop if finished

    mov     al, [esi+ebx]       ; load latches
    mov     [edi+ebx], 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, [ebp].cb_height ; cx = # of lines to copy
    mov     dx, screen_width    ; dx = width of screen
    clr     ebx                 ; bx = offset into image

@cb_left_loop:
    mov     al, [esi+ebx]       ; load latches
    mov     [edi+ebx], al       ; unload latches
    add     bx, dx              ; advance offset to next line
    loopjz  cx, @cb_left_done   ; exit loop if finished

    mov     al, [esi+ebx]       ; load latches
    mov     [edi+ebx], al       ; unload latches
    add     bx, dx              ; advance offset to next line
    loopx   cx, @cb_left_loop   ; loop until finished

@cb_left_done:
    inc     edi                 ; move dest over 1 band
    inc     esi                 ; move source over 1 band
    dec     [ebp].cb_width      ; band width--

; determine if right edge of bitmap needs special copy

@cb_check_right:
    mov     bx, [ebp].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     [ebp].cb_width      ; band width--
    mov     cx, [ebp].cb_height ; cx = # of lines to copy
    mov     dx, screen_width    ; dx = width of screen
    movzx   ebx, [ebp].cb_width ; bx = offset to right edge

@cb_right_loop:
    mov     al, [esi+ebx]       ; load latches
    mov     [edi+ebx], al       ; unload latches
    add     bx, dx              ; advance offset to next line
    loopjz  cx, @cb_right_done  ; exit loop if finished

    mov     al, [esi+ebx]       ; load latches
    mov     [edi+ebx], 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, [ebp].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)
    movzx   edx, dx
    mov     ax, [ebp].cb_height ; ax = # of lines to copy
    movzx   ecx,cx
    mov     ebx, ecx            ; bx = quick rep reload count

; actual copy loop.  rep movsb does the work

@cb_middle_copy:
    mov     ecx, ebx            ; recharge rep count
    rep     movsb               ; move bands
    loopjz  ax, @cb_finish      ; exit loop if finished

    add     esi, edx            ; adjust esi to next line
    add     edi, edx            ; adjust edi to next line

    mov     ecx, ebx            ; recharge rep count
    rep     movsb               ; move bands

    add     esi, edx            ; adjust esi to next line
    add     edi, edx            ; adjust edi 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     esp, 4              ; deallocate stack workspace
    pop     edi esi ebp         ; restore saved registers
    ret     16                  ; exit and clean up stack

; return to mode 03 before exiting to dos

        public mode03

mode03:
        mov v86r_ax,3h
        mov al,10h
        int 33h
        ret

fb_stack    struc
    fb_add      dw  ?
    fb_count    dd  ?
                dd  ?x3 ; edi, esi, ebp
                dd  ?   ; caller
    fb_pal      dd  ?   ; source palette
fb_stack    ends

        public wipeoffpalette
        public fadeoffpalette
        public fadeonpalette

; load esi to palette

tmppal  db 768 dup (0)

wipeoffpalette:

        xor     al,al
        mov     dx,3c8h
        out     dx,al
        inc     dx
        mov     cx,768
        xor     al,al

wipeit: out     dx,al
        loop    wipeit

        ret

fadeoffpalette:
        mov     bh, 2
        mov     ah,0
        jmp     fadepalette

fadeonpalette:
        mov     bh,-2
        mov     ah,64

fadepalette:
        push    ebp esi edi         ; preserve important registers
        sub     esp, 6
        mov     ebp, esp            ; set up stack frame
        mov     ecx,32
        cld                         ; block xfer forwards
fonp2:
        mov     esi,[ebp].fb_pal
        mov     [ebp].fb_add,ax
        mov     [ebp].fb_count,ecx

        mov     edi,offset tmppal
        mov     ecx,768

fonp1:
        lodsb
        sub     al,ah
        jnc     fonp3
        xor     al,al
fonp3:
        stosb
        loop    fonp1

        call    sync_display
        mov     dx,3c8h
        mov     esi,offset tmppal
        mov     ecx,768/6/2
        xor     al,al
        out     dx,al
        inc     dx

fonp4:
        lodsb
        out     dx,al
        lodsb
        out     dx,al
        lodsb
        out     dx,al
        lodsb
        out     dx,al
        lodsb
        out     dx,al
        lodsb
        out     dx,al
        loop    fonp4

        call    sync_display
        mov     dx,3c8h
        mov     ecx,768/6/2

fonp5:
        lodsb
        out     dx,al
        lodsb
        out     dx,al
        lodsb
        out     dx,al
        lodsb
        out     dx,al
        lodsb
        out     dx,al
        lodsb
        out     dx,al
        loop    fonp5

        mov     ecx,[ebp].fb_count
        mov     ax,[ebp].fb_add
        add     ah,bh
        loop    fonp2

        add     esp,6
        pop     edi esi ebp

        ret     4

        public turn_screen_off
        public turn_screen_on

turn_screen_off:           ; guess what these do!
        mov dx,03dah
        in al,dx
        mov dx,03c0h
        mov al,0
        out dx,al
        ret

turn_screen_on:
        mov dx,03dah
        in al,dx
        mov dx,03c0h
        mov al,20h
        out dx,al
        ret

code32  ends
        end
