;-------------------------------------------------------------------------------
;       NAME:  STARTUP.ASM $Revision: 1.38 $
;       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."
;-------------------------------------------------------------------------------
; 
; OVERVIEW:
; Contains startup code for the sys and exe drivers that does not need to
; remain in memory.  This module name must be placed after the __END_MPU_LABEL
; marker on the link command line.
;
; If the startup code detects MPU401 mode the resident portion of the code
; will include all the MPU401 necessary procedures and some envelope
; information.  Otherwise, in FM emu mode, all of the MPU401 emu stuff 
; will not be included.  The memory structure looks like this:
;
; This module contains data which will not remain resident and is only used
; during the startup procedure.
;
; Memory map looks like:
;
;              FM emulation code
;              __END_BASE_LABEL
;              MPU401 emulation code - optional
;              __END_MPU_LABEL
;              WAVE_INFO
;              MPU-401 INFO          - optional
;
;-------------------------------------------------------------------------------
.386

INCLUDE sbosdefs.inc
INCLUDE version.inc
INCLUDE error.inc
INCLUDE viwd.inc

_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

;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; LIST OF PUBLICS
;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
public __EXESTART       ; for map file only
public init
public strat
public _psp_seg_addr
public _env_seg_addr
public _cmd_line_args
public _cmd_line_argv0
public _alloc_seg
public req_hdr
public _num_paras
public _sbos512
public _sbos1024
public _verstring2
public _default_banner
public _error_string

public _crlf
public _sbos_ini_hdr
public _setup_ini_hdr
public _sbos_mls
public _library512
public _library1024
public _lib_path
public _ini_path
public _csn
public _pnprdp
public _Mpu401Emulation
public _memcfg
public _chainnmi
public _seveneee
public _SbosVector
public _quietst
public _quiet
public _on
public _languages
public _defaultstr
public _mls_path
public _mls_language
public _mls_default
public _mls_iwini
public _mls_file
public _iwave_string
public _sbos_string
public _fff_string

;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; LIST OF EXTERNS
;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
extrn endstack:word
extrn _find_sbos:near
extrn _sbos_mls_init:near
extrn _setup_registers:near
extrn _test_base_port:near
extrn _size_dram:near
extrn _dram_size:byte
extrn _hook7x:near
extrn _lower_base_port:word
extrn _upper_base_port:word
extrn _codec_base_port:word
extrn _orig_dma1:byte
extrn _orig_dma2:byte
extrn _orig_irq1:byte
extrn _orig_irq2:byte
extrn _sb_irq:byte
extrn _sb_dma:byte
extrn _shared:word
extrn _mpu401_port:word
extrn _mpu401_irq:byte
extrn _comm_vect:byte
extrn _do_fixup:near
extrn _setup_driver:near
extrn _test_dram_size:near
extrn _multiplex_id:byte
extrn _print_version:near
extrn _install_id:word
extrn _gapicrdid:byte
extrn _gapisrdid:byte
extrn _gapi_status:byte

extrn _pnp_read:word
extrn _pnp_csn:word    
extrn _pnp_index:word 
extrn _pnp_write:word
extrn _setup_pnp:near
extrn _iw_get_comm_vect:near
extrn _iw_get_mpu_opt:near
extrn _iw_get_memcfg:near
extrn _iw_get_quiet:near
extrn _iw_get_nmi_opt:near
extrn _iw_test_iw_ini:near

EXE_START equ 1
SYS_START equ 2
ID_START equ 0cdh
ID_END   equ 0ech

; MACROS
TestResult MACRO
cmp ax,0
jne exit
ENDM

SysTestResult MACRO
cmp ax,0
jne sys_exit
ENDM

_TEXT           segment byte public use16 'CODE'
assume          cs:DGROUP, ds:DGROUP, ss:DGROUP
;
; These stamps are used to locate text in both the environment and the
; argument list for the driver.
;
ini_stamp       db  "INTERWAVE="
load_stamp      db  "LOW"
quiet_stamp     db  "QUIET"
;
; These defines are the lengths of the above stamps for the string search alg.
;
INI_STAMP_LEN   equ 10
ULTRA_STAMP_LEN equ 0Ah
VECT_STAMP_LEN  equ 02h
LOAD_STAMP_LEN  equ 03h
QUIET_STAMP_LEN equ 05h

IFDEF MPU401
mpu401_stamp    db  "MPU401"
MPU_STAMP_LEN   equ 06h
ENDIF

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; __EXESTART - Executable entry point.  All driver setup procedures called
;   from here
;
; DESCRIPTION:
;   Sets stack to TOP_STARTUP_STACK for all startup needs
;   Checks to see if there is another instance of IWSBOS loaded
;       If there is another instance, the driver is unloaded
;   Saves pointers to environment, command line, commnad line arguments and PSP
;   Performs DRAM test to determine if card is present and quantity of DRAM
;       If DRAM amount is too low, the driver is unloaded
;   Reads command line and/or iw.ini file to determine loading options
;   If the driver is to remain resident:
;       Loads itself into upper memory unless told not to
;          - performs fixups on any segment data if copied high
;       Hooks a communication vector (usually 7eh)
;       Goes resident using DOS int 21h funcion 31h
;
; EXPECTS: called from DOS in the foreground
;
; MODIFIES: everything
;
;-------------------------------------------------------------------------------
__EXESTART proc far
    mov ax,cs
    mov ss,ax
    lea sp,__TOP_STARTUP_STACK
    mov bp, sp
    push cs
    pop ds                          ; set ds to cs
    mov _psp_seg_addr,es            ; store address of PSP
    call near ptr _find_sbos        ; see if I'm here already
    TestResult                      ; AX=0, SBOS is not loaded, continue
    call near ptr win_check         ; See if windows is running
    mov sys_exe,EXE_START           ; let startup code know start is from exe
    call near ptr save_env          ; save the env and cmd line pointers
    call near ptr strupr            ; convert command line to upper case
    call near ptr _get_ini_path     ; get INTERWAVE iw.ini path from environment
    call near ptr _iw_test_iw_ini   ; see if the iw.ini file is there
    TestResult
    call near ptr _sbos_mls_init    ; Initialize the Multi Language Support
    call near ptr _get_quiet_opt    ; get quiet setting on command line
    call near ptr _iw_get_nmi_opt   ; see if we should chain the NMI
    call near ptr _iw_get_quiet     ; get quiet setting in iw.ini
    call near ptr _print_version    ; Print version 
    call near ptr get_2f_id         ; Get a multiplex ID
    TestResult                      ; AX = 0, passed 2f check, continue
    call near ptr get_install_id    ; Get int 2f InterWav Install ID number
    call near ptr test_2f_id        ; See if we can get the card
    TestResult
    call near ptr _setup_pnp        ; read ini file and get pnp data
    TestResult
    call near ptr _iw_get_memcfg    ; get the memory configuration info from ini
    TestResult                      ; AX=0, Memory config is OK, continue
    mov ax,EXE_START                ; search env for MAX compile
    call near ptr _get_base_port    ; look in system for baseport or use PNP
    call near ptr _test_base_port   ; make sure the base port is not ffff
    TestResult
    call near ptr _setup_registers  ; setup registers for use in the driver
    call near ptr _size_dram        ; get the size of dram to make room for data
    call near ptr _test_dram_size   ; see if the dram size found is OK
    TestResult                      ; AX=0, dram size is OK
IFDEF MPU401
    call near ptr _get_mpu_opt      ; see if user wants mpu401 emulation
ENDIF
    call near ptr _get_comm_vect    ; Look in cmd line for change in comm vect
    TestResult                      ; AX=0, Vector is OK, continue
    call near ptr _get_load_opt     ; See if user does not want to load high
    call near ptr _setup_driver     ; open lib, get paras and other info
    TestResult                      ; AX=0, libs are OK, continue
    ;
    ; At this point - we ARE going to stay resident.
    ; Any tests after this should not jmp to exit
    ; Note that if int21-4bh is called, the environment is freed automatically
    ; as DOS clears up the memory from the executable.  If it is NOT called
    ; (if we go resident) we need to free it manually
    ; Be careful that nothing is changed in the system that DOS can't fix if
    ; we exit without staying resident (like hooking vectors or such)
    ;
    call near ptr free_env          ; free the env
    call near ptr _hook7x           ; hooks only 7x vector for comm with loader
    add _num_paras,10h              ; Account for the PSP for driver size
    ;
    ; Several things here can hinder the auto loading high of the driver:
    ; - User forces load_low to 1
    ; - User typed lh or loadhi before driver name and driver is already high
    ; - Cannot allocate high memory
    ;
    cmp load_low,01h                ; 0 = load high - 1 = load low
    je short remain_low             ; if 1 bypass load high stuff
    call near ptr high_alloc        ; try to allocate high memory
    cmp ax,0                        ; see if it passed - 0 is fail
    je short remain_low             ; can't allocate memory - skip
    call near ptr copy_to_high      ; copy code to high memory block
    ;
    ; NO data in driver data space should change after this point
    ; Once data is copied high and fixed up, this copy of _shared is invalid
    ; and any changed made will not be saved!!!
    ;
    call near ptr _do_fixup         ; change memory to point high
    call near ptr setup_psp         ; setup the new psp and free this one
remain_low:
    mov dx,_num_paras               ; get number of paras
    mov ax,3100h                    ; Fill out the application
    int 21h                         ; Apply for residency
exit:
    mov ax,4c00h
    int 21h                         ; end program normally
__EXESTART endp

search_id db ID_START
first_id        db 0
;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; get_2f_id - 
;
; DESCRIPTION:
;   Calls the 2f chain with ID numbers starting at ID_START and ending with 
;   ID_END searching for a free ID number or one occupied by an InterWave TSR.
;
; EXPECTS:
;   search_id = ID_START 
;
; MODIFIES:
;   all registers
;
; SEE ALSO:
;   init, __EXESTART
;
;-------------------------------------------------------------------------------
get_2f_id  proc near
id_start_search:
    mov ah,search_id      ; id number
    mov al,0              ; init check
    mov dx, 'IW'
    mov si, 'VE'
    int 2fh
    cmp al,0ffh
    je short used_id

    cmp first_id,0
    jne short already_got_lowest
    mov al,search_id      ; keep first free ID number
    mov first_id,al
already_got_lowest:
    jmp short do_next_id

used_id:
    cmp dx,'ET'
    jne short do_next_id  ; and it's not one of ours
    cmp si,'EK'
    jne short do_next_id  ; and it's not one of ours
    mov first_id,ah       ; InterWave driver located
    jmp short done_id

do_next_id:
    inc search_id
    cmp search_id,ID_END
    ja done_id
    jmp short id_start_search
done_id:
    cmp first_id,0
    jne got_valid_id
    mov ax,-1                 ; No free IDs available
	ret
got_valid_id:
    mov al,first_id
    mov _multiplex_id,al      ; save the id
    mov ax,0
    ret
get_2f_id  endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; test_2f_id - 
;
; DESCRIPTION:
;
; EXPECTS:
;
; MODIFIES:
;
; SEE ALSO:
;
;-------------------------------------------------------------------------------
test_2f_id proc near
    mov ax,_install_id
    mov id_test,ax
test_top:
    dec id_test         ; between 0 and install_id
    cmp id_test,0ffffh
    je test_ok
test_try_synth:
    mov ah,_multiplex_id
    mov al,2
    mov bx,id_test
    int 2fh             ; function 2 - get status and information
    test cx,1           ; bit 1 - synth
    jne test_get_synth
    jmp test_try_codec
test_get_synth:         ; get synth from program 
    mov ah,_multiplex_id
    mov al,3
    mov bx,id_test
    mov cx,1            ; get synth
    int 2fh
    cmp al,0
    jne test_out_of_luck
    mov ax,id_test
    mov _gapisrdid,al   ; save id number
    or _gapi_status, GAME_CODEC_STOLEN
test_try_codec:
    mov ah,_multiplex_id
    mov al,2
    mov bx,id_test
    int 2fh             ; function 2 - get status and information
    test cx,2           ; bit 2 - codec
    jne test_get_codec
    jmp test_top
test_get_codec:
    mov ah,_multiplex_id
    mov al,3
    mov bx,id_test
    mov cx,2            ; get codec
    int 2fh
    cmp al,0
    jne test_out_of_luck 
    mov ax,id_test
    mov _gapicrdid,al   ; save id number
    or _gapi_status, GAME_SYNTH_STOLEN
    jmp test_top
test_ok:
    mov ax,0
    ret
test_out_of_luck:
    mov ax,ERROR_IW_TSR
    push ax
    call _print_error
    add sp,2
    mov ax,-1
    ret
test_2f_id  endp

id_test dw 0

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; get_install_id - Assigns an InterWav int 2f Install ID number
;
; DESCRIPTION:
;
; EXPECTS:
;
; MODIFIES:
;
; SEE ALSO:
;
;-------------------------------------------------------------------------------
get_install_id  proc near
    mov ah,_multiplex_id
    mov al,1
    mov bx,0
    mov dx,'IW'
    mov si,'VE'
    int 2fh
    mov _install_id,bx
    ret
get_install_id  endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; win_check -  performs a check to see if MS Windows is currently running
;
; DESCRIPTION:
;   Calls int 2fh function 1600h and returns the return value in al
;
; MODIFIES:
;   ax
;
; RETURNS:
;   al - MS Windows running status
;      x000 0000 - not running
;      0000 0001 - 2.X running
;      1111 1111 - 2.X running
;      else      - AL = major
;                - AH = minor
;
;-------------------------------------------------------------------------------
win_check proc near
;    mov ax,1600h
;    int 2fh
;    cmp al,0
;    je win_not_running
;    or _shared,STAT_WINDOWS_RUNNING
;   ret
;win_not_running:
    ; Windows is running in enhanced mode.  Now, get device API entry point
    ;
    and _shared,NOT STAT_WINDOWS_RUNNING
    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
    je win_not_running
    cmp di,0                   ; Otherwise, if ES is NULL and DI is NULL,
    je win_not_running         ; The address is invalid
    ;
    or _shared,STAT_WINDOWS_RUNNING
win_not_running:
    ret
win_check endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; save_env - sets pointers to environment, command line, command line arguments
;
; DESCRIPTION:
;   Looks in PSP segment at offset 02ch for word segment pointer to environment
;   Stores pointer to environment
;   Stores pointer to command line arguments in PSP offset 81h
;   Finds full pathname of the driver at the end of the environment
;       The environment is terminated by two NULLs plus two bytes (0,0,1,0)
;       The byte following the last terminator is the start of the drive path 
;   Copies full pathname in to local memory
;
; EXPECTS:
;   psp_seg_addr - segment of PSP for this program
;
; MODIFIES: ax,cx,si,di,es
;
;-------------------------------------------------------------------------------
save_env proc near
    mov es,_psp_seg_addr          ; ES is PSP segment
    mov si,2ch                    ; ES:SI -> ptr to environment (segment)
    mov ax,es:[si]                ; get environment segment
    mov _env_seg_addr,ax          ; 
    mov _cmd_line_args,81h        ; ES:81h is cmd line arguments
    mov _cmd_line_args+2,es
    mov di,0
    mov es,ax                     ; ES:DI is start of environment
    mov al,0                      ; search for 0 (end of env is 0,0)
    cld                           ; search up
    mov cx,0ffffh                 ; don't want to stop for a while
next_env_string:
    repnz scasb                   ; search for the next NULL - ES:DI is next ch
    cmp byte ptr es:[di],0        ; see if there are two 0s in a row
    je end_of_env
    loop next_env_string          ; should this be jmp?
end_of_env:                       ;         v------< pointing to here
    add di,3                      ; ENV..,0,0,1,0,C:\PROGDIR\PROGNAME.EXE
    mov cx,7fh
    lea si,_cmd_line_argv0
    xchg si,di                    ; offsets are setup
    push ds                       ; save DS
    push ds
    push es                       ; swap DS and ES for copy
    pop ds
    pop es
    rep movsb                     ; DS:SI  -->  ES:DI
    pop ds                        ; restore DS
    ret
save_env endp

;
; These variables have to do with the psp, environment, and command line
; during startup ony.  These get destroyed after the driver goes resident
;
_cmd_line_args  dw ?,?     ; Pointer to command line arguments
_cmd_line_argv0 dw 7fh dup(0) ; copy of executable path and name in environment
_env_seg_addr   dw ?       ; Pointer to environment segment
_psp_seg_addr   dw ?       ; Pointer to PSP segment
_num_paras      dw ?       ; Number paragraphs to go resident with
load_low        db 0       ; Load high by default
_quiet          db 0       ; Should the driver print out banner messages?
sys_exe         db EXE_START ; 1 for EXE   --  2 for sys
;
; These are backup values for how DOS was setup prior to us trying to load
; high.  The last is the return value from the allocate procedure
;
old_strat       dw ?        ; old DOS allocation strategy 
old_umb_state   db ?        ; old UMB status
_alloc_seg      dw 0        ; return value for high_alloc procedure

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; high_alloc - Tries to allocate high memory for all resident code and data
;
; DESCRIPTION:
;   Sets up DOS to allocate memory in the high memory area adding UMBS 
;     to DOS memory chain
;   Resets DOS allocation methods to the way they were upon completion
;
; MODIFIES: ax,bx
;
; RETURNS:
;   AX = segment of the new memory block
;      = 0 if failed
;
;-------------------------------------------------------------------------------
high_alloc proc near
    mov ax,cs              ; get seg in ax for compare
    cmp ax,0a000h          ; compare to high memory boundary
    jl short end_high_alloc; program already loaded high - jump to end
    ;
    ; Get DOS Memory allocation strategy
    ;
    ; return value is initialized to zero in case of fail
    ;
    mov ax,5800h           ; get DOS allocation strategy 
    int 21h
    mov old_strat,ax       ; save old one
    jc short end_high_alloc; leave in case of error
    ;
    ; Get DOS Upper Memory Block link state
    ;
    mov ax,5802h           ; get UMB link state
    int 21h
    mov old_umb_state,al   ; store old
    jc short reset_strat   ; this failed so reset what's changed
    ;
    ; Set UMB link state to add UMBS to DOS memory chain
    ; This doesn't seem to work if we don't link the blocks
    ;
    mov bx,1               ; link UMBs
    mov ax,5803h           ; set link status
    int 21h
    ;
    ; Set new allocation strategy for high memory - first fit
    ; Questionable allocation strategy - first vs. last vs. best ???
    ;
    mov bx,40h             ; high memory first fit
    mov ax,5801h           ; set allocation strategy
    int 21h
    ;
    ; Try to allocate high memory
    ;
    mov bx,_num_paras      ; load length
    mov ax,4800h           ; allocate memory
    int 21h
    jc short allocate_fail ; carry set on fail
    cmp ax,0a000h          ; compare to upper memory boundary
    jl short allocate_fail ; if less than boundary - allocate failed
    mov _alloc_seg,ax      ; set return value on pass
allocate_fail:
    ;
    ; Set back UMB link state
    ;
    xor bx,bx
    mov bl,old_umb_state   ; get old link state
    mov ax,5803h           ; set link state
    int 21h
    ;
    ; Set back allocation strategy
    ;
reset_strat:
    mov bx,old_strat       ; get old strategy
    mov ax,5801h           ; set strategy
    int 21h
end_high_alloc:
    mov ax,_alloc_seg      ; return return value
    ret
high_alloc endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; copy_to_high - Copies all to be resident code to allocated upper memory block
;
; DESCRIPTION:
;   Performs a block copy of all code and data which will remain resident to 
;       upper memory
;
; EXPECTS:
;   _alloc_seg     - segment of upper memory area to be used
;   _psp_seg_addr  - segment of this PSP
;   num_paras      - number of paragraphs of memory to keep resident
;
; MODIFIES: cx,data block pointed to by alloc_seg
;
;-------------------------------------------------------------------------------
copy_to_high proc near
    push ds                ; save seg registers
    push es                ; save seg registers
    cld                    ; I want to count up not down
    mov si, 0              ; offsets are zero
    mov di, 0              ; offsets are zero
    mov es, _alloc_seg     ; get high memory segment
    mov cx, _num_paras     ; load number of paragraphs to move
    mov ds, _psp_seg_addr  ; get psp segment
    shl cx,3               ; number of words to move (paras << 3)
    repnz movsw            ; do the move
    pop es                 ; restore seg registers
    pop ds                 ; restore seg registers
    ret
copy_to_high endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; setup_psp() - Frees this PSP so DOS thinks upper memory block has control
; 
; DESCRIPTION:
;   Sets the PSP of the upper memory block to the active one and frees this PSP
;   This effectivly tells DOS not to free the upper memory block when the lower
;       one is freed.
;   expects the driver to be resident in upper memory PSP and not this one
;   expects alloc_seg to be segment of correctly allocated memory block
;
; EXPECTS:
;   alloc_seg    - segment of allocated upper memory block of the correct size
;   psp_seg_addr - segment of the PSP for this executable
;
; MODIFIES: ax,bx,cx,es
;
;-------------------------------------------------------------------------------
setup_psp proc near
    mov bx,_alloc_seg       ; high memory address
    mov ax,5000h            ; set psp
    int 21h
    ;
    ; Free the old PSP - The one for this cs
    ;
    mov es,_psp_seg_addr    ; this psp segment address
    mov ax,4900h            ; free this program space - should never fail
    int 21h                 ; blows cx
    ret
setup_psp endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; free_env - Frees the environment
; 
; DESCRIPTION:
;   Frees the environment given in _env_seg_addr
;   Does NOT check for failure
;
; EXPECTS:
;   env_seg_addr - segment of the environment for this PSP
;
; MODIFIES: ax,es,cx
;
;-------------------------------------------------------------------------------
free_env proc near
    mov ax,_env_seg_addr
    mov es,ax
    mov ax,4900h           ; Free allocated memory block
    int 21h                ; Should never fail
    ret
free_env endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; get_ini_path - Parses command line searching for string which specifies the
;    location of the iw.ini file
; 
; DESCRIPTION:
;   Parses command line for the executable searching for:
;       INTERWAVE= - path for iw.ini file
;   Expects _env_seg_addr to hold the segment of the environment for this PSP
;   Copies path to ini_path in local memory
;
; EXPECTS:
;   env_seg_addr - pointer to the segment for this PSP
;   ini_stamp    - stamp we're looking for
;
; MODIFIES: ax,cx,si,di,es,ini_path
;
;-------------------------------------------------------------------------------
_get_ini_path proc near
    cmp sys_exe,EXE_START
    je ini_exe
    les di,dword ptr _cmd_line_args ; otherwise use cmd_line_args
    jmp ini_drv
ini_exe:
    mov es,_env_seg_addr          ; environment segment
    xor di,di                     ; beginning of environment paragraph
ini_drv:
    lea si,ini_stamp
    mov ax, INI_STAMP_LEN         ; # bytes in ini_stamp
    call near ptr string_search   ; search for ini_stamp in command line
    jnc short no_interwave        ; carry not set - no string found - just leave
    ;
    ; INTERWAVE= found in environment at ES:DI
    ; Now I have to switch source and destination registers for the movsb
    ;
    cld                           ; count up
    mov cx,07fh                   ; this many chars maximum
    push ds
    push es                       ;
    pop ds                        ; DS is now setup
    mov si,di                     ; DS:SI is source
    push cs
    pop es                        ; ES is now setup
    lea di,_ini_path
    rep movsb                     ; copy string DS:SI --> ES:DI
    pop ds                        ; DS is now CS again
    ;
    ; The sys driver gets the INTERWAVE= off the command line
    ; The command line is terminated with a 0Dh 0Ah and we need to find the
    ; first space or the first 0Dh after INTERWAVE= and make it a NULL
    ;
    cmp sys_exe,EXE_START
    je ini_exe2
    sub di,7fh                    ; move di back 7f bytes - our copy of string
    mov al,0dh 
    mov cx,7fh
    repne scasb                   ; search ES:DI for al
    mov byte ptr [di-1],0
ini_exe2:
no_interwave:
    ret
_get_ini_path endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; get_quiet_opt - Parses command line searching for string which forces the 
;   driver not to print out banner messages
; 
; DESCRIPTION:
;   Parses command line for the executable searching for:
;       QUIET - Force the driver to be quiet
;   Expects _cmd_line_args to hold a pointer to the command line
;
; EXPECTS:
;   cmd_line_args - pointer to command line arguments
;   quiet_stamp    - stamp for what we're looking for
;
; MODIFIES:
;   all registers
;
;-------------------------------------------------------------------------------
_get_quiet_opt proc near
    les di, dword ptr _cmd_line_args
    lea si,quiet_stamp
    mov ax, QUIET_STAMP_LEN ; # bytes in load_stamp
    call near ptr string_search  ; search for load stamp in command line
    jnc short leave_quiet        ; carry not set - no string found - just leave
    ;
    ; Quiet option found as parameter
    ;
    mov _quiet,01h             ; save option
leave_quiet:
    ret
_get_quiet_opt endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; get_load_opt - Parses command line searching for string which forces the 
;   driver not to load itself into upper memory
; 
; DESCRIPTION:
;   Parses command line for the executable searching for:
;       LOW - Force the driver to remain in low memory - DO not load high
;   Expects _cmd_line_args to hold a pointer to the command line
;
; EXPECTS:
;   cmd_line_args - pointer to command line arguments
;   load_stamp    - stamp for what we're looking for
;
; MODIFIES:
;   all regs
;
;-------------------------------------------------------------------------------
_get_load_opt proc near
    les di, dword ptr _cmd_line_args
    lea si,load_stamp
    mov ax, LOAD_STAMP_LEN ; # bytes in load_stamp
    call near ptr string_search  ; search for load stamp in command line
    jnc short leave_load         ; carry not set - no string found - just leave
    ;
    ; Load Low option found as parameter
    ;
    mov load_low,01h             ; save option
leave_load:
    ret
_get_load_opt endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; get_comm_vect - Parses command line searching for string which changes the
;   communication vector
; 
; DESCRIPTION:
;   Parses command line for the executable & device driver searching for:
;       C7  - sets the communication vector to 7x, if x is above 8 
;             sets it to 7e if not present
;   Expects _cmd_line_args to hold a pointer to the command line
;   Modifies output string to reflect change in comm vector
;   Prints string
;
;-------------------------------------------------------------------------------
_get_comm_vect proc near
    call near ptr _iw_get_comm_vect
    ret
_get_comm_vect endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; get_mpu_opt - Parses command line searching for string which enables MPU-401
;   emulation
; 
; DESCRIPTION:
;   Parses command line for the executable  & device driver searching for:
;       MPU401 - turn on MPU401 emulation
;       MPU401=3xx - set MPU401 emulation address
;   Expects _cmd_line_args to hold a pointer to the command line
;   Modifies output string to reflect change in emulation
;   Prints string
;
;-------------------------------------------------------------------------------
IFDEF MPU401
_get_mpu_opt proc near
    call near ptr _iw_get_mpu_opt
    les di, dword ptr _cmd_line_args
    lea si,mpu401_stamp
    mov ax, MPU_STAMP_LEN  ; # bytes in mpu401 stamp
    call near ptr string_search     ; search for mpu401 stamp in command line
    jnc short leave_mpu          ; carry not set - no string found - just leave
    ;
    ; MPU401 emulation flag found as executable parameter
    ;
    or _shared,STAT_MPU401_ENABLED; tell shared I'm in 401 mode
    ;
    ; If there is an '3' two bytes after the MPU401 stamp then we have to get
    ; the MPU401 base port too
    ;
    ; DOS SUCKS!!!!!  It appears as though the '=' sign will not get passed
    ; into the batch file if sbos mpu401=300 is passed in so I have to search
    ; for the '3'
    ; DOS apparently gets rid of all redirection characters in the command
    ; tail before it copies it to the PSP.   OOOooooohhh.
    ;
leave_mpu:
    ret
_get_mpu_opt endp
ENDIF

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; hex2num - Converts an ASCII character to a HEX number between 0 and F
;
; DESCRIPTION:
;   Converts the ASCII character in AL to a HEX number between 0 and F
;   Expects al to be 0-9, a-z, or A-Z
;   If the number is above an 'f', returns 0x0f
;
; RETURNS: char - number from 0 to f reflecting conversion of input ASCII char
;
;-------------------------------------------------------------------------------
hex2num proc near
    cmp al,39h             ; compare to '9'
    jbe short digit
    cmp al,46h             ; compare to 'F'
    jbe short capital
    sub al,20h             ; assume its a-z
capital:
    sub al,7h
digit:
    sub al,30h
    cmp al,0fh
    jb leave_hex2num       ; if it's above f make it an f
    mov al,0fh
leave_hex2num:
    ret
hex2num endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; string_search - searches an ASCIIZ string for a match
;
; DESCRIPTION:
;   Searches a string for a match until a 0Dh or 00h,00h is found in the 
;       search field:
;   The end of the environment is a NULL (00h)
;   The end of the command line is a CR (0dh) 
;   expects: DS:SI - source - search pattern
;   expects: ES:DI - destination - search field
;   expects: AX    - number of bytes in search pattern
;
; RETURNS:
;   Carry set - found match
;       ES:DI - pointer to byte after matching pattern
;   Carry clr - no match found
;
;-------------------------------------------------------------------------------
string_search proc near
    xor bx,bx                  ; incrementor
    mov di_sav,di              ; save offset of destination
    mov si_sav,si              ; save offset of source
start_compare:
    mov di,di_sav              ; restore offset destination
    mov si,si_sav              ; restore offset source
    add di,bx                  ; increment through the environment
    inc bx                     ; next time test one more byte
    cmp word ptr es:[di],00h   ; if the next word is a NULL
    je short no_match_found    ; Outtahere
    cmp byte ptr es:[di],0dh   ; if the next char is a CR
    je short no_match_found    ; Outtahere
    mov cx,ax                  ; there are ax bytes in the string
    repe cmpsb                 ; do the compare
    jne short start_compare    ; not found? go back up to the top and restart
found_match:
    stc                        ; set carry 
    jmp short end_s_s
no_match_found:
    clc                        ; clear carry
end_s_s:
    ret
string_search endp
;
; These are used in the string_search procedure as backups for used registers
;
si_sav    dw  ?                ; backup of si
di_sav    dw  ?                ; backup of di

;
; Windows (3.1 or '95) VxD entry point
;
_vxd_entry       label   dword
_vxd_entry_off   dw      0
_vxd_entry_seg  dw      0
loop_cnt        db      0
msb             db      0
;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; get_base_port - Searches location for string which sets baseport location 
; 
; DESCRIPTION:
;   IF compiled for the MAX:
;       Searches for the string "ULTRASND=2" and calculates the card's
;       base location.  This is called from both startup routines 
;           called from __EXESTART - use environment
;           called from init()     - use cmd_line_args
;       Stores the base location in the _lower_baseport memory location.
;       If the ULTRASND variable is not found in either location, 220h is used.
;   IF compiled for the INTERWAVE:
;     
;-------------------------------------------------------------------------------
_get_base_port proc near
    ; 
    ; See if Windows is running...
    ; If so, get the device information from the VxD
    ; Otherwise, get it from the PnP Stuff
    ; 
;    mov ax,1600h               ; MS Windows install check
;    int 2fh
;    and al,07fh                ; if al is 00 or 80 we can't run
;    je not_vxd
    ;
    ; Windows is running in enhanced mode.  Now, get device API entry point
    ;
    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 not_vxd                  ; The address is invalid
got_valid_address:
    ;
    ; Now, we got a valid address from Windows into the VxD
    ;
    mov _vxd_entry_seg,es      ; store the address
    mov _vxd_entry_off,di
    ;
    ; Get the configuraion information from the VxD
    ;
    mov dx,7                   ; VxD Config

    mov ax,IWGRC_SYNTH_BASE_2X
    call dword ptr _vxd_entry
    jc not_vxd               ; carry set from VxD if function not supported
    mov _lower_base_port,ax

    mov ax,IWGRC_SYNTH_BASE_3X
    call dword ptr _vxd_entry
    mov _upper_base_port,ax

    mov ax,IWGRC_CODEC_BASE
    call dword ptr _vxd_entry
    mov _codec_base_port,ax

    mov ax,IWGRC_DMA_1
    call dword ptr _vxd_entry
    mov _orig_dma1,al

    mov ax,IWGRC_DMA_2
    call dword ptr _vxd_entry
    mov _orig_dma2,al

    mov ax,IWGRC_IRQ_1
    call dword ptr _vxd_entry
    mov _orig_irq1,al

    mov ax,IWGRC_IRQ_2
    call dword ptr _vxd_entry
    mov _orig_irq2,al

    mov ax,IWGRC_SB_IRQ
    call dword ptr _vxd_entry
    mov _sb_irq,al

    mov _sb_dma,1           ; set the resource here ....

    mov ax,IWGRC_MPU_BASE
    call dword ptr _vxd_entry
    mov _mpu401_port,ax

    mov ax,IWGRC_MPU_IRQ
    call dword ptr _vxd_entry
    mov _mpu401_irq,al

    jmp done_get_base_port
not_vxd:
; FIRST SEND THE KEY

    mov    bl,6Ah
    mov    dx,_pnp_index
    mov    al,0
    out    dx,al
    out    dx,al
    mov    dx,_pnp_index
    mov    al,bl
    out    dx,al
    mov    loop_cnt,1
    jmp    short key_loop_chk
key_loop:
   ;    
   ;        msb = ((code&0x01)^((code&0x02)>>1))<<7;
   ;    
    mov    al,bl
    mov    ah,0
    and    ax,2
    sar    ax,1
    mov    dl,bl
    and    dl,1
    xor    dl,al
    mov    cl,7
    shl    dl,cl
    mov    msb,dl
   ;    
   ;        code = (code>>1)|msb;
   ;    
    mov    al,bl
    mov    ah,0
    sar    ax,1
    or    al,msb
    mov    bl,al
   ;    
   ;        OS_OUTPORTB(pidxr,code);
   ;    
    mov    dx,_pnp_index
    mov    al,bl
    out    dx,al
    inc    loop_cnt
key_loop_chk:
    cmp    loop_cnt,32
    jb    short key_loop

    mov dx,_pnp_index       ; wake up the card ....
    mov al,IW_PWAKEI
    out dx,al
    mov dx,_pnp_write
    mov ax,_pnp_csn         ; pnp_csn
    out dx,al

; SYNTH WAKE
    mov dx,_pnp_index       ;  Set the logical device number for SYNTH
    mov al,IW_PLDNI
    out dx,al
    mov dx,_pnp_write
    mov al,IWL_PNP_AUDIO    ; logical dev #
    out dx,al

; SYNTH LOWER
    mov dx,_pnp_index       ; Read the address control registers
    mov al,IW_P2X0HI
    out dx,al
    mov dx,_pnp_read
    in al,dx
    xchg al,ah              ; save high address
    mov dx,_pnp_index
    mov al,IW_P2X0LI
    out dx,al
    mov dx,_pnp_read
    in al,dx
    mov _lower_base_port,ax

; SYNTH UPPER
    mov dx,_pnp_index      ; Now read uppper baseport
    mov al,IW_P3X0HI
    out dx,al
    mov dx,_pnp_read
    in al,dx
    xchg al,ah             ; save high address
    mov dx,_pnp_index
    mov al,IW_P3X0LI
    out dx,al
    mov dx,_pnp_read
    in al,dx
    mov _upper_base_port,ax

; IRQ1
    mov dx,_pnp_index
    mov al,IW_PUI1SI
    out dx,al
    mov dx,_pnp_read
    in al,dx
    xor ah,ah
    mov _orig_irq1,al

; IRQ2
    mov dx,_pnp_index
    mov al,IW_PUI2SI
    out dx,al
    mov dx,_pnp_read
    in al,dx
    xor ah,ah
    mov _orig_irq2,al

; SYNTH DMA 1
    mov dx,_pnp_index
    mov al,IW_PUD1SI
    out dx,al
    mov dx,_pnp_read
    in al,dx
    xor ah,ah
    mov _orig_dma1,al

; SYNTH DMA 2
    mov dx,_pnp_index
    mov al,IW_PUD2SI
    out dx,al
    mov dx,_pnp_read
    in al,dx
    xor ah,ah
    mov _orig_dma2,al

; CODEC BASE
    mov dx,_pnp_index       ; Read the address control registers
    mov al,IW_PHCAI
    out dx,al
    mov dx,_pnp_read
    in al,dx
    xchg al,ah              ; save high address
    mov dx,_pnp_index
    mov al,IW_PLCAI
    out dx,al
    mov dx,_pnp_read
    in al,dx
    mov _codec_base_port,ax

; SB WAKE
    mov dx,_pnp_index       ;  Set the logical device number for SB emul
    mov al,IW_PLDNI
    out dx,al
    mov dx,_pnp_write
    mov al,IWL_PNP_ADLIB    ; logical dev # SB device
    out dx,al

; SB IRQ
    mov dx,_pnp_index
    mov al,IW_PSBISI
    out dx,al
    mov dx,_pnp_read
    in al,dx
    xor ah,ah
    mov _sb_irq,al

; SB DMA
    mov _sb_dma,1           ; set the resource here ....

; MPU401 WAKE
    mov dx,_pnp_index       ;  Set the logical device number for MPU emul
    mov al,IW_PLDNI
    out dx,al
    mov dx,_pnp_write
    mov al,IWL_PNP_MPU401   ; logical dev # for MPU401
    out dx,al

; MPU401 PORT
    mov dx,_pnp_index       ; Read the address control registers
    mov al,IW_P401HI
    out dx,al
    mov dx,_pnp_read
    in al,dx
    xchg al,ah              ; save high address
    mov dx,_pnp_index
    mov al,IW_P401LI
    out dx,al
    mov dx,_pnp_read
    in al,dx
    mov _mpu401_port,ax

; MPU401 IRQ
    mov dx,_pnp_index
    mov al,IW_PMISI
    out dx,al
    mov dx,_pnp_read
    in al,dx
    mov _mpu401_irq,al      ; save MPU401 IRQ

    mov dx,_pnp_index      ; Put card back to wait for key state.....
    mov al,IW_PCCCI
    out dx,al
    mov dx,_pnp_write
    mov al,02h             ; out of config mode ...
    out dx,al

done_get_base_port:
    ret
_get_base_port endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; strupr - converts command line to all upper case
;
; EXPECTS:
;   cmd_line_args - pointer to the command line arguments
;
; MODIFIES: ax,di,command line arguments
;
;-------------------------------------------------------------------------------
strupr proc near
    les di,dword ptr _cmd_line_args
strupr_start:
    mov al,es:[di]             ; move next byte to ax
    cmp al,0dh                 ; compare to CR
    je short end_strupr        ; on CR jump to end
    cmp al,61h                 ; compare to 'a'
    jl short strupr_continue   ; on less then 'a' jump to continue
    cmp al,7ah                 ; compare to 'z'
    jg short strupr_continue   ; on greater than 'z' jump to continue
    sub byte ptr es:[di],20h   ;make upper case by sub 20h from lower case
strupr_continue:
    inc di                     ; go to next letter
    jmp short strupr_start     ; start over on next letter
end_strupr:
    ret
strupr endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; strat - device driver strategy routine
;
; DESCRIPTION:
;   This is called once by DOS at startup if called as a device driver
;   Passed in is the request header for the device driver for DOS communications
;   in ES:BX
;   Request header location stored in dd_seg, dd_off in local memory
;
; EXPECTS: ES:BX pointer to driver data block
;
; MODIFIES: dd_seg
;
;-------------------------------------------------------------------------------
strat proc far             ; Strategy routine
    mov cs:dd_seg,es       ; save driver data address
    mov cs:dd_off,bx
    ret
strat endp
;
; Address of the driver data block passed in from DOS on device driver startup
; This is setup by the strategy routine
;
req_hdr     label dword
dd_off      dw    ?
dd_seg      dw    ?
init_ss     dw    ?
init_sp     dw    ?

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; init - Device driver function 0 - init()
;
; DESCRIPTION:
;   This is the device driver init routine called from the intr() procedure
;   with function 0 - init
;   This should be called only once - just like __EXESTART
;   Sets up cmd_line_args and driver end address and does the same as EXESTART
;   Calls command line and environment parsing routines and sets up the driver
;   to remain resident in DOS structures
;
; SEE ALSO: __EXESTART   
;
;-------------------------------------------------------------------------------
init proc near
    ;
    ; Setup DS and SS:SP
    ;
    mov init_ss,ss
    mov init_sp,sp
    push cs
    pop ss
    lea sp,__TOP_STARTUP_STACK
    push bp
    mov bp,sp
    pusha
    ;
    ; Setup command line pointer
    ;
    les di,req_hdr
    mov ax,word ptr es:[di+18]      ; offset 18 is command line
    mov _cmd_line_args,ax
    mov ax,word ptr es:[di+18+2]
    mov _cmd_line_args+2,ax
    ;
    ; Call setup routines
    ;
    mov sys_exe,SYS_START           ; let startup code know start is from sys
    call near ptr strupr            ; convert command line to upper case
    call near ptr _get_quiet_opt    ; get quiet setting on command line
    call near ptr get_2f_id         ; Get a multiplex ID
    SysTestResult                   ; AX = 0, passed 2f check, continue
    call near ptr _get_ini_path     ; get INTERWAVE iw.ini path from environment
    call near ptr _iw_test_iw_ini   ; see if the iw.ini file is there
    SysTestResult
    call near ptr _sbos_mls_init    ; Initialize the Multi Language Support
    call near ptr _setup_pnp        ; read ini file and get pnp data
    SysTestResult
    call near ptr _iw_get_quiet     ; get quiet setting in iw.ini
    call near ptr _iw_get_nmi_opt   ; see if we should chain the NMI
    call near ptr _iw_get_memcfg    ; get the memory configuration info from ini
    SysTestResult
    call near ptr _get_base_port    ; look in system for baseport or use PNP
    call near ptr _test_base_port   ; make sure the base port is not ffff
    SysTestResult
    call near ptr _setup_registers  ; setup registers for use in the driver
    call near ptr _size_dram        ; get the size of dram to make room for data
    call near ptr _test_dram_size   ; see if the dram size found is OK
    SysTestResult
IFDEF MPU401
    call near ptr _get_mpu_opt      ; see if user wants mpu401 emulation
ENDIF
    call near ptr _print_version    ; Print version & load parameters to screen
    call near ptr _get_comm_vect    ; see if user wants to change comm vector
    SysTestResult
    call near ptr _hook7x           ; hooks just the 7x vector
    call near ptr _setup_driver     ; get size of res portion & save data space
    SysTestResult

    mov ax,_num_paras
    shl ax,4                        ; make paragraphs to bytes
    jmp sys_ok
sys_exit:
    xor ax,ax
sys_ok:
    les di,req_hdr
    mov word ptr es:[di+14],ax
    mov es:[di+14+2],cs            ; Set end address of the driver
    xor ax,ax
    popa
    pop bp
    mov ss,init_ss
    mov sp,init_sp
    ret
init endp
;
; Here's where all the strings for printing are allocated
;
_default_banner db "InterWave SBOS Version "
_verstring2  db VERSION_HIGH+30h,".", VERSION_LOW1+30h, VERSION_LOW2+30h,"$"
__copyright db 0dh,0ah,"InterWave SBOS Copyright (C) 1992, 1995 Advanced Gravis, Inc.  All Rights Reserved.",0dh,0ah
comvs      db "Driver Communication Vector: 7"
comv       db "E",0dh,0ah,"$"
;
; These strings are used to create the filename for the lib files in the
; setup_driver() routine in startupc.c.  
;
_sbos512        db "iwsb512.iwl",0     ; filename for 512k library
_sbos1024       db "iwsb1024.iwl",0    ; filename for 1024k library

_error_string   db ERROR_STR_LEN dup ('E') ; spot for error strings
_crlf           db 0dh,0ah,"$"
_languages      db "LANGUAGES",0       ; [languages]
_defaultstr     db "default",0         ; default
_sbos_mls       db "sbos.mls",0        ; mls filename
_sbos_ini_hdr   db "IWSBOS",0          ; [sbos_ini_hdr] in iw.ini file
_setup_ini_hdr  db "SETUP 0",0         ; [setup 0] in iw.ini file
_library512     db "library512",0      ; library512=c:\path to 512k library
_library1024    db "library1024",0     ; library1024=c:\path to 1024k library
_seveneee       db "7e",0              ; default communication vector
_on             db "on",0              ; on?
_csn            db "csn",0             ; CSN header in iw.ini file
_pnprdp         db "pnprdp",0          ; PNP read port header in iw.ini file
_SbosVector     db "SbosVector",0      ; SBOS communication vector in iw.ini 
_quietst        db "quiet",0           ; Quiet option in iw.ini
_Mpu401Emulation db "Mpu401Emulation",0; mpu401 emulation header in iw.ini
_memcfg         db "memcfg",0          ; memory config used for sbos
_chainnmi       db "chainnmi",0        ; to chain or not to chain
_ini_path       db 7fh dup('I')        ; path to iw.ini file from environment
_lib_path       db 7fh dup('L')        ; storage for full path to library
_mls_path       db 128 dup(0)          ; MLS storage
_mls_language   db "Invalid Language Section in IW.INI.$"
_mls_default    db "Invalid Default Language in IW.INI.$"
_mls_iwini      db "Can't Find IW.INI.$"
_mls_file       db "Bad or Missing Language File.$"
_fff_string        db "FFFF",0
_sbos_string    db "SBOS",0
_iwave_string    db "INTRWAVE",0

public __TOP_STARTUP_STACK
public __BOT_STARTUP_STACK

__BOT_STARTUP_STACK label byte
db STARTUP_STACK_SIZE dup('S')
__TOP_STARTUP_STACK label byte

_TEXT ends
    end __EXESTART ; executable entry point - should only be specified once
