/*H*************************************************************************************/
/*!
    \File dirtynetwin.c

    \Description
        Provide a wrapper that translates the Winsock network interface
        into DirtySock calls. In the case of Winsock, little translation
        is needed since it is based off BSD sockets (as is DirtySock).

    \Copyright
        Copyright (c) Electronic Arts 1999-2018.

    \Version 1.0 01/02/2002 (gschaefer) First Version
*/
/*************************************************************************************H*/


/*** Include files *********************************************************************/

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN 1           // avoid windows.h including extra stuff, including winsock.h which we don't want
#endif

#include "DirtySDK/platform.h"

#if defined(DIRTYCODE_XBOXONE)
#pragma warning(push,0)
#include <windows.h>
#pragma warning(pop)
#include <winsock2.h>
#include <ws2tcpip.h>
#include <mstcpip.h>   // for tcp keep alive definitions
#else
#pragma warning(push,0)
#include <windows.h>
#include <ws2tcpip.h>
#include <mstcpip.h>   // for tcp keep alive definitions
#pragma warning(pop)
#endif

#include "DirtySDK/dirtysock/dirtylib.h"
#include "DirtySDK/dirtysock/dirtymem.h"
#include "DirtySDK/dirtysock/dirtynet.h"
#include "DirtySDK/dirtysock/dirtyerr.h"
#include "DirtySDK/dirtysock/dirtythread.h"
#include "DirtySDK/dirtysock/netconn.h"
#include "DirtySDK/dirtyvers.h"

#include "dirtynetpriv.h"       // private include for dirtynet common functions

/*** Defines ***************************************************************************/

#define SOCKET_MAXEVENTS        (64)

/*** Macros ****************************************************************************/

/*** Type Definitions ******************************************************************/

//! dirtysock connection socket structure
struct SocketT
{
    SocketT *pNext;             //!< link to next active
    SocketT *pKill;             //!< link to next killed socket

    SOCKET uSocket;             //!< winsock socket ref

    int32_t iFamily;            //!< protocol family
    int32_t iType;              //!< protocol type
    int32_t iProto;             //!< protocol ident

    int8_t iOpened;             //!< negative=error, zero=not open (connecting), positive=open
    uint8_t uImported;          //!< whether socket was imported or not
    uint8_t bVirtual;           //!< if true, socket is virtual
    uint8_t uShutdown;          //!< shutdown flag

    int32_t iLastError;         //!< last socket error

    struct sockaddr LocalAddr;  //!< local address
    struct sockaddr RemoteAddr; //!< remote address

    uint16_t uVirtualPort;      //!< virtual port, if set
    uint8_t bAsyncRecv;         //!< TRUE if async recv is enabled
    int8_t iVerbose;            //!< debug level
    uint8_t bSendCbs;           //!< TRUE if send cbs are enabled, false otherwise
    uint8_t _pad0[3];

    SocketRateT SendRate;       //!< send rate estimation data
    SocketRateT RecvRate;       //!< recv rate estimation data

    uint32_t uCallMask;         //!< valid callback events
    uint32_t uCallLast;         //!< last callback tick
    uint32_t uCallIdle;         //!< ticks between idle calls
    void *pCallRef;             //!< reference calback value
    int32_t (*pCallback)(SocketT *pSocket, int32_t iFlags, void *pRef);

    WSAOVERLAPPED Overlapped;   //!< overlapped i/o structure
    NetCritT RecvCrit;          //!< receive critical section
    int32_t iAddrLen;           //!< storage for async address length write by WSARecv
    uint32_t uRecvFlag;         //!< flags from recv operation
    uint8_t bRecvInp;           //!< if true, a receive operation is in progress  
    uint8_t bInCallback;        //!< in a socket callback
    uint8_t _pad1[2];

    struct sockaddr RecvAddr;   //!< receive address
    struct sockaddr_in6 RecvAddr6;   //!< receive address (ipv6)

    int32_t iPacketQueueResizePending;  // -1 if no resize pending, new size otherwise
    SocketPacketQueueT *pRecvQueue;
    SocketPacketQueueEntryT *pRecvPacket;
};

//! local state
typedef struct SocketStateT
{
    SocketT *pSockList;                 //!< master socket list
    SocketT *pSockKill;                 //!< list of killed sockets
    HostentT *pHostList;                //!< list of ongoing name resolution operations

    uint16_t aVirtualPorts[SOCKET_MAXVIRTUALPORTS]; //!< virtual port list
    int32_t iMaxPacket;                 //!< maximum packet size
    int32_t iFamily;                    //!< family to use for socket operations

    // module memory group
    int32_t iMemGroup;                  //!< module mem group id
    void *pMemGroupUserData;            //!< user data associated with mem group

    int32_t iVersion;                   //!< winsock version
    int32_t iVerbose;                   //!< debug level
    #if defined(DIRTYCODE_XBOXONE)
    uint32_t uLocalAddr;                //!< local ipv4 address
    uint32_t uRandBindPort;
    #else
    uint32_t uAdapterAddress;           //!< local interface used for SocketBind() operations if non-zero
    #endif

    volatile int32_t iRecvLife;         //!< receive thread alive indicator
    WSAEVENT hEvent;                    //!< event used to wake up receive thread
    NetCritT EventCrit;                 //!< event critical section (used when killing events)

    SocketAddrMapT AddrMap;             //!< address map for translating ipv6 addresses to ipv4 virtual addresses and back

    SocketHostnameCacheT *pHostnameCache; //!< hostname cache

    SocketSendCallbackEntryT aSendCbEntries[SOCKET_MAXSENDCALLBACKS]; //!< collection of send callbacks
} SocketStateT;

/*** Function Prototypes ***************************************************************/

/*** Variables *************************************************************************/

// Private variables

//! module state ref
static SocketStateT *_Socket_pState = NULL;

// Public variables


/*** Private Functions *****************************************************************/


/*F*************************************************************************************/
/*!
    \Function _XlatError0

    \Description
        Translate a winsock error to dirtysock

    \Input iErr     - return value from winsock call
    \Input iWsaErr  - winsock error (from WSAGetLastError())

    \Output
        int32_t     - dirtysock error

    \Version 09/09/2004 (jbrookes)
*/
/************************************************************************************F*/
static int32_t _XlatError0(int32_t iErr, int32_t iWsaErr)
{
    if (iErr < 0)
    {
        iErr = iWsaErr;
        if ((iErr == WSAEWOULDBLOCK) || (iErr == WSA_IO_PENDING))
            iErr = SOCKERR_NONE;
        else if ((iErr == WSAENETUNREACH) || (iErr == WSAEHOSTUNREACH))
            iErr = SOCKERR_UNREACH;
        else if (iErr == WSAENOTCONN)
            iErr = SOCKERR_NOTCONN;
        else if (iErr == WSAECONNREFUSED)
            iErr = SOCKERR_REFUSED;
        else if (iErr == WSAEINVAL)
            iErr = SOCKERR_INVALID;
        else if (iErr == WSAECONNRESET)
            iErr = SOCKERR_CONNRESET;
        else
        {
            NetPrintf(("dirtynetwin: error %s\n", DirtyErrGetName(iErr)));
            iErr = SOCKERR_OTHER;
        }
    }
    return(iErr);
}

/*F*************************************************************************************/
/*!
    \Function _XlatError

    \Description
        Translate the most recent winsock error to dirtysock

    \Input iErr     - return value from winsock call

    \Output
        int32_t     - dirtysock error

    \Version 01/02/2002 (gschaefer)
*/
/************************************************************************************F*/
static int32_t _XlatError(int32_t iErr)
{
    return(_XlatError0(iErr, WSAGetLastError()));
}

/*F*************************************************************************************/
/*!
    \Function _SocketOpen

    \Description
        Allocates a SocketT.  If uSocket is INVALID_SOCKET, a WinSock socket ref is
        created, otherwise uSocket is used.

    \Input uSocket  - socket to use, or INVALID_SOCKET
    \Input iFamily  - address family
    \Input iType    - type (SOCK_DGRAM, SOCK_STREAM, ...)
    \Input iProto   - protocol

    \Output
        SocketT *   - pointer to new socket, or NULL

    \Version 03/03/2005 (jbrookes)
*/
/************************************************************************************F*/
static SocketT *_SocketOpen(SOCKET uSocket, int32_t iFamily, int32_t iType, int32_t iProto)
{
    SocketStateT *pState = _Socket_pState;
    const uint32_t uTrue = 1, uFalse = 0;
    const int32_t iQueueSize = (iType != SOCK_STREAM) ? 1 : 8;
    SocketT *pSocket;

    // allocate memory
    if ((pSocket = (SocketT *)DirtyMemAlloc(sizeof(*pSocket), SOCKET_MEMID, pState->iMemGroup, pState->pMemGroupUserData)) == NULL)
    {
        NetPrintf(("dirtynetwin: unable to allocate memory for socket\n"));
        return(NULL);
    }
    ds_memclr(pSocket, sizeof(*pSocket));

    // open a winsock socket
    if ((uSocket == INVALID_SOCKET) && ((uSocket = WSASocketW(iFamily, iType, iProto, NULL, 0, WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET))
    {
        NetPrintf(("dirtynetwin: error %d creating socket\n", WSAGetLastError()));
        DirtyMemFree(pSocket, SOCKET_MEMID, pState->iMemGroup, pState->pMemGroupUserData);
        return(NULL);
    }

    // create packet queue
    if ((pSocket->pRecvQueue = SocketPacketQueueCreate(iQueueSize, pState->iMemGroup, pState->pMemGroupUserData)) == NULL)
    {
        NetPrintf(("dirtynetwin: failed to create socket queue for socket\n"));
        closesocket(uSocket);
        DirtyMemFree(pSocket, SOCKET_MEMID, pState->iMemGroup, pState->pMemGroupUserData);
        return(NULL);
    }
    pSocket->iPacketQueueResizePending = -1;

    // save the socket
    pSocket->uSocket = uSocket;
    pSocket->iLastError = SOCKERR_NONE;
    pSocket->iVerbose = 1;

    // set to non blocking
    ioctlsocket(uSocket, FIONBIO, (u_long *)&uTrue);

    // if udp, allow broadcast
    if (iType == SOCK_DGRAM)
    {
        setsockopt(uSocket, SOL_SOCKET, SO_BROADCAST, (char *)&uTrue, sizeof(uTrue));
    }
    // if raw, set hdrincl
    if (iType == SOCK_RAW)
    {
        setsockopt(uSocket, IPPROTO_IP, IP_HDRINCL, (char *)&uTrue, sizeof(uTrue));
    }
    // disable IPv6 only (allow IPv4 use)
    if (iFamily == AF_INET6)
    {
        setsockopt(uSocket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&uFalse, sizeof(uFalse));
    }

    // set family/proto info
    pSocket->iFamily = iFamily;
    pSocket->iType = iType;
    pSocket->iProto = iProto;
    pSocket->bAsyncRecv = ((iType == SOCK_DGRAM) || (iType == SOCK_RAW)) ? TRUE : FALSE;
    pSocket->bSendCbs = TRUE;

    // create overlapped i/o event object
    ds_memclr(&pSocket->Overlapped, sizeof(pSocket->Overlapped));
    pSocket->Overlapped.hEvent = WSACreateEvent();

    // initialize receive critical section
    NetCritInit(&pSocket->RecvCrit, "recvthread");

    // install into list
    NetCritEnter(NULL);
    pSocket->pNext = pState->pSockList;
    pState->pSockList = pSocket;
    NetCritLeave(NULL);

    // return the socket
    return(pSocket);
}

/*F*************************************************************************************/
/*!
    \Function _SocketClose

    \Description
        Disposes of a SocketT, including removal from the global socket list and
        disposal of the SocketT allocated memory.  Does NOT dispose of the winsock
        socket ref.

    \Input *pSocket     - socket to close
    \Input bShutdown    - if TRUE, shutdown and close socket ref

    \Output
        int32_t         - negative=failure, zero=success

    \Version 01/14/2005 (jbrookes)
*/
/************************************************************************************F*/
static int32_t _SocketClose(SocketT *pSocket, uint32_t bShutdown)
{
    SocketStateT *pState = _Socket_pState;
    uint8_t bSockInList = FALSE;
    SocketT **ppSocket;

    // for access to socket list
    NetCritEnter(NULL);

    // remove sock from linked list
    for (ppSocket = &pState->pSockList; *ppSocket != NULL; ppSocket = &(*ppSocket)->pNext)
    {
        if (*ppSocket == pSocket)
        {
            *ppSocket = pSocket->pNext;
            bSockInList = TRUE;
            break;
        }
    }

    // release before NetIdleDone
    NetCritLeave(NULL);

    // make sure the socket is in the socket list (and therefore valid)
    if (!bSockInList)
    {
        NetPrintf(("dirtynetwin: warning, trying to close socket 0x%08x that is not in the socket list\n", (uintptr_t)pSocket));
        return(-1);
    }

    // finish any idle call
    NetIdleDone();

    // acquire global critical section
    NetCritEnter(NULL);

    // wake up out of WaitForMultipleEvents()
    WSASetEvent(pState->hEvent);

    // acquire event critical section
    NetCritEnter(&pState->EventCrit);

    // close event
    WSACloseEvent(pSocket->Overlapped.hEvent);
    pSocket->Overlapped.hEvent = WSA_INVALID_EVENT;

    // release event critical section
    NetCritLeave(&pState->EventCrit);

    // release global critical section
    NetCritLeave(NULL);

    // destroy packet queue
    if (pSocket->pRecvQueue != NULL)
    {
        SocketPacketQueueDestroy(pSocket->pRecvQueue);
    }

    // mark as closed
    if (bShutdown && (pSocket->uSocket != INVALID_SOCKET))
    {
        // close winsock socket
        shutdown(pSocket->uSocket, 2);
        closesocket(pSocket->uSocket);
    }
    pSocket->uSocket = INVALID_SOCKET;
    pSocket->iOpened = 0;

    /* Put into killed list:
       Usage of a kill list allows for postponing two things
        * destruction of RecvCrit
        * release of socket data structure memory
      This ensures that RecvCrit is not freed while in-use by a running thread.
      Such a scenario can occur when the receive callback invoked by _SocketRecvThread()
      (while RecvCrit is entered) calls _SocketClose() */
    NetCritEnter(NULL);
    pSocket->pKill = pState->pSockKill;
    pState->pSockKill = pSocket;
    NetCritLeave(NULL);

    return(0);
}

/*F*************************************************************************************/
/*!
    \Function _SocketIdle

    \Description
        Call idle processing code to give connections time.

    \Input *_pState  - module state

    \Version 10/15/1999 (gschaefer)
*/
/************************************************************************************F*/
static void _SocketIdle(void *_pState)
{
    SocketStateT *pState = (SocketStateT *)_pState;
    SocketT *pSocket;
    uint32_t uTick = NetTick();

    // for access to socket list and kill list
    NetCritEnter(NULL);

    // walk socket list and perform any callbacks
    for (pSocket = pState->pSockList; pSocket != NULL; pSocket = pSocket->pNext)
    {
        // see if we should do callback
        if ((pSocket->uCallIdle != 0) && (pSocket->pCallback != NULL) && (!pSocket->bInCallback) && (NetTickDiff(uTick, pSocket->uCallLast) > pSocket->uCallIdle))
        {
            pSocket->bInCallback = TRUE;
            pSocket->pCallback(pSocket, 0, pSocket->pCallRef);
            pSocket->bInCallback = FALSE;
            pSocket->uCallLast = uTick = NetTick();
        }
    }

    // delete any killed sockets
    while ((pSocket = pState->pSockKill) != NULL)
    {
        pState->pSockKill = pSocket->pKill;

        // release the socket's receive critical section
        NetCritKill(&pSocket->RecvCrit);

        // free the socket memory
        DirtyMemFree(pSocket, SOCKET_MEMID, pState->iMemGroup, pState->pMemGroupUserData);
    }

    // process dns cache list, delete expired entries
    SocketHostnameCacheProcess(pState->pHostnameCache, pState->iVerbose);

    // process hostname list, delete completed lookup requests
    SocketHostnameListProcess(&pState->pHostList, pState->iMemGroup, pState->pMemGroupUserData);

    // release access to socket list and kill list
    NetCritLeave(NULL);
}

/*F*************************************************************************************/
/*!
    \Function _SocketRecvfromPacketQueue

    \Description
        Check if there is a pending inbound packet in the socket packet queue.

    \Input *pSocket     - pointer to socket
    \Input *pBuf        - [out] buffer to receive data
    \Input iLen         - length of recv buffer
    \Input *pFrom       - [out] address data was received from (NULL=ignore)
    \Input *pFromLen    - [out] length of address

    \Output
        int32_t         - size of packet extracted from queue, 0 if no packet

    \Version 04/20/2016 (mclouatre)
*/
/************************************************************************************F*/
static int32_t _SocketRecvfromPacketQueue(SocketT *pSocket, const char *pBuf, int32_t iLen, struct sockaddr *pFrom, int32_t *pFromLen)
{
    int32_t iResult = 0;

    // make sure destination buffer is valid
    if ((iLen > 0) && (pBuf != NULL))
    {
        // get a packet
        if (pSocket->iType != SOCK_STREAM)
        {
            iResult = SocketPacketQueueRem(pSocket->pRecvQueue, (uint8_t *)pBuf, iLen, &pSocket->RecvAddr);
        }
        else
        {
            iResult = SocketPacketQueueRemStream(pSocket->pRecvQueue, (uint8_t *)pBuf, iLen);
        }

        if (iResult > 0)
        {
            if (pFrom != NULL)
            {
                ds_memcpy_s(pFrom, sizeof(*pFrom), &pSocket->RecvAddr, sizeof(pSocket->RecvAddr));
                *pFromLen = sizeof(*pFrom);
            }
        }
    }

    return(iResult);
}

/*F*************************************************************************************/
/*!
    \Function _SocketRecvfrom

    \Description
        Check if there is a pending inbound packet in the receive buffer of the
        system socket

    \Input *pSocket     - pointer to socket
    \Input *pBuf        - [out] buffer to receive data
    \Input iLen         - length of recv buffer
    \Input *pFrom       - [out] address data was received from (NULL=ignore)
    \Input *pFromLen    - [out] length of address
    \Input *pRecvErr    - [out] pointer to variable to be filled with recv err code

    \Output
        int32_t         - positive=data bytes received, else standard error code

    \Version 04/20/2016 (mclouatre)
*/
/************************************************************************************F*/
static int32_t _SocketRecvfrom(SocketT *pSocket, const char *pBuf, int32_t iLen, struct sockaddr *pFrom, int32_t *pFromLen, int32_t *pRecvErr)
{
    int32_t iResult = 0;

    // make sure socket ref is valid
    if (pSocket->uSocket == INVALID_SOCKET)
    {
        pSocket->iLastError = SOCKERR_INVALID;
        return(pSocket->iLastError);
    }

    if (pFrom != NULL)
    {
        if (pSocket->iFamily == AF_INET)
        {
            iResult = recvfrom(pSocket->uSocket, (char *)pBuf, iLen, 0, pFrom, pFromLen);
        }
        if (pSocket->iFamily == AF_INET6)
        {
            struct sockaddr_in6 SockAddr6;
            int32_t iFromLen6 = sizeof(SockAddr6);
            SockAddr6.sin6_family = AF_INET;
            if ((iResult = recvfrom(pSocket->uSocket, (char *)pBuf, iLen, 0, (struct sockaddr *)&SockAddr6, &iFromLen6)) > 0)
            {
                SocketAddrMapTranslate(&_Socket_pState->AddrMap, pFrom, (struct sockaddr *)&SockAddr6, pFromLen);
            }
        }
        SockaddrInSetMisc(pFrom, NetTick());
    }
    else
    {
        iResult = recv(pSocket->uSocket, (char *)pBuf, iLen, 0);
        pFrom = &pSocket->RemoteAddr;
    }

    // get most recent socket error
    if ((*pRecvErr = WSAGetLastError()) == WSAEMSGSIZE)
    {
        /* if there was a message truncation, simply return the truncated size.  this matches what we
           do with the async recv thread version and also the linux behavior of recvfrom() */
        iResult = iLen;
    }

    return(iResult);
}

/*F*************************************************************************************/
/*!
    \Function _SocketProcessQueueResize

    \Description
        Resize socket packet queue if needed.

    \Input *pSocket - pointer to socket

    \Notes
        This function is meant to be called from the _SocketRecvThread() only.
        Do not use this function in code paths exercised by other threads.

    \Version 04/20/2016 (mclouatre)
*/
/************************************************************************************F*/
static void _SocketProcessQueueResize(SocketT *pSocket)
{
    SocketStateT *pState = _Socket_pState;
    if ((pSocket->iPacketQueueResizePending != -1))
    {
        pSocket->pRecvQueue = SocketPacketQueueResize(pSocket->pRecvQueue, pSocket->iPacketQueueResizePending, pState->iMemGroup, pState->pMemGroupUserData);
        pSocket->iPacketQueueResizePending = -1;
    }
}

/*F*************************************************************************************/
/*!
    \Function    _SocketRecvfromAsyncComplete

    \Description
        Called when data is received by _SocketRecvThread(). If there is a callback
        registered, then the socket is passed to that callback so the data may be
        consumed.

    \Input *pSocket       - pointer to socket that has new data
    \Input iBytesReceived - number of bytes received

    \Notes
        This function is meant to be called from the _SocketRecvThread() only.
        Do not use this function in code paths exercised by other threads.

    \Version 08/30/2004 (jbrookes)
*/
/************************************************************************************F*/
static void _SocketRecvfromAsyncComplete(SocketT *pSocket, int32_t iBytesReceived)
{
    // translate IPv6->IPv4, save receive timestamp
    if (pSocket->iType != SOCK_STREAM)
    {
        if (pSocket->iFamily == AF_INET6)
        {
            int32_t iNameLen = sizeof(pSocket->RecvAddr);
            SockaddrInit(&pSocket->RecvAddr, AF_INET);
            SocketAddrMapTranslate(&_Socket_pState->AddrMap, &pSocket->RecvAddr, (struct sockaddr *)&pSocket->RecvAddr6, &iNameLen);
        }
        SockaddrInSetMisc(&pSocket->RecvAddr, NetTick());
    }

    // complete setup of packet queue entry (already has data) by updating the size and reception time
    pSocket->pRecvPacket->iPacketSize = iBytesReceived;
    pSocket->pRecvPacket->uPacketTick = NetTick();
    ds_memcpy_s(&pSocket->pRecvPacket->PacketAddr, sizeof(pSocket->pRecvPacket->PacketAddr), &pSocket->RecvAddr, sizeof(pSocket->RecvAddr));

    // we are done with populating that specific packet queue entry, kill reference into packet queue
    pSocket->pRecvPacket = NULL;

    /* This is a safe place to deal with pending packet queue resize because the recvcrit is guaranteed to be locked
       and there is no async recv operation in progress on the queue (which implicitly means that pSocket->pRecvPacket is NOT
       pointing to a buffer in the queue.

       It is important to execute this after pSocket->pRecvPacket (which points to an entry in the queue) is fully initialized
       and befor the recv callback is invoked (because we want to resize to happen before SocketRecvfrom being potentially
       invoked from the callback. */
    _SocketProcessQueueResize(pSocket);

    // see if we should issue callback
    if ((!pSocket->bInCallback) && (pSocket->pCallback != NULL) && (pSocket->uCallMask & CALLB_RECV))
    {
        pSocket->bInCallback = TRUE;
        (pSocket->pCallback)(pSocket, 0, pSocket->pCallRef);
        pSocket->bInCallback = FALSE;
        pSocket->uCallLast = NetTick();
    }
}

/*F*************************************************************************************/
/*!
    \Function _SocketRecvfromAsync

    \Description
        Issue an overlapped recv call on the given socket.

    \Input *pSocket - pointer to socket to read from

    \Notes
        This function is meant to be called from the _SocketRecvThread() only.
        Do not use this function in code paths exercised by other threads.

    \Version 08/30/2004 (jbrookes)
*/
/************************************************************************************F*/
static void _SocketRecvfromAsync(SocketT *pSocket)
{
    int32_t iResult = 0;
    int32_t iRecvErr;
    WSABUF RecvBuf;

    /* Mark the operation as in progress.
    For scenarios where the recv call returned 0 (meaning immediate completion), we enforce an execution
    path similar to recv call not completing immediately. We asssume two things:
    * _SocketRecvThread() will not wait on the next WSAWaitForMultipleEvents() because the event for this socket is signaled.
    * The following call to WSAGetOverlappedResult() will then detect completion of the recv operation. */
    pSocket->bRecvInp = TRUE;

    // get a packet queue entry to receive into
    pSocket->pRecvPacket = SocketPacketQueueAlloc(pSocket->pRecvQueue);

    // set up for recv call
    RecvBuf.buf = (CHAR *)pSocket->pRecvPacket->aPacketData;
    RecvBuf.len = sizeof(pSocket->pRecvPacket->aPacketData);
    pSocket->uRecvFlag = 0;

    // try and receive some data
    if (pSocket->iType == SOCK_DGRAM)
    {
        if (pSocket->iFamily == AF_INET)
        {
            pSocket->iAddrLen = sizeof(pSocket->RecvAddr);
            iResult = WSARecvFrom(pSocket->uSocket, &RecvBuf, 1, NULL, (LPDWORD)&pSocket->uRecvFlag, (struct sockaddr *)&pSocket->RecvAddr, (LPINT)&pSocket->iAddrLen, &pSocket->Overlapped, NULL);
        }
        if (pSocket->iFamily == AF_INET6)
        {
            pSocket->iAddrLen = sizeof(pSocket->RecvAddr6);
            iResult = WSARecvFrom(pSocket->uSocket, &RecvBuf, 1, NULL, (LPDWORD)&pSocket->uRecvFlag, (struct sockaddr *)&pSocket->RecvAddr6, (LPINT)&pSocket->iAddrLen, &pSocket->Overlapped, NULL);
        }
    }
    else // pSocket->iType == SOCK_RAW
    {
        iResult = WSARecv(pSocket->uSocket, &RecvBuf, 1, NULL, (LPDWORD)&pSocket->uRecvFlag, &pSocket->Overlapped, NULL);
    }

    // error?
    if ((iResult == SOCKET_ERROR) && ((iRecvErr = WSAGetLastError()) != WSA_IO_PENDING))
    {
        NetPrintf(("dirtynetwin: [%p] error %d when trying to initiate async receive on socket\n", pSocket, iRecvErr));

        // clean up resources that were reserved when the async recv was initiated
        SocketPacketQueueAllocUndo(pSocket->pRecvQueue);

        // mark that receive operation is no longer in progress
        pSocket->bRecvInp = FALSE;
    }
}

// FIFA_BEGIN | TEMP | mmisalucha | 2016-07-18 | Debug DT Issue 38887
#ifdef OTP_BUILD
volatile uint32_t gInputFrame = 0;
#endif
// FIFA_END

/*F*************************************************************************************/
/*!
    \Function _SocketRecvThread

    \Description
        Wait for incoming data and deliver it immediately to the registered socket callback,
        if any.

    \Input  pUnused - unused

    \Version 08/30/2004 (jbrookes)
*/
/************************************************************************************F*/
static void _SocketRecvThread(void *pUnused)
{
    SocketStateT *pState = _Socket_pState;
    WSAEVENT aEventList[SOCKET_MAXEVENTS];
    int32_t iNumEvents, iResult;
    SocketT *pSocket;

// FIFA_BEGIN | TEMP | mmisalucha | 2016-07-18 | Debug DT Issue 38887
#ifdef OTP_BUILD
	uint32_t inputFrame = gInputFrame;
	uint64_t inputTime = NetTick();
    EA_UNUSED(inputFrame);
    EA_UNUSED(inputTime);
#endif
// FIFA_END
    char strThreadId[32];

    // get the thread id
    DirtyThreadGetThreadId(strThreadId, sizeof(strThreadId));

    // show we are alive
    NetPrintfVerbose((pState->iVerbose, 0, "dirtynetwin: receive thread started (thid=%s)\n", strThreadId));

    // clear event list
    ds_memclr(aEventList, sizeof(aEventList));

    // loop until done
    while (pState->iRecvLife == 1)
    {
        // acquire global critical section for access to g_socklist
        NetCritEnter(NULL);
    
        // add global event to list
        iNumEvents = 0;
        aEventList[iNumEvents++] = pState->hEvent;
    
        /* Walk the socket list and for each eligible socket do:
                1- If an async recv has recently completed, then finalize the recv operation and invoke the recv callback if registered.
                Then mark the async recv operatoin has no longer in progress.
                2- If an async recv is not in progress, initiate one if n-deep packet queue is not full. Then mark the async recv operation
                as in progress.
                3- If an async revc is in progress (old or newly initiated), then add the socket's event to the event list that the
                thread later blocks on when calling WSAWaitForMultipleEvents(). */
        for (pSocket = pState->pSockList; (pSocket != NULL) && (iNumEvents < SOCKET_MAXEVENTS); pSocket = pSocket->pNext)
        {
            // only handle non-virtual sockets with asyncrecv true
            if ((!pSocket->bVirtual) && (pSocket->uSocket != INVALID_SOCKET) && pSocket->bAsyncRecv)
            {
                // acquire socket critical section
                NetCritEnter(&pSocket->RecvCrit);

                // is an asynchronous recv in progress on this socket?
                if (pSocket->bRecvInp)
                {
                    int32_t iRecvResult;

                    // check for overlapped read completion
                    if (WSAGetOverlappedResult(pSocket->uSocket, &pSocket->Overlapped, (LPDWORD)&iRecvResult, FALSE, (LPDWORD)&pSocket->uRecvFlag) == TRUE)
                    {
                        // mark that receive operation is no longer in progress
                        pSocket->bRecvInp = FALSE;

                        _SocketRecvfromAsyncComplete(pSocket, iRecvResult);
                    }
                    else if ((iResult = WSAGetLastError()) != WSA_IO_INCOMPLETE)
                    {
                        // clean up resources that were reserved when the async recv was initiated
                        SocketPacketQueueAllocUndo(pSocket->pRecvQueue);

                        #if DIRTYCODE_LOGGING
                        if (iResult != WSAECONNRESET)
                        {
                            NetPrintf(("dirtynetwin: [%p] WSAGetOverlappedResult error %d\n", pSocket, iResult));
                        }
                        #endif

                        // mark that receive operation is no longer in progress
                        pSocket->bRecvInp = FALSE;

                        /* This is a safe place to deal with pending packet queue resize because the recvcrit is guaranteed to be locked
                            and there is no async read operation in progress on the queue (which implicitly means that pSocket->pRecvPacket is NOT
                            pointing to a buffer in the queue. */
                        _SocketProcessQueueResize(pSocket);
                    }
                }

                /* Before proceeding, make sure that the user callback invoked in _SocketRecvThreadFinishAsyncRead() did not close the socket
                    with SocketClose(). We know that SocketClose() function resets the value of pSocket->socket to INVALID_SOCKET. Also, we
                    know that it does not destroy pSocket but it queues it in the global kill list. Since this list cannot be processed before
                    the code below, as it also runs in the context of the global critical section, the following code is thread-safe. */
                if (pSocket->uSocket != INVALID_SOCKET)
                {
                    // should we initiate a new asynchronous recv on this socket?
                    if (!pSocket->bRecvInp && !SocketPacketQueueStatus(pSocket->pRecvQueue, 'pful'))
                    {
                        _SocketRecvfromAsync(pSocket);
                    }

                    // if an asynchronous recv (old or newly initiated) is in progress, then add the associated event to event list
                    if (pSocket->bRecvInp)
                    {
                        aEventList[iNumEvents++] = pSocket->Overlapped.hEvent;
                    }
                }

                // release socket critical section
                NetCritLeave(&pSocket->RecvCrit);
            }
        }

        // protect against events being deleted
        NetCritEnter(&pState->EventCrit);

        // release global critical section
        NetCritLeave(NULL);

        // wait for an event to trigger
        iResult = WSAWaitForMultipleEvents(iNumEvents, aEventList, FALSE, WSA_INFINITE, FALSE) - WSA_WAIT_EVENT_0;

        // reset the signaled event
        if ((iResult >= 0) && (iResult < iNumEvents))
        {
            WSAResetEvent(aEventList[iResult]);
        }

        // leave event protected section
        NetCritLeave(&pState->EventCrit);
    }

    // indicate we are done
    NetPrintfVerbose((pState->iVerbose, 0, "dirtynetwin: receive thread exit\n"));
    pState->iRecvLife = 0;
}

/*F*************************************************************************************/
/*!
    \Function _SocketPoll

    \Description
        Perform a blocking poll in nanoseconds

    \Input *pState          - pointer to module state
    \Input uPollUsec        - time to perform the poll (select) for
    \Input *ppSocketList    - socket list or NULL

    \Output
        int32_t             - result of the select call

    \Version 04/17/2019 (eesponda)
*/
/************************************************************************************F*/
static int32_t _SocketPoll(SocketStateT *pState, uint32_t uPollUsec, SocketT **ppSocketList)
{
    fd_set FdRead, FdExcept;
    struct timeval TimeVal;
    SocketT *pTempSocket, **ppSocket;
    int32_t iSocket, iResult;
    int32_t iMaxSocket = 0;

    FD_ZERO(&FdRead);
    FD_ZERO(&FdExcept);
    TimeVal.tv_sec = uPollUsec/1000000;
    TimeVal.tv_usec = uPollUsec%1000000;

    // if socket list specified, use it
    if (ppSocketList != NULL)
    {
        // add sockets to select list (FD_SETSIZE is 64)
        for (ppSocket = ppSocketList, iSocket = 0; (*ppSocket != NULL) && (iSocket < FD_SETSIZE); ppSocket++, iSocket++)
        {
            FD_SET((*ppSocket)->uSocket, &FdRead);
            FD_SET((*ppSocket)->uSocket, &FdExcept);
            iMaxSocket = DS_MAX((int32_t)((*ppSocket)->uSocket), iMaxSocket);
        }
    }
    else
    {
        // get exclusive access to socket list
        NetCritEnter(NULL);
        // walk socket list and add all sockets
        for (pTempSocket = pState->pSockList, iSocket = 0; (pTempSocket != NULL) && (iSocket < FD_SETSIZE); pTempSocket = pTempSocket->pNext, iSocket++)
        {
            if (pTempSocket->uSocket != INVALID_SOCKET)
            {
                FD_SET(pTempSocket->uSocket, &FdRead);
                FD_SET(pTempSocket->uSocket, &FdExcept);
                iMaxSocket = DS_MAX((int32_t)(pTempSocket->uSocket), iMaxSocket);
            }
        }
        // for access to g_socklist and g_sockkill
        NetCritLeave(NULL);
    }

    // wait for input on the socket list for up to iData1 milliseconds
    iResult = select(iMaxSocket + 1, &FdRead, NULL, &FdExcept, &TimeVal);

    // if any sockets have pending data, figure out which ones
    if (iResult > 0)
    {
        // re-acquire critical section
        NetCritEnter(NULL);

        // update sockets if there is data to be read or not
        for (pTempSocket = pState->pSockList; pTempSocket != NULL; pTempSocket = pTempSocket->pNext)
        {
            if ((pTempSocket->uSocket != INVALID_SOCKET) &&
                (FD_ISSET(pTempSocket->uSocket, &FdRead)) &&
                (pTempSocket->pCallback != NULL) &&
                (pTempSocket->uCallMask & CALLB_RECV))
            {
                pTempSocket->pCallback(pTempSocket, 0, pTempSocket->pCallRef);
                pTempSocket->uCallLast = NetTick();
            }
        }

        // release the critical section
        NetCritLeave(NULL);
    }

    // return number of file descriptors with pending data
    return(iResult);
}

/*F*************************************************************************************/
/*!
    \Function _SocketInfoGlobal

    \Description
        Return information about global state

    \Input iInfo    - selector for desired information
    \Input iData    - selector specific
    \Input *pBuf    - [out] return buffer
    \Input iLen     - buffer length

    \Output
        int32_t     - size of returned data or error code (negative value)

    \Notes
        These selectors need to be documented in SocketInfo() to allow our
        documentation generation to pick them up.

    \Version 03/31/2017 (eesponda)
*/
/************************************************************************************F*/
static int32_t _SocketInfoGlobal(int32_t iInfo, int32_t iData, void *pBuf, int32_t iLen)
{
    SocketStateT *pState = _Socket_pState;

    if (iInfo == 'addr')
    {
        #if defined(DIRTYCODE_XBOXONE)
        // force new acquisition of address?
        if (iData == 1)
        {
            pState->uLocalAddr = 0;
        }
        #endif
        return(SocketGetLocalAddr());
    }
    // get socket bound to given port
    if ( (iInfo == 'bind') || (iInfo == 'bndu') )
    {
        SocketT *pSocket;
        struct sockaddr BindAddr;
        int32_t iFound = -1;

        // for access to socket list
        NetCritEnter(NULL);

        // walk socket list and find matching socket
        for (pSocket = pState->pSockList; pSocket != NULL; pSocket = pSocket->pNext)
        {
            // if iInfo is 'bndu', only consider sockets of type SOCK_DGRAM
            // note: 'bndu' stands for "bind udp"
            if ( (iInfo == 'bind') || ((iInfo == 'bndu') && (pSocket->iType == SOCK_DGRAM)) )
            {
                // get socket info
                SocketInfo(pSocket, 'bind', 0, &BindAddr, sizeof(BindAddr));
                if (SockaddrInGetPort(&BindAddr) == iData)
                {
                    *(SocketT **)pBuf = pSocket;
                    iFound = 0;
                    break;
                }
            }
        }

        // for access to g_socklist and g_sockkill
        NetCritLeave(NULL);
        return(iFound);
    }
    // check if specified ipv4 address is virtual and return associated ipv6 address if so
    if (iInfo == '?ip6')
    {
        int32_t iResult = -1;
        struct sockaddr_in6 SockAddr6, *pSockAddr6;
        struct sockaddr SockAddr;
        int32_t iNameLen;

        SockaddrInit(&SockAddr, AF_INET);
        SockaddrInSetAddr(&SockAddr, iData);
        
        SockaddrInit6(&SockAddr6, AF_INET6);
        pSockAddr6 = ((pBuf != NULL) && (iLen == sizeof(SockAddr6))) ? (struct sockaddr_in6 *)pBuf : &SockAddr6;
        iNameLen = sizeof(SockAddr6);

        iResult = SocketAddrMapGet(&pState->AddrMap, (struct sockaddr *)pSockAddr6, &SockAddr, &iNameLen) ? 1 : 0;
        return(iResult);
    }

    #if !defined(DIRTYCODE_XBOXONE)
    // get local address previously specified by user for subsequent SocketBind() operations
    if (iInfo == 'ladr')
    {
        // If 'ladr' had not been set previously, address field of output sockaddr buffer
        // will just be filled with 0.
        SockaddrInSetAddr((struct sockaddr *)pBuf, pState->uAdapterAddress);
        return(0);
    }
    #endif
    // return max packet size
    if (iInfo == 'maxp')
    {
        return(pState->iMaxPacket);
    }
    // get send callback function pointer (iData specifies index in array)
    if (iInfo == 'sdcf')
    {
        if ((pBuf != NULL) && (iLen == sizeof(pState->aSendCbEntries[iData].pSendCallback)))
        {
            ds_memcpy(pBuf, &pState->aSendCbEntries[iData].pSendCallback, sizeof(pState->aSendCbEntries[iData].pSendCallback));
            return(0);
        }

        NetPrintf(("dirtynetwin: 'sdcf' selector used with invalid paramaters\n"));
        return(-1);
    }
    // get send callback user data pointer (iData specifies index in array)
    if (iInfo == 'sdcu')
    {
        if ((pBuf != NULL) && (iLen == sizeof(pState->aSendCbEntries[iData].pSendCallref)))
        {
            ds_memcpy(pBuf, &pState->aSendCbEntries[iData].pSendCallref, sizeof(pState->aSendCbEntries[iData].pSendCallref));
            return(0);
        }

        NetPrintf(("dirtynetwin: 'sdcu' selector used with invalid paramaters\n"));
        return(-1);
    }
    // return global debug output level
    if (iInfo == 'spam')
    {
        return(pState->iVerbose);
    }
    // unhandled
    NetPrintf(("dirtynetwin: unhandled global SocketInfo() selector '%C'\n", iInfo));
    return(-1);
}

/*F*************************************************************************************/
/*!
    \Function _SocketControlGlobal

    \Description
        Process a global control message (type specific operation)

    \Input iOption  - the option to pass
    \Input iData1   - message specific parm
    \Input *pData2  - message specific parm
    \Input *pData3  - message specific parm

    \Output
        int32_t     - message specific result (-1=unsupported message, -2=no such module)

    \Notes
        These selectors need to be documented in SocketControl() to allow our
        documentation generation to pick them up.

    \Version 03/31/2017 (eesponda)
*/
/************************************************************************************F*/
static int32_t _SocketControlGlobal(int32_t iOption, int32_t iData1, void *pData2, void *pData3)
{
    SocketStateT *pState = _Socket_pState;

    // set address family to use for socket operations
    if (iOption == 'afam')
    {
        if ((iData1 != AF_INET) && (iData1 != AF_INET6))
        {
            return(-1);
        }
        pState->iFamily = iData1;
        NetPrintf(("dirtynetwin: address family used for socket operations set to %s\n", pState->iFamily == AF_INET ? "AF_INET" : "AF_INET6"));
        return(0);
    }
    // handle connect message
    if (iOption == 'conn')
    {
        return(0);
    }
    // handle disconnect message
    if (iOption == 'disc')
    {
        return(0);
    }
    // set an ipv6 address into the mapping table
    if (iOption == '+ip6')
    {
        return(SocketAddrMapAddress(&pState->AddrMap, (const struct sockaddr *)pData2, iData1));
    }
    // del an ipv6 address from the mapping table
    if (iOption == '-ip6')
    {
        return(SocketAddrUnmapAddress(&pState->AddrMap, (const struct sockaddr *)pData2, iData1));
    }
    // remap an existing ipv6 address in the mapping table
    if (iOption == '~ip6')
    {
        return(SocketAddrRemapAddress(&pState->AddrMap, (const struct sockaddr *)pData2, (const struct sockaddr *)pData3, iData1));
    }
    #if !defined(DIRTYCODE_XBOXONE)
    // set local address? (used to select between multiple network interfaces)
    if (iOption == 'ladr')
    {
        pState->uAdapterAddress = (unsigned)iData1;
        return(0);
    }
    #endif
    // set max udp packet size
    if (iOption == 'maxp')
    {
        NetPrintf(("dirtynetwin: setting max udp packet size to %d\n", iData1));
        pState->iMaxPacket = iData1;
        return(0);
    }
    // block waiting on input from socket list in milliseconds
    if (iOption == 'poll')
    {
        return(_SocketPoll(pState, (unsigned)iData1*1000, pData2));
    }
    if (iOption == 'poln')
    {
        return(_SocketPoll(pState, (unsigned)iData1, pData2));
    }
    // set/unset send callback (iData1=TRUE for set - FALSE for unset, pData2=callback, pData3=callref)
    if (iOption == 'sdcb')
    {
        SocketSendCallbackEntryT sendCbEntry;
        sendCbEntry.pSendCallback = (SocketSendCallbackT *)pData2;
        sendCbEntry.pSendCallref = pData3;

        if (iData1)
        {
            return(SocketSendCallbackAdd(&pState->aSendCbEntries[0], &sendCbEntry));
        }
        else
        {
            return(SocketSendCallbackRem(&pState->aSendCbEntries[0], &sendCbEntry));
        }
    }
    // set debug level
    if (iOption == 'spam')
    {
        // set module level debug level
        pState->iVerbose = iData1;
        return(0);
    }
    // mark a port as virtual
    if (iOption == 'vadd')
    {
        int32_t iPort;

        // find a slot to add virtual port
        for (iPort = 0; pState->aVirtualPorts[iPort] != 0; iPort++)
            ;
        if (iPort < SOCKET_MAXVIRTUALPORTS)
        {
            NetPrintfVerbose((pState->iVerbose, 1, "dirtynetwin: added port %d to virtual port list\n", iData1));
            pState->aVirtualPorts[iPort] = (uint16_t)iData1;
            return(0);
        }
    }
    // remove port from virtual port list
    if (iOption == 'vdel')
    {
        int32_t iPort;

        // find virtual port in list
        for (iPort = 0; (iPort < SOCKET_MAXVIRTUALPORTS) && (pState->aVirtualPorts[iPort] != (uint16_t)iData1); iPort++)
            ;
        if (iPort < SOCKET_MAXVIRTUALPORTS)
        {
            NetPrintfVerbose((pState->iVerbose, 1, "dirtynetwin: removed port %d from virtual port list\n", iData1));
            pState->aVirtualPorts[iPort] = 0;
            return(0);
        }
    }
    // unhandled
    NetPrintf(("dirtynetwin: unhandled global SocketControl() option '%C'\n", iOption));
    return(-1);
}

/*** Public Functions ******************************************************************/


/*F*************************************************************************************/
/*!
    \Function SocketCreate

    \Description
        Create new instance of socket interface module.  Initializes all global
        resources and makes module ready for use.

    \Input iThreadPrio        - priority to start threads with
    \Input iThreadStackSize   - stack size to start threads with (in bytes)
    \Input iThreadCpuAffinity - cpu affinity to start threads with

    \Output
        int32_t               - negative=error, zero=success

    \Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
int32_t SocketCreate(int32_t iThreadPrio, int32_t iThreadStackSize, int32_t iThreadCpuAffinity)
{
    SocketStateT *pState = _Socket_pState;
    WSADATA WSAData;
    int32_t iResult;
    int32_t iMemGroup;
    void *pMemGroupUserData;
    DirtyThreadConfigT ThreadConfig;

    // Query current mem group data
    DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData);

    // error if already started
    if (pState != NULL)
    {
        NetPrintfVerbose((pState->iVerbose, 0, "dirtynetwin: SocketCreate() called while module is already active\n"));
        return(-1);
    }

    // print version info
    NetPrintf(("dirtynetwin: DirtySDK v%d.%d.%d.%d.%d\n", DIRTYSDK_VERSION_YEAR, DIRTYSDK_VERSION_SEASON, DIRTYSDK_VERSION_MAJOR, DIRTYSDK_VERSION_MINOR, DIRTYSDK_VERSION_PATCH));

    // alloc and init state ref
    if ((pState = (SocketStateT *)DirtyMemAlloc(sizeof(*pState), SOCKET_MEMID, iMemGroup, pMemGroupUserData)) == NULL)
    {
        NetPrintf(("dirtynetwin: unable to allocate module state\n"));
        return(-2);
    }
    ds_memclr(pState, sizeof(*pState));
    pState->iMemGroup = iMemGroup;
    pState->pMemGroupUserData = pMemGroupUserData;
    pState->iMaxPacket = SOCKET_MAXUDPRECV;
    pState->iFamily = AF_INET6;
    pState->iVerbose = 1;

    // save global module ref
    _Socket_pState = pState;

    // startup network libs
    NetLibCreate(iThreadPrio, iThreadStackSize, iThreadCpuAffinity);

    // start winsock
    ds_memclr(&WSAData, sizeof(WSAData));
    iResult = WSAStartup(MAKEWORD(2,2), &WSAData);
    if (iResult != 0)
    {
        NetPrintf(("dirtynetwin: error %d loading winsock library\n", iResult));
        SocketDestroy((uint32_t)(-1));
        return(-3);
    }

    // save the available version
    pState->iVersion = (LOBYTE(WSAData.wVersion)<<8)|(HIBYTE(WSAData.wVersion)<<0);

    // create hostname cache
    if ((pState->pHostnameCache = SocketHostnameCacheCreate(iMemGroup, pMemGroupUserData)) == NULL)
    {
        NetPrintf(("dirtynetwin: unable to create hostname cache\n"));
        SocketDestroy((uint32_t)(-1));
        return(-4);
    }

    // add our idle handler
    NetIdleAdd(&_SocketIdle, pState);

    // create a global event to notify recv thread of newly available sockets
    pState->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (pState->hEvent == NULL)
    {
        NetPrintf(("dirtynetwin: error %d creating state event\n", GetLastError()));
        SocketDestroy(0);
        return(-5);
    }

    // initialize event critical section
    NetCritInit(&pState->EventCrit, "recv-event-crit");

    /* if recvthread has been created but has no chance to run (upon failure or shutdown
       immediately), there will be a problem.  by setting iRecvLife to non-zero before
       thread creation we ensure SocketDestroy work properly. */
    pState->iRecvLife = 1; // see _SocketRecvThread for more details

    // configure thread parameters
    ds_memclr(&ThreadConfig, sizeof(ThreadConfig));
    ThreadConfig.pName = "SocketRecv";
    ThreadConfig.iPriority = iThreadPrio;
    ThreadConfig.iAffinity = iThreadCpuAffinity;
    ThreadConfig.iVerbosity = pState->iVerbose;

    // start up socket receive thread
    if ((iResult = DirtyThreadCreate(_SocketRecvThread, NULL, &ThreadConfig)) != 0)
    {
        pState->iRecvLife = 0; // no recvthread was created, reset to 0
        NetPrintf(("dirtynetwin: error %d creating socket receive thread\n", iResult));
        SocketDestroy(0);
        return(-6);
    }

    // init socket address map
    SocketAddrMapInit(&pState->AddrMap, pState->iMemGroup, pState->pMemGroupUserData);

    // return success
    return(0);
}

/*F*************************************************************************************/
/*!
    \Function SocketDestroy

    \Description
        Release resources and destroy module.

    \Input uShutdownFlags   - shutdown flags

    \Output
        int32_t             - negative=error, zero=success

    \Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
int32_t SocketDestroy(uint32_t uShutdownFlags)
{
    SocketStateT *pState = _Socket_pState;

    // error if not active
    if (pState == NULL)
    {
        NetPrintf(("dirtynetwin: SocketDestroy() called while module is not active\n"));
        return(-1);
    }

    NetPrintf(("dirtynetwin: shutting down\n"));

    // $$TODO- why don't we do this on XONE too?
    #if !defined(DIRTYCODE_XBOXONE)
    // wait until all lookup threads are done
    while (pState->pHostList != NULL)
    {
        volatile HostentT **ppHost;
        int32_t iSocketLookups;

        // check for lookup threads that are still active
        for (ppHost = &pState->pHostList, iSocketLookups = 0; *ppHost != NULL; ppHost = (volatile HostentT **)&(*ppHost)->pNext)
        {
            iSocketLookups += (*ppHost)->thread ? 0 : 1;
        }
        // if no ongoing socket lookups, we're done
        if (iSocketLookups == 0)
        {
            break;
        }
        Sleep(1);
    }
    #endif

    // kill idle callbacks
    NetIdleDel(&_SocketIdle, pState);

    // let any idle event finish
    NetIdleDone();

    // tell receive thread to quit and wake it up (if running)
    if (pState->iRecvLife == 1)
    {
        pState->iRecvLife = 2;
        if (pState->hEvent != NULL)
        {
            WSASetEvent(pState->hEvent);
        }

        // wait for thread to terminate
        while (pState->iRecvLife > 0)
        {
            Sleep(1);
        }
    }

    // cleanup addr map, if allocated
    SocketAddrMapShutdown(&pState->AddrMap);

    // close all sockets
    NetCritEnter(NULL);
    while (pState->pSockList != NULL)
    {
        SocketClose(pState->pSockList);
    }
    NetCritLeave(NULL);

    // clear the kill list
    _SocketIdle(pState);

    // destroy hostname cache
    if (pState->pHostnameCache != NULL)
    {
        SocketHostnameCacheDestroy(pState->pHostnameCache);
    }

    // we rely on the Handle having been created to know if the critical section was created.
    if (pState->hEvent != NULL)
    {
        // destroy event critical section
        NetCritKill(&pState->EventCrit);
        // delete global event
        CloseHandle(pState->hEvent);
    }

    // free the memory and clear global module ref
    _Socket_pState = NULL;
    DirtyMemFree(pState, SOCKET_MEMID, pState->iMemGroup, pState->pMemGroupUserData);

    // shut down network libs
    NetLibDestroy(0);

    // shutdown winsock, unless told otherwise
    if (uShutdownFlags == 0)
    {
        WSACleanup();
    }

    NetPrintf(("dirtynetwin: shutdown complete\n"));

    return(0);
}

/*F*************************************************************************************/
/*!
    \Function SocketOpen

    \Description
        Create a new transfer endpoint. A socket endpoint is required for any
        data transfer operation.

    \Input iFamily  - address family (AF_INET) [IGNORED]
    \Input iType    - socket type (SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, ...)
    \Input iProto   - protocol type for SOCK_RAW (unused by others)

    \Output
        SocketT     - socket reference

    \Notes
        The address family specified here is ignored; instead it is dictated based on
        the internal configuration setting which defaults to AF_INET6, but can be
        overridden using the 'fam' SocketControl() selector.

    \Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
SocketT *SocketOpen(int32_t iFamily, int32_t iType, int32_t iProto)
{
    return(_SocketOpen(INVALID_SOCKET, _Socket_pState->iFamily, iType, iProto));
}

/*F*************************************************************************************/
/*!
    \Function SocketClose

    \Description
        Close a socket. Performs a graceful shutdown of connection oriented protocols.

    \Input *pSocket - socket reference

    \Output
        int32_t     - zero

    \Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
int32_t SocketClose(SocketT *pSocket)
{
    return(_SocketClose(pSocket, TRUE));
}

/*F*************************************************************************************/
/*!
    \Function SocketImport

    \Description
        Import a socket.  The given socket ref may be a SocketT, in which case a
        SocketT pointer to the ref is returned, or it can be an actual Sony socket ref,
        in which case a SocketT is created for the Sony socket ref.

    \Input uSockRef     - socket reference

    \Output
        SocketT *       - pointer to imported socket, or NULL

    \Version 01/14/2005 (jbrookes)
*/
/************************************************************************************F*/
SocketT *SocketImport(intptr_t uSockRef)
{
    SocketStateT *pState = _Socket_pState;
    int32_t iProto, iProtoSize;
    SocketT *pSocket;

    // see if this socket is already in our socket list
    NetCritEnter(NULL);
    for (pSocket = pState->pSockList; pSocket != NULL; pSocket = pSocket->pNext)
    {
        if (pSocket == (SocketT *)uSockRef)
        {
            break;
        }
    }
    NetCritLeave(NULL);

    // if socket is in socket list, just return it
    if (pSocket != NULL)
    {
        return(pSocket);
    }

    //$$ TODO - this assumes AF_INET

    // get info from socket ref
    iProtoSize = sizeof(iProto);
    if (getsockopt((SOCKET)uSockRef, 0, SO_TYPE, (char *)&iProto, &iProtoSize) != SOCKET_ERROR)
    {
        // create the socket (note: winsock socket types directly map to dirtysock socket types)
        pSocket = _SocketOpen(uSockRef, pSocket->iFamily, iProto, 0);

        // update local and remote addresses
        SocketInfo(pSocket, 'bind', 0, &pSocket->LocalAddr, sizeof(pSocket->LocalAddr));
        SocketInfo(pSocket, 'peer', 0, &pSocket->RemoteAddr, sizeof(pSocket->RemoteAddr));

        // mark it as imported
        pSocket->uImported = 1;
    }

    return(pSocket);
}

/*F*************************************************************************************/
/*!
    \Function SocketRelease

    \Description
        Release an imported socket.

    \Input *pSocket   - pointer to socket

    \Version 01/14/2005 (jbrookes)
*/
/************************************************************************************F*/
void SocketRelease(SocketT *pSocket)
{
    // if it wasn't imported, nothing to do
    if (!pSocket->uImported)
    {
        return;
    }

    // dispose of SocketT, but leave the sockref alone
    _SocketClose(pSocket, FALSE);
}

/*F*************************************************************************************/
/*!
    \Function SocketShutdown

    \Description
        Perform partial/complete shutdown of socket indicating that either sending
        and/or receiving is complete.

    \Input *pSocket - socket reference
    \Input iHow     - SOCK_NOSEND and/or SOCK_NORECV

    \Output
        int32_t     - zero

    \Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
int32_t SocketShutdown(SocketT *pSocket, int32_t iHow)
{
    // no shutdown for an invalid socket
    if (pSocket->uSocket == INVALID_SOCKET)
    {
        return(0);
    }

    // translate how
    if (iHow == SOCK_NOSEND)
    {
        iHow = SD_SEND;
    }
    else if (iHow == SOCK_NORECV)
    {
        iHow = SD_RECEIVE;
    }
    else if (iHow == (SOCK_NOSEND|SOCK_NORECV))
    {
        iHow = SD_BOTH;
    }

    pSocket->uShutdown |= iHow;
    shutdown(pSocket->uSocket, iHow);

    return(0);
}

/*F*************************************************************************************/
/*!
    \Function SocketBind

    \Description
        Bind a local address/port to a socket.

    \Input *pSocket - socket reference
    \Input *pName   - local address/port
    \Input iNameLen - length of name

    \Output
        int32_t     - standard network error code (SOCKERR_xxx)

    \Notes
        If either address or port is zero, then they are filled in automatically.

    \Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
int32_t SocketBind(SocketT *pSocket, const struct sockaddr *pName, int32_t iNameLen)
{
    SocketStateT *pState = _Socket_pState;
    struct sockaddr_in6 SockAddr6;
    int32_t iResult;

    #if !defined(DIRTYCODE_XBOXONE)
    struct sockaddr BindAddr;

    // bind to specific address?
    if ((SockaddrInGetAddr(pName) == 0) && (pState->uAdapterAddress != 0))
    {
        ds_memcpy_s(&BindAddr, sizeof(BindAddr), pName, sizeof(*pName));
        SockaddrInSetAddr(&BindAddr, pState->uAdapterAddress);
        pName = &BindAddr;
    }
    #endif

    // is the bind port a virtual port?
    if (pSocket->iType == SOCK_DGRAM)
    {
        int32_t iPort;
        uint16_t uPort;

        if ((uPort = SockaddrInGetPort(pName)) != 0)
        {
            // find virtual port in list
            for (iPort = 0; (iPort < SOCKET_MAXVIRTUALPORTS) && (pState->aVirtualPorts[iPort] != uPort); iPort++)
                ;
            if (iPort < SOCKET_MAXVIRTUALPORTS)
            {
                // acquire socket critical section
                NetCritEnter(&pSocket->RecvCrit);

                // check to see if the socket is bound
                if (pSocket->bVirtual && (pSocket->uVirtualPort != 0))
                {
                    NetPrintf(("dirtynetwin: [%p] failed to bind socket to %u which was already bound to port %u virtual\n", pSocket, uPort, pSocket->uVirtualPort));
                    NetCritLeave(&pSocket->RecvCrit);
                    return(pSocket->iLastError = SOCKERR_INVALID);
                }

                // close winsock socket
                NetPrintf(("dirtynetwin: [%p] making socket bound to port %d virtual\n", pSocket, uPort));
                if (pSocket->uSocket != INVALID_SOCKET)
                {
                    shutdown(pSocket->uSocket, SOCK_NOSEND);
                    closesocket(pSocket->uSocket);
                    pSocket->uSocket = INVALID_SOCKET;
                }
                /* increase socket queue size; this protects virtual sockets from having data pushed into
                   them and overwriting previous data that hasn't been read yet */
                pSocket->pRecvQueue = SocketPacketQueueResize(pSocket->pRecvQueue, 4, pState->iMemGroup, pState->pMemGroupUserData);
                // mark socket as virtual
                pSocket->uVirtualPort = uPort;
                pSocket->bVirtual = TRUE;

                // release socket critical section
                NetCritLeave(&pSocket->RecvCrit);

                return(0);
            }
        }
    }

    #if defined(DIRTYCODE_XBOXONE)
    // set up IPv6 address (do not use an IPv4-mapped IPv6 here as it will disallow IPv6 address use)
    ds_memclr(&SockAddr6, sizeof(SockAddr6));
    SockAddr6.sin6_family = AF_INET6;
    SockAddr6.sin6_port = SocketNtohs(SockaddrInGetPort(pName));
    pName = (const struct sockaddr *)&SockAddr6;
    iNameLen = sizeof(SockAddr6);
    #else
    // translate IPv4 -> IPv6 address (if needed)
    if ((pSocket->iFamily == AF_INET6) && (pName->sa_family != AF_INET6))
    {
        ds_memclr(&SockAddr6, sizeof(SockAddr6));
        SockAddr6.sin6_family = AF_INET6;
        SockAddr6.sin6_port = SocketNtohs(SockaddrInGetPort(pName));
        pName = SocketAddrMapTranslate(&_Socket_pState->AddrMap, (struct sockaddr *)&SockAddr6, pName, &iNameLen);
    }
    #endif

    // execute the bind
    iResult = _XlatError(bind(pSocket->uSocket, pName, iNameLen));

    // notify read thread that socket is ready to be read from
    if ((iResult == SOCKERR_NONE) && pSocket->bAsyncRecv)
    {
        WSASetEvent(pState->hEvent);
    }

    // return result to caller
    pSocket->iLastError = iResult;
    return(pSocket->iLastError);
}

/*F*************************************************************************************/
/*!
    \Function SocketConnect

    \Description
        Initiate a connection attempt to a remote host.

    \Input *pSocket - socket reference
    \Input *pName   - pointer to name of socket to connect to
    \Input iNameLen - length of name

    \Output
        int32_t     - standard network error code (SOCKERR_xxx)

    \Notes
        Only has real meaning for stream protocols. For a datagram protocol, this
        just sets the default remote host.

    \Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
int32_t SocketConnect(SocketT *pSocket, struct sockaddr *pName, int32_t iNameLen)
{
    struct sockaddr_in6 SockAddr6;
    struct sockaddr SockAddr, *pSockAddr = NULL;
    int32_t iResult, iSockAddrLen = 0;

    // mark as not open
    pSocket->iOpened = 0;

    // save connect address
    ds_memcpy_s(&pSocket->RemoteAddr, sizeof(pSocket->RemoteAddr), pName, sizeof(*pName));

    // init sockaddr
    if (pSocket->iFamily == AF_INET)
    {
        SockaddrInit(&SockAddr, AF_INET);
        pSockAddr = &SockAddr;
        iSockAddrLen = sizeof(SockAddr);
    }
    else if (pSocket->iFamily == AF_INET6)
    {
        // initialize family of SockAddr6
        SockaddrInit6(&SockAddr6, AF_INET6);
        pSockAddr = (struct sockaddr *)&SockAddr6;
        iSockAddrLen = sizeof(SockAddr6);
    }

    #if !defined(DIRTYCODE_XBOXONE)
    /* execute an explicit bind - this allows us to specify a non-zero local address
       or port (see SocketBind()).  a SOCKERR_INVALID result here means the socket has
       already been bound, so we ignore that particular error */
    if (((iResult = SocketBind(pSocket, pSockAddr, iSockAddrLen)) < 0) && (iResult != SOCKERR_INVALID))
    {
        pSocket->iLastError = iResult;
        return(pSocket->iLastError);
    }
    #endif

    // translate to IPv6 if required
    if (pSocket->iFamily == AF_INET6)
    {
        pName = SocketAddrMapTranslate(&_Socket_pState->AddrMap, pSockAddr, pName, &iNameLen);
    }

    // execute the connect
    NetPrintfVerbose((pSocket->iVerbose, 0, "dirtynetwin: connecting to %A\n", pName));
    iResult = _XlatError(connect(pSocket->uSocket, pName, iNameLen));

    // notify read thread that socket is ready to be read from
    if ((iResult == SOCKERR_NONE) && pSocket->bAsyncRecv)
    {
        WSASetEvent(_Socket_pState->hEvent);
    }

    // return result to caller
    pSocket->iLastError = iResult;
    return(pSocket->iLastError);
}

/*F*************************************************************************************/
/*!
    \Function SocketListen

    \Description
        Start listening for an incoming connection on the socket.  The socket must already
        be bound and a stream oriented connection must be in use.

    \Input *pSocket - socket reference to bound socket (see SocketBind())
    \Input iBackLog - number of pending connections allowed

    \Output
        int32_t         - standard network error code (SOCKERR_xxx)

    \Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
int32_t SocketListen(SocketT *pSocket, int32_t iBackLog)
{
    // do the listen
    pSocket->iLastError = _XlatError(listen(pSocket->uSocket, iBackLog));
    return(pSocket->iLastError);
}

/*F*************************************************************************************/
/*!
    \Function SocketAccept

    \Description
        Accept an incoming connection attempt on a socket.

    \Input *pSocket     - socket reference to socket in listening state (see SocketListen())
    \Input *pAddr       - pointer to storage for address of the connecting entity, or NULL
    \Input *pAddrLen    - pointer to storage for length of address, or NULL

    \Output
        SocketT *       - the accepted socket, or NULL if not available

    \Notes
        The integer pointed to by addrlen should on input contain the number of characters
        in the buffer addr.  On exit it will contain the number of characters in the
        output address.

    \Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
SocketT *SocketAccept(SocketT *pSocket, struct sockaddr *pAddr, int32_t *pAddrLen)
{
    SocketT *pOpen = NULL;
    SOCKET iIncoming;

    pSocket->iLastError = SOCKERR_INVALID;

    // see if already connected
    if (pSocket->uSocket == INVALID_SOCKET)
    {
        return(NULL);
    }

    // make sure turn parm is valid
    if ((pAddr != NULL) && (*pAddrLen < sizeof(struct sockaddr)))
    {
        return(NULL);
    }

    #if !defined(DIRTYCODE_XBOXONE)
    // perform inet accept
    if (pSocket->iFamily == AF_INET)
    {
        iIncoming = accept(pSocket->uSocket, pAddr, pAddrLen);
        if (iIncoming != INVALID_SOCKET)
        {
            pOpen = _SocketOpen(iIncoming, pSocket->iFamily, pSocket->iType, pSocket->iProto);
            pSocket->iLastError = SOCKERR_NONE;
        }
        else
        {
            // use a negative error code to force _XlatError to internally call WSAGetLastError() and translate the obtained result
            pSocket->iLastError = _XlatError(-99);

            if (WSAGetLastError() != WSAEWOULDBLOCK)
            {
                NetPrintf(("dirtynetwin: accept() failed err=%d\n", WSAGetLastError()));
            }
        }
    }
    #endif

    // perform inet6 accept
    if (pSocket->iFamily == AF_INET6)
    {
        struct sockaddr_in6 SockAddr6;
        int32_t iAddrLen;

        SockaddrInit6(&SockAddr6, AF_INET6);
        iAddrLen = sizeof(SockAddr6);
        iIncoming = accept(pSocket->uSocket, (struct sockaddr *)&SockAddr6, &iAddrLen);
        if (iIncoming != INVALID_SOCKET)
        {
            pOpen = _SocketOpen(iIncoming, pSocket->iFamily, pSocket->iType, pSocket->iProto);
            pSocket->iLastError = SOCKERR_NONE;
            // translate ipv6 to ipv4 virtual address
            SocketAddrMapAddress(&_Socket_pState->AddrMap, (struct sockaddr *)&SockAddr6, sizeof(SockAddr6));

            // save translated connecting info for caller
            SockaddrInit(pAddr, AF_INET);
            SocketAddrMapTranslate(&_Socket_pState->AddrMap, pAddr, (struct sockaddr *) &SockAddr6, pAddrLen);
        }
        else
        {
            // use a negative error code to force _XlatError to internally call WSAGetLastError() and translate the obtained result
            pSocket->iLastError = _XlatError(-99);

            if (WSAGetLastError() != WSAEWOULDBLOCK)
            {
                NetPrintf(("dirtynetwin: accept() failed err=%d\n", WSAGetLastError()));
            }
        }
    }

    // return the socket
    return(pOpen);
}

/*F*************************************************************************************/
/*!
    \Function SocketSendto

    \Description
        Send data to a remote host. The destination address is supplied along with
        the data. Should only be used with datagram sockets as stream sockets always
        send to the connected peer.

    \Input *pSocket - socket reference
    \Input *pBuf    - the data to be sent
    \Input iLen     - size of data
    \Input iFlags   - unused
    \Input *pTo     - the address to send to (NULL=use connection address)
    \Input iToLen   - length of address

    \Output
        int32_t     - standard network error code (SOCKERR_xxx)

    \Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
int32_t SocketSendto(SocketT *pSocket, const char *pBuf, int32_t iLen, int32_t iFlags, const struct sockaddr *pTo, int32_t iToLen)
{
    SocketStateT *pState = _Socket_pState;
    int32_t iResult;

    if (pSocket->bSendCbs)
    {
        // if installed, give socket callback right of first refusal
        if ((iResult = SocketSendCallbackInvoke(&pState->aSendCbEntries[0], pSocket, pSocket->iType, pBuf, iLen, pTo)) > 0)
        {
            return(iResult);
        }
    }

    // make sure socket ref is valid
    if (pSocket->uSocket == INVALID_SOCKET)
    {
        #if DIRTYCODE_LOGGING
        uint32_t uAddr = 0, uPort = 0;
        if (pTo)
        {
            uAddr = SockaddrInGetAddr(pTo);
            uPort = SockaddrInGetPort(pTo);
        }
        NetPrintf(("dirtynetwin: attempting to send to %a:%d on invalid socket\n", uAddr, uPort));
        #endif
        pSocket->iLastError = SOCKERR_INVALID;
        return(pSocket->iLastError);
    }

    // handle optional data rate throttling
    if ((iLen = SocketRateThrottle(&pSocket->SendRate, pSocket->iType, iLen, "send")) == 0)
    {
        return(0);
    }

    // use appropriate version
    if (pTo == NULL)
    {
        iResult = send(pSocket->uSocket, pBuf, iLen, 0);
        pTo = &pSocket->RemoteAddr;
    }
    else
    {
        struct sockaddr_in6 SockAddr6;
        if (pSocket->iFamily == AF_INET6)
        {
            SockaddrInit6(&SockAddr6, AF_INET6);
            iToLen = sizeof(SockAddr6);
            SocketAddrMapTranslate(&pState->AddrMap, (struct sockaddr *)&SockAddr6, pTo, &iToLen);
            pTo = (struct sockaddr *)&SockAddr6;
        }
        iResult = sendto(pSocket->uSocket, pBuf, iLen, 0, pTo, iToLen);
    }

    // update data rate estimation
    SocketRateUpdate(&pSocket->SendRate, iResult, "send");

    // return bytes sent
    pSocket->iLastError = _XlatError(iResult);
    return(pSocket->iLastError);
}

/*F*************************************************************************************/
/*!
    \Function SocketRecvfrom

    \Description
        Receive data from a remote host. If socket is a connected stream, then data can
        only come from that source. A datagram socket can receive from any remote host.

    \Input *pSocket     - socket reference
    \Input *pBuf        - buffer to receive data
    \Input iLen         - length of recv buffer
    \Input iFlags       - unused
    \Input *pFrom       - address data was received from (NULL=ignore)
    \Input *pFromLen    - length of address

    \Output
        int32_t         - positive=data bytes received, else standard error code

    \Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
int32_t SocketRecvfrom(SocketT *pSocket, char *pBuf, int32_t iLen, int32_t iFlags, struct sockaddr *pFrom, int32_t *pFromLen)
{
    int32_t iRecv = 0, iRecvErr = 0;

    // handle rate throttling, if enabled
    if ((iLen = SocketRateThrottle(&pSocket->RecvRate, pSocket->iType, iLen, "recv")) == 0)
    {
        return(0);
    }

    // handle if the socket was killed
    if (pSocket->pKill != NULL)
    {
        pSocket->iLastError = SOCKERR_INVALID;
        return(pSocket->iLastError);
    }

    // sockets marked for async recv had actual receive operation take place in the thread
    if (pSocket->bAsyncRecv)
    {
        uint32_t bSocketPacketQueueFull;

        // acquire socket receive critical section
        NetCritEnter(&pSocket->RecvCrit);

        // remember if packet queue was full prior to calling _SocketRecvfromPacketQueue()
        bSocketPacketQueueFull = SocketPacketQueueStatus(pSocket->pRecvQueue, 'pful');

        iRecv = _SocketRecvfromPacketQueue(pSocket, pBuf, iLen, pFrom, pFromLen);

        // when data is obtained from the packet queue, we lose visibility on system socket errors
        pSocket->iLastError = SOCKERR_NONE;

        /* If the socket is not virtual and if no packet extracted from the packet queue,
           then consider polling the physical socket directly.

           Continuous polling of SocketRecvfrom by the application layer can result in
           a complete drain of the socket packet queue. At that point, it is very possible
           that there is inbound traffic sitting in the system socket receive buffer
           because the socket recv thread is not adding the socket to the poll list when
           its packet queue is full. To optimize receive behavior, instead of waiting for
           the socket recv thread to wakeup and finally re-add this socket to the poll list,
           we immediately perform a direct recv call on the socket in case there is something
           waiting to be processed there. */
        if (!pSocket->bVirtual && (iRecv == 0))
        {
            // avoid performing direct recv call while the socket recv thread already has an async recv initiated on that system socket
            if (!pSocket->bRecvInp)
            {
                iRecv = _SocketRecvfrom(pSocket, pBuf, iLen, pFrom, pFromLen, &iRecvErr);
            }
            else
            {
                // fake WSAEWOULDBLOCK
                iRecv = -1;
                iRecvErr = WSAEWOULDBLOCK;
            }
        }

        // release socket receive critical section
        NetCritLeave(&pSocket->RecvCrit);

        /* If packet queue had reached "full" state, then _SocketRecvThread had not been
           adding that socket to the event list anymore. Consequently, if _SocketRecvThread
           is currently blocked on WSAWaitForMultipleEvents(), it may not wake up if there is
           inbound traffic on this socket. To work around this, we wake it up explicitly here. */
        if (bSocketPacketQueueFull)
        {
            WSASetEvent(_Socket_pState->hEvent);
        }
    }
    else
    {
        iRecv = _SocketRecvfrom(pSocket, pBuf, iLen, pFrom, pFromLen, &iRecvErr);
    }

    // do error conversion
    if (!pSocket->bVirtual && pSocket->iType == SOCK_STREAM)
    {
        iRecv = (iRecv == 0) ? SOCKERR_CLOSED : _XlatError0(iRecv, iRecvErr);
    }
    else
    {
        iRecv = (iRecv == 0) ? SOCKERR_NONE : _XlatError0(iRecv, iRecvErr);
    }

    // update data rate estimation
    SocketRateUpdate(&pSocket->RecvRate, pSocket->iLastError, "recv");

    // return the error code
    pSocket->iLastError = iRecv;
    return(pSocket->iLastError);
}

/*F*************************************************************************************/
/*!
    \Function SocketInfo

    \Description
        Return information about an existing socket.

    \Input *pSocket - socket reference
    \Input iInfo    - selector for desired information
    \Input iData    - selector specific
    \Input *pBuf    - [out] return buffer
    \Input iLen     - buffer length

    \Output
        int32_t     - size of returned data or error code (negative value)

    \Notes
        iInfo can be one of the following:

        \verbatim
            'addr' - return local address
            'conn' - who we are connecting to
            'bind' - return bind data (if pSocket == NULL, get socket bound to given port)
            'bndu' - return bind data (only with pSocket=NULL, get SOCK_DGRAM socket bound to given port)
            '?ip6' - return TRUE if ipv4 address specified in iData is virtual, and fill in pBuf with ipv6 address if not NULL
            'ladr' - get local address previously specified by user for subsequent SocketBind() operations (pc only)
            'maxp' - return configured max packet size
            'maxr' - return configured max recv rate (bytes/sec; zero=uncapped)
            'maxs' - return configured max send rate (bytes/sec; zero=uncapped)
            'pdrp' - return socket packet queue number of packets dropped
            'peer' - peer info (only valid if connected)
            'pmax' - return socket packet queue max depth
            'ratr' - return current recv rate estimation (bytes/sec)
            'rats' - return current send rate estimation (bytes/sec)
            'sdcf' - get installed send callback function pointer (iData specifies index in array)
            'sdcu' - get installed send callback userdata pointer (iData specifies index in array)
            'serr' - last socket error
            'psiz' - return socket packet queue max size
            'sock' - return windows socket associated with the specified DirtySock socket
            'spam' - return debug level for debug output
            'stat' - TRUE if connected, else FALSE
            'virt' - TRUE if socket is virtual, else FALSE
        \endverbatim

    \Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
int32_t SocketInfo(SocketT *pSocket, int32_t iInfo, int32_t iData, void *pBuf, int32_t iLen)
{
    SocketStateT *pState = _Socket_pState;

    // always zero results by default
    #if defined(DIRTYCODE_XBOXONE)
    if (pBuf != NULL)
    #else
    if ((pBuf != NULL) && (iInfo != 'ladr'))
    #endif
    {
        ds_memclr(pBuf, iLen);
    }

    // handle global socket options
    if (pSocket == NULL)
    {
        return(_SocketInfoGlobal(iInfo, iData, pBuf, iLen));
    }

    // return local bind data
    if (iInfo == 'bind')
    {
        int32_t iResult = -1;
        if (pSocket->bVirtual)
        {
            SockaddrInit((struct sockaddr *)pBuf, AF_INET);
            SockaddrInSetPort((struct sockaddr *)pBuf, pSocket->uVirtualPort);
            iResult = 0;
        }
        else if (pSocket->uSocket != INVALID_SOCKET)
        {
            struct sockaddr_in6 SockAddr6;
            iLen = sizeof(SockAddr6);
            if ((iResult = getsockname(pSocket->uSocket, (struct sockaddr *)&SockAddr6, &iLen)) == 0)
            {
                SockaddrInit((struct sockaddr *)pBuf, AF_INET);
                SockaddrInSetPort((struct sockaddr *)pBuf, SocketHtons(SockAddr6.sin6_port));
                SockaddrInSetAddr((struct sockaddr *)pBuf, SocketAddrMapAddress(&pState->AddrMap, (struct sockaddr *)&SockAddr6, sizeof(SockAddr6)));
            }
            iResult = _XlatError(iResult);
        }
        return(iResult);
    }

    // return configured max recv rate
    if (iInfo == 'maxr')
    {
        return(pSocket->RecvRate.uMaxRate);
    }

	// get packet queue info
#if SOCKET_ASYNCRECVTHREAD
	if ((iInfo == 'pdrp') || (iInfo == 'pmax'))
	{
		int32_t iResult;
		// acquire socket receive critical section
		NetCritEnter(&pSocket->RecvCrit);
		// get packet queue status
		iResult = SocketPacketQueueStatus(pSocket->pRecvQueue, iInfo);
		// release socket receive critical section
		NetCritLeave(&pSocket->RecvCrit);
		// return success
		return(iResult);
	}
#endif

    // return configured max send rate
    if (iInfo == 'maxs')
    {
        return(pSocket->SendRate.uMaxRate);
    }

    // return socket protocol
    if (iInfo == 'prot')
    {
        return(pSocket->iProto);
    }

    // return whether the socket is virtual or not
    if (iInfo == 'virt')
    {
        return(pSocket->bVirtual);
    }

    /* 
       make sure the socket is alive
       ** AFTER THIS POINT WE ENSURE THE SOCKET DESCRIPTOR AND PACKET QUEUE ARE VALID **
    */
    if (pSocket->pKill != NULL)
    {
        pSocket->iLastError = SOCKERR_INVALID;
        return(pSocket->iLastError);
    }

    // return peer info (only valid if connected)
    if ((iInfo == 'conn') || (iInfo == 'peer'))
    {
        getpeername(pSocket->uSocket, (struct sockaddr *)pBuf, &iLen);
        //$$TODO this needs to be IPv6, translate to virtual address for output
        return(0);
    }
    // get packet queue info
    if ((iInfo == 'pdrp') || (iInfo == 'pmax') || (iInfo == 'psiz'))
    {
        int32_t iResult;
        // acquire socket receive critical section
        NetCritEnter(&pSocket->RecvCrit);
        // get packet queue status
        iResult = SocketPacketQueueStatus(pSocket->pRecvQueue, iInfo);
        // release socket receive critical section
        NetCritLeave(&pSocket->RecvCrit);
        return(iResult);
    }

    // return current recv rate estimation
    if (iInfo == 'ratr')
    {
        return(pSocket->RecvRate.uCurRate);
    }

    // return current send rate estimation
    if (iInfo == 'rats')
    {
        return(pSocket->SendRate.uCurRate);
    }

    // return last socket error
    if (iInfo == 'serr')
    {
        return(pSocket->iLastError);
    }

    // return windows socket identifier
    if (iInfo == 'sock')
    {
        if (pBuf != NULL)
        {
            if (iLen == (int32_t)sizeof(pSocket->uSocket))
            {
                ds_memcpy(pBuf, &pSocket->uSocket, sizeof(pSocket->uSocket));
            }
        }
        return((int32_t)pSocket->uSocket);
    }

    // return socket status
    if (iInfo == 'stat')
    {
        fd_set FdRead, FdWrite, FdExcept;
        struct timeval TimeVal;

        // if not a connected socket, return TRUE
        if (pSocket->iType != SOCK_STREAM)
        {
            return(1);
        }

        // if not connected, use select to determine connect
        if (pSocket->iOpened == 0)
        {
            // setup write/exception lists so we can select against the socket
            FD_ZERO(&FdWrite);
            FD_ZERO(&FdExcept);
            FD_SET(pSocket->uSocket, &FdWrite);
            FD_SET(pSocket->uSocket, &FdExcept);
            TimeVal.tv_sec = TimeVal.tv_usec = 0;
            if (select(pSocket->uSocket + 1, NULL, &FdWrite, &FdExcept, &TimeVal) > 0)
            {
                // if we got an exception, that means connect failed
                if (FdExcept.fd_count > 0)
                {
                    NetPrintfVerbose((pSocket->iVerbose, 0, "dirtynetwin: connection failed\n"));
                    pSocket->iOpened = -1;
                }
                // if socket is writable, that means connect succeeded
                else if (FdWrite.fd_count > 0)
                {
                    NetPrintfVerbose((pSocket->iVerbose, 0, "dirtynetwin: connection open\n"));
                    pSocket->iOpened = 1;
                }
            }
        }

        // if previously connected, make sure connect still valid
        if (pSocket->iOpened > 0)
        {
            FD_ZERO(&FdRead);
            FD_ZERO(&FdExcept);
            FD_SET(pSocket->uSocket, &FdRead);
            FD_SET(pSocket->uSocket, &FdExcept);
            TimeVal.tv_sec = TimeVal.tv_usec = 0;
            if (select(pSocket->uSocket + 1, &FdRead, NULL, &FdExcept, &TimeVal) > 0)
            {
                // if we got an exception, that means connect failed (usually closed by remote peer)
                if (FdExcept.fd_count > 0)
                {
                    NetPrintfVerbose((pSocket->iVerbose, 0, "dirtynetwin: connection failure\n"));
                    pSocket->iOpened = -1;
                }
                else if (FdRead.fd_count > 0)
                {
                    u_long uAvailBytes = 1; // u_long is required by ioctlsocket
                    // if socket is readable but there's no data available, connect was closed
                    // uAvailBytes might be less than actual bytes, so it can only be used for zero-test
                    if ((ioctlsocket(pSocket->uSocket, FIONREAD, &uAvailBytes) != 0) || (uAvailBytes == 0))
                    {
                        pSocket->iLastError = SOCKERR_CLOSED;
                        NetPrintfVerbose((pSocket->iVerbose, 0, "dirtynetwin: connection closed (wsaerr=%d)\n", (uAvailBytes==0) ? WSAECONNRESET : WSAGetLastError()));
                        pSocket->iOpened = -1;
                    }
                }
            }
        }

        // return connect status
        return(pSocket->iOpened);
    }

    return(-1);
}

/*F*************************************************************************************/
/*!
    \Function SocketCallback

    \Description
        Register a callback routine for notification of socket events.  Also includes
        timeout support.

    \Input *pSocket - socket reference
    \Input iMask    - valid callback events (CALLB_NONE, CALLB_SEND, CALLB_RECV)
    \Input iIdle    - if nonzero, specifies the number of ticks between idle calls
    \Input *pRef    - user data to be passed to proc
    \Input *pProc   - user callback

    \Output
        int32_t     - zero

    \Notes
        A callback will reset the idle timer, so when specifying a callback and an
        idle processing time, the idle processing time represents the maximum elapsed
        time between calls.

    \Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
#pragma optimize("", off) // make sure this block of code is not reordered
int32_t SocketCallback(SocketT *pSocket, int32_t iMask, int32_t iIdle, void *pRef, int32_t (*pProc)(SocketT *pSocket, int32_t iFlags, void *pRef))
{
    pSocket->uCallIdle = iIdle;
    pSocket->uCallMask = iMask;
    pSocket->pCallRef = pRef;
    pSocket->pCallback = pProc;
    return(0);
}
#pragma optimize("", on)

/*F*************************************************************************************/
/*!
    \Function SocketControl

    \Description
        Process a control message (type specific operation)

    \Input *pSocket - socket to control, or NULL for module-level option
    \Input iOption  - the option to pass
    \Input iData1   - message specific parm
    \Input *pData2  - message specific parm
    \Input *pData3  - message specific parm

    \Output
        int32_t     - message specific result (-1=unsupported message, -2=no such module)

    \Notes
        iOption can be one of the following:

        \verbatim
            'afam' - set internet address family to use for socket operations (defaults to AF_INET6, AF_INET is also supported))
            'arcv' - set async receive enable/disable (default enabled for DGRAM/RAW, disabled for TCP)
            'conn' - handle connect message
            'disc' - handle disconnect message
            '+ip6' - add an IPv6 address into the mapping table and return a virtual IPv4 address to reference it
            '-ip6' - del an IPv6 address from the mapping table
            '~ip6' - remap an existing IPv6 address in the mapping table
            'keep' - set TCP keep-alive settings on PC (iData1=enable/disable, iData2=keep-alive time, iData3=keep-alive interval)
            'ladr' - set local address for subsequent SocketBind() operations (pc only)
            'maxp' - set max udp packet size
            'maxr' - set max recv rate (bytes/sec; zero=uncapped)
            'maxs' - set max send rate (bytes/sec; zero=uncapped)
            'nbio' - set nonblocking/blocking mode (TCP only, iData1=TRUE (nonblocking) or FALSE (blocking))
            'ndly' - set TCP_NODELAY state for given stream socket (iData1=zero or one)
            'pdev' - set simulated packet deviation
            'plat' - set simulated packet latency
            'plos' - set simulated packet loss
            'poll' - execute blocking wait on given socket list (pData2) or all sockets (pData2=NULL), 64 max sockets in milliseconds
            'poln' - execute blocking wait on given socket list (pData2) or all sockets (pData2=NULL), 64 max sockets in microseconds
            'pque' - set socket packet queue depth
            'push' - push data into given socket (iData1=size, pData2=data ptr, pData3=sockaddr ptr)
            'rbuf' - set socket recv buffer size
            'sbuf' - set socket send buffer size
            'scbk' - enable/disable "send callbacks usage" on specified socket (defaults to enable)
            'sdcb' - set/unset send callback (iData1=TRUE for set - FALSE for unset, pData2=callback, pData3=callref)
            'soli' - set SO_LINGER On the specified socket, iData1 is timeout in seconds
            'spam' - set debug level for debug output
            'vadd' - add a port to virtual port list
            'vdel' - del a port from virtual port list
        \endverbatim

    \Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
int32_t SocketControl(SocketT *pSocket, int32_t iOption, int32_t iData1, void *pData2, void *pData3)
{
    SocketStateT *pState = _Socket_pState;
    int32_t iResult;

    // handle global control
    if (pSocket == NULL)
    {
        return(_SocketControlGlobal(iOption, iData1, pData2, pData3));
    }

    // set async recv enable
    if (iOption == 'arcv')
    {
        // set socket async recv flag
        pSocket->bAsyncRecv = iData1 ? TRUE : FALSE;
        // wake up recvthread to update socket polling
        WSASetEvent(pState->hEvent);
        return(0);
    }
    // set max recv rate
    if (iOption == 'maxr')
    {
        NetPrintf(("dirtynetwin: setting max recv rate to %d bytes/sec\n", iData1));
        pSocket->RecvRate.uMaxRate = iData1;
        return(0);
    }
    // set max send rate
    if (iOption == 'maxs')
    {
        NetPrintf(("dirtynetwin: setting max send rate to %d bytes/sec\n", iData1));
        pSocket->SendRate.uMaxRate = iData1;
        return(0);
    }
    // enable/disable "send callbacks usage" on specified socket (defaults to enable)
    if (iOption == 'scbk')
    {
        if (pSocket->bSendCbs != (iData1?TRUE:FALSE))
        {
            NetPrintf(("dirtynetwin: send callbacks usage changed from %s to %s for socket ref %p\n", (pSocket->bSendCbs?"ON":"OFF"), (iData1?"ON":"OFF"), pSocket));
            pSocket->bSendCbs = (iData1?TRUE:FALSE);
        }
        return(0);
    }
    // set debug level
    if (iOption == 'spam')
    {
        // per-socket debug level
        pSocket->iVerbose = iData1;
        return(0);
    }

    /* 
       make sure the socket is alive
       ** AFTER THIS POINT WE ENSURE THE SOCKET DESCRIPTOR AND PACKET QUEUE ARE VALID **
    */
    if (pSocket->pKill != NULL)
    {
        pSocket->iLastError = SOCKERR_INVALID;
        return(pSocket->iLastError);
    }

    #if !defined(DIRTYCODE_XBOXONE)
    // configure TCP keep-alive
    if (iOption == 'keep')
    {
        struct tcp_keepalive TcpKeepAlive;
        DWORD dwBytesReturned;

        if (pSocket->iType != SOCK_STREAM)
        {
            NetPrintf(("dirtynetwin: [%p] 'keep' selector can only be used on a SOCK_STREAM socket\n", pSocket));
            return(-1);
        }
        if (pSocket->uSocket == INVALID_SOCKET)
        {
            NetPrintf(("dirtynetwin: [%p] 'keep' selector used with an invalid socket\n", pSocket));
            return(-1);
        }

        // initialize tcpkeep alive structure
        ds_memclr(&TcpKeepAlive, sizeof(TcpKeepAlive));

        TcpKeepAlive.onoff = (uint8_t)iData1;              // on/off
        if (TcpKeepAlive.onoff)
        {
            TcpKeepAlive.keepalivetime = *(uint32_t*)pData2;     // timeout in ms
            TcpKeepAlive.keepaliveinterval = *(uint32_t*)pData3; // interval in ms
        }
        else
        {
            TcpKeepAlive.keepalivetime = 0;     // timeout in ms
            TcpKeepAlive.keepaliveinterval = 0; // interval in ms
        }

        if ((iResult = WSAIoctl(pSocket->uSocket, SIO_KEEPALIVE_VALS, (void *)&TcpKeepAlive, sizeof(TcpKeepAlive), NULL, 0, &dwBytesReturned, NULL, NULL)) == 0)
        {
            pSocket->iLastError = SOCKERR_NONE;
            NetPrintfVerbose((pSocket->iVerbose, 1, "dirtynetwin: [%p] successfully %s the TCP keep-alive (timeout=%dms, interval=%dms)\n", pSocket,
                iData1?"enabled":"disabled", TcpKeepAlive.keepalivetime, TcpKeepAlive.keepaliveinterval));
        }
        else
        {
            pSocket->iLastError = _XlatError(iResult);
            NetPrintf(("dirtynetwin: [%p] failed to %s TCP keep-alive for low-level socket (err = %d)\n", pSocket,
                iData1?"enable":"disable", WSAGetLastError()));
        }

        return(pSocket->iLastError);
    }
    #endif
    // if a stream socket, set nonblocking/blocking mode
    if ((iOption == 'nbio') && (pSocket->iType == SOCK_STREAM))
    {
        uint32_t uNbio = (uint32_t)iData1;
        iResult = ioctlsocket(pSocket->uSocket, FIONBIO, (u_long *)&uNbio);
        pSocket->iLastError = _XlatError(iResult);
        NetPrintf(("dirtynetwin: setting socket 0x%p to %s mode %s (LastError=%d).\n", pSocket, iData1 ? "nonblocking" : "blocking", iResult ? "failed" : "succeeded", pSocket->iLastError));
        return(pSocket->iLastError);
    }
    // if a stream socket, set TCP_NODELAY state
    if ((iOption == 'ndly') && (pSocket->iType == SOCK_STREAM))
    {
        iResult = setsockopt(pSocket->uSocket, IPPROTO_TCP, TCP_NODELAY, (const char *)&iData1, sizeof(iData1));
        pSocket->iLastError = _XlatError(iResult);
        return(pSocket->iLastError);
    }
    // set simulated packet loss or packet latency
    if ((iOption == 'pdev') || (iOption == 'plat') || (iOption == 'plos'))
    {
        // acquire socket receive critical section
        NetCritEnter(&pSocket->RecvCrit);
        // forward selector to packet queue
        iResult = SocketPacketQueueControl(pSocket->pRecvQueue, iOption, iData1);
        // release socket receive critical section
        NetCritLeave(&pSocket->RecvCrit);
        return(iResult);
    }

    // change packet queue size
    if (iOption == 'pque')
    {
        if (pSocket->bAsyncRecv)
        {
            pSocket->iPacketQueueResizePending = iData1;
        }
        else
        {
            // acquire socket receive critical section
            NetCritEnter(&pSocket->RecvCrit);
            // resize the queue
            pSocket->pRecvQueue = SocketPacketQueueResize(pSocket->pRecvQueue, iData1, pState->iMemGroup, pState->pMemGroupUserData);
            // release socket receive critical section
            NetCritLeave(&pSocket->RecvCrit);
        }
        // return success
        return(0);
    }

    // push data into receive buffer
    if (iOption == 'push')
    {
        // acquire socket critical section
        NetCritEnter(&pSocket->RecvCrit);

        // don't allow data that is too large (for the buffer) to be pushed
        if (iData1 > SOCKET_MAXUDPRECV)
        {
            NetPrintf(("dirtynetwin: request to push %d bytes of data discarded (max=%d)\n", iData1, SOCKET_MAXUDPRECV));
            NetCritLeave(&pSocket->RecvCrit);
            return(-1);
        }

        // add packet to queue
        SocketPacketQueueAdd(pSocket->pRecvQueue, (uint8_t *)pData2, iData1, (struct sockaddr *)pData3);

        // release socket critical section
        NetCritLeave(&pSocket->RecvCrit);

        // see if we should issue callback
        if ((pSocket->pCallback != NULL) && (pSocket->uCallMask & CALLB_RECV))
        {
            pSocket->pCallback(pSocket, 0, pSocket->pCallRef);
        }
        return(0);
    }
    // set REUSEADDR
    if (iOption == 'radr')
    {
        iResult = setsockopt(pSocket->uSocket, SOL_SOCKET, SO_REUSEADDR, (const char *)&iData1, sizeof(iData1));
        pSocket->iLastError = _XlatError(iResult);
        return(pSocket->iLastError);
    }
    // set socket receive buffer size
    if ((iOption == 'rbuf') || (iOption == 'sbuf'))
    {
        int32_t iOldSize, iNewSize, iOptLen=4;
        int32_t iSockOpt = (iOption == 'rbuf') ? SO_RCVBUF : SO_SNDBUF;

        // get current buffer size
        getsockopt(pSocket->uSocket, SOL_SOCKET, iSockOpt, (char *)&iOldSize, &iOptLen);

        // set new size
        iResult = setsockopt(pSocket->uSocket, SOL_SOCKET, iSockOpt, (const char *)&iData1, sizeof(iData1));
        pSocket->iLastError = _XlatError(iResult);

        // get new size
        getsockopt(pSocket->uSocket, SOL_SOCKET, iSockOpt, (char *)&iNewSize, &iOptLen);
        NetPrintf(("dirtynetwin: setsockopt(%s) changed buffer size from %d to %d\n", (iOption == 'rbuf') ? "SO_RCVBUF" : "SO_SNDBUF",
            iOldSize, iNewSize));

        return(pSocket->iLastError);
    }
    // set SO_LINGER
    if (iOption == 'soli')
    {
        struct linger lingerOptions;
        lingerOptions.l_onoff = TRUE;
        lingerOptions.l_linger = iData1;
        iResult = setsockopt(pSocket->uSocket, SOL_SOCKET, SO_LINGER, (const char *)&lingerOptions, sizeof(lingerOptions));
        pSocket->iLastError = _XlatError(iResult);
        return(pSocket->iLastError);
    }
    // unhandled
    return(-1);
}

/*F*************************************************************************************/
/*!
    \Function SocketGetLocalAddr

    \Description
        Returns the "external" local address (ie, the address as a machine "out on
        the Internet" would see as the local machine's address).

    \Output
        uint32_t        - local address

    \Version 07/28/2003 (jbrookes)
*/
/************************************************************************************F*/
uint32_t SocketGetLocalAddr(void)
{
    #if defined(DIRTYCODE_XBOXONE)
    SocketStateT *pState = _Socket_pState;
    struct sockaddr InetAddr, HostAddr;

    if ((pState->uLocalAddr == 0) || (pState->uLocalAddr == 0x7f000001))
    {
        // create a remote internet address
        ds_memclr(&InetAddr, sizeof(InetAddr));
        InetAddr.sa_family = AF_INET;
        SockaddrInSetPort(&InetAddr, 79);
        SockaddrInSetAddr(&InetAddr, 0x9f990000);

        // ask socket to give us local address that can connect to it
        ds_memclr(&HostAddr, sizeof(HostAddr));
        SocketHost(&HostAddr, sizeof(HostAddr), &InetAddr, sizeof(InetAddr));

        pState->uLocalAddr = SockaddrInGetAddr(&HostAddr);
    }
    return(pState->uLocalAddr);
    #else
    struct sockaddr InetAddr, HostAddr;

    // create a remote internet address
    ds_memclr(&InetAddr, sizeof(InetAddr));
    InetAddr.sa_family = AF_INET;
    SockaddrInSetPort(&InetAddr, 79);
    SockaddrInSetAddr(&InetAddr, 0x9f990000);

    // ask socket to give us local address that can connect to it
    ds_memclr(&HostAddr, sizeof(HostAddr));
    SocketHost(&HostAddr, sizeof(HostAddr), &InetAddr, sizeof(InetAddr));

    return(SockaddrInGetAddr(&HostAddr));
    #endif
}

/*F*************************************************************************************/
/*!
    \Function _SocketLookupDone

    \Description
        Callback to determine if gethostbyname is complete.

    \Input *pHost   - pointer to host lookup record

    \Output
        int32_t     - zero=in progess, neg=done w/error, pos=done w/success

    \Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
static int32_t _SocketLookupDone(HostentT *pHost)
{
    // return current status
    return(pHost->done);
}

/*F*************************************************************************************/
/*!
    \Function _SocketLookupFree

    \Description
        Release resources used by gethostbyname.

    \Input *pHost   - pointer to host lookup record

    \Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
static void _SocketLookupFree(HostentT *pHost)
{
    // release resource
    pHost->refcount -= 1;
}

/*F*************************************************************************************/
/*!
    \Function _SocketLookupThread

    \Description
        Socket lookup thread

    \Input *pUserData   - pointer to host lookup record

    \Output
        int32_t     - zero

    \Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
static void _SocketLookupThread(void *pUserData)
{
    SocketStateT *pState = _Socket_pState;
    HostentT *pHost = (HostentT *)pUserData;
    struct addrinfo Hints, *pList = NULL;
    int32_t iResult;

    // setup lookup hints
    ds_memclr(&Hints, sizeof(Hints));
    Hints.ai_family = AF_UNSPEC;
    Hints.ai_socktype = SOCK_STREAM; // set specific socktype to limit to one result per type
    Hints.ai_protocol = IPPROTO_TCP; // set specific proto to limit to one result per type
    Hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG;

    // perform the blocking dns query
    if ((iResult = getaddrinfo(pHost->name, NULL, &Hints, &pList)) == 0)
    {
        struct addrinfo *pAddrInfo;

        // first loop we look for IPv4 addresses (prefer IPv4 to IPv6)
        for (pAddrInfo = pList; pAddrInfo != NULL; pAddrInfo = pAddrInfo->ai_next)
        {
            // verbose logging of address info if spam settings warrant
            NetPrintfVerbose((pState->iVerbose, 1, "dirtynetwin: addr=%A\n", pAddrInfo->ai_addr));
            NetPrintfVerbose((pState->iVerbose, 2, "dirtynetwin:   ai_flags=0x%08x\n", pAddrInfo->ai_flags));
            NetPrintfVerbose((pState->iVerbose, 2, "dirtynetwin:   ai_family=%d\n", pAddrInfo->ai_family));
            NetPrintfVerbose((pState->iVerbose, 2, "dirtynetwin:   ai_socktype=%d\n", pAddrInfo->ai_socktype));
            NetPrintfVerbose((pState->iVerbose, 2, "dirtynetwin:   ai_protocol=%d\n", pAddrInfo->ai_protocol));
            NetPrintfVerbose((pState->iVerbose, 2, "dirtynetwin:   name=%s\n", pAddrInfo->ai_canonname));

            // extract first IPv4 address we come across
            if ((pHost->addr == 0) && (pAddrInfo->ai_family == AF_INET))
            {
                pHost->addr = SocketAddrMapAddress(&pState->AddrMap, pAddrInfo->ai_addr, (int32_t)pAddrInfo->ai_addrlen);
            }
        }

        // if we haven't found one yet, look for any address, and pick the first one we come across
        for (pAddrInfo = pList; (pAddrInfo != NULL) && (pHost->addr == 0); pAddrInfo = pAddrInfo->ai_next)
        {
            pHost->addr = SocketAddrMapAddress(&pState->AddrMap, pAddrInfo->ai_addr, (int32_t)pAddrInfo->ai_addrlen);
        }

        // print selected address
        NetPrintfVerbose((pState->iVerbose, 0, "dirtynetwin: %s=%a\n", pHost->name, pHost->addr));

        // mark success
        pHost->done = 1;

        // add hostname to cache
        SocketHostnameCacheAdd(pState->pHostnameCache, pHost->name, pHost->addr, pState->iVerbose);

        // release memory
        freeaddrinfo(pList);
    }
    else
    {
        // unsuccessful
        NetPrintf(("dirtynetwin: getaddrinfo('%s', ...) failed err=%d\n", pHost->name, iResult));
        pHost->done = -1;
    }

    #if !defined(DIRTYCODE_XBOXONE)
    // note thread completion
    pHost->thread = 1;
    #endif
    // release thread-allocated refcount on hostname resource
    pHost->refcount -= 1;
}

/*F*************************************************************************************/
/*!
    \Function SocketLookup

    \Description
        Lookup a host by name and return the corresponding Internet address. Uses
        a callback/polling system since the socket library does not allow blocking.

    \Input *pText   - pointer to null terminated address string
    \Input iTimeout - number of milliseconds to wait for completion

    \Output
        HostentT *  - hostent struct that includes callback vectors

    \Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
HostentT *SocketLookup(const char *pText, int32_t iTimeout)
{
    SocketStateT *pState = _Socket_pState;
    int32_t iAddr, iResult;
    HostentT *pHost, *pHostRef;
    DirtyThreadConfigT ThreadConfig;

    NetPrintf(("dirtynetwin: looking up address for host '%s'\n", pText));

    // dont allow negative timeouts
    if (iTimeout < 0)
    {
        return(NULL);
    }

    // create new structure
    pHost = DirtyMemAlloc(sizeof(*pHost), SOCKET_MEMID, pState->iMemGroup, pState->pMemGroupUserData);
    ds_memclr(pHost, sizeof(*pHost));

    // setup callbacks
    pHost->Done = &_SocketLookupDone;
    pHost->Free = &_SocketLookupFree;
    // copy over the target address
    ds_strnzcpy(pHost->name, pText, sizeof(pHost->name));

    // check for refcounted lookup
    if ((pHostRef = SocketHostnameAddRef(&pState->pHostList, pHost, TRUE)) != NULL)
    {
        DirtyMemFree(pHost, SOCKET_MEMID, pState->iMemGroup, pState->pMemGroupUserData);
        return(pHostRef);
    }

    // check for dot notation, then check hostname cache
    if (((iAddr = SocketInTextGetAddr(pText)) != 0) || ((iAddr = SocketHostnameCacheGet(pState->pHostnameCache, pText, pState->iVerbose)) != 0))
    {
        // save the data
        pHost->addr = iAddr;
        pHost->done = 1;
        // mark as "thread complete" so _SocketLookupFree will release memory
        pHost->thread = 1;
        // return completed record
        return(pHost);
    }

    /* add an extra refcount for the thread; this ensures the host structure survives until the thread
       is done with it.  this must be done before thread creation. */
    pHost->refcount += 1;

    // configure dns thread parameters
    ds_memclr(&ThreadConfig, sizeof(ThreadConfig));
    ThreadConfig.pName = "SocketLookup";
    ThreadConfig.iAffinity = NetConnStatus('affn', 0, NULL, 0);
    ThreadConfig.iVerbosity = pState->iVerbose-1;

    // create dns lookup thread
    if ((iResult = DirtyThreadCreate(_SocketLookupThread, pHost, &ThreadConfig)) != 0)
    {
        NetPrintf(("dirtynetwin: unable to create socket lookup thread (err=%d)\n", iResult));
        pHost->done = -1;
        // remove refcount we just added
        pHost->refcount -= 1;
    }

    // return the host reference
    return(pHost);
}

/*F*************************************************************************************/
/*!
    \Function SocketHost

    \Description
        Return the host address that would be used in order to communicate with the given
        destination address.

    \Input *pHost    - local sockaddr struct
    \Input iHostLen  - length of structure (sizeof(host))
    \Input *pDest    - remote sockaddr struct
    \Input iDestLen  - length of structure (sizeof(dest))

    \Output
        int32_t      - zero=success, negative=error

    \Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
int32_t SocketHost(struct sockaddr *pHost, int32_t iHostLen, const struct sockaddr *pDest, int32_t iDestLen)
{
    SocketStateT *pState = _Socket_pState;
    struct sockaddr SockAddr;
    char strHostName[256];
    int32_t iErr;
    SOCKET iSock;

    // must be same kind of addresses
    if (iHostLen != iDestLen)
    {
        return(-1);
    }

    // do family specific lookup
    if (pDest->sa_family == AF_INET)
    {
        // make them match initially
        ds_memcpy(pHost, pDest, iHostLen);

        #if !defined(DIRTYCODE_XBOXONE)
        // respect adapter override
        if (pState->uAdapterAddress != 0)
        {
            SockaddrInSetAddr(pHost, pState->uAdapterAddress);
            return(0);
        }
        #endif

        // zero the address portion
        pHost->sa_data[2] = pHost->sa_data[3] = pHost->sa_data[4] = pHost->sa_data[5] = 0;

        // create a temp socket (must be datagram)
        iSock = socket(AF_INET, SOCK_DGRAM, 0);
        if (iSock != INVALID_SOCKET)
        {
            // use routing check if winsock 2.0
            if (pState->iVersion >= 0x200)
            {
                // get interface that would be used for this dest
                ds_memclr(&SockAddr, sizeof(SockAddr));
                if (WSAIoctl(iSock, SIO_ROUTING_INTERFACE_QUERY, (void *)pDest, iDestLen, &SockAddr, sizeof(SockAddr), (LPDWORD)&iHostLen, NULL, NULL) < 0)
                {
                    iErr = WSAGetLastError();
                    NetPrintf(("dirtynetwin: WSAIoctl(SIO_ROUTING_INTERFACE_QUERY) returned %d\n", iErr));
                }
                else
                {
                    // copy over the result
                    ds_memcpy(pHost->sa_data+2, SockAddr.sa_data+2, 4);
                }

                // convert loopback to requested address
                if (SockaddrInGetAddr(pHost) == INADDR_LOOPBACK)
                {
                    ds_memcpy(pHost->sa_data+2, pDest->sa_data+2, 4);
                }
            }

            // if no result, try connecting to socket then reading
            // (this only works on some non-microsoft stacks)
            if (SockaddrInGetAddr(pHost) == 0)
            {
                // connect to remote address
                if (connect(iSock, pDest, iDestLen) == 0)
                {
                    // query the host address
                    if (getsockname(iSock, &SockAddr, &iHostLen) == 0)
                    {
                        // just copy over the address portion
                        ds_memcpy(pHost->sa_data+2, SockAddr.sa_data+2, 4);
                    }
                }
            }

            // $$TODO evaluate possibility of unifying - (keep PC version)
            #if defined(DIRTYCODE_XBOXONE) && !defined(DIRTYCODE_GDK)
            // if still no result, use classic gethosthame/gethostbyname
            // (works for microsoft, not for non-microsoft stacks)
            if (SockaddrInGetAddr(pHost) == 0)
            {
                struct hostent *pHostent;

                // get machine name
                gethostname(strHostName, sizeof(strHostName));
                // lookup ip info
                pHostent = gethostbyname(strHostName);
                if (pHostent != NULL)
                {
                    ds_memcpy(pHost->sa_data+2, pHostent->h_addr_list[0], 4);
                }
            }
            #else
            // if still no result, use classic gethosthame/getaddrinfo
            // (works for microsoft, not for non-microsoft stacks)
            if (SockaddrInGetAddr(pHost) == 0)
            {
                int32_t iResult;
                struct addrinfo Hints, *pList = NULL;

                // get machine name
                ds_memclr(&Hints, sizeof(Hints));
                Hints.ai_family = AF_INET;
                Hints.ai_socktype = SOCK_STREAM;
                Hints.ai_protocol = IPPROTO_TCP;
                gethostname(strHostName, sizeof(strHostName));
                // lookup ip info
                if ((iResult = getaddrinfo(strHostName, NULL, &Hints, &pList)) == 0)
                {
                    ds_memcpy(pHost->sa_data+2, pList->ai_addr->sa_data+2, 4);
                }
                else
                {
                    NetPrintf(("dirtynetwin: getaddrinfo('%s', ...) failed err=%d\n", strHostName, iResult));
                }

                // release memory
                freeaddrinfo(pList);
            }
            #endif

            // close the socket
            closesocket(iSock);
        }
        return(0);
    }

    // unsupported family
    ds_memclr(pHost, iHostLen);
    return(-3);
}
