;-------------------------------------------------------------------------------
;       NAME:  INTS.ASM $Revision: 1.60 $
;       COPYRIGHT:
;       "Copyright (c) 1994,1995 by e-Tek Labs"
; 
;        "This software is furnished under a license and may be used,
;        copied, or disclosed only in accordance with the terms of such
;        license and with the inclusion of the above copyright notice.
;        This software or any other copies thereof may not be provided or
;        otherwise made available to any other person. No title to and
;        ownership of the software is hereby transfered."
;-------------------------------------------------------------------------------
;
; Contains interrupt handlers and related functions
; The only entry points include:
;     _new_int02()  - NMI handler
;     _new_int08()  - Timer handler
;     _new_int15()  - Keyboard handler
;     _new_int21()  - DOS handler
;     _new_int2f()  - DOS multiplex handler
;     _new_int7x()  - Communication handler
;     win_api_entry - MS Windows VxD Entry Point
;
;-------------------------------------------------------------------------------
.386

INCLUDE sbosdefs.inc
INCLUDE dsp.inc
INCLUDE dac.inc
INCLUDE gf1hware.inc
IFDEF DEBUG
SHOW_COMMAND10 equ 1
extrn _poke_dram:near
ENDIF

_DATA     segment word public use16 'DATA'
_DATA     ends
_BSS      segment word public use16 'BSS'
_BSS      ends
_TEXT     segment byte public use16 'CODE'
_TEXT     ends

DGROUP    group    _DATA,_BSS,_TEXT
_DATA     segment word public use16 'DATA'

;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; DEBUG MACRO DEFINITIONS
;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
printstring MACRO ptr
IFDEF DEBUG
push ax
push dx
mov ah,09h
lea dx, ptr
int 21h
pop dx
pop ax
ENDIF
ENDM

;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; LIST OF PUBLICS
;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
public __DRIVER_HEADER
public endstack
public _oldvec         ; Old vector information
public _config         ; Configuration
public _old_int7x      ; Needed for the startupc.c routines
public _sbosdata
public _dram_size
public _mpu401_port
public _mpu401_irq
public _comm_vect
public _seg_loader_parm
public _regctl_mask
public _mixer_mask
public _adsbint_mask
public _spec_eoi
public _pic_mask
public _save_lmcfi
public _new_lmcfi
public _multiplex_id
public nmi_ss
public nmi_sp
public _lower_base_port
public _upper_base_port
public _codec_base_port
public _orig_dma1
public _orig_dma2
public _orig_irq1
public _orig_irq2
public _sb_irq
public _sb_dma
public _dram_begin
public _dram_stopped_voice
public _dram_temp_buff
public _dram_wave_info
public _dram_perc_map
IFDEF RATIONAL_FLY
public nmi_port
ENDIF

public _new_int02
public _new_int08
public _new_int15
public _new_int21
public _new_int2f
public _new_int7x
public _new_win21
public handle_port_irq
public write_nmi
public extra_stack ; for the map file only
public intr        ; ditto
public startstack  ; ditto ditto
public _install_id
public _gapicrdid
public _gapisrdid
public _gapi_status
public _dev_mode_synth
public _dev_mode_codec

public _pnp_read
public _pnp_csn
public _pnp_index
public _pnp_write

;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; LIST OF EXTERNS
;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
extrn simulate_sb_irq:near
extrn _synth_handler:near
IFDEF DEBUG
extrn changeborder:near
ENDIF
extrn strat:far
extrn init:near
extrn _SetPic:near
extrn dsp_speaker_on:near
extrn change_from_pio:near
extrn _new_intsynth:near
extrn _shared:word
extrn _reg_codec_addr:word
extrn _reg_codec_status:word
extrn _reg_codec_pio:word
extrn dsp_direct_dac:near
extrn dsp_status:word
extrn req_hdr:dword
;
; Emulation procedures - called for port specific function
;
; MPU-401
;
extrn proc_gpr1w:near
extrn proc_gpr1r:near
extrn proc_gpr2w:near
extrn proc_gpr2r:near
extrn mpu_full_reset:near
;
; FM
;
extrn proc_2xE_read:near
extrn proc_2x6_write:near
extrn proc_2x9_write:near
extrn proc_2xC_write:near
extrn reset_2xF:near
extrn reset_SB:near
extrn flip_on:near
extrn flip_off:near
;
; FM C variables
;
extrn _fm_channel:word
extrn _fm_operator:word
;
; "C" defined routines
;
extrn _int7x_loaded:near
IFDEF MPU401
extrn _parse_midi:near
ENDIF
;
; All needed ports
;
extrn _reg_mixer:word
extrn _reg_sb2x6:word
extrn _reg_irq_status:word
extrn _reg_dram_io:word
extrn _reg_adlib_status:word
extrn _reg_adlib_data:word
extrn _reg_sb2xC:word
extrn _reg_2xC_write:word
extrn _reg_sb2xE:word
extrn _reg_status_read:word
extrn _reg_2xFcontrol:word
extrn _reg_2xBindexed:word
extrn _reg_index:word
extrn _reg_data_high:word
extrn _reg_data_low:word
extrn _reg_388_read:word
extrn _reg_389:word
;
; Debugging data space
;
IFDEF DEBUG
extrn _add_to_debug_table:near
extrn _nmi_count:word
extrn _nmi_table_cnt:word
extrn _snapshot:near
extrn _debug_ptrs:word
ENDIF

;-=- Device Driver Header - 16 bytes -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
__DRIVER_HEADER label byte ; This MUST be first in the .map file
dw -1, -1                ; Will be chaining address
dw 1000000000000000b     ; Driver attribute - we don't support anything here...
dw offset cs:strat       ; Pointer to strategy routine
dw offset cs:intr        ; Pointer to interrupt routine
sbos_string db "SBOS",0,0,0,0        ; New driver name
;
; This is the end of the driver header
;
; The above driver name is not standard...  To conform to what DOS expects
; as a driver name, there would be spaces after SBOS^^^^.  This causes DOS
; to think of SBOS as a real device driver like COM1 or CON.  It would 
; therefore be unable to create or change into a directory named SBOS.  The
; NULLs are there so the real name for the driver is SBOS\0\0\0\0 which can
; not interfere with normal usage of DOS.
;
; _oldvec is a label for the shared structure
; C routines map the typedef for the old vectors (shared.h) over this and
; access it like it was defined in C.  "OLD_VECTORS"
;
_oldvec          label   byte
_old_int02       label   dword   ; original NMI
_oldint02off     dw      0
_oldint02seg     dw      0
_old_int08       label   dword   ; original timer interrupt
_oldint08off     dw      0
_oldint08seg     dw      0
_old_int15       label   dword   ; original keyboard interrupt
_oldint15off     dw      0
_oldint15seg     dw      0
_old_int21       label   dword   ; original dos dispatcher
_oldint21off     dw      0
_oldint21seg     dw      0
_old_int2f       label   dword   ; original multiplexer
_oldint2foff     dw      0
_oldint2fseg     dw      0
_old_int7x       label   dword   ; original 7x
_oldint7xoff     dw      0
_oldint7xseg     dw      0
_old_intsynth    label   dword   ; original synth int
_oldintsynthoff  dw      0
_oldintsynthseg  dw      0
_old_win21       label   dword   ; original DOS BOX int 21 Vector
_oldintwin21off  dw      0
_oldintwin21seg  dw      0
;
; _config is another label from the shared structure
; C is again mapped to this location so both C and assembly can access this
; with minimal trouble.  Structure is BOARD_CFG defined in sbosdefs.h
;
_config          label byte
_lower_base_port       dw      0
_upper_base_port       dw      0
_codec_base_port       dw      0
_orig_dma1             db      0
_orig_dma2             db      0
_orig_dma_mask         db      0
_orig_irq1             db      0
_orig_irq2             db      0
_orig_irq_mask         db      0
_orig_ieirqi_mask      db      0
_sb_dma                db      0
_sb_irq                db      0
_sbos_dma_mask         db      0
_game_irq_mask         db      0
_game_ieirqi_mask      db      0
_pnp_read              dw      0203h   ; can change
_pnp_csn               dw      01h     ; can change
_pnp_index             dw      0279h   ; fixed
_pnp_write             dw      0A79h   ; fixed
;
; This is another one defined in shared.h
; This structure hold all kinds of miscellaneous sbos information
; The structure is SBOSDATA
;
_sbosdata        label   byte
_dev_mode_synth  db      0;
_dev_mode_codec  db      0;
_loader_name     db      LOADER_NAME_LEN dup (?)
_comm_vect       db      7eh  ; comm_vect will be 7e by default
_synth_vect      db      00h
_mpu401_port     dw      330h ; mpu401 port will be at 330 by default
_mpu401_irq      db      00h  ; mpu401 irq (0)
_mixer_mask      db      09h
_regctl_mask     db      00h  ; 2xfw
_adsbint_mask    db      22h  ; enable sb and adlib interrupts
_dram_size       db      00h  ; number of 256k banks of DRAM i.e. 2 = 512k
_spec_eoi        db      00h
_pic_mask        db      00h
_multiplex_id    db      00h  ; Int 2F ID
_dram_begin      dd      00h  ;
_dram_stopped_voice dd   00h  ;
_dram_temp_buff  dd      00h  ;
_dram_perc_map   dd      00h  ;
_dram_wave_info  dd      00h  ;
_dram_mpu_info   dd      00h  ;
_save_lmcfi      dw      0000h ;
_new_lmcfi       dw      0000h ;
;
; Loader spawning variables
; They have to do with reloading SBOS when there is a possibility of
; a program messing up the card
;
spawn_ss         dw      ?       ; saved stack seg and off for spawning
spawn_sp         dw      ?
int21_bypass     db      0       ; set to 1 to skip tests in our int21 handler
;
; This is the parameter block for the loader passed for spawning
; Data is:
; Number of bytes in arguments not counting CR
; Arguments
; Carriage Return
;
_loader_com_parm db      5,"-Q -K",0dh  ; loader command parameters - 
_loader_param    dw      0              ; Environment is same as driver
                 dw      offset cs:_loader_com_parm
;
; This is a label so I can adjust the segment if the driver gets loaded high
; See do_fixup()
;
_seg_loader_parm dw      seg cs:_loader_com_parm
                 dw      0,0,0,0       ; FCB1, FCB2 addresses
;
; MS Windows API entry point - determined from call to int 2fh
; Valid only if Windows is running.
;
_api_entry       label   dword
_api_entry_off   dw      0
_api_entry_seg   dw      0
;
; This is the jump table for the int 7x routine
; DON'T forget to change this number if you add or subtract functions
;
INT7x_NUM_FUNCS equ     6 ; number of functions in the table below
int7x_fct_tab    label   word
    ; Function 0 - Return address of shared data structure
                 dw      offset cs:int7x_shared
    ; Function 1 - Tells driver that loader has loaded
                 dw      offset cs:_int7x_loaded
    ; Function 2 - Debugging only - see function for functionality
                 dw      offset cs:int7x_debug
    ; Function 3 - 
                 dw      offset cs:int7x_winapi
    ; Function 4 - 
                 dw      offset cs:int7x_unload
    ; Function 5 - 
                 dw      offset cs:int7x_ignore
;
; Jump table for Windows API Callback for us
; There are 6 ports that Windows traps for us
; 0 - 2x6 write
; 1 - 2x9 write
; 2 - 2xC write
; 3 - 2xe read
; 4 - 330 write
; 5 - 331 write
; 6 - 330 read
;
WINAPI_NUM_FUNCS equ 7
winapi_fct_tab label word
    dw offset cs:proc_2x6_write
    dw offset cs:proc_2x9_write
    dw offset cs:proc_2xC_write
    dw offset cs:proc_2xE_read
    dw offset cs:proc_gpr1w
    dw offset cs:proc_gpr2w
    dw offset cs:proc_gpr1r
_install_id dw 0
;
; NMI service routine storage
;
nmi_ss           dw      ?       ; temp store of old stack location
nmi_sp           dw      ?
save_ax          dw      ?       ; temp store of ax,dx for start of int02
save_bx          dw      ?       ; Save to see if we need to 'fix' flip prob
save_dx          dw      ?       ; handler so we don't push on their stack
IFDEF RATIONAL_FLY
nmi_port         dw      0ffffh  ; Port that caused NMI
ENDIF
;
; Strings for Game API compare
;
midisimple db "MIDISIMPLE",0
directcodec db "DIRECTCODEC",0
MIDISIMPLE_LEN equ 11
DIRECTCODEC_LEN equ 12
;
; GAME API save stuff
;
_gapicrdid db ?   ; game api codec registered device id save number
_gapisrdid db ?   ; game api synth registered device id save number
_gapi_status db ? ; game api status
_DATA ends

_TEXT           segment byte public use16 'CODE'
assume          cs:DGROUP, ds:DGROUP, ss:DGROUP

startstack label near            ; label here for map file only
;
; we declare space here for any extra stack space we need for runtime.
; The total stack space includes all code from endstack to startstack as well
;
extra_stack db XTRA_STACK dup ('S') ; 'S' is for Stack

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; intr - Device driver initerrupt routine
;
; DESCRIPTION:
;   This will only execute function 0 - initialization called by DOS
;   There is no need to support other functions
;   Function 0 is called by DOS at startup for initialization
;   This code is executed once -- I hope
;   This function should not need to be resident
;   Since it is, I blow it away by overwriting it with stack...
;
; EXPECTS: called from DOS once with function 0 - init
;
; RETURNS: void
;
;-------------------------------------------------------------------------------
intr proc far
    push ds
    push cs
    pop ds
    push es
    les di,req_hdr
    cmp byte ptr es:[di+2],0    ; get command from DOS
    jne intr_end                ; Outta here if it is not
    call near ptr init          ; Call init
intr_end:
    mov ax,0100h                ; Set done bit
    les di,req_hdr
    mov word ptr es:[di+3],ax   ; Store AX in the status field
    pop es
    pop ds
    ret
intr endp
endstack label near

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; check_n_flip - Tests registers to see if we need to fix the Rational bug
;
; DESCRIPTION:
;   Looks at registers BX and DX to see if they seem to have been flipped
;   and flips them back if need be.
;
; EXPECTS:
;   EBX and EDX contain the original application's register values
;
; MODIFIES:
;   EBX, EDX
;
; SEE ALSO:
;   operation_4gw
;
;-------------------------------------------------------------------------------
IFDEF RATIONAL_FLY
check_n_flip proc near
   push ax

; DX is back to what came in to us.
; BX is also restored to its original value (that we got)

    test _shared,STAT_FORCE_FLIPPER
    jne do_flip

; if DX is right, skip it ....
    cmp dx,nmi_port
    je skip_flip

; If BX is exactly what we expected DX to be (where we got NMI from)
; then flip em back ...
    cmp bx,nmi_port
    je do_flip

; OK, things are a bit indeterminate, try a few other checks before 
; blowing it off ...

    ; if dx is NOT valid and BX is, then ASSUME they have been flipped
    ; (might be a bad assumption ....) and flip them back ....
    ;
    cmp dx,_reg_sb2x6    ; if below 2x6, MIGHT be bad
    jb maybe_flip
    cmp dx,_reg_sb2xE    ; if below 2xE (and above 2x6), its OK
    jb skip_flip
    cmp dx,388h            ; if its 388, then ok
    je skip_flip
    cmp dx,389h            ; if its 388, then ok
    je skip_flip
    ;
maybe_flip:
    ;
    ; OK. DX is not in range. (between 2x6 and 2xe, or 388 or 389)
    ; if BX IS in range, then flip.
    ; ANYTHING else, LEAVE IT ALONE !!!!!
    ;
    cmp bx,_reg_sb2x6
    jb skip_flip            ; BX out of range too, skip flip
    cmp bx,_reg_sb2xe
    jb do_flip                ; in range, flip em .....
    cmp bx,388h
    je do_flip            ; good value, do flip
    cmp bx,389h
    jne skip_flip            ; bad value, don't flip
do_flip:
    push ebx
    push edx
    pop ebx
    pop edx
skip_flip:
    pop ax
    ret
check_n_flip endp
ENDIF

wapi_ax dw ?
can_enter db 1

;-------------------------------------------------------------------------------
;
; FUNCTION DEFINITION:
; win_api_entry - entry point for the Windows VxD
;
; DESCRIPTION:
;   Communication entry point for windows to let SBOS know there has been
;   a port write to one of the SoundBlaster ports
;
; EXPECTS:
; AL 
;   FF- DOS BOX closed - not supported by the VxD yet!
;   0 - 2x6 write
;   1 - 2x9 write
;   2 - 2xC write
;   3 - 2xe read
;   4 - 330 write
;   5 - 331 write
;   6 - 331 read
;
;-------------------------------------------------------------------------------
_win_api_entry proc far
    pushf
    cli
    push ds
    push cs
    pop ds
    cmp int21_bypass,0
    jne leave_api_entry
IFDEF DEBUG
    inc _nmi_count ; how many times was I here?
ENDIF
    ;
    ; There are only 6 commands now.  Range check input.
    ;
IFDEF NOT_YET
    cmp ax,0ffh
    jne not_close_box
    and _shared, NOT STAT_WINAPI_ACTIVE
not_close_box:
ENDIF

    cmp ax,WINAPI_NUM_FUNCS
    jae leave_api_entry

    ; If the synth is asleep, don't call synth functions
    cmp _dev_mode_synth, DEV_WINDOWS
    je synth_not_asleep
    cmp ax,0
    je leave_api_entry
    cmp ax,4
    je leave_api_entry
    cmp ax,5
    je leave_api_entry
    cmp ax,6
    je leave_api_entry
synth_not_asleep:

    ; If the codec is asleep, don't call codec functions
    cmp _dev_mode_codec, DEV_WINDOWS
    je codec_not_asleep
    cmp ax,1
    je leave_api_entry
    cmp ax,2
    je leave_api_entry
    cmp ax,3
    je leave_api_entry
codec_not_asleep:

    cmp can_enter,1
    jne leave_api_entry
    ;
    ; restore ax
    ;
    mov wapi_ax,ax
    mov save_ax,ax
    ;
    ; Switch to local stack
    ;
    mov    nmi_ss,ss       ; save current ss
    mov    nmi_sp,sp       ; save current sp
    mov    ax,cs              ; load ss with cs
    mov    ss,ax
    lea sp,endstack
    ; 
    ; Stack is setup - finish pushing regs
    ;
    push ds
    mov ds,ax
    pushad
    push es
    ;
    ; Every int 21h Execute will set FIRST_CONTROL regardless of Windows
    ; Call the first control routine to setup the DMAs and stuff for the IW
    ;
    test _shared,STAT_FIRST_CONTROL
    je api_not_first_time
    call near ptr first_access
    xor _shared,STAT_FIRST_CONTROL
api_not_first_time:
    ;
    ; Create jump address, set semaphore, and call emu routine
    ;
    ; Call synth IRQ handler to handle 'lost' interrupts.
    call   near ptr _synth_handler
    ;
    mov bx,wapi_ax
    shl bx,1
    mov can_enter,0
    call word ptr [winapi_fct_tab+bx]
    mov can_enter,1
    ;
    ; Pop registers and restore ax
    ;
    pop es
    popad
    mov ax,save_ax
    pop ds
    ;
    ; Put stack back to apps.
    ;
    mov    ss,nmi_ss
    mov    sp,nmi_sp
leave_api_entry:
    pop ds
    popf
    ret
_win_api_entry endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; new_int02 - NMI handler
;
; DESCRIPTION:
;   This switches to a local stack, calls the IRQ handling routine and 
;   then clears the HW NMI
;
; MODIFIES: state of internal synth machine
;
;-------------------------------------------------------------------------------
_new_int02 proc far
IFDEF DEBUG
    inc cs:_nmi_count ; how many times was I here?
ENDIF
    mov cs:save_ax,ax
    mov cs:save_bx,bx
    mov cs:save_dx,dx
IFDEF RATIONAL_FLY
    mov cs:nmi_port,0ffffh
ENDIF
    ;
    ;
    ; Switch to local stack
    ;
    mov cs:nmi_ss,ss       ; save current ss
    mov cs:nmi_sp,sp       ; save current sp
    mov ax,cs              ; load ss with cs
    mov ss,ax
    lea sp,endstack
    ; 
    ; Stack is setup - finish pushing regs
    ;
    push ds
    mov ds,ax
    ; 
    ; Mask the NMI
    ; I'm not sure that this is necessary.  It seems to work OK without it.
    ; However, it should be tested on a 386 without the auto NMI disable during
    ; NMI execution.
    ;
    ;mov dx,70h            ; Port to mask NMI
    ;mov al,80h            ; Bit to mask NMI
    ;out dx,al             ; Command to mask NMI
    ;
    ; Call the IRQ handling procedure - same for NMI and synth IRQ
    ;
    and _shared, NOT STAT_UNKNOWN_NMI
    call near ptr handle_port_irq
    ;
    ; If we got an unknown NMI - chain it
    ;
    mov ax,_shared
    and ax,(STAT_CHAIN_NMI OR STAT_UNKNOWN_NMI)
    cmp ax,(STAT_CHAIN_NMI OR STAT_UNKNOWN_NMI)
    jne dont_chain_nmi
    and _shared,NOT STAT_UNKNOWN_NMI
    call dword ptr _old_int02 ; chain NMI
dont_chain_nmi:
    ;
    ; Unmask the NMI
    ; See above comment
    ;
    ;mov dx,70h            ; Port to unmask NMI
    ;mov al,00h            ; Bit to unmask NMI
    ;out dx,al             ; Command to unmask NMI
    ;
    ; Clear the NMI
    ;
    ; NOTE: Bit 6 should still be set at this point
    ;       Bit 6 specifies that the IOCKCH line on the ISA bus was pulled low
    ;       After the next sequence of I/Os, Bit 6 should be cleared again
    ;
    in al,61h             ; Pulse bit 3 high to clear NMI
    or al,08h             ; Go high
    out 61h,al            ; Send it
    and al,0f7h           ; Go low again - see comments in loader code
    out 61h,al            ; Send it
    ;
    mov ax,save_ax
    mov bx,save_bx
    mov dx,save_dx
    ;
    ; Test for the DOS4GW thing...
    ;
    ; Do this EVERY time unless explicitly turned off by loader ...
    ;
IFDEF RATIONAL_FLY
    test _shared,STAT_SKIP_DOS4GW_BUG
    jne no_flip
    call near ptr check_n_flip
no_flip:
ENDIF

    pop ds
    ;
    ; Put stack back to apps.
    ;
    mov    ss,cs:nmi_ss
    mov    sp,cs:nmi_sp
    iret
_new_int02 endp

_prog_resources proc near
    pushf
    cli
    push dx
    push ax
    push ds
    push cs
    pop ds

; bl = dma_mask
; bh = irq_mask
; cl = ieirqi mask

    ;// First open the back door to allow the DMA channel to be changed ...
    mov dx,_reg_index
    mov al,IW_ICMPTI
    out dx,al
    mov dx,_reg_data_high
    in al,dx
    or al,010h
    out dx,al
    mov dx,_reg_2xFcontrol
    mov al,_regctl_mask
    and al,NOT 07h
    out dx,al

; First, program the DMA channels (mask set up by loader code ...)
    mov dx,_reg_mixer
    mov al,_mixer_mask
    out dx,al
    mov dx,_reg_2xBindexed
    mov al,bl
    out dx,al

; Next, set up the IRQ's (mask set up by loader code ...)
    mov dx,_reg_mixer
    mov al,_mixer_mask
    or al,40H
    out dx,al
    mov dx,_reg_2xBindexed
    mov al,bh
    out dx,al

; Next, program the GPOUT bit. (controls irq4 and irq 10)
    mov dx,_reg_index
    mov al,IW_IEIRQI
    out dx,al
    mov dx,_reg_data_high
    in al,dx
    and al,cl
    out dx,al

    ;// Now slam the back door to stop the DMA channel from being changed ...
    mov dx,_reg_2xFcontrol
    mov al,_regctl_mask
    out dx,al

    mov dx,_reg_index
    mov al,IW_ICMPTI
    out dx,al
    mov dx,_reg_data_high
    in al,dx
; DO I want to do this ?????
    and al,NOT 010h
    out dx,al

    pop ds
    pop ax
    pop dx
    popf
    ret
_prog_resources endp
;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; first_access - Combines both DMA channels for SB compatability.  Sets 
;   LMCFI to see 4 banks of 4meg roms if roms are being used.
;
; SEE ALSO:
;   last_access
;
; MODIFIES:
;
;-------------------------------------------------------------------------------
first_access proc near
    push ds
    push cs
    pop ds

    cmp _dev_mode_codec,DEV_ASLEEP
    je dont_change_codec

    mov bl,_sbos_dma_mask
    mov bh,_orig_irq_mask
    mov cl,_orig_ieirqi_mask
    call near ptr _prog_resources

    ; turn on combine bit
    push ax
    push dx
    mov dx,_reg_mixer
    in al,dx
    and al,NOT 40h ; select DMA control register
    out dx,al
    mov dx,_reg_2xFcontrol
    mov al,_regctl_mask
    out dx,al
    mov dx,_reg_2xBindexed
    in al,dx
    or al, 40h ; turn on combine bit
    out dx,al
    pop dx
    pop ax
dont_change_codec:
;
; Now re-configure RAM/ROM config (if its using ROM)

    cmp _dev_mode_synth,DEV_ASLEEP
    je dont_change_synth

    ; If its a ROM sbos, then skip DRAM check ...
    test _shared,STAT_ROM
    je no_roms

    mov dx,_reg_index
    mov al,52H
    out dx,al

    mov dx,_reg_data_low
    mov ax,_new_lmcfi
    out dx,ax

no_roms:
dont_change_synth:
IFDEF DEBUG
; OK. lets clear out the debug data buffer
    push cx
    push ax
    mov  cx,0fffh
    mov eax,32000
deb_loop:
    push 0
    push eax
    call near ptr _poke_dram
    pop eax
    add sp,2
    inc eax
    loop deb_loop
    pop ax
    pop cx
ENDIF
    call dsp_speaker_on               ; try enabling speaker on first NMI
                                      ; in case adlib only (no DSP)
    pop ds
    ret
first_access endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; handle_port_irq - Dispaches control based on cause of IRQ generated by card
;
; DESCRIPTION:
;   IRQ handling routine called from both the NMI and synth IRQ routines
;   Responsible for determining the cause of the interrupt and calling the 
;   appropriate procedure.
;   There is no distinction here as to which IRQ line caused the IRQ.
;
;   AX,DX,DS are pushed prior to calling this routine
;
;   There is a problem with the GF1 and clearing the SB interrupt
;   When an app writes to 2x? and reads from Adlib data, the interrupt
;   is cleared and the NMI routine has no idea what caused it... 
;   This is not a problem with the INTERWAVE.
;
; EXPECTS: Expects to have arrived here via a hw generated irq.
;
;-------------------------------------------------------------------------------
handle_port_irq proc near
    ;
    ; The following code is only here for speeding up the direct digital 
    ; writes to the DSP.  This basically intercepts the writes, tests them
    ; against Direct Mode ADC (0x10,data).  If it is, it gets the data
    ; and pokes it directly to card's dram without calling the
    ; handling routine (ie, parsing command tables, handling number of 
    ; arguments, etc) to save execution time during time critical operations.
    ;
    ; Variable Overview:
    ;  - COMMAND_10 states that the last command sent to the dsp was 0x10
    ;    and the next byte coming through is the audio data.
    ;  - BUSY states that there has been a command sent to the DSP and we 
    ;    are now waiting for the data bytes associated with that command 
    ;    to come through.
    ; NOTE that there is no need to set BUSY when COMMAND_10 is active.  It
    ; never gets tested until COMMAND_10 fails.
    ; 
    ; Here's the pseudocode for the above...
    ;
    ;   if(2xC write)
    ;     if(COMMAND_10)
    ;       get data and send to dram i/o port
    ;       clear COMMAND_10
    ;       goto done
    ;     else if(!BUSY) // see above note about BUSY
    ;       if(2xC command is 0x10)
    ;         set COMMAND_10
    ;         goto done
    ;
    ;   do irq handling routine 
    ;   goto end
    ;
    ; done:
    ;   clear IRQ
    ; end:
    ; 
    ;
    mov dx,_reg_adlib_status ; load status register
    in al,dx               ; read status register
    test al,10h            ; see if 2xC write
    je do_handler
    test dsp_status,COMMAND_10 ; see if expecting command 10 data
    je no_data_here
    ;
    ; We know they are doing direct-io. Get data and shove it out codec
    ;
    mov dx,_reg_sb2xC      ; Data written to 2xC by application
    in al,dx               ; Read value
    ;
    mov    dx,_reg_codec_pio
    out dx,al              ; send byte out codec
IFDEF SHOW_COMMAND10
    mov dl,R2xCW_CODE
    call near ptr _add_to_debug_table
ENDIF
    ;
    xor dsp_status,COMMAND_10 ; now, done with this command 10
    call near ptr flip_on     ; turn bit flipping back on
    jmp IRQ_clear             ; we're done... outta here
no_data_here:
    test _shared,STAT_CODEC_ENABLED
    je do_handler             ; no codec, let later code clear it ....
    test dsp_status,BUSY      ; is the status idle
    jne do_handler            ; if busy - outta here
    mov dx,_reg_sb2xC
    in al,dx
    and al,07Fh               ; kill high bit (it flips)
    cmp al,10h                ; see if command is 10h 
    jne do_handler            ; if not - continue with the handler
IFDEF SHOW_COMMAND10
    mov al,10H                ; don't care what was written
    mov dl,R2xCW_CODE
    call near ptr _add_to_debug_table
ENDIF
    call near ptr dsp_direct_dac    ; setup codec for direct PIO
    call near ptr flip_off          ; shut off bit flipping
IRQ_clear:
    call near ptr reset_SB          ; reset IRQ
    jmp IRQ_leave          ; outta here and don't pop the unpushed registers
do_handler:
    pushad                 ; EAX-ECX-EDX-EBX-ESP-EBP-ESI-EDI
    push es
    ;
    test _shared,STAT_FIRST_CONTROL
    je not_first_time
    call near ptr first_access
    xor _shared,STAT_FIRST_CONTROL
not_first_time:
    ;
    ; Find out where the IRQ was generated from
    ; Read IRQ status register 2x6 and test bit 4 - Adlib Data Register IRQ
    ;
    mov dx,_reg_irq_status ; irq status register
    in al,dx               ; read register
    test al,10h            ; Triggered by an ADLIB/SB port?
    je not_sb_port         ; jmp if not
    ;
    ; At this point - IRQ was generated from the ADLIB/SB ports - now which one?
    ; Read Adlib Status Register 2x8 and test bits 0, 3, 4
    ; Bit 0 - DIRQ   - signals write to UADR
    ; Bit 3 - 2x6IRQ - signals write to 2x6
    ; Bit 4 - 2xCIRQ - signals write to 2xC
    ;
    mov dx,_reg_adlib_status; adlib status
    in al,dx               ; read port
    ; 
    ; Was IRQ generated by Adlib Data Register
    ; This is bit 0 in Adlib Status Register (bl)
    ;
    test al,01h            ; UADR triggered - bit 0
    je not_2x9b0
    ;
    ; There has been a write to 2x9,389 Adlib Data Register
    ; Clear IRQ 
    ; Now, in the new IW hardware, a write of 00h 22h to 3x3i45h will clear IRQ
    ; MAX hardware says read from 2x9,389 to clear
    ;

IFDEF RATIONAL_FLY
    mov ax,_reg_adlib_data
    mov nmi_port,ax
ENDIF

    call near ptr proc_2x9_write
    jmp IRQ_end            ; Outta here
not_2x9b0:
    test al,08h            ; 2x6 triggered?
    je not_2x6b3           ; not caused by 2x6 write
    ;
    ; There has been a write to 2x6
    ;

IFDEF RATIONAL_FLY
    mov ax,_reg_irq_status
    mov nmi_port,ax
ENDIF

    call near ptr write_synth
    call near ptr proc_2x6_write
    jmp IRQ_end            ; Outta here
not_2x6b3:
    ; 
    ; So, IRQ was not generated by write to 2x6, Try 2xC
    ; This is bit 4 in Adlib Status Register (bl)
    ;
    test al,10h            ; 2xC triggered - bit 4
    je not_2xCb4
    ;
    ; There has been a write to 2xC
    ; This is the SoundBlaster DSP register
    ;

IFDEF RATIONAL_FLY
    mov ax,_reg_sb2xC
    mov nmi_port,ax
ENDIF

    call near ptr write_synth
    call near ptr proc_2xC_write
    jmp IRQ_end            ; See ya
not_2xCb4:
    ;
    ; OK, so what caused this???...
    ; Since I don't know what caused it, I'll ignore it...
    ; Should never get here though...
    ;
    jmp IRQ_end
not_sb_port:
    ;
    ; Read 2xF and check for the next set of interrupts
    ; General Purpose Register Interrupts will only be tested if the driver
    ; is in MPU401 mode
    ; The bit for 2xE read interrupt will be tested always (after mpu401 code)
    ;
    mov dx,_reg_status_read ; Load status read register
    in al,dx                ; 
    mov bl,al               ; Make backup copy of register contents
    ;
    ; Check for MPU401 emulation by reading status bit in shared data
    ; If I'm in MPU401 mode I'll fall through and test all bits in 2xF
    ; If I'm not in MPU401 mode I'll jump to the test for the 2xE interrupt
    ; since that is in 2xF too
    ;
    mov ax,word ptr _shared
    test ax,STAT_MPU401_ENABLED
    je not_mpu401
    ;
    ; Read status from 2xF
    ; test bits 3,4,5,6,7:
    ;
    ; 3: General Purpose Register 1 Write Interrupt
    ; 4: General Purpose Register 1 Read Interrupt
    ; 5: General Purpose Register 2 Write Interrupt
    ; 6: General Purpose Register 2 Read Interrupt
    ; 7: 2xE Read Interrupt
    ;
    mov al,bl
    test al,08h            ; See above list
    je not_2xf_bit3        ; Go to next test if not it
    call near ptr proc_gpr1w        ; Call appropriate procedure
    jmp IRQ_end            ; See ya
not_2xf_bit3:
    test al,10h            ; See above list
    je not_2xf_bit4        ; Go to next test if not it
    call near ptr proc_gpr1r        ; Call appropriate procedure
    jmp IRQ_end            ; See ya
not_2xf_bit4:
    test al,20h            ; See above list
    je not_2xf_bit5        ; Go to next test if not it
    call near ptr proc_gpr2w        ; Call appropriate procedure
    jmp IRQ_end            ; See ya
not_2xf_bit5:
    test al,40h            ; See above list
    je not_2xf_bit6        ; Go to next test if not it
    call near ptr proc_gpr2r        ; Call appropriate procedure
    jmp IRQ_end            ; See ya
not_2xf_bit6:
    ; 
    ; The IRQ was not caused by MPU-401 port access
    ;
not_mpu401:
    ;
    ; Always test bit 7 (2xE read Interrupt) regardless of MPU401 emulation
    ;
    mov al,bl              ; restore al
    and al,80h             ; See above list
    je not_2xf_bit7        ; Go to next test if not it

IFDEF RATIONAL_FLY
    mov ax,_reg_sb2xE
    mov nmi_port,ax
ENDIF

    call near ptr proc_2xE_read ; Call appropriate procedure
    jmp IRQ_end            ; See ya
not_2xf_bit7:
    ;
    ; At this point, we don't know what caused the NMI.
    ; On a MAX, we can assume the app wrote to 389 and immediately read it
    ; to to timing or something and inadvertantly cleared the interrupt on the
    ; card.  We don't have to worry about this on an InterWave because the
    ; 389 interrupt is cleared differently.
    ;
    or _shared,STAT_UNKNOWN_NMI
IRQ_end:
    pop es
    popad
IRQ_leave:
    ret
handle_port_irq endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; new_int08 - Timer interrupt routine
;
; DESCRIPTION:
;   Every tick we write our NMI vector to the vector table
;   Calls the old routine
;
; MODIFIES: Zero segment
;
;-------------------------------------------------------------------------------
_new_int08 proc far
    push bx
    push ds
    push cs
    pop ds
    pushf
    call dword ptr _old_int08 ; call the old interrupt routine
    ;
    ; Now, let's write our NMI vector into the vector table in case someone 
    ; doesn't use DOS to change them
    ; I wonder if this will piss anyone off.
    ;
    call near ptr write_nmi ; write nmi vector to the vector table
    pop ds
    pop bx
    iret
_new_int08 endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; _new_int15 - Keyboard interrupt routine
;
; DESCRIPTION:
;   If compiled with debug option, Checks for both shift keys to reset command
;       table
;   Jumps to the old handler
;
; MODIFIES: debug data
;
;-------------------------------------------------------------------------------
_new_int15 proc far
;IFDEF DEBUG
IFDEF NEVER
    push ds
    push cs
    pop ds
    pushad
    push es
    ;
    ; Check for both shift keys
    ; Reset command table pointer and nmi_count
    ;
    mov ah,02h                  ; enhanced shift keys
    int 16h
    and al,03h                  ; mask off all but L & R shift
    cmp al,03h                  ; L and R shift keys both on?
    jne noshift                 ; if not goto noshift
    mov _nmi_count,0            ; clear nmi count
    mov _nmi_table_cnt,0        ; reset table pointer to start
    jmp done_debug
noshift:
    ;
    ; Check for both ALT keys
    ; Take snapshot of current fm configuration
    ;
    mov ah,12h                  ; enhanced shift keys
    int 16h
    and ah,0Ah                  ; test for both alt keys
    cmp ah,0Ah                  ; both alt keys on?
    jne noalt
    call near ptr _snapshot     ; take a shapshot
    jmp done_debug
noalt:
done_debug:
    pop es
    popad
    pop ds
ENDIF
    ;
    ; Jump to the old handler
    ;
    cmp ah,04fh    ; see if its polling the keyboard
    jne not_key_request

        test cs:_shared,STAT_WINDOWS_RUNNING ; see if windows is running
        jne not_key_request

    push ax
    in al,70h        ; re-enable NMI's since a Phoenix BIOS turns
    and al,7fh       ; em off when keyboard is polled ....
    out 70h,al 

    push ds
    in al,60h        ; see if its a delete key ....
    and al,07fh
    cmp al,53h
    jne not_reset    ; nope, just let it go ...
                     ; yes its a delete, now see if ctrl-alt are pushed ...
    mov ax,40h        ; load up bios data segment
    mov ds,ax
    mov al,ds:[17h] ; get shift status bits ....
    and al,0ch
    cmp al,0ch
    jne not_reset

    pushad
    push cs            ; point to OUR data seg.
    pop ds
        call near ptr proc_2x6_write
    call near ptr _sleep_synth_nmi
    popad

not_reset:
    pop ds
    pop ax

not_key_request:

    call near ptr write_nmi         ; write out nmi_vector to the vector table

    jmp dword ptr cs:_old_int15   ; call old handler

_new_int15 endp

IFDEF RATIONAL_DISK
RATIONAL_COUNT      EQU 75
RATIONAL_STAMP_SIZE EQU 23
rational_stamp  db 67h,66h, 87h,4dh,1ch,07h,1fh,66h,5fh, 66h,5eh,66h,5dh
                db 83h,0c4h,04h,66h,5ah,66h,5bh,66h,0cah,2
dummy           db 0AAh ; in the real string, this byte is a zero....
read_segment    dw 0    ; segment for apps file buffer
read_offset     dw 0    ; offset for apps file buffer
read_size       dw 0    ; buffer size
operation_ss    dw 0    ; SS save
operation_sp    dw 0    ; SP save
operation_buffer_count db 0;

IFDEF DEBUG
operation_color db 0
ENDIF
ENDIF

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; last_access - Changes memory config and DMA config so emulation s/w will 
;   work.
;
; DESCRIPTION:
;   Turns off the combine bit for the DMA channels.  This bit will be turned
;   back on when first_access is called during the first NMI.  Also reset
;   LMCFI to the original value.  This will also get reset during the first
;   NMI.  LMCFI is changed so we see 4 banks of 4 meg roms.
;
; SEE ALSO:
;   first_access
;
;-------------------------------------------------------------------------------
last_access proc near
    push ds
    push cs
    pop ds
    ;
    ; Every time through here reset the cards DMAs for any native mode code 
    ; to run AND, set a flag saying that we changed values to ones that are now
    ; consistent with the iw.ini file.  When we get a port write, either
    ; through an NMI or Windows trap, we will call first_access which will
    ; reset the DMA channels to what we want.
    ;
    or _shared,STAT_FIRST_CONTROL 

    push ax
    push bx
    push cx
    push dx

; Make sure that we don't leave CODEC in polled I/O mode ...
    call near ptr change_from_pio

    mov bl,_orig_dma_mask
    mov bh,_orig_irq_mask
    mov cl,_orig_ieirqi_mask
    call near ptr _prog_resources

;  Now put the RAM/ROM config back to normal ....
    test _shared,STAT_ROM
    je no_roms_cfg

    mov dx,_reg_index
    mov al,52H
    out dx,al

    mov dx,_reg_data_low
    mov ax,_save_lmcfi
    out dx,ax

no_roms_cfg:
    pop dx
    pop cx
    pop bx
    pop ax
    pop ds
    ret
last_access endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; new_win21 - INT 21h handler for DOS BOXs 
;
; DESCRIPTION:
;   This int21 handler spawns the loader if the card is in an unacceptable
;   state.  This gets installed by the loader only when windows is running
;   and the loader is run in a dos box AND the VxD has been found and
;   connected to.  The original testnspawn code in the
;   initial int21 handler will not spawn the loader if windows is running.
;   Therefore, if some app messes up the card in a dos box, the user had to
;   run the loader by hand in order to restore it.  With this handler installed,
;   and trapping executes in this dos box only, the loader will run 
;   automatically to restore the card.
;
; SEE ALSO:
;   new_int21
;
;-------------------------------------------------------------------------------
_new_win21 proc far
    or cs:_shared,STAT_WIN21_FLAG    ; Keep this outside the bypass check

    cmp cs:int21_bypass,0
    jne win_not_execute
    cmp ah,04bh                      ; execute program
    jnz win_not_execute              ; if it isn't -- leave
    ;
    ; Someone is trying to execute a program - shall we test the card?
    ;
    test cs:_shared,STAT_IGNORE_SPAWN
    jne win_ignore_spawn
    call near ptr testnspawn         ; Test the card and spawn loader if needed
    call near ptr last_access        ; Setup card for emulation s/w
win_ignore_spawn:
    and cs:_shared,NOT STAT_IGNORE_SPAWN

win_not_execute:
    jmp dword ptr cs:_old_win21
_new_win21 endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; new_int21 - DOS services interrupt routine
;
; DESCRIPTION:
;   Does not allow changing of NMI vector via subfunction 25
;   Calls testnspawn during every program execution
;   Calls the old handler
;   Traps function 3fh disk reads for fixing Rational bug
;
;-------------------------------------------------------------------------------
_new_int21 proc far
    ;
    ; If bypass is on, we're in our code so just go to the old handler
    ;
    cmp cs:int21_bypass,0
    jne bypass_int21
    pushf
    ;
    ; *** Test for a setvect on the NMI vector
    ;
    cmp ax,2502h                     ; See if setvect is for the NMI
    jne not_setnmi
    popf
    jmp dont_jump                    ; if it is - leave
not_setnmi:
    ;
    ; *** Test for 4B - Execute a program
    ; Don't do the check if windows is running...
    ;
    cmp ah,04bh                      ; execute program
    jnz not_execute                  ; if it isn't -- leave
    test cs:_shared,STAT_WINDOWS_RUNNING ; see if windows is running
    jne windows_running
    test cs:_shared,STAT_IGNORE_SPAWN
    jne ignore_spawn
    ;
    ; Someone is trying to execute a program - shall we test the card?
    ;
    call near ptr testnspawn         ; Test the card and spawn loader if needed
    call near ptr last_access        ; Setup card for emulation s/w
ignore_spawn:
    and cs:_shared,NOT STAT_IGNORE_SPAWN
    ;
windows_running:
    ;
    ; On every spawn, we will reset the rational test to RATIONAL_COUNT
    ; times
    ;
IFDEF RATIONAL_DISK
    mov cs:operation_buffer_count,RATIONAL_COUNT
ENDIF
    ;
not_execute:
    ;
    ; Test for DOS File read
    ;   INT 21 FUNCTION 3F
    ;   DS:DX -> ptr to buffer
    ;   CX    :  number of bytes to xfer
    ;   BX    :  file handle
    ; 
IFDEF RATIONAL_DISK
    cmp ah,3fh
    jne not_file_read
    test cs:_shared,STAT_WINDOWS_RUNNING
    jne not_file_read              ; No NMIs in Windows anyway
    cmp cs:operation_buffer_count,0
    je not_file_read
    dec cs:operation_buffer_count
    push ds
    pop cs:read_segment             ; store the buffers segment
    mov cs:read_size,cx             ;
    mov cs:read_offset,dx           ; calculate buffer start offset
    call dword ptr cs:_old_int21    ; we need the flags from this...
    call near ptr operation_4gw     ; see if we should fix the buffer
    iret                            ;
not_file_read:
ENDIF
    call near ptr write_nmi         ; write out nmi_vector to the vector table
    ;
    ; Jump to the old INT 21 handler
    ;
    popf                            ; restore flags from entry point
bypass_int21:
    jmp dword ptr cs:_old_int21
dont_jump:
    iret
_new_int21 endp

;-------------------------------------------------------------------------------
;
; FUNCTION DEFINITION:
; operation_4gw
;
; DESCRIPTION:  This gets called during some int 21/3F (Read file)
;   Change stacks
;   Push registers
;   Perform String compare searching for rational_stamp in the disk buffer
;   IF found
;     Make sure it's not ours AND Repair it
;   Get flags from the previously called INT 21 handler 
;   Pop registers
;   Copy the carry flag (Bit 0) back 12 bytes on the stack so the iret will
;     send the correct flags back to the application
;
; NOTES:
; It was found that dos4gw reads the search string off the disk on two separate
; occasions.  It is the second read the needs to be repaired but we repair 
; both anyway.  To eliminate the scaning of buffers during games and such,
; we only scan a certain number of buffers when a program starts.  It was found
; that different versions of dos4gw read the search string off the disk 
; at different times:  
;   Version 1.95 reads the first in the 41st buffer and the second in the 44th.
;   Version 1.97 reads the first in the 42nd buffer and the second in the 45th.
; Therefore we need to test at least 45 buffers to guarantee two repairs.
; To be safe we will choose a number larger than 45.  This is specified in the
; equate RATIONAL_COUNT
;
; EXPECTS:
;   Incomming flags are the result of the int 21/3F call
;   
;-------------------------------------------------------------------------------
IFDEF RATIONAL_DISK
operation_4gw proc near
    push ax
    push bx
    pushf
    ;
    ; Switch stacks and save registers
    ;
    mov cs:operation_ss,ss         ; save old ss
    mov cs:operation_sp,sp         ; save old sp
    mov ax, cs
    mov ss, ax
    lea sp,endstack
    push ds
    push cs
    pop ds                         ; this also sets up source segment
    pushad
    push es
    cld
    ;
    ; Normalize the segment and offset so the segment is a big as possible
    ; and the offset is as small as possible.  Also, if the size of the buffer
    ; is larger than the largest offset (0x0f) and the stamp size,
    ; subtract off the size of the offset (0 to 0x0f) and the size of the 
    ; buffer so the search doesn't go past the end of the segment
    ;
    push eax
    push edx
    movzx eax,read_segment
    shl eax,4
    movzx edx,read_offset
    add eax,edx                 ; EAX is now 20 bit address of buffer start
    and read_offset,0fh
    shr eax,4
    mov read_segment,ax         ; read_offset, read_segment are normalized
    mov es,ax                   ; need this for search
    mov bx,read_offset
    cmp read_size,RATIONAL_STAMP_SIZE+15
    jbe skip_adjust
    sub read_size,RATIONAL_STAMP_SIZE
    sub read_size,bx
skip_adjust:
    pop edx
    pop eax
    ;
    ; BX is start offset into search field
    ; ES is segment of search field
    ; Increment BX/DI through the buffer looking for a DWORD match on the first
    ; four bytes of the rational_stamp. If four bytes match up,
    ; reset the pointers to the beginning of both strings and search up to
    ; the end of the search string or until the first difference.  If there
    ; is no match, continue with the original method until the end of the 
    ; buffer.  This is for speed optimization only.
start_compare:
    mov di,bx
    inc bx
    mov eax,dword ptr rational_stamp
    cmp dword ptr es:[di],eax      ; first four bytes
    je initial_match
    cmp bx,read_size                ; see if I'm at the end
    ja leave_operation
    jmp start_compare
initial_match:
    lea si,rational_stamp
    mov cx,RATIONAL_STAMP_SIZE ; there are n bytes in the string
    ; ES:DI is current search target
    ; BX is running byte count into segment so far
    ; CX is number of bytes to compare
    ; DS:SI is start of search string
    repe cmpsb                 ; do the compare
    jne start_compare          ; not found? go back up to the top and restart
found_match:
    cmp byte ptr es:[di],0     ; if the byte following the string is 0, found it
    jne start_compare
IFDEF DEBUG
    call changeborder               ; cycle through the palette if DEBUG is on
ENDIF
    mov byte ptr es:[di-6],5bh ; ES:DI points to the 0
    mov byte ptr es:[di-4],5ah ; reach back and repair the buffer
leave_operation:
    pop es
    popad
    pop ds
    mov ss,cs:operation_ss     ; restore the stack
    mov sp,cs:operation_sp
just_leave:
    mov bx,sp                  ; BX is stack pointer
    pop ax                     ; AX is flags from the int21
    test ax,0001h              ; test carry flag from int21 call
    jne carry_was_on
    and word ptr ss:[bx+12],NOT 1 ; Carry was off
    jmp carry_was_off
carry_was_on:
    or word ptr ss:[bx+12],1   ; Carry was on
carry_was_off:
    pop bx
    pop ax
    ret
operation_4gw endp
ENDIF

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; new_int2f - DOS Multiplexer interrupt routine
;
; DESCRIPTION:
;   This routine supports the InterWave GAME API.
;
; NOTES:
;   - All numeric values are in HEX
;   - Int 2fh must be chained if the calling Installed Device ID does not match
;
;   Function 0:  Install Check - See if this int 2fh ID has any InterWave
;                TSRs hooked into it.  The calling app must check for the
;                DX:SI combination ('ETEK') before attaching to any used ID.
;                If this combination is not found, the app will attach to the
;                first unused ID.  The ID search will start on ID number CDh
;                and end on ECh.
;                
;     Called With:                  Returns:
;        AH   - INT 2fh ID            
;        AL   - 0                     AL   - FF - We are here
;        DX   - "IW"                  DX   - "ET
;        SI   - "VE"                  SI   - "EK"
;
;   Function 1:  Get Number of Devices - On return, BX will be the number of
;                InterWave Devices on this int 2fh chain.  The number returned
;                should be used as a new Installed Device ID Number for any
;                new installed driver or application.  No other registers should
;                be modified and control should be chained, not returned so
;                all devices can be counted. 
;     Called With:                  Returns:
;        AH   - INT 2fh ID            
;        AL   - 1                     
;        BX   - 0                     BX   - BX + 1
;
;   Function 2:  Get Device Status and Information - Returns current status
;                of the device with the matching Installed Device ID Number
;                and pointer to identification string.
;     Called With:                  Returns:
;        AH   - INT 2fh ID
;        AL   - 2
;        BX   - Installed Device ID   BX   - Game Functions Version (BCD BH.BL)
;                                     CX   - Device Status
;                                     ES:DI- Pointer to ASCIIZ ID string
;                                     
;     Bit fields in CX are as follows (1 is TRUE):
;        [0]  - Using Synthesizer Section of InterWave Card
;        [1]  - Using CODEC Section of InterWave Card
;        [2]  - Supports Game Devices (see fcts. 21h and 22h)
;
;   Function 3:  Suspend Resident Device Driver - Suspend the device
;                identified by the Installed Device ID so the new device can
;                use some or all of the InterWave Card.  Once the device
;                is suspended, the new app should use the Base Port, IRQ and
;                DMA (if applicable) returned from the call.
;     Called With:                  Returns:
;        AH   - INT 2fh ID            
;        AL   - 3                     AL   - 0 (Suspended) - Always pass
;        BX   - Installed Device ID   BX   - Base Port
;        CX   - Requested Device      CL   - IRQ
;                                     CH   - DMA - for CODEC only
;     Values for CX are:
;        01   - Just SYNTH
;        02   - Just CODEC
;
;   Function 4:  Wake Up Resident Device Driver - Attempts to wake up the
;                device identified by the Installed Device ID after the
;                has been successfully suspended by funcion 3.
;     Called With:                  Returns:
;        AH   - INT 2fh ID            
;        AL   - 4                     AL   - 0 (Woken) else (Failed)
;        BX   - Installed Device ID 
;
;   Function 5:  Free Resident Device Driver - Attempts to free the device
;                identified by the Installed Device ID.  This function assumes
;                the driver has resident code to handle freeing and the int 2fh
;                chain will not hinder successfull unhooking of interrupts.
;                Startup code for devices can use this function to free the
;                resident portion of the same device.
;     Called With:                  Returns:
;        AH   - INT 2fh ID            
;        AL   - 5                     AL   - 0 (Freed) else (Failed)
;        BX   - Installed Device ID 
;
;   Function 21:  Game Function: Device Open - Open a specific device.
;     Called With:                  Returns:
;        AH   - INT 2fh ID            AL   - 0 (allocated) else (Failed)
;        AL   - 21                       
;        BX   - Installed Device ID   BX   - Real Mode Interrupt Number
;        ES:DI- ASCIIZ Device Name
;                                     DX   - Device Handle
;
;   Function 22:  Game Function: Device Close - Close a specific device.
;     Called With:                  Returns:
;        AH   - INT 2fh ID            AL   - 0 (closed) -1 (bad handle/not open)
;        AL   - 22                       
;        BX   - Installed Device ID
;        DX   - Device Handle
;
;   Function 80:  Mixer Setting Changed Broadcast - From Mixer TSR as a
;                 general broadcast to other apps notifying that the mixer
;                 hardware has changed.
;     Called With:
;        AH   - INT 2fh ID
;        AL   - 80
;
;-------------------------------------------------------------------------------
int21_di_sav dw ?
_new_int2f proc far
    push ds
    push cs
    pop ds

    cmp ax,1605h               ; Windows INIT Broadcast
;    cmp ax,1608h               ; Windows INIT complete Broadcast
    je dosx_init
    cmp ax,1606h               ; Windows EXIT Broadcast
    je exit_cmplt
    cmp ah,_multiplex_id       ; InterWave API
    je iw_multiplex
    jmp chain_int2f
dosx_init:

    ; Check to make sure its windows 3.x or better. (Killing Moon fakes
    ; a windows start, but its sets this version to 0 ....)
    push di
    shr di,8
    cmp di,3h
    pop di
    jb chain_int2f
    
    ;
    ; Windows enhanced mode and 286 DOSX init broadcast message
    ; Called before Windows starts
    ;
    printstring wininit
    or _shared, STAT_WINDOWS_RUNNING
    call near ptr _sleep_synth_nmi
    call near ptr _sleep_codec_nmi
    mov _dev_mode_synth, DEV_WINDOWS
    mov _dev_mode_codec, DEV_WINDOWS
    jmp chain_int2f
exit_cmplt:
    ;
    ; Windows enhanced mode and 286 DOSX exit broadcast message
    ; Called when Windows is done exiting
    ;
    printstring winexit
    and _shared, NOT (STAT_WINDOWS_RUNNING OR STAT_WINAPI_ACTIVE)
    call near ptr _wake_synth
    call near ptr _wake_codec
    jmp chain_int2f
iw_multiplex:
    ;
    ; InterWave INT 2Fh Device Driver API
    ;
    cmp al,INT2F_INSTALL
    je id_install
    cmp al,INT2F_NUM_DEVICES
    je id_devices
    cmp al,INT2F_STATUS
    je id_status
    cmp al,INT2F_SUSPEND
    je id_suspend
    cmp al,INT2f_WAKE
    je id_wake
    cmp al,INT2F_FREE
    je id_free
    cmp al,INT2F_GAME_OPEN
    je id_game_open
    cmp al,INT2F_GAME_CLOSE
    je id_game_close
    jmp chain_int2f

id_install:
    mov al,0ffh            ; I'm here
    mov dx,'ET'
    mov si,'EK'
    jmp exit_int2f
id_devices:
    inc bx
    jmp chain_int2f
id_status:
    cmp bx,_install_id
    jne chain_int2f
    xor bx,bx              ; device id
    xor cx,cx              ; start with clear status
    cmp _dev_mode_synth, DEV_ASLEEP
    je id_status_synth_not_mine
    or cx,1
id_status_synth_not_mine:
    cmp _dev_mode_codec, DEV_ASLEEP
    je id_status_codec_not_mine
    or cx,2
id_status_codec_not_mine:
    test _shared,STAT_MPU401_ENABLED
    je id_status_no_game_support
    test _shared, STAT_WINDOWS_RUNNING
    je id_test_status
    mov dx,_reg_mixer
    in al,dx
    cmp al,0ffh            ; if win is running and read ff from mixer
    je id_status_no_game_support ; assume we don't have card in this box
id_test_status:
    or cx,4
    mov bx,0100h           ; Game funcions version 1.0
id_status_no_game_support:
    push ds
    pop es                 ; ES:DI pointer to id string
    lea di,sbos_string
    jmp exit_int2f
id_suspend:
    cmp bx,_install_id
    jne chain_int2f
    cmp cx,1
    jne id_not_suspend_synth
    call _is_synth_avail
    je exit_int2f_bad
    mov _dev_mode_synth, DEV_ASLEEP   ; suspend synth
    call near ptr _sleep_synth_nmi
    mov ax,0
    mov bx,_lower_base_port
    mov cl, byte ptr _orig_irq1
    mov ch, 0 ; no DMA
    jmp exit_int2f
id_not_suspend_synth:
    cmp cx,2
    jne exit_int2f_bad
    call _is_codec_avail
    je exit_int2f_bad
    mov _dev_mode_codec, DEV_ASLEEP   ; suspend codec
    call near ptr _sleep_codec_nmi
    call _move_codec_irq
    mov ax,0
    mov bx, _codec_base_port
    mov cl, byte ptr _sb_irq
    mov ch, byte ptr _orig_dma2
    jmp exit_int2f
id_wake:
    cmp bx,_install_id
    jne chain_int2f
    call near ptr _wake_synth
    call near ptr _wake_codec ; testnspawn will run
    mov ax,0
    jmp exit_int2f
id_free:
    cmp bx,_install_id
    jne chain_int2f
    mov ax,-1              ; fail on free
    jmp exit_int2f
id_game_open:
    cmp bx,_install_id
    jne chain_int2f

    mov int21_di_sav,di     ; save di for compare later
    lea si,midisimple
    mov cx, MIDISIMPLE_LEN
    repe cmpsb
    je id_request_midisimple
    lea si,directcodec
    mov di,int21_di_sav
    mov cx, DIRECTCODEC_LEN
    repe cmpsb
    je id_request_directcodec
    jmp exit_int2f_bad
id_request_midisimple:
    call _is_synth_avail
    je exit_int2f_bad
    mov _dev_mode_synth, DEV_GAME
    call first_access
    call _sleep_synth_nmi
    mov ax,0               ; pass
    movzx bx,_comm_vect
    mov dx,1               ; always use device handle 1 for synth
    jmp exit_int2f
id_request_directcodec:
    call _is_codec_avail
    je exit_int2f_bad
    call first_access
    call _sleep_codec_nmi
    call _move_codec_irq
    call _codec_mode2
    mov _dev_mode_codec, DEV_GAME
    mov ax,0               ; else pass
    mov si,0               ; no fifo
    mov bx, _codec_base_port
    mov cl, byte ptr _sb_irq
    mov ch, byte ptr _orig_dma2
    mov dx,2               ; always use device handle 2 for codec
    jmp exit_int2f
id_game_close:
    cmp bx,_install_id
    jne chain_int2f
    cmp dx,1
    je id_close_synth     ; synth handle
    cmp dx,2
    je id_close_codec     ; codec handle
    jmp exit_int2f_bad
id_close_synth:
    cmp _dev_mode_synth, DEV_GAME
    jne exit_int2f_bad    ; nobody opened it
    call mpu_full_reset
    mov _dev_mode_synth, DEV_ASLEEP ; will get changed in wake
    call _wake_synth
    mov ax,0
    jmp exit_int2f
id_close_codec:
    cmp _dev_mode_codec, DEV_GAME
    jne exit_int2f_bad    ; nobody opened it
    mov _dev_mode_codec, DEV_ASLEEP ; will get changed in wake
    call _wake_codec  ; this should spawn the loader and put codec in mode 3
    mov ax,0
    jmp exit_int2f

chain_int2f:
    pop ds
    jmp dword ptr cs:_old_int2f
exit_int2f_bad:
    mov ax,-1
exit_int2f:
    pop ds
    iret
_new_int2f endp

;-------------------------------------------------------------------------------
;
; FUNCTION DEFINITION:
; is_codec_avail - Returns the Zero flag set if the codec is not available
;    based on the current mode of sbos
;
;-------------------------------------------------------------------------------
_is_codec_avail proc near
    cmp _dev_mode_codec, DEV_GAME
    je codec_not
    cmp _dev_mode_codec, DEV_ASLEEP
    je codec_not
    test _shared,STAT_CODEC_ENABLED
    ;je codec_not
codec_not:
    ret                  ; Z flag will be set on fail
_is_codec_avail endp

;-------------------------------------------------------------------------------
;
; FUNCTION DEFINITION:
; is_synth_avail - Returns the Zero flag set if the synth is not available
;    based on the current mode of sbos
;
;-------------------------------------------------------------------------------
_is_synth_avail proc near
    cmp _dev_mode_synth, DEV_GAME
    je synth_not
    cmp _dev_mode_synth, DEV_ASLEEP
    je synth_not
    test _shared, STAT_MPU401_ENABLED
    ;je synth_not
synth_not:
    ret                 ; Z flag set on fail
_is_synth_avail endp

IFDEF DEBUG
wininit db "SBOSDEBUG: Got Windows Init Broadcast Message",0dh,0ah,"$"
winexit db "SBOSDEBUG: Got Windows Exit Broadcast Message",0dh,0ah,"$"

winmode db "SBOSDEBUG: Windows Is in Enhanced Mode",0dh,0ah,"$"
winapientry db "SBOSDEBUG: Got Windows API Entry Point",0dh,0ah,"$"
winmpuactive db "SBOSDEBUG: Attempting Setup for MPU-401 Emulation",0dh,0ah,"$"
winapiactive db "SBOSDEBUG: Windows VxD Found and Activated",0dh,0ah,"$"
ENDIF

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; sleep_synth_nmi - Puts SBOS NMI synth generation to sleep
;
; DESCRIPTION:
;   Turns off HW port NMIs for ADLIB and MPU-401 registers.
;   Sets Sleep flags in shared.status.
;   Modifies port masks so these will not get re-enabled by accident.
;   This does not turn SBOS off.  The Windows VxD/SBOS API can be active.
;   Does not require that SBOS is awake.
;
; MODIFIES:
;   regctcl_mask, adsbint_mask
;
; SEE ALSO:
;
;-------------------------------------------------------------------------------
_sleep_synth_nmi proc near
    push ax
    push dx
    ;
    ; Turn off ADLIB irqs
    ;
    mov dx,_reg_index               ; set flag in shared - sbos is asleep
    mov al,45h                      ; select adlib/sb control register
    out dx,al                       ;
    mov dx,_reg_data_high           ;
    and _adsbint_mask,NOT 02h       ; shut off adlib
    mov al,_adsbint_mask            ; download new mask
    out dx,al
    ;
    ; If we're in MPU-401 mode, shut off 401 emulation IRQs too
    ;
    test _shared, STAT_MPU401_ENABLED   ; am I in mpu401 emulation
    je ss_no_mpu401
    mov dx, _reg_2xFcontrol
    and _regctl_mask,0E0h           ; shut off gp register irqs in mask
    mov al,_regctl_mask
    out dx,al                       ; download new mask
ss_no_mpu401:

    pop dx 
    pop ax
    ret
_sleep_synth_nmi endp

_sleep_codec_nmi proc near
    push ax
    push dx
    ;
    ; Turn off SB irqs
    ;
    mov dx,_reg_index               ; set flag in shared - sbos is asleep
    mov al,45h                      ; select adlib/sb control register
    out dx,al                       ;
    mov dx,_reg_data_high           ;
    and _adsbint_mask,NOT 20h       ; shut off sb
    mov al,_adsbint_mask            ; download new mask
    out dx,al

    pop dx 
    pop ax
    ret
_sleep_codec_nmi endp

_move_codec_irq proc near
    mov dx,_reg_index       ; Move CODEC IRQ to IRQ2
    mov al,05ah
    out dx,al
    mov dx,_reg_data_high
    in al,dx
    or al,80h
    out dx,al
    ret
_move_codec_irq endp

_codec_mode2 proc near
    mov dx,_codec_base_port
    ;mov al,0Ch
    mov al,51h    ; Hit MCE too
    out dx,al
    inc dx
    in  al,dx
    and al,0FBh
    out dx,al
    dec dx
    mov al,4Ch    ; Hit MCE too
    out dx,al
    inc dx
    mov al,40h
    out dx,al
    mov al,0Ch
    dec dx
    out dx,al    ; turn off MCE
    ret
_codec_mode2 endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; wake_sbos - Wakes up SBOS NMI generation
;
; DESCRIPTION:
;   Turns on HW port NMIs for SB, ADLIB and MPU-401 registers.
;   Clears Sleep flags in shared.status.
;   Modifies port masks so NMIs will be reenabled
;   Does not require that SBOS is asleep.
;
; MODIFIES:
;   regctcl_mask, adsbint_mask
;
; SEE ALSO:
;
;-------------------------------------------------------------------------------
_wake_synth proc near
    push ax
    push dx

    cmp _dev_mode_synth,DEV_GAME
    je ws_leave

    test _shared, STAT_WINDOWS_RUNNING
    je ws_not_windows
    mov _dev_mode_synth, DEV_WINDOWS
    call force_spawn
    jmp ws_leave
ws_not_windows:

    mov _dev_mode_synth, DEV_HW

    test _shared, STAT_NO_NMIS
    jne ws_leave

    ;
    ; Enable ADLIB IRQs
    ;
    test _shared, STAT_ADLIB_ENABLED
    je ws_mpu401
    or _adsbint_mask, 02h           ; enable adlib irqs in mask
    ; Dont't out this mask so testnspawn has something that forces it to
    ; re-spawn loader after Windows exits

ws_mpu401:
    test _shared, STAT_MPU401_ENABLED   ; am I in mpu401 emulation
    je ws_leave
    mov dx, _reg_2xFcontrol         ; set flag in shared - synth is asleep
    or _regctl_mask,18h             ; reenable gpreg irqs
    mov al,_regctl_mask
    out dx,al                       ; download new mask
ws_leave:
    pop dx
    pop ax
    ret
_wake_synth endp

_wake_codec proc near
    push ax
    push dx

    cmp _dev_mode_codec, DEV_GAME
    je wc_leave

    test _shared, STAT_CODEC_ENABLED
    jne wc_enabled
    mov _dev_mode_codec, DEV_ASLEEP
    jmp wc_leave
wc_enabled:

    test _shared, STAT_WINDOWS_RUNNING
    je wc_not_windows
    mov _dev_mode_codec, DEV_WINDOWS
    call force_spawn
    jmp wc_leave
wc_not_windows:

    mov _dev_mode_codec, DEV_HW

    test _shared, STAT_NO_NMIS
    jne wc_leave

    ;
    ; Enable IRQs
    ;
    or _adsbint_mask,20h
    ;
    ; Dont't set this register so testnspawn has something that forces it to
    ; re-spawn loader after Windows exits
    ;
    ;    out dx,al
    ;
    mov dx,_reg_index       ; make CODEC IRQ not use IRQ2
    mov al,05ah
    out dx,al
    mov dx,_reg_data_high
    in al,dx
    and al,NOT 80h
    out dx,al
    ;
    ; Clear the flags in status
    ;
wc_leave:
    pop dx
    pop ax
    ret
_wake_codec endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; write_synth -  Writes our SYNTH vector to the vector table
;
; EXPECTS:
;   synth_vect is setup
;
; MODIFIES: 
;    Zero segment
;
;-------------------------------------------------------------------------------
write_synth proc near
    push ds
    xor bx,bx                    ; load ds with 0 to talk to the vector table
    mov ds,bx
    mov bl,cs:_synth_vect        ; load bx with offset of SYNTH irq offset
    shl bx,2
    mov word ptr [bx],offset cs:_new_intsynth ; store SYNTH irq offset
    mov word ptr [bx+2],cs         ; store SYNTH irq segment in table
    pop ds
    call _SetPic                ; reset the PIC's bit also
    ret
write_synth endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; write_nmi - Writes our NMI vector to the vector table and enabled H/W NMIs
;
; EXPECTS: 
;   shared.status is valid
;
; MODIFIES:
;    Zero segment and port 61h data
;
;-------------------------------------------------------------------------------
write_nmi proc near
    test cs:_shared,STAT_WINDOWS_RUNNING ; see if windows is running
    jne write_windows_running
    push ax
    push bx

    test cs:_shared,STAT_COMBINED_IRQ    ; If NMIs and IRQs are shared, don't write
    jne not_nmi                  ; the NMI vector out
    push ds
    xor bx,bx                    ; load ds with 0 to talk to the vector table
    mov ds,bx
    mov bx,0Ah                   ; load bx with offset of NMI segment
    mov word ptr [bx],cs         ; store NMI segment in table
    mov bx,08h                   ; load bx with offset of NMI offset
    mov word ptr [bx],offset cs:_new_int02 ; store NMI offset

    in al,70H                    ; RTC Make sure NMis are enabled
    and al,07FH                  ; drive both bits low - enable I/O channel chk
    out 70H,al                   ; and enable NMI

    in al,61H                    ; 8255 Make sure NMis are enabled
    and al,0F7H                  ; drive bit low - enable I/O channel chk
    out 61H,al

    pop ds
not_nmi:
    pop bx
    pop ax
write_windows_running:
    ret
write_nmi endp

; Blow 41h to make loader spawn.
; This will work in or out of windows
force_spawn proc near
    mov dx,_reg_index
    mov al,41h
    out dx,al
    mov dx,_reg_data_high
    mov al,0
    out dx,al
    ret
force_spawn endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; testnspawn - Spawns the loader if SBOS cannot perform in the current state of
;   the machine
;
; DESCRIPTION:
;   Called from the INT 21h handler during an execute ONLY.
;   Read location in DRAM to verify SBOS is o.k.
;   Checks the interface for NMI's on the soundcard
;   If either are bad, the loader is spawned to reset the card to normal
;
; EXPECTS:
;   Must be called in the foreground - ie, no timers, keyboard, or NMIs
;   _reg_ registers must be setup
;   loader_param must point to a valid parameter block   
;
;-------------------------------------------------------------------------------
testnspawn proc near
    ;
    ; Switch stacks    - gotta do it here -- see note below
    ;
    push ax
    mov    cs:spawn_ss,ss              ; save old ss
    mov    cs:spawn_sp,sp              ; save old sp
    mov    ax, cs
    mov    ss, ax
    lea    sp, endstack
    ;
    push ds
    push cs
    pop  ds
    push es
    pushad
    ;
    ; Check the DRAM to see if it has been changed
    ;
    ;
    ; DRAM has not been changed
    ; Check ADLIB control register 
    ; If it does not equal adsbint_mask then someone messed with it
    ;
    mov dx,_reg_index               ;
    mov al,45h                      ; select adlib control register
    out dx,al                       ;
    mov dx,_reg_data_high           ; read contents of register
    in  al,dx                       ;
    cmp al,_adsbint_mask            ; compare with mask
    jne spawn_sbos                  ; if its same then all is OK - leave
    ;
    ; Read the dma control register (index 41h) and see if what we wrote
    ; before is still there
    ;
    mov dx,_reg_index
    mov al,41h
    out dx,al
    mov dx,_reg_data_high
    in al,dx
    and al,DMA_CONTROL_DEFAULT
    cmp al,DMA_CONTROL_DEFAULT
    jne spawn_sbos
    jmp no_spawn
spawn_sbos:
    ;
    ; if SYNTH is not ours, don't spawn
    ;
    cmp _dev_mode_synth, DEV_ASLEEP
    je no_spawn
    ;
    ; Execute loader to reload DRAMs and resetup the card for SB emulation 
    ;
    ; I used to switch stacks here but in games like stunts, this wouldn't
    ; work.  They have a .com file loader that spawns the game executable
    ; and has only a limited stack.  This was written originally with 
    ; the intention that we would be using a DOS stack here (big) and 
    ; wouldn't have any space problems.
    ;
    ; ES:BX - Parameter block
    ; DS:DX - Program name
    ;
    mov    ax,ds
    mov    es,ax                    ; load ES
    mov    dx,offset _loader_name   ; load offset loader name
    mov    bx,offset _loader_param  ; load offset parameter block
    mov    ax,4b00H                 ; execute program
    mov int21_bypass,1              ; Tell myself I'm executing the loader
    int    21h                      ; so as not to come through here again...
    mov int21_bypass,0              ; Tell myself I'm done (Hi me, I'm done!)
no_spawn:
    popad
    pop    es
    pop    ds
    ;
    ; Set the stack back to what it was
    ;
    mov    ss,cs:spawn_ss
    mov    sp,cs:spawn_sp
    pop ax
    ret
testnspawn endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; new_int7x - Communication interrupt handler
;
; DESCRIPTION:
;   Supports a custom API for SBOS communication and GAME API
;
;   Function 1:  MIDI Byte Out - Parse a single MIDI byte
;     Called With:                  Returns:
;        EAX  - 1                      EAX = 0 pass, -1 bad handle
;        BL   - MIDI Byte
;        DX   - Handle
;
;   Function 2:  MIDI String Out - Parse a string of MIDI data
;     Called With:                  Returns:
;        EAX  - 2                      EAX - 0 pass, -1 bad handle
;        ES:EDI - MIDI string
;        ECX  - Count
;        DX   - Handle
;
;   Other SBOS Specific Functions:
;      - 80 : Return the address of the shared data in dx:ax
;      - 81 : Notify driver that the loader has completed the load procedure
;      - 82 : Return Debugging values - see procedure for details
;      - 83 : Try to connect to the Windows VxD
;
;-------------------------------------------------------------------------------
_new_int7x proc far
    push ds
    push cs
    pop ds               ; ds is ok and old ds is on the stack
    test al,80h          ; see if the hi bit is on
    je i7x_midi          ; if it's off, its a midi call
    and al,7fh           ; clear upper bit for jump table
    cmp al,INT7x_NUM_FUNCS
    jge i7x_end          ; outta here if the number is too big
    mov bl,al            ; put command in bl
    xor bh,bh            ; clear bh
    shl bx,1             ; calc pointer in jump table
    xor ah,ah            ; get rid of the command and leave subfunction
    call [int7x_fct_tab+bx]  ; call function
    jmp i7x_end
i7x_midi:
    cmp _dev_mode_synth,DEV_GAME
    jne i7x_bad_end          ; LEAVE if SYNTH not allocated
    cmp dx,1
    jne i7x_bad_end          ; LEAVE if bad handle
    cmp ax,COM_MIDI_BYTE
    je midi_byte
    cmp ax,COM_MIDI_STRING
    je midi_string
    jmp i7x_bad_end
midi_byte:
IFDEF DEBUG
    push ax
    push dx
    mov ax,bx
    mov dl,GPR1W_CODE
    call near ptr _add_to_debug_table
    pop dx
    pop ax
ENDIF
    xor bh,bh
    mov ax,bx
    call near ptr _parse_midi
    xor eax,eax
    jmp i7x_end
midi_string:
    mov i7x_ptr_off,edi
    mov i7x_ptr_seg,es
midi_string_top:
    or ecx,ecx
    je i7x_end
    les edi, pword ptr i7x_ptr
    movzx ax,byte ptr es:[edi]
    push ecx
IFDEF DEBUG
    push ax
    push dx
    mov dl,GPR1W_CODE
    call near ptr _add_to_debug_table
    pop dx
    pop ax
ENDIF
    call near ptr _parse_midi
    pop ecx
    inc dword ptr i7x_ptr_off
    dec ecx
    xor eax,eax
    jmp midi_string_top
i7x_end:
    pop ds
    iret
i7x_bad_end:
    mov eax,-1
    pop ds
    iret
_new_int7x endp

i7x_ptr label pword
i7x_ptr_off dd ?
i7x_ptr_seg dw ?

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; int7x_shared - Returns far address of shared memory structure
; 
; DESCRIPTION:
;   int7x function 00 - return shared data address in DX:AX
;
; RETURNS: far pointer - DX:AX: location of shared memory structure
;
;-------------------------------------------------------------------------------
int7x_shared proc near
    mov dx,cs            ; send back the segment too...
    lea ax,_shared
    ret
int7x_shared endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; int7x_debug - Returns far address of debugging structure
; 
; DESCRIPTION:
;   int7x function 02 -  Return information for debugging
;
; EXPECTS:
;
; MODIFIES:
;
; RETURNS: 
;    DEBUG defined:
;       AX = 0
;       CX:DX = debug pointer
;    DEBUG not defined:
;       AX = -1
;
;-------------------------------------------------------------------------------
int7x_debug proc near
IFDEF DEBUG
    cmp ah,0              ; subfunction 0 - return pointer to debug pointers
    jne debug_not_0
    lea dx, _debug_ptrs
    mov cx,cs
    mov ax,0              ; I am in debug mode - don't want any confusion
    jmp debug_end
debug_not_0:
ELSE
    mov ax,-1             ; fail return code
ENDIF
debug_end:
     ret
int7x_debug endp

int7x_unload proc near
    test _gapi_status, GAME_SYNTH_STOLEN
    je unload_try_codec
    mov ah,_multiplex_id
    mov al,4
    movzx bx,_gapisrdid
    int 2fh
unload_try_codec:
    test _gapi_status, GAME_CODEC_STOLEN
    je unload_done
    movzx bx,_gapicrdid
    cmp bl,_gapisrdid
    je unload_done       ; if they're the same, don't bother
    mov ah,_multiplex_id
    mov al,4
    int 2fh
unload_done:
    ret
int7x_unload endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; int7x_winapi - Foreground code to connect to the Windows VxD
;
; DESCRIPTION:
;   int7x function 03 - Connect to MS Windows VxD for DOS BOX management
;   Performs tests to the system to make sure VxD connection can be made:
;     Makes sure that Windows is in Enhanced mode (int 2f Fctn. 1600h)
;     Get Device API Entry point to VxD and make sure its valid (Fctn. 1684h)
;     Call the API entry point with the correct registers for configuring VxD
;     Make sure return values reflect a present VxD
;
; MODIFIES:
;   shared.status
;
;-------------------------------------------------------------------------------
int7x_winapi proc near
    ;
    ; First, see if Windows is in enhanced mode
    ;

;    mov ax,1600h               ; MS Windows install check
;    int 2fh
;    and al,07fh                ; if al is 00 or 80 we can't run
;    je no_api_support

    ; Assume WIndows is in enhanced mode so we don't have to do a Windows
    ; check in a DOS box. (We want WIN95 DOS box to be able to NOT have
    ; DOS apps detect we are running under windows, but SBOS still needs
    ; to know....
    ;
    ; Windows is running in enhanced mode.  Now, get device API entry point
    ;
    printstring winmode
    mov ax,1684h               ; Get device API entry point
    mov bx,API_ID              ; Our Windows API ID number
    mov di,0
    mov es,di                  ; clear ES:DI for address
    int 2fh                    ; ES:DI setup
    ;
    ; ES:DI has API entry point
    ; IF ES:DI is not empty, we have a valid address
    ;
    mov ax,es
    cmp ax,0                   ; if ES is not zero, it's valid
    jne got_valid_address
    cmp di,0                   ; Otherwise, if ES is NULL and DI is NULL,
    je no_api_support          ; The address is invalid
got_valid_address:
    ;
    ; Now, we got a valid address from Windows
    ; Let's see if it's a good version of the VxD
    ; Call the address with parameters the VxD is expecting
    ; - ES:DI is an entry point to an SBOS routine for handling port writes
    ; - AX,BX are going to be some memory size stuff
    ; - DX is 4, telling VxD the communication is from SBOS
    ; - SI: [0] - high; MPU-401 mode active
    ;
    printstring winapientry
    mov _api_entry_seg,es      ; store the address
    mov _api_entry_off,di
    push cs
    pop es
    lea di,_win_api_entry
    test _shared,STAT_MPU401_ENABLED
    je api_no_mpu
    mov si,1
    printstring winmpuactive
api_no_mpu:
    and _shared, NOT STAT_WINAPI_ACTIVE
    mov dx,4                   ; Tell the VxD that SBOS is on the phone
    call dword ptr _api_entry  ; pray this works...
    cmp dx,0                   ; VxD will clear dx if it's there
    jne no_api_support         ; Old versions of the driver will leave it alone
    or _shared,STAT_WINAPI_ACTIVE ; We're alive, set the flag and leave
    printstring winapiactive
no_api_support:                ; something didn't work
    ret
int7x_winapi endp

int7x_ignore proc near
    or _shared,STAT_IGNORE_SPAWN
    ret
int7x_ignore endp

_TEXT ends
    end
