//////////////////////////////////////////////////////////////////////////////
//
//  Module: cping.cpp (cping.exe)
//
//  Microsoft Research Detours Package
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//
//  COM Ping text program.
//
#define _RPCRT4_
#define INITGUID
#include <winsock2.h>
#include <objbase.h>
#include <objidl.h>
#include <ocidl.h>
#include <olectl.h>
#include <shellapi.h>
#include <stdio.h>
#include <stdlib.h>
#pragma warning(push)
#if _MSC_VER > 1400
#pragma warning(disable:6102 6103) // /analyze warnings
#endif
#include <strsafe.h>
#pragma warning(pop)
#include <winnt.h>
#include <rpc.h>
#include <rpcdcep.h>
#include <detours.h>
#include "iping.h"

// ARM64/ARM64EC ReadTimeStampCounter is a function.
// ARM ReadTimeStampCounter is a declared function but not implemented.
// old IA64: ReadTimeStampCounter nonexisant.
// new IA64: ReadTimeStampCounter is a macro.
// old x86; ReadTimeStampCounter is a function.
// new x86: ReadTimeStampCounter is a macro.
// AMD64: ReadTimeStampCounter is a macro.

#if defined(_ARM64_) || defined(_ARM64EC_) || defined(ReadTimeStampCounter)
#define GetTimeStamp() ReadTimeStampCounter()
#elif defined(_X86_) || defined(_AMD64_)
extern "C"
DWORD64
__rdtsc (
    VOID
    );
#pragma intrinsic(__rdtsc)
#define GetTimeStamp() __rdtsc()
#else
UINT64 GetTimeStamp(void)
{
    LARGE_INTEGER a = { 0 };
    QueryPerformanceCounter(&a);
    return a.QuadPart;
}
#endif

#define BE_VERBOSE
////////////////////////////////////////////////////////// Assertion Handling.
//
#pragma warning(disable:4127)   // Many of our asserts are constants.

#ifndef NODEBUG
#undef ASSERT
VOID PingAssertMessage(CONST PCHAR szMsg, CONST PCHAR szFile, ULONG nLine);
#define ASSERT(x)   \
do { if (!((int)(x))) { PingAssertMessage(#x, __FILE__, __LINE__); DebugBreak(); }} while (0)
;
#else   // NODEBUG
#undef ASSERT
#define ASSERT(x)
#endif  // NODEBUG

//////////////////////////////////////////////////////////////////////////////

#define wcssize(x)      ((wcslen(x) + 1) * sizeof(WCHAR))
#define strsize(x)      ((strlen(x) + 1) * sizeof(CHAR))

extern "C" {
    ULONG WINAPI iping_DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved);
    HRESULT STDAPICALLTYPE iping_DllRegisterServer(void);
    HRESULT STDAPICALLTYPE iping_DllUnregisterServer(void);
    HRESULT STDAPICALLTYPE iping_DllGetClassObject(REFCLSID rclsid,
                                                   REFIID riid, PVOID *ppv);
    HRESULT STDAPICALLTYPE iping_DllCanUnloadNow(void);
}

STDAPI PingMessage(PCSTR msg, ...);

///////////////////////////////////////////////////////////////////// Globals.

void *              g_pBuffer = NULL;
WCHAR               g_wzServerName[128];
WCHAR               g_wzClientName[128];
ULONG               g_cbBufferMax = 262144;
double              g_dCyclesPerSecond = 0.0;
double              g_dMsPerCycle = 0.0;
double              g_dLatency = 0.0;
BOOL                g_fSummarize = TRUE;
ULONG               g_nFixedToClient = 0;
ULONG               g_nFixedToServer = 0;

//////////////////////////////////////////////////////////////////////////////
//
static CHAR s_szMessageBuf[2048];

STDAPI PingMessage(PCSTR msg, ...)
{
    HRESULT hr;
    double d = 0.0;                                     // Required for FP support
    (void)d;
    va_list args;

    va_start(args, msg);
    hr = StringCchVPrintfA(s_szMessageBuf, ARRAYSIZE(s_szMessageBuf), msg, args);
    va_end(args);
    if (FAILED(hr)) {
        return hr;
    }

    // OutputDebugStringA(s_szMessageBuf);

    printf("%s", s_szMessageBuf);
    return S_FALSE;
}

VOID PingAssertMessage(CONST PCHAR szMsg, CONST PCHAR szFile, ULONG nLine)
{
    PingMessage("%08lx ASSERT(%s) failed in %s, line %d.\n",
                GetCurrentThreadId(), szMsg, szFile, nLine);
    printf("ASSERT(%s) failed in %s, line %ld.\n", szMsg, szFile, nLine);
}

BOOLEAN CheckResult(HRESULT hr, PCSTR pszMsg, ...)
{
    if (FAILED(hr)) {
        HRESULT ihr;
        va_list args;

        va_start(args, pszMsg);
        ihr = StringCchVPrintfA(s_szMessageBuf, ARRAYSIZE(s_szMessageBuf), pszMsg, args);
        va_end(args);
        if (FAILED(ihr)) {
            return FALSE;
        }

        printf("  %-57.57s -> %08lx\n", s_szMessageBuf, hr);
        return FALSE;
    }
    return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
#define NTSYSAPI            DECLSPEC_IMPORT
#define NTAPI               __stdcall
#define NTSTATUS            LONG
#define PIO_APC_ROUTINE     PVOID

typedef struct
{
    NTSTATUS    Status;
    LONG        Information;
} *PIO_STATUS_BLOCK;

NTSTATUS (NTAPI *Real_NtWaitForSingleObject)(HANDLE Handle,
                                             BOOLEAN Alertable,
                                             PLARGE_INTEGER Timeout) = NULL;

NTSTATUS (NTAPI *Real_NtDeviceIoControlFile)(HANDLE FileHandle,
                                             HANDLE Event,
                                             PIO_APC_ROUTINE ApcRoutine,
                                             PVOID ApcContext,
                                             PIO_STATUS_BLOCK IoStatusBlock,
                                             ULONG IoControlCode,
                                             PVOID InputBuffer,
                                             ULONG InputBufferLength,
                                             PVOID OutputBuffer,
                                             ULONG OutputBufferLength) = NULL;

//////////////////////////////////////////////////////////////////////////////
//
static LONG     s_nInCall = 0;
static ULONG    s_nThread = 0;

enum {
    E_MinValue = 0,
    E_SleepEx = 1,

    E_Proxy,

    E_I_RpcGetBuffer,
    E_I_RpcSendReceive,
    E_I_RpcFreeBuffer,
    E_I_RpcSend,
    E_I_RpcReceive,
    E_I_RpcFreePipeBuffer,
    E_I_RpcReallocPipeBuffer,
    E_I_RpcRequestMutex,
    E_I_RpcClearMutex,
    E_I_RpcAllocate,
    E_I_RpcFree,
    E_I_RpcPauseExecution,
    E_I_RpcMonitorAssociation,
    E_I_RpcStopMonitorAssociation,

    E_Recv,
    E_RecvFrom,
    E_NtWaitForSingleObject,
    E_NtDeviceIoControlFileRecv,
    E_NtDeviceIoControlFile,

    E_Send,
    E_SendTo,
    E_NtDeviceIoControlFileSend,

    E_DCOM,
    E_RPC,
    E_UDP,
    E_NET,

    E_MaxValue,

    E_DcomBeg = E_Proxy,
    E_DcomEnd = E_Proxy,

    E_RpcBeg = E_I_RpcGetBuffer,
    E_RpcEnd = E_I_RpcStopMonitorAssociation,

    E_UdpBeg = E_Send,
    E_UdpEnd = E_NtDeviceIoControlFileSend,

    E_NetBeg = E_Recv,
    E_NetEnd = E_NtDeviceIoControlFile,
};

PCHAR s_rszRouteNames[E_MaxValue] =
{
    "<NULL>",
    "SleepEx",
    "Proxy",
    "I_RpcGetBuffer",
    "I_RpcSendReceive",
    "I_RpcFreeBuffer",
    "I_RpcSend",
    "I_RpcReceive",
    "I_RpcFreePipeBuffer",
    "I_RpcReallocPipeBuffer",
    "I_RpcRequestMutex",
    "I_RpcClearMutex",
    "I_RpcAllocate",
    "I_RpcFree",
    "I_RpcPauseExecution",
    "I_RpcMonitorAssociation",
    "I_RpcStopMonitorAssociation",
    "Recv",
    "RecvFrom",
    "NtWaitForSingleObject",
    "NtDeviceIoControlRecv",
    "NtDeviceIoControlFile",
    "Send",
    "SendTo",
    "NtDeviceIoControlSend",
    "DCOM",
    "RPC",
    "UDP/TCP (Send Only)",
    "NET",
};

LONGLONG    s_rllCycles[E_MaxValue];
LONGLONG    s_rllTotals[E_MaxValue];
LONG        s_rllCounts[E_MaxValue];

class CRouteTime
{
  public:
    inline CRouteTime(LONG nRoute)
        {
            if (s_nInCall && GetCurrentThreadId() == s_nThread) {
                LONGLONG llBeg;
                m_nOldRoute = s_nRoute;
                m_llOldMinus = s_llMinus;

                s_nRoute = m_nRoute = nRoute;

                s_rllCounts[m_nRoute]++;
                s_llMinus = 0;
                ASSERT(m_nRoute != m_nOldRoute);

                llBeg = GetTimeStamp();
                m_llBeg = llBeg;
            }
            else {
                m_nRoute = 0;
            }
        }

    inline ~CRouteTime()
        {
            if (m_nRoute) {
                LONGLONG llEnd = GetTimeStamp();
                llEnd -= m_llBeg;
                s_rllTotals[m_nRoute] += llEnd;
                s_rllCycles[m_nRoute] += llEnd - s_llMinus;
                s_nRoute = m_nOldRoute;
                s_llMinus = m_llOldMinus + llEnd;
            }
        }

    inline BOOL Routed()
        {
            return m_nRoute;
        }

  public:
    ULONG       m_nRoute;
    ULONG       m_nOldRoute;
    LONGLONG    m_llBeg;
    LONGLONG    m_llOldMinus;
    static ULONG    s_nRoute;
    static LONGLONG s_llMinus;
};

ULONG       CRouteTime::s_nRoute = 0;
LONGLONG    CRouteTime::s_llMinus = 0;

VOID ZeroCycles(VOID)
{
    for (ULONG n = 0; n < E_MaxValue; n++) {
        s_rllCycles[n] = 0;
        s_rllTotals[n] = 0;
        s_rllCounts[n] = 0;
    }
}

VOID DumpCycles(LONG nRoute)
{
    if (s_rllCycles[nRoute] != 0 || s_rllTotals[nRoute] != 0) {
        printf(";;   %-21.21s %10I64d %8.3fms %10I64d %8.3fms :%6ld\n",
               s_rszRouteNames[nRoute],
               s_rllCycles[nRoute], (double)s_rllCycles[nRoute] * g_dMsPerCycle,
               s_rllTotals[nRoute], (double)s_rllTotals[nRoute] * g_dMsPerCycle,
               s_rllCounts[nRoute]);
    }
}

VOID SummarizeCycles(VOID)
{
    ULONG n;
    for (n = E_DCOM; n <= E_NET; n++) {
        s_rllCycles[n] = 0;
        s_rllTotals[n] = 0;
        s_rllCounts[n] = 0;
    }

    for (n = E_DcomBeg; n <= E_DcomEnd; n++) {
        s_rllCycles[E_DCOM] += s_rllCycles[n];
        s_rllTotals[E_DCOM] += s_rllTotals[n];
    }
    for (n = E_RpcBeg; n <= E_RpcEnd; n++) {
        s_rllCycles[E_RPC] += s_rllCycles[n];
        s_rllTotals[E_RPC] += s_rllTotals[n];
    }
    for (n = E_UdpBeg; n <= E_UdpEnd; n++) {
        s_rllCycles[E_UDP] += s_rllCycles[n];
        s_rllTotals[E_UDP] += s_rllTotals[n];
    }
    for (n = E_NetBeg; n <= E_NetEnd; n++) {
        s_rllCycles[E_NET] += s_rllCycles[n];
        s_rllTotals[E_NET] += s_rllTotals[n];
    }

#ifdef BE_VERBOSE
    printf("::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"
           "::::::::::::::::::\n");
    printf(":: Protocol Cycles:\n");
    DumpCycles(E_DCOM);
    DumpCycles(E_RPC);
    DumpCycles(E_UDP);
    DumpCycles(E_NET);
#endif

    s_rllCycles[E_DCOM] /= s_rllCounts[E_DCOM];
    s_rllCycles[E_RPC] /= s_rllCounts[E_DCOM];
    s_rllCycles[E_UDP] /= s_rllCounts[E_DCOM];
    s_rllCycles[E_NET] /= s_rllCounts[E_DCOM];
    s_rllTotals[E_DCOM] /= s_rllCounts[E_DCOM];
    s_rllTotals[E_RPC] /= s_rllCounts[E_DCOM];
    s_rllTotals[E_UDP] /= s_rllCounts[E_DCOM];
    s_rllTotals[E_NET] /= s_rllCounts[E_DCOM];

#ifdef BE_VERBOSE
    printf("::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"
           "::::::::::::::::::\n");
    printf(":: Protocol Cycles Per DCOM Call:\n");
    DumpCycles(E_DCOM);
    DumpCycles(E_RPC);
    DumpCycles(E_UDP);
    DumpCycles(E_NET);
#endif

    for (n = 0; n < E_DCOM; n++) {
        s_rllCycles[n] = 0;
        s_rllTotals[n] = 0;
        s_rllCounts[n] = 0;
    }
}

//////////////////////////////////////////////////////////////////////////////
//
DWORD (WINAPI * Real_SleepEx)(DWORD dwMilliseconds, BOOL bAlertable)
    = SleepEx;
int (WSAAPI * Real_send)(SOCKET s,
                         const char * buf, int len, int flags)
    = send;
int (WSAAPI * Real_sendto)(SOCKET s,
                           const char * buf, int len, int flags,
                           const struct sockaddr * to, int tolen)
    = sendto;
int (WSAAPI * Real_recv)(SOCKET s, char * buf, int len, int flags)
    = recv;
int (WSAAPI * Real_recvfrom)(SOCKET s,char * buf, int len, int flags,
                             struct sockaddr * from, int * fromlen)
    = recvfrom;

RPC_STATUS (RPC_ENTRY *
            Real_I_RpcGetBuffer)(RPC_MESSAGE * Message)
    = I_RpcGetBuffer;
RPC_STATUS (RPC_ENTRY *
            Real_I_RpcSendReceive)(RPC_MESSAGE * Message)
    = I_RpcSendReceive;
RPC_STATUS (RPC_ENTRY *
            Real_I_RpcFreeBuffer)(RPC_MESSAGE * Message)
    = I_RpcFreeBuffer;
RPC_STATUS (RPC_ENTRY *
            Real_I_RpcSend)(PRPC_MESSAGE Message)
    = I_RpcSend;
RPC_STATUS (RPC_ENTRY *
            Real_I_RpcReceive)(PRPC_MESSAGE Message,
                               unsigned int Size)
    = I_RpcReceive;
RPC_STATUS (RPC_ENTRY *
            Real_I_RpcFreePipeBuffer)(RPC_MESSAGE * Message)
    = I_RpcFreePipeBuffer;
RPC_STATUS (RPC_ENTRY *
            Real_I_RpcReallocPipeBuffer)(PRPC_MESSAGE Msg,
                                         unsigned int Size)
    = I_RpcReallocPipeBuffer;
void (RPC_ENTRY *
      Real_I_RpcRequestMutex)(I_RPC_MUTEX * Mutex)
    = I_RpcRequestMutex;
void (RPC_ENTRY *
      Real_I_RpcClearMutex)(I_RPC_MUTEX Mutex)
    = I_RpcClearMutex;
void * (RPC_ENTRY *
      Real_I_RpcAllocate)(unsigned int Size)
    = I_RpcAllocate;
void (RPC_ENTRY *
      Real_I_RpcFree)(void * Object)
    = I_RpcFree;
void (RPC_ENTRY *
      Real_I_RpcPauseExecution)(unsigned long Milliseconds)
    = I_RpcPauseExecution;

#if _MSC_VER < 1300
RPC_STATUS (RPC_ENTRY *
            Real_I_RpcMonitorAssociation)(RPC_BINDING_HANDLE Handle,
                                          PRPC_RUNDOWN RundownRoutine,
                                          void * Context)
    = I_RpcMonitorAssociation;
RPC_STATUS (RPC_ENTRY *
            Real_I_RpcStopMonitorAssociation)(RPC_BINDING_HANDLE Handle)
    = I_RpcStopMonitorAssociation;
#endif

//////////////////////////////////////////////////////////////////////////////
//
static DWORD WINAPI Catch_SleepEx(DWORD dwMilliseconds, BOOL bAlertable)
{
    CRouteTime rt(E_SleepEx);
    return Real_SleepEx(dwMilliseconds, bAlertable);
}

static int WSAAPI Catch_send(SOCKET s, const char * buf, int len, int flags)
{
    CRouteTime rt(E_Send);
    return Real_send(s, buf, len, flags);
}

static NTSTATUS NTAPI Catch_NtWaitForSingleObject(HANDLE Handle,
                                                  BOOLEAN Alertable,
                                                  PLARGE_INTEGER Timeout)
{
    CRouteTime rt(E_NtWaitForSingleObject);
    if (rt.Routed()) {
        //printf("WaitForSingle(%d, %I64d)\n", Alertable, Timeout->QuadPart);
    }

    return Real_NtWaitForSingleObject(Handle, Alertable, Timeout);
}

#define IO_CONTROL_AFD_SEND_DATAGRAM        0x12023
#define IO_CONTROL_AFD_SEND                 0x1201f
#define IO_CONTROL_AFD_RECV_DATAGRAM        0x1201b
#define IO_CONTROL_AFD_RECV                 0x12017

static NTSTATUS NTAPI Catch_NtDeviceIoControlFile(HANDLE FileHandle,
                                                  HANDLE Event,
                                                  PIO_APC_ROUTINE ApcRoutine,
                                                  PVOID ApcContext,
                                                  PIO_STATUS_BLOCK IoStatusBlock,
                                                  ULONG IoControlCode,
                                                  PVOID InputBuffer,
                                                  ULONG InputBufferLength,
                                                  PVOID OutputBuffer,
                                                  ULONG OutputBufferLength)
{
    if (IoControlCode == IO_CONTROL_AFD_SEND_DATAGRAM ||
        IoControlCode == IO_CONTROL_AFD_SEND) {
        CRouteTime rt(E_NtDeviceIoControlFileSend);
        NTSTATUS NtStatus = Real_NtDeviceIoControlFile(FileHandle,
                                                       Event,
                                                       ApcRoutine,
                                                       ApcContext,
                                                       IoStatusBlock,
                                                       IoControlCode,
                                                       InputBuffer,
                                                       InputBufferLength,
                                                       OutputBuffer,
                                                       OutputBufferLength);
        if (NtStatus == STATUS_PENDING) {
            LARGE_INTEGER li;
            li.QuadPart = INFINITE;
            Real_NtWaitForSingleObject(Event, FALSE, &li);
            NtStatus = IoStatusBlock->Status;
        }
        return NtStatus;
    }
    else if (IoControlCode == IO_CONTROL_AFD_RECV_DATAGRAM ||
             IoControlCode == IO_CONTROL_AFD_RECV) {
        CRouteTime rt(E_NtDeviceIoControlFileRecv);
        return Real_NtDeviceIoControlFile(FileHandle,
                                          Event,
                                          ApcRoutine,
                                          ApcContext,
                                          IoStatusBlock,
                                          IoControlCode,
                                          InputBuffer,
                                          InputBufferLength,
                                          OutputBuffer,
                                          OutputBufferLength);
    }
    else {
        CRouteTime rt(E_NtDeviceIoControlFile);
        if (rt.Routed()) {
            printf("IoControlCode: %08lx\n", IoControlCode);
            __debugbreak();
        }
        return Real_NtDeviceIoControlFile(FileHandle,
                                          Event,
                                          ApcRoutine,
                                          ApcContext,
                                          IoStatusBlock,
                                          IoControlCode,
                                          InputBuffer,
                                          InputBufferLength,
                                          OutputBuffer,
                                          OutputBufferLength);
    }
}


static RPC_STATUS RPC_ENTRY Catch_I_RpcGetBuffer(RPC_MESSAGE * Message)
{
    CRouteTime rt(E_I_RpcGetBuffer);
    return Real_I_RpcGetBuffer(Message);
}

static RPC_STATUS RPC_ENTRY Catch_I_RpcSendReceive(RPC_MESSAGE * Message)
{
    CRouteTime rt(E_I_RpcSendReceive);
    return Real_I_RpcSendReceive(Message);
}

static RPC_STATUS RPC_ENTRY Catch_I_RpcFreeBuffer(RPC_MESSAGE * Message)
{
    CRouteTime rt(E_I_RpcFreeBuffer);
    return Real_I_RpcFreeBuffer(Message);
}

static RPC_STATUS RPC_ENTRY Catch_I_RpcSend(PRPC_MESSAGE Message)
{
    CRouteTime rt(E_I_RpcSend);
    return Real_I_RpcSend(Message);
}

static RPC_STATUS RPC_ENTRY Catch_I_RpcReceive(PRPC_MESSAGE Message, unsigned int Size)
{
    CRouteTime rt(E_I_RpcReceive);
    return Real_I_RpcReceive(Message, Size);
}

static RPC_STATUS RPC_ENTRY Catch_I_RpcFreePipeBuffer(RPC_MESSAGE * Message)
{
    CRouteTime rt(E_I_RpcFreePipeBuffer);
    return Real_I_RpcFreePipeBuffer(Message);
}

static RPC_STATUS RPC_ENTRY Catch_I_RpcReallocPipeBuffer(PRPC_MESSAGE Message,
                                                         unsigned int NewSize)
{
    CRouteTime rt(E_I_RpcReallocPipeBuffer);
    return Real_I_RpcReallocPipeBuffer(Message, NewSize);
}

static void RPC_ENTRY Catch_I_RpcRequestMutex(I_RPC_MUTEX * Mutex)
{
    CRouteTime rt(E_I_RpcRequestMutex);
    Real_I_RpcRequestMutex(Mutex);
}

static void RPC_ENTRY Catch_I_RpcClearMutex(I_RPC_MUTEX Mutex)
{
    CRouteTime rt(E_I_RpcClearMutex);
    Real_I_RpcClearMutex(Mutex);
}

static void * RPC_ENTRY Catch_I_RpcAllocate(unsigned int Size)
{
    CRouteTime rt(E_I_RpcAllocate);
    return Real_I_RpcAllocate(Size);
}

static void RPC_ENTRY Catch_I_RpcFree(void * Object)
{
    CRouteTime rt(E_I_RpcFree);
    Real_I_RpcFree(Object);
}

static void RPC_ENTRY Catch_I_RpcPauseExecution(unsigned long Milliseconds)
{
    CRouteTime rt(E_I_RpcPauseExecution);
    Real_I_RpcPauseExecution(Milliseconds);
}

#if _MSC_VER < 1300
static RPC_STATUS RPC_ENTRY Catch_I_RpcMonitorAssociation(RPC_BINDING_HANDLE Handle,
                                                          PRPC_RUNDOWN RundownRoutine,
                                                          void * Context)
{
    CRouteTime rt(E_I_RpcMonitorAssociation);
    return Real_I_RpcMonitorAssociation(Handle, RundownRoutine, Context);
}

static RPC_STATUS RPC_ENTRY Catch_I_RpcStopMonitorAssociation(RPC_BINDING_HANDLE Handle)
{
    CRouteTime rt(E_I_RpcStopMonitorAssociation);
    return Real_I_RpcStopMonitorAssociation(Handle);
}
#endif

static STDMETHODIMP Catch_IPing_Ping(IPing *pip)
{
    HRESULT hr;
    InterlockedIncrement(&s_nInCall);
    {
        CRouteTime rt(E_Proxy);
        hr = pip->Ping();
    }
    InterlockedDecrement(&s_nInCall);
    return hr;
}

static STDMETHODIMP Catch_IPing_PingToServer(IPing *pip, LPSTR pszString)
{
    HRESULT hr;
    InterlockedIncrement(&s_nInCall);
    {
        CRouteTime rt(E_Proxy);
        hr = pip->PingToServer(pszString);
    }
    InterlockedDecrement(&s_nInCall);
    return hr;
}

static STDMETHODIMP Catch_IPing_PingToClient(IPing *pip, LPSTR *ppszString)
{
    HRESULT hr;
    InterlockedIncrement(&s_nInCall);
    {
        CRouteTime rt(E_Proxy);
        hr = pip->PingToClient(ppszString);
    }
    InterlockedDecrement(&s_nInCall);
    return hr;
}

//////////////////////////////////////////////////////////////////////////////
//
LONG RerouteEntryPoints(VOID)
{
    Real_NtWaitForSingleObject
        = ((NTSTATUS (NTAPI *)(HANDLE,
                               BOOLEAN,
                               PLARGE_INTEGER))
           DetourFindFunction("ntdll.dll", "NtWaitForSingleObject"));

    Real_NtDeviceIoControlFile
        = ((NTSTATUS (NTAPI *)(HANDLE,
                               HANDLE,
                               PIO_APC_ROUTINE,
                               PVOID,
                               PIO_STATUS_BLOCK,
                               ULONG,
                               PVOID,
                               ULONG,
                               PVOID,
                               ULONG))
           DetourFindFunction("ntdll.dll", "NtDeviceIoControlFile"));

    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());

    DetourAttach(&(PVOID&)Real_NtWaitForSingleObject,
                 Catch_NtWaitForSingleObject);
    DetourAttach(&(PVOID&)Real_NtDeviceIoControlFile,
                 Catch_NtDeviceIoControlFile);

    DetourAttach(&(PVOID&)Real_SleepEx,
                 Catch_SleepEx);
    DetourAttach(&(PVOID&)Real_send,
                 Catch_send);
    DetourAttach(&(PVOID&)Real_I_RpcGetBuffer,
                 Catch_I_RpcGetBuffer);
    DetourAttach(&(PVOID&)Real_I_RpcSendReceive,
                 Catch_I_RpcSendReceive);
    DetourAttach(&(PVOID&)Real_I_RpcFreeBuffer,
                 Catch_I_RpcFreeBuffer);
    DetourAttach(&(PVOID&)Real_I_RpcSend,
                 Catch_I_RpcSend);
    DetourAttach(&(PVOID&)Real_I_RpcReceive,
                 Catch_I_RpcReceive);
    DetourAttach(&(PVOID&)Real_I_RpcFreePipeBuffer,
                 Catch_I_RpcFreePipeBuffer);
    DetourAttach(&(PVOID&)Real_I_RpcReallocPipeBuffer,
                 Catch_I_RpcReallocPipeBuffer);
    DetourAttach(&(PVOID&)Real_I_RpcRequestMutex,
                 Catch_I_RpcRequestMutex);
    DetourAttach(&(PVOID&)Real_I_RpcClearMutex,
                 Catch_I_RpcClearMutex);
    DetourAttach(&(PVOID&)Real_I_RpcAllocate,
                 Catch_I_RpcAllocate);
    DetourAttach(&(PVOID&)Real_I_RpcFree,
                 Catch_I_RpcFree);
    DetourAttach(&(PVOID&)Real_I_RpcPauseExecution,
                 Catch_I_RpcPauseExecution);
#if _MSC_VER < 1300
    DetourAttach(&(PVOID&)Real_I_RpcMonitorAssociation,
                 Catch_I_RpcMonitorAssociation);
    DetourAttach(&(PVOID&)Real_I_RpcStopMonitorAssociation,
                 Catch_I_RpcStopMonitorAssociation);
#endif

    return DetourTransactionCommit();
}

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////// Classes.

class CNetPingFactory : public IClassFactory
{
  public:
    CNetPingFactory();
    ~CNetPingFactory();

    // IUnknown
    STDMETHODIMP    QueryInterface(REFIID riid, void** ppv);
    STDMETHODIMP_(ULONG) AddRef(void);
    STDMETHODIMP_(ULONG) Release(void);

    // IClassFactory
    STDMETHODIMP    CreateInstance(LPUNKNOWN punkOuter, REFIID iid, void **ppv);
    STDMETHODIMP    LockServer(BOOL fLock);

  public:
    static HRESULT  InitSystem(VOID);
    static HRESULT  FiniSystem(VOID);

    static HRESULT  InitObject(VOID);
    static HRESULT  FiniObject(VOID);

    static HRESULT  Lock(BOOL fLock);

    static HRESULT  Wait(VOID);

  private:
    LONG            m_cRef;

    static HANDLE   s_hevtDone;
    static LONG     s_nObjects;
    static LONG     s_nLocks;
};

class CNetPingObject : public IPing
{
  public:
    CNetPingObject();
    ~CNetPingObject();

    // IUnknown
    STDMETHODIMP    QueryInterface(REFIID iid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef(void);
    STDMETHODIMP_(ULONG) Release(void);

    // IPing
    STDMETHODIMP    Ping();
    STDMETHODIMP    PingToServer(LPSTR pszString);
    STDMETHODIMP    PingToClient(LPSTR *ppszString);
    STDMETHODIMP    PingToClientSize(ULONG cbOut);

  private:
    LONG        m_cRef;
    ULONG       m_cbLast;
    ULONG       m_cbOut;
};

/////////////////////////////////////////////////////////////////////// GUIDs.

DEFINE_GUID(CLSID_NetPingObject,
            0xdecdbeed, 0xd1ac, 0x11d1, 0x96, 0xbc, 0x00, 0xaa, 0x00, 0x57, 0x3f, 0xb0);

/////////////////////////////////////////////////////////// Initialize String.
//
void InitializeString(LPSTR pszString, LONG cbSize)
{
    ASSERT(cbSize >= 1);

    while (cbSize-- > 1) {
        *pszString++ = '+';
    }
    *pszString = '\0';
}

BOOL GetKeyValue(HKEY hRootKey, PWCHAR pwzKey, PWCHAR pwzValueName, PWCHAR pwzValue,
                 DWORD cbValue)
{
    HKEY        hKey;
    WCHAR       wzKey[256];
    HRESULT     hr;

    hr = StringCchCopyW(wzKey, ARRAYSIZE(wzKey), pwzKey);
    if (FAILED(hr)) {
        return FALSE;
    }

    if (RegOpenKeyExW(hRootKey, wzKey, 0, KEY_READ, &hKey) != NO_ERROR) {
      abort:
        pwzValue[0] = '\0';
        return FALSE;
    }

    DWORD nType = 0;
    cbValue -= sizeof(WCHAR);
    if (RegQueryValueExW(hKey, pwzValueName, 0, &nType, (PBYTE)pwzValue, &cbValue)
        != NO_ERROR || nType != REG_SZ) {

        RegCloseKey(hKey);
        goto abort;
    }

    RegCloseKey(hKey);

    cbValue /= sizeof(WCHAR);
    pwzValue[cbValue] = L'\0';

    return TRUE;
}

static BOOLEAN SetKeyAndValue(HKEY hRootKey,
                              PWCHAR pwzKey, PWCHAR pwzSubkey,
                              PWCHAR pwzValueName, PWCHAR pwzValue)
{
    HKEY        hKey;
    WCHAR       wzKey[256];
    HRESULT     hr;

    hr = StringCchCopyW(wzKey, ARRAYSIZE(wzKey), pwzKey);
    if (FAILED(hr)) {
        return FALSE;
    }

    if (pwzSubkey != NULL) {
        hr = StringCchCatW(wzKey, ARRAYSIZE(wzKey), L"\\");
        if (FAILED(hr)) {
            return FALSE;
        }
        hr = StringCchCatW(wzKey, ARRAYSIZE(wzKey), pwzSubkey);
        if (FAILED(hr)) {
            return FALSE;
        }
    }

    if (RegCreateKeyExW(hRootKey, wzKey, 0, NULL, REG_OPTION_NON_VOLATILE,
                        KEY_ALL_ACCESS, NULL, &hKey, NULL) != ERROR_SUCCESS) {
        return FALSE;
    }

    if (pwzValue != NULL) {
        RegSetValueExW(hKey, pwzValueName, 0, REG_SZ,
                       (BYTE *)pwzValue, (DWORD)wcssize(pwzValue));
    }
    RegCloseKey(hKey);
    return TRUE;
}

static BOOLEAN SetKeyAndValue(HKEY hRootKey,
                              PWCHAR pwzKey, PWCHAR pwzSubkey,
                              PWCHAR pwzValueName,
                              PBYTE pbData, ULONG cbData)
{
    HKEY        hKey;
    WCHAR       wzKey[256];
    HRESULT     hr;

    hr = StringCchCopyW(wzKey, ARRAYSIZE(wzKey), pwzKey);
    if (FAILED(hr)) {
        return FALSE;
    }

    if (pwzSubkey != NULL) {
        hr = StringCchCatW(wzKey, ARRAYSIZE(wzKey), L"\\");
        if (FAILED(hr)) {
            return FALSE;
        }
        hr = StringCchCatW(wzKey, ARRAYSIZE(wzKey), pwzSubkey);
        if (FAILED(hr)) {
            return FALSE;
        }
    }

    if (RegCreateKeyExW(hRootKey, wzKey, 0, NULL, REG_OPTION_NON_VOLATILE,
                        KEY_ALL_ACCESS, NULL, &hKey, NULL) != ERROR_SUCCESS) {
        return FALSE;
    }

    if (pbData != NULL) {
        RegSetValueExW(hKey, pwzValueName, 0, REG_BINARY, pbData, cbData);
    }
    RegCloseKey(hKey);
    return TRUE;
}

static void Register(void)
{

    WCHAR wzModule[256];
    WCHAR wzName[256];
    WCHAR wzValue[256];
    WCHAR wzClass[48];
    WCHAR wzKey[256];
    PWCHAR pwz;
    HRESULT hr;

    BYTE rgEveryone[] = {
        0x01,0x00,0x04,0x80,0x34,0x00,0x00,0x00,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x14,0x00,0x00,0x00,0x02,0x00,0x20,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x18,0x00,
        0x01,0x00,0x00,0x00,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x01,0x05,0x00,0x00,0x00,0x00,0x00,0x05,0x15,0x00,0x00,0x00,
        0xa0,0x65,0xcf,0x7e,0x78,0x4b,0x9b,0x5f,0xe7,0x7c,0x87,0x70,0x32,0x7f,0x00,0x00,
        0x01,0x05,0x00,0x00,0x00,0x00,0x00,0x05,0x15,0x00,0x00,0x00,0xa0,0x65,0xcf,0x7e,
        0x78,0x4b,0x9b,0x5f,0xe7,0x7c,0x87,0x70,0x32,0x7f,0x00,0x00
    };

    GetModuleFileNameW(NULL, wzModule, sizeof(wzModule)/sizeof(WCHAR));
    if ((pwz = wcsrchr(wzModule, '\\')) != NULL) {
        hr = StringCchCopyW(wzName, ARRAYSIZE(wzName), pwz + 1);
    }
    else if ((pwz = wcsrchr(wzModule, ':')) != NULL) {
        hr = StringCchCopyW(wzName, ARRAYSIZE(wzName), pwz + 1);
    }
    else {
        hr = StringCchCopyW(wzName, ARRAYSIZE(wzName), wzModule);
    }
    CheckResult(hr, "IPing_DllRegisterServer");

    // printf("Server: %ls / %ls\n", wzModule, wzName);

    StringFromGUID2(CLSID_NetPingObject, wzClass, ARRAYSIZE(wzClass));
    // printf("  Class: %ls\n", wzClass);
    hr = StringCchCopyW(wzKey, ARRAYSIZE(wzKey), L"CLSID\\");
    CheckResult(hr, "IPing_DllRegisterServer");
    hr = StringCchCatW(wzKey, ARRAYSIZE(wzKey), wzClass);
    CheckResult(hr, "IPing_DllRegisterServer");

    SetKeyAndValue(HKEY_CLASSES_ROOT, wzKey, NULL, NULL, L"COM Ping Network Server");
    hr = StringCchPrintfW(wzValue, ARRAYSIZE(wzValue), L"%ls /s", wzModule);
    CheckResult(hr, "IPing_DllRegisterServer");
    SetKeyAndValue(HKEY_CLASSES_ROOT, wzKey, L"LocalServer32", NULL, wzValue);
    SetKeyAndValue(HKEY_CLASSES_ROOT, wzKey, L"LaunchPermission", NULL, L"Y");
    SetKeyAndValue(HKEY_CLASSES_ROOT, wzKey, NULL, L"AppID", wzClass);

    hr = StringCchCopyW(wzKey, ARRAYSIZE(wzKey), L"AppID\\");
    CheckResult(hr, "IPing_DllRegisterServer");
    hr = StringCchCatW(wzKey, ARRAYSIZE(wzKey), wzClass);
    CheckResult(hr, "IPing_DllRegisterServer");
    SetKeyAndValue(HKEY_CLASSES_ROOT, wzKey, NULL, NULL, L"COM Ping Network Server");
    SetKeyAndValue(HKEY_CLASSES_ROOT, wzKey, NULL, L"RunAs", L"Interactive User");
    SetKeyAndValue(HKEY_CLASSES_ROOT, wzKey, NULL, L"AccessPermission",
                   rgEveryone, sizeof(rgEveryone));

    hr = StringCchCopyW(wzKey, ARRAYSIZE(wzKey), L"AppID\\");
    CheckResult(hr, "IPing_DllRegisterServer");
    hr = StringCchCatW(wzKey, ARRAYSIZE(wzKey), wzName);
    CheckResult(hr, "IPing_DllRegisterServer");

    SetKeyAndValue(HKEY_CLASSES_ROOT, wzKey, NULL, L"AppID", wzClass);

    /////////////////////////////////////////////////// Register Proxy & Stub.
    //
    iping_DllRegisterServer();
    CheckResult(hr, "IPing_DllRegisterServer");

    //////////////////////////////////////////////// Register Processor Speed.
    //
    DWORD   cycles = 0;

    hr = StringCchCopyW(wzKey, ARRAYSIZE(wzKey), L"Software\\Microsoft\\Detours\\ProcessorCycles");
    CheckResult(hr, "IPing_DllRegisterServer");

    if (GetKeyValue(HKEY_LOCAL_MACHINE, wzKey, NULL, wzValue, sizeof(wzValue))) {
        cycles = _wtoi(wzValue);

        printf("[Recorded Cycles/Second: %ld]\n", cycles);
    }

    if (cycles  < 10000) {
        LONGLONG llBeg;
        LONGLONG llEnd;

        printf("[Calibrating Processors...]\r");

        LARGE_INTEGER liBeg;
        LARGE_INTEGER liEnd;
        LARGE_INTEGER liBrk;
        LARGE_INTEGER liFrq;

        QueryPerformanceFrequency(&liFrq);
        QueryPerformanceCounter(&liBeg);
        llBeg = GetTimeStamp();
        liBrk.QuadPart = liBeg.QuadPart + liFrq.QuadPart * 5;
        do {
            QueryPerformanceCounter(&liEnd);
            llEnd = GetTimeStamp();
        } while (liEnd.QuadPart < liBrk.QuadPart);

        double secs = (double)(liEnd.QuadPart - liBeg.QuadPart) / (double)liFrq.QuadPart;
        double clks = (double)(llEnd - llBeg);
        double cycs = clks / secs;

        cycles = (DWORD)cycs;
        printf("[Measured Cycles/Second: %ld]          \n", cycles);

        hr = StringCchPrintfW(wzValue, ARRAYSIZE(wzValue), L"%d", cycles);
        CheckResult(hr, "IPing_DllRegisterServer");

        SetKeyAndValue(HKEY_LOCAL_MACHINE, wzKey, NULL, NULL, wzValue);
    }
}

void Unregister(void)
{
    ///////////////////////////////////////////////// Unregister Proxy & Stub.
    //
    HRESULT hr = iping_DllUnregisterServer();
    if (FAILED(hr)) {
        CheckResult(hr, "IPing_DllUnregisterServer");
    }
}

//////////////////////////////////////////////////////////////////////////////
//
HRESULT GetClockInfo(LONGLONG *pllCyclesPerSecond)
{
    WCHAR   wzKey[512];
    WCHAR   wzValue[128];
    LONG    cbValue;
    HRESULT hr;

    ////////////////////////////////////////////////////////// Check Registry.

    cbValue = sizeof(wzValue);
    hr = StringCchCopyW(wzKey, ARRAYSIZE(wzKey), L"Software\\Microsoft\\Detours\\ProcessorCycles");
    CheckResult(hr, "GetClockInfo");

    if (RegQueryValueW(HKEY_LOCAL_MACHINE, wzKey, wzValue, &cbValue) == NO_ERROR) {
        *pllCyclesPerSecond = _wtoi(wzValue);
        return S_OK;
    }
    *pllCyclesPerSecond = 1000000;
    return E_FAIL;
}

///////////////////////////////////////////////////////// CNetPingFactory.
//
LONG    CNetPingFactory::s_nObjects = 0;
LONG    CNetPingFactory::s_nLocks = 0;
HANDLE  CNetPingFactory::s_hevtDone = NULL;

CNetPingFactory::CNetPingFactory()
{
    m_cRef = 1;
}


CNetPingFactory::~CNetPingFactory()
{
    m_cRef = 0;
}

ULONG CNetPingFactory::AddRef(void)
{
    return InterlockedIncrement(&m_cRef);
}

ULONG CNetPingFactory::Release(void)
{
    if (InterlockedDecrement(&m_cRef) == 0) {
        delete this;
        return 0;
    }
    return 1;
}

HRESULT CNetPingFactory::InitSystem(VOID)
{
    s_nObjects = 0;
    s_nLocks = 0;

    s_hevtDone = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (s_hevtDone == NULL) {
        HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
        CheckResult(hr, "Server: CreateEvent");
        exit(hr);
    }

    return S_OK;
}

HRESULT CNetPingFactory::FiniSystem(VOID)
{
    if (s_hevtDone != NULL) {
        CloseHandle(s_hevtDone);
        s_hevtDone = NULL;
    }
    return S_OK;
}


HRESULT CNetPingFactory::InitObject(VOID)
{
    InterlockedIncrement(&s_nObjects);
    return S_OK;
}

HRESULT CNetPingFactory::FiniObject(VOID)
{
    if (InterlockedDecrement(&s_nObjects) == 0 && s_nLocks == 0)
        SetEvent(s_hevtDone);
    return S_OK;
}

HRESULT CNetPingFactory::Lock(BOOL fLock)
{
    if (fLock) {
        InterlockedIncrement(&s_nLocks);
    }

    else {
        if (InterlockedDecrement(&s_nLocks) == 0 && s_nObjects == 0)
            SetEvent(s_hevtDone);
    }
    return S_OK;
}

HRESULT CNetPingFactory::Wait(VOID)
{
    DWORD dwWaitResult;
    MSG msg;

    for (;;) {
        dwWaitResult = MsgWaitForMultipleObjects(1, &s_hevtDone,
                                                 FALSE, INFINITE,
                                                 QS_ALLINPUT);

        if (dwWaitResult == WAIT_OBJECT_0) {
            ResetEvent(s_hevtDone);
            break;
        }

        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return S_OK;
}

STDMETHODIMP CNetPingFactory::QueryInterface(REFIID riid, void** ppv)
{
    if (ppv == NULL) {
        return E_INVALIDARG;
    }
    if (riid == IID_IClassFactory || riid == IID_IUnknown) {
        *ppv = (IClassFactory *) this;
        AddRef();
        return S_OK;
    }
    *ppv = NULL;
    return E_NOINTERFACE;
}

HRESULT CNetPingFactory::LockServer (BOOL fLock)
{
    return Lock(fLock);
}

STDMETHODIMP CNetPingFactory::CreateInstance(LPUNKNOWN punkOuter,
                                             REFIID riid, void** ppv)
{
    LPUNKNOWN   punk;
    HRESULT     hr;

    *ppv = NULL;

    if (punkOuter != NULL) {
        return CLASS_E_NOAGGREGATION;
    }

    printf("  Server: IClassFactory:CreateInstance\n");

    punk = new CNetPingObject;
    if (punk == NULL) {
        return E_OUTOFMEMORY;
    }

    hr = punk->QueryInterface(riid, ppv);
    punk->Release();
    return hr;
}

/////////////////////////////////////////////////////////////// CNetPingObject.
//
CNetPingObject::CNetPingObject()
{
    m_cRef = 1;
    m_cbLast = ~0u;
    m_cbOut = 2;
    CNetPingFactory::InitObject();
}

CNetPingObject::~CNetPingObject()
{
    CNetPingFactory::FiniObject();
}

STDMETHODIMP CNetPingObject::QueryInterface(REFIID riid, void** ppv)
{
    if (ppv == NULL) {
        return E_INVALIDARG;
    }
    if (riid == IID_IUnknown || riid == IID_IPing) {
        *ppv = (IPing *) this;
        AddRef();
        return S_OK;
    }
    *ppv = NULL;
    return E_NOINTERFACE;
}

STDMETHODIMP_(ULONG) CNetPingObject::AddRef(void)
{
    return InterlockedIncrement(&m_cRef);
}

STDMETHODIMP_(ULONG) CNetPingObject::Release(void)
{
    if (InterlockedDecrement(&m_cRef) == 0) {
        delete this;
        return 0;
    }
    return 1;
}

STDMETHODIMP CNetPingObject::Ping()
{
    return S_OK;
}

STDMETHODIMP CNetPingObject::PingToServer(LPSTR pszString)
{
    (void)pszString;
    return S_OK;
}

STDMETHODIMP CNetPingObject::PingToClient(LPSTR *ppszString)
{
    LPSTR pszString = (LPSTR)CoTaskMemAlloc(m_cbOut);
    if (pszString == NULL) {
        return E_OUTOFMEMORY;
    }

    CopyMemory(pszString, g_pBuffer, m_cbOut);
    *ppszString = pszString;

    return S_OK;
}

STDMETHODIMP CNetPingObject::PingToClientSize(ULONG cbOut)
{
    if (cbOut < 1) {
        return E_INVALIDARG;
    }

    InitializeString((LPSTR)g_pBuffer, cbOut);
    m_cbOut = cbOut;
    return S_OK;
}

//////////////////////////////////////////////////////////////////////////////
//
class CSampleRecord
{
  public:
    DOUBLE      m_dTime;
    FILETIME    m_nWhen;
    LONG        m_cbToClient;
    LONG        m_cbToServer;
    DOUBLE      m_dDcom;
    DOUBLE      m_dRpc;
    DOUBLE      m_dUdp;
    DOUBLE      m_dNet;

  protected:
    static LONG s_cbToClient;
    static LONG s_cbToServer;

  public:
    CSampleRecord();
    CSampleRecord(IPing *pIPing, LONG cbToClient, LONG cbToServer);

    HRESULT     Measure(IPing *pIPing, LONG cbToClient, LONG cbToServer);
    HRESULT     Write();

    double      GetTime()           { return m_dTime; }
    FILETIME    GetWhen()           { return m_nWhen; }
    LONG        GetToClient()       { return m_cbToClient; }
    LONG        GetToServer()       { return m_cbToServer; }

};

//////////////////////////////////////////////////////////////////////////////
//
LONG CSampleRecord::s_cbToClient = 0;
LONG CSampleRecord::s_cbToServer = 0;

//////////////////////////////////////////////////////////////////////////////
//
CSampleRecord::CSampleRecord()
{
    m_dTime = 0;
    m_dDcom = 0;
    m_dRpc = 0;
    m_dUdp = 0;
    m_dNet = 0;
}

CSampleRecord::CSampleRecord(IPing *pIPing, LONG cbToClient, LONG cbToServer)
{
    Measure(pIPing, cbToClient, cbToServer);
}

HRESULT CSampleRecord::Measure(IPing *pIPing, LONG cbToClient, LONG cbToServer)
{
    HRESULT hr;
    LONGLONG llBeg;
    LONGLONG llEnd;

    GetSystemTimeAsFileTime(&m_nWhen);
    m_cbToClient = cbToClient;
    m_cbToServer = cbToServer;

    if (cbToClient == 0 && cbToServer == 0) {
        llBeg = GetTimeStamp();
        hr = Catch_IPing_Ping(pIPing);
        llEnd = GetTimeStamp();
    }
    else if (cbToClient) {
        if (s_cbToClient != cbToClient) {
            hr = pIPing->PingToClientSize(cbToClient);
            s_cbToClient = cbToClient;
        }

        LPSTR pszString = NULL;

        llBeg = GetTimeStamp();
        hr = Catch_IPing_PingToClient(pIPing, &pszString);
        llEnd = GetTimeStamp();

        if (pszString) {
            LONG cb = (LONG)strlen(pszString) + 1;
            ASSERT(cb == cbToClient);
            CoTaskMemFree(pszString);
            pszString = NULL;
        }
    }
    else {
        if (s_cbToServer != cbToServer) {
            InitializeString((LPSTR)g_pBuffer, cbToServer);
            s_cbToServer = cbToServer;
        }

        llBeg = GetTimeStamp();
        hr = Catch_IPing_PingToServer(pIPing, (LPSTR)g_pBuffer);
        llEnd = GetTimeStamp();
    }

    if (FAILED(hr)) {
        printf(";; Operation failed: %08lx\n", hr);
        exit(999);
    }

    if (g_fSummarize) {
        SummarizeCycles();
        m_dDcom = (double)s_rllCycles[E_DCOM] * g_dMsPerCycle;
        m_dRpc = (double)s_rllCycles[E_RPC] * g_dMsPerCycle;
        m_dUdp = (double)s_rllCycles[E_UDP] * g_dMsPerCycle;
        m_dNet = (double)s_rllCycles[E_NET] * g_dMsPerCycle;
    }

    m_dTime = (double)(llEnd - llBeg) * g_dMsPerCycle;

    return S_OK;
}

HRESULT CSampleRecord::Write()
{
    SYSTEMTIME  st;
    FILETIME ft;

    FileTimeToLocalFileTime(&m_nWhen, &ft);
    FileTimeToSystemTime(&ft, &st);

    printf("%02d/%02d %2d:%02d:%02d %6ld %ld %6.3f [ %6.3f %6.3f %6.3f %6.3f ]\n",
           st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond,
           m_cbToClient, m_cbToServer, m_dTime,
           m_dDcom, m_dRpc, m_dUdp, m_dNet);

    return S_OK;
}

//////////////////////////////////////////////////////////////////////////////
//
double NetTest(HKEY hNetwork, IPing *pIPing,
               BOOLEAN fToClient, LONG cbPacket, LONG nCount)
{
    //////////////////////////////////////////////////////////////////// ToClient.
    //
    HRESULT hr;
    double msAvg = 0.0;
    double msMin = 1.0e12;
    double msMax = 0.0;
    ULONG nMax = 999;
    ULONG nMin = 999;

    if (fToClient) {
        printf(">Client %6ld %6ld ", cbPacket, nCount);
    }
    else {
        printf(">Server %6ld %6ld ", cbPacket, nCount);
    }

    for (LONG n = 0; n < nCount; n++) {
        double ms;

        if (fToClient) {
            ms = CSampleRecord(pIPing, cbPacket, 0).GetTime();
        }
        else {
            ms = CSampleRecord(pIPing, 0, cbPacket).GetTime();
        }

        if (ms < 0) {
            break;
        }

        if (msMin > ms) {
            msMin = ms;
            nMin = n;
        }
        if (msMax < ms) {
            msMax = ms;
            nMax = n;
        }
        msAvg += ms;
    }

    if (nCount) {
        msAvg /= nCount;
    }

    if (cbPacket == 0) {
        g_dLatency = msMin;
    }

    double mbps = (double)cbPacket / msMin;
    mbps *= 8.0 * 1000.0 / 1024.0 / 1024.0;

    double mbps2 = (double)cbPacket / (msMin - g_dLatency);
    mbps2 *= 8.0 * 1000.0 / 1024.0 / 1024.0;
    if (cbPacket == 0) {
        mbps2 = 0;
    }

    if (hNetwork != NULL) {
        WCHAR wzKey[64];
        WCHAR wzLatency[64];

        if (fToClient) {
            hr = StringCchPrintfW(wzKey, ARRAYSIZE(wzKey), L"ToClient\\%d", cbPacket);
            CheckResult(hr, "NetTest");
        }
        else {
            hr = StringCchPrintfW(wzKey, ARRAYSIZE(wzKey), L"ToServer\\%d", cbPacket);
            CheckResult(hr, "NetTest");
        }
        hr = StringCchPrintfW(wzLatency, ARRAYSIZE(wzLatency), L"%I64d", msAvg);
        CheckResult(hr, "NetTest");

        RegSetValueW(hNetwork, wzKey, REG_SZ, wzLatency, (DWORD)wcssize(wzLatency));
    }

    printf("%8.3f %8.3f %8.3f %9.4f %8.3f %9.4f%3ld\n",
           msMin,
           msAvg,
           msMax,
           mbps,
           msMin - g_dLatency,
           mbps2,
           nMax);
    return mbps;
}

//////////////////////////////////////////////////////////////////////// main.

static WCHAR    wzServers[32][64];
static int      nServers = 0;

void Sample_Fixed(IPing *pIPing)
{
    CSampleRecord csrRecords[512];
    LONG nRecords = 0;
    HRESULT hr;

    double dAvg = 0;
    double dMin = 500000.0;
    double dMax = 0.0;
    double dMinDcom = dMin;
    double dMinRpc = dMin;
    double dMinUdp = dMin;
    double dMinNet = dMin;

    for (int i = 0; i < 512; i++) {
        CSampleRecord& csr = csrRecords[nRecords++];

        hr = csr.Measure(pIPing, g_nFixedToClient, g_nFixedToServer);
        double d = csr.GetTime();
        if (dMin > d) {
            dMin = d;
        }
        if (dMax < d) {
            dMax = d;
        }
        if (dMinDcom > csr.m_dDcom) {
            dMinDcom = csr.m_dDcom;
        }
        if (dMinRpc > csr.m_dRpc) {
            dMinRpc = csr.m_dRpc;
        }
        if (dMinUdp > csr.m_dUdp) {
            dMinUdp = csr.m_dUdp;
        }
        if (dMinNet > csr.m_dNet) {
            dMinNet = csr.m_dNet;
        }
        dAvg += d;
    }

    dAvg /= 512;
    printf("size: %ld, min: %.3f, max: %.3f avg: %.3f [ %8.3f %8.3f %8.3f %8.3f ]\n",
           g_nFixedToClient, dMin, dMax, dAvg, dMinDcom, dMinRpc, dMinUdp, dMinNet);
    for (int n = 0; n < nRecords; n++) {
        csrRecords[n].Write();
    }
}

void Sample_Simple(IPing *pIPing)
{
    CSampleRecord csrRecords[512];
    LONG nRecords = 0;
    HRESULT hr;

    for (int cb = 0; cb < 64000; cb = cb ? cb << 1 : 32) {
        double n[5];
        int i = 0;

        for (; i < 5; i++) {
            CSampleRecord& csr = csrRecords[nRecords++];

            hr = csr.Measure(pIPing, cb, 0);
            n[i] = csr.GetTime();
        }

        double nAvg = 0;
        double nApx = 0;
        double nMin = n[0];
        double nMax = n[0];

        for (i = 0; i < 5; i++) {
            if (nMin > n[i]) {
                nMin = n[i];
            }
            if (nMax < n[i]) {
                nMax = n[i];
            }
            nAvg += n[i];
        }
        nApx = nAvg - nMax;
        nAvg /= 5;
        nApx /= 4;
        printf("min: %8.3f ms (%6d) %7.3f%7.3f%7.3f%7.3f%7.3f:%8.3f%8.3f\n",
               nMin, cb, n[0], n[1], n[2], n[3], n[4], nAvg, nApx);
    }
    for (int n = 0; n < nRecords; n++) {
        csrRecords[n].Write();
    }
}

void Sample_More(IPing *pIPing)
{
    CSampleRecord csrRecords[64];
    LONG nRecords = 0;

    for (int cb = 0; cb < 64000; cb = cb ? cb << 1 : 32) {
        int i = 0;
        for (; i < 64; i++) {
            CSampleRecord& csr = csrRecords[nRecords++];

            csr.Measure(pIPing, cb, 0);
        }

        double nAvg = 0;
        double nMin = csrRecords[0].GetTime();
        double nMax = csrRecords[0].GetTime();

        for (i = 0; i < 64; i++) {
            double n = csrRecords[i].GetTime();

            if (nMin > n) {
                nMin = n;
            }
            if (nMax < n) {
                nMax = n;
            }
            nAvg += n;
        }
        nAvg /= i;
        printf("min: %8.3f ms (%6d) : %8.3f %8.3f\n",
               nMin, cb, nMax, nAvg);

        for (int n = 0; n < nRecords; n++) {
            csrRecords[n].Write();
        }
        nRecords = 0;
    }
}

void Sample_Less(IPing *pIPing)
{
    CSampleRecord csrRecords[16];
    LONG nRecords = 0;

    for (int cb = 0; cb < 64000; cb = cb ? cb << 1 : 16) {
        int i = 0;
        for (; i < 16; i++) {
            CSampleRecord& csr = csrRecords[nRecords++];

            csr.Measure(pIPing, cb, 0);
        }

        double nAvg = 0;
        double nMin = csrRecords[0].GetTime();
        double nMax = csrRecords[0].GetTime();

        for (i = 0; i < 16; i++) {
            double n = csrRecords[i].GetTime();

            if (nMin > n) {
                nMin = n;
            }
            if (nMax < n) {
                nMax = n;
            }
            nAvg += n;
        }
        nAvg /= i;
        printf("min: %8.3f ms (%6d) : %8.3f %8.3f\n",
               nMin, cb, nMax, nAvg);

        for (int n = 0; n < nRecords; n++) {
            csrRecords[n].Write();
        }
        nRecords = 0;
    }
}

void Sample_Profile(IPing *pIPing)
{
    CSampleRecord csrRecords[64];

    double dbZero = 0;

    printf("\nPacket_Size_ Min_Latency Max_Latency Avg_Latency "
           "Relative_Bnd ___Bandwidth\n");

    for (int cb = 0; cb < 256 * 1024;) {
        int n = 0;
        for (; n < 64; n++) {
            CSampleRecord& csr = csrRecords[n];
            csr.Measure(pIPing, cb, 0);
        }

        double dbAvg = 0;
        double dbMin = csrRecords[0].GetTime();
        double dbMax = csrRecords[0].GetTime();
        LONG nMin = 0;
        LONG nMax = 0;

        for (n = 0; n < 64; n++) {
            double db = csrRecords[n].GetTime();

            if (dbMin > db) {
                dbMin = db;
                nMin = n;
            }
            if (dbMax < db) {
                dbMax = db;
                nMax = n;
            }
            dbAvg += db;
        }
        dbAvg /= n;
        if (cb == 0) {
            dbZero = dbMin;
        }

        double dbBnd = 0;
        if (dbMin > dbZero) {
            dbBnd = ((8 * cb) * 1000.0) / (1024 * 1024);
            dbBnd /= dbMin - dbZero;
        }
        double dbReal = ((8 * cb) * 1000.0) / (1024 * 1024) / dbMin;

        printf("%6d bytes %9.3fms %9.3fms %9.3fms %8.3fMbps %8.3fMbps\r",
               cb, dbMin, dbMax, dbAvg, dbBnd, dbReal);

        csrRecords[nMin].Write();

        if (cb < 2048) {
            cb++;
        }
        else if (cb < 4096) {
            cb += 2;
        }
        else if (cb < 8192) {
            cb += 8;
        }
        else if (cb < 16384) {
            cb += 32;
        }
        else {
            cb += 128;
        }
    }
}

//////////////////////////////////////////////////////////////////////////////
//
class CInit
{
  public:
    CInit(HINSTANCE hinst)
        {
            m_hinst = hinst;

            AllocConsole();

            // initialize COM for free-threading
            HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
            if (FAILED(hr)) {
                CheckResult(hr, "CoInitializeEx");
                exit(hr);
            }

            ULONG ul = iping_DllMain(m_hinst, DLL_PROCESS_ATTACH, NULL);
            ASSERT(ul);
        }

    ~CInit()
        {
            ULONG ul = iping_DllMain(m_hinst, DLL_PROCESS_DETACH, NULL);
            ASSERT(ul);

            CoUninitialize();
        }

  private:
    HINSTANCE   m_hinst;
};

class CInitStub
{
  public:
    CInitStub()
        {
            m_dwRegister = ~0u;

            IClassFactory *pClassFactory = NULL;
            HRESULT hr = iping_DllGetClassObject(IID_IPing,
                                                 IID_IUnknown,
                                                 (void **)&pClassFactory);
            if (FAILED(hr)) {
                CheckResult(hr, "IPing_DllGetClassObject");
                ASSERT(SUCCEEDED(hr));
            }

            if (pClassFactory) {
                hr = CoRegisterClassObject(IID_IPing,
                                           pClassFactory,
                                           CLSCTX_SERVER,
                                           REGCLS_MULTIPLEUSE,
                                           &m_dwRegister);
                if (FAILED(hr)) {
                    ASSERT(SUCCEEDED(hr));
                    CheckResult(hr, "CoRegisterClassObject(IID_IPing)\n");
                }
                pClassFactory->Release();
                pClassFactory = NULL;
            }
        }

    ~CInitStub()
        {
            if (m_dwRegister != ~0u) {
                CoRevokeClassObject(m_dwRegister);
                m_dwRegister = ~0u;
            }
        }

  private:
    DWORD       m_dwRegister;
};

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
int __cdecl main(void)
{
    CInit cinit(GetModuleHandle(NULL));
    int argc;
    WCHAR **argv = CommandLineToArgvW(GetCommandLineW(), &argc);

    HRESULT hr;
    BOOLEAN fUnreg = FALSE;
    BOOLEAN fNeedHelp = FALSE;
    BOOLEAN fServer = FALSE;
    BOOLEAN fLong = FALSE;
    BOOLEAN fProfile = FALSE;
    BOOLEAN fInstrument = TRUE;
    BOOLEAN fFixed = FALSE;

    s_nThread = GetCurrentThreadId();

    printf("Ping Network Server: [" __DATE__ " " __TIME__ "]\n");
    int arg = 1;
    for (; arg < argc; arg++) {
        if (argv[arg][0] == '-' || argv[arg][0] == '/') {
            WCHAR *argn = argv[arg] + 1;
            WCHAR *argp = argn;
            while (*argp && *argp != ':') {
                argp++;
            }
            if (*argp == ':') {
                *argp++ = '\0';
            }

            switch (argn[0]) {

              case 'f':                                 // Fixed
              case 'F':
                fFixed = TRUE;
                g_nFixedToClient = _wtoi(argp);
                g_nFixedToServer = 0;
                break;

              case 'i':
              case 'I':                                 // Instrument
                fInstrument = !fInstrument;
                break;

              case 'n':                                 // Null
              case 'N':
                fFixed = TRUE;
                g_nFixedToClient = g_nFixedToServer = 0;
                break;

              case 'l':                                 // Long-term loop
              case 'L':
                fLong = !fLong;
                break;

              case 'p':                                 // Profile Network
              case 'P':
                fProfile = !fProfile;
                break;

              case 's':                                 // Server
              case 'S':
                fServer = !fServer;
                break;

              case 'u':                                 // Unregister
              case 'U':
                fUnreg = !fUnreg;
                break;

              case 'x':                                 // Xtract Data
              case 'X':
                g_fSummarize = !g_fSummarize;
                break;

              case '?':                                 // Help
                fNeedHelp = TRUE;
                break;

              case '\0':                                // Local Host
                hr = StringCchCopyW(wzServers[nServers++], ARRAYSIZE(wzServers[nServers++]),
                               L"localhost");
                if (FAILED(hr)) {
                    return 900;
                }
                ASSERT(nServers <= 32);
                break;

              default:
                fNeedHelp = TRUE;
                printf("Bad argument: %ls\n", argv[arg]);
                break;
            }
        }
        else {
            hr = StringCchCopyW(wzServers[nServers++], ARRAYSIZE(wzServers[nServers++]), argv[arg]);
            if (FAILED(hr)) {
                return 900;
            }
            ASSERT(nServers <= 32);
        }
    }
    if (argc == 1 || (nServers == 0 && !fUnreg && !fServer)) {
        fNeedHelp = TRUE;
    }

    if (fNeedHelp) {
        printf("Usage:\n"
               "    cping [options] [hosts]  ..or.. cping [options] /s\n"
               "Options:\n"
               "    /u      : Unregister.\n"
               "    /s      : Act as a server, waiting for clients.\n"
               "    /?      : Display this help screen.\n"
               "Client Options:\n"
               "    /l      : Long-term loop test.                (Default: %3s)\n"
               "    /p      : Profile test.                       (Default: %3s)\n"
               "    /n      : Null (0 length) test.               (Default: Off)\n"
               "    /f:size : Fixed sized packets.                (Default: %3s)\n"
               "    /x      : Xtract detailed DCOM/RPC/NET data.  (Default: %3s)\n"
               "    /i      : Toggle instrumentation.             (Default: %3s)\n",
               fLong        ? "On" : "Off",
               fProfile     ? "On" : "Off",
               fFixed       ? "On" : "Off",
               g_fSummarize ? "Off" : "Off",
               fInstrument  ? "On" : "Off");

        exit(1);
    }

    //////////////////////////////////////////////////////////////////////////
    if (fUnreg) {
        Unregister();
    }
    else {
        //////////////////////////////////////////////////////////////////////////////
        //
        CInitStub cinitstub;

        // Register in the registry.
        Register();

        if (fInstrument) {
            RerouteEntryPoints();
        }

        LONGLONG llCycles;
        hr = GetClockInfo(&llCycles);
        ASSERT(SUCCEEDED(hr));
        g_dCyclesPerSecond = (double)llCycles;
        g_dMsPerCycle = (double)1000.0 / (double)llCycles;

        g_pBuffer = CoTaskMemAlloc(g_cbBufferMax);
        ASSERT(g_pBuffer != NULL);

        if (fServer) {
            // register the class-object with OLE
            CNetPingFactory::InitSystem();

            CNetPingFactory *pClassFactory = new CNetPingFactory;

            printf("Registering.\n");
            DWORD dwRegister;
            hr = CoRegisterClassObject(CLSID_NetPingObject, pClassFactory,
                                       CLSCTX_SERVER, REGCLS_MULTIPLEUSE, &dwRegister);
            printf("Releasing Registered.\n");
            pClassFactory->Release();
            if (FAILED(hr)) {
                CheckResult(hr, "Server: CoRegisterClassObject");
                ASSERT(SUCCEEDED(hr));
            }

            printf("  Server: Waiting   <<<Press Ctrl-C to stop.>>>\n");

            while (fServer) {
                CNetPingFactory::Wait();
            }

            hr = CoRevokeClassObject(dwRegister);
            if (FAILED(hr)) {
                CheckResult(hr, "Server: CoRevokeClassObject");
                ASSERT(SUCCEEDED(hr));
            }

            CNetPingFactory::FiniSystem();
        }
        else if (nServers) {
            LONGLONG llBeg;
            LONGLONG llEnd;
            COSERVERINFO csi;
            MULTI_QI mq;

            //////////////////////////////////////////////////////////////////
            //
            printf("Processor Speed: %.0f MHz\n", g_dCyclesPerSecond / 1000000.0);

            DWORD dwSize = ARRAYSIZE(g_wzClientName);
            GetComputerNameW(g_wzClientName, &dwSize);

            printf(";;; %ls - %.0f MHz\n",
                    g_wzClientName,
                    g_dCyclesPerSecond / 1000000.0);

            for (int n = 0; n < nServers; n++) {
                if (g_wzServerName[0] == '\\' && g_wzServerName[1] == '\\') {
                    hr = StringCchCopyW(g_wzServerName, ARRAYSIZE(g_wzServerName), wzServers[n] + 2);
                }
                else {
                    hr = StringCchCopyW(g_wzServerName, ARRAYSIZE(g_wzServerName), wzServers[n]);
                }
                CheckResult(hr, "Main");

                printf("Server: %ls->%ls\n", g_wzClientName, g_wzServerName);

                printf(";; %ls %ls\n", g_wzClientName, g_wzServerName);

                ZeroMemory(&csi, sizeof(csi));
                csi.pwszName = wzServers[n];

                // create a remote instance of the object on the argv[1] machine
                mq.pIID = &IID_IPing;
                mq.pItf = NULL;
                mq.hr = S_OK;
                llBeg = GetTimeStamp();
                hr = CoCreateInstanceEx(CLSID_NetPingObject, NULL, CLSCTX_SERVER,
                                        &csi, 1, &mq);
                llEnd = GetTimeStamp();

                printf("  CoCreateInstanceEx: %0.4f seconds (%lu ticks)\n",
                       (double)(llEnd - llBeg)/(double)llCycles,
                       (ULONG)(llEnd - llBeg));

                CheckResult(mq.hr, "CoCreateInstanceEx [mq]");
                CheckResult(hr, "CoCreateInstanceEx");

                if (FAILED(hr)) {
                    CheckResult(hr, "CoCreateInstanceEx");
                    continue;
                }

                //////////////////////////////////////////////////////////////////
                //
                IPing *pIPing = (IPing *)mq.pItf;

                hr = pIPing->Ping();
                if (FAILED(hr)) {
                    CheckResult(hr, "Ping");
                }
                ASSERT(SUCCEEDED(hr));
                hr = Catch_IPing_Ping(pIPing);
                if (FAILED(hr)) {
                    CheckResult(hr, "Ping");
                }
                ASSERT(SUCCEEDED(hr));
                ZeroCycles();

                if (fFixed) {
                    Sample_Fixed(pIPing);
                }
                else if (fProfile) {
                    Sample_Profile(pIPing);
                }
                else {
                    Sample_Simple(pIPing);
                    if (fLong) {
                        for (;;) {
                            Sample_More(pIPing);

                            for (int j = 0; j < 5; j++) {
                                Sleep(20000);
                                Sample_Simple(pIPing);
                            }
                            Sleep(20000);

                            for (int i = 0; i < 18; i++) {
                                Sample_Less(pIPing);

                                for (int j = 0; j < 3; j++) {
                                    Sleep(20000);
                                    Sample_Simple(pIPing);
                                }
                                Sleep(20000);
                            }
                        }
                    }
                }

                pIPing->Release();
            }
        }

        if (g_pBuffer) {
            CoTaskMemFree(g_pBuffer);
            g_pBuffer = NULL;
        }

        Sleep(2);

        if (fInstrument && !g_fSummarize && s_rllCounts[E_Proxy]) {
            printf("::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"
                   "::::::::::::::::::\n");
            printf(":: Instrumented Cycles:     _____Function Time__  "
                   "________Total Time__ : Count\n");
            LONG n = E_DCOM;
            for (; n < E_MaxValue; n++) {
                s_rllCycles[n] = 0;
                s_rllTotals[n] = 0;
                s_rllCounts[n] = 0;
            }

            for (n = E_MinValue + 1; n < E_MaxValue; n++) {
                DumpCycles(n);
            }

            printf("::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"
                   "::::::::::::::::::\n");
            printf(":: Protocol Cycles:\n");
            for (n = E_DcomBeg; n <= E_DcomEnd; n++) {
                s_rllCycles[E_DCOM] += s_rllCycles[n];
                s_rllTotals[E_DCOM] += s_rllTotals[n];
                s_rllCounts[E_DCOM] += s_rllCounts[n];
            }
            for (n = E_RpcBeg; n <= E_RpcEnd; n++) {
                s_rllCycles[E_RPC] += s_rllCycles[n];
                s_rllTotals[E_RPC] += s_rllTotals[n];
                s_rllCounts[E_RPC] += s_rllCounts[n];
            }
            for (n = E_UdpBeg; n <= E_UdpEnd; n++) {
                s_rllCycles[E_UDP] += s_rllCycles[n];
                s_rllTotals[E_UDP] += s_rllTotals[n];
                s_rllCounts[E_UDP] += s_rllCounts[n];
            }
            for (n = E_NetBeg; n <= E_NetEnd; n++) {
                s_rllTotals[E_NET] += s_rllCycles[n];
                s_rllCycles[E_NET] += s_rllTotals[n];
                s_rllCounts[E_NET] += s_rllCounts[n];
            }
            DumpCycles(E_DCOM);
            DumpCycles(E_RPC);
            DumpCycles(E_UDP);
            DumpCycles(E_NET);

            printf("::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"
                   "::::::::::::::::::\n");
            printf(":: Protocol Cycles Per DCOM Call:\n");
            s_rllCycles[E_DCOM] /= s_rllCounts[E_DCOM];
            s_rllCycles[E_RPC] /= s_rllCounts[E_DCOM];
            s_rllCycles[E_UDP] /= s_rllCounts[E_DCOM];
            s_rllCycles[E_NET] /= s_rllCounts[E_DCOM];

            s_rllTotals[E_DCOM] /= s_rllCounts[E_DCOM];
            s_rllTotals[E_RPC] /= s_rllCounts[E_DCOM];
            s_rllTotals[E_UDP] /= s_rllCounts[E_DCOM];
            s_rllTotals[E_NET] /= s_rllCounts[E_DCOM];

            DumpCycles(E_DCOM);
            DumpCycles(E_RPC);
            DumpCycles(E_UDP);
            DumpCycles(E_NET);
        }
    }
    return 0;
}

//
///////////////////////////////////////////////////////////////// End of File.
