GEM pages
Home -> GEM -> GEM/1 and VGA

GEM/1 and VGA Drivers

GEM/1 was written before VGA, so the highest resolution it can manage on standard PC hardware is 640x350 using the EGA driver (or 720x348 mono if you've got a Hercules card). If you try to substitute a VGA driver from a later version (say VGA.SYS from GEM/2) then the menus start acting oddly; the top left-hand corner of the screen fills up with the desktop pattern or bits of menus.

I've developed a patch to fix this; it can be downloaded as VGAPATCH.ZIP from my programs page. The rest of this document describes what is going wrong and why.

Investigation

My first thought was that this was a driver incompatibility, so I proceeded to take the GEM/1 EGA driver and gradually change it into a VGA driver. Since EGA and VGA are very similar from a hardware point of view, this is quite easy. The first step is to change the video mode selected; this is at 39C0h in IBMEHFP3.SYS, and just needs to be changed from 10h to 12h. GEM/1 didn't complain; it simply left the bottom quarter of the screen blank.

The next step was to increase the height of the screen. This is done by searching in the driver file for the number 349 (015Dh) and replacing it with the new height. The obvious change to make is to make it 479 (01DFh); doing this resulted in the same problems as when the GEM/2 driver was used. By varying the replacement height, I found that the maximum value that works is 0198h (408) corresponding to a 640x409 resolution.

This suggests a signed-integer overflow in the AES, because a 640x409 screen takes 32720 bytes (fitting in 15 bits), but a 640x410 one would take 32800 (which needs 16 bits). Further investigation shows that the gsx_mcalc() function in GEM/2 uses 32-bit arithmetic, but the equivalent function in GEM/1 does not:

gsx_mcalc() in GEM/1

;
; GEM/1 calculation of memory required for temporary buffer
;
gsx_mcalc   proc near
        push    bp
        sub sp, 6
        mov bp, sp
        xor ax, ax
        push    ax
        push    ax
        xor ax, ax
        push    ax
        xor bx, bx
        push    bx
        mov ax, offset gl_tmp_fd_addr
        push    ax
        call    gsx_fix ;Set up the gl_tmp MFDB
        mov sp, bp
        mov ax, gl_tmp_fww  ;Form width in words
        add ax, 7
        mov bx, 8
        cwd 
        idiv    bx
        mov [bp+0], ax  ;(fww + 7) / 8
        mov ax, gl_tmp_fh
        imul    gl_nplanes
        mov bx, 4
        cwd 
        idiv    bx
        mov [bp+2], ax  ;(fh * planes) / 4
        mov ax, [bp+0]
        mov bx, [bp+2]
        imul    bx      ;16-bit multiply to get number of 
        mov cl, 4       ;paragraphs
        mov [bp+4], ax
        shl ax, cl      ;16-bit left shift
        xor bx, bx
        test    ax, ax      ;Sign-extend to 32-bit signed
        jns 4f      ;long integer.
        dec bx

4:      mov word ptr gl_mlen, ax
        mov word ptr gl_mlen+2, bx
        xchg    ax, bx
        add sp, 6
        pop bp
        retn    
gsx_mcalc   endp

gsx_mcalc() in GEM/2

;
; GEM/2 calculation of memory required for temporary buffer
;
gsx_mcalc   proc near
        push    bp
        sub sp, 0Ch
        mov bp, sp
        xor ax, ax
        push    ax
        push    ax
        xor ax, ax
        push    ax
        xor bx, bx
        push    bx
        mov ax, offset gl_tmp_fd_addr
        push    ax
        call    gsx_fix ;Set up the gl_tmp memory form
        mov sp, bp
        mov ax, gl_tmp_fww
        add ax, 7
        mov bx, 8
        cwd 
        idiv    bx  ;ax = (fww + 7) / 8
        xor bx, bx
        test    ax, ax
        jns 1f
        dec bx

1:      mov [bp+0], ax
        mov ax, gl_tmp_fh
        imul    gl_nplanes
        mov cx, 4
        cwd 
        idiv    cx  ;(fh * planes) / 4
        xor cx, cx
        test    ax, ax
        jns 2f
        dec cx

2:      mov [bp+4], ax
        mov ax, bx
        mov bx, [bp+0]
        mov dx, [bp+4]
        call    _mul32  ;32-bit multiply
        mov cx, 4
        mov [bp+0Ah], ax
        mov [bp+8], bx

3:      shl bx, 1   ;32-bit left shift
        rcl ax, 1
        loop    3b
        mov word ptr gl_mlen+2, ax
        mov word ptr gl_mlen, bx
        add sp, 0Ch
        pop bp
        retn    
gsx_mcalc   endp

Fixing it

So, can GEM/1 be patched to use 32-bit arithmetic here? Perhaps not completely, but the data loss is at the point that the size in paragraphs gets converted to a byte count. Since a 640x480 plane fits in 38400 bytes, we should be able to get away with using 16-bit unsigned arithmetic rather than signed. That's a simple change; just remove the dec bx at the end of the function.

To do this, take a hex editor to GEM1.EXE. Search for the sequence

        b1 04 89 46 04 d3 e0 33 db 85 c0 79 01 4b

(in GEM v1.2 it's at offset 0A8CFh from the start of the file). Change the last byte to 90h, so it becomes:

        b1 04 89 46 04 d3 e0 33 db 85 c0 79 01 90

This fix doubles the maximum plane size, which is fine for 640x480 and 800x600 displays. But it's possible to do better, by using a 32-bit shift rather than a 16-bit shift. The code to do this is actually shorter than the original:

    mov     cx,4
    xor     bx,bx
4:
    shl     ax,1
    rcl     bx,1 
    loop    4b
    nop
    nop
    nop 

To do this, search for the same byte sequence as before, i.e.:

        b1 04 89 46 04 d3 e0 33 db 85 c0 79 01 4b

and change it to:

        b9 04 00 31 db d1 e0 d1 d3 e2 fa 90 90 90

Links

I've done something similar for Windows 1.0.

John Elliott