/****************************************************************************
 *
 *   config.c
 *
 *   Copyright (c) 1991 Microsoft Corporation.  All Rights Reserved.
 *
 ***************************************************************************/

/*
 *  Definition of interface to kernel driver (synth.sys)
 *
 *     The kernel driver's Dos device name is assumed fixed and known
 *
 *          adlib.mid or adlib.mid0
 *
 *     The kernel driver is opened in read/write mode.
 *
 *     Writing to the driver sends a list of SYNTH_DATA structures
 *     to the driver.  The port number MUST be 0x388 or 0x389.
 *
 *
 *     Reading always reads just 1 byte - the status port.
 */

#include <windows.h>              // The VDD is just a win32 DLL
#include <vddsvc.h>               // Definition of VDD calls
#include "vdd.h"                // Common data with kernel driver
#include <stdio.h>

/*
 *  Debugging
 */

#if DBG

    int VddDebugLevel = 1;


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

    Generate debug output in printf type format

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

    void VddDbgOut(LPSTR lpszFormat, ...)
    {
        char buf[256];
        va_list va;

        OutputDebugStringA("Ad Lib VDD: ");

        va_start(va, lpszFormat);
        vsprintf(buf, lpszFormat, va);
        va_end(va);

        OutputDebugStringA(buf);
        OutputDebugStringA("\r\n");
    }

    #define dprintf( _x_ )                          VddDbgOut _x_
    #define dprintf1( _x_ ) if (VddDebugLevel >= 1) VddDbgOut _x_
    #define dprintf2( _x_ ) if (VddDebugLevel >= 2) VddDbgOut _x_
    #define dprintf3( _x_ ) if (VddDebugLevel >= 3) VddDbgOut _x_
    #define dprintf4( _x_ ) if (VddDebugLevel >= 4) VddDbgOut _x_


#else

    #define dprintf(x)
    #define dprintf1(x)
    #define dprintf2(x)
    #define dprintf3(x)
    #define dprintf4(x)

#endif // DBG


/*
 *   Symbolic names for port addresses
 */

 #define ADLIB_DATA_PORT 0x389
 #define ADLIB_REGISTER_SELECT_PORT 0x388
 #define ADLIB_STATUS_PORT 0x388

/*
 *   Batch data to the device - for true Adlib use a size of 2
 */

 #define BATCH_SIZE 40
 int Position = 0;
 SYNTH_DATA PortData[BATCH_SIZE];


/*
 *  Internal Routines
 */

 void MyByteIn(WORD port, BYTE *data);
 void MyByteOut(WORD port, BYTE data);

/*
 *  IO handler table.
 *
 *  There's no point in providing string handlers because the chip
 *  can't respond very quickly (need gaps of at least 23 microseconds
 *  between writes).
 */

 VDD_IO_HANDLERS handlers = {
     MyByteIn,
     NULL,
     NULL,
     NULL,
     MyByteOut,
     NULL,
     NULL,
     NULL};

/*
 *  Note that we rely on the kernel driver to pretend the device is
 *  at address 388 even the driver supports it somewhere else.
 */

 VDD_IO_PORTRANGE ports[] = {
    {
       0x228,
       0x229
    },
    {
       0x388,
       0x389
    }
 };

/*
 *  Globals
 */


 //
 // Track timers.  The basic rule is that if no timer is started then
 // the only way the status register can change is via the reset bit
 // in which case we know what will happen.
 //
 // If a timer interrupts then it's 'stopped'
 //

 BOOL Timer1Started;
 BOOL Timer2Started;
 BYTE Status;

/*
 *  Current device handle
 *
 *  NULL if device is (potentially) free
 *  INVALID_HANDLE_VALUE if device was not obtainable
 */

 HANDLE DeviceHandle;

 HANDLE OpenDevice(PWSTR DeviceName)
 {
     WCHAR DosDeviceName[MAX_PATH];


    /*
     *  Make up a string suitable for opening a Dos device
     */

     wcscpy(DosDeviceName, TEXT("\\\\."));
     wcscat(DosDeviceName, DeviceName +
                           wcslen(TEXT("\\Device")));

    /*
     *  Open the device with GENERIC_READ and GENERIC_WRITE
     *  Also use FILE_SHARE_WRITE so other applications can
     *  set the device volume
     */

     return         CreateFile(DosDeviceName,
                               GENERIC_WRITE | GENERIC_READ,
                               FILE_SHARE_WRITE,
                               NULL,
                               OPEN_EXISTING,
                               0,
                               NULL);

 }

/*
 *  Open our device is it can be opened and we haven't tried before
 *
 *  Returns FALSE if device can't be acquired.
 */

 BOOL CheckDeviceAccess(void)
 {

    /*
     *  If we don't have a handle (valid or invalid) already try
     *  opening the device
     */

     if (DeviceHandle == NULL) {

         DeviceHandle = OpenDevice(STR_ADLIB_DEVICENAME);

         if (DeviceHandle == INVALID_HANDLE_VALUE) {
             DeviceHandle = OpenDevice(STR_ADLIB_DEVICENAME L"0");
         }
         Position = 0;
     }

     return DeviceHandle != INVALID_HANDLE_VALUE;
 }

/*
 *  Map a write to a port
 *
 *  How are we going to simulate timer stuff?
 *  Answer: Allow reading of the status port.
 *
 *  This is optimized to only write when we get a data port write
 */


 void MyByteOut(WORD port, BYTE data)
 {
     //
     // Remember what register is selected
     //

     static BYTE AdlibRegister;

     //
     // Just package the stuff up and call write file
     //

     DWORD BytesWritten;

     dprintf3(("Received write to Port %4X, Data %2X", port, data));

     port = (port & 1) | ADLIB_REGISTER_SELECT_PORT;


    /*
     *  Check for special values - don't let them switch to
     *  OPL3 mode.
     */

#if 0
     if (port == ADLIB_DATA_PORT && AdlibRegister == AD_NEW) {
         data &= 0xFE;
     }
#endif


     if (port == ADLIB_REGISTER_SELECT_PORT) {
        /*
         *  Just remember which register is supposed to be selected
         *  to cut down the number of times we go to the device driver
         */

         AdlibRegister = data;
     } else {

        /*
         *  Write this one to the device
         */

         PortData[Position].IoPort = ADLIB_REGISTER_SELECT_PORT;
         PortData[Position].PortData = AdlibRegister;
         PortData[Position + 1].IoPort = port;
         PortData[Position + 1].PortData = data;

         Position += 2;

         if (Position == BATCH_SIZE ||
             AdlibRegister >= 0xA0 && AdlibRegister <= 0xBF ||
             AdlibRegister == AD_MASK) {

            /*
             *  See if we have the device
             */

             if (CheckDeviceAccess()) {

                 if (!WriteFile(DeviceHandle,
                                &PortData,
                                Position * sizeof(PortData[0]),
                                &BytesWritten,
                                NULL)) {
                     dprintf1(("Failed to write to device!"));
                 } else {
                    /*
                     *  Work out what status change may have occurred
                     */

                     if (AdlibRegister == AD_MASK) {

                        /*
                         *  Look for RST and starting timers
                         */

                         if (data & 0x80) {
                             Status = 0;
                         }

                        /*
                         *  We ignore starting of timers if their interrupt
                         *  flag is set because the timer status will have to
                         *  be set again to make the status for this timer change
                         */

                         if ((data & 1) && !(Status & 0x40)) {
                             dprintf2(("Timer 1 started"));
#if 0
                             Timer1Started = TRUE;
#else
                             Status |= 0xC0;
#endif
                         } else {
                             Timer1Started = FALSE;
                         }

                         if ((data & 2) && !(Status & 0x20)) {
                             dprintf2(("Timer 2 started"));
#if 0
                             Timer2Started = TRUE;
#else
                             Status |= 0xA0;
#endif
                             Timer2Started = TRUE;
                         } else {
                             Timer2Started = FALSE;
                         }
                     }
                 }
             }

             Position = 0;
         }
     }
 }


/*
 *  Gets called when the application reads from one of our ports.
 *  We know the device only returns interesting things in the status port.
 */

 void MyByteIn(WORD port, BYTE *data)
 {
     DWORD BytesRead;

     dprintf4(("Received read from Port %4X", port));

     port = (port & 1) | ADLIB_STATUS_PORT;

    /*
     *  If we fail simulate nothing at the port
     */

     *data = 0xFF;

    /*
     *  Say there's nothing there if we didn't get the device driver or
     *  it's not the status port
     */

     if (port != ADLIB_STATUS_PORT || !CheckDeviceAccess()) {
         return;
     }

#if 0 // WSS interrupt messed this up
    /*
     *  Are we expecting a state change ?
     */

     if (Timer1Started || Timer2Started) {

        /*
         *  Read the status port from the driver - this is how the
         *  driver interprets read.
         *  Well, actually don't because the WSS driver doesn't work!
         */

         if (!ReadFile(DeviceHandle,
                       &Status,
                       1,
                       &BytesRead,
                       NULL)) {

             dprintf1(("Failed to read from device - code %d", GetLastError()));
         } else {

            /*
             *  Look for state change
             */

             if (Status & 0x40) {
                 Timer1Started = FALSE;
                 dprintf2(("Timer 1 finished"));
             }

             if (Status & 0x20) {
                 Timer2Started = FALSE;
                 dprintf2(("Timer 2 finished"));
             }
         }
     }
#endif

     dprintf3(("Data read was %2X", Status));
     *data = Status;
 }


/*
 *  Standard DLL entry point routine.
 */

 BOOL DllEntryPoint(HINSTANCE hInstance, DWORD Reason, LPVOID Reserved)
 {
     switch (Reason) {
     case DLL_PROCESS_ATTACH:
         if (!VDDInstallIOHook(hInstance, 2, ports, &handlers)) {
             dprintf2(("Ad Lib VDD failed to load - error in VDDInstallIoHook"));
             return FALSE;
         } else {
             dprintf2(("Ad Lib VDD loaded OK"));
             return TRUE;
         }

     case DLL_PROCESS_DETACH:
         VDDDeInstallIOHook(hInstance, 2, ports);

        /*
         *  Note that  this event corresponds to FreeLibrary on our DLL,
         *  NOT termination of the process - so we can't rely on process
         *  termination to close our device handle.
         *
         */

         if (DeviceHandle) {
             CloseHandle(DeviceHandle);
             DeviceHandle = NULL;      // Redundant but neater.
         }
         return TRUE;

     default:
         return TRUE;
     }
 }