The Art of
ASSEMBLY LANGUAGE PROGRAMMING

Chapter Twenty Four (Part 3)

Table of Content

Chapter Twenty Five  

CHAPTER TWENTY FOUR:
THE PC GAME ADAPTER (Part 4)
24.7 - Patching Existing Games
24.7 Patching Existing Games

Maybe you're not quite ready to write the next million dollar game. Perhaps you'd like to get a little more enjoyment out of the games you already own. Well, this section will provide a practical application of a semiresident program that patches the Lucas Arts' XWing (Star Wars simulation) game. This program patches the XWing game to take advantage of the special features found on the CH Products' FlightStick Pro. In particular, it lets you use the throttle pot on the FSP to control the speed of the spacecraft. It also lets you program each of the buttons with up to four strings of eight characters each.

To describe how you can patch an existing game, a short description of how this patch was developed is in order. The FSPXW patch was developed by using the Soft-ICE' debugging tool. This program lets you set a breakpoint whenever an 80386 or later processor accesses a specific I/O port. Setting a breakpoint at I/O address 201h while running the xwing.exe file stopped the XWing program when it decided to read the analog and switch inputs. Disassembly of the surrounding code produced complete joystick and button read routines. After locating these routines, it was easy enough to write a program to search through memory for the code and patch in jumps to code in the FSPXW patch program.

Note that the original joystick code inside XWing works perfectly fine with the FPS. The only reason for patching into the joystick code is so our code can read the throttle every how and then and take appropriate action.

The button routines were another story altogether. The FSPXW patch needs to take control of XWing's button routines because the user of FSPXW might want to redefine a button recognized by XWing for some other purpose. Therefore, whenever XWing calls its button routine, control transfers to the button routine inside FSPXW that decides whether to pass real button information back to XWing or to fake buttons in the up position because those buttons are redefined to other functions. By default (unless you change the source code, the buttons have the following programming:

The programming of the cooley switch demonstrates an interesting feature of the FSPXW patch: you can program up to four different strings on each button. The first time you press a button, FSPXW emits the first string, the second time you press a button it emits the second string, then the third, and finally the fourth. If the string is empty, the FSPXW string skips it. The FSPXW patch uses the cooley switch to select the cockpit views. Pressing the cooley switch forward displays the forward view. Pulling the cooley switch backwards presents the rear view. However, the XWing game provides three left and right views. Pushing the cooley switch to the left or right once displays the 45 degree view. Pressing it a second time presents the 90 degree view. Pressing it to the left or right a third time provides the 135 degree view. The following diagram shows the default programming on the cooley switch:

One word of caution concerning this patch: it only works with the basic XWing game. It does not support the add-on modules (Imperial Pursuit, B-Wing, Tie Fighter, etc.). Furthermore, this patch assumes that the basic XWing code has not changed over the years. It could be that a recent release of the XWing game uses new joystick routines and the code associated with this application will not be able to locate or patch those new routines. This patch will detect such a situation and will not patch XWing if this is the case. You must have sufficient free RAM for this patch, XWing, and anything else you have loaded into memory at the same time (the exact amount of RAM XWing needs depends upon the features you've installed, a fully installed system requires slightly more than 610K free).

Without further ado, here's the FSPXW code:

                .286
                page    58, 132
                name    FSPXW
                title   FSPXW (Flightstick Pro driver for XWING).
                subttl  Copyright (C) 1994 Randall Hyde.


; FSPXW.EXE
;
;       Usage:
;               FSPXW
;
; This program executes the XWING.EXE program and patches it to use the
; Flightstick Pro.


byp             textequ <byte ptr>
wp              textequ <word ptr>


cseg            segment para public 'CODE'
cseg            ends

sseg            segment para stack 'STACK'
sseg            ends

zzzzzzseg       segment para public 'zzzzzzseg'
zzzzzzseg       ends

                include         stdlib.a
                includelib      stdlib.lib
                matchfuncs

                ifndef  debug
Installation    segment para public 'Install'
Installation    ends
                endif

CSEG            segment para public 'CODE'
                assume  cs:cseg, ds:nothing

; Timer interrupt vector

Int1CVect       dword   ?


; PSP-  Program Segment Prefix. Needed to free up memory before running
;       the real application program.

PSP             word    0



; Program Loading data structures (for DOS).

ExecStruct              word    0       ;Use parent's Environment blk.
                dword   CmdLine         ;For the cmd ln parms.
                dword   DfltFCB
                dword   DfltFCB
LoadSSSP        dword   ?
LoadCSIP        dword   ?
PgmName         dword   Pgm


; Variables for the throttle pot.
; LastThrottle contains the character last sent (so we only send one copy).
; ThrtlCntDn counts the number of times the throttle routine gets called.

LastThrottle    byte    0
ThrtlCntDn      byte    10


; Button Mask- Used to mask out the programmed buttons when the game
; reads the real buttons.

ButtonMask      byte    0f0h


; The following variables allow the user to reprogram the buttons.

KeyRdf          struct
Ptrs            word    ?       ;The PTRx fields point at the
ptr2            word    ?       ; four possible strings of 8 chars
ptr3            word    ?       ; each. Each button press cycles
ptr4            word    ?       ; through these strings.
Index           word    ?       ;Index to next string to output.
Cnt             word    ?
Pgmd            word    ?       ;Flag = 0 if not redefined.
KeyRdf          ends


; Left codes are output if the cooley switch is pressed to the left.
; Note that the strings ares actually zero terminated strings of words.

Left            KeyRdf  <Left1, Left2, Left3, Left4, 0, 6, 1>
Left1           word    '7', 0
Left2           word    '4', 0
Left3           word    '1', 0
Left4           word    0

; Right codes are output if the cooley switch is pressed to the Right.

Right           KeyRdf  <Right1, Right2, Right3, Right4, 0, 6, 1>
Right1          word    '9', 0
Right2          word    '6', 0
Right3          word    '3', 0
Right4          word    0

; Up codes are output if the cooley switch is pressed Up.

Up              KeyRdf  <Up1, Up2, Up3, Up4, 0, 2, 1>
Up1             word    '8', 0
Up2             word    0
Up3             word    0
Up4             word    0

; DownKey codes are output if the cooley switch is pressed Down.

Down            KeyRdf  <Down1, Down2, Down3, Down4, 0, 2, 1>
Down1           word    '2', 0
Down2           word    0
Down3           word    0
Down4           word    0

; Sw0 codes are output if the user pulls the trigger.(This switch is not
; redefined.)

Sw0             KeyRdf  <Sw01, Sw02, Sw03, Sw04, 0, 0, 0>
Sw01            word    0
Sw02            word    0
Sw03            word    0
Sw04            word    0


; Sw1 codes are output if the user presses Sw1 (the left button
; if the user hasn't swapped the left and right buttons). Not Redefined.

Sw1             KeyRdf  <Sw11, Sw12, Sw13, Sw14, 0, 0, 0>
Sw11            word    0
Sw12            word    0
Sw13            word    0
Sw14            word    0

; Sw2 codes are output if the user presses Sw2 (the middle button).

Sw2             KeyRdf  <Sw21, Sw22, Sw23, Sw24, 0, 2, 1>
Sw21            word    'w', 0
Sw22            word    0
Sw23            word    0
Sw24            word    0

; Sw3 codes are output if the user presses Sw3 (the right button
; if the user hasn't swapped the left and right buttons).

Sw3             KeyRdf  <Sw31, Sw32, Sw33, Sw34, 0, 0, 0>
Sw31            word    0
Sw32            word    0
Sw33            word    0
Sw34            word    0

; Switch status buttons:

CurSw           byte    0
LastSw          byte    0


;****************************************************************************
; FSPXW patch begins here. This is the memory resident part. Only put code
; which which has to be present at run-time or needs to be resident after
; freeing up memory.
;****************************************************************************

Main            proc
                mov     cs:PSP, ds
                mov     ax, cseg                ;Get ptr to vars segment
                mov     ds, ax


; Get the current INT 1Ch interrupt vector:

                mov     ax, 351ch
                int     21h
                mov     wp Int1CVect, bx
                mov     wp Int1CVect+2, es

; The following call to MEMINIT assumes no error occurs. If it does,
; we're hosed anyway.

                mov     ax, zzzzzzseg
                mov     es, ax
                mov     cx, 1024/16
                meminit2


; Do some initialization before running the game. These are calls to the
; initialization code which gets dumped before actually running XWING.

                call    far ptr ChkBIOS15
                call    far ptr Identify
                call    far ptr Calibrate

; If any switches were programmed, remove those switches from the
; ButtonMask:

                mov     al, 0f0h                ;Assume all buttons are okay.
                cmp     sw0.pgmd, 0
                je      Sw0NotPgmd
                and     al, 0e0h                ;Remove sw0 from contention.
Sw0NotPgmd:

                cmp     sw1.pgmd, 0
                je      Sw1NotPgmd
                and     al, 0d0h                ;Remove Sw1 from contention.
Sw1NotPgmd:

                cmp     sw2.pgmd, 0
                je      Sw2NotPgmd
                and     al, 0b0h                ;Remove Sw2 from contention.
Sw2NotPgmd:

                cmp     sw3.pgmd, 0
                je      Sw3NotPgmd
                and     al, 070h                ;Remove Sw3 from contention.
Sw3NotPgmd:
                mov     ButtonMask, al          ;Save result as button mask

; Now, free up memory from ZZZZZZSEG on to make room for XWING.
; Note: Absolutely no calls to UCR Standard Library routines from
; this point forward! (ExitPgm is okay, it's just a macro which calls DOS.)
; Note that after the execution of this code, none of the code & data
; from zzzzzzseg on is valid.

                mov     bx, zzzzzzseg
                sub     bx, PSP
                inc     bx
                mov     es, PSP
                mov     ah, 4ah
                int     21h
                jnc     GoodRealloc
                print
                byte    "Memory allocation error."
                byte    cr,lf,0
                jmp     Quit

GoodRealloc:

; Now load the XWING program into memory:

                mov     bx, seg ExecStruct
                mov     es, bx
                mov     bx, offset ExecStruc    ;Ptr to program record.
                lds     dx, PgmName
                mov     ax, 4b01h               ;Load, do not exec, pgm
                int     21h
                jc      Quit                    ;If error loading file.

; Search for the joystick code in memory:

                mov     si, zzzzzzseg
                mov     ds, si
                xor     si, si

                mov     di, cs
                mov     es, di
                mov     di, offset JoyStickCode
                mov     cx, JoyLength
                call    FindCode
                jc      Quit                    ;If didn't find joystick code.


; Patch the XWING joystick code here

                mov     byp ds:[si], 09ah       ;Far call
                mov     wp ds:[si+1], offset ReadGame
                mov     wp ds:[si+3], cs

; Find the Button code here.

                mov     si, zzzzzzseg
                mov     ds, si
                xor     si, si

                mov     di, cs
                mov     es, di
                mov     di, offset ReadSwCode
                mov     cx, ButtonLength
                call    FindCode
                jc      Quit

; Patch the button code here.

                mov     byp ds:[si], 9ah
                mov     wp ds:[si+1], offset ReadButtons
                mov     wp ds:[si+3], cs
                mov     byp ds:[si+5], 90h      ;NOP.


; Patch in our timer interrupt handler:

                mov     ax, 251ch
                mov     dx, seg MyInt1C
                mov     ds, dx
                mov     dx, offset MyInt1C
                int     21h


; Okay, start the XWING.EXE program running

                mov     ah, 62h                 ;Get PSP
                int     21h
                mov     ds, bx
                mov     es, bx
                mov     wp ds:[10], offset Quit
                mov     wp ds:[12], cs
                mov     ss, wp cseg:LoadSSSP+2
                mov     sp, wp cseg:LoadSSSP
                jmp     dword ptr cseg:LoadCSIP


Quit:           lds     dx, cs:Int1CVect        ;Restore timer vector.
                mov     ax, 251ch
                int     21h
                ExitPgm

Main            endp

;****************************************************************************
;
; ReadGame-     This routine gets called whenever XWing reads the joystick.
;               On every 10th call it will read the throttle pot and send
;               appropriate characters to the type ahead buffer, if
;               necessary.

                assume  ds:nothing
ReadGame        proc    far
                dec     cs:ThrtlCntDn           ;Only do this each 10th time
                jne     SkipThrottle            ; XWING calls the joystick
                mov     cs:ThrtlCntDn, 10       ; routine.

                push    ax
                push    bx                      ;No need to save bp, dx, or cx as
                push    di                      ; XWING preserves these.

                mov     ah, 84h
                mov     dx, 103h                ;Read the throttle pot
                int     15h

; Convert the value returned by the pot routine into the four characters
; 0..63:"\", 64..127:"[", 128..191:"]", 192..255:<bs>, to denote zero, 1/3,
; 2/3, and full power, respectively.

                mov     dl, al
                mov     ax, "\"         ;Zero power
                cmp     dl, 192
                jae     SetPower
                mov     ax, "["         ;1/3 power.
                cmp     dl, 128
                jae     SetPower
                mov     ax, "]"         ;2/3 power.
                cmp     dl, 64
                jae     SetPower
                mov     ax, 8           ;BS, full power.
SetPower:       cmp     al, cs:LastThrottle
                je      SkipPIB
                mov     cs:LastThrottle, al
                call    PutInBuffer

SkipPIB:                pop     di
                pop     bx
                pop     ax
SkipThrottle:   neg     bx              ;XWING returns data in these registers.
                neg     di              ;We patched the NEG and STI instrs
                sti                     ; so do that here.
                ret
ReadGame        endp

                assume  ds:nothing
ReadButtons     proc    far
                mov     ah, 84h
                mov     dx, 0
                int     15h
                not     al
                and     al, ButtonMask  ;Turn off pgmd buttons.
                ret
ReadButtons     endp



; MyInt1C- Called every 1/18th second. Reads switches and decides if it
; should shove some characters into the type ahead buffer.

                assume  ds:cseg
MyInt1c         proc    far
                push    ds
                push    ax
                push    bx
                push    dx
                mov     ax, cseg
                mov     ds, ax

                mov     al, CurSw
                mov     LastSw, al

                mov     dx, 900h        ;Read the 8 switches.
                mov     ah, 84h
                int     15h

                mov     CurSw, al
                xor     al, LastSw      ;See if any changes
                jz      NoChanges
                and     al, CurSw       ;See if sw just went down.
                jz      NoChanges


; If a switch has just gone down, output an appropriate set of scan codes
; for it, if that key is active. Note that pressing *any* key will reset
; all the other key indexes.

                test    al, 1           ;See if Sw0 (trigger) was pulled.
                jz      NoSw0
                cmp     Sw0.Pgmd, 0
                je      NoChanges
                mov     ax, 0
                mov     Left.Index, ax  ;Reset the key indexes for all keys
                mov     Right.Index, ax ; except SW0.
                mov     Up.Index, ax
                mov     Down.Index, ax
                mov     Sw1.Index, ax
                mov     Sw2.Index, ax
                mov     Sw3.Index, ax
                mov     bx, Sw0.Index
                mov     ax, Sw0.Index
                mov     bx, Sw0.Ptrs[bx]
                add     ax, 2
                cmp     ax, Sw0.Cnt
                jb      SetSw0
                mov     ax, 0
SetSw0:         mov     Sw0.Index, ax
                call    PutStrInBuf
                jmp     NoChanges



NoSw0:          test    al, 2           ;See if Sw1 (left sw) was pressed.
                jz      NoSw1
                cmp     Sw1.Pgmd, 0
                je      NoChanges
                mov     ax, 0
                mov     Left.Index, ax  ;Reset the key indexes for all keys
                mov     Right.Index, ax ; except Sw1.
                mov     Up.Index, ax
                mov     Down.Index, ax
                mov     Sw0.Index, ax
                mov     Sw2.Index, ax
                mov     Sw3.Index, ax
                mov     bx, Sw1.Index
                mov     ax, Sw1.Index
                mov     bx, Sw1.Ptrs[bx]
                add     ax, 2
                cmp     ax, Sw1.Cnt
                jb      SetSw1
                mov     ax, 0
SetSw1:         mov     Sw1.Index, ax
                call    PutStrInBuf
                jmp     NoChanges


NoSw1:          test    al, 4           ;See if Sw2 (middle sw) was pressed.
                jz      NoSw2
                cmp     Sw2.Pgmd, 0
                je      NoChanges
                mov     ax, 0
                mov     Left.Index, ax  ;Reset the key indexes for all keys
                mov     Right.Index, ax ; except Sw2.
                mov     Up.Index, ax
                mov     Down.Index, ax
                mov     Sw0.Index, ax
                mov     Sw1.Index, ax
                mov     Sw3.Index, ax
                mov     bx, Sw2.Index
                mov     ax, Sw2.Index
                mov     bx, Sw2.Ptrs[bx]
                add     ax, 2
                cmp     ax, Sw2.Cnt
                jb      SetSw2
                mov     ax, 0
SetSw2:         mov     Sw2.Index, ax
                call    PutStrInBuf
                jmp     NoChanges


NoSw2:          test    al, 8           ;See if Sw3 (right sw) was pressed.
                jz      NoSw3
                cmp     Sw3.Pgmd, 0
                je      NoChanges
                mov     ax, 0
                mov     Left.Index, ax  ;Reset the key indexes for all keys
                mov     Right.Index, ax ; except Sw3.
                mov     Up.Index, ax
                mov     Down.Index, ax
                mov     Sw0.Index, ax
                mov     Sw1.Index, ax
                mov     Sw2.Index, ax
                mov     bx, Sw3.Index
                mov     ax, Sw3.Index
                mov     bx, Sw3.Ptrs[bx]
                add     ax, 2
                cmp     ax, Sw3.Cnt
                jb      SetSw3
                mov     ax, 0
SetSw3:         mov     Sw3.Index, ax
                call    PutStrInBuf
                jmp     NoChanges


NoSw3:          test    al, 10h         ;See if Cooly was pressed upwards.
                jz      NoUp
                cmp     Up.Pgmd, 0
                je      NoChanges
                mov     ax, 0
                mov     Right.Index, ax ;Reset all but Up.
                mov     Left.Index, ax
                mov     Down.Index, ax
                mov     Sw0.Index, ax
                mov     Sw1.Index, ax
                mov     Sw2.Index, ax
                mov     Sw3.Index, ax
                mov     bx, Up.Index
                mov     ax, Up.Index
                mov     bx, Up.Ptrs[bx]
                add     ax, 2
                cmp     ax, Up.Cnt
                jb      SetUp
                mov     ax, 0
SetUp:          mov     Up.Index, ax
                call    PutStrInBuf
                jmp     NoChanges


NoUp:           test    al, 20h         ;See if Cooley was pressed left.
                jz      NoLeft
                cmp     Left.Pgmd, 0
                je      NoChanges
                mov     ax, 0
                mov     Right.Index, ax ;Reset all but Left.
                mov     Up.Index, ax
                mov     Down.Index, ax
                mov     Sw0.Index, ax
                mov     Sw1.Index, ax
                mov     Sw2.Index, ax
                mov     Sw3.Index, ax
                mov     bx, Left.Index
                mov     ax, Left.Index
                mov     bx, Left.Ptrs[bx]
                add     ax, 2
                cmp     ax, Left.Cnt
                jb      SetLeft
                mov     ax, 0
SetLeft:        mov     Left.Index, ax
                call    PutStrInBuf
                jmp     NoChanges


NoLeft:         test    al, 40h         ;See if Cooley was pressed Right
                jz      NoRight
                cmp     Right.Pgmd, 0
                je      NoChanges
                mov     ax, 0
                mov     Left.Index, ax  ;Reset all but Right.
                mov     Up.Index, ax
                mov     Down.Index, ax
                mov     Sw0.Index, ax
                mov     Sw1.Index, ax
                mov     Sw2.Index, ax
                mov     Sw3.Index, ax
                mov     bx, Right.Index
                mov     ax, Right.Index
                mov     bx, Right.Ptrs[bx]
                add     ax, 2
                cmp     ax, Right.Cnt
                jb      SetRight
                mov     ax, 0
SetRight:       mov     Right.Index, ax
                call    PutStrInBuf
                jmp     NoChanges


NoRight:        test    al, 80h         ;See if Cooly was pressed Downward.
                jz      NoChanges
                cmp     Down.Pgmd, 0
                je      NoChanges
                mov     ax, 0
                mov     Left.Index, ax  ;Reset all but Down.
                mov     Up.Index, ax
                mov     Right.Index, ax
                mov     Sw0.Index, ax
                mov     Sw1.Index, ax
                mov     Sw2.Index, ax
                mov     Sw3.Index, ax
                mov     bx, Down.Index
                mov     ax, Down.Index
                mov     bx, Down.Ptrs[bx]
                add     ax, 2
                cmp     ax, Down.Cnt
                jb      SetDown
                mov     ax, 0
SetDown:        mov     Down.Index, ax
                call    PutStrInBuf

NoChanges:      pop     dx
                pop     bx
                pop     ax
                pop     ds
                jmp     cs:Int1CVect
MyInt1c         endp
                assume  ds:nothing

; PutStrInBuf- BX points at a zero terminated string of words.
;                Output each word by calling PutInBuffer.

PutStrInBuf     proc    near
                push    ax
                push    bx
PutLoop:        mov     ax, [bx]
                test    ax, ax
                jz      PutDone
                call    PutInBuffer
                add     bx, 2
                jmp     PutLoop

PutDone:        pop     bx
                pop     ax
                ret
PutStrInBuf     endp

; PutInBuffer- Outputs character and scan code in AX to the type ahead
; buffer.

                assume  ds:nothing
KbdHead         equ     word ptr ds:[1ah]
KbdTail         equ     word ptr ds:[1ch]
KbdBuffer       equ     word ptr ds:[1eh]
EndKbd          equ     3eh
Buffer          equ     1eh

PutInBuffer     proc    near
                push    ds
                push    bx
                mov     bx, 40h
                mov     ds, bx
                pushf
                cli                     ;This is a critical region!
                mov     bx, KbdTail     ;Get ptr to end of type
                inc     bx              ; ahead buffer and make room
                inc     bx              ; for this character.
                cmp     bx, buffer+32   ;At physical end of buffer?
                jb      NoWrap
                mov     bx, buffer      ;Wrap back to 1eH if at end.
;
NoWrap:         cmp     bx, KbdHead     ;Buffer overrun?
                je      PIBDone
                xchg    KbdTail, bx     ;Set new, get old, ptrs.
                mov     ds:[bx], ax     ;Output AX to old location.
PIBDone:        popf                    ;Restore interrupts
                pop     bx
                pop     ds
                ret
PutInBuffer     endp



;****************************************************************************
;
; FindCode: On entry, ES:DI points at some code in *this* program which
;        appears in the ATP game. DS:SI points at a block of memory
;        in the XWing game. FindCode searches through memory to find the
;        suspect piece of code and returns DS:SI pointing at the start of
;        that code. This code assumes that it *will* find the code!
;        It returns the carry clear if it finds it, set if it doesn't.

FindCode        proc    near
                push    ax
                push    bx
                push    dx

DoCmp:          mov     dx, 1000h
CmpLoop:        push    di              ;Save ptr to compare code.
                push    si              ;Save ptr to start of string.
                push    cx              ;Save count.
        repe    cmpsb
                pop     cx
                pop     si
                pop     di
                je      FoundCode
                inc     si
                dec     dx
                jne     CmpLoop
                sub     si, 1000h
                mov     ax, ds
                inc     ah
                mov     ds, ax
                cmp     ax, 9000h
                jb      DoCmp

                pop     dx
                pop     bx
                pop     ax
                stc
                ret

FoundCode:      pop     dx
                pop     bx
                pop     ax
                clc
                ret
FindCode        endp


;****************************************************************************
;
; Joystick and button routines which appear in XWing game. This code is
; really data as the INT 21h patch code searches through memory for this code
; after loading a file from disk.



JoyStickCode    proc    near
                sti
                neg     bx
                neg     di
                pop     bp
                pop     dx
                pop     cx
                ret
                mov     bp, bx
                in      al, dx
                mov     bl, al
                not     al
                and     al, ah
                jnz     $+11h
                in      al, dx
JoyStickCode    endp
EndJSC:

JoyLength       =       EndJSC-JoyStickCode

ReadSwCode      proc
                mov     dx, 201h
                in      al, dx
                xor     al, 0ffh
                and     ax, 0f0h
ReadSwCode      endp
EndRSC:

ButtonLength    =       EndRSC-ReadSwCode

cseg            ends


Installation    segment

; Move these things here so they do not consume too much space in the
; resident part of the patch.

DfltFCB         byte    3," ",0,0,0,0,0
CmdLine         byte    2, " ", 0dh, 126 dup (" ")      ;Cmd line for program
Pgm             byte    "XWING.EXE",0
                byte    128 dup (?)                     ;For user's name


; ChkBIOS15- Checks to see if the INT 15 driver for FSPro is present in memory.

ChkBIOS15       proc    far
                mov     ah, 84h
                mov     dx, 8100h
                int     15h
                mov     di, bx
                strcmpl
                byte    "CH Products:Flightstick Pro",0
                jne     NoDriverLoaded
                ret

NoDriverLoaded: 
                print
                byte    "CH Products SGDI driver for Flightstick Pro is not "
                byte    "loaded into memory.",cr,lf
                byte    "Please run FSPSGDI before running this program."
                byte    cr,lf,0
                exitpgm

ChkBIOS15       endp


;****************************************************************************
;
; Identify-             Prints a sign-on message.

                assume  ds:nothing
Identify        proc    far

; Print a welcome string. Note that the string "VersionStr" will be
; modified by the "version.exe" program each time you assemble this code.

                print
                byte    cr,lf,lf
                byte    "X W I N G P A T C H",cr,lf
                byte    "CH Products Flightstick Pro",cr,lf
                byte    "Copyright 1994, Randall Hyde",cr,lf
                byte    lf
                byte    0

                ret
Identify        endp

;****************************************************************************
;
; Calibrate the throttle down here:

                assume  ds:nothing
Calibrate       proc    far
                print
                byte    cr,lf,lf
                byte    "Calibration:",cr,lf,lf
                byte    "Move the throttle to one extreme and press any "
                byte    "button:",0

                call    Wait4Button
                mov     ah, 84h
                mov     dx, 1h
                int     15h
                push    dx              ;Save pot 3 reading.

                print
                byte    cr,lf
                byte    "Move the throttle to the other extreme and press "
                byte    "any button:",0

                call    Wait4Button
                mov     ah, 84h
                mov     dx, 1
                int     15h
                pop     bx
                mov     ax, dx
                cmp     ax, bx
                jb      RangeOkay
                xchg    ax, bx
RangeOkay:      mov     cx, bx          ;Compute a centered value.
                sub     cx, ax
                shr     cx, 1
                add     cx, ax
                mov     ah, 84h
                mov     dx, 303h        ;Calibrate pot three.
                int     15h
                ret
Calibrate       endp



Wait4Button     proc    near
                mov     ah, 84h         ;First, wait for all buttons
                mov     dx, 0           ; to be released.
                int     15h
                and     al, 0F0h
                cmp     al, 0F0h
                jne     Wait4Button

                mov     cx, 0
Delay:          loop    Delay

Wait4Press:     mov     ah, 1           ;Eat any characters from the
                int     16h             ; keyboard which come along, and
                je      NoKbd           ; handle ctrl-C as appropriate.
                getc

NoKbd:          mov     ah, 84h         ;Now wait for any button to be
                mov     dx, 0           ; pressed.
                int     15h
                and     al, 0F0h
                cmp     al, 0F0h
                je      Wait4Press

                ret
Wait4Button     endp
Installation    ends

sseg            segment para stack 'STACK'
                word    256 dup (0)
endstk          word    ?
sseg            ends

zzzzzzseg       segment para public 'zzzzzzseg'
Heap            byte    1024 dup (0)
zzzzzzseg       ends
                end     Main

Chapter Twenty Four (Part 3)

Table of Content

Chapter Twenty Five  

Chapter Twenty Four: The PC Game Adapter (Part 4)
29 SEP 1996