|
Table of Content | Chapter Twenty Four (Part 3) |
| CHAPTER TWENTY
FOUR: THE PC GAME ADAPTER (Part 2) |
| 24.5.13 - An SGDI Driver for the Standard Game Adapter Card |
If you write your program to make SGDI calls
you will
discover that the TestPresence call will probably return "not
present" when your program searches for a resident SGDI driver in memory. This is
because few manufacturers provide SGDI drivers at this point and even fewer standard game
adapter companies ship any software at all with their products
much less an SGDI driver.
Gee
what kind of standard is this if no one uses it? Well
the purpose of this section is
to rectify that problem.
The assembly code that appears at the end of this section provides a fully functional public domain SGDI driver for the standard game adapter card (the next section present an SGDI driver for the CH Products' Flightstick Pro). This allows you to write your application making only SGDI calls. By supplying the SGDI TSR with your product your customers can use your software with all standard joysticks. Later if they purchase a specialized device with its own SGDI driver your software will automatically work with that driver with no changes to your software.
If you do not like the idea of having a user run a TSR
before your application
you can always include the following code within your program's
code space and activate it if the SGDI TestPresence call determines that no
other SGDI driver is present in memory when you start your program.
Here's the complete code for the standard game adapter SGDI driver:
.286 page 58 132 name SGDI title SGDI Driver for Standard Game Adapter Card subttl This Program is Public Domain Material. ; SGDI.EXE ; ; Usage: ; SDGI ; ; This program loads a TSR which patches INT 15 so arbitrary game programs ; can read the joystick in a portable fashion. ; ; ; We need to load cseg in memory before any other segments! cseg segment para public 'code' cseg ends ; Initialization code which we do not need except upon initial load ; goes in the following segment: Initialize segment para public 'INIT' Initialize ends ; UCR Standard Library routines which get dumped later on. .xlist include stdlib.a includelib stdlib.lib .list sseg segment para stack 'stack' sseg ends zzzzzzseg segment para public 'zzzzzzseg' zzzzzzseg ends CSEG segment para public 'CODE' assume cs:cseg ds:nothing wp equ <word ptr> byp equ <byte ptr> Int15Vect dword 0 PSP word ? ; Port addresses for a typical joystick card: JoyPort equ 201h JoyTrigger equ 201h ; Data structure to hold information about each pot. ; (mainly for calibration and normalization purposes). Pot struc PotMask byte 0 ;Pot mask for hardware. DidCal byte 0 ;Is this pot calibrated? min word 5000 ;Minimum pot value max word 0 ;Max pot value center word 0 ;Pot value in the middle Pot ends ; Variables for each of the pots. Must initialize the masks so they ; mask out all the bits except the incomming bit for each pot. Pot0 Pot <1> Pot1 Pot <2> Pot2 Pot <4> Pot3 Pot <8> ; The IDstring address gets passed back to the caller on a testpresence ; call. The four bytes before the IDstring must contain the serial number ; and current driver number. SerialNumber byte 0 0 0 IDNumber byte 0 IDString byte "Standard SGDI Driver" 0 byte "Public Domain Driver Written by Randall L. Hyde" 0 ;============================================================================ ; ; ReadPots- AH contains a bit mask to determine which pots we should read. ; Bit 0 is one if we should read pot 0 bit 1 is one if we should ; read pot 1 bit 2 is one if we should read pot 2 bit 3 is one ; if we should read pot 3. All other bits will be zero. ; ; This code returns the pot values in SI BX BP and DI for Pot 0 1 ; 2 & 3. ; ReadPots proc near sub bp bp mov si bp mov di bp mov bx bp ; Wait for any previous signals to finish up before trying to read this ; guy. It is possible that the last pot we read was very short. However ; the trigger signal starts timers running for all four pots. This code ; terminates as soon as the current pot times out. If the user immediately ; reads another pot it is quite possible that the new pot's timer has ; not yet expired from the previous read. The following loop makes sure we ; aren't measuring the time from the previous read. mov dx JoyPort mov cx 400h Wait4Clean: in al dx and al 0Fh loopnz Wait4Clean ; Okay read the pots. The following code triggers the 558 timer chip ; and then sits in a loop until all four pot bits (masked with the pot mask ; in AL) become zero. Each time through this loop that one or more of these ; bits contain zero this loop increments the corresponding register(s). mov dx JoyTrigger out dx al ;Trigger pots mov dx JoyPort mov cx 1000h ;Don't let this go on forever. PotReadLoop: in al dx and al ah jz PotReadDone shr al 1 adc si 0 ;Increment SI if pot 0 still active. shr al 1 adc bx 0 ;Increment BX if pot 1 still active. shr al 1 adc bp 0 ;Increment BP if pot 2 still active. shr al 1 adc di 0 ;Increment DI if pot 3 still active. loop PotReadLoop ;Stop eventually if funny hardware. and si 0FFFh ;If we drop through to this point and bx 0FFFh ; one or more pots timed out (usually and bp 0FFFh ; because they are not connected). and di 0FFFh ; The reg contains 4000h set it to 0. PotReadDone: ret ReadPots endp ;---------------------------------------------------------------------------- ; ; Normalize- BX contains a pointer to a pot structure AX contains ; a pot value. Normalize that value according to the ; calibrated pot. ; ; Note: DS must point at cseg before calling this routine. assume ds:cseg Normalize proc near push cx ; Sanity check to make sure the calibration process went okay. cmp [bx].Pot.DidCal 0 ;Is this pot calibrated? je BadNorm ;If not quit. mov dx [bx].Pot.Center ;Do a sanity check on the cmp dx [bx].Pot.Min ; min center and max jbe BadNorm ; values to make sure cmp dx [bx].Pot.Max ; min < center < max. jae BadNorm ; Clip the value if it is out of range. cmp ax [bx].Pot.Min ;If the value is less than ja MinOkay ; the minimum value set it mov ax [bx].Pot.Min ; to the minimum value. MinOkay: cmp ax [bx].Pot.Max ;If the value is greater than jb MaxOkay ; the maximum value set it mov ax [bx].Pot.Max ; to the maximum value. MaxOkay: ; Scale this guy around the center: cmp ax [bx].Pot.Center ;See if less than or greater jb Lower128 ; than centered value. ; Okay current reading is greater than the centered value scale the reading ; into the range 128..255 here: sub ax [bx].Pot.Center mov dl ah ;Multiply by 128 mov ah al mov dh 0 mov al dh shr dl 1 rcr ax 1 mov cx [bx].Pot.Max sub cx [bx].Pot.Center jz BadNorm ;Prevent division by zero. div cx ;Compute normalized value. add ax 128 ;Scale to range 128..255. cmp ah 0 je NormDone mov ax 0ffh ;Result must fit in 8 bits! jmp NormDone ; If the reading is below the centered value scale it into the range ; 0..127 here: Lower128: sub ax [bx].Pot.Min mov dl ah mov ah al mov dh 0 mov al dh shr dl 1 rcr ax 1 mov cx [bx].Pot.Center sub cx [bx].Pot.Min jz BadNorm div cx cmp ah 0 je NormDone mov ax 0ffh jmp NormDone ; If something went wrong return zero as the normalized value. BadNorm: sub ax ax NormDone: pop cx ret Normalize endp assume ds:nothing ;============================================================================ ; INT 15h handler functions. ;============================================================================ ; ; Although these are defined as near procs they are not really procedures. ; The MyInt15 code jumps to each of these with BX a far return address and ; the flags sitting on the stack. Each of these routines must handle the ; stack appropriately. ; ;---------------------------------------------------------------------------- ; BIOS- Handles the two BIOS calls DL=0 to read the switches DL=1 to ; read the pots. For the BIOS routines we'll ignore the cooley ; switch (the hat) and simply read the other four switches. BIOS proc near cmp dl 1 ;See if switch or pot routine. jb Read4Sw je ReadBIOSPots ; If not a valid BIOS call jump to the original INT 15 handler and ; let it take care of this call. pop bx jmp cs:Int15Vect ;Let someone else handle it! ; BIOS read switches function. Read4Sw: push dx mov dx JoyPort in al dx and al 0F0h ;Return only switch values. pop dx pop bx iret ; BIOS read pots function. ReadBIOSPots: pop bx ;Return a value in BX! push si push di push bp mov ah 0Fh ;Read all four pots. call ReadPots mov ax si mov cx bp ;BX already contains pot 1 reading. mov dx di pop bp pop di pop si iret BIOS endp ;---------------------------------------------------------------------------- ; ; ReadPot- On entry DL contains a pot number to read. ; Read and normalize that pot and return the result in AL. assume ds:cseg ReadPot proc near ;;;;;;;;;; push bx ;Already on stack. push ds push cx push dx push si push di push bp mov bx cseg mov ds bx ; If dl = 0 read and normalize the value for pot 0 if not try some ; other pot. cmp dl 0 jne Try1 mov ah Pot0.PotMask ;Get bit for this pot. call ReadPots ;Read pot 0. lea bx Pot0 ;Pointer to pot data. mov ax si ;Get pot 0 reading. call Normalize ;Normalize to 0..FFh. jmp GotPot ;Return to caller. ; Test for DL=1 here (read and normalize pot 1). Try1: cmp dl 1 jne Try2 mov ah Pot1.PotMask call ReadPots mov ax bx lea bx Pot1 call Normalize jmp GotPot ; Test for DL=2 here (read and normalize pot 2). Try2: cmp dl 2 jne Try3 mov ah Pot2.PotMask call ReadPots lea bx Pot2 mov ax bp call Normalize jmp GotPot ; Test for DL=3 here (read and normalize pot 3). Try3: cmp dl 3 jne BadPot mov ah Pot3.PotMask call ReadPots lea bx Pot3 mov ax di call Normalize jmp GotPot ; Bad value in DL if we drop to this point. The standard game card ; only supports four pots. BadPot: sub ax ax ;Pot not available return zero. GotPot: pop bp pop di pop si pop dx pop cx pop ds pop bx iret ReadPot endp assume ds:nothing ;---------------------------------------------------------------------------- ; ; ReadRaw- On entry DL contains a pot number to read. ; Read that pot and return the unnormalized result in AX. assume ds:cseg ReadRaw proc near ;;;;;;;;;; push bx ;Already on stack. push ds push cx push dx push si push di push bp mov bx cseg mov ds bx ; This code is almost identical to the ReadPot code. The only difference ; is that we don't bother normalizing the result and (of course) we return ; the value in AX rather than AL. cmp dl 0 jne Try1 mov ah Pot0.PotMask call ReadPots mov ax si jmp GotPot Try1: cmp dl 1 jne Try2 mov ah Pot1.PotMask call ReadPots mov ax bx jmp GotPot Try2: cmp dl 2 jne Try3 mov ah Pot2.PotMask call ReadPots mov ax bp jmp GotPot Try3: cmp dl 3 jne BadPot mov ah Pot3.PotMask call ReadPots mov ax di jmp GotPot BadPot: sub ax ax ;Pot not available return zero. GotPot: pop bp pop di pop si pop dx pop cx pop ds pop bx iret ReadRaw endp assume ds:nothing ;---------------------------------------------------------------------------- ; Read4Pots- Reads pots zero one two and three returning their ; values in AL AH DL and DH. ; ; On entry AL contains the pot mask to select which pots ; we should read (bit 0=1 for pot 0 bit 1=1 for pot 1 etc). Read4Pots proc near ;;;;;;;;;;; push bx ;Already on stack push ds push cx push si push di push bp mov dx cseg mov ds dx mov ah al call ReadPots push bx ;Save pot 1 reading. mov ax si ;Get pot 0 reading. lea bx Pot0 ;Point bx at pot0 vars. call Normalize ;Normalize. mov cl al ;Save for later. pop ax ;Retreive pot 1 reading. lea bx Pot1 call Normalize mov ch al ;Save normalized value. mov ax bp lea bx Pot2 call Normalize mov dl al ;Pot 2 value. mov ax di lea bx Pot3 call Normalize mov dh al ;Pot 3 value. mov ax cx ;Pots 0 and 1. pop bp pop di pop si pop cx pop ds pop bx iret Read4Pots endp ;---------------------------------------------------------------------------- ; CalPot- Calibrate the pot specified by DL. On entry AL contains ; the minimum pot value (it better be less than 256!) BX ; contains the maximum pot value and CX contains the centered ; pot value. assume ds:cseg CalPot proc near pop bx ;Retrieve maximum value push ds push si mov si cseg mov ds si ; Sanity check on parameters sort them in ascending order: mov ah 0 cmp bx cx ;Make sure center < max ja GoodMax xchg bx cx GoodMax: cmp ax cx ;Make sure min < center. jb GoodMin ; (note: may make center<max). xchg ax cx GoodMin: cmp cx bx ;Again be sure center < max. jb GoodCenter xchg cx bx GoodCenter: ; Okay figure out who were supposed to calibrate: lea si Pot0 cmp dl 1 jb DoCal ;Branch if this is pot 0 lea si Pot1 je DoCal ;Branch if this is pot 1 lea si Pot2 cmp dl 3 jb DoCal ;Branch if this is pot 2 jne CalDone ;Branch if not pot 3 lea si Pot3 DoCal: mov [si].Pot.min ax ;Store away the minimum mov [si].Pot.max bx ; maximum and mov [si].Pot.center cx ; centered values. mov [si].Pot.DidCal 1 ;Note we've cal'd this pot. CalDone: pop si pop ds iret CalPot endp assume ds:nothing ;---------------------------------------------------------------------------- ; TestCal- Just checks to see if the pot specified by DL has already ; been calibrated. assume ds:cseg TestCal proc near ;;;;;;;; push bx ;Already on stack push ds mov bx cseg mov ds bx sub ax ax ;Assume no calibration (also zeros AH) lea bx Pot0 ;Get the address of the specified cmp dl 1 ; pot's data structure into the jb GetCal ; BX register. lea bx Pot1 je GetCal lea bx Pot2 cmp dl 3 jb GetCal jne BadCal lea bx Pot3 GetCal: mov al [bx].Pot.DidCal BadCal: pop ds pop bx iret TestCal endp assume ds:nothing ;---------------------------------------------------------------------------- ; ; ReadSw- Reads the switch whose switch number appears in DL. ReadSw proc near ;;;;;;; push bx ;Already on stack push cx sub ax ax ;Assume no such switch. cmp dl 3 ;Return if the switch number is ja NotDown ; greater than three. mov cl dl ;Save switch to read. add cl 4 ;Move from position four down to zero. mov dx JoyPort in al dx ;Read the switches. shr al cl ;Move desired switch bit into bit 0. xor al 1 ;Invert so sw down=1. and ax 1 ;Remove other junk bits. NotDown: pop cx pop bx iret ReadSw endp ;---------------------------------------------------------------------------- ; ; Read16Sw- Reads all four switches and returns their values in AX. Read16Sw proc near ;;;;;;;; push bx ;Already on stack mov dx JoyPort in al dx shr al 4 xor al 0Fh ;Invert all switches. and ax 0Fh ;Set other bits to zero. pop bx iret Read16Sw endp ;**************************************************************************** ; ; MyInt15- Patch for the BIOS INT 15 routine to control reading the ; joystick. MyInt15 proc far push bx cmp ah 84h ;Joystick code? je DoJoystick OtherInt15: pop bx jmp cs:Int15Vect DoJoystick: mov bh 0 mov bl dh cmp bl 80h jae VendorCalls cmp bx JmpSize jae OtherInt15 shl bx 1 jmp wp cs:jmptable[bx] jmptable word BIOS word ReadPot Read4Pots CalPot TestCal word ReadRaw OtherInt15 OtherInt15 word ReadSw Read16Sw JmpSize = ($-jmptable)/2 ; Handle vendor specific calls here. VendorCalls: je RemoveDriver cmp bl 81h je TestPresence pop bx jmp cs:Int15Vect ; TestPresence- Returns zero in AX and a pointer to the ID string in ES:BX TestPresence: pop bx ;Get old value off stack. sub ax ax mov bx cseg mov es bx lea bx IDString iret ; RemoveDriver- If there are no other drivers loaded after this one in ; memory disconnect it and remove it from memory. RemoveDriver: push ds push es push ax push dx mov dx cseg mov ds dx ; See if we're the last routine patched into INT 15h mov ax 3515h int 21h cmp bx offset MyInt15 jne CantRemove mov bx es cmp bx wp seg MyInt15 jne CantRemove mov ax PSP ;Free the memory we're in mov es ax push es mov ax es:[2ch] ;First free env block. mov es ax mov ah 49h int 21h pop es ;Now free program space. mov ah 49h int 21h lds dx Int15Vect ;Restore previous int vect. mov ax 2515h int 21h CantRemove: pop dx pop ax pop es pop ds pop bx iret MyInt15 endp cseg ends Initialize segment para public 'INIT' assume cs:Initialize ds:cseg Main proc mov ax cseg ;Get ptr to vars segment mov es ax mov es:PSP ds ;Save PSP value away mov ds ax mov ax zzzzzzseg mov es ax mov cx 100h meminit2 print byte " Standard Game Device Interface driver" cr lf byte " PC Compatible Game Adapter Cards" cr lf byte " Written by Randall Hyde" cr lf byte cr lf byte cr lf byte "'SGDI REMOVE' removes the driver from memory" cr lf byte lf byte 0 mov ax 1 argv ;If no parameters empty str. stricmpl byte "REMOVE" 0 jne NoRmv mov dh 81h ;Remove opcode. mov ax 84ffh int 15h ;See if we're already loaded. test ax ax ;Get a zero back? jz Installed print byte "SGDI driver is not present in memory REMOVE " byte "command ignored." cr lf 0 mov ax 4c01h ;Exit to DOS. int 21h Installed: mov ax 8400h mov dh 80h ;Remove call int 15h mov ax 8400h mov dh 81h ;TestPresence call int 15h cmp ax 0 je NotRemoved print byte "Successfully removed SGDI driver from memory." byte cr lf 0 mov ax 4c01h ;Exit to DOS. int 21h NotRemoved: print byte "SGDI driver is still present in memory." cr lf 0 mov ax 4c01h ;Exit to DOS. int 21h ; Okay Patch INT 15 and go TSR at this point. NoRmv: mov ax 3515h int 21h mov wp Int15Vect bx mov wp Int15Vect+2 es mov dx cseg mov ds dx mov dx offset MyInt15 mov ax 2515h int 21h mov dx cseg mov ds dx mov dx seg Initialize sub dx ds:psp add dx 2 mov ax 3100h ;Do TSR int 21h Main endp Initialize ends sseg segment para stack 'stack' word 128 dup (0) endstk word ? sseg ends zzzzzzseg segment para public 'zzzzzzseg' byte 16 dup (0) zzzzzzseg ends end Main
The following program makes several different types of calls to an SGDI driver. You can use this code to test out an SGDI TSR:
.xlist
include stdlib.a
includelib stdlib.lib
.list
cseg segment para public 'code'
assume cs:cseg
ds:nothing
MinVal0 word ?
MinVal1 word ?
MaxVal0 word ?
MaxVal1 word ?
; Wait4Button- Waits until the user presses and releases a button.
Wait4Button proc near
push ax
push dx
push cx
W4BLp: mov ah
84h
mov dx
900h ;Read the L.O. 16 buttons.
int 15h
cmp ax
0 ;Any button down? If not
je W4BLp ; loop until this is so.
xor cx
cx ;Debouncing delay loop.
Delay: loop Delay
W4nBLp: mov ah
84h ;Now wait until the user releases
mov dx
900h ; all buttons
int 15h
cmp ax
0
jne W4nBLp
Delay2: loop Delay2
pop cx
pop dx
pop ax
ret
Wait4Button endp
Main proc
print
byte "SGDI Test Program."
cr
lf
byte "Written by Randall Hyde"
cr
lf
lf
byte "Press any key to continue"
cr
lf
0
getc
mov ah
84h
mov dh
4 ;Test presence call.
int 15h
cmp ax
0 ;See if there
je MainLoop0
print
byte "No SGDI driver present in memory."
cr
lf
0
jmp Quit
MainLoop0: print
byte "BIOS: "
0
; Okay
read the switches and raw pot values using the BIOS compatible calls.
mov ah
84h
mov dx
0 ;BIOS compat. read switches.
int 15h
puth ;Output switch values.
mov al
' '
putc
mov ah
84h ;BIOS compat. read pots.
mov dx
1
int 15h
putw
mov al
' '
putc
mov ax
bx
putw
mov al
' '
putc
mov ax
cx
putw
mov al
' '
putc
mov ax
dx
putw
putcr
mov ah
1 ;Repeat until key press.
int 16h
je MainLoop0
getc
; Read the minimum and maximum values for each pot from the user so we
; can calibrate the pots.
print
byte cr
lf
lf
lf
byte "Move joystick to upper left corner and press "
byte "any button."
cr
lf
0
call Wait4Button
mov ah
84h
mov dx
1 ;Read Raw Values
int 15h
mov MinVal0
ax
mov MinVal1
bx
print
byte cr
lf
byte "Move the joystick to the lower right corner "
byte "and press any button"
cr
lf
0
call Wait4Button
mov ah
84h
mov dx
1 ;Read Raw Values
int 15h
mov MaxVal0
ax
mov MaxVal1
bx
; Calibrate the pots.
mov ax
MinVal0 ;Will be eight bits or less.
mov bx
MaxVal0
mov cx
bx ;Compute centered value as the
add cx
ax ; average of these two (this is
shr cx
1 ; dangerous
but usually works!)
mov ah
84h
mov dx
300h ;Calibrate pot 0
int 15h
mov ax
MinVal1 ;Will be eight bits or less.
mov bx
MaxVal1
mov cx
bx ;Compute centered value as the
add cx
ax ; average of these two (this is
shr cx
1 ; dangerous
but usually works!)
mov ah
84h
mov dx
301h ;Calibrate pot 1
int 15h
MainLoop1: print
byte "ReadSw: "
0
; Okay
read the switches and raw pot values using the BIOS compatible calls.
mov ah
84h
mov dx
800h ;Read switch zero.
int 15h
or al
'0'
putc
mov ah
84h
mov dx
801h ;Read switch one.
int 15h
or al
'0'
putc
mov ah
84h
mov dx
802h ;Read switch two.
int 15h
or al
'0'
putc
mov ah
84h
mov dx
803h ;Read switch three.
int 15h
or al
'0'
putc
mov ah
84h
mov dx
804h ;Read switch four
int 15h
or al
'0'
putc
mov ah
84h
mov dx
805h ;Read switch five.
int 15h
or al
'0'
putc
mov ah
84h
mov dx
806h ;Read switch six.
int 15h
or al
'0'
putc
mov ah
84h
mov dx
807h ;Read switch seven.
int 15h ;We won't bother with
or al
'0' ; any more switches.
putc
mov al
' '
putc
mov ah
84h
mov dh
9 ;Read all 16 switches.
int 15h
putw
print
byte " Pots: "
0
mov ax
8403h ;Read joystick pots.
mov dx
200h ;Read four pots.
int 15h
puth
mov al
' '
putc
mov al
ah
puth
mov al
' '
putc
mov ah
84h
mov dx
503h ;Raw read
pot 3.
int 15h
putw
putcr
mov ah
1 ;Repeat until key press.
int 16h
je MainLoop1
getc
Quit: ExitPgm ;DOS macro to quit program.
Main endp
cseg ends
sseg segment para stack 'stack'
stk byte 1024 dup ("stack ")
sseg ends
zzzzzzseg segment para public 'zzzzzz'
LastBytes byte 16 dup (?)
zzzzzzseg ends
end Main
|
Table of Content | Chapter Twenty Four (Part 3) |
Chapter Twenty Four: The PC Game
Adapter (Part 2)
29 SEP 1996