page    60,132
title   Ega720      Hercules-compatible resident driver for EGA cards

; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; This program demonstrates how to program an Enhanced Graphics Adapter driving
; a Monochrome display in a Hercules-compatible 720x348 mode.  It will work
; with *any* EGA card with at least 128K memory.  It does not require an
; EGA clones with built-in Hercules emulation.  However, it will NOT work if
; you have a CGA installed in addition to the EGA.
;
; You can run this as a "stay resident" program, and patch existing Hercules-
; compatible programs to call it so they can run on an EGA in graphics mode.
; (Or similarly, patch a program that does know about the EGA to run in the
; higher 720x348 resolution instead of the normal 640x350 EGA resolution, by
; using its Hercules driver instead of its EGA driver.)
;
; Or, you can incorporate this code into your own program to provide a higher
; resolution mode on Monochrome EGA's.  In this case, ignore all the details
; about how to patch existing programs and look at the "DoInt" subroutine and
; the "HOW IT WORKS" discussion.
;
; The program does not make the EGA register-compatible with the Hercules,
; however it does give the same memory map and 720 pixel wide display as the
; Hercules card.  To make an existing Hercules program work with this program,
; you will have to patch that program so that its initialization code calls
; this routine instead of loading the Hercules registers.  Once the card is
; initialized, the program can write to the display buffer just as if it were
; writing to a Hercules card.
;
; ***HOW TO INSTALL THIS PROGRAM***
;
; Assemble, link, and exe2bin this file.  Then run it.  It installs a routine
; under Interrupt 68h (normally unused).  If INT 68h is in use for something
; else on your system, change the interrupt number below.
;
; ***HOW TO PATCH AN EXISTING PROGRAM***
;
; To patch an existing Hercules-compatible program, you will need to use
; DEBUG to: 1) find the Hercules initialization code, 2) determine whether it
; uses Hercules Page 0, Page 1, or both, and 3) patch in an appropriate INT 68h
; call.
;
; Hercules initialization code will consist of some OUT instructions to these
; registers: 3BFh, 3B8h, 3B4h, and 3B5h.  The sequence will be something like:
;
; Output the value 1 or 3 to 3BFh (this may be omitted in older programs)
; Output any value with bit 3 off to 3B8h (turns video off)
; A loop writing various values to 3B4h and 3B5h (loads CRTC registers)
; Output a value with bit 3 on to 3B8h (turns video back on)
;
; * This last output gives you some critical information.  Look at the values
;   of bits 1 and 7 in particular:
;       Bit 1 (mask 02h): 0 = text mode, 1 = graphics mode.
;       Bit 7 (mask 80h): 0 = page 0, 1 = page 1.
;
; To find the code, you can use the Search command in DEBUG something like:
;
;       -s 100 l xxxx bf 3
; or
;       -s 100 l xxxx b8 3
;
; where xxxx is the value in register CX.  If the program has a separate
; Hercules driver, that's the file you should be looking in.  If not, try
; the program file itself (if a .EXE file do the usual trick of renaming it
; to a different extension, because you will need to write it back out.)
;
; If the initialization sequence is setting TEXT mode, replace it with a
; normal BIOS "set mode" call.
;
;       mov     ah,0                    ; (You're in DEBUG, so hex is default)
;       mov     al,7                    ; (Yes, this could just be a MOV AX)
;       int     10
;
; You may find code that loads the Hercules registers and then also does a
; BIOS set mode call.  In that case, just patch out the Hercules code and
; let the set mode do its thing.  Or there may just be a BIOS call.  Leave
; it alone.
;
; If the initialization sequence is setting GRAPHICS mode, replace it with
; a call to this resident routine:
;
;       mov     al,80 *or* 00           ; 80 for page 1, 00 for page 0
;       int     68
;
; Use 80 or 00 depending on whether you found the initialization code to be
; using page 1 or page 0.  For example, Microsoft Windows uses page 1, while
; Framework version 1 uses page 0.
;
; Naturally, when making these patches you should look at the surrounding code
; to duplicate any register saving, return values, NEAR or FAR return, etc.
; Note that the INT 10 and INT 68 calls preserve all registers except AX.
;
; These patches will be sufficient for many programs.  If you have a program
; that uses both Hercules pages 0 and 1 and flips back and forth between them,
; you will need to do some additional work.  The program will contain
; additional output instructions to port 3B8, with bit 7 (mask 80h) on or off.
; Replace these with:
;
;       mov     al,81 *or* 01           ; 81 for page 1, 01 for page 0
;       int     68
;
; *Note* I haven't tested this option, sorry.
;
; ***A SAMPLE PATCH - MICROSOFT WINDOWS***
;
; To patch Microsoft Windows for this high resolution mode, do the following:
; (This is for Windows version 1.01, either the retail or Developer's version)
;
;   1.  **Make COPIES of your SETUP and BUILD disks**
;       **Use the COPIES.  DO NOT MODIFY YOUR ORIGINALS!!**
;
;   2.  On your SETUP disk, apply this patch:
;
;           A>debug hercules.drv
;           -a a97
;           xxxx:0A97 mov al,80
;           xxxx:0A99 int 68
;           xxxx:0A9B mov al,1
;           xxxx:0A9D ret
;           xxxx:0A9E
;           -a ab2
;           xxxx:0AB2 mov ah,0
;           xxxx:0AB4 int 10
;           xxxx:0AB6 mov al,1
;           xxxx:0AB8 ret
;           xxxx:0AB9
;           -w
;           -q
;
;   3.  On your BUILD disk, apply this patch:
;
;           A>copy egamono.grb hercules.grb
;
;           A>debug hercules.lgo
;           -a 343
;           xxxx:0343 mov al,80
;           xxxx:0345 int 68
;           xxxx:0347 ret
;           xxxx:0348
;           -w
;           -q
;
;   4.  Run SETUP to install Windows.  When it asks for the display type,
;       specify Hercules.
;
; You should be up and running in the higher resolution mode!
; The only problem I have found is that if you run an "old" application
; that takes over the screen it doesn't display the "press any key to
; return to Windows" message.  This is done by HERCULES.GRB, which I didn't
; get patched, but instead took the shortcut of copying EGAMONO.GRB.
;
; NOTE: Microsoft has OK'd my uploading this patch (see BIX message
; microsoft/applications #192), but of course does not endorse or take
; any responsibility for it.  Thank you Microsoft for a sensible attitude.
;
; I have also successfully patched Lotus 1-2-3 to run on the EGA in
; this mode, but will NOT make that patch available because Lotus is very
; unfriendly to people who patch their products.  See BIX message
; spreadsheets/other #24.  The words I have for Lotus are not printable.
;
; (I have also patched Framework version 1.1 but haven't yet checked with
; Ashton-Tate about uploading patches.  Hint:  It's HERCULES.DRV, and they
; use page 0, unlike Windows which uses page 1.)
;
; ***HOW IT WORKS***
;
; If you want to support the 720x348 mode in a program of your own, this is
; the section of interest to you.  (Or you can ignore this and just use the
; code.)
;
; This program uses some EGA features which are not used in the standard modes.
; Page number references are from the August 2, 1984 EGA manual.
;
; The "Memory Map" bits in Graphics Controller register 6 ("Miscellaneous
; Register") are set to 00.  This maps the EGA display memory into a full
; 128K bytes from A0000h to BFFFFh.  This is why you can't have a CGA in
; the same machine.  Theoretically, you could set these to 10 if you were
; using Hercules page 0 (B0000-B7FFF) and then you could co-exist with a CGA.
; I didn't get this to work, however.  (p.53)
;
; In this 128K mode (only), bit 5 of the Miscellaneous Output Register selects
; which 64K block is displayed.  This is described wrong in the manual.  All
; standard modes set this to 1.  We set it to 0, which selects the higher
; 64K block (B0000-BFFFF).  1 would give us A0000-AFFFF.  (p.13)
;
; Like in the standard modes, we then use CRTC registers 0C/0D (Start Address)
; to select the display base address within that 64K block.  This selects
; Hercules page 0 or 1.  (p.34)
;
; The "native" EGA graphics modes use linear addressing, where each scan line
; begins in memory right after the previous one.  The CGA has a strange
; interleaving, where alternate rows are offset by 2000h.  The Hercules card
; is like the CGA except it uses groups of four rows instead of two.  So your
; row addressing is like this:
;   Row 0:  0000h
;   Row 1:  2000h
;   Row 2:  4000h
;   Row 3:  6000h
;   Row 4:  0000h + (size of one row = 90 bytes)
;   Row 5:  2000h + 90
;   etc.
;
; CRTC register 17h (Mode Control) contains two bits (bits 0 and 1) which
; control this addressing.  In the EGA native modes, these bits are both 1,
; and addressing is linear.  If bit 0 is 0, you get the CGA compatibility
; mode, with alternating lines offset by 2000h.  If bits 0 and 1 are both 0,
; you get the Hercules addressing mode as shown above.  Don't be misled by
; the funny name of bit 1 in the manual.  It should have been called CMS 1
; because it is exactly like bit 0 (CMS 0).  (p.41-42)
;
; If you are writing your own software to support the 720 mode, you may find
; it easier to use linear addressing instead of the funny Hercules/CGA style
; addressing.  To do this, set both these bits to 1 instead of 0.  Fiddle
; with the CRTC registers a little and you will then get a 720x350 mode.
; (Hercules is 348 because it had to be divisible by 4.)  I haven't tested
; this, however.
;
; ***NO NONSENSE PUBLIC DOMAIN STATEMENT***
;
; This program and documentation are placed in the public domain.  Period.
;
; June 22, 1986
;
; Michael Geary
; P.O. Box 1479
; Los Gatos, CA  95031
; BIX: geary

HercInt equ     68h                     ; Interrupt to put EGA in Hercules mode
                                        ; Change this if INT 68h conflicts
                                        ; with something else

Code    SEGMENT


Zseg    SEGMENT at 0

        org     HercInt*4               ; Our interrupt vector
IntOff  dw      ?
IntSeg  dw      ?

        org     4A8h                    ; EGA BIOS "Save Table" pointer
SvXoff  dw      ?
SvXseg  dw      ?

Zseg    ENDS


Data    SEGMENT byte

; This is a "Save Table" as used by the EGA BIOS.  Since it is just used
; temporarily for our mode setting, we provide only the single parameter
; table instead of the full set.

Tabl    label   dword
        dw      offset Com:Params - (11h*40h)
TblSeg  dw      ?
        dd      7 dup (?)

Params  label   byte

; "Hercules" Mode F (large memory)

        ; cols, rows, lines
        db      05Ah, 018h, 00Eh

        ; page len
        dw      08000h

        ; sequencer
        db      001h, 001h, 000h, 006h

        ; miscoutreg
        db      086h

        ; crtc
        db      06Ch, 059h, 05Ch, 02Ch
        db      05Bh, 04Ch, 070h, 01Fh
        db      000h, 003h, 000h, 000h
PageBit label   byte
        db      000h, 000h, 000h, 000h
        db      05Dh, 02Dh, 05Bh, 02Dh
        db      00Dh, 05Ch, 06Ch, 0E0h, 0FFh

        ; attribute
        db      000h, 008h, 008h, 008h, 008h, 008h, 008h, 008h
        db      008h, 008h, 008h, 008h, 008h, 008h, 008h, 008h
        db      003h, 000h, 001h, 000h

        ; graphics
        db      000h, 000h, 000h, 000h, 000h, 000h, 001h, 00Fh, 0FFh

Data    ENDS


Com     GROUP   Code, Data, Top

        ASSUME  cs:Com, ds:Com, es:nothing, ss:nothing

        org     100h

; This is the code to install the resident driver.  You won't need it if you
; are incorporating the 720 technique into your own program, of course.

Start:
        xor     ax, ax
        mov     es, ax
        ASSUME  es:Zseg

        CLI

        mov     IntOff, offset Com:DoInt
        mov     IntSeg, cs

        mov     TblSeg, cs

        STI

        mov     dx, offset Com:TopOff
        shr     dx, 1
        shr     dx, 1
        shr     dx, 1
        shr     dx, 1

        mov     ax, 3100h
        int     21h                         ; Terminate and stay resident

        ASSUME  ds:nothing

; This the code to set up the 720x348 mode.  To make life easy, we
; temporarily set up a pointer to our register table and then let the BIOS
; do the work of loading all the registers.

DoInt:
        push    es
        push    ax

        test    al, 1                   ; is it just a page flip?
        jnz     Flip                    ; yes, just go do that

; Set up Hercules mode from scratch
        and     al, 80h
        mov     PageBit, al             ; set page 0 or 1

        xor     ax, ax
        mov     es, ax
        ASSUME  es:Zseg

        CLI

        push    SvXoff                  ; save previous parameter table
        push    SvXseg

        mov     SvXoff, offset Com:Tabl     ; temporarily set to our table
        mov     SvXseg, cs

        STI

        mov     ax, 000Fh               ; call BIOS to do the mode setting
        int     10h                     ; using our parameters

        CLI

        pop     SvXseg                  ; restore previous parameter table
        pop     SvXoff

        STI

return:
        pop     ax
        pop     es

        iret

; Do a simple page flip.  *Note*  I haven't tested this!  -MG
; If you are using the 720 technique in your own program you probably won't
; want to use this code anyway.
Flip:
        and     al, 80h
        push    dx
        mov     dx, 3B4h
        mov     ah, al
        mov     al, 0Ch
        out     dx, ax
        pop     dx
        jmp short return


Code    ENDS


Top     SEGMENT para
TopOff:
Top     ENDS


        END     Start
