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