//**************************************************************************
//
//      MSGAME.C -- Xena Gaming Project
//
//      Version 3.XX
//
//      Copyright (c) 1997 Microsoft Corporation. All rights reserved.
//
//      @doc
//      @module MSGAME.C | Human Input Device (HID) gameport driver
//**************************************************************************

#include    "msgame.h"

//---------------------------------------------------------------------------
//  Alloc_text pragma to specify routines that can be paged out.
//---------------------------------------------------------------------------

#ifdef  ALLOC_PRAGMA
#pragma alloc_text (INIT, DriverEntry)
#pragma alloc_text (INIT, MSGAME_ReadRegistry)
#pragma alloc_text (PAGE, MSGAME_CreateClose)
#pragma alloc_text (PAGE, MSGAME_SystemControl)
#pragma alloc_text (PAGE, MSGAME_AddDevice)
#endif

//---------------------------------------------------------------------------
//      Private Data
//---------------------------------------------------------------------------

static  UNICODE_STRING      RegistryPath;

//---------------------------------------------------------------------------
// @func        Main driver entry point
//  @parm       PDRIVER_OBJECT | DriverObject | Pointer to driver object
//  @parm       PUNICODE_STRING | registryPath | Registry path for this device
// @rdesc   Returns NT status code
//  @comm       Public function
//---------------------------------------------------------------------------

NTSTATUS    DriverEntry (IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING registryPath)
{
    NTSTATUS                                ntStatus;
    RTL_QUERY_REGISTRY_TABLE        Parameters[2];
    HID_MINIDRIVER_REGISTRATION HidMinidriverRegistration;

    MsGamePrint ((DBG_CRITICAL, "%s: Built %s at %s\n", MSGAME_NAME, __DATE__, __TIME__));
    MsGamePrint ((DBG_INFORM,   "%s: DriverEntry Enter\n", MSGAME_NAME));

    //
    //  Fill in driver dispatch table
    //

    DriverObject->MajorFunction[IRP_MJ_CREATE]                      =   MSGAME_CreateClose;
    DriverObject->MajorFunction[IRP_MJ_CLOSE]                       =   MSGAME_CreateClose;
    DriverObject->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL]     =   MSGAME_Internal_Ioctl;
    DriverObject->MajorFunction[IRP_MJ_PNP]                         =   MSGAME_PnP;
    DriverObject->MajorFunction[IRP_MJ_POWER]                       =   MSGAME_Power;
    DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL]              =   MSGAME_SystemControl;
    DriverObject->DriverUnload                                      =   MSGAME_Unload;
    DriverObject->DriverExtension->AddDevice                        =   MSGAME_AddDevice;

    //
    // Register driver with Hid.Sys
    //

    HidMinidriverRegistration.Revision                  = HID_REVISION;
    HidMinidriverRegistration.DriverObject              = DriverObject;
    HidMinidriverRegistration.RegistryPath              = registryPath;
    HidMinidriverRegistration.DeviceExtensionSize   = sizeof (DEVICE_EXTENSION);
    HidMinidriverRegistration.DevicesArePolled      = TRUE;
    MsGamePrint ((DBG_CONTROL, "%s: Registering with HID.SYS\n", MSGAME_NAME));
    ntStatus = HidRegisterMinidriver (&HidMinidriverRegistration);
    
    //
    // Need to ensure that the registry path is null-terminated.
    // Allocate pool to hold a null-terminated copy of the path.
    //

    if (NT_SUCCESS(ntStatus))
        {
        RtlInitUnicodeString (&RegistryPath, NULL);
        RegistryPath.Length = registryPath->Length + sizeof(UNICODE_NULL);
        RegistryPath.MaximumLength = RegistryPath.Length;
        RegistryPath.Buffer = ExAllocatePool (PagedPool, RegistryPath.Length);
        RtlZeroMemory (RegistryPath.Buffer, RegistryPath.Length);
        RtlMoveMemory (RegistryPath.Buffer, registryPath->Buffer, registryPath->Length);
        }

    //
    //  Read any driver specific registry values
    //

    if (NT_SUCCESS(ntStatus))
        {
        RtlZeroMemory (Parameters, sizeof(Parameters));
        Parameters[0].Flags             = RTL_QUERY_REGISTRY_DIRECT;
        Parameters[0].Name              = L"PollingInterval";
        Parameters[0].EntryContext  = &PollingInterval;
        Parameters[0].DefaultType       = REG_DWORD;
        Parameters[0].DefaultData       = &PollingInterval;
        Parameters[0].DefaultLength = sizeof(ULONG);
        if (!NT_SUCCESS(RtlQueryRegistryValues (RTL_REGISTRY_ABSOLUTE, RegistryPath.Buffer, Parameters, NULL, NULL)))
            {
            MsGamePrint((DBG_INFORM,"%s: %s_DriverEntry RtlQueryRegistryValues failed\n", MSGAME_NAME, MSGAME_NAME));
            RtlWriteRegistryValue (RTL_REGISTRY_ABSOLUTE, RegistryPath.Buffer, L"PollingInterval", REG_DWORD, &PollingInterval, sizeof (ULONG));
            }
        MsGamePrint((DBG_CONTROL,"%s: Polling interval will be %lu milliseconds\n", MSGAME_NAME, PollingInterval));
        }

    //
    //  Initialize portio layer on entry
    //

    if (NT_SUCCESS(ntStatus))
        ntStatus = PORTIO_DriverEntry ();

    //
    //  Initialize device layer on entry
    //

    if (NT_SUCCESS(ntStatus))
        ntStatus = DEVICE_DriverEntry ();

    // 
    // Return driver status
    //

    MsGamePrint ((DBG_INFORM, "%s: DriverEntry Exit = %x\n", MSGAME_NAME, ntStatus));
    return (ntStatus);
}

//---------------------------------------------------------------------------
// @func        Process the Create and Close IRPs
//  @parm       PDEVICE_OBJECT | DeviceObject | Pointer to device object
//  @parm       PIRP | pIrp | Pointer to IO request packet
// @rdesc   Returns NT status code
//  @comm       Public function
//---------------------------------------------------------------------------

NTSTATUS    MSGAME_CreateClose (IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    PIO_STACK_LOCATION  IrpStack;
    NTSTATUS                    ntStatus = STATUS_SUCCESS;

    PAGED_CODE ();

    MsGamePrint ((DBG_INFORM, "%s: %s_CreateClose Enter\n", MSGAME_NAME, MSGAME_NAME));

    //
    // Get pointer to current location in Irp
    //

    IrpStack = IoGetCurrentIrpStackLocation (Irp);

    //
    // Process Create or Close function call
    //

    switch (IrpStack->MajorFunction)
        {
        case IRP_MJ_CREATE:
            MsGamePrint ((DBG_VERBOSE, "%s: IRP_MJ_CREATE\n", MSGAME_NAME));
            Irp->IoStatus.Information = 0;
            break;

        case IRP_MJ_CLOSE:
            MsGamePrint ((DBG_VERBOSE, "%s: IRP_MJ_CLOSE\n", MSGAME_NAME));
            Irp->IoStatus.Information = 0;
            break;

        default:
            MsGamePrint ((DBG_SEVERE, "%s:  Invalid CreateClose Parameter\n", MSGAME_NAME));
            ntStatus = STATUS_INVALID_PARAMETER;
            break;
        }

    //
    // Save Status for return and complete Irp
    //

    Irp->IoStatus.Status = ntStatus;
    IoCompleteRequest (Irp, IO_NO_INCREMENT);

    MsGamePrint ((DBG_INFORM, "%s:  %s_CreateClose Exit = %x\n", MSGAME_NAME, MSGAME_NAME, ntStatus));
    return (ntStatus);
}

//---------------------------------------------------------------------------
// @func        Process the WMI system control IRPs
//  @parm       PDEVICE_OBJECT | DeviceObject | Pointer to device object
//  @parm       PIRP | pIrp | Pointer to IO request packet
// @rdesc   Returns NT status code
//  @comm       Public function
//---------------------------------------------------------------------------

NTSTATUS    MSGAME_SystemControl (IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    NTSTATUS ntStatus = STATUS_SUCCESS;

    PAGED_CODE ();

    MsGamePrint ((DBG_INFORM, "%s: %s_SystemControl Enter\n", MSGAME_NAME, MSGAME_NAME));

    IoSkipCurrentIrpStackLocation (Irp);

    ntStatus = IoCallDriver (GET_NEXT_DEVICE_OBJECT(DeviceObject), Irp);
    
    MsGamePrint ((DBG_INFORM, "%s:  %s_SystemControl Exit = %x\n", MSGAME_NAME, MSGAME_NAME, ntStatus));
    
    return (ntStatus);
}

//---------------------------------------------------------------------------
// @func        Processes the Pnp Add Device call
//  @parm       PDRIVER_OBJECT | DriverObject | Pointer to driver object
//  @parm       PDEVICE_OBJECT | DeviceObject | Pointer to device object
// @rdesc   Returns NT status code
//  @comm       Public function
//---------------------------------------------------------------------------

NTSTATUS    MSGAME_AddDevice (IN PDRIVER_OBJECT DriverObject, IN PDEVICE_OBJECT DeviceObject)
{
    NTSTATUS                ntStatus    = STATUS_SUCCESS;
    PDEVICE_EXTENSION   pDevExt;

    PAGED_CODE ();

    MsGamePrint ((DBG_INFORM, "%s: %s_AddDevice Entry\n", MSGAME_NAME, MSGAME_NAME));

    //
    // Initialize the device extension
    //

    pDevExt = GET_MINIDRIVER_DEVICE_EXTENSION (DeviceObject);
    memset(pDevExt, 0, sizeof(DEVICE_EXTENSION));

    pDevExt->Driver     =   DriverObject;
    pDevExt->Self           =   DeviceObject;
    pDevExt->IrpCount   =   1;
    pDevExt->Started        =   FALSE;
    pDevExt->Removed        =   FALSE;
    pDevExt->Surprised  =   FALSE;
    pDevExt->Removing       =   FALSE;
    pDevExt->TopOfStack =   NULL;
    KeInitializeEvent (&pDevExt->StartEvent, NotificationEvent, FALSE);
    KeInitializeEvent (&pDevExt->RemoveEvent, SynchronizationEvent, FALSE);

    //
    //  Clear device initialization flags
    //
    
    DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;

    //
    // Attach our functional driver to the device stack. The return value of
    // IoAttachDeviceToDeviceStack is the top of the attachment chain. This
    // is where all the IRPs should be routed.
    //

    pDevExt->TopOfStack = GET_NEXT_DEVICE_OBJECT(DeviceObject);

    //
    // If this attachment fails then top of stack will be null. Failure
    // for attachment is an indication of a broken plug play system.
    //

    ASSERT (pDevExt->TopOfStack);

    //
    //  Return status
    //

    MsGamePrint ((DBG_INFORM, "%s: %s_AddDevice Exit = %x\n", MSGAME_NAME, MSGAME_NAME, ntStatus));
    return (ntStatus);
}

//---------------------------------------------------------------------------
// @func        Processes the driver unload call
//  @parm       PDRIVER_OBJECT | DriverObject | Pointer to driver object
// @rdesc   Returns NT status code
//  @comm       Public function
//---------------------------------------------------------------------------

VOID    MSGAME_Unload (IN PDRIVER_OBJECT DriverObject)
{
    PAGED_CODE();

    MsGamePrint ((DBG_INFORM, "%s: %s_Unload Enter\n", MSGAME_NAME, MSGAME_NAME));

    //
    // All the device objects should be gone
    //

    ASSERT (!DriverObject->DeviceObject);

    //
    // Free the unicode strings.
    //

    ExFreePool (RegistryPath.Buffer);

    MsGamePrint ((DBG_CONTROL, "%s: %s_Unload Exit\n", MSGAME_NAME, MSGAME_NAME));
}

//---------------------------------------------------------------------------
// @func        Reads registry data for a named device
//  @parm       PCHAR | DeviceName | Device name string
//  @parm       PDEVICE_VALUES | DeviceValues | Device values structure to fill
// @rdesc   Returns nothing
//  @comm       Public function
//---------------------------------------------------------------------------

VOID    MSGAME_ReadRegistry (PCHAR DeviceName, PDEVICE_VALUES DeviceValues)
{
    #define PARAMS_PLUS_ONE 13

    NTSTATUS                            ntStatus;
    ANSI_STRING                     AnsiName;
    UNICODE_STRING                  UnicodeName;
    UNICODE_STRING                  ParametersPath;
    PRTL_QUERY_REGISTRY_TABLE   Parameters;

    MsGamePrint((DBG_INFORM,"%s: %s_ReadRegistry Enter\n", MSGAME_NAME, MSGAME_NAME));

    //
    //  Initialize local variables
    //

    RtlInitAnsiString       (&AnsiName, DeviceName);
    RtlInitUnicodeString    (&UnicodeName, NULL);
    RtlInitUnicodeString    (&ParametersPath, NULL);

    Parameters = ExAllocatePool (PagedPool, sizeof(RTL_QUERY_REGISTRY_TABLE) * PARAMS_PLUS_ONE);

    if (!Parameters)
        {
        MsGamePrint((DBG_CRITICAL, "%s: %s_ReadRegistry couldn't allocate Rtl query table for %ws\n", MSGAME_NAME, MSGAME_NAME, RegistryPath.Buffer));
        goto ReadRegistryExit;
        }

    RtlZeroMemory (Parameters, sizeof(RTL_QUERY_REGISTRY_TABLE) * PARAMS_PLUS_ONE);

    //
    // Form a path to this driver's Parameters subkey.
    //

    ParametersPath.MaximumLength    = RegistryPath.Length + MAX_DEVICE_NAME;
    ParametersPath.Buffer           = ExAllocatePool (PagedPool, ParametersPath.MaximumLength);

    if (!ParametersPath.Buffer)
        {
        MsGamePrint((DBG_CRITICAL, "%s: %s_ReadRegistry couldn't allocate path string for %ws\n", MSGAME_NAME, MSGAME_NAME, RegistryPath.Buffer));
        goto ReadRegistryExit;
        }

    //
    // Form the Parameters path.
    //

    RtlZeroMemory (ParametersPath.Buffer, ParametersPath.MaximumLength);
    RtlAppendUnicodeToString (&ParametersPath, RegistryPath.Buffer);
    RtlAppendUnicodeToString (&ParametersPath, L"\\");

    RtlAnsiStringToUnicodeString (&UnicodeName, &AnsiName, TRUE);
    RtlAppendUnicodeStringToString (&ParametersPath, &UnicodeName);
    RtlFreeUnicodeString (&UnicodeName);

    MsGamePrint((DBG_VERBOSE, "%s: %s_ReadRegistry path is %ws\n", MSGAME_NAME, MSGAME_NAME, ParametersPath.Buffer));

    //
    // Gather all device information from the registry.
    //

    Parameters[0].Flags             = RTL_QUERY_REGISTRY_DIRECT;
    Parameters[0].Name              = L"PacketStartTimeout";
    Parameters[0].EntryContext  = &DeviceValues->PacketStartTimeout;
    Parameters[0].DefaultType       = REG_DWORD;
    Parameters[0].DefaultData       = &DeviceValues->PacketStartTimeout;
    Parameters[0].DefaultLength = sizeof(ULONG);
 
    Parameters[1].Flags             = RTL_QUERY_REGISTRY_DIRECT;
    Parameters[1].Name              = L"PacketLowHighTimeout";
    Parameters[1].EntryContext  = &DeviceValues->PacketLowHighTimeout;
    Parameters[1].DefaultType       = REG_DWORD;
    Parameters[1].DefaultData       = &DeviceValues->PacketLowHighTimeout;
    Parameters[1].DefaultLength = sizeof(ULONG);

    Parameters[2].Flags             = RTL_QUERY_REGISTRY_DIRECT;
    Parameters[2].Name              = L"PacketHighLowTimeout";
    Parameters[2].EntryContext  = &DeviceValues->PacketHighLowTimeout;
    Parameters[2].DefaultType       = REG_DWORD;
    Parameters[2].DefaultData       = &DeviceValues->PacketHighLowTimeout;
    Parameters[2].DefaultLength = sizeof(ULONG);

    Parameters[3].Flags             = RTL_QUERY_REGISTRY_DIRECT;
    Parameters[3].Name              = L"IdStartTimeout";
    Parameters[3].EntryContext  = &DeviceValues->IdStartTimeout;
    Parameters[3].DefaultType       = REG_DWORD;
    Parameters[3].DefaultData       = &DeviceValues->IdStartTimeout;
    Parameters[3].DefaultLength = sizeof(ULONG);

    Parameters[4].Flags             = RTL_QUERY_REGISTRY_DIRECT;
    Parameters[4].Name              = L"IdLowHighTimeout";
    Parameters[4].EntryContext  = &DeviceValues->IdLowHighTimeout;
    Parameters[4].DefaultType       = REG_DWORD;
    Parameters[4].DefaultData       = &DeviceValues->IdLowHighTimeout;
    Parameters[4].DefaultLength = sizeof(ULONG);

    Parameters[5].Flags             = RTL_QUERY_REGISTRY_DIRECT;
    Parameters[5].Name              = L"IdHighLowTimeout";
    Parameters[5].EntryContext  = &DeviceValues->IdHighLowTimeout;
    Parameters[5].DefaultType       = REG_DWORD;
    Parameters[5].DefaultData       = &DeviceValues->IdHighLowTimeout;
    Parameters[5].DefaultLength = sizeof(ULONG);

    Parameters[6].Flags             = RTL_QUERY_REGISTRY_DIRECT;
    Parameters[6].Name              = L"InterruptDelay";
    Parameters[6].EntryContext  = &DeviceValues->InterruptDelay;
    Parameters[6].DefaultType       = REG_DWORD;
    Parameters[6].DefaultData       = &DeviceValues->InterruptDelay;
    Parameters[6].DefaultLength = sizeof(ULONG);

    Parameters[7].Flags             = RTL_QUERY_REGISTRY_DIRECT;
    Parameters[7].Name              = L"MaxClockDutyCycle";
    Parameters[7].EntryContext  = &DeviceValues->MaxClockDutyCycle;
    Parameters[7].DefaultType       = REG_DWORD;
    Parameters[7].DefaultData       = &DeviceValues->MaxClockDutyCycle;
    Parameters[7].DefaultLength = sizeof(ULONG);

    Parameters[8].Flags             = RTL_QUERY_REGISTRY_DIRECT;
    Parameters[8].Name              = L"StatusStartTimeout";
    Parameters[8].EntryContext  = &DeviceValues->StatusStartTimeout;
    Parameters[8].DefaultType       = REG_DWORD;
    Parameters[8].DefaultData       = &DeviceValues->StatusStartTimeout;
    Parameters[8].DefaultLength = sizeof(ULONG);

    Parameters[9].Flags             = RTL_QUERY_REGISTRY_DIRECT;
    Parameters[9].Name              = L"StatusLowHighTimeout";
    Parameters[9].EntryContext  = &DeviceValues->StatusLowHighTimeout;
    Parameters[9].DefaultType       = REG_DWORD;
    Parameters[9].DefaultData       = &DeviceValues->StatusLowHighTimeout;
    Parameters[9].DefaultLength = sizeof(ULONG);

    Parameters[10].Flags                = RTL_QUERY_REGISTRY_DIRECT;
    Parameters[10].Name             = L"StatusHighLowTimeout";
    Parameters[10].EntryContext     = &DeviceValues->StatusHighLowTimeout;
    Parameters[10].DefaultType      = REG_DWORD;
    Parameters[10].DefaultData      = &DeviceValues->StatusHighLowTimeout;
    Parameters[10].DefaultLength    = sizeof(ULONG);

    Parameters[11].Flags                = RTL_QUERY_REGISTRY_DIRECT;
    Parameters[11].Name             = L"StatusGateTimeout";
    Parameters[11].EntryContext     = &DeviceValues->StatusGateTimeout;
    Parameters[11].DefaultType      = REG_DWORD;
    Parameters[11].DefaultData      = &DeviceValues->StatusGateTimeout;
    Parameters[11].DefaultLength    = sizeof(ULONG);

    ntStatus = RtlQueryRegistryValues (RTL_REGISTRY_ABSOLUTE, ParametersPath.Buffer, Parameters, NULL, NULL);

    if (!NT_SUCCESS(ntStatus))
        {
        MsGamePrint((DBG_INFORM,"%s: %s_ReadRegistry RtlQueryRegistryValues failed with 0x%x\n", MSGAME_NAME, MSGAME_NAME, ntStatus));
        //
        //  Create registry entries as needed
        //
        RtlWriteRegistryValue (RTL_REGISTRY_ABSOLUTE, ParametersPath.Buffer, L"PacketStartTimeout", REG_DWORD, &DeviceValues->PacketStartTimeout, sizeof (ULONG));
        RtlWriteRegistryValue (RTL_REGISTRY_ABSOLUTE, ParametersPath.Buffer, L"PacketLowHighTimeout", REG_DWORD, &DeviceValues->PacketLowHighTimeout, sizeof (ULONG));
        RtlWriteRegistryValue (RTL_REGISTRY_ABSOLUTE, ParametersPath.Buffer, L"PacketHighLowTimeout", REG_DWORD, &DeviceValues->PacketHighLowTimeout, sizeof (ULONG));
        RtlWriteRegistryValue (RTL_REGISTRY_ABSOLUTE, ParametersPath.Buffer, L"IdStartTimeout", REG_DWORD, &DeviceValues->IdStartTimeout, sizeof (ULONG));
        RtlWriteRegistryValue (RTL_REGISTRY_ABSOLUTE, ParametersPath.Buffer, L"IdLowHighTimeout", REG_DWORD, &DeviceValues->IdLowHighTimeout, sizeof (ULONG));
        RtlWriteRegistryValue (RTL_REGISTRY_ABSOLUTE, ParametersPath.Buffer, L"IdHighLowTimeout", REG_DWORD, &DeviceValues->IdHighLowTimeout, sizeof (ULONG));
        RtlWriteRegistryValue (RTL_REGISTRY_ABSOLUTE, ParametersPath.Buffer, L"InterruptDelay", REG_DWORD, &DeviceValues->InterruptDelay, sizeof (ULONG));
        RtlWriteRegistryValue (RTL_REGISTRY_ABSOLUTE, ParametersPath.Buffer, L"MaxClockDutyCycle", REG_DWORD, &DeviceValues->MaxClockDutyCycle, sizeof (ULONG));
        RtlWriteRegistryValue (RTL_REGISTRY_ABSOLUTE, ParametersPath.Buffer, L"StatusStartTimeout", REG_DWORD, &DeviceValues->StatusStartTimeout, sizeof (ULONG));
        RtlWriteRegistryValue (RTL_REGISTRY_ABSOLUTE, ParametersPath.Buffer, L"StatusLowHighTimeout", REG_DWORD, &DeviceValues->StatusLowHighTimeout, sizeof (ULONG));
        RtlWriteRegistryValue (RTL_REGISTRY_ABSOLUTE, ParametersPath.Buffer, L"StatusHighLowTimeout", REG_DWORD, &DeviceValues->StatusHighLowTimeout, sizeof (ULONG));
        RtlWriteRegistryValue (RTL_REGISTRY_ABSOLUTE, ParametersPath.Buffer, L"StatusGateTimeout", REG_DWORD, &DeviceValues->StatusGateTimeout, sizeof (ULONG));
        }

    //  -----------------
        ReadRegistryExit:
    //  -----------------

    if (ParametersPath.Buffer)
        ExFreePool(ParametersPath.Buffer);

    if (Parameters)
        ExFreePool(Parameters);

    #undef  PARAMS_PLUS_ONE
}

//---------------------------------------------------------------------------
// @func        Posts a transaction to hooking driver
//  @parm       PPACKETINFO | PacketInfo | Device packet info struct
// @rdesc   None
//  @comm       Public function
//---------------------------------------------------------------------------

VOID  MSGAME_PostTransaction (PPACKETINFO PacketInfo)
{
    //
    //  Not Implemented
    //
}