/***************************************************************************
*       NAME:  LOADER.C $Revision: 1.52 $
**      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."
****************************************************************************/
#include <stdio.h>
#include <io.h>
#include <dos.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <dir.h>
#include <conio.h>
#include <sys\stat.h>
#include "adsr.h"
#include "sbosdata.h"
#include "findsbos.h"
#include "version.h"
#include "sbosdefs.h"
#include "shared.h"
#include "fm.h"
#include "dac.h"
#include "pick.h"
#include "utillib.h"
#include "hardware.h"
#include "stuff.h"
#include "error.h"

// LOCAL DEFINES
#define MAX_SB_IRQS 8
#define MAX_IRQS 16
#define WAIT_FOR_NMI 50
#define DMA_BUFFK    16U
#define DMA_SIZE (DMA_BUFFK*1024U)

// GLOBALS
char copyright[] = "\nInterWave SBOS Copyright (C) 1992, 1995 Advanced Gravis, Inc. All Rights Reserved.\n";

SHARED far * shared;
PORT_STRUC ports;
int nmi_count = 0;
int nmi_ok_flag = 0;
unsigned int comm_vect;
unsigned int driver_seg = 0x0000;
int user_dram_size = 0;
unsigned int synth_atten=0;
unsigned int dac_atten=0;
unsigned int synth_set = FALSE;
unsigned int dac_set = FALSE;
unsigned int line_enabled = FALSE;
unsigned int adlib_only = FALSE;
unsigned int dos4gw_bug = FALSE;
unsigned int combine = FALSE;
unsigned int force_flip = FALSE;
unsigned int save_state = FALSE;     // True with /k option - keep current setup

// MLS Stuff
mls_path[128];
char error_string[ERROR_STR_LEN];

unsigned char mixer_mask = 0x0b; // Disable line-in, mic-in, enable line-out
char *ini_file;

OLD_VECTORS far * old_vectors;
NEW_VECTORS far * new_vectors;
SBOSDATA far * sbosdata;
BOARD_CFG far * config;
WAVE_DATA far * wave_data;

// GLOBAL OPTIONS
int unload_sbos = FALSE;
int mpu401_mode = FALSE;
int print_messages = TRUE;

// External prototypes
long find_rom_sbos(int port,SBOS_LIB_HEADER *lib_header,
                    SHARED *shared,int rom_flag);

PeekString(unsigned int port, unsigned long location,
           void far *data,unsigned int length,int rom_flag);

extern unsigned int codec_addr;
extern unsigned int codec_data;

// INTERNAL PROTOTYPES
void parse_command_line(int argc, char *argv[]);
void get_shared_address(void);
void unload_tsr(void);
void get_vectors(void);
unsigned int get_driver_status(void);
void reset_vectors(void);
void report_loaded(void);
int can_reset_vectors(void);
void setup_sbos(void);
void SetPic(int);
void setup_nmi(void);
void interrupt test_nmi(void);
void reset_nmi(void);
void setup_driver(char * command_line);
void get_config(void);
void disable_nmi_irq(void);
void enable_nmi_irq(void);
void write_dram( unsigned long address, unsigned char value );
unsigned char read_dram( unsigned long address );
void reload_data(void);
void update_pick_table(unsigned char far *pick_table, unsigned char *sustain);
void usage(void);
void setup_for_iw(void);
void setup_options(void);
void wincheck(void);
void print_banner(void);

/****************************************************************************

FUNCTION DEFINITION:
main -

DESCRIPTION:

EXPECTS:

MODIFIES:

SEE ALSO:

RETURNS:

*/
void main(int argc, char *argv[])
{
union REGS regs;

comm_vect = find_sbos();

setup_for_iw();       /** Test for INI file and get QUIET option     **/
sbos_mls_init();      /** Must be before parse_command_line() **/
parse_command_line(argc, argv);
if (comm_vect)
    {
    get_shared_address();
    setup_options();  /** Setup save state or command line options   **/
    get_config();     /** Setup port structure - must be done early  **/
    wincheck();       /** Unload TSR if needed and do checks for Win **/

    /**
     ** We can do emulation...  Setup the card and make sure DRAM (if there)
     ** is properly loaded 
     **/

    if(!(get_driver_status() & STAT_DRIVER_INIT)) /** driver un-init'd **/
        {
        setup_driver(argv[0]); /** Misc. setup routines for driver   **/
        init_codec(adlib_only);/** init codec                        **/
        reload_data();    /** Send library down to DRAM & init shared**/
        setup_sbos();     /** Sets up the NMI, DMA, IRQ, masks, etc. **/
        if ( !combine && !(shared->status & STAT_WINDOWS_RUNNING))
            setup_nmi();  /** Makes sure NMI can be set and sets it  **/
        get_vectors();    /** Sets up rest of vectors other than NMI **/
        SetPic(config->orig_irq1);
        report_loaded();  /** Tell the driver the loader is done     **/
        enable_outputs(); /** enable the codec (if present)          **/
        }
    else /** User wants to reload with options or spawned from driver**/
        {
// Reset synth vector in case of a badly behaving program ...
        setvect(sbosdata->synth_vect,new_vectors->new_intsynth);
        init_codec(adlib_only);/** init codec                        **/
        reload_data();    /** Send library down to DRAM & init shared**/
        setup_sbos();     /** Sets up cards NMI, DMA, IRQ, etc       **/
        SetPic(config->orig_irq1);
        report_loaded();  /** Reads new stamp-will include more later**/ 
        enable_outputs(); /** enable the codec (if present)          **/
        }
    }
else /** The driver is not out there, could be Windows or an ignorant user **/
    print_message(LM_DRIVER_NOT_LOADED);
    //printf("SBOS Driver Not Loaded\n");
exit(0);
}

/****************************************************************************

FUNCTION DEFINITION:
wincheck - Arbitrates SBOS loading and unloading based on weather Windows
   is loaded or not

DESCRIPTION:
   The driver will not be allowed to unload if windows is running.
   Will connect to the VxD if Windows is running.

*/
void wincheck(void)
{
    union REGS regs;

    if(unload_sbos)
        {
        if(shared->status & STAT_WINDOWS_RUNNING)
            {
            print_message(LM_WINDOWS_UNLOAD);
//            printf("\nCan't Unload SBOS While Windows is Running!\n");
//            printf("Exit Windows, then type 'SBOS /f' to unload\n");
            }
        else
            unload_tsr();
        exit(0);
        }

    if(shared->status & STAT_WINDOWS_RUNNING)
        {
        /**
         ** If Windows is running - always try to connect to the VxD
         ** The VxD needs to know which virtual machine is doing the
         ** the emulation.
         **/
        regs.x.ax = COM_WINAPI;
        int86(comm_vect,&regs,&regs);
        if(shared->status & STAT_WINAPI_ACTIVE)
            {
            /**
             ** The Windows VxD connection passed, we can do emulation
             ** See if there is a second int21 handler for this dos box out 
             ** there.  Clear the flag in shared and make some dos int21 call 
             ** and see if it comes back set.  If it is still clear, install
             ** an int21 handler so we can run testnspawn inside a 
             ** Windows DOS BOX
             **/
            if(print_messages)
            print_message(LM_WINDOWS_INIT);
//                printf("Initializing SBOS to run in Windows DOS BOX!\n");
            shared->status &= ~STAT_WIN21_FLAG;
            regs.x.ax = 0x3000; /** get dos version **/
            int86(0x21,&regs,&regs);
            if((shared->status & STAT_WIN21_FLAG) == 0)
                {
#ifdef DEBUG
                printf("SBOSDEBUG: Installed DOSBOX int 21h Handler\n");
#endif
                old_vectors->old_win21 = getvect(INT_DOS);
                setvect(INT_DOS,*(new_vectors->new_win21));
                }
            }
        else
            {
            /**
             ** Flag not set, so the VxD is wrong version if it's there
             ** at all 
             **/
        print_message(LM_WINDOWS_NO_VIWD);
//            printf("\nSBOS Can Not Gain Access to the Windows Drivers!\n");
//            printf("Check That VIWD.386 is Properly Loaded in SYSTEM.INI.\n");
//            printf("Emulation is Not Available Under Windows.\n");
            if(!(shared->status & STAT_DRIVER_INIT))
                unload_tsr(); /** run driver NOT loader and run Windows??? **/
            exit(-1);
            }
        }
}

/****************************************************************************

FUNCTION DEFINITION:
setup_options - Sets up the state of the loader based on command line options
  and previous state of the driver.

*/
void setup_options(void)
{
    /**
     ** If the save state option was given from the driver to the loader
     ** or typed on the command line, 'save_state' will be true.
     ** In that case, each following variable gets set from the status in 
     ** shared memory (shared->status) instead of options from the command
     ** line.  This preserves the way the driver was configured for spawning.
     **/
    if (synth_set)
        shared->synth_atten = synth_atten;
    if (dac_set)
        shared->dac_atten = dac_atten;

    if ((save_state) && (shared->status & STAT_FORCE_FLIPPER))
        force_flip = TRUE;
    else if (force_flip)
        shared->status |= STAT_FORCE_FLIPPER;
    else
        shared->status &= ~STAT_FORCE_FLIPPER;

    if ((save_state) && !(shared->status & STAT_CODEC_ENABLED))
        adlib_only = TRUE;
    else if (adlib_only)
        shared->status &= ~STAT_CODEC_ENABLED;

    if ((save_state) && (shared->status & STAT_COMBINED_IRQ))
        combine = TRUE;
    else if (combine)
        shared->status |= STAT_COMBINED_IRQ;
    else
        shared->status &= ~STAT_COMBINED_IRQ;

    if ((save_state) && (shared->status & STAT_SKIP_DOS4GW_BUG))
        dos4gw_bug = TRUE;
    else if (dos4gw_bug)
        shared->status |= STAT_SKIP_DOS4GW_BUG;
    else
        shared->status &= ~STAT_SKIP_DOS4GW_BUG;

    if (shared->status & STAT_MPU401_ENABLED)
        mpu401_mode = TRUE;
}

/****************************************************************************

FUNCTION DEFINITION:
setup_for_iw -

DESCRIPTION: Tests for the existence of the iw.ini file from the INTERWAVE
  environment variable and gets the quiet option for the rest of the loader.

*/
void setup_for_iw(void)
{
    char char_quiet[5];

    ini_file = getenv("INTERWAVE");

    /** 
     ** If we can't find the iw.ini file by the next two tests, assume the
     ** driver couldn't either and is not resident...
     **/
    if(ini_file == NULL)
        {
        print_error(ERROR_NO_INTERWAVE);
//      printf("Cant find the INTERWAVE environment variable\n");
//      unload_tsr();
        exit(-1);
        }

    if(access(ini_file,0) != 0)
        {
        print_error(ERROR_BAD_IWINI);
//      printf("Can't open the iw.ini file\n");
//      unload_tsr();
        exit(-1);
        }
    
    iwu_get_profile_string("IWSBOS","QUIET","off",char_quiet, 5,ini_file);
    if(char_quiet[0] == 'o' || char_quiet[0] == 'O')
    if(char_quiet[1] == 'n' || char_quiet[1] == 'N')
        print_messages = FALSE;    
}

/****************************************************************************

FUNCTION DEFINITION:
SetPic - Sets up the PIC to unmask the interrupts associated with emulation

DESCRIPTION:

MODIFIES:
  pic_mask and spec_eoi in driver data space

*/
void SetPic(int irq_num)
{
unsigned char val;
unsigned char mask[16] = {0xfe,0xfd,0xfb,0xf7,0xEF,0xDF,0xBF,0x7F,
                          0xfe,0xfd,0xfb,0xf7,0xEF,0xDF,0xBF,0x7F};
unsigned char spec_eoi[16] = {0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,
                            0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67};
int imr;

    if(irq_num > 7)
        imr = 0xa1;
    else
        imr = 0x21;

    /* unmask gf1 interrupt */
    val = inp(imr);
    val &= mask[irq_num];
    outp(imr,val);
    outp(imr-1,spec_eoi[irq_num]);

    /* Un-mask IRQ 2 from first controller if using 2nd controller */
    if (irq_num > 7)
        {
        val = inp(0x21);
        val &= mask[2];
        outp(0x21,val);
        outp(0x20,spec_eoi[2]);
        }

    sbosdata->pic_mask = mask[irq_num];
    sbosdata->spec_eoi = spec_eoi[irq_num];
}

/****************************************************************************

FUNCTION DEFINITION:
parse_command_line - Parses the command line and accepts any valid options

EXPECTS:
  argc and argv* are the original values passed into main()

MODIFIES:
  performs a strupr on the command line

*/
void parse_command_line(int argc, char *argv[])
{
    int i;

    for( i=1; i < argc; i++ )
        {
        if( argv[i][0] == '-' || argv[i][0] == '/' )
            {
            argv[i] = strupr( argv[i] );

            switch( argv[i][1] )
                {
                case 'Q':
                    print_messages = FALSE;    
                    break;

                case 'K':
                    if(argv[i][2] == 0x00)
                        save_state = TRUE;
                    break;

                case 'A':
                    if(argv[i][2] == 0x00)
                        adlib_only = TRUE;
                    break;

                case 'B':
                    if(argv[i][2] == 0x00)
                        dos4gw_bug = TRUE;
                    break;

                case 'C':
                    if(argv[i][2] == 0x00)
                        combine = TRUE;
                    break;

                case 'F':
                    if(argv[i][2] == 0x00)
                        unload_sbos = TRUE;
                    break;

                case '?':
                case 'H':
                    if(argv[i][2] == 0x00)
                        usage();
                    break;

                case 'V':        // Set the volumes for dig and/or synth
                    {
                    switch(argv[i][2])
                        {
                        case 'D':    // Set digital attenuation
                            dac_set = TRUE;
                            dac_atten = atoi(&(argv[i][3]));
                            break;
                        case 'F':    // Set synth attenuation
                            synth_set = TRUE;
                            synth_atten = atoi(&(argv[i][3]));
                            break;
                        default:    // Set them to the same thing
                            synth_set = dac_set = TRUE;
                            synth_atten = atoi(&(argv[i][2]));
                            dac_atten = synth_atten;
                            break;
                        }
                    }
                    break;

                case 'X':
                    force_flip = TRUE;
                    break;
                }
            }
        else
            usage();
        }
}

/****************************************************************************

FUNCTION DEFINITION:
get_shared_address - Gets a pointer to driver data space and performs 
  authenticity check on data.

DESCRIPTION:
  Calls up to the driver using int7x function 0 and gets the address of shared
  memory.  Creates pointers into driver structures.  Checks both version number
  and size of SHARED structure to see if the driver code is compatible.

EXPECTS:
  Driver should be loaded

*/
void get_shared_address(void)
{
    union REGS regs;

#ifdef DEBUG
    printf("********* DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG *********\n");
#endif

    regs.x.ax = COM_GET_SHARED;  /* Get Shared Data Address */
    int86(comm_vect,&regs,&regs);
    driver_seg = regs.x.dx;

    /** get far pointers to all the needed structs in the driver **/
    shared      = (SHARED far *)MK_FP(regs.x.dx,regs.x.ax);
    config      = (BOARD_CFG far *)MK_FP(driver_seg,shared->config);
    old_vectors = (OLD_VECTORS far *)MK_FP(driver_seg,shared->old_vectors);
    new_vectors = (NEW_VECTORS far *)MK_FP(driver_seg,shared->new_vectors);
    sbosdata    = (SBOSDATA far *)MK_FP(driver_seg,shared->sbosdata);
    wave_data   = (WAVE_DATA far *)MK_FP(driver_seg,shared->wave_data);

    if((VERSION_HIGH != shared->version_high) || 
       (VERSION_LOW1 != shared->version_low1) ||
       (VERSION_LOW2 != shared->version_low2))
        {
        if(!unload_sbos)
            {
        print_message(LM_BAD_VERSION);
//            printf("Version number mismatch\n");
//            printf("Driver should be reinstalled\n");
            unload_tsr();
            exit(0);
            }
        }
    else
        {
        if(print_messages)
            {
            print_banner();
            //printf("InterWave SBOS Loader Version %d.%d%d\n",
            }
        }

    /** See if the driver thinks the size of shared data is the same as the 
     ** loader 
     **/

    if(shared->size != sizeof(SHARED))
        {
        if(!unload_sbos)
            {
        print_message(LM_SHARED_SIZE);
//            printf("SBOS: Driver data space has an invalid size.\n");
//            printf("Driver should be reinstalled\n");
            unload_tsr();
            exit(0);
            }
        }

    /** The driver was already told wheather it is in MPU401 emulation mode
     ** or not - just get the information and let the loader know
     **/
#ifdef MPU401
    if (shared->status & STAT_MPU401_ENABLED)
        mpu401_mode = TRUE;
#endif
}


/****************************************************************************

FUNCTION DEFINITION:
LoaderGetCfg -

DESCRIPTION:

EXPECTS:

MODIFIES:

SEE ALSO:

RETURNS:

*/
int LoaderGetCfg(BOARD_CFG far *config)
{
    char *ptr;
    unsigned char synth_vect;

    GetCfg(config);

    /** Valid SB ports are 220 and 240 **/
    if( config->lower_base_port != 0x220 &&
        config->lower_base_port != 0x240 )
        {
    print_message(LM_BAD_BASE);
//        printf("Invalid Base Port Selection - Cannot load SBOS!\n");
        unload_tsr();
        exit (-2);
        }

// See if the SB hasn't been activated ...
    if( config->sb_irq == 0 )
        {
    print_message(LM_NO_RESOURCES);
//        printf("No resources allocated to SB emulation.\n");
//        printf("SB DAC emulation deactivated.\n\n");
        adlib_only = TRUE;
        }
    else

    /** Check for valid midi irq numbers **/
    if( config->sb_irq != 2 &&
        config->sb_irq != 3 &&
        config->sb_irq != 5 &&
        config->sb_irq != 7 )
        {
    print_message(LM_BAD_SB_IRQ);
//        printf("Invalid SBOS Interrupt Selection\n");
        unload_tsr();
        exit(-3);
        }

    /** Check for valid synth irq numbers **/
    if( config->orig_irq1 != 2  &&
        config->orig_irq1 != 3  &&
        config->orig_irq1 != 4  &&
        config->orig_irq1 != 5  &&
        config->orig_irq1 != 7  &&
        config->orig_irq1 != 10  &&
        config->orig_irq1 != 11 &&
        config->orig_irq1 != 12 &&
        config->orig_irq1 != 15 )
        {
    print_message(LM_BAD_IW_IRQ);
//        printf("Invalid InterWave Interrupt Selection\n");
        unload_tsr();
        exit(-4);
        }

    /** Calculate the IRQ vector number **/
    if (config->orig_irq1 >= 8)
        synth_vect = 0x68 + config->orig_irq1;
    else
        synth_vect = 0x08 + config->orig_irq1;

    sbosdata->synth_vect = synth_vect;

    /** setup all ports for loader use -- has nothing to do with driver **/
    assign_ports(config,&ports);

    return(1);
}

/****************************************************************************

FUNCTION DEFINITION:
get_config -

DESCRIPTION:

EXPECTS:

MODIFIES:

SEE ALSO:

RETURNS:

*/
void get_config(void)
{
    unsigned char val1, val2 ,val3;

    if(!LoaderGetCfg(config))
        {
//        printf("ULTRASND environment variable not present!\n");
//        printf("Using Default Settings\n");
        }

    /** Pull Master reset on the chip in case ULTRINIT didn't run prior to 
     ** execution
     **/
    outp( ports.reg_index, 0x4C ); 
    outp( ports.reg_data_high, 0x07 );

    /**
     ** See if the card is out there 
     **/
    outp( ports.reg_index, 0x45 ); 
    if(inp( ports.reg_data_high) == 0xff) /** Card not there **/
        {
        if(shared->status & STAT_WINDOWS_RUNNING)
        //if(shared->status & STAT_WINAPI_ACTIVE)
            {
            if(unload_sbos)
                {
        print_message(LM_WINDOWS_UNLOAD);
//                printf("\nCan't Unload SBOS While Windows is Running!\n");
//                printf("Exit Windows, then type 'SBOS /f' to unload\n");
                }
            else
        print_message(LM_IW_USED);
//                printf("The InterWave Hardware is Currently in Use by Another Device\n");
            }
        else
            {
        print_message(LM_IW_NOT_FOUND);
//            printf("InterWave Card Not Found at Baseport Address 0x%X\n",
//              ports.lower_baseport);
            unload_tsr();
            }
        exit (-1);
        }
}

/****************************************************************************

FUNCTION DEFINITION:
get_vectors - Hooks all the vectors SBOS needs and stores the old ones in
  the driver

EXPECTS:
  old_vectors and new_vectors point into the driver

SEE ALSO:
  can_reset_vectors and reset_vectors

*/
void get_vectors(void)
{
    old_vectors->old_int08 = getvect(INT_TIMER);
    setvect(INT_TIMER,*(new_vectors->new_int08));

    old_vectors->old_int15 = getvect(INT_KEY);
    setvect(INT_KEY,*(new_vectors->new_int15));

    old_vectors->old_int2f = getvect(INT_2F);
    setvect(INT_2F,*(new_vectors->new_int2f));

    old_vectors->old_int21 = getvect(INT_DOS);
    setvect(INT_DOS,*(new_vectors->new_int21));

    old_vectors->old_intsynth = getvect(sbosdata->synth_vect);
    setvect(sbosdata->synth_vect,new_vectors->new_intsynth);

}

/****************************************************************************

FUNCTION DEFINITION:
can_reset_vectors - Test to see if the vectors in the vector table can
  properly be unhooked as SBOS terminates

DESCRIPTION:
  Returns TRUE if all vectors can be unhooked OK
  Checks the address in the shared data space with the address in the
    vector table.  If the vectors are the same, it can be unloaded
  Otherwise, returns FALSE if any one can't and prints out message.
  Should be called and tested before call to reset_vectors

SEE ALSO:
  get_vectors

RETURNS:
  int - TRUE if vectors are OK to be unhooked
      - FALSE if vectors have been hooked by another

*/
int can_reset_vectors(void)
{
    void (interrupt *vector)();
    int retval = TRUE;

    /** See if there are any vectors that can't be unhooked... **/

    vector = getvect(INT_TIMER);
    if(vector != new_vectors->new_int08)
        {
    print_message(LM_TIMER);
//        printf("Timer Vector has been Hooked\n");
        retval = FALSE;
        }

    vector = getvect(INT_KEY);
    if(vector != new_vectors->new_int15)
        {
    print_message(LM_KEYBOARD);
//        printf("Keyboard Vector has been Hooked\n");
        retval = FALSE;
        }

    vector = getvect(INT_2F);
    if(vector != new_vectors->new_int2f)
        {
    print_message(LM_MUX);
//        printf("DOS Multiplexed Interrupt has been Hooked\n");
        retval = FALSE;
        }

    vector = getvect(INT_DOS);
    if(vector != new_vectors->new_int21)
        {
    print_message(LM_DOS);
//        printf("DOS Vector has been Hooked\n");
        retval = FALSE;
        }

    if(retval == FALSE)
        print_message(LM_BAD_UNLOAD);
//        printf("Cannot Unload SBOS driver\n\n");
    return(retval);
}

/****************************************************************************

FUNCTION DEFINITION:
reset_vectors - Unhooks all hooked vectors

DESCRIPTION:
  Properly unhooks all hooked vectors from the vector table.
  Should call can_reset_vectors before calling this to make sure all vectors
  can properly be restored.

SEE ALSO:
  get_vectors, can_reset_vectors, reset_vectors

*/
void reset_vectors(void)
{
    /** should unhook int 21 BEFORE trying to unhook the NMI vector **/
    setvect(INT_DOS,*(old_vectors->old_int21));
        
    /** Also should unhook TIMER BEFORE trying to unhook NMI vector **/
    setvect(INT_TIMER,*(old_vectors->old_int08));
        
    /** now its safe to unhook NMI w/o int 21 or int 8 hooked**/
    setvect(INT_NMI,*(old_vectors->old_int02));

    setvect(INT_2F,*(old_vectors->old_int2f));
        
    setvect(INT_KEY,*(old_vectors->old_int15));
        
    setvect(comm_vect,*(old_vectors->old_int7x));

    setvect(sbosdata->synth_vect,*(old_vectors->old_intsynth));
}

/****************************************************************************

FUNCTION DEFINITION:
report_loaded - Makes a call up to the driver to notify it that it has been
  loaded.

DESCRIPTION:
  Calls the driver with int 7x function 1 and sets a flag in shared->status
  representing this call.

MODIFIES:
  shared->status

*/
void report_loaded(void)
{
    union REGS regs;

    regs.x.ax = COM_DRIVER_LOADED;
    int86(comm_vect,&regs,&regs);

    /** Why take up code space in the driver to do this ?? **/
    shared->status |= STAT_DRIVER_INIT;   /** set driver initialized bit **/

    /** Output the masks to the mixer and register control register **/
    /** To set up the card (ie 2xC MSB flipping etc...)  **/
    outp(ports.reg_2xFcontrol,sbosdata->regctl_mask);
    outp(ports.reg_mixer,sbosdata->mixer_mask);

    /** If they're not initialized yet **/
    if(sbosdata->dev_mode_synth == 0 && sbosdata->dev_mode_codec == 0)
        {
        if(shared->status & STAT_WINDOWS_RUNNING)
            {
            sbosdata->dev_mode_synth = DEV_WINDOWS;
            sbosdata->dev_mode_codec = DEV_WINDOWS;
            }
        else
            {
            sbosdata->dev_mode_synth = DEV_HW;
            sbosdata->dev_mode_codec = DEV_HW;
            }
        }
    
    if(!(shared->status & STAT_CODEC_ENABLED))
        sbosdata->dev_mode_codec =  DEV_ASLEEP;
}

/****************************************************************************

FUNCTION DEFINITION:
setup_driver - Tests the length of the command line for the loader and copies
  it to driver data space.

DESCRIPTION:
  The driver needs a copy of the full path name for the loader so it can spawn
  it off during int 21 calls.  Since there is only a fixed space in the driver
  to store the path name, if it is too long it can't store it.

MODIFIES:
  sbosdata->loader_name

*/
void setup_driver(char * command_line)
{
    int len,i;

    if((len = strlen(command_line)) > LOADER_NAME_LEN)
        {
    print_message(LM_DEEP_TREE);
//        printf("SBOS resides at too deep of a directory structure to load\n");
//        printf("Move SBOS to a higher level and try to load again\n");
        unload_tsr();
        exit(0);
        }
    
    for(i=0;i<len;i++) /** copy over the pathname to the driver data space **/
        sbosdata->loader_name[i] = command_line[i];
}

/****************************************************************************

FUNCTION DEFINITION:
get_driver_status - Returns the status field of the driver

DESCRIPTION:
  Reaches into shared memory in the driver and returns the driver status
  field

RETURNS:
   uint - driver status

*/
unsigned int get_driver_status(void)
{    
    return(shared->status);
}

/****************************************************************************

FUNCTION DEFINITION:
unload_tsr - Attempts to unload the SBOS TSR from memory

DESCRIPTION:
  If the driver has not been initialized (only the 7x vector has been hooked)
  only reset the 7x vector back and free the driver.  However, if the driver
  has been init'd and all the vectors have been hooked, make sure the vectors
  can be restored and restore them.  If that passed, free the driver and
  turn off NMIs.

SEE ALSO:
  can_reset_vectors, reset_vectors

*/
/***************************************************************************
    unload_tsr()
        - Calls can_reset_vectors() to make sure no vectors are hooked wrong
        - frees the psp segment for the driver and tests result codes
        - If freed OK, resets hooked vectors
        - Disables NMI generation
***************************************************************************/
void unload_tsr(void)
{
    union REGS regs;
    struct SREGS sregs;

    /** if the driver has not been initialized we will do this
     ** OR if it has been AND the vectors are all OK we can do this
     **/
    if(!(get_driver_status() & STAT_DRIVER_INIT) || can_reset_vectors() ) 
        {
        /** try to do free **/
        regs.x.ax = 0x4900; /** Free allocated memory block **/
        sregs.es = FP_SEG(new_vectors->new_int7x) - 0x10;
        intdosx(&regs, &regs, &sregs);
        if(regs.x.cflag)
            {
            /** Free failed **/
        print_message(LM_BAD_UNLOAD);
//            printf("Cannot unload SBOS\n");
            if(regs.x.ax == 7)
        print_message(LM_BAD_MEMORY_BLOCK);
//                printf("Memory control block destroyed\n");
            else
        print_message(LM_FREE_DRIVER);
//                printf("Unable To Free Device Driver \n");
                /**  used to say:                       **/
                /**     "Incorrect memory area passed"     **/
                /**  This one is easier to understand   **/
            }
        else
            {
            /** Tell the driver that we're unloading **/
            regs.x.ax = COM_DRIVER_UNLOAD;
            int86(comm_vect,&regs,&regs);

            /** Freed OK
             ** This is where we don't want to reset all vectors unless
             ** the driver has been init'd - Test the bit...
             ** If the driver has not been set up, only unhook 7x
             ** otherwise, unhook them all
             ** And yes we can do that now because can_reset_vectors() has 
             ** passed
             **/
            if(!(get_driver_status() & STAT_DRIVER_INIT))
                setvect(comm_vect,*(old_vectors->old_int7x));
            else
                reset_vectors();

        print_message(LM_TSR_FREED);
//            printf("SBOS Driver Freed\n");

            /** Disable NMI generation on the card **/
            disable_nmi_irq();
            reset_nmi();
            }
        }
}

/****************************************************************************

FUNCTION DEFINITION:
setup_sbos -

DESCRIPTION:

EXPECTS:

MODIFIES:

SEE ALSO:

RETURNS:

*/
void setup_sbos(void)
{
    unsigned char irq_port_data;        /**  Data sent to IRQ data port **/
    unsigned int mpu_port;              /**  MPU-401 baseport address   **/
    int i;
    unsigned char compat;               /** compatibility register contents **/
    unsigned char mpuemureg;
    unsigned char regctl_mask = 0x00; // Disable all interrupts in this register
    unsigned char adsbint_mask;
    unsigned char val;

    /**  Port 2xB -- IRQ Control Register
     *
     *   bit 0-2 Channel 1 DOC/SB/ADLIB IRQ Selector
     *   bit 3-5 Channel 2 MIDI IRQ Selector
     *
     *   0 = NMI         Bit 6 -- 1 = Combine interrupts
     *   1 = IRQ2
     *   2 = IRQ5        Bit 7 -- 1 = Generate NMI for SB and ADLIB
     *   3 = IRQ3
     *   4 = IRQ7
     *   5 = IRQ11
     *   6 = IRQ12
     *   7 = IRQ15
     *
     **/

    /** IRQ is offset into this table -- table value is bit pattern for port **/

    /** Map invalid irqs to IRQ7 **/
    unsigned char lookup_sb_irq[ MAX_SB_IRQS ] = { 4, 4, 1, 3, 4, 2, 4, 4  };

    /** Map invalid irqs to IRQ11 (irq 4 and 10 to 15) **/
    unsigned char lookup_irq[ MAX_IRQS ] = { 5, 5, 1, 3, 7, 2, 5, 4, 
          5, 5, 7, 5, 6, 5, 5, 7 };

    unsigned char dma_latch[8]={6,1,0,2,0,3,4,5};

    /** Set this by default - can't be turned off yet ... **/
    shared->status |= STAT_ADLIB_ENABLED;

    /**
     ** Don't enable nmis if windows is running
     **/
    if(shared->status & STAT_WINDOWS_RUNNING)
        adsbint_mask = 0;
    else
        {
        adsbint_mask  = 0x02; /** turn on ADLIB irqs  **/
        if (!adlib_only)
            adsbint_mask |= 0x20;      /** turn on SB DAC irqs **/
        }
    sbosdata->adsbint_mask = adsbint_mask; /** set this for enable_nmi_irq **/

    outp( ports.reg_index, SET_GLOBAL_MODE ); 
    outp( ports.reg_data_high, 0x01 );// Go to enhanced mode 

    outp( ports.reg_index, IW_ICMPTI ); 
    outp( ports.reg_data_high, 0x0f );    // Serial out, compat off (lock out dma/irq)

    /* Set local memory for auto increment */
    outp( ports.reg_index, IW_LMCI); 
    outp( ports.reg_data_high,IWL_LMCI_AI);

    for (i=0;i<32;i++)
        {
        outp(0x322,i);
        outp( ports.reg_index, SET_VOICE_MODE); 
        outp(ports.reg_data_high,0x00);        // enable the voice ...
        }

    /**
     ** The automatic hand-shaking for the MPU command port is useless here...
     ** There is no way for the bit to go high again after the app reads the
     ** data port.  Oh well...
     ** Disable interrupts on 331 read
     **/
    outp( ports.reg_index, IW_IEMUBI); 
    mpuemureg = inp(ports.reg_data_high);
    mpuemureg |= 0x02; /** Disable interrupts on 331 Reads **/
    outp(ports.reg_data_high,mpuemureg);

// Now set up the DMA/IRQ masks that will be programmed later ....

    /** Convert IRQ numbers to data the chip will understand - see above **/
    config->game_irq_mask = lookup_irq[config->orig_irq1];
    config->game_irq_mask |= (lookup_sb_irq[config->sb_irq]<<3);
    /* If combining NMI to IRQ do not set bit 7 */
    if (!combine)
        config->game_irq_mask |= 0x80;  /** bit 7 - NMI combine to irq1 **/

    // Force it to use one DMA channel ....
    config->sbos_dma_mask = dma_latch[config->sb_dma];
    config->sbos_dma_mask |= 0x40;

    // Now see if either irq is 10 or 4. If so, we need to set special bit
    if ((config->sb_irq == 10) || (config->sb_irq == 4) ||
        (config->orig_irq1 == 10) || (config->orig_irq1 == 4))
        config->game_ieirqi_mask = 0x7F;
    else
        config->game_ieirqi_mask = 0xFF;

    /** Convert IRQ numbers to data the chip will understand - see above **/
    config->orig_irq_mask = lookup_irq[config->orig_irq1];
    if (config->orig_irq1 == config->orig_irq2)
        config->orig_irq_mask |= 0x40;
    else
        config->orig_irq_mask |= (lookup_irq[config->orig_irq2]<<3);
    /* If combining NMI to IRQ do not set bit 7 */
    // Need to do this on BOTH orig and sbos masks to let us get NMI's
    if (!combine)
        config->orig_irq_mask |= 0x80;  /** bit 7 - NMI combine to irq1 **/

    // Now see if either irq is 10 or 4. If so, we need to set special bit
    if ((config->orig_irq2 == 10) || (config->orig_irq2 == 4) ||
        (config->orig_irq1 == 10) || (config->orig_irq1 == 4))
        config->orig_ieirqi_mask = 0x7F;
    else
        config->orig_ieirqi_mask = 0xFF;

    config->orig_dma_mask = dma_latch[config->orig_dma1];

    if(config->orig_dma1 == config->orig_dma2)
        config->orig_dma_mask |= 0x40;
    else
        config->orig_dma_mask |= (dma_latch[config->orig_dma2]<<3);

    asm    pushf;
    disable();

    /** Some NMIs cleared by write to 2xBi5 **/
    /** Write to 2x0 enables write to 2xB **/
    /** This port clears all IRQs with status listed in 2xF **/
    /** This is the same for both chips **/
    outp( ports.reg_2xFcontrol, regctl_mask | 0x05);
    outp( ports.reg_mixer, mixer_mask);
    outp( ports.reg_2xBindexed,  0x00);
    outp( ports.reg_2xFcontrol, regctl_mask);

    /** Clear Pending NMI from 389 if there **/
    inportb(ports.reg_389);

    /** Clear pending NMI from ADLIB/SoundBlaster **/
    disable_nmi_irq();

    /** Enable NMI/IRQ generation for ADLIB/SoundBlaster **/
    enable_nmi_irq(); /** enables NMIs for test_nmi() later **/


    // This will enable the NMI sensativity but leave the IRQs on
    // on IRQ1 and 2. The SBirq will NOT be put into the latches yet.
    // That happens on the first NMI. Same is true for DMA channel ...
        /*IRQ control register */
    outp( ports.reg_mixer, 0x43 | mixer_mask );
        /* This is the IRQ control register */
    outp( ports.reg_2xBindexed, config->orig_irq_mask );
    outp( ports.reg_2xFcontrol, regctl_mask );

    /** Set adlib timer preload **/
    outp( ports.reg_index, 0x46); // timer 1
    outp( ports.reg_data_high, 0xff);
    outp( ports.reg_index, 0x47); // timer 2
    outp( ports.reg_data_high, 0xff);

    /** Out to 388 to setup Adlib data register (389) as timer registers **/
    outp( ports.reg_388_write,4);    
    outp( ports.reg_389,0);
    (void)inportb(ports.reg_389);

    /** OK, If the driver is in MPU401 mode setup the floating registers **/
    if(mpu401_mode)
        {
        mpu_port = sbosdata->mpu401_port;

        /* The following is the port writes to setup the two floating registers
        ** for MPU-401 emulation.
        ** There are notes included to aid in the upgrade to the IW chip ????
        **
        ****** Set address for General Purpose Register 1
        ** OUT to 2xF[2:0] = 0x03 to enable write to Gen Purp Reg 1 Addr
        ** OUT to 2x0      = mixer_mask to enable write to 2xB
        ** OUT to 2xB      = mpu401_port in shared data - 300h
        ** NOTE: on the IW, we must write 11b to 3x5i59[1:0] for ISA bits [9:8]
        **
        ****** Set address for General Purpose Register 2
        ** OUT to 2xF[2:0] = 0x04 to enable write to Gen Purp Reg 2 Addr
        ** OUT to 2x0      = mixer_mask to enable write to 2xB
        ** OUT to 2xB      = mpu401_port in shared data - 3200h
        ** NOTE: on the IW, we must write 11b to 3x5i59[3:2] for ISA bits [9:8]
        **
        ****** Enable Access to and Interrupts via GPR1 & GPR2
        ** Set regctl_mask 0x58
        ** OUT to 2xF[3,4,5,6] = 1, 0x58
        */

        /**
         ** Make sure the upper two bits for the address for the MPU ports are 
         ** both 1s.
         ** OUT to 3x3 59h   = select compatibility register
         ** OUT to 3x5 contents | 0x0f
         **/
        outp( ports.reg_index, IW_ICMPTI);
        compat = inp ( ports.reg_data_high);
        compat &= 0xf0;
        compat |= ((mpu_port & 0x0300) >> 8);
        compat |= ((mpu_port & 0x0300) >> 6);
        outp( ports.reg_data_high, compat);

        outp( ports.reg_2xFcontrol, regctl_mask | 0x03);
        outp( ports.reg_mixer, mixer_mask);
        outp( ports.reg_2xBindexed, mpu_port);
        outp( ports.reg_2xFcontrol, regctl_mask | 0x04);
        outp( ports.reg_mixer, mixer_mask);
        outp( ports.reg_2xBindexed, mpu_port+1);

        /** MPU-401 Port Initializations **/

        /** Setup so app will initially read fe in 330 **/
        outp( ports.reg_2xFcontrol, regctl_mask | 0x01);
        outp( ports.reg_mixer, mixer_mask);
        outp( ports.reg_2xBindexed, 0xfe);

        /** Setup so app will read a 0x80 in 331 - rcv ready, send not ready **/
        /** This may be VERY important **/
        outp( ports.reg_2xFcontrol, regctl_mask | 0x02);
        outp( ports.reg_mixer, mixer_mask);
        outp( ports.reg_2xBindexed, 0x80);
        }

    if(shared->status & STAT_WINDOWS_RUNNING)
        regctl_mask |= 0x40; /** enable the ports but not the nmis **/
    else
        regctl_mask |= 0x58; /** enable both the ports and the nmis **/

    regctl_mask |= 0x20;     /** Enable toggle of MSB of 2xC **/

    /** 
     ** Set the dma control register so we have another method of
     ** seeing if the card has been tampered with - see testnspawn
     **/
    outp(ports.reg_index, DMA_CONTROL);
    outp(ports.reg_data_high, DMA_CONTROL_DEFAULT);

    /** Save the register write masks to preserve setup **/
    sbosdata->mixer_mask = mixer_mask;
    sbosdata->regctl_mask = regctl_mask;

asm popf;    
}

/****************************************************************************

FUNCTION DEFINITION:
setup_nmi - Tests the NMI capabilities of the host PC

DESCRIPTION:
  Temporarily attempts to set the NMI vector 
    If vector cannot be changed, the driver gets unloaded
    If the NMI can't be generated for whatever reason, the driver is unloaded
    Else NMI vector is set to ADLIB/SB handler and execution continues...

SEE ALSO:
  test_nmi

*/
void setup_nmi(void)
{
    long far *int02_vector = (long far *)0x00000008L; /** ptr to nmi vector **/
    int wait;
    unsigned char pic_io;

    nmi_ok_flag = FALSE;
    old_vectors->old_int02 = getvect(INT_NMI);
    setvect(INT_NMI,test_nmi);

    /** Clear hardware before enabling NMIs **/
    reset_nmi();

    /** Enable only sb irqs **/
    outp( ports.reg_index, 0x45 ); // adlib control
    outp( ports.reg_data_high, 0x20 );

    /** Write to I/O address 0x70 with data bit 7 equal to logic zero
     ** to unmask the NonMaskableInterrupt
     ** This is the CMOS access port which uses addresses 0-3f - high bit
     ** is not used for CMOS addressing.  Therefore, whenever anyone
     ** accesses CMOS, they enable the NMIs ????
     **/
    outp( 0x70, 0x00 );

    /** See if the NMI vector on this computer can be modified... **/
    if( (long)(*int02_vector) != (long)(test_nmi) )
        {
    print_message(LM_NO_MOD_NMI);
//        printf("\n The NMI procedure cannot be changed on this P.C.\n");
        // let it go through so we load anyway...
        //unload_tsr();
        //exit(-5);
        }

    /** Clear the NMI count in case of a pending - Never happen... **/
    nmi_count = 0;

    /** Write to SoundBlaster port 2x6 to generate an NMI and wait... **/
    /** Do a junk read from the joystick port (0x201) to kill time    **/
    outp(ports.reg_sb2x6, 0x01);

    for(wait = 0; (wait < WAIT_FOR_NMI) && !nmi_count; wait++)
        inportb(0x201);

    /** Set back the old NMI after we did the test **/
    setvect( 0x02, old_vectors->old_int02 );            

    /** If it never got into the NMI handler - the NMI must be disabled
     ** on this machine 
     ** 
     ** NOW: (7/17/95) with the addition of the game api, we MUST let SBOS
     ** load.  So, set the sleep flags and continue.  If anyone wakes sbos
     ** later and enables the NMI, it 'shouldnt' matter because the NMI
     ** won't do anything anyway...
     **/
    if(!nmi_count)
        {
    print_message(LM_DISABLED_NMI);
//        printf("The NMI procedure on this P.C. is disabled.\n");
//        printf("Emulation is NOT available!\n");
//        printf("Press any key to continue...");
//        getch();
//        printf("\n");
        nmi_ok_flag = TRUE;  // Make it so it will pass later
        shared->status |= STAT_NO_NMIS;
        sbosdata->adsbint_mask = 0;
        disable_nmi_irq();
        //unload_tsr();
        //exit(-6);        
        }

//vjf Must add defines for InterWave and MAX here

    /** If the nmi_count is above 1, the chip couldn't clear the source
     ** Therefore, there must be a port confilct
     **/
    if( nmi_count > 1 )        
        {
        /** Shut off chip's NMI generation */
    print_message(LM_PORT_CONFLICT);
//        printf("The InterWave port address is in conflict\n");
        unload_tsr();
        exit(-7);        
        }

    /** The card couldn't find the cause of the interrupt **/
    /** This is bad and we should probably unload anyway **/
    if( !nmi_ok_flag )
        {
    print_message(LM_BAD_IW_NMI);
//        printf("The NMI is not functioning on the InterWave\n");
        unload_tsr();
        exit(-8);        
       }        
    /** Set NMI vector to new handler -- All done here **/
    setvect(INT_NMI,*(new_vectors->new_int02));
}

/****************************************************************************

FUNCTION DEFINITION:
enable_nmi_irq - Enables NMIs to be generated through port writes.

DESCRIPTION:
  Write 22h to reg_index register index 45 to enable SB/AD interrupts.
  Write |58 to 2xF to enable MPU401 NMIs if in MPU401 mode

MODIFIES:
  sbosdata->regctl_mask

SEE ALSO: 
  disable_nmi_irq

*/
void enable_nmi_irq(void)
{
#ifdef MPU401
    /** Enable MPU-401 GPRs **/
    if(mpu401_mode)
        {
        sbosdata->regctl_mask |= 0x58;
        outp( ports.reg_2xFcontrol, sbosdata->regctl_mask); 
        }
#endif
    outp( ports.reg_index, 0x45 ); // adlib control
    outp( ports.reg_data_high, sbosdata->adsbint_mask );        
}

/****************************************************************************

FUNCTION DEFINITION:
disable_nmi_irq -

DESCRIPTION:
  Write 00h to reg_index register index 45 to disable SB/AD interrupts
  Write &~58 to 2xF to disable MPU401 NMIs if in MPU401 mode

MODIFIES:
  sbosdata->regctl_mask

SEE ALSO:
  enable_nmi_irq

*/
void disable_nmi_irq(void)
{
#ifdef MPU401
    /** Disable MPU-401 GPRs **/
    sbosdata->regctl_mask &= ~0x58;
    outp( ports.reg_2xFcontrol, sbosdata->regctl_mask); 
#endif

    /** And disable adlib **/
    outp( ports.reg_index, 0x45 ); // adlib control
    outp( ports.reg_data_high, 0x00 );        
}

/****************************************************************************

FUNCTION DEFINITION:
test_nmi - Test interrupt routine for the NMI

DESCRIPTION:
  This routine is installed as a temporary NMI handler to test the card
  and the motherboard for NMI functionality.

SEE ALSO:
  setup_nmi

*/
void interrupt test_nmi(void)
{
unsigned char irq_status;
unsigned char adlib_status;

    nmi_count++;
    
    irq_status = inportb(ports.reg_irq_status); // 2x6 - irq status

    if( irq_status & 0x10 ) // Triggered by an ADLIB/SB port
        {
        adlib_status = inportb(ports.reg_adlib_status); // 2x8 - adlib status
    
        if(adlib_status & 0x08) // 2x6 write triggered
            {
            /** Yep, I was here OK **/
            nmi_ok_flag = TRUE;

            /** Clear and reset the NMI **/
            disable_nmi_irq();
            enable_nmi_irq();
            reset_nmi();        
            }
        }
        
    if( nmi_count > 1)
        {
        disable_nmi_irq();
        enable_nmi_irq();
        reset_nmi();        
        }
}

/****************************************************************************

FUNCTION DEFINITION:
reset_nmi - Resets the NMI on the 8259

DESCRIPTION:
  Resets the NMI triggered by the hardware pull of the I/O CK CH line.
      Toggles bit 3 of port 61 high and low
  Also Pulls Memory Parity Check bit ( Bit 2 ) Low

SEE ALSO:
  test_nmi, setup_nmi

*/
/***************************************************************************
 * reset_nmi()
 *
 ***************************************************************************/
void reset_nmi(void)
{
unsigned char    val;

    if (!combine)
        {    
        val = inportb(0x61);
        val |= 0x08;
        outp(0x61,val);
//        val &= ~0x0C;
        val &= ~0x08;
        outp(0x61,val);
        }
}

/****************************************************************************

FUNCTION DEFINITION:
write_dram - Writes value at DRAM memory location address 

SEE ALSO:
  read_dram

*/
void write_dram( unsigned long address, unsigned char value )
{
    outp(ports.reg_index, 0x43); /** set DRAM low **/
    outpw (ports.reg_data_low, (unsigned int)(address));
    outp(ports.reg_index,0x44); /** set DRAM high **/
    outp(ports.reg_data_high,LOW_B(MS_WORD(address)));
    outp(ports.reg_dram_io,value);
}

/****************************************************************************

FUNCTION DEFINITION:
read_dram - Reads DRAM at the memory location specified as address and returns
  the value

SEE ALSO:
  write_dram

RETURNS:
  uchar - dram data

*/
unsigned char read_dram( unsigned long address )
{
    unsigned char retval;

    outp(ports.reg_index, 0x43); /** set DRAM low **/
    outpw (ports.reg_data_low, (unsigned int)(address));
    outp(ports.reg_index,0x44); /** set DRAM high **/
    outp(ports.reg_data_high,LOW_B(MS_WORD(address)));
    retval = inportb(ports.reg_dram_io);
    return retval;
}

/****************************************************************************

FUNCTION DEFINITION:
reload_data -

DESCRIPTION:
  Uses the SDK to Download one of the .IWL files into DRAM
  Reads data from .IWL file directly into driver data space

EXPECTS:

MODIFIES:

SEE ALSO:

RETURNS:

*/
void reload_data(void)
{
int fd;
char lib_name[80];
SBOS_LIB_HEADER lib_header;
unsigned int length;
char *dma_buff1,*dma_buff2;
unsigned int loop_count;
int i;
int val;
int size;
char drive[MAXDRIVE];
char dir[MAXDIR];
long wave_loc;
char lib_file[127];
unsigned int new_lmcfi;
unsigned int old_lmcfi;
#ifdef FAKE_ROM
int rom_flag = FALSE;
#else
int rom_flag = TRUE;
#endif

    if (SynOpen(config,NUM_OSCILLATORS) == NO_CARD)
        {
        unload_tsr();
        exit(-1);
        }
    
    if(!(shared->status & STAT_ROM))
        {
        /** Get library filename **/
        fnsplit(sbosdata->loader_name,drive,dir,NULL,NULL);
    
        size = 256*sbosdata->dram_size;
    
        sprintf(lib_name,"library%d",size);
        iwu_get_profile_string("iwsbos",lib_name,"",lib_file,
                        127,ini_file);
        fd = open( lib_file, O_BINARY|O_RDWR,S_IREAD|S_IWRITE );
        if(fd < 0)
        print_message(LM_CANT_OPEN_LIB);
       //     printf("Can't Open Lib file %s\n",lib_file);
    
        /** Read the library header **/
        if (read(fd,&lib_header,sizeof(SBOS_LIB_HEADER)) != 
            sizeof(SBOS_LIB_HEADER))
            {
        print_message(LM_BAD_LIB_HEADER);
//            printf("Can't read library header\n");
            unload_tsr();
            exit(-1);
            }
        shared->instrument_maps = lib_header.lib_map;
        shared->patch_info      = lib_header.patch_info;
        shared->num_waves       = lib_header.num_waves;
        shared->num_basic       = lib_header.num_basic;
        shared->num_melodics    = lib_header.num_melodics;
        shared->num_percs       = lib_header.num_percs;
        shared->num_effects     = lib_header.num_effects;
    
        /** Check the version numbers in the header verses version of the driver **/
        if ((lib_header.major  != shared->version_high) || 
            (lib_header.minor1 != shared->version_low1) ||
            (lib_header.minor2 != shared->version_low2))
            {
        print_message(LM_BAD_LIB_VERSION);
//            printf("\nIWSBOS: Library version does not match driver's.\n");
//            printf("IWSBOS: Data may not be valid\n");
            }
    
        lseek(fd,(long)(sizeof(WAVE_INFO)*lib_header.num_waves),1);
    
        /** Now read the PC-resident wave data **/
        val = read(fd,wave_data,sizeof(WAVE_DATA)*lib_header.num_waves);
        if (val != sizeof(WAVE_DATA)*lib_header.num_waves)
            {
        print_message(LM_BAD_LIB_DATA);
//            printf("Can't read wave data\n");
            unload_tsr();
            exit(-1);
            }
    
        /** Now read past the mpu-401 envelope info **/
        lseek(fd,(long)(sizeof(SYN_ENVELOPE)*lib_header.num_waves),1);
    
        /** Try to allocate first buffer for DMA transfer **/
        dma_buff1 = malloc(DMA_SIZE);
        if (dma_buff1 == NULL)
            {
        print_message(LM_MEM_DLB);
//            printf("Can't allocate download buffer (%d)\n",DMA_SIZE);
            unload_tsr();
            exit(-1);
            }
    
        /** Try to allocate second buffer for DMA transfer **/
        dma_buff2 = malloc(DMA_SIZE);
        if (dma_buff2 == NULL)
            {
        print_message(LM_MEM_DLB);
//            printf("Can't allocate download buffer (%d)\n",DMA_SIZE);
            unload_tsr();
            exit(-1);
            }
    
        /** OK. Now download the rest of the file and put in DRAM **/
        /** Use a double buffering system -- read from disk while DMAing to card **/
        loop_count = 1024/DMA_BUFFK;        // Use max, file MIGHT be less ...
        for (i=0;i<loop_count;i+=2)
            {
            if ((val=read(fd,dma_buff1,DMA_SIZE)) != DMA_SIZE)
                break;
            Download(dma_buff1,0,(long)i*(long)DMA_SIZE,DMA_SIZE,FALSE);
            if ((val=read(fd,dma_buff2,DMA_SIZE)) != DMA_SIZE)
                break;
            Download(dma_buff2,0,(long)(i+1)*(long)DMA_SIZE,DMA_SIZE,FALSE);
            }
        
        free(dma_buff1);
        free(dma_buff2);
        close(fd);

        }
    else
        {
        // Save the current RAM/ROM config
        outp(ports.reg_index,IW_LMCFI);
        old_lmcfi = inpw(ports.reg_data_low);
        // Now change it so ROMS are 4 meg on 4 meg boundaries
        new_lmcfi = ((old_lmcfi & 0xFF1F) | 0x0080);
        outpw(ports.reg_data_low,new_lmcfi);

// Find this stuff in ROM somewhere ....
        wave_loc = find_rom_sbos(ports.reg_dram_io,&lib_header,shared,rom_flag);
        if (wave_loc == 0)
            {
        print_message(LM_NO_SBOS_IN_ROM);
//            printf("No SBOS data found in ROMs\n");
            unload_tsr();
            exit(-1);
            }

        shared->instrument_maps = lib_header.lib_map;
        shared->patch_info      = lib_header.patch_info;
        shared->num_waves       = lib_header.num_waves;
        shared->num_basic       = lib_header.num_basic;
        shared->num_melodics    = lib_header.num_melodics;
        shared->num_percs       = lib_header.num_percs;
        shared->num_effects     = lib_header.num_effects;

        sbosdata->dram_begin         = lib_header.dram_info.dram_begin;
        sbosdata->dram_stopped_voice = lib_header.dram_info.dram_stopped_voice;
        sbosdata->dram_temp_buff     = lib_header.dram_info.dram_temp_buff;
        sbosdata->dram_perc_map      = lib_header.dram_info.dram_perc_map;
        sbosdata->dram_wave_info     = lib_header.dram_info.dram_wave_info;
        sbosdata->dram_mpu_info      = lib_header.dram_info.dram_mpu_info;

        /** Now get the PC-resident wave data **/
        // wave_loc is where it sits in ROM ....
        PeekString(ports.reg_dram_io, wave_loc, wave_data,
                        lib_header.num_waves*sizeof(WAVE_DATA),rom_flag);

        // Now restore the old config
        outp(ports.reg_index,IW_LMCFI);
        outpw(ports.reg_data_low,old_lmcfi);
        }

        SynClose();
        SynthReset(NUM_OSCILLATORS);
                    
        /**
         ** Make a far pointer to the pick table in the driver.
         ** Take the segment of shared and add on the offset of the pick table.
         **/
    update_pick_table(MK_FP(FP_SEG(shared),FP_OFF(shared->pick_table)), 
        &(lib_header.sustain[0]));

}

/****************************************************************************

FUNCTION DEFINITION:
update_pick_table - Updates the instrument pick table in the driver with
  sustain information required for valid FM instrument picking

DESCRIPTION:
  Sets or clears the sustain bit in the pick_table in the driver based
  on information in the library header.  The header contains a boolean
  based on a patches' ability to sustain and is directly copied into
  bit 3 (08) of byte 4 in the instrument picking table.

EXPECTS:
  Data in the instrument table in the driver has been loaded

MODIFIES:
  inst_tbl

SEE ALSO:
  asm_fm_picker

*/
void update_pick_table(unsigned char far *pick_table, unsigned char *sustain)
{
    int inst_cnt;
    int inst_number;
    
    for(inst_cnt = 0; inst_cnt < (NUM_INSTRUMENTS*4); inst_cnt+=4)
        {
        /**
         ** This will index the pick_table on every fourth byte to see each
         ** instrument's data.  We look at the first byte to get the instrument
         ** number and index the table accordingly.
         **/
        inst_number = pick_table[inst_cnt];
            
        if(sustain[inst_number] == 0x01)
            pick_table[inst_cnt+3] |= 0x08;
        else
            pick_table[inst_cnt+3] &= 0xF7;
        }
}

/****************************************************************************

FUNCTION DEFINITION:
usage - Prints out usage information for driver and loader

SEE ALSO:
  main

*/
void usage(void)
{
print_message(LM_USAGE);
//printf("IWSBOS Version %d.%d%d\n\n",
//    VERSION_HIGH,VERSION_LOW1,VERSION_LOW2);
exit(0);
}

int print_error(int errornum)
{
    char *str;
    int ret = 0;

    if (errornum == ERROR_NO_LANGUAGE) 
        str = "Invalid Language Section in IW.INI.";
    else if (errornum == ERROR_NO_DEF_LANGUAGE) 
        str = "Invalid Default Language in IW.INI.";
    else if (errornum == ERROR_BAD_IWINI)
        str = "Can't Find IW.INI.";
    else if (errornum == ERROR_BAD_MESSAGE_FILE)
        str = "Bad or Missing language File.";
    else if (errornum == ERROR_NO_INTERWAVE)
        str = "Can't Find INTERWAVE Environment Variable.";
    else
        {
        str = error_string;
        error_string[0] = 0;
        ret = iw_mls_getstring("sbos.mls",errornum,error_string,ERROR_STR_LEN);
        }

    if(ret == 0) /** OK **/
        {
        printf("%s\n",str);
        return(0);
        }

    if(errornum != ret) /** BAD STATUS - print out error message **/
        print_error(ret);
    return (-1);
}

void print_banner(void)
{
        int ret;

    if(!print_messages) return;

    ret = iw_mls_getstring("sbos.mls", LM_BANNER, error_string, ERROR_STR_LEN);
    if(ret == 0)
        {
        printf("%s ",error_string);
        printf("%d.%d%d\n", VERSION_HIGH,VERSION_LOW1,VERSION_LOW2);
        }
    else
        print_error(ERROR_BAD_MESSAGE_FILE);
//    print_message(LM_COPYRIGHT);
}

int print_message(int errornum)
{
    int ret;

    if(!print_messages) return 0;

    ret = iw_mls_getstring("sbos.mls", errornum, error_string, ERROR_STR_LEN);
    if(ret == 0)
        printf("%s\n",error_string);

    return ret;
}

int sbos_mls_init(void)
{
    char buf[128];
    char *cp;

    iwu_get_profile_string("languages","default","",buf,128,ini_file);
    if(*buf == 0)
        {
        print_error(ERROR_NO_LANGUAGE);
        return(-1);
        }

    iwu_get_profile_string("languages",buf,"",buf,128,ini_file);
    if(*buf == 0)
        {
        print_error(ERROR_NO_DEF_LANGUAGE);
        return(-1);
        }

    /**
     ** go to the next null or ',' in the language string
     **/
    for (cp=buf; *cp && *cp != ','; cp++) ;
    if (*cp == ',')
        cp++;
    else 
        {
        print_error(ERROR_NO_DEF_LANGUAGE);
        return(-1);
        }
    while (iwu_isspace(*cp)) cp++;
    iw_mls_init(cp);
    return(0);
}
