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 SDPSC8.VGA from GEM/3.0) 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