//---------------------------------------------------------------------------
//
//  Module:   util.c
//
//  Description:
//
//
//@@BEGIN_MSINTERNAL
//  Development Team:
//     Mike McLaughlin
//
//  History:   Date       Author      Comment
//
//  To Do:     Date       Author      Comment
//
//@@END_MSINTERNAL
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1996-1999 Microsoft Corporation.  All Rights Reserved.
//
//---------------------------------------------------------------------------

#include "common.h"

#define SMALL_BLOCK_SIZE        32

extern KSDATARANGE DataRangeWildCard;
extern KSDATARANGE VirtualPinDataRange;

//===========================================================================
//===========================================================================

#pragma LOCKED_DATA

#ifdef DEBUG
//#define MEMORY_LIST
ULONG ulDebugFlags = 0;
ULONG ulDebugNumber = MAXULONG;
#ifdef UNDER_NT
int SYSAUDIOTraceLevel = 5;
#else
int SYSAUDIOTraceLevel = 50;
#endif
ULONG cAllocMem = 0;
ULONG cAllocMemSmall = 0;
ULONG cAllocMem64 = 0;
ULONG cAllocMem128 = 0;
ULONG cbMemoryUsage = 0;
#endif

//===========================================================================
//===========================================================================

LIST_ENTRY glehQueueWorkList;
KSPIN_LOCK gSpinLockQueueWorkList;
WORK_QUEUE_ITEM gWorkItem;
LONG gcQueueWorkList = 0;
KMUTEX gMutex;
PKSWORKER gWorkerObject = NULL;
PKSWORKER gCriticalWorkerObject = NULL;
#ifdef USE_ZONES
ZONE_HEADER gZone;
#endif
#ifdef MEMORY_LIST
LIST_ENTRY gleMemoryHead;
KSPIN_LOCK gSpinLockMemoryHead;
#endif

#pragma PAGEABLE_DATA

//===========================================================================
//===========================================================================

#pragma INIT_CODE
#pragma INIT_DATA

NTSTATUS
InitializeUtil()
{
    NTSTATUS Status = STATUS_SUCCESS;
#ifdef USE_ZONES
    PVOID pInitial = NULL;

    pInitial = ExAllocatePoolWithTag(PagedPool, 4096, 0x41535953);
    if(pInitial == NULL) {
        Trap();
        Status = STATUS_INSUFFICIENT_RESOURCES;
        goto exit;
    }
    Status = ExInitializeZone(&gZone, SMALL_BLOCK_SIZE, pInitial, 4096);
    if(!NT_SUCCESS(Status)) {
        Trap();
        goto exit;
    }
#endif

#ifdef MEMORY_LIST
    InitializeListHead(&gleMemoryHead);
    KeInitializeSpinLock(&gSpinLockMemoryHead);
#endif

#ifdef DEBUG
#ifndef UNDER_NT
#ifdef _X86_
    InitializeDebug();
#endif
#endif
#endif
    KeInitializeSpinLock(&gSpinLockQueueWorkList);
    InitializeListHead(&glehQueueWorkList);
    ExInitializeWorkItem(
      &gWorkItem,
      CQueueWorkListData::AsyncWorker,
      NULL);
    //
    // Note... if we fail during preparation, the DriverUnload() routine
    // calls the DeinitializeUtil() function which handles the clean up.
    //
    Status = KsRegisterWorker(DelayedWorkQueue, &gWorkerObject);
    if(!NT_SUCCESS(Status)) {
        Trap();
        goto exit;
    }
    Status = KsRegisterWorker(CriticalWorkQueue, &gCriticalWorkerObject);
    if(!NT_SUCCESS(Status)) {
        Trap();
        goto exit;
    }
exit:
#ifdef USE_ZONES
    if(!NT_SUCCESS(Status)) {
	if(pInitial != NULL) {
	    //
	    // Make sure UninitializeMemory doesn't also try to free this.
	    //
	    gZone.SegmentList.Next = NULL;
	    //
	    // Free initial zone page if failure
	    //
	    ExFreePool(pInitial);
	}
    }
#endif
    return(Status);
}

#pragma PAGEABLE_CODE
#pragma PAGEABLE_DATA

VOID
UninitializeUtil()
{
    if(gWorkerObject != NULL) {
        KsUnregisterWorker(gWorkerObject);
        gWorkerObject = NULL;
    }
    if(gCriticalWorkerObject != NULL) {
        KsUnregisterWorker(gCriticalWorkerObject);
        gCriticalWorkerObject = NULL;
    }
}

VOID
UninitializeMemory()
{
#ifdef USE_ZONES
    PSINGLE_LIST_ENTRY psle, psleNext;

    psle = gZone.SegmentList.Next;
    while(psle != NULL) {
        psleNext = psle->Next;
        ExFreePool(psle);
        psle = psleNext;
    }
#endif
    ASSERT(cbMemoryUsage == 0);
}

NTSTATUS
DispatchInvalidDeviceRequest(
   IN PDEVICE_OBJECT pdo,
   IN PIRP           pIrp
)
{
    return KsDispatchInvalidDeviceRequest(pdo,pIrp);
}

BOOLEAN
DispatchFastIoDeviceControlFailure(
   IN PFILE_OBJECT FileObject,
   IN BOOLEAN Wait,
   IN PVOID InputBuffer OPTIONAL,
   IN ULONG InputBufferLength,
   OUT PVOID OutputBuffer OPTIONAL,
   IN ULONG OutputBufferLength,
   IN ULONG IoControlCode,
   OUT PIO_STATUS_BLOCK IoStatus,
   IN PDEVICE_OBJECT DeviceObject
)
{
    return KsDispatchFastIoDeviceControlFailure(
      FileObject,
      Wait,
      InputBuffer,
      InputBufferLength,
      OutputBuffer,
      OutputBufferLength,
      IoControlCode,
      IoStatus,
      DeviceObject);
}

BOOLEAN
DispatchFastReadFailure(
   IN PFILE_OBJECT FileObject,
   IN PLARGE_INTEGER FileOffset,
   IN ULONG Length,
   IN BOOLEAN Wait,
   IN ULONG LockKey,
   OUT PVOID Buffer,
   OUT PIO_STATUS_BLOCK IoStatus,
   IN PDEVICE_OBJECT DeviceObject
)
{
    return KsDispatchFastReadFailure(
      FileObject,
      FileOffset,
      Length,
      Wait,
      LockKey,
      Buffer,
      IoStatus,
      DeviceObject);
}

BOOL
CompareDataRange(
    PKSDATARANGE pDataRange1,
    PKSDATARANGE pDataRange2
)
{
    KSDATARANGE_AUDIO DataRangeAudioIntersection;

    if(CompareDataRangeGuids(pDataRange1, pDataRange2)) {

        //
        // See if there is a valid intersection
        //
	if(DataIntersectionAudio(
          (PKSDATARANGE_AUDIO)pDataRange1,
          (PKSDATARANGE_AUDIO)pDataRange2,
          &DataRangeAudioIntersection)) {
	    return(TRUE);
        }
        if(pDataRange1 == &DataRangeWildCard ||
           pDataRange2 == &DataRangeWildCard ||
           pDataRange1 == &VirtualPinDataRange ||
           pDataRange2 == &VirtualPinDataRange) {
            return(TRUE);
        }
        return(FALSE);
    }
    return(FALSE);
}

BOOL DataIntersectionRange(
    PKSDATARANGE pDataRange1,
    PKSDATARANGE pDataRange2,
    PKSDATARANGE pDataRangeIntersection
)
{
    // Pick up pDataRange1 values by default.
    *pDataRangeIntersection = *pDataRange1;

    if(IsEqualGUID(&pDataRange1->MajorFormat, &pDataRange2->MajorFormat) ||
       IsEqualGUID(&pDataRange1->MajorFormat, &KSDATAFORMAT_TYPE_WILDCARD)) {
        pDataRangeIntersection->MajorFormat = pDataRange2->MajorFormat;
    }
    else if(!IsEqualGUID(
      &pDataRange2->MajorFormat,
      &KSDATAFORMAT_TYPE_WILDCARD)) {
        return FALSE;
    }
    if(IsEqualGUID(&pDataRange1->SubFormat, &pDataRange2->SubFormat) ||
       IsEqualGUID(&pDataRange1->SubFormat, &KSDATAFORMAT_SUBTYPE_WILDCARD)) {
        pDataRangeIntersection->SubFormat = pDataRange2->SubFormat;
    }
    else if(!IsEqualGUID(
      &pDataRange2->SubFormat,
      &KSDATAFORMAT_SUBTYPE_WILDCARD)) {
        return FALSE;
    }
    if(IsEqualGUID(&pDataRange1->Specifier, &pDataRange2->Specifier) ||
       IsEqualGUID(&pDataRange1->Specifier, &KSDATAFORMAT_SPECIFIER_WILDCARD)) {
        pDataRangeIntersection->Specifier = pDataRange2->Specifier;
    }
    else if(!IsEqualGUID(
      &pDataRange2->Specifier,
      &KSDATAFORMAT_SPECIFIER_WILDCARD)) {
        return FALSE;
    }
    pDataRangeIntersection->Reserved = 0; // Must be zero
    return(TRUE);
}

BOOL
DataIntersectionAudio(
    PKSDATARANGE_AUDIO pDataRangeAudio1,
    PKSDATARANGE_AUDIO pDataRangeAudio2,
    PKSDATARANGE_AUDIO pDataRangeAudioIntersection
)
{
    if((IsEqualGUID(
      &pDataRangeAudio1->DataRange.Specifier,
      &KSDATAFORMAT_SPECIFIER_WAVEFORMATEX) ||
       IsEqualGUID(
      &pDataRangeAudio1->DataRange.Specifier,
      &KSDATAFORMAT_SPECIFIER_DSOUND)) &&
       IsEqualGUID(
      &pDataRangeAudio2->DataRange.Specifier,
      &KSDATAFORMAT_SPECIFIER_WILDCARD)) {

        pDataRangeAudioIntersection->MaximumChannels =
          pDataRangeAudio1->MaximumChannels;
        pDataRangeAudioIntersection->MaximumSampleFrequency =
          pDataRangeAudio1->MaximumSampleFrequency;
        pDataRangeAudioIntersection->MinimumSampleFrequency =
          pDataRangeAudio1->MinimumSampleFrequency;
        pDataRangeAudioIntersection->MaximumBitsPerSample =
          pDataRangeAudio1->MaximumBitsPerSample;
        pDataRangeAudioIntersection->MinimumBitsPerSample =
          pDataRangeAudio1->MinimumBitsPerSample;
        return(TRUE);
    }
    if(IsEqualGUID(
      &pDataRangeAudio1->DataRange.Specifier,
      &KSDATAFORMAT_SPECIFIER_WILDCARD) &&
      (IsEqualGUID(
      &pDataRangeAudio2->DataRange.Specifier,
      &KSDATAFORMAT_SPECIFIER_WAVEFORMATEX) ||
       IsEqualGUID(
      &pDataRangeAudio2->DataRange.Specifier,
      &KSDATAFORMAT_SPECIFIER_DSOUND))) {

        pDataRangeAudioIntersection->MaximumChannels =
          pDataRangeAudio2->MaximumChannels;
        pDataRangeAudioIntersection->MaximumSampleFrequency =
          pDataRangeAudio2->MaximumSampleFrequency;
        pDataRangeAudioIntersection->MinimumSampleFrequency =
          pDataRangeAudio2->MinimumSampleFrequency;
        pDataRangeAudioIntersection->MaximumBitsPerSample =
          pDataRangeAudio2->MaximumBitsPerSample;
        pDataRangeAudioIntersection->MinimumBitsPerSample =
          pDataRangeAudio2->MinimumBitsPerSample;
        return(TRUE);
    }
    if((IsEqualGUID(
      &pDataRangeAudio1->DataRange.Specifier,
      &KSDATAFORMAT_SPECIFIER_WAVEFORMATEX) ||
       IsEqualGUID(
      &pDataRangeAudio1->DataRange.Specifier,
      &KSDATAFORMAT_SPECIFIER_DSOUND)) &&
      (IsEqualGUID(
      &pDataRangeAudio2->DataRange.Specifier,
      &KSDATAFORMAT_SPECIFIER_WAVEFORMATEX) ||
       IsEqualGUID(
      &pDataRangeAudio2->DataRange.Specifier,
      &KSDATAFORMAT_SPECIFIER_DSOUND))) {

	if(pDataRangeAudio1->MaximumChannels <
	   pDataRangeAudio2->MaximumChannels) {
	    pDataRangeAudioIntersection->MaximumChannels =
	      pDataRangeAudio1->MaximumChannels;
	}
	else {
	    pDataRangeAudioIntersection->MaximumChannels =
	      pDataRangeAudio2->MaximumChannels;
	}

	if(pDataRangeAudio1->MaximumSampleFrequency <
	   pDataRangeAudio2->MaximumSampleFrequency) {
	    pDataRangeAudioIntersection->MaximumSampleFrequency =
	      pDataRangeAudio1->MaximumSampleFrequency;
	}
	else {
	    pDataRangeAudioIntersection->MaximumSampleFrequency =
	      pDataRangeAudio2->MaximumSampleFrequency;
	}
	if(pDataRangeAudio1->MinimumSampleFrequency >
	   pDataRangeAudio2->MinimumSampleFrequency) {
	    pDataRangeAudioIntersection->MinimumSampleFrequency =
	      pDataRangeAudio1->MinimumSampleFrequency;
	}
	else {
	    pDataRangeAudioIntersection->MinimumSampleFrequency =
	      pDataRangeAudio2->MinimumSampleFrequency;
	}
	if(pDataRangeAudioIntersection->MaximumSampleFrequency <
	   pDataRangeAudioIntersection->MinimumSampleFrequency ) {
	    DPF2(110, "DataIntersectionAudio: SR %08x %08x",
	      pDataRangeAudio1,
	      pDataRangeAudio2);
	    return(FALSE);
	}

	if(pDataRangeAudio1->MaximumBitsPerSample <
	   pDataRangeAudio2->MaximumBitsPerSample) {
	    pDataRangeAudioIntersection->MaximumBitsPerSample =
	      pDataRangeAudio1->MaximumBitsPerSample;
	}
	else {
	    pDataRangeAudioIntersection->MaximumBitsPerSample =
	      pDataRangeAudio2->MaximumBitsPerSample;
	}
	if(pDataRangeAudio1->MinimumBitsPerSample >
	   pDataRangeAudio2->MinimumBitsPerSample) {
	    pDataRangeAudioIntersection->MinimumBitsPerSample =
	      pDataRangeAudio1->MinimumBitsPerSample;
	}
	else {
	    pDataRangeAudioIntersection->MinimumBitsPerSample =
	      pDataRangeAudio2->MinimumBitsPerSample;
	}
	if(pDataRangeAudioIntersection->MaximumBitsPerSample <
	   pDataRangeAudioIntersection->MinimumBitsPerSample ) {
	    DPF2(110, "DataIntersectionAudio: BPS %08x %08x",
	      pDataRangeAudio1,
	      pDataRangeAudio2);
	    return(FALSE);
	}
	return(TRUE);
    }
    return(FALSE);
}

BOOL
CompareDataRangeExact(
    PKSDATARANGE pDataRange1,
    PKSDATARANGE pDataRange2
)
{
    if(pDataRange1 == NULL || pDataRange2 == NULL) {
        Trap();
        return(FALSE);
    }
    ASSERT(pDataRange1->Reserved == pDataRange2->Reserved);
    if(pDataRange1->FormatSize == pDataRange2->FormatSize) {
        return(!memcmp(pDataRange1, pDataRange2, pDataRange1->FormatSize));
    }
    return(FALSE);
}

BOOL
CompareDataRangeGuids(
    PKSDATARANGE pDataRange1,
    PKSDATARANGE pDataRange2
)
{
    if(pDataRange1 == NULL || pDataRange2 == NULL) {
        Trap();
        return(FALSE);
    }
    if((IsEqualGUID(&pDataRange1->MajorFormat, &pDataRange2->MajorFormat) ||
      IsEqualGUID(&pDataRange1->MajorFormat, &KSDATAFORMAT_TYPE_WILDCARD) ||
      IsEqualGUID(&pDataRange2->MajorFormat, &KSDATAFORMAT_TYPE_WILDCARD)) &&

     (IsEqualGUID(&pDataRange1->SubFormat, &pDataRange2->SubFormat) ||
      IsEqualGUID(&pDataRange1->SubFormat, &KSDATAFORMAT_SUBTYPE_WILDCARD) ||
      IsEqualGUID(&pDataRange2->SubFormat, &KSDATAFORMAT_SUBTYPE_WILDCARD)) &&

     (IsEqualGUID(&pDataRange1->Specifier, &pDataRange2->Specifier) ||
      IsEqualGUID(&pDataRange1->Specifier, &KSDATAFORMAT_SPECIFIER_WILDCARD) ||
      IsEqualGUID(&pDataRange2->Specifier, &KSDATAFORMAT_SPECIFIER_WILDCARD))) {
        return(TRUE);
    }
    return(FALSE);
}

BOOL
CompareIdentifier(
    PKSIDENTIFIER pIdentifier1,
    PKSIDENTIFIER pIdentifier2
)
{
    if(pIdentifier1 == NULL || pIdentifier2 == NULL) {
        Trap();
        return(FALSE);
    }
    if(pIdentifier1 == INTERNAL_WILDCARD ||
       pIdentifier2 == INTERNAL_WILDCARD) {
        return(TRUE);
    }
    if(IsEqualGUID(&pIdentifier1->Set, &pIdentifier2->Set) &&
      (pIdentifier1->Id == pIdentifier2->Id)) {
        return(TRUE);
    }
    return(FALSE);
}

PWAVEFORMATEXTENSIBLE 
GetWaveFormatExtensible(
    PKSPIN_CONNECT pPinConnect
)
{
    ASSERT(pPinConnect);

    PWAVEFORMATEXTENSIBLE pWaveFormatExt = NULL;
    PKSDATAFORMAT pDataFormat = (PKSDATAFORMAT) (pPinConnect + 1);

    if(IsEqualGUID(&pDataFormat->Specifier, &KSDATAFORMAT_SPECIFIER_WAVEFORMATEX)) {
        pWaveFormatExt = (PWAVEFORMATEXTENSIBLE)
          &PKSDATAFORMAT_WAVEFORMATEX(pDataFormat)->WaveFormatEx;
    }
    else if(IsEqualGUID(&pDataFormat->Specifier, &KSDATAFORMAT_SPECIFIER_DSOUND)) {
        pWaveFormatExt = (PWAVEFORMATEXTENSIBLE)
          &PKSDATAFORMAT_DSOUND(pDataFormat)->BufferDesc.WaveFormatEx;
    }
    else {
        DPF(5, "Unknown format specifier");
    }

    return pWaveFormatExt;
} // GetWaveFormatExtensible

void
ModifyPinConnect(
    PKSPIN_CONNECT pPinConnect,
    WORD  nChannels
)
{
    ASSERT(pPinConnect);

    PKSDATAFORMAT           pDataFormat = (PKSDATAFORMAT) (pPinConnect + 1);
    PWAVEFORMATEXTENSIBLE   pWaveFormatExt = NULL;

    pWaveFormatExt = GetWaveFormatExtensible(pPinConnect);
    if (NULL != pWaveFormatExt) {
        if (IsEqualGUID(&pDataFormat->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM)) {
            
            pWaveFormatExt->Format.nChannels = nChannels;
            pWaveFormatExt->Format.nBlockAlign = 
                (pWaveFormatExt->Format.wBitsPerSample / 8) * 
                pWaveFormatExt->Format.nChannels;
            pWaveFormatExt->Format.nAvgBytesPerSec =
                pWaveFormatExt->Format.nSamplesPerSec *
                pWaveFormatExt->Format.nBlockAlign;

            if (WAVE_FORMAT_EXTENSIBLE == pWaveFormatExt->Format.wFormatTag) {
                if (1 == nChannels) {
                    pWaveFormatExt->dwChannelMask = SPEAKER_FRONT_CENTER;
                }
                else if (2 == nChannels) {
                    pWaveFormatExt->dwChannelMask = 
                        SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
                }
            }
        }
        else {
            DPF(5, "ModifyPinConnect : Not touching NON-PCM formats.");            
        }
    }
} // ModifyPinConnect

NTSTATUS
OpenDevice(
    PWSTR pwstrDevice,
    PHANDLE pHandle
)
{
    IO_STATUS_BLOCK IoStatusBlock;
    UNICODE_STRING UnicodeDeviceString;
    OBJECT_ATTRIBUTES ObjectAttributes;

    RtlInitUnicodeString(&UnicodeDeviceString, pwstrDevice);

    InitializeObjectAttributes(
      &ObjectAttributes,
      &UnicodeDeviceString,
      OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
      NULL,
      NULL);

    return(ZwCreateFile(pHandle,
      GENERIC_READ | GENERIC_WRITE,
      &ObjectAttributes,
      &IoStatusBlock,
      NULL,
      FILE_ATTRIBUTE_NORMAL,
      0,
      FILE_OPEN,
      0,
      NULL,
      0));
}

NTSTATUS
GetPinProperty(
    PFILE_OBJECT pFileObject,
    ULONG PropertyId,
    ULONG PinId,
    ULONG cbProperty,
    PVOID pProperty
)
{
    ULONG BytesReturned;
    KSP_PIN Pin;
    NTSTATUS Status;

    Pin.Property.Set = KSPROPSETID_Pin;
    Pin.Property.Id = PropertyId;
    Pin.Property.Flags = KSPROPERTY_TYPE_GET;
    Pin.PinId = PinId;
    Pin.Reserved = 0;

    AssertFileObject(pFileObject);
    Status = KsSynchronousIoControlDevice(
      pFileObject,
      KernelMode,
      IOCTL_KS_PROPERTY,
      &Pin,
      sizeof(Pin),
      pProperty,
      cbProperty,
      &BytesReturned);

    if(!NT_SUCCESS(Status)) {
        DPF2(10,
	  "GetPinProperty: id %d p %d KsSynchronousIoControlDevice FAILED",
	  PropertyId,
	  PinId);
        goto exit;
    }
    ASSERT(BytesReturned == cbProperty);
exit:
    return(Status);
}

NTSTATUS
PinConnectionProperty(
    PFILE_OBJECT pFileObject,
    ULONG ulPropertyId,
    ULONG ulFlags,
    ULONG cbProperty,
    PVOID pProperty
)
{
    KSIDENTIFIER Property;
    ULONG BytesReturned;
    NTSTATUS Status;

    Property.Set = KSPROPSETID_Connection;
    Property.Id = ulPropertyId;
    Property.Flags = ulFlags;

    AssertFileObject(pFileObject);
    Status = KsSynchronousIoControlDevice(
      pFileObject,
      KernelMode,
      IOCTL_KS_PROPERTY,
      &Property,
      sizeof(Property),
      pProperty,
      cbProperty,
      &BytesReturned);

    if(!NT_SUCCESS(Status)) {
        DPF(10, "SetPinConnectionProperty: KsSynchronousIoControlDevice Failed");
        goto exit;
    }
exit:
    return(Status);
}

NTSTATUS
GetPinPropertyEx(
    PFILE_OBJECT pFileObject,
    ULONG PropertyId,
    ULONG PinId,
    PVOID *ppProperty
)
{
    ULONG BytesReturned;
    NTSTATUS Status;
    KSP_PIN Pin;

    Pin.Property.Set = KSPROPSETID_Pin;
    Pin.Property.Id = PropertyId;
    Pin.Property.Flags = KSPROPERTY_TYPE_GET;
    Pin.PinId = PinId;
    Pin.Reserved = 0;

    AssertFileObject(pFileObject);
    Status = KsSynchronousIoControlDevice(
      pFileObject,
      KernelMode,
      IOCTL_KS_PROPERTY,
      &Pin,
      sizeof(KSP_PIN),
      NULL,
      0,
      &BytesReturned);

    ASSERT(!NT_SUCCESS(Status));
    if(Status != STATUS_BUFFER_OVERFLOW) {
        goto exit;
    }
    if(BytesReturned == 0) {
        *ppProperty = NULL;
        Status = STATUS_SUCCESS;
        goto exit;
    }

    *ppProperty = new BYTE[BytesReturned];
    if(*ppProperty == NULL) {
        Status = STATUS_INSUFFICIENT_RESOURCES;
        goto exit;
    }

    AssertFileObject(pFileObject);
    Status = KsSynchronousIoControlDevice(
      pFileObject,
      KernelMode,
      IOCTL_KS_PROPERTY,
      &Pin,
      sizeof(KSP_PIN),
      *ppProperty,
      BytesReturned,
      &BytesReturned);

    if(!NT_SUCCESS(Status)) {
        Trap();
        delete *ppProperty;
        *ppProperty = NULL;
        goto exit;
    }
exit:
    if(Status == STATUS_PROPSET_NOT_FOUND ||
       Status == STATUS_NOT_FOUND) {
        Status = STATUS_SUCCESS;
        *ppProperty = NULL;
    }
    return(Status);
}

NTSTATUS
GetPinProperty2(
    PFILE_OBJECT pFileObject,
    ULONG ulPropertyId,
    ULONG ulPinId,
    ULONG cbInput,
    PVOID pInputData,
    PVOID *ppPropertyOutput
)
{
    ULONG cbPropertyInput = sizeof(KSP_PIN);
    ULONG BytesReturned;
    NTSTATUS Status;
    PKSP_PIN pPin;

    cbPropertyInput += cbInput;
    pPin = (PKSP_PIN)new BYTE[cbPropertyInput];
    if(pPin == NULL) {
        Status = STATUS_INSUFFICIENT_RESOURCES;
        goto exit;
    }
    pPin->Property.Set = KSPROPSETID_Pin;
    pPin->Property.Id = ulPropertyId;
    pPin->Property.Flags = KSPROPERTY_TYPE_GET;
    pPin->PinId = ulPinId;
    pPin->Reserved = 0;

    if(pInputData != NULL) {
        memcpy(pPin + 1, pInputData, cbInput);
    }

    AssertFileObject(pFileObject);
    Status = KsSynchronousIoControlDevice(
      pFileObject,
      KernelMode,
      IOCTL_KS_PROPERTY,
      pPin,
      cbPropertyInput,
      NULL,
      0,
      &BytesReturned);

    ASSERT(!NT_SUCCESS(Status));
    if(Status != STATUS_BUFFER_OVERFLOW) {
        DPF(10, "GetPinProperty2: KsSynchronousIoControlDevice 1 Failed");
        goto exit;
    }

    if(BytesReturned == 0) {
        *ppPropertyOutput = NULL;
        Status = STATUS_SUCCESS;
        goto exit;
    }
    *ppPropertyOutput = new BYTE[BytesReturned];
    if(*ppPropertyOutput == NULL) {
        Status = STATUS_INSUFFICIENT_RESOURCES;
        goto exit;
    }
    AssertFileObject(pFileObject);
    Status = KsSynchronousIoControlDevice(
      pFileObject,
      KernelMode,
      IOCTL_KS_PROPERTY,
      pPin,
      cbPropertyInput,
      *ppPropertyOutput,
      BytesReturned,
      &BytesReturned);

    if(!NT_SUCCESS(Status)) {
        DPF(10, "GetPinProperty2: KsSynchronousIoControlDevice 2 Failed");
        delete *ppPropertyOutput;
        goto exit;
    }
exit:
    delete [] pPin;
    if(!NT_SUCCESS(Status)) {
        *ppPropertyOutput = NULL;
        if(Status == STATUS_PROPSET_NOT_FOUND ||
           Status == STATUS_NOT_FOUND) {
            Status = STATUS_SUCCESS;
        }
    }
    return(Status);
}

NTSTATUS
GetProperty(
    PFILE_OBJECT pFileObject,
    CONST GUID *pguidPropertySet,
    ULONG ulPropertyId,
    ULONG cbInput,
    PVOID pInputData,
    PVOID *ppPropertyOutput
)
{
    ULONG BytesReturned;
    ULONG cbPropertyInput = sizeof(KSPROPERTY);
    PKSPROPERTY pPropertyInput;
    NTSTATUS Status;

    cbPropertyInput += cbInput;

    pPropertyInput = (PKSPROPERTY)new BYTE[cbPropertyInput];
    if(pPropertyInput == NULL) {
	Status = STATUS_INSUFFICIENT_RESOURCES;
	goto exit;
    }
    pPropertyInput->Set = *pguidPropertySet;
    pPropertyInput->Id = ulPropertyId;
    pPropertyInput->Flags = KSPROPERTY_TYPE_GET;

    if(pInputData != NULL) {
        Trap();
        memcpy(pPropertyInput + 1, pInputData, cbInput);
    }

    AssertFileObject(pFileObject);
    Status = KsSynchronousIoControlDevice(
      pFileObject,
      KernelMode,
      IOCTL_KS_PROPERTY,
      pPropertyInput,
      cbPropertyInput,
      NULL,
      0,
      &BytesReturned);

    ASSERT(!NT_SUCCESS(Status));
    if(Status != STATUS_BUFFER_OVERFLOW) {
        DPF(10, "GetProperty: KsSynchronousIoControlDevice 1 Failed");
        goto exit;
    }

    if(BytesReturned == 0) {
        *ppPropertyOutput = NULL;
        Status = STATUS_SUCCESS;
        goto exit;
    }
    *ppPropertyOutput = new BYTE[BytesReturned];
    if(*ppPropertyOutput == NULL) {
        Status = STATUS_INSUFFICIENT_RESOURCES;
        goto exit;
    }
    AssertFileObject(pFileObject);
    Status = KsSynchronousIoControlDevice(
      pFileObject,
      KernelMode,
      IOCTL_KS_PROPERTY,
      pPropertyInput,
      cbPropertyInput,
      *ppPropertyOutput,
      BytesReturned,
      &BytesReturned);

    if(!NT_SUCCESS(Status)) {
        DPF(10, "GetProperty: KsSynchronousIoControlDevice 2 Failed");
        delete *ppPropertyOutput;
        goto exit;
    }
exit:
    delete [] pPropertyInput;
    if(!NT_SUCCESS(Status)) {
        *ppPropertyOutput = NULL;
        if(Status == STATUS_PROPSET_NOT_FOUND ||
           Status == STATUS_NOT_FOUND) {
            Status = STATUS_SUCCESS;
        }
    }
    return(Status);
}

CQueueWorkListData::CQueueWorkListData(
    NTSTATUS (*Function)(PVOID Reference1, PVOID Reference2),
    PVOID Reference1,
    PVOID Reference2
)
{
    this->Function = Function;
    this->Reference1 = Reference1;
    this->Reference2 = Reference2;
}

NTSTATUS
CQueueWorkListData::QueueAsyncList(
)
{
    NTSTATUS ntStatus = STATUS_SUCCESS;

    ExInterlockedInsertTailList(
      &glehQueueWorkList,
      &leNext,
      &gSpinLockQueueWorkList);

    // Schedule the workitem, if it is not already running.
    // 
    if(InterlockedIncrement(&gcQueueWorkList) == 1) {
        ntStatus = KsQueueWorkItem(gWorkerObject, &gWorkItem);
    }

    return ntStatus;
}

VOID
CQueueWorkListData::AsyncWorker(
    IN OUT PVOID pReference
)
{
    PQUEUE_WORK_LIST_DATA pQueueWorkListData;
    PLIST_ENTRY ple;

    ::GrabMutex();

    while(
      (ple = ExInterlockedRemoveHeadList(
      &glehQueueWorkList,
      &gSpinLockQueueWorkList)) != NULL) {
        pQueueWorkListData =
          CONTAINING_RECORD(ple, QUEUE_WORK_LIST_DATA, leNext);

        Assert(pQueueWorkListData);
        (*pQueueWorkListData->Function)
          (pQueueWorkListData->Reference1,
           pQueueWorkListData->Reference2);

        delete pQueueWorkListData;

        if(InterlockedDecrement(&gcQueueWorkList) == 0) {
            break;
        }
    }
    ::ReleaseMutex();
}

NTSTATUS
QueueWorkList(
    NTSTATUS (*Function)(PVOID Reference1, PVOID Reference2),
    PVOID Reference1,
    PVOID Reference2
)
{
    NTSTATUS Status = STATUS_SUCCESS;

    PQUEUE_WORK_LIST_DATA pQueueWorkListData = NULL;

    pQueueWorkListData = new QUEUE_WORK_LIST_DATA(
       Function,
       Reference1,
       Reference2);

    if(pQueueWorkListData == NULL) {
        Status = STATUS_INSUFFICIENT_RESOURCES;
        goto exit;
    }

    Status = pQueueWorkListData->QueueAsyncList();
    
exit:
    return(Status);
}

VOID 
GetDefaultOrder(
    ULONG fulType,
    PULONG pulOrder
)
{
    if(fulType != 0) {
	if(*pulOrder == ORDER_NONE) {
	    if(fulType & FILTER_TYPE_ENDPOINT) {
		*pulOrder = ORDER_ENDPOINT;
		return;
	    }
	    if(fulType & FILTER_TYPE_VIRTUAL) {
		*pulOrder = ORDER_VIRTUAL;
		return;
	    }
	    if(fulType & FILTER_TYPE_GFX) {
		*pulOrder = ORDER_GFX;
		return;
	    }
	    if(fulType & FILTER_TYPE_INTERFACE_TRANSFORM) {
		*pulOrder = ORDER_INTERFACE_TRANSFORM;
		return;
	    }
	    if(fulType & FILTER_TYPE_AEC) {
		*pulOrder = ORDER_AEC;
		return;
	    }
	    if(fulType & FILTER_TYPE_MIC_ARRAY_PROCESSOR) {
		*pulOrder = ORDER_MIC_ARRAY_PROCESSOR;
		return;
	    }
	    if(fulType & FILTER_TYPE_SPLITTER) {
		*pulOrder = ORDER_SPLITTER;
		return;
	    }
	    if(fulType & FILTER_TYPE_MIXER) {
		*pulOrder = ORDER_MIXER;
		return;
	    }
	    if(fulType & FILTER_TYPE_SYNTHESIZER) {
		*pulOrder = ORDER_SYNTHESIZER;
		return;
	    }
	    if(fulType & FILTER_TYPE_DRM_DESCRAMBLE) {
		*pulOrder = ORDER_DRM_DESCRAMBLE;
		return;
	    }
	    if(fulType & FILTER_TYPE_DATA_TRANSFORM) {
		*pulOrder = ORDER_DATA_TRANSFORM;
		return;
	    }
	}
    }
}

//===========================================================================
// ISSUE-2001/03/06-alpers
// This is a temporary solution for GFX glitching problem.
// In BlackComb time-frame after the right fix is implemented, we should delete
// this definition and references to it.
// 
#define STATIC_KSPROPSETID_Frame\
    0xA60D8368L, 0x5324, 0x4893, 0xB0, 0x20, 0xC4, 0x31, 0xA5, 0x0B, 0xCB, 0xE3
DEFINE_GUIDSTRUCT("A60D8368-5324-4893-B020-C431A50BCBE3", KSPROPSETID_Frame);
#define KSPROPSETID_Frame DEFINE_GUIDNAMED(KSPROPSETID_Frame)

typedef enum {
    KSPROPERTY_FRAME_HOLDING
} KSPROPERTY_FRAME;
//===========================================================================

NTSTATUS
SetKsFrameHolding(
    PFILE_OBJECT pFileObject
)
{
    KSPROPERTY      Property;
    NTSTATUS        ntStatus;
    ULONG           ulBytesReturned;
    BOOL            fFrameEnable = TRUE;

    ASSERT(pFileObject);

    //
    // Form the IOCTL packet & send it down
    //
    Property.Set    = KSPROPSETID_Frame;
    Property.Id     = KSPROPERTY_FRAME_HOLDING;
    Property.Flags  = KSPROPERTY_TYPE_SET;

    DPF(60,"Sending KSPROPERTY_FRAME_HOLDING");

    //
    // We actually throw away the status we got back from the device.
    //
    ntStatus = KsSynchronousIoControlDevice(pFileObject,
                                            KernelMode,
                                            IOCTL_KS_PROPERTY,
                                            &Property,
                                            sizeof(Property),
                                            &fFrameEnable,
                                            sizeof(fFrameEnable),
                                            &ulBytesReturned);
    DPF1(60,"KSPROPERTY_FRAME_HOLDING %s", 
        (NT_SUCCESS(ntStatus)) ? "Succeeded" : "Failed");

    return ntStatus;
} // SetKsFrameHolding

//---------------------------------------------------------------------------

#pragma LOCKED_CODE

//
//  Zero initializes the block.
//

void * __cdecl operator new( size_t size )
{
    PVOID p;
    ASSERT(size != 0);
    ASSERT(size < 0x10000);

#if defined(USE_ZONES) || defined(DEBUG)
    size += sizeof(ULONGLONG);
#endif

#ifdef MEMORY_LIST
    size += sizeof(LIST_ENTRY);
#endif

#ifdef USE_ZONES
    if(size <= SMALL_BLOCK_SIZE) {
        if(ExIsFullZone(&gZone)) {
            p = ExAllocatePoolWithTag(PagedPool, 4096, 0x41535953);     // SYSA
            if(p != NULL) {
                if(!NT_SUCCESS(ExExtendZone(&gZone, p, 4096))) {
                    Trap();
		    ExFreePool(p);
                    DPF(5, "ExExtendZone FAILED");
		    return(NULL);
                }
            }
        }
        p = ExAllocateFromZone(&gZone);
    }
    else {
        p = ExAllocatePoolWithTag(PagedPool, size, 0x41535953);		// SYSA
    }
#else
    p = ExAllocatePoolWithTag(PagedPool, size, 0x41535953);		// SYSA
#endif
    if(p != NULL) {
	RtlZeroMemory(p, size);

    #if defined(USE_ZONES) || defined(DEBUG)
	*((PULONG)p) = size;
	p = ((PULONGLONG)p) + 1;
    #endif

    #ifdef MEMORY_LIST
	ExInterlockedInsertTailList(
	  &gleMemoryHead,
	  ((PLIST_ENTRY)p),
	  &gSpinLockMemoryHead);
	p = ((PLIST_ENTRY)p) + 1;
    #endif

    #ifdef DEBUG
	cbMemoryUsage += size;
	++cAllocMem;
	if(size <= SMALL_BLOCK_SIZE) {
	    ++cAllocMemSmall;
	}
	else if(size <= 64) {
	    ++cAllocMem64;
	}
	else if(size <= 128) {
	    ++cAllocMem128;
	}
    #endif
    }
    AssertAligned(p);
    return(p);
}

//
// Frees memory
//

void __cdecl operator delete( void *p )
{
    if(p != NULL) {

        #ifdef MEMORY_LIST
        KIRQL OldIrql;
        p = ((PLIST_ENTRY)p) - 1;
        KeAcquireSpinLock(&gSpinLockMemoryHead, &OldIrql);
        RemoveEntryList((PLIST_ENTRY)p);
        KeReleaseSpinLock(&gSpinLockMemoryHead, OldIrql);
        #endif

        #if defined(USE_ZONES) || defined(DEBUG)
        ULONG size;
        AssertAligned(p);
        p = ((PULONGLONG)p) - 1;
        size = *((PULONG)p);
        #endif

        #ifdef DEBUG
        cbMemoryUsage -= size;
        --cAllocMem;
        if(size <= SMALL_BLOCK_SIZE) {
            --cAllocMemSmall;
        }
        else if(size <= 64) {
            --cAllocMem64;
        }
        else if(size <= 128) {
            --cAllocMem128;
        }
        #endif

        #ifdef USE_ZONES
        if(size <= SMALL_BLOCK_SIZE) {
            ExFreeToZone(&gZone, p);
        }
        else {
            ExFreePool(p);
        }
        #else
        ExFreePool(p);
        #endif
    }
}

#pragma PAGEABLE_CODE

//---------------------------------------------------------------------------

#ifdef DEBUG

VOID
DumpPinConnect(
    LONG Level,
    PKSPIN_CONNECT pPinConnect
)
{
    DPF1(Level, "    PinId: %d", pPinConnect->PinId);
    DPF1(Level, "Interface: %s", DbgIdentifier2Sz(&pPinConnect->Interface));
    DPF1(Level, "   Medium: %s", DbgIdentifier2Sz(&pPinConnect->Medium));
    DumpDataFormat(Level, ((PKSDATAFORMAT)(pPinConnect + 1)));
}

VOID
DumpDataFormat(
    LONG Level,
    PKSDATAFORMAT pDataFormat
)
{
    DPF2(Level, 
      " FormatSize: %02x Flags: %08x", 
      pDataFormat->FormatSize,
      pDataFormat->Flags);
    DPF2(Level, 
      " SampleSize: %02x Reserved: %08x", 
      pDataFormat->SampleSize,
      pDataFormat->Reserved);
    DPF1(Level, "MajorFormat: %s", DbgGuid2Sz(&pDataFormat->MajorFormat));
    DPF1(Level, "  SubFormat: %s", DbgGuid2Sz(&pDataFormat->SubFormat));
    DPF1(Level, "  Specifier: %s", DbgGuid2Sz(&pDataFormat->Specifier));

    if(IsEqualGUID(
      &KSDATAFORMAT_SPECIFIER_WAVEFORMATEX,
      &pDataFormat->Specifier)) {

        DumpWaveFormatEx(
	  Level,
	  "WaveFmtEx",
          &((PKSDATAFORMAT_WAVEFORMATEX)pDataFormat)->WaveFormatEx);
    }
    if(IsEqualGUID(
      &KSDATAFORMAT_SPECIFIER_DSOUND,
      &pDataFormat->Specifier)) {

        DumpWaveFormatEx(
	  Level,
	  "DSOUND",
	  &((PKSDATAFORMAT_DSOUND)pDataFormat)->BufferDesc.WaveFormatEx);
    }
}

VOID
DumpWaveFormatEx(
    LONG Level,
    PSZ pszSpecifier,
    WAVEFORMATEX *pWaveFormatEx
)
{
    DPF8(Level, "%s T %u SR %u CH %u BPS %u ABPS %u BA %u cb %u",
      pszSpecifier,
      pWaveFormatEx->wFormatTag,
      pWaveFormatEx->nSamplesPerSec,
      pWaveFormatEx->nChannels,
      pWaveFormatEx->wBitsPerSample,
      pWaveFormatEx->nAvgBytesPerSec,
      pWaveFormatEx->nBlockAlign,
      pWaveFormatEx->cbSize);

    if(pWaveFormatEx->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
	DPF3(Level, "VBPS %u CHMASK %08x %s",
	  ((PWAVEFORMATEXTENSIBLE)pWaveFormatEx)->Samples.wValidBitsPerSample,
	  ((PWAVEFORMATEXTENSIBLE)pWaveFormatEx)->dwChannelMask,
	  DbgGuid2Sz(&((PWAVEFORMATEXTENSIBLE)pWaveFormatEx)->SubFormat));
    }
}

VOID
DumpDataRange(
    LONG Level,
    PKSDATARANGE_AUDIO pDataRangeAudio
)
{
    DPF2(Level,
      " FormatSize: %02x Flags: %08x", 
      pDataRangeAudio->DataRange.FormatSize,
      pDataRangeAudio->DataRange.Flags);
    DPF2(Level,
      " SampleSize: %02x Reserved: %08x", 
      pDataRangeAudio->DataRange.SampleSize,
      pDataRangeAudio->DataRange.Reserved);
    DPF1(Level, "MajorFormat: %s",
      DbgGuid2Sz(&pDataRangeAudio->DataRange.MajorFormat));
    DPF1(Level, "  SubFormat: %s",
      DbgGuid2Sz(&pDataRangeAudio->DataRange.SubFormat));
    DPF1(Level, "  Specifier: %s",
      DbgGuid2Sz(&pDataRangeAudio->DataRange.Specifier));

    if(IsEqualGUID(
      &KSDATAFORMAT_SPECIFIER_WAVEFORMATEX,
      &pDataRangeAudio->DataRange.Specifier)) {

	DPF5(Level, "WaveFmtEx: MaxCH %d MaxSR %u MinSR %u MaxBPS %u MinBPS %u",
          pDataRangeAudio->MaximumChannels,
          pDataRangeAudio->MinimumSampleFrequency,
          pDataRangeAudio->MaximumSampleFrequency,
          pDataRangeAudio->MinimumBitsPerSample,
          pDataRangeAudio->MaximumBitsPerSample);
    }

    if(IsEqualGUID(
      &KSDATAFORMAT_SPECIFIER_DSOUND,
      &pDataRangeAudio->DataRange.Specifier)) {

	DPF5(Level, "DSOUND:    MaxCH %d MaxSR %u MinSR %u MaxBPS %u MinBPS %u",
          pDataRangeAudio->MaximumChannels,
          pDataRangeAudio->MinimumSampleFrequency,
          pDataRangeAudio->MaximumSampleFrequency,
          pDataRangeAudio->MinimumBitsPerSample,
          pDataRangeAudio->MaximumBitsPerSample);

    }
}

VOID
DumpDataRangeAudio(
    PKSDATARANGE_AUDIO pDataRangeAudio
)
{
    if(IsEqualGUID(
      &KSDATAFORMAT_SPECIFIER_DSOUND,
      &pDataRangeAudio->DataRange.Specifier) ||
       IsEqualGUID(
      &KSDATAFORMAT_SPECIFIER_WAVEFORMATEX,
      &pDataRangeAudio->DataRange.Specifier)) {
	dprintf("    MaxCH %d MaxSR %u MinSR %u MaxBPS %u MinBPS %u\n",
	  pDataRangeAudio->MaximumChannels,
	  pDataRangeAudio->MaximumSampleFrequency,
	  pDataRangeAudio->MinimumSampleFrequency,
	  pDataRangeAudio->MaximumBitsPerSample,
	  pDataRangeAudio->MinimumBitsPerSample);
    }
}

VOID
DumpfulType(
    ULONG fulType
)
{
    if(fulType & FILTER_TYPE_AUDIO) {
	dprintf("AUDIO ");
    }
    if(fulType & FILTER_TYPE_GFX) {
	dprintf("GFX ");
    }
    if(fulType & FILTER_TYPE_TOPOLOGY) {
	dprintf("TOPOLOGY ");
    }
    if(fulType & FILTER_TYPE_BRIDGE) {
	dprintf("BRIDGE ");
    }
    if(fulType & FILTER_TYPE_RENDERER) {
	dprintf("RENDERER ");
    }
    if(fulType & FILTER_TYPE_CAPTURER) {
	dprintf("CAPTURER ");
    }
    if(fulType & FILTER_TYPE_MIXER) {
	dprintf("MIXER ");
    }
    if(fulType & FILTER_TYPE_SPLITTER) {
	dprintf("SPLITTER ");
    }
    if(fulType & FILTER_TYPE_SYNTHESIZER) {
	dprintf("SYNTHESIZER ");
    }
    if(fulType & FILTER_TYPE_DRM_DESCRAMBLE) {
	dprintf("DRM_DESCRAMBLE ");
    }
    if(fulType & FILTER_TYPE_DATA_TRANSFORM) {
	dprintf("DATA_TRANSFORM ");
    }
    if(fulType & FILTER_TYPE_AEC) {
	dprintf("AEC ");
    }
    if(fulType & FILTER_TYPE_MIC_ARRAY_PROCESSOR) {
	dprintf("MIC_ARRAY_PROCESSOR ");
    }
    if(fulType & FILTER_TYPE_COMMUNICATION_TRANSFORM) {
	dprintf("COMMUNICATION_TRANSFORM ");
    }
    if(fulType & FILTER_TYPE_INTERFACE_TRANSFORM) {
	dprintf("INTERFACE_TRANSFORM ");
    }
    if(fulType & FILTER_TYPE_MEDIUM_TRANSFORM) {
	dprintf("MEDIUM_TRANSFORM ");
    }
    if(fulType & FILTER_TYPE_VIRTUAL) {
	dprintf("VIRTUAL ");
    }
}

ENUMFUNC
DumpListData(
    PVOID pData
)
{
    dprintf(" %08x", pData);
    return(STATUS_CONTINUE);
}

PSZ
DbgUnicode2Sz(
    PWSTR pwstr
)
{
    static char sz[256];
    UNICODE_STRING UnicodeString;
    ANSI_STRING AnsiString;

    sz[0] = '\0';
    if(pwstr != NULL) {
        RtlInitUnicodeString(&UnicodeString, pwstr);
        RtlInitAnsiString(&AnsiString, sz);
        AnsiString.MaximumLength = sizeof(sz);
        RtlUnicodeStringToAnsiString(&AnsiString, &UnicodeString, FALSE);
    }
    return(sz);
}

PSZ
DbgIdentifier2Sz(
    PKSIDENTIFIER pIdentifier
)
{
    static char sz[256];

    sz[0] = '\0';
    if(pIdentifier != NULL && pIdentifier != INTERNAL_WILDCARD) {
        if(IsEqualGUID(
          &KSMEDIUMSETID_Standard,
          &pIdentifier->Set) &&
          (pIdentifier->Id == KSMEDIUM_STANDARD_DEVIO)) {
            return("KSMEDIUM_STANDARD_DEVIO");
        }
        if(IsEqualGUID(
          &KSINTERFACESETID_Standard,
          &pIdentifier->Set)) {
            switch(pIdentifier->Id) {
                case KSINTERFACE_STANDARD_STREAMING:
                    return("KSINTERFACE_STANDARD_STREAMING");
                case KSINTERFACE_STANDARD_LOOPED_STREAMING:
                    return("KSINTERFACE_STANDARD_LOOPED_STREAMING");
                case KSINTERFACE_STANDARD_CONTROL:
                    return("KSINTERFACE_STANDARD_CONTROL");
            }
        }
        if(IsEqualGUID(
          &KSINTERFACESETID_Media,
          &pIdentifier->Set)) {
            switch(pIdentifier->Id) {
                case KSINTERFACE_MEDIA_MUSIC:
                    return("KSINTERFACE_MEDIA_MUSIC");
                case KSINTERFACE_MEDIA_WAVE_BUFFERED:
                    return("KSINTERFACE_MEDIA_WAVE_BUFFERED");
                case KSINTERFACE_MEDIA_WAVE_QUEUED:
                    return("KSINTERFACE_MEDIA_WAVE_QUEUED");
            }
        }
        if(IsEqualGUID(
          &KSPROPSETID_Pin,
          &pIdentifier->Set)) {
            switch(pIdentifier->Id) {
                case KSPROPERTY_PIN_CINSTANCES:
                    return("KSPROPERTY_PIN_CINSTANCES");
                case KSPROPERTY_PIN_CTYPES:
                    return("KSPROPERTY_PIN_CTYPES");
                case KSPROPERTY_PIN_DATAFLOW:
                    return("KSPROPERTY_PIN_DATAFLOW");
                case KSPROPERTY_PIN_DATARANGES:
                    return("KSPROPERTY_PIN_DATARANGES");
                case KSPROPERTY_PIN_DATAINTERSECTION:
                    return("KSPROPERTY_PIN_DATAINTERSECTION");
                case KSPROPERTY_PIN_INTERFACES:
                    return("KSPROPERTY_PIN_INTERFACES");
                case KSPROPERTY_PIN_MEDIUMS:
                    return("KSPROPERTY_PIN_MEDIUMS");
                case KSPROPERTY_PIN_COMMUNICATION:
                    return("KSPROPERTY_PIN_COMMUNICATION");
                case KSPROPERTY_PIN_GLOBALCINSTANCES:
                    return("KSPROPERTY_PIN_GLOBALCINSTANCES");
                case KSPROPERTY_PIN_NECESSARYINSTANCES:
                    return("KSPROPERTY_PIN_NECESSARYINSTANCES");
                case KSPROPERTY_PIN_PHYSICALCONNECTION:
                    return("KSPROPERTY_PIN_PHYSICALCONNECTION");
                case KSPROPERTY_PIN_CATEGORY:
                    return("KSPROPERTY_PIN_CATEGORY");
                case KSPROPERTY_PIN_NAME:
                    return("KSPROPERTY_PIN_NAME");
                case KSPROPERTY_PIN_CONSTRAINEDDATARANGES:
                    return("KSPROPERTY_PIN_CONSTRAINEDDATARANGES");
            }
        }
        if(IsEqualGUID(
          &KSPROPSETID_Connection,
          &pIdentifier->Set)) {
            switch(pIdentifier->Id) {
                case KSPROPERTY_CONNECTION_STATE:
                    return("KSPROPERTY_CONNECTION_STATE");
                case KSPROPERTY_CONNECTION_PRIORITY:
                    return("KSPROPERTY_CONNECTION_PRIORITY");
                case KSPROPERTY_CONNECTION_DATAFORMAT:
                    return("KSPROPERTY_CONNECTION_DATAFORMAT");
                case KSPROPERTY_CONNECTION_ALLOCATORFRAMING:
                    return("KSPROPERTY_CONNECTION_ALLOCATORFRAMING");
                case KSPROPERTY_CONNECTION_PROPOSEDATAFORMAT:
                    return("KSPROPERTY_CONNECTION_PROPOSEDATAFORMAT");
                case KSPROPERTY_CONNECTION_ACQUIREORDERING:
                    return("KSPROPERTY_CONNECTION_ACQUIREORDERING");
                case KSPROPERTY_CONNECTION_ALLOCATORFRAMING_EX:
                    return("KSPROPERTY_CONNECTION_ALLOCATORFRAMING_EX");
            }
        }
        if(IsEqualGUID(
          &KSPROPSETID_Stream,
          &pIdentifier->Set)) {
            switch(pIdentifier->Id) {
                case KSPROPERTY_STREAM_ALLOCATOR:
                    return("KSPROPERTY_STREAM_ALLOCATOR");
                case KSPROPERTY_STREAM_MASTERCLOCK:
                    return("KSPROPERTY_STREAM_MASTERCLOCK");
            }
            sprintf(sz, "KSPROPSETID_Stream Id: %02x", pIdentifier->Id);
            return(sz);
        }
        if(IsEqualGUID(
          &KSPROPSETID_Clock,
          &pIdentifier->Set)) {
            sprintf(sz, "KSPROPSETID_Clock Id: %02x", pIdentifier->Id);
            return(sz);
        }
        if(IsEqualGUID(
          &KSPROPSETID_StreamAllocator,
          &pIdentifier->Set)) {
            sprintf(sz, "KSPROPSETID_StreamAllocator Id: %02x",
              pIdentifier->Id);
            return(sz);
        }
        if(IsEqualGUID(
          &KSPROPSETID_StreamInterface,
          &pIdentifier->Set)) {
            sprintf(sz, "KSPROPSETID_StreamInterface Id: %02x",
              pIdentifier->Id);
            return(sz);
        }
        if(IsEqualGUID(
          &KSPROPSETID_Topology,
          &pIdentifier->Set)) {
            switch(pIdentifier->Id) {
                case KSPROPERTY_TOPOLOGY_CATEGORIES:
                    return("KSPROPERTY_TOPOLOGY_CATEGORIES");
                case KSPROPERTY_TOPOLOGY_NODES:
                    return("KSPROPERTY_TOPOLOGY_NODES");
                case KSPROPERTY_TOPOLOGY_CONNECTIONS:
                    return("KSPROPERTY_TOPOLOGY_CONNECTIONS");
                case KSPROPERTY_TOPOLOGY_NAME:
                    return("KSPROPERTY_TOPOLOGY_NAME");
                default:
                    sprintf(sz, "KSPROPSETID_Topology Id: %02x",
                      pIdentifier->Id);
                    return(sz);
            }
        }
        if(IsEqualGUID(
          &KSPROPSETID_Audio,
          &pIdentifier->Set)) {
            switch(pIdentifier->Id) {
                case KSPROPERTY_AUDIO_VOLUMELEVEL:
                    return("KSPROPERTY_AUDIO_VOLUMELEVEL");
                case KSPROPERTY_AUDIO_MUTE:
                    return("KSPROPERTY_AUDIO_MUTE");
                case KSPROPERTY_AUDIO_MIX_LEVEL_TABLE:
                    return("KSPROPERTY_AUDIO_MIX_LEVEL_TABLE");
                case KSPROPERTY_AUDIO_MIX_LEVEL_CAPS:
                    return("KSPROPERTY_AUDIO_MIX_LEVEL_CAPS");
                case KSPROPERTY_AUDIO_MUX_SOURCE:
                    return("KSPROPERTY_AUDIO_MUX_SOURCE");
                case KSPROPERTY_AUDIO_BASS:
                    return("KSPROPERTY_AUDIO_BASS");
                case KSPROPERTY_AUDIO_MID:
                    return("KSPROPERTY_AUDIO_MID");
                case KSPROPERTY_AUDIO_TREBLE:
                    return("KSPROPERTY_AUDIO_TREBLE");
                case KSPROPERTY_AUDIO_BASS_BOOST:
                    return("KSPROPERTY_AUDIO_BASS_BOOST");
                case KSPROPERTY_AUDIO_AGC:
                    return("KSPROPERTY_AUDIO_AGC");
                default:
                    sprintf(sz, "KSPROPSETID_Audio Id: %02x", pIdentifier->Id);
                    return(sz);
            }
        }
        if(IsEqualGUID(
          &KSPROPSETID_Sysaudio,
          &pIdentifier->Set)) {
            switch(pIdentifier->Id) {
                case KSPROPERTY_SYSAUDIO_DEVICE_COUNT:
                    return("KSPROPERTY_SYSAUDIO_DEVICE_COUNT");
                case KSPROPERTY_SYSAUDIO_DEVICE_FRIENDLY_NAME:
                    return("KSPROPERTY_SYSAUDIO_DEVICE_FRIENDLY_NAME");
                case KSPROPERTY_SYSAUDIO_DEVICE_INSTANCE:
                    return("KSPROPERTY_SYSAUDIO_DEVICE_INSTANCE");
                case KSPROPERTY_SYSAUDIO_DEVICE_INTERFACE_NAME:
                    return("KSPROPERTY_SYSAUDIO_DEVICE_INTERFACE_NAME");
                case KSPROPERTY_SYSAUDIO_SELECT_GRAPH:
                    return("KSPROPERTY_SYSAUDIO_SELECT_GRAPH");
                case KSPROPERTY_SYSAUDIO_CREATE_VIRTUAL_SOURCE:
                    return("KSPROPERTY_SYSAUDIO_CREATE_VIRTUAL_SOURCE");
                case KSPROPERTY_SYSAUDIO_DEVICE_DEFAULT:
                    return("KSPROPERTY_SYSAUDIO_DEVICE_DEFAULT");
                case KSPROPERTY_SYSAUDIO_ALWAYS_CREATE_VIRTUAL_SOURCE:
                    return("KSPROPERTY_SYSAUDIO_ALWAYS_CREATE_VIRTUAL_SOURCE");
                case KSPROPERTY_SYSAUDIO_ADDREMOVE_LOCK:
                    return("KSPROPERTY_SYSAUDIO_ADDREMOVE_LOCK");
                case KSPROPERTY_SYSAUDIO_ADDREMOVE_UNLOCK:
                    return("KSPROPERTY_SYSAUDIO_ADDREMOVE_UNLOCK");
                case KSPROPERTY_SYSAUDIO_RENDER_PIN_INSTANCES:
                    return("KSPROPERTY_SYSAUDIO_RENDER_PIN_INSTANCES");
                case KSPROPERTY_SYSAUDIO_RENDER_CONNECTION_INDEX:
                    return("KSPROPERTY_SYSAUDIO_RENDER_CONNECTION_INDEX");
                default:
                    sprintf(sz, "KSPROPSETID_Sysaudio Id: %02x",
                      pIdentifier->Id);
                    return(sz);
            }
        }
        if(IsEqualGUID(
          &KSEVENTSETID_Connection,
          &pIdentifier->Set)) {
            switch(pIdentifier->Id) {
                case KSEVENT_CONNECTION_POSITIONUPDATE:
                    return("KSEVENT_CONNECTION_POSITIONUPDATE");
                case KSEVENT_CONNECTION_DATADISCONTINUITY:
                    return("KSEVENT_CONNECTION_DATADISCONTINUITY");
                case KSEVENT_CONNECTION_TIMEDISCONTINUITY:
                    return("KSEVENT_CONNECTION_TIMEDISCONTINUITY");
                case KSEVENT_CONNECTION_PRIORITY:
                    return("KSEVENT_CONNECTION_PRIORITY");
                case KSEVENT_CONNECTION_ENDOFSTREAM:
                    return("KSEVENT_CONNECTION_ENDOFSTREAM");
            }
        }
        if(IsEqualGUID(
          &KSEVENTSETID_Clock,
          &pIdentifier->Set)) {
            switch(pIdentifier->Id) {
                case KSEVENT_CLOCK_INTERVAL_MARK:
                    return("KSEVENT_CLOCK_INTERVAL_MARK");
                case KSEVENT_CLOCK_POSITION_MARK:
                    return("KSEVENT_CLOCK_POSITION_MARK");
            }
        }
        if(IsEqualGUID(
          &GUID_NULL,
          &pIdentifier->Set)) {
            sprintf(sz, "GUID_NULL Id: %02x", pIdentifier->Id);
            return(sz);
        }
        sprintf(sz, "Set: %s Id: %02x",
          DbgGuid2Sz(&pIdentifier->Set),
          pIdentifier->Id);
    }
    else {
        sprintf(sz, "%08x", (ULONG_PTR)pIdentifier);
    }
    return(sz);
}

PSZ
DbgGuid2Sz(
    GUID *pGuid
)
{
    static char sz[256];
    if(pGuid == NULL) {
        return("NO GUID");
    }
    if(IsEqualGUID(
      &GUID_NULL,
      pGuid)) {
        return("GUID_NULL");
    }
    if(IsEqualGUID(
      &KSDATAFORMAT_TYPE_AUDIO,
      pGuid)) {
        return("KSDATAFORMAT_TYPE_AUDIO");
    }
    if(IsEqualGUID(
      &KSDATAFORMAT_SUBTYPE_ANALOG,
      pGuid)) {
        return("KSDATAFORMAT_SUBTYPE_ANALOG");
    }
    if(IsEqualGUID(
      &KSDATAFORMAT_SUBTYPE_PCM,
      pGuid)) {
        return("KSDATAFORMAT_SUBTYPE_PCM");
    }
    if(IsEqualGUID(
      &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT,
      pGuid)) {
        return("KSDATAFORMAT_SUBTYPE_IEEE_FLOAT");
    }
    if(IsEqualGUID(
      &KSDATAFORMAT_TYPE_MUSIC,
      pGuid)) {
        return("KSDATAFORMAT_TYPE_MUSIC");
    }
    if(IsEqualGUID(
      &KSDATAFORMAT_SUBTYPE_MIDI,
      pGuid)) {
        return("KSDATAFORMAT_SUBTYPE_MIDI");
    }
    if(IsEqualGUID(
      &KSDATAFORMAT_SUBTYPE_MIDI_BUS,
      pGuid)) {
        return("KSDATAFORMAT_SUBTYPE_MIDI_BUS");
    }
    if(IsEqualGUID(
      &KSDATAFORMAT_SPECIFIER_DSOUND,
      pGuid)) {
        return("KSDATAFORMAT_SPECIFIER_DSOUND");
    }
    if(IsEqualGUID(
      &KSDATAFORMAT_SPECIFIER_WAVEFORMATEX,
      pGuid)) {
        return("KSDATAFORMAT_SPECIFIER_WAVEFORMATEX");
    }
    if(IsEqualGUID(
      &KSDATAFORMAT_SPECIFIER_NONE,
      pGuid)) {
        return("KSDATAFORMAT_SPECIFIER_NONE");
    }
    if(IsEqualGUID(
      &KSCATEGORY_AUDIO,
      pGuid)) {
        return("KSCATEGORY_AUDIO");
    }
    if(IsEqualGUID(
      &KSNODETYPE_SPEAKER,
      pGuid)) {
        return("KSNODETYPE_SPEAKER");
    }
    if(IsEqualGUID(
      &KSNODETYPE_MICROPHONE,
      pGuid)) {
        return("KSNODETYPE_MICROPHONE");
    }
    if(IsEqualGUID(
      &KSNODETYPE_CD_PLAYER,
      pGuid)) {
        return("KSNODETYPE_CD_PLAYER");
    }
    if(IsEqualGUID(
      &KSNODETYPE_LEGACY_AUDIO_CONNECTOR,
      pGuid)) {
        return("KSNODETYPE_LEGACY_AUDIO_CONNECTOR");
    }
    if(IsEqualGUID(
      &KSNODETYPE_ANALOG_CONNECTOR,
      pGuid)) {
        return("KSNODETYPE_ANALOG_CONNECTOR");
    }
    if(IsEqualGUID(
      &KSCATEGORY_WDMAUD_USE_PIN_NAME,
      pGuid)) {
        return("KSCATEGORY_WDMAUD_USE_PIN_NAME");
    }
    if(IsEqualGUID(
      &KSNODETYPE_LINE_CONNECTOR,
      pGuid)) {
        return("KSNODETYPE_LINE_CONNECTOR");
    }
    if(IsEqualGUID(
      &GUID_TARGET_DEVICE_QUERY_REMOVE,
      pGuid)) {
        return("GUID_TARGET_DEVICE_QUERY_REMOVE");
    }
    if(IsEqualGUID(
      &GUID_TARGET_DEVICE_REMOVE_CANCELLED,
      pGuid)) {
        return("GUID_TARGET_DEVICE_REMOVE_CANCELLED");
    }
    if(IsEqualGUID(
      &GUID_TARGET_DEVICE_REMOVE_COMPLETE,
      pGuid)) {
        return("GUID_TARGET_DEVICE_REMOVE_COMPLETE");
    }
    if(IsEqualGUID(
      &PINNAME_CAPTURE,
      pGuid)) {
        return("PINNAME_CAPTURE");
    }
    if(IsEqualGUID(&KSNODETYPE_DAC, pGuid)) {
	return("KSNODETYPE_DAC");
    }
    if(IsEqualGUID(&KSNODETYPE_ADC, pGuid)) {
	return("KSNODETYPE_ADC");
    }
    if(IsEqualGUID(&KSNODETYPE_SRC, pGuid)) {
	return("KSNODETYPE_SRC");
    }
    if(IsEqualGUID(&KSNODETYPE_SUPERMIX, pGuid)) {
	return("KSNODETYPE_SUPERMIX");
    }
    if(IsEqualGUID( &KSNODETYPE_MUX, pGuid)) {
	return("KSNODETYPE_MUX");
    }
    if(IsEqualGUID( &KSNODETYPE_DEMUX, pGuid)) {
	return("KSNODETYPE_DEMUX");
    }
    if(IsEqualGUID(&KSNODETYPE_SUM, pGuid)) {
	return("KSNODETYPE_SUM");
    }
    if(IsEqualGUID(&KSNODETYPE_MUTE, pGuid)) {
	return("KSNODETYPE_MUTE");
    }
    if(IsEqualGUID(&KSNODETYPE_VOLUME, pGuid)) {
	return("KSNODETYPE_VOLUME");
    }
    if(IsEqualGUID(&KSNODETYPE_TONE, pGuid)) {
	return("KSNODETYPE_TONE");
    }
    if(IsEqualGUID(&KSNODETYPE_AGC, pGuid)) {
	return("KSNODETYPE_AGC");
    }
    if(IsEqualGUID(&KSNODETYPE_SYNTHESIZER, pGuid)) {
	return("KSNODETYPE_SYNTHESIZER");
    }
    if(IsEqualGUID(&KSNODETYPE_SWSYNTH, pGuid)) {
	return("KSNODETYPE_SWSYNTH");
    }
    if(IsEqualGUID(&KSNODETYPE_3D_EFFECTS, pGuid)) {
	return("KSNODETYPE_3D_EFFECTS");
    }
    sprintf(sz, "%08x %04x %04x %02x%02x%02x%02x%02x%02x%02x%02x",
      pGuid->Data1,
      pGuid->Data2,
      pGuid->Data3,
      pGuid->Data4[0],
      pGuid->Data4[1],
      pGuid->Data4[2],
      pGuid->Data4[3],
      pGuid->Data4[4],
      pGuid->Data4[5],
      pGuid->Data4[6],
      pGuid->Data4[7]);

    return(sz);
}

//---------------------------------------------------------------------------

#ifndef UNDER_NT

#ifdef _X86_

extern ULONG nFilter;
extern ULONG nDevice;
extern ULONG nLogicalFilter;

VOID
DebugCommand(
)
{
    PKSDATAFORMAT pDataFormat;
    PKSDATARANGE pDataRange;
    PCOBJ pcobj;
    CHAR c;

    nFilter = 0;
    nDevice = 0;
    nLogicalFilter = 0;
    ulDebugFlags = 0;
    ulDebugNumber = MAXULONG;
    while((c = DebugGetCommandChar()) != '\0') {
        switch(c) {
            case 'A':
                ulDebugFlags |= DEBUG_FLAGS_ADDRESS;
                break;
            case 'D':
                ulDebugFlags |= DEBUG_FLAGS_DEVICE;
                break;
            case 'F':
                ulDebugFlags |= DEBUG_FLAGS_FILTER;
                break;
            case 'L':
                ulDebugFlags |= DEBUG_FLAGS_LOGICAL_FILTER;
                break;
            case 'P':
                ulDebugFlags |= DEBUG_FLAGS_PIN;
                break;
            case 'I':
                ulDebugFlags |= DEBUG_FLAGS_INSTANCE;
                break;
            case 'G':
                ulDebugFlags |= DEBUG_FLAGS_GRAPH;
                break;
            case 'T':
                ulDebugFlags |= DEBUG_FLAGS_TOPOLOGY;
                break;
            case 'R':
		pDataRange = (PKSDATARANGE)DebugEvaluateExpression();
		if(ulDebugFlags & DEBUG_FLAGS_FILTER) {
		    DumpDataFormat(MAXULONG, pDataRange);
		}
		else {
		    DumpDataRangeAudio((PKSDATARANGE_AUDIO)pDataRange);
		}
		return;
            case 'O':
                ulDebugFlags |= DEBUG_FLAGS_OBJECT;
		pcobj = (PCOBJ)DebugEvaluateExpression();

		if(PFILTER_INSTANCE(pcobj)->m_Signature.IsAssert()) {
		    PFILTER_INSTANCE(pcobj)->Dump();
		    return;
		}
		if(PPIN_INSTANCE(pcobj)->m_Signature.IsAssert()) {
		    PPIN_INSTANCE(pcobj)->Dump();
		    return;
		}
		if(PINSTANCE(pcobj)->m_Signature.IsAssert()) {
		    PINSTANCE(pcobj)->Dump();
		    return;
		}
		pcobj->Dump();
                return;
            case 'V':
                ulDebugFlags |= DEBUG_FLAGS_VERBOSE;
                break;
            case 'X':
                ulDebugFlags |= DEBUG_FLAGS_DETAILS;
                break;
            case '?':
                dprintf(".S[D|F|V|I|O]\n");
                dprintf("[L|P|T|A|V|X]O <addr> - Dump Object\n");
                dprintf("R <addr>              - Dump DataRange\n");
                dprintf("D[0-9][L|P|T][A|V|X]  - Devices\n");
                dprintf("F[0-9][L|P|T][A|V|X]  - Filters\n");
                dprintf("G[0-9][L|P|T][A|V|X]  - Graphs\n");
                dprintf("I[0-9][L|P|T][A|V|X]  - Instances\n");
                dprintf(" 0-9 - Device or Filter number\n");
                dprintf("   L - Logical Filter Nodes\n");
                dprintf("   P - Pins\n");
                dprintf("   T - Topology\n");
                dprintf("   A - Addresses\n");
                dprintf("   V - Verbose\n");
                dprintf("   X - Details\n");
                return;
            default:
                if(c >= '0' && c <= '9') {
		    if(ulDebugNumber == MAXULONG) {
		       ulDebugNumber++;
		    }
                    ulDebugNumber *= 10;
                    ulDebugNumber += c - '0';
                }
                break;
        }
    }
    if((ulDebugFlags != 0) &&
      (ulDebugFlags &
      (DEBUG_FLAGS_DEVICE |
       DEBUG_FLAGS_FILTER |
       DEBUG_FLAGS_INSTANCE |
       DEBUG_FLAGS_LOGICAL_FILTER |
       DEBUG_FLAGS_GRAPH)) == 0) {
        ulDebugFlags |= DEBUG_FLAGS_DEVICE;
    }
    DumpSysAudio();
}

VOID
DebugDotCommand(
)
{
    DebugCommand();
    __asm xor eax, eax
    __asm retf
}

VOID
InitializeDebug(
)
{
    static char *pszHelp = ".S - Dump System Audio Driver data structures\n";

    __asm {
        _emit 0xcd
        _emit 0x20
        _emit 0xc1
        _emit 0x00
        _emit 0x01
        _emit 0x00
        jz exitlab

        mov bl, 'S'
        mov esi, offset DebugDotCommand
        mov edi, pszHelp
        mov eax, 0x70   // DS_RegisterDotCommand
        int 41h
exitlab:
    }
}

VOID
UninitializeDebug(
)
{
    __asm {
        _emit 0xcd
        _emit 0x20
        _emit 0xc1
        _emit 0x00
        _emit 0x01
        _emit 0x00
        jz exitlab

        mov bl, 'S'
        mov eax, 0x72   // DS_DeRegisterDotCommand
        int 41h
exitlab:
    }
}

VOID __cdecl
dprintf(
    PSZ pszFmt,
    ...
)
{
    __asm mov esi, [pszFmt]
    __asm lea edi, [pszFmt+4]
    __asm mov eax, 0x73
    __asm int 41h
}

CHAR
DebugGetCommandChar(
)
{
    CHAR c;
    __asm mov ax, 0x77          // get command char
    __asm mov bl, 1             // get char
    __asm int 41h
    __asm or ah, ah
    __asm jnz morechars
    __asm mov al, ah
morechars:
    __asm movzx eax, al
    __asm mov c, al
    return(c);
}

ULONG
DebugEvaluateExpression(
)
{
    ULONG ul;
    __asm mov ax, 0x78          // EvaluateExpression
    __asm int 41h
    __asm mov ul, ebx
    return(ul);
}

#endif  // _X86_

#endif  // UNDER_NT

#endif  // DEBUG

//---------------------------------------------------------------------------
//  End of File: util.c
//---------------------------------------------------------------------------