/*H*************************************************************************************************/
/*!

    \File    netgamedist.c

    \Description
        This file provides some upper layer protocol abstractions such as controller packet
        buffering and exchange logic.

    \Notes
    \verbatim

    From a "client" perspective:

        pRef->InpBufData is the inbound queue where inbound multipackets from server are accumulated.
            * pRef->InpBufData.iBeg identifies the oldest entry in that queue 
              (i.e. the next entry to be consumed locally)
            * pRef->InpBufData.iEnd identifies the next free entry in that queue
              (i.e. the next spot to write into when we read from the socket)
            * An overflow in that queue is always detected by comparing pRef->InpBufData.iBeg and pRef->InpBufData.iEnd.

        pRef->OutBufData is the outbound queue where outbound packets are accumulated pending transmission to the server.
            * pRef->OutBufData.iBeg identifies the oldest entry in that queue
              (i.e. the next entry to be sent over the network)
            * pRef->OutBufData.iEnd identifies the next free entry in that queue
              (i.e. the next spot to write into when user submits data)
            * SPECIFICITY: When pRef->OutBufData.iBeg is advanced, the entry that it used to point to is NOT YET invalidated
              because it still needs to be surfaced up to the user later when paired with a inbound bundle from the server. 
              To track those "pending entries" located before the position of pRef->OutBufData.iBeg, this third pointer
              is used: "pRef->InpBufData.iBeg+pRef->iIOOffset".  Notice that it consists of a pointer tracking the 
              other queue + an adjustment offset.
            * An overflow in that queue is always detected by comparing "pRef->InpBufData.iBeg+pRef->iIOOffset"
              and pRef->OutBufData.iEnd.

    From a "server" perspective:

        pRef->InpBufData is the inbound queue where inbound packets from a specific client are accumulated.
            * pRef->InpBufData.iBeg identifies the oldest entry in that queue
                (i.e. the next entry to be consumed locally)
            * pRef->InpBufData.iEnd identifies the next free entry in that queue
                (i.e. the next spot to write into when we read from the socket)
            * An overflow in that queue is always detected by comparing pRef->InpBufData.iBeg and pRef->InpBufData.iEnd.

        pRef->OutBufData is the outbound queue where outbound multipackets are accumulated pending transmission to a specific client.
            * pRef->OutBufData.iBeg identifies the oldest entry in that queue
                (i.e. the next entry to be sent over the network)
            * pRef->OutBufData.iEnd identifies the next free entry in that queue
              (i.e. the next spot to write into when the server has a bundle of paired inputs to submit for transmission)
            * An overflow in that queue is always detected by comparing pRef->OutBufData.iBeg and pRef->OutBufData.iEnd

    \endverbatim

    \Copyright
        Copyright (c) Tiburon Entertainment / Electronic Arts 2000-2018.  ALL RIGHTS RESERVED.

    \Version    1.0        12/20/00 (GWS) Based on split of GmClient.c
    \Version    1.1        12/31/01 (GWS) Cleaned up and made really platform independent
    \Version    1.2        12/03/09 (mclouatre) Added configurable run-time verbosity
*/
/*************************************************************************************************H*/


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

#include <string.h>
#include <stdio.h>

#include "DirtySDK/dirtysock.h"
#include "DirtySDK/dirtysock/dirtymem.h"
#include "DirtySDK/dirtysock/dirtylib.h"
#include "DirtySDK/game/netgamepkt.h"
#include "DirtySDK/game/netgamelink.h"
#include "DirtySDK/game/netgamedist.h"

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

#define NETGAMEDIST_VERBOSITY (2)

#define TIMING_DEBUG                    (0)
#define PING_DEBUG                      (0)
#define INPUTCHECK_LOGGING_DELAY        (15)    // 15 msec
#define GMDIST_META_ARRAY_SIZE          (32)    // how many past versions of sparse multipacket to keep

// PACKET_WINDOW can be overriden at build time with the nant global property called dirtysdk-distpktwindow-size
#ifndef PACKET_WINDOW
#define PACKET_WINDOW                   (64)
#endif

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

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

//! Describe an entry in the flat buffer.
typedef struct GameBufferLookupT
{
    uint32_t uInsertTime;   //!< time when packet was queued
    uint32_t uPos;          //!< indexes into OutBufData.pControllerIO or InpBufData.pControllerIO
    uint16_t uLen;
    uint16_t uLenSize;

// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
#ifdef ENABLED_LATENCY_METER
	uint64_t aLatencyBuf[IN_PHASE_NUM];
	uint64_t *pLatency;
#endif
// FIFA_END
} GameBufferLookupT;

//! Flat buffer structure. Implements a wrapping queue of packets.
typedef struct GameBufferDataT
{
    //! incoming and outgoing controller packets
    unsigned char *pControllerIO;
    //! length of the io buffer
    uint32_t uBufLen;
    // input/output lookup, addresses into pControllerIO
    GameBufferLookupT IOLookUp[PACKET_WINDOW];
    //! index of first packet to send/process in pControllerIO
    int32_t iBeg;
    //! index of last packet to send/process in pControllerIO
    int32_t iEnd;
} GameBufferDataT;

//! Describes one version of multipacket.
typedef struct NetGameDistMetaInfoT
{
    //! which entries are used
    uint32_t uMask;
    //! number of players used
    uint8_t uPlayerCount;
    //! version number
    uint8_t uVer;
} NetGameDistMetaInfoT;

//! netgamedist internal state
struct NetGameDistRefT
{
    //! module memory group
    int32_t iMemGroup;
    void *pMemGroupUserData;

    //! output buffer for packets and lookup table
    GameBufferDataT OutBufData;
    //! input buffer for packets and lookup table
    GameBufferDataT InpBufData;

    //! offset between the input and output queues
    int32_t iIOOffset;

    //! local sequence number
    uint32_t uLocalSeq;
    //! global sequence number
    uint32_t uGlobalSeq;

    //! external status monitoring
    NetGameLinkStatT NetGameLinkStats;

    //! current exchange rate
    int32_t iInputRate;
    //! input exchange window
    int32_t iInputWind;
    //! clamp the min window size
    int32_t iInputMini;
    //! clamp the max window size
    int32_t iInputMaxi;

    //! when to recalc window
    uint32_t uInpCalc;
    //! when packet was last send
    uint32_t uInpNext;

    //! netgamelink ref
    void *pNetGameLinkRef;
    //! netgamelink stat func
    NetGameDistStatProc *pStatProc;
    //! netgame send function
    NetGameDistSendProc *pSendProc;
    //! netgame recv function
    NetGameDistRecvProc *pRecvProc;

    NetGameDistDropProc *pDropProc;
    NetGameDistPeekProc *pPeekProc;

    NetGameDistLinkCtrlProc *pLinkCtrlProc;

    //! when bActAsServer is true the index is of the player that owns the dist 
    //! if bActAsServer is false the index is of the local player in the dist game
    uint32_t uDistIndex;

    //! the total number of players
    uint32_t uTotalPlyrs;

    //! true if we are receiving multi packets from dirtycast in OTP mode
    //! false if when inbound packets originated from the client in a 2 player mode
    uint32_t bRecvMulti;

    //! whether we are acting as the server.
    uint32_t bActAsServer;

    //! a max packet for use by input
    NetGameMaxPacketT MaxPkt;

    //! a max packet for use by input
    char aMultiBuf[NETGAME_DATAPKT_MAXSIZE];

    //! the number of writes to a position in the input queue, (dropproc)
    int32_t aPacketId[PACKET_WINDOW];

    //! latest stats received by each pClient
    NetGameDistStatT aRecvStats[GMDIST_MAX_CLIENTS];

    //! Error condition. Set during calls like update, if an error occurs.
    int32_t iErrCond;
    int32_t iLastSentDelta;
    uint32_t uSkippedInputCheckLogCount;
    uint32_t uLastInputCheckLogTick;
    
    int32_t iInboundDropPktCnt;
    int32_t iInboundPktCnt;
    int32_t iOutboundPktCnt;

    //! total wait time in input queue
    int32_t iWaitTimeTotal;

    //! total Input deqeued count
    int32_t iInboundDequeueCnt;

    //! total dist processing time
    int32_t iDistProcTimeTotal;

    //! total dist inputs processed
    int32_t iDistProcCnt;

    char strErrorText[GMDIST_ERROR_SIZE];

    //! whether we must update the flow control flags to the game server
    uint8_t bUpdateFlowCtrl;

    //! the range of meta info we must update the
    uint8_t uUpdateMetaInfoBeg;
    uint8_t uUpdateMetaInfoEnd;

    //! whether we are ready to send or not
    uint8_t bRdySend;

    //! whether we are ready to receive or not
    uint8_t bRdyRecv;

    //! whether remote is ready to send or not
    uint8_t bRdySendRemote;

    //! whether remote is ready to receive or not
    uint8_t bRdyRecvRemote;

    uint32_t uLocalCRC;
    uint8_t bLocalCRCValid;

    uint32_t uRemoteCRC;
    uint8_t bRemoteCRCValid;

    //! debug output verbosity
    uint8_t uVerbose;

    //! boolean indicating whether we want to surface CRC cahllenges from the GS
    uint8_t bCRCChallenges;

    //! boolean indicating whether we received meta information on the packet layout
    uint8_t bGotMetaInfo;

    //! boolean indicating whether we are sending sparse multi-packets
    uint8_t bSparse;

    //! Meta information about sparse multi-packets to send
    NetGameDistMetaInfoT aMetaInfoToSend[GMDIST_META_ARRAY_SIZE];

    //! Received meta information about sparse multi-packets (wraps around)
    NetGameDistMetaInfoT aMetaInfo[GMDIST_META_ARRAY_SIZE];

    //! The version meta information from the last peeked or queried packet
    uint32_t uLastQueriedVersion;
};

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

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

// Private variables

#if DIRTYCODE_LOGGING
// The following is meant to be indexed with the corresponding constants defined in netgamedist.h.
// Array contents need to be tailored if constants are removed, modified or added.
static char _strNetGameDistDataTypes[6][32] =
    {"INVALID",
     "GMDIST_DATA_NONE",
     "GMDIST_DATA_INPUT",
     "GMDIST_DATA_INPUT_DROPPABLE",
     "GMDIST_DATA_DISCONNECT",
     "GMDIST_DATA_NODATA"};
#endif

// Public variables


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


#if PING_DEBUG
/*F*************************************************************************************************/
/*!
    \Function    _PingHistory

    \Description
        Display the uPing history [DEBUG only]

    \Input *pStats      - pointer to NetGameLinkStat struct

    \Version 12/20/00 (GWS)
*/
/*************************************************************************************************F*/
static void _PingHistory(const NetGameLinkStatT *pStats)
{
    int32_t iPing;
    int32_t iIndex;
    char strMin[64];
    char strMax[64];
    char strAvg[64];
    char strCnt[64];
    const NetGameLinkHistT *pHist;
    static uint32_t uPrev = 0;

    // see if its time
    if (pStats->pingslot == uPrev)
    {
        return;
    }
    uPrev = pStats->pingslot;

    iPing = 0;

    for (iIndex = 0; iIndex < PING_HISTORY; ++iIndex)
    {
        pHist = pStats->pinghist + ((pStats->pingslot - iIndex) & (PING_HISTORY-1));
        strMin[iIndex] = '0'+pHist->min/50;
        strMax[iIndex] = '0'+pHist->max/50;
        strAvg[iIndex] = '0'+pHist->avg/50;
        strCnt[iIndex] = '0'+pHist->cnt;

        iPing += pHist->avg;
    }
    strMin[iIndex] = 0;
    strMax[iIndex] = 0;
    strAvg[iIndex] = 0;
    strCnt[iIndex] = 0;

    iPing /= PING_HISTORY;

    NetPrintf(("history(%d/%d): ping=%d, late=%d, calc=%d\n", pStats->pingslot, pStats->pingtick, pStats->ping, pStats->late, iPing));
    NetPrintf((" %s\n", strMin));
    NetPrintf((" %s\n", strMax));
    NetPrintf((" %s\n", strAvg));
    NetPrintf((" %s\n", strCnt));
}
#endif

/*F*************************************************************************************************/
/*!
    \Function    _SetDataPtrTry

    \Description
        returns the position in the input or output buffer for addition of a packet of length uLength.

    \Input *pRef    - reference pointer
    \Input *pBuffer - buffer pointer
    \Input uLength  - length of the next packet to be stored

    \Output
        int32_t     - pos if a space was found. -1 if the buffer cannot accomodate uLength

    \Version 02/08/07 (jrainy)
*/
/*************************************************************************************************F*/
static int32_t _SetDataPtrTry(NetGameDistRefT *pRef, GameBufferDataT *pBuffer, uint16_t uLength)
{
    uint32_t uIndexA, uIndexB;
    uint32_t uPosA;

    if (uLength > pBuffer->uBufLen)
    {
        ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "overflow in _SetDataPtrTry. requested length (%d) > buffer size (%d), ", uLength, pBuffer->uBufLen);
        NetPrintf(("netgamedist: [%p] critical failure in _SetDataPtrTry()\n", pRef));
        return(-1);
    }

    // identify the IOLookup index that points to the last-written entry (uIndexA), and find out where the corresponding next free byte exactly is (uPosA)
    uIndexA = GMDIST_Modulo(pBuffer->iEnd - 1, PACKET_WINDOW);
    uPosA = pBuffer->IOLookUp[uIndexA].uPos + pBuffer->IOLookUp[uIndexA].uLen;
    uPosA = ((uPosA + 3)&~3); // aligns posA to the next 4-byte boundary

    // identify the IOLookup index that points to the oldest valid entry (uIndexB)
    if ((pBuffer == &pRef->InpBufData) || pRef->bActAsServer)
    {
        /* for the input queue (inbound data from either server or client) or the server output queue (outbound data to client)
           the iBeg index identifies the oldest valid data */
        uIndexB = pBuffer->iBeg;
    }
    else
    {
        /* For the client output queue (outbound data to server), the iBeg index identifies the next packet to be
        sent to the server not the oldest valid data. The oldest valid data, i.e. pending data to be
        notified as paired by the game server, is rather always identified using a combination of
        the "inbound" iBeg and the iOOffset. */
        uIndexB = GMDIST_Modulo(pRef->InpBufData.iBeg + pRef->iIOOffset, PACKET_WINDOW);
    }

    // if the buffer is empty then we can just start filling it in from the beginning
    if (uIndexB == (unsigned)pBuffer->iEnd)
    {
        return(0);
    }
    else
    {
        /* note: uIndexB can be used to access valid data in IOLookup[] only if uIndexB != pBuffer->iEnd
                 because pBuffer->iEnd points to the next "free" entry... so it contains no valid data yet. */

        // find out where the last valid byte exactly is (uPosB)
        uint32_t uPosB = pBuffer->IOLookUp[uIndexB].uPos;

        if (uPosA >= uPosB)
        {
            // if we can't fit at the end, let's retry from the beginning
            if ((uPosA + uLength) > pBuffer->uBufLen)
            {
                uPosA = 0;
                // fall through to the next 'if'
            }
            else
            {
                return(uPosA);
            }
        }
        if (uPosB >= uPosA)
        {
            if ((uPosA + uLength) >= uPosB)
            {
                ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "overflow in _SetDataPtrTry. indexA was %d, indexB was %d, posA was %d, posB was %d, ", uIndexA, uIndexB, pBuffer->IOLookUp[uIndexA].uPos + pBuffer->IOLookUp[uIndexA].uLen, uPosB);

                NetPrintf(("netgamedist: [%p] %s buffer full.\n", pRef, ((pBuffer == &pRef->InpBufData)?"input":"output")));
                return(-1);
            }
            else
            {
                return(uPosA);
            }
        }
    }

    // unreachable code. If we get here something went terribly wrong.
    ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "_SetDataPtrTry reached unreachable code.");
    NetPrintf(("netgamedist: [%p] critical failure in _SetDataPtrTry() - unreachable code\n", pRef));

    return(-1);
}

/*F*************************************************************************************************/
/*!
    \Function    _SetDataPtr

    \Description
        Prepares the input or output buffer for addition of a packet of length uLength.

    \Input *pRef    - reference pointer
    \Input *pBuffer - buffer pointer
    \Input uLength  - length of the next packet to be stored

    \Output
        uint8_t     - TRUE if a space was found. FALSE if the buffer cannot accomodate uLength

    \Version 02/08/07 (jrainy)
*/
/*************************************************************************************************F*/
static uint8_t _SetDataPtr(NetGameDistRefT *pRef, GameBufferDataT *pBuffer, uint16_t uLength)
{
    int32_t uPos = _SetDataPtrTry(pRef, pBuffer, uLength);

    if (uPos != -1)
    {
        pBuffer->IOLookUp[pBuffer->iEnd].uPos = uPos;
        return(TRUE);
    }

    return(FALSE);
}

/*F*************************************************************************************************/
/*!
    \Function    _NetGameDistCheckWindow

    \Description
        Handle the window stretching. If we have too many unacknowledged packets, we start sending less often

    \Input *pRef    - The NetGameDist ref
    \Input iRemain  - Remaining frame time (before window stretching)
    \Input uTick    - Current tick

    \Version 12/21/09 (jrainy)
*/
/*************************************************************************************************F*/
static void _NetGameDistCheckWindow(NetGameDistRefT *pRef, int32_t iRemain, uint32_t uTick)
{
    int32_t iQueue;

    if (!pRef->bRecvMulti)
    {
        // stretch cycle if we are over window
        iQueue = GMDIST_Modulo(pRef->OutBufData.iEnd - GMDIST_Modulo(pRef->InpBufData.iBeg + pRef->iIOOffset, PACKET_WINDOW), PACKET_WINDOW);
        if ((iQueue > pRef->iInputWind) && (iRemain < pRef->iInputRate /2))
        {
            #if TIMING_DEBUG
            // dont show single cycle adjustments
            if (uTick+pRef->iInputRate/2-pRef->uInpNext != 1)
            {
                NetPrintf(("netgamedist: [%p] stretching cycle (que=%d, win=%d, tick=%d, add=%d)\n",
                     pRef, iQueue, pRef->iInputWind, uTick, (uTick+pRef->iInputRate/2)-pRef->uInpNext));
            }
            #endif
            // stretch the next send
            pRef->uInpNext = uTick+pRef->iInputRate /2;
        }
    }
}

/*F*************************************************************************************************/
/*!
    \Function    _NetGameDistUpdateSendTime

    \Description
        Update the next send time after sending each packet. In p2p mode, clamp the next send to
        no earlier than half a frame after *now* and no later than two frames after *now*

    \Input *pRef    - The NetGameDist ref

    \Version 12/21/09 (jrainy)
*/
/*************************************************************************************************F*/
static void _NetGameDistUpdateSendTime(NetGameDistRefT *pRef)
{
    uint32_t uTick;
    int32_t iNext;
    // get current time

    uTick = NetTick();
    // record the send time so we can schedule next packet
    pRef->uInpNext += pRef->iInputRate;

    if (!pRef->bRecvMulti)
    {
        // figure time till next send
        iNext = pRef->uInpNext - uTick;
        // clamp the time range to half/double rate
        if (iNext < pRef->iInputRate / 2)
        {
            #if TIMING_DEBUG
            NetPrintf(("send clamping to half (was %d)\n", iNext));
            #endif
            pRef->uInpNext = uTick + pRef->iInputRate /2;
        }
        if (iNext > pRef->iInputRate * 2)
        {
            #if TIMING_DEBUG
            NetPrintf(("send clamping to double (was %d)\n", iNext));
            #endif
            pRef->uInpNext = uTick + pRef->iInputRate *2;
        }
    }
}

/*F*************************************************************************************************/
/*!
    \Function    _NetGameDistSendInput

    \Description
        Provide local input data

    \Input *pRef        - reference pointer
    \Input *pBuffer     - controller data
    \Input iLength      - data length
    \Input iLengthSize  - size of length buffer

    \Output
        int32_t         - negative=error (including GMDIST_OVERFLOW=overflow), positive=packet successfully sent or saved to NetGameDist send buffer

    \Version 12/20/00 (GWS)
*/
/*************************************************************************************************F*/
// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
static int32_t _NetGameDistSendInput(NetGameDistRefT *pRef, void *pBuffer, int32_t iLength, int32_t iLengthSize, uint64_t *pLatency)
// FIFA_END
{
    int32_t iNext, iResult, iBeg;
    unsigned char *pData;

    // add packet to queue
    if (pBuffer != NULL)
    {
        // verify length is valid
        if (iLength < 0)
        {
            ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "netgamedist: _NetGameDistSendInput with iLength %d.", iLength);
            NetPrintf(("netgamedist: invalid buffer length passed to _NetGameDistSendInput()!\n"));
            return(GMDIST_INVALID);
        }

        // see if room to buffer packet
        iNext = GMDIST_Modulo(pRef->OutBufData.iEnd+1, PACKET_WINDOW);
        // the - 1 steals a spot but prevents:
        // input local prechanging iooffset triggering this
        iBeg = GMDIST_Modulo(pRef->InpBufData.iBeg + pRef->iIOOffset - 1, PACKET_WINDOW);
        if (!pRef->bActAsServer && (iNext == iBeg))
        {
            ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "netgamedist: _NetGameDistSendInput with iNext %d and iBeg %d.", iNext, iBeg);
            return(GMDIST_OVERFLOW);
        }

        if (iNext == pRef->OutBufData.iBeg)
        {
            ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "netgamedist: _NetGameDistSendInput with iNext %d and OutBufData.iBeg %d.", iNext, pRef->OutBufData.iBeg);
            return(GMDIST_OVERFLOW);
        }

        // point to buffer
        if (!_SetDataPtr(pRef, &pRef->OutBufData, iLength))
        {
            return(GMDIST_OVERFLOW);
        }
        pData = pRef->OutBufData.pControllerIO+pRef->OutBufData.IOLookUp[pRef->OutBufData.iEnd].uPos;

        // copy data into buffer
        pRef->OutBufData.IOLookUp[pRef->OutBufData.iEnd].uLen = iLength;
        pRef->OutBufData.IOLookUp[pRef->OutBufData.iEnd].uLenSize = iLengthSize;
        ds_memcpy(pData, pBuffer, iLength);

// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
#ifdef ENABLED_LATENCY_METER
		if (pLatency != NULL)
		{
			pLatency[OUT_NETGAMEDIST_QUEUE] = NetProcTime();
		}
		pRef->OutBufData.IOLookUp[pRef->OutBufData.iEnd].pLatency = pLatency;
#endif
// FIFA_END

        // save down the time 
        pRef->OutBufData.IOLookUp[pRef->OutBufData.iEnd].uInsertTime = NetTick();

        // incorporate into buffer
        pRef->OutBufData.iEnd = iNext;

        // moved from inside the 'while' below
        // this will update the send time on send attempt, not on actual send.
        // i.e. "queue packets at regular interval", not "queue packets so that
        // they send at regular interval", which is not really doable anyway.
        _NetGameDistUpdateSendTime(pRef);
    }

    // try and send packet
    // prevent sending if we have pending metainfo to send as it will affect the format
    while ((pRef->OutBufData.iBeg != pRef->OutBufData.iEnd) && (pRef->uUpdateMetaInfoBeg == pRef->uUpdateMetaInfoEnd))
    {
        // point to buffer
        pData = pRef->OutBufData.pControllerIO+pRef->OutBufData.IOLookUp[pRef->OutBufData.iBeg].uPos;

        // setup a data input packet
        if (pRef->bActAsServer)
        {
            if (pRef->OutBufData.IOLookUp[pRef->OutBufData.iBeg].uLenSize == 2)
            {
                pRef->MaxPkt.head.kind = GAME_PACKET_INPUT_MULTI_FAT;
            }
            else
            {
                pRef->MaxPkt.head.kind = GAME_PACKET_INPUT_MULTI;
            }
        }
        else
        {
            pRef->MaxPkt.head.kind = GAME_PACKET_INPUT;
        }

        pRef->MaxPkt.head.len = pRef->OutBufData.IOLookUp[pRef->OutBufData.iBeg].uLen;
        ds_memcpy(pRef->MaxPkt.body.data, pData, pRef->MaxPkt.head.len);

// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
#ifdef ENABLED_LATENCY_METER
		pRef->MaxPkt.head.latency = pRef->OutBufData.IOLookUp[pRef->OutBufData.iBeg].pLatency;
#endif
// FIFA_END

        // try and send
        iResult = pRef->pSendProc(pRef->pNetGameLinkRef, (NetGamePacketT*)&pRef->MaxPkt, 1);
        if (iResult > 0)
        {
            // all is well -- remove from buffer
            pRef->OutBufData.iBeg = GMDIST_Modulo(pRef->OutBufData.iBeg+1, PACKET_WINDOW);
        }
        else if (iResult < 0)
        {
            ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "netgamedist: GMDIST_SENDPROC_FAILED result is %d.", iResult);

            pRef->iErrCond = GMDIST_SENDPROC_FAILED;
            NetPrintf(("netgamedist: sendproc failed in _NetGameDistSendInput()!\n"));
            return(GMDIST_SENDPROC_FAILED);
        }
        else
        {
            // lower-level transport is out of send buffer space at the moment… exit for now and try again later.
            break;
        }
    }

    // packet was either buffered and/or sent
    return(1);
}

/*F*************************************************************************************************/
/*!
    \Function    _ProcessPacketType

    \Description
        Whenever a packet of a given type is received, this function is called
        to do any specific process.

    \Input *pRef        - reference pointer
    \Input uPacketType  - the packet type

    \Version 02/22/07 (jrainy)
*/
/*************************************************************************************************F*/
static void _ProcessPacketType(NetGameDistRefT *pRef, uint8_t uPacketType)
{
    if (!pRef->bActAsServer && ((uPacketType == GAME_PACKET_INPUT_MULTI) || (uPacketType == GAME_PACKET_INPUT_MULTI_FAT)))
    {
        pRef->bRecvMulti = TRUE;
    }
}

/*F*************************************************************************************************/
/*!
    \Function    _NetGameDistSendFlowUpdate

    \Description
        Sends a packet to the server enabling or disabling flow control

    \Input *pRef        - reference pointer

    \Version 09/29/09 (jrainy)
*/
/*************************************************************************************************F*/
static void _NetGameDistSendFlowUpdate(NetGameDistRefT *pRef)
{
    int32_t iResult;

    pRef->MaxPkt.head.kind = GAME_PACKET_INPUT_FLOW;
    pRef->MaxPkt.head.len = 7;

// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
#ifdef ENABLED_LATENCY_METER
	pRef->MaxPkt.head.latency = NULL;
#endif
// FIFA_END

    pRef->MaxPkt.body.data[0] = pRef->bRdySend;
    pRef->MaxPkt.body.data[1] = pRef->bRdyRecv;

    pRef->MaxPkt.body.data[2] = pRef->bLocalCRCValid;
    pRef->MaxPkt.body.data[3] = (pRef->uLocalCRC >> 24);
    pRef->MaxPkt.body.data[4] = (pRef->uLocalCRC >> 16);
    pRef->MaxPkt.body.data[5] = (pRef->uLocalCRC >> 8);
    pRef->MaxPkt.body.data[6] = pRef->uLocalCRC;

    pRef->bLocalCRCValid = FALSE;

    iResult = pRef->pSendProc(pRef->pNetGameLinkRef, (NetGamePacketT*)&pRef->MaxPkt, 1);

    if (iResult < 0)
    {
        NetPrintf(("netgamedist: [%p] Flow update failed (error=%d)!\n", pRef, iResult));
        pRef->bUpdateFlowCtrl = FALSE;
    }
    else if (iResult > 0)
    {
        pRef->bUpdateFlowCtrl = FALSE;
    }
    else
    {
        NetPrintf(("netgamedist: [%p] Flow update deferred (error=overflow)!\n", pRef));
        // nothing to do here, the caller - NetGameDistUpdate - will try again later.
    }
}

/*F*************************************************************************************************/
/*!
    \Function    _NetGameDistSendMetaInfo

    \Description
        Sends a packet to the client describing the format of upcoming packets

    \Input *pRef        - reference pointer

    \Version 09/29/09 (jrainy)
*/
/*************************************************************************************************F*/
static void _NetGameDistSendMetaInfo(NetGameDistRefT *pRef)
{
    int32_t iResult;
    NetGameDistMetaInfoT *pMetaInfo;

    while(pRef->uUpdateMetaInfoBeg != pRef->uUpdateMetaInfoEnd)
    {
        pMetaInfo = &pRef->aMetaInfoToSend[pRef->uUpdateMetaInfoBeg];

        pRef->MaxPkt.head.kind = GAME_PACKET_INPUT_META;
        pRef->MaxPkt.head.len = 5;

// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
#ifdef ENABLED_LATENCY_METER
		pRef->MaxPkt.head.latency = NULL;
#endif
// FIFA_END

        pRef->MaxPkt.body.data[0] = pMetaInfo->uVer;
        pRef->MaxPkt.body.data[1] = pMetaInfo->uMask >> 24;
        pRef->MaxPkt.body.data[2] = pMetaInfo->uMask >> 16;
        pRef->MaxPkt.body.data[3] = pMetaInfo->uMask >> 8;
        pRef->MaxPkt.body.data[4] = pMetaInfo->uMask;

        iResult = pRef->pSendProc(pRef->pNetGameLinkRef, (NetGamePacketT*)&pRef->MaxPkt, 1);

        if (iResult < 0)
        {
            NetPrintf(("netgamedist: [%p] Meta info update failed (error=%d)!\n", pRef, iResult));
            pRef->uUpdateMetaInfoBeg = GMDIST_Modulo(pRef->uUpdateMetaInfoBeg + 1, GMDIST_META_ARRAY_SIZE);
        }
        else if (iResult > 0)
        {
            NetPrintf(("netgamedist: [%p] Meta info update sent %d 0x%08x !\n", pRef, pMetaInfo->uVer, pMetaInfo->uMask));
            pRef->uUpdateMetaInfoBeg = GMDIST_Modulo(pRef->uUpdateMetaInfoBeg + 1, GMDIST_META_ARRAY_SIZE);
        }
        else
        {
            NetPrintf(("netgamedist: [%p] Meta info update deferred (error=overflow)!\n", pRef));
            // nothing to do here, the caller - NetGameDistUpdate - will try again later.

            break;
        }
    }
}

/*F*************************************************************************************************/
/*!
    \Function    _NetGameDistCountBits

    \Description
        Count the number of bits set in a given uint32_t variable

    \Input *pRef        - reference pointer
    \Input uMask        - mask

    \Output
        uint32_t        - the number of bits set

    \Version 09/26/09 (jrainy)
*/
/*************************************************************************************************F*/
static uint32_t _NetGameDistCountBits(NetGameDistRefT *pRef, uint32_t uMask)
{
    uint32_t uCount = 0;

    while(uMask)
    {
        uCount += (uMask & 1);
        uMask /= 2;
    };

    return(uCount);
}

#if DIRTYCODE_LOGGING
/*F*************************************************************************************************/
/*!
    \Function    _NetGameDistInputCheckLog

    \Description
        Logs the values returned by NetGameDistInputCheck.

    \Input *pRef        - reference pointer
    \Input *pSend       - the pointer to pSend the client passed to InputCheck
    \Input *pRecv       - the pointer to pRecv the client passed to InputCheck

    \Version 12/21/09 (jrainy)
*/
/*************************************************************************************************F*/
static void _NetGameDistInputCheckLog(NetGameDistRefT *pRef, int32_t *pSend, int32_t *pRecv)
{
    uint32_t uCurrentTick = NetTick();
    uint32_t uDelay;

    // Calculate delay since last log
    uDelay = NetTickDiff(uCurrentTick, pRef->uLastInputCheckLogTick);

    // Check if condition to display the trace is met.
    // Condition is: meaningful send/recv info ready OR skip timeout expire
    if ( (pSend && (*pSend==0)) || (pRecv && (*pRecv!=0)) ||     // check meaningful send/recv
         (uDelay >= INPUTCHECK_LOGGING_DELAY) )                    // check skip timeout
    {
        if (pSend && pRecv)
        {
            NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] exiting NetGameDistInputCheck() tick=%d, *pSend=%d, *pRecv=%d logskipped_count=%d\n",
                pRef, uCurrentTick, *pSend, *pRecv, pRef->uSkippedInputCheckLogCount));
        }
        else if (pSend && !pRecv)
        {
            NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] exiting NetGameDistInputCheck() tick=%d, *pSend=%d, pRecv=NULL logskipped_count=%d\n",
                pRef, uCurrentTick, *pSend, pRef->uSkippedInputCheckLogCount));
        }
        else if (!pSend && pRecv)
        {
            NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] exiting NetGameDistInputCheck() tick=%d, pSend=NULL, *pRecv=%d logskipped_count=%d\n",
                pRef, uCurrentTick, *pRecv, pRef->uSkippedInputCheckLogCount));
        }
        else
        {
            NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] exiting NetGameDistInputCheck() tick=%d, pSend=NULL, pRecv=NULL logskipped_count=%d\n",
                pRef, uCurrentTick, pRef->uSkippedInputCheckLogCount));
        }

        // Re-initialize variables used to skip some logs
        pRef->uSkippedInputCheckLogCount = 0;
        pRef->uLastInputCheckLogTick = uCurrentTick;
    }
    else
    {
        pRef->uSkippedInputCheckLogCount++;
    }
}
#endif

/*F*************************************************************************************************/
/*!
    \Function    _NetGameDistInputPeekRecv

    \Description
        Behaves just as a receive function, taking data from netgamelink. However, we peek first and
        if an overflow is detected, we do not take the packet from netgamelink. In this case, we
        return 0, as if doing was ready to be received

    \Input *pRef     - reference pointer
    \Input *pBuf     - buffer to receive into
    \Input iLen      - available length in buf

    \Output
        int32_t      - recvproc return value, or forced to 0 if we'd be in overflow

    \Version 08/15/11 (jrainy)
*/
/*************************************************************************************************F*/
static int32_t _NetGameDistInputPeekRecv(NetGameDistRefT *pRef, NetGamePacketT *pBuf, int32_t iLen)
{
    NetGamePacketT* pPeekPacket = NULL;

    if (pRef->pPeekProc)
    {
        uint32_t uDistMask = (1 << GAME_PACKET_INPUT) |
                             (1 << GAME_PACKET_INPUT_MULTI) |
                             (1 << GAME_PACKET_INPUT_MULTI_FAT);

        (*pRef->pPeekProc)(pRef->pNetGameLinkRef, &pPeekPacket, uDistMask);

        // ok, we have a mean to see what is coming, and we got something.
        if (pPeekPacket)
        {
            // let's check if we have space for it.
            // for completeness, we may check buffer overflow on slots and dropproc, but this seems overkill
            if (_SetDataPtrTry(pRef, &pRef->InpBufData, pPeekPacket->head.len + 1) < 0)
            {
                NetPrintf(("netgamedist: [%p] not receiving from link layer a packet of type %d and length %d because our buffer is full\n", pRef, pPeekPacket->head.kind, pPeekPacket->head.len));
                // act as if the recvproc had nothing.
                return(0);
            }
        }
    }

    return((*pRef->pRecvProc)(pRef->pNetGameLinkRef, pBuf, iLen, TRUE));
}
/*** Public Functions ******************************************************************/

/*F*************************************************************************************************/
/*!
    \Function    NetGameDistCreate

    \Description
        Create the game pClient

    \Input *pNetGameLinkRef - netgamelink module ref
    \Input *pStatProc       - netgamelink stat callback
    \Input *pSendProc       - game send func
    \Input *pRecvProc       - game recv func
    \Input uInBufferSize    - input buffer size, plan around PACKET_WINDOW*n*packets
    \Input uOutBufferSize   - output buffer size, plan around PACKET_WINDOW*packets

    \Output
        NetGameDistRefT * - pointer to module state

    \Version 12/20/00 (GWS)
*/
/*************************************************************************************************F*/
NetGameDistRefT *NetGameDistCreate(void *pNetGameLinkRef, NetGameDistStatProc *pStatProc, NetGameDistSendProc *pSendProc, NetGameDistRecvProc *pRecvProc, uint32_t uInBufferSize, uint32_t uOutBufferSize )
{
    NetGameDistRefT *pRef;
    int32_t iMemGroup;
    void *pMemGroupUserData;

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

    // allocate and init module state
    if ((pRef = DirtyMemAlloc(sizeof(*pRef), NETGAMEDIST_MEMID, iMemGroup, pMemGroupUserData)) == NULL)
    {
        NetPrintf(("netgamedist: [%p] unable to allocate module state\n", pRef));
        return(NULL);
    }
    ds_memclr(pRef, sizeof(*pRef));
    pRef->iMemGroup = iMemGroup;
    pRef->pMemGroupUserData = pMemGroupUserData;
    pRef->iLastSentDelta = -1;

    // Allocate the input and output buffers
    pRef->InpBufData.pControllerIO = DirtyMemAlloc( uInBufferSize, NETGAMEDIST_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData );
    pRef->OutBufData.pControllerIO = DirtyMemAlloc( uOutBufferSize, NETGAMEDIST_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData );

    ds_memclr(pRef->InpBufData.pControllerIO, uInBufferSize);
    ds_memclr(pRef->OutBufData.pControllerIO, uOutBufferSize);

    pRef->InpBufData.uBufLen = uInBufferSize;
    pRef->OutBufData.uBufLen = uOutBufferSize;

    // save the link layer callbacks
    pRef->pNetGameLinkRef = pNetGameLinkRef;
    pRef->pStatProc = pStatProc;
    pRef->pSendProc = pSendProc;
    pRef->pRecvProc = pRecvProc;

    // set default controller exchange rate
    pRef->iInputRate = 50;
    // set the defaults
    pRef->iInputMini = 1;
    pRef->iInputMaxi = 10;

    // Defaults to two players so that the non-multi version behaves as it used to.
    pRef->uTotalPlyrs = 2;

    // Set default verbosity level
    pRef->uVerbose = 1;

    // init check tick counter
    pRef->uLastInputCheckLogTick = NetTick();

    NetPrintf(("netgamedist: module created with PACKET_WINDOW = %d\n", PACKET_WINDOW));

    return(pRef);
}

/*F*************************************************************************************************/
/*!
    \Function    NetGameDistDestroy

    \Description
        Destroy the game pClient

    \Input *pRef     - reference pointer

    \Version 12/20/00 (GWS)
*/
/*************************************************************************************************F*/
void NetGameDistDestroy(NetGameDistRefT *pRef)
{
	NetPrintf(("NetGameDistDestroy: [0x%08x] Free Up pControllerIO from pRef->InpBufData, pRef->OutBufData\n", pRef));

    DirtyMemFree( pRef->InpBufData.pControllerIO, NETGAMEDIST_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData );
    DirtyMemFree( pRef->OutBufData.pControllerIO, NETGAMEDIST_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData );
    pRef->InpBufData.pControllerIO = NULL;
    pRef->OutBufData.pControllerIO = NULL;

    // free our memory
    DirtyMemFree(pRef, NETGAMEDIST_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData);

}

/*F*************************************************************************************************/
/*!
    \Function    NetGameDistStatus

    \Description
        Get status information

    \Input *pRef                     - reference pointer
    \Input iSelect                   - selector
    \Input iValue                    - input value
    \Input *pBuf                     - output buffer
    \Input iBufSize                  - output buffer size

    \Output
        int32_t                      - selector specific return value

    \Notes
        This is read-only data which can be read at any time.  This reference becomes
        invalid when the module is destroyed.

        The count of [?snd..?out] is the number of packets ready to send.  The min of
        [?cmp..?inp] and [?cmp..?out] is the number of packets ready to process (because
        you need a packet from each peer in order to perform processing).

        Selectors are:

    \verbatim
        SELECTOR    RETURN RESULT
        'dcnt'      returns the total inbound dequeued count
        'drop'      returns the total input packet dropped
        'icnt'      returns the total inbound packet count
        'late'      returns the latency from the link stats
        'mult'      returns the number of players and writes their stats to pBuf
        'ocnt'      returns the total outbound packet count
        'pcnt'      returns the total packet processed count (full round trip)
        'plat'      returns the end-to-end latency in number of packets
        'prti'      returns the total packet processed time (full round trip)
        'pwin'      returns PACKET_WINDOW configured for netgamedist
        'qver'      returns the last queried packet version
        'rate'      returns current exchange rate
        'rcrc'      if iValue==0 returns whether we have a value otherwise returns the value itself
        'rrcv'      returns if remote is ready to receive or not
        'rsnd'      returns if remote is ready to send or not
        'stat'      returns the link stats via pBuf
        'wait'      returns total inbound packet wait time;
        'wind'      returns the current exchange window
        '?cmp'      returns the offset of first packet to process in input buffer
        '?cws'      returns if we can send. FALSE if sending would overflow the send queue, iValue is length
        '?inp'      returns the offset of last packet to process in input buffer
        '?out'      returns the offset of last packet to send in output buffer
        '?snd'      returns the offset of first packet to send in output buffer
    \endverbatim

        Unhandled selectors are passed on to NetGameLink

    \Version 12/20/00 (GWS)
*/
/*************************************************************************************************F*/
int32_t NetGameDistStatus(NetGameDistRefT *pRef, int32_t iSelect, int32_t iValue, void *pBuf, int32_t iBufSize)
{
    if (iSelect == 'dcnt')
    {
        return(pRef->iInboundDequeueCnt);
    }
    if (iSelect == 'drop')
    {
        return(pRef->iInboundDropPktCnt);
    }
    if (iSelect == 'icnt')
    {
        return(pRef->iInboundPktCnt);
    }
    if (iSelect == 'late')
    {
        return(pRef->NetGameLinkStats.late);
    }
    if (iSelect == 'mult')
    {
        // Make user-provide buffer is large enough to receive a pointer
        if ((pBuf != NULL) && (iBufSize >= (int32_t)(sizeof(NetGameDistStatT) * pRef->uTotalPlyrs)))
        {
            ds_memcpy(pBuf, pRef->aRecvStats, sizeof(NetGameDistStatT) * pRef->uTotalPlyrs);
            return(pRef->uTotalPlyrs);
        }
        else
        {
            // unhandled
            return(-1);
        }
    }
    if (iSelect == 'ocnt')
    {
        return(pRef->iOutboundPktCnt);
    }
    if (iSelect == 'plat')
    {
        return(GMDIST_Modulo(pRef->OutBufData.iEnd - GMDIST_Modulo(pRef->InpBufData.iBeg + pRef->iIOOffset, PACKET_WINDOW), PACKET_WINDOW));
    }
    if (iSelect == 'pcnt')
    {
        return(pRef->iDistProcCnt);
    }
    if (iSelect == 'prti')
    {
        return(pRef->iDistProcTimeTotal);
    }
    if (iSelect == 'pwin')
    {
        return(PACKET_WINDOW);
    }
    if (iSelect == 'qver')
    {
        return(pRef->uLastQueriedVersion);
    }
    if (iSelect == 'rate')
    {
        return(pRef->iInputRate);
    }
    if (iSelect == 'rcrc')
    {
        if (iValue)
        {
            int32_t ret = pRef->uRemoteCRC;
            pRef->uRemoteCRC = 0;
            pRef->bRemoteCRCValid = FALSE;
            return(ret);
        }
        else
        {
            return(pRef->bRemoteCRCValid);
        }
    }
    if (iSelect == 'rrcv')
    {
        return(pRef->bRdyRecvRemote);
    }
    if (iSelect == 'rsnd')
    {
        return(pRef->bRdySendRemote);
    }
    if (iSelect == 'stat')
    {
        (pRef->pStatProc)(pRef->pNetGameLinkRef, iSelect, iValue, &pRef->NetGameLinkStats, sizeof(NetGameLinkStatT));

        // Make user-provide buffer is large enough to receive a pointer
        if ((pBuf != NULL) && (iBufSize >= (int32_t)sizeof(NetGameLinkStatT)))
        {
            ds_memcpy(pBuf, &pRef->NetGameLinkStats, sizeof(NetGameLinkStatT));
            return(0);
        }
        else
        {
            // unhandled
            return(-1);
        }
    }
    if (iSelect == 'wait')
    {
        return(pRef->iWaitTimeTotal);
    }
    if (iSelect == 'wind')
    {
        return(pRef->iInputWind);
    }
    if (iSelect == '?cmp')
    {
        return(pRef->InpBufData.iBeg);
    }
    if (iSelect == '?cws')
    {
        int32_t iNext;
        if (iValue < 0)
        {
            return(FALSE);
        }

        iNext = GMDIST_Modulo(pRef->OutBufData.iEnd +1, PACKET_WINDOW);

        // mclouatre April 26th 2018 - unclear to me what the -1 is for in the condition below
        if (!pRef->bActAsServer && (iNext == GMDIST_Modulo(pRef->InpBufData.iBeg + pRef->iIOOffset - 1, PACKET_WINDOW)))
        {
            ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "'?cws' failure while comparing indices");
            return(FALSE);
        }
        if (_SetDataPtrTry(pRef, &pRef->OutBufData, iValue) == -1)
        {
            return(FALSE);
        }

        return(TRUE);
    }
    if (iSelect == '?inp')
    {
        return(pRef->InpBufData.iEnd);
    }
    if (iSelect == '?out')
    {
        return(pRef->OutBufData.iEnd);
    }
    if (iSelect == '?snd')
    {
        return(pRef->OutBufData.iBeg);
    }

    // fallthrough to NetGameLinkStatus
    return((pRef->pStatProc)(pRef->pNetGameLinkRef, iSelect, iValue, pBuf, iBufSize));
}

/*F*************************************************************************************************/
/*!
    \Function    NetGameDistSetServer

    \Description
        Get pointer to status counters

    \Input *pRef            - reference pointer
    \Input bActAsServer     - boolean, whether NetGameDist should send multi-packets

    \Version 02/26/07 (jrainy)
*/
/*************************************************************************************************F*/
void NetGameDistSetServer(NetGameDistRefT *pRef, uint8_t bActAsServer)
{
    // the trigraph is there to make sure we don't store non-{0,1} value in.
    pRef->bActAsServer = bActAsServer ? TRUE : FALSE;
}

/*F*************************************************************************************************/
/*!
    \Function    NetGameDistMultiSetup

    \Description
        Tells NetGameDist how many players there are in the game, and which one we are.
        Will only affect the order packets are received from NetGameDistInputQueryMulti.

    \Input *pRef            - netgamelink module ref
    \Input iDistIndex       - which player we are
    \Input iTotPlrs         - total number of players

    \Version 02/08/07 (jrainy)
*/
/*************************************************************************************************F*/
void NetGameDistMultiSetup(NetGameDistRefT *pRef, int32_t iDistIndex, int32_t iTotPlrs)
{
    NetPrintf(("netgamedist: [%p] NetGameDistMultiSetup index: %d, total players: %d\n", pRef, iDistIndex, iTotPlrs));

    pRef->uTotalPlyrs = iTotPlrs;
    pRef->uDistIndex = iDistIndex;
}

/*F*************************************************************************************************/
/*!
    \Function    NetGameDistMetaSetup

    \Description
        Tells NetGameDist whether to enable meta-information sending, and
        sets the mask & the version number for the meta-information.

    \Input *pRef            - netgamelink module ref
    \Input bSparse          - enable or disable meta-information sending (disabling after enabling is currently not-supported on the client)
    \Input uMask            - set the mask for the meta-information
    \Input uVersion         - set the version number for the meta-information.

    \Version 08/29/11 (szhu)
*/
/*************************************************************************************************F*/
void NetGameDistMetaSetup(NetGameDistRefT *pRef, uint8_t bSparse, uint32_t uMask, uint32_t uVersion)
{
    NetPrintf(("netgamedist: [%p] NetGameDistMetaSetup Enabled: %s, Mask: 0x%x, Version: %d\n",
               pRef, bSparse?"TRUE":"FALSE", uMask, uVersion));

    pRef->bSparse = bSparse;
    pRef->aMetaInfoToSend[pRef->uUpdateMetaInfoEnd].uMask = uMask;
    pRef->aMetaInfoToSend[pRef->uUpdateMetaInfoEnd].uPlayerCount = _NetGameDistCountBits(pRef, uMask);
    pRef->aMetaInfoToSend[pRef->uUpdateMetaInfoEnd].uVer = GMDIST_Modulo(uVersion, GMDIST_META_ARRAY_SIZE);
    pRef->uUpdateMetaInfoEnd = GMDIST_Modulo(pRef->uUpdateMetaInfoEnd + 1, GMDIST_META_ARRAY_SIZE);
}

/*F*************************************************************************************************/
/*!
    \Function    NetGameDistInputPeek

    \Description
        See if a completed packet is ready

    \Input *pRef    - reference pointer
    \Input *pType   - will be filled with data type
    \Input *pPeer   - buffer sent by peer
    \Input *pPlen   - length of data in peer buffer. Must pass in the length of pPeer

    \Output
        int32_t     - zero=no data pending, negative=error, positive=data returned

    \Version 02/05/2007 (JLB)
*/
/*************************************************************************************************F*/
int32_t NetGameDistInputPeek(NetGameDistRefT *pRef, uint8_t *pType, void *pPeer, int32_t *pPlen)
{
    uint8_t *pPsrc;
    int32_t iLength;
    uint8_t uInputKind;
    int16_t iLengthSize = 1;
    uint8_t uOffsetLengths;
    uint8_t uOffsetBuffer;
    uint8_t uOffsetTypes;
    uint8_t uOffsetVersion;
    uint16_t uPlayerCount = pRef->uTotalPlyrs;
    uint8_t uVer = 0;


    // if no remote data, do an update
    if (pRef->InpBufData.iBeg == pRef->InpBufData.iEnd)
    {
        NetGameDistUpdate(pRef);
    }

    // no packets from peer?
    if (pRef->InpBufData.iBeg == pRef->InpBufData.iEnd)
    {
        return(0);
    }

    // point to data buffer
    pPsrc = pRef->InpBufData.pControllerIO+pRef->InpBufData.IOLookUp[pRef->InpBufData.iBeg].uPos;

    uInputKind = pPsrc[pRef->InpBufData.IOLookUp[pRef->InpBufData.iBeg].uLen - 1];

    if (uInputKind == GAME_PACKET_INPUT_MULTI_FAT)
    {
        iLengthSize = 2;
    }

    // find the location to lookup the version
    uOffsetVersion = pRef->bRecvMulti ? 1 : 0;

    // extract the version number of the packet, and player count, if the server ever gave us metadata
    if (pRef->bGotMetaInfo && pRef->bRecvMulti)
    {
        uVer = GMDIST_Modulo(pPsrc[uOffsetVersion], GMDIST_META_ARRAY_SIZE);
        uPlayerCount = pRef->aMetaInfo[uVer].uPlayerCount;
        pRef->uLastQueriedVersion = uVer;
    }

    // always pack packets as if there was at least 2 players (to avoid negatively-sized fields)
    if (pRef->bRecvMulti && (pRef->uTotalPlyrs == 1))
    {
        uPlayerCount = 2;
    }

    // compute offsets to various parts
    uOffsetTypes = uOffsetVersion + (pRef->bGotMetaInfo ? 1 : 0);
    uOffsetLengths = uOffsetTypes + (pRef->bRecvMulti ? (uPlayerCount / 2) : 1);
    uOffsetBuffer = uOffsetLengths + (pRef->bRecvMulti ? (iLengthSize * (uPlayerCount - 2)) : 0);

    // copy over the data, -1 to skip the packet kind tacked at the end
    iLength = pRef->InpBufData.IOLookUp[pRef->InpBufData.iBeg].uLen - uOffsetBuffer - 1;
    if (pPeer != NULL)
    {
        //check the passed-in length to prevent overflow
        if (iLength > *pPlen)
        {
            ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "NetGameDistInputPeek error. length is %d, *pLen is %d.", iLength, *pPlen);

            // fail if the buffer is insufficient
            *pPlen = iLength;
            return(GMDIST_OVERFLOW);
        }

        ds_memcpy(pPeer, pPsrc + uOffsetBuffer, iLength);
    }

    *pPlen = iLength;

    if (pType != NULL)
    {
        *pType = *(pPsrc + (pRef->bRecvMulti ? 1 : 0));
    }

    #if DIRTYCODE_LOGGING
    NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] peeked type %d len %d\n", pRef, pType?*pType:0, pPlen?*pPlen:0));
    #endif

    // return success
    return(pRef->aPacketId[pRef->InpBufData.iBeg]);
}

/*F*************************************************************************************************/
/*!
    \Function    NetGameDistInputQuery

    \Description
        See if a completed packet is ready

    \Input *pRef    - reference pointer
    \Input *pOurs   - buffer previously sent with NetGameDistInputLocal
    \Input *pOlen   - length of data in ours buffer
    \Input *pPeer   - buffer sent by peer
    \Input *pPlen   - length of data in peer buffer

    \Output
        int32_t     - zero=no data pending, negative=error, positive=data returned

    \Version 12/20/00 (GWS)
*/
/*************************************************************************************************F*/
// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
int32_t NetGameDistInputQuery(NetGameDistRefT *pRef, void *pOurs, int32_t *pOlen, void *pPeer, int32_t *pPlen, uint64_t *pLatency)
// FIFA_END
{
    void* aInputs[2];
    int32_t aLengths[2];
    uint8_t aTypes[2];
    int32_t iRet;

    if ( pRef->uTotalPlyrs != 2 )
    {
        return(GMDIST_BADSETUP);
    }

    // for a 2 player setup our index is always 0 and the peer is always at 1
    aInputs[0] = pOurs;
    aInputs[1] = pPeer;

	// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
    iRet = NetGameDistInputQueryMulti(pRef, aTypes, aInputs, aLengths, pLatency);
	// FIFA_END

    *pOlen = aLengths[0];
    *pPlen = aLengths[1];

    return(iRet);
}

/*F*************************************************************************************************/
/*!
    \Function    NetGameDistInputQueryMulti

    \Description
        See if a completed packet is ready

    \Input *pRef        - reference pointer
    \Input *pDataTypes  - array of data types (GMDIST_DATA_NONE, GMDIST_DATA_INPUT, GMDIST_DATA_INPUT_DROPPABLE, GMDIST_DATA_DISCONNECT).
    \Input **ppInputs   - array of pointers to inputs to receive.
    \Input *pLen        - array of lengths from the data received.

    \Output
        int32_t         - zero=no data pending, negative=error, positive=data returned

    \Version 02/08/07 (jrainy)
*/
/*************************************************************************************************F*/
// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
int32_t NetGameDistInputQueryMulti(NetGameDistRefT *pRef, uint8_t *pDataTypes, void **ppInputs, int32_t *pLen, uint64_t *pLatency)
// FIFA_END
{
    unsigned char *pOsrc;
    uint32_t uIndex, uCount, uPos, uRecvLen;
    unsigned char *pRecvBuf;
    uint8_t *pLengths;
    uint8_t *pTypePos;
    uint8_t uDelta;
    uint8_t uOffsetLengths;
    uint8_t uOffsetBuffer;
    uint8_t uOffsetTypes;
    uint8_t uOffsetVersion;
    uint32_t uOurInp;
    uint16_t uPlayerCount = pRef->uTotalPlyrs;
    uint8_t bCRCRequest = FALSE;
    uint8_t uVer = 0;

    // if waiting on remote data, do an update
    if (pRef->InpBufData.iBeg == pRef->InpBufData.iEnd)
    {
        NetGameDistUpdate(pRef);
    }

    // check for inputs ready to process; in 2p mode we also require a non-empty output buffer to pair with
    if ((pRef->InpBufData.iBeg == pRef->InpBufData.iEnd) || (!pRef->bActAsServer && !pRef->bRecvMulti && (GMDIST_Modulo(pRef->InpBufData.iBeg + pRef->iIOOffset, PACKET_WINDOW) == pRef->OutBufData.iEnd)))
    {
        #if DIRTYCODE_LOGGING
        NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] exiting NetGameDistInputQueryMulti() retval=0 tick=%d len=n/a type=n/a\n", pRef, NetTick()));
        #endif

        return(0);
    }

    if (ppInputs)
    {
        // access the first byte after the received data
        uint8_t* pPacketKind = pRef->InpBufData.pControllerIO + pRef->InpBufData.IOLookUp[pRef->InpBufData.iBeg].uPos + pRef->InpBufData.IOLookUp[pRef->InpBufData.iBeg].uLen - 1;
		if (pRef->InpBufData.pControllerIO == NULL)
		{
			NetPrintf(("NetGameDistInputQueryMulti 1: [0x%08x] InpBufData.pControllerIO is NULL\n", pRef));
		}

        int16_t iLengthSize = 1;

        if (*pPacketKind == GAME_PACKET_INPUT_MULTI_FAT)
        {
            iLengthSize = 2;
        }

        // Go through the received packet and fill all the input data except ours, then copy our own from outlkup

        uCount = 0;
        uPos = 0;
        uRecvLen = pRef->InpBufData.IOLookUp[pRef->InpBufData.iBeg].uLen - 1;
        pRecvBuf = pRef->InpBufData.pControllerIO+pRef->InpBufData.IOLookUp[pRef->InpBufData.iBeg].uPos;

// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
#ifdef ENABLED_LATENCY_METER
		if (pLatency != NULL)
		{
			ds_memcpy(pLatency, pRef->InpBufData.IOLookUp[pRef->InpBufData.iBeg].aLatencyBuf, sizeof(pRef->InpBufData.IOLookUp[pRef->InpBufData.iBeg].aLatencyBuf));
		}
#endif
// FIFA_END

        uDelta = pRef->bRecvMulti ? pRecvBuf[0] : 1;

        uOffsetVersion = pRef->bRecvMulti ? 1 : 0;

        // extract the version number of the packet, and player count, if the server ever gave us metadata
        if (pRef->bGotMetaInfo && pRef->bRecvMulti)
        {
            uVer = GMDIST_Modulo(((char *)pRecvBuf)[uOffsetVersion], GMDIST_META_ARRAY_SIZE);
            uPlayerCount = pRef->aMetaInfo[uVer].uPlayerCount;
            pRef->uLastQueriedVersion = uVer;
        }

        // always pack packets as if there was at least 2 players (to avoid negatively-sized fields)
        if (pRef->bRecvMulti && (uPlayerCount == 1))
        {
            uPlayerCount = 2;
        }

        // compute offsets to various parts
        uOffsetTypes = uOffsetVersion + (pRef->bGotMetaInfo ? 1 : 0);
        uOffsetLengths = uOffsetTypes + (pRef->bRecvMulti ? (uPlayerCount / 2) : 1);
        uOffsetBuffer = uOffsetLengths + (pRef->bRecvMulti ? (iLengthSize * (uPlayerCount - 2)) : 0);

        pLengths = pRecvBuf + uOffsetLengths;
        pTypePos = pRecvBuf + uOffsetTypes;
        pRecvBuf += uOffsetBuffer;

        for (uIndex = 0; uIndex < pRef->uTotalPlyrs; uIndex++)
        {
            /* if this is not our own input and we are either
               sending for everyone
               or this is a player we are including in the current sparse multi-packet
               then, add it in */
            if (uIndex != pRef->uDistIndex)
            {
                if (!pRef->bGotMetaInfo || (pRef->aMetaInfo[uVer].uMask & (1 << uIndex)))
                {
                    /* compute the length of packet for player i.
                       last length is known (ours) second to last is implicit (total - known ones) */
                    if (uCount == (uPlayerCount - 2u))
                    {
                        pLen[uIndex] = uRecvLen - uPos - uOffsetBuffer;
                    }
                    else
                    {
                        if (iLengthSize == 2)
                        {
                            pLen[uIndex] = (pLengths[uCount * iLengthSize] * 256) + pLengths[uCount * iLengthSize + 1];
                        }
                        else
                        {
                            pLen[uIndex] = pLengths[uCount];
                        }
                    }

                    // prevent invalid sizes if bogus data is received
                    if (pLen[uIndex] < 0)
                    {
                        pLen[uIndex] = 0;
                    }

                    // clamp the total read length to the received buffer, to prevent overflow.
                    if (pLen[uIndex] + uPos + uOffsetBuffer > uRecvLen)
                    {
                        NetPrintfVerbose((pRef->uVerbose, 0, "netgamedist: Warning ! Buffer overrun. Packet trimmed.\n"));
                        pLen[uIndex] = uRecvLen - uPos - uOffsetBuffer;
                    }

                    // compute the type of packet for player i. (nibbles)
                    if (uCount % 2)
                    {
                        pDataTypes[uIndex] = *pTypePos >> 4;
                        pTypePos++;
                    }
                    else
                    {
                        pDataTypes[uIndex] = *pTypePos & 0x0f;
                    }

                    // copy the packet for player i.
                    if (((pDataTypes[uIndex] & ~GMDIST_DATA_CRC_REQUEST) == GMDIST_DATA_INPUT) ||
                        ((pDataTypes[uIndex] & ~GMDIST_DATA_CRC_REQUEST) == GMDIST_DATA_INPUT_DROPPABLE))
                    {
                        ds_memcpy(ppInputs[uIndex], pRecvBuf + uPos, pLen[uIndex]);
                        uPos += pLen[uIndex];
                    }
                    else
                    {
                        pLen[uIndex] = 0;
                    }

                    // if crc-checking is disabled locally, clear the "crc request" bit
                    if (!pRef->bCRCChallenges)
                    {
                        pDataTypes[uIndex] &= ~GMDIST_DATA_CRC_REQUEST;
                    }

                    /* set a local variable if any of the input has a crc request in.
                       this is to set the "crc request" bit in local input too, to ease our customers job. */
                    if (pDataTypes[uIndex] & GMDIST_DATA_CRC_REQUEST)
                    {
                        bCRCRequest = TRUE;
                    }

                    uCount++;
                }
                else
                {
                    pDataTypes[uIndex] = GMDIST_DATA_NODATA;
                    pLen[uIndex] = 0;
                }
            }
        }

        if (uDelta)
        {
            uint8_t uDropIndex;

            // update dist process times for dropped inputs (inputs dropped are considered processed)
            for (uDropIndex = 0; uDropIndex < (uDelta - 1) ; uDropIndex++, pRef->iDistProcCnt++)
            {
                uint32_t uCurInp = GMDIST_Modulo(pRef->InpBufData.iBeg + uDropIndex, PACKET_WINDOW);
                pRef->iDistProcTimeTotal += NetTickDiff(NetTick(), pRef->OutBufData.IOLookUp[uCurInp].uInsertTime);
            }

            // point to data buffers. Our local storage has just 1-byte type field
            pRef->iIOOffset += (uDelta - 1);
            uOurInp = GMDIST_Modulo(pRef->InpBufData.iBeg + pRef->iIOOffset, PACKET_WINDOW);
            pOsrc = pRef->OutBufData.pControllerIO + pRef->OutBufData.IOLookUp[uOurInp].uPos + 1;

            // copy over the data
            if (pRef->OutBufData.IOLookUp[uOurInp].uLen != 0)
            {
                pLen[pRef->uDistIndex] = pRef->OutBufData.IOLookUp[uOurInp].uLen - 1;
                ds_memcpy(ppInputs[pRef->uDistIndex], pOsrc, pLen[pRef->uDistIndex]);
                pRef->iDistProcTimeTotal += NetTickDiff(NetTick(), pRef->OutBufData.IOLookUp[uOurInp].uInsertTime);
                pRef->iDistProcCnt++;
                pDataTypes[pRef->uDistIndex] = *(pRef->OutBufData.pControllerIO + pRef->OutBufData.IOLookUp[uOurInp].uPos);
            }
            else
            {
                NetPrintf(("netgamedist: detected invalid pairing index between input and output queue; ignoring"));
                pLen[pRef->uDistIndex] = 0;
                pDataTypes[pRef->uDistIndex] = GMDIST_DATA_NONE;
            }
        }
        else
        {
            pRef->iIOOffset = GMDIST_Modulo(pRef->iIOOffset - 1, PACKET_WINDOW);
            pDataTypes[pRef->uDistIndex] = GMDIST_DATA_NONE;
            pLen[pRef->uDistIndex] = 0;
        }

        if (bCRCRequest)
        {
            pDataTypes[pRef->uDistIndex] |= GMDIST_DATA_CRC_REQUEST;
        }
    }

    // calculate time in queue and add it to the total
    pRef->iWaitTimeTotal += NetTickDiff(NetTick(), pRef->InpBufData.IOLookUp[pRef->InpBufData.iBeg].uInsertTime);
    pRef->iInboundDequeueCnt++;

    // return item from buffer
    pRef->InpBufData.iBeg = GMDIST_Modulo(pRef->InpBufData.iBeg + 1, PACKET_WINDOW);
    // update global sequence number
    pRef->uGlobalSeq += 1;

    #if DIRTYCODE_LOGGING
    NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] exiting NetGameDistInputQueryMulti() retval=%d tick=%d\n", pRef, pRef->uGlobalSeq, NetTick()));
    NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "  ==== Dumping player-specific data returned in [OUT] params\n"));
    if (ppInputs)
    {
        for (uIndex = 0; uIndex < pRef->uTotalPlyrs; uIndex++)
        {
            uint8_t uDataType = (pDataTypes[uIndex] & ~GMDIST_DATA_CRC_REQUEST);
            NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "    == Player %u --> len=%d type=%s\n", uIndex, pLen[uIndex],
                _strNetGameDistDataTypes[(((uDataType >= GMDIST_DATA_NONE) && (uDataType <= GMDIST_DATA_NODATA)) ? uDataType : 0)]));
        }
    }
    NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "  ==== End of player-specific data dump\n"));
    #endif

    // return the sequence number
    return(pRef->uGlobalSeq);
}

/*F*************************************************************************************************/
/*!
    \Function    NetGameDistInputLocal

    \Description
        Provide local input data

    \Input *pRef    - reference pointer
    \Input *pBuffer - controller data
    \Input iLength  - data length

    \Output
        int32_t     - negative=error (including GMDIST_OVERFLOW=overflow), positive=successfully sent or queued

    \Version 12/20/00 (GWS)
*/
/*************************************************************************************************F*/
// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
int32_t NetGameDistInputLocal(NetGameDistRefT *pRef, void *pBuffer, int32_t iLength, uint64_t *pLatency)
// FIFA_END
{
    if (!pBuffer)
    {
		// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
        return(_NetGameDistSendInput(pRef, NULL, iLength, 0, NULL));
		// FIFA_END
    }
    pRef->aMultiBuf[0] = GMDIST_DATA_INPUT;
    ds_memcpy(pRef->aMultiBuf + 1, pBuffer, iLength);
	// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
    return(_NetGameDistSendInput(pRef, pRef->aMultiBuf, iLength + 1, 1, pLatency));
	// FIFA_END
}

/*F*************************************************************************************************/
/*!
    \Function    NetGameDistInputLocalMulti

    \Description
        Provide local input data for all players, including ours, which will be discarded

    \Input *pRef            - reference pointer
    \Input *pTypesArray     - array of data types (GMDIST_DATA_INPUT_DROPPABLE, GMDIST_DATA_INPUT)
    \Input **ppBuffer       - array of controller data
    \Input *pLengthsArray   - array of data length
    \Input iDelta           - game team should pass in 1, other values only usable on server

    \Output
        int32_t             - negative=error (including GMDIST_OVERFLOW*=overflow), positive=successfully sent or queued

    \Version 02/08/07 (jrainy)
*/
/*************************************************************************************************F*/
// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
int32_t NetGameDistInputLocalMulti(NetGameDistRefT *pRef, uint8_t *pTypesArray, void **ppBuffer, int32_t *pLengthsArray, int32_t iDelta, uint64_t *pLatency)
// FIFA_END
{
    uint32_t uIndex, uPos, uCount;
    uint8_t uOffsetLengths;
    uint8_t uOffsetBuffer;
    uint8_t uOffsetTypes;
    uint8_t uOffsetVersion;
    uint8_t *pLengths;
    uint8_t *pTypePos;
    uint8_t uOurType, uType;
    uint32_t uSendSize;
    int16_t iLengthSize = 1;
    uint16_t uPlayerCount = pRef->bActAsServer ? pRef->uTotalPlyrs : 1;
    uint16_t uLastPlayer = uPlayerCount;
    int32_t iResult;

    // check if any input forces us to send fat multi-packets.
    for (uIndex = 0; uIndex < uPlayerCount; uIndex++)
    {
        if (pLengthsArray[uIndex] > 0xFF)
        {
            iLengthSize = 2;
        }
    }

    // if we are sending sparse multipacket, adjust the player count accordingly
    if (pRef->bSparse && pRef->bActAsServer)
    {
        uPlayerCount = pRef->aMetaInfoToSend[GMDIST_Modulo(pRef->uUpdateMetaInfoEnd - 1, GMDIST_META_ARRAY_SIZE)].uPlayerCount;
    }

    // always pack multipacket as if there was at least 2 players (simplify the logic)
    if (pRef->bActAsServer && (uPlayerCount == 1))
    {
        uLastPlayer = 2;
        uPlayerCount = 2;
    }

    // compute the offset to various parts of the multipacket
    uOffsetVersion = pRef->bActAsServer ? 1 : 0;
    uOffsetTypes = uOffsetVersion + ((pRef->bSparse && pRef->bActAsServer) ? 1 : 0);
    uOffsetLengths = uOffsetTypes + (pRef->bActAsServer ? (uPlayerCount / 2) : 1);
    uOffsetBuffer = uOffsetLengths + (pRef->bActAsServer ? (iLengthSize * (uPlayerCount - 2)) : 0);

    // if server mode
    if (pRef->bActAsServer)
    {
        // compute the total packet size
        uSendSize = uOffsetBuffer;
        for (uIndex = 0; uIndex < pRef->uTotalPlyrs; uIndex++)
        {
            if (uIndex != pRef->uDistIndex)
            {
                uSendSize += pLengthsArray[uIndex];
            }
        }

        // bail out if the packet would overflow
        if (uSendSize >= NETGAME_DATAPKT_MAXSIZE)
        {
            ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "Multipacket bigger than NETGAME_DATAPKT_MAXSIZE (%d). Discarding and reporting overflow.\n", NETGAME_DATAPKT_MAXSIZE);
            NetPrintf(("netgamedist: [%p] Multipacket bigger than NETGAME_DATAPKT_MAXSIZE (%d). Discarding and reporting overflow.\n", pRef, NETGAME_DATAPKT_MAXSIZE));
            return(GMDIST_OVERFLOW_MULTI);
        }

        // mark the version, if we are sending meta information
        if (pRef->bSparse)
        {
            pRef->aMultiBuf[uOffsetVersion] = GMDIST_Modulo(pRef->aMetaInfoToSend[GMDIST_Modulo(pRef->uUpdateMetaInfoEnd - 1, GMDIST_META_ARRAY_SIZE)].uVer, GMDIST_META_ARRAY_SIZE);
        }

        uOurType = (pTypesArray[pRef->uDistIndex] & ~GMDIST_DATA_CRC_REQUEST);
        if (uOurType == GMDIST_DATA_NONE)
        {
            pRef->aMultiBuf[0] = 0;
        }
        else
        {
            if (((iDelta - 1) - pRef->iLastSentDelta) >= PACKET_WINDOW)
            {
                ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "GMDIST_OVERFLOW_WINDOW (pRef->m_packetid[pRef->InpBufData.iBeg]-1) is %d, pRef->iLastSentDelta is %d.\n", (pRef->aPacketId[pRef->InpBufData.iBeg]-1), pRef->iLastSentDelta);
                return(GMDIST_OVERFLOW_WINDOW);
            }

            pRef->aMultiBuf[0] = (iDelta-1) - pRef->iLastSentDelta;

            pRef->iLastSentDelta = (iDelta-1);
        }
        pRef->aPacketId[pRef->InpBufData.iBeg] = 0;

        if (uOurType == GMDIST_DATA_NONE)
        {
            pRef->iIOOffset = GMDIST_Modulo(pRef->iIOOffset + 1, PACKET_WINDOW);
        }
    }

    pLengths = (unsigned char *)pRef->aMultiBuf + uOffsetLengths;
    pTypePos = (unsigned char *)pRef->aMultiBuf + uOffsetTypes;

    uPos = uOffsetBuffer;
    uCount = 0;

    for (uIndex = 0; uIndex < uLastPlayer; uIndex++)
    {
        uType = (pTypesArray[uIndex] & ~GMDIST_DATA_CRC_REQUEST);

        if (((uType == GMDIST_DATA_NONE) || (uType == GMDIST_DATA_DISCONNECT)) && !pRef->bActAsServer)
        {
            #if DIRTYCODE_LOGGING
            NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY,"netgamedist: [%p] Exiting NetGameDistInputLocalMulti() because of invalid data type.\n", pRef));
            #endif
            return(GMDIST_INVALID);
        }

        // if sending multipackets, don’t send input back to user who originally sent it
        if (pRef->bActAsServer && (uIndex == pRef->uDistIndex))
        {
            continue;
        }
        // if sending sparse multipackets, skip sending blank inputs for users that aren't in the game
        if (pRef->bSparse && ((pRef->aMetaInfoToSend[GMDIST_Modulo(pRef->uUpdateMetaInfoEnd - 1, GMDIST_META_ARRAY_SIZE)].uMask & (1 << uIndex)) == 0))
        {
            continue;
        }

        // put the data in
        ds_memcpy(pRef->aMultiBuf + uPos, ppBuffer[uIndex], pLengthsArray[uIndex]);
        uPos += pLengthsArray[uIndex];

        if (pRef->bActAsServer)
        {
            if (uCount < (uPlayerCount - 2u))
            {
                // we assign the length here for normal packets
                if (iLengthSize == 1)
                {
                    pLengths[uCount] = pLengthsArray[uIndex];
                }
                else
                {
                    // and for fat multipackets
                    pLengths[uCount * 2] = pLengthsArray[uIndex] / 256;
                    pLengths[uCount * 2 + 1] = pLengthsArray[uIndex] % 256;
                }
            }
        }

        if (uCount % 2)
        {
            *pTypePos = (*pTypePos & 0x0f) | ((pTypesArray[uIndex] << 4) & 0xf0);
            pTypePos++;
        }
        else
        {
            *pTypePos = (pTypesArray[uIndex] & 0x0f);
        }
        uCount++;
    }

    if (!NetGameDistStatus(pRef, '?cws', uPos, NULL, 0))
    {
        #if DIRTYCODE_LOGGING
        NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] Exiting NetGameDistInputLocalMulti() with return value GMDIST_OVERFLOW.\n", pRef));
        #endif
        return(GMDIST_OVERFLOW);
    }

    #if DIRTYCODE_LOGGING
    {
        uint8_t uDataType = (pTypesArray[0] & ~GMDIST_DATA_CRC_REQUEST);
        NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] NetGameDistInputLocalMulti() called with tick=%d len=%d type=%s\n",
            pRef, NetTick(), pLengthsArray[0], _strNetGameDistDataTypes[(((uDataType >= GMDIST_DATA_NONE) && (uDataType <= GMDIST_DATA_NODATA)) ? uDataType : 0)]));

    }
    #endif

    // increment output packet count
    if ((iResult = _NetGameDistSendInput(pRef, pRef->aMultiBuf, uPos, iLengthSize, pLatency)) > 0)
    {
        pRef->iOutboundPktCnt++;
    }

    // pass the size of length used to the send input function
    return(iResult);
}

/*F*************************************************************************************************/
/*!
    \Function    NetGameDistInputCheck

    \Description
        Check input status (see how long till next)

    \Input *pRef    - reference pointer
    \Input *pSend   - (optional) stores time until next packet should be sent
    \Input *pRecv   - (optional) stores whether data is available

    \Version 12/20/00 (GWS)
*/
/*************************************************************************************************F*/
void NetGameDistInputCheck(NetGameDistRefT *pRef, int32_t *pSend, int32_t *pRecv)
{
    int32_t iRemain;
    uint32_t uTick;
    int32_t iInData;
    int32_t iOutData;

    // call the status proc
    (pRef->pStatProc)(pRef->pNetGameLinkRef, 'stat', 0, &pRef->NetGameLinkStats, sizeof(NetGameLinkStatT));

    // get current time
    uTick = NetTick();

    // make sure next send time is initialized
    if (pRef->uInpNext == 0)
    {
        pRef->uInpNext = uTick;
    }

    // see if its time to recalc network parms
    if (uTick > pRef->uInpCalc)
    {
        // set the number of unacknowledged packets permitted
        // (this number must be large enough to cover the network latency
        pRef->iInputWind = (pRef->NetGameLinkStats.late+pRef->iInputRate)/pRef->iInputRate;

        if (pRef->bRecvMulti)
        {
            pRef->iInputWind *= 2;
        }

        if (pRef->iInputWind < pRef->iInputMini)
        {
            pRef->iInputWind = pRef->iInputMini;
        }
        if (pRef->iInputWind > pRef->iInputMaxi)
        {
            pRef->iInputWind = pRef->iInputMaxi;
        }
        if (pRef->iInputWind > 500/pRef->iInputRate)
        {
            pRef->iInputWind = 500/pRef->iInputRate;
        }
        // determine time till next update
        pRef->uInpCalc = uTick + pRef->iInputRate;
    }

    // figure out time remaining until next send
    iRemain = pRef->uInpNext - uTick;

    // clamp to valid rate
    if (iRemain < 0)
    {
        iRemain = 0;
    }
    if (iRemain > pRef->iInputRate *2)
    {
        iRemain = pRef->iInputRate *2;
    }

    _NetGameDistCheckWindow(pRef, iRemain, uTick);

    // figure out time till next send
    iRemain = pRef->uInpNext - uTick;
    // clamp to valid rate
    if (iRemain < 0)
    {
        iRemain = 0;
    }

    // return time until next packet should be sent
    if (pSend != NULL)
    {
        // make sure rate is set
        if (pRef->iInputRate == 0)
        {
            // data is not initialized -- just return a delay
            *pSend = 50;
        }
        else if ((pRef->OutBufData.iBeg != pRef->OutBufData.iEnd) || (!pRef->bRdyRecvRemote))
        {
            // dont send while something in output queue
            *pSend = 10;
        }
        else
        {
            *pSend = iRemain;
        }
    }

    // indicate if a packet is waiting
    if (pRecv != NULL)
    {
        // if waiting on remote data, do an update
        if (pRef->InpBufData.iBeg == pRef->InpBufData.iEnd)
        {
            NetGameDistUpdate(pRef);
        }
        // get input data queue length
        iInData = GMDIST_Modulo(pRef->InpBufData.iEnd - pRef->InpBufData.iBeg, PACKET_WINDOW);
        // get output data queue length (in multi-mode we assume one so we don't gate receiving due to an empty output queue)
        iOutData = !pRef->bRecvMulti ? GMDIST_Modulo(pRef->OutBufData.iEnd - GMDIST_Modulo(pRef->InpBufData.iBeg + pRef->iIOOffset, PACKET_WINDOW), PACKET_WINDOW) : 1;
        // write if data is available (non-zero=available)
        *pRecv = (iInData < iOutData) ? iInData : iOutData;
    }

    #if DIRTYCODE_LOGGING
    _NetGameDistInputCheckLog(pRef, pSend, pRecv);
    #endif
}

/*F*************************************************************************************************/
/*!
    \Function    NetGameDistInputRate

    \Description
        Set the input rate

    \Input *pRef    - reference pointer
    \Input iRate    - new input rate

    \Version 12/20/00 (GWS)
*/
/*************************************************************************************************F*/
void NetGameDistInputRate(NetGameDistRefT *pRef, int32_t iRate)
{
    // save rate if changed
    if (iRate > 0)
    {
        pRef->iInputRate = iRate;
    }
}

/*F*************************************************************************************************/
/*!
    \Function    NetGameDistInputClear

    \Description
        Flush the input queue.

    \Input *pRef     - reference pointer

    \Notes
        This must be done with independent synchronization before and after to avoid issues.

    \Version 12/20/00 (GWS)
*/
/*************************************************************************************************F*/
void NetGameDistInputClear(NetGameDistRefT *pRef)
{
    // reset buffer pointers
    pRef->OutBufData.iEnd = 0;
    pRef->OutBufData.iBeg = 0;
    pRef->InpBufData.iEnd = 0;
    pRef->InpBufData.iBeg = 0;

    // reset sequence numbers
    pRef->uLocalSeq = 0;
    pRef->uGlobalSeq = 0;

    // reset the iooffset between the two queues.
    pRef->iIOOffset = 0;

    // reset the send time
    pRef->uInpNext = NetTick();
}

/*F*************************************************************************************************/
/*!
    \Function    NetGameDistControl

    \Description
        Set NetGameDist operation parameters

    \Input *pRef     - reference pointer
    \Input iSelect   - item to tweak
    \Input iValue    - tweak value
    \Input pValue    - pointer tweak value

    \Output
        int32_t         - selector specific

    \Notes

        Selectors are:

    \verbatim
        SELECTOR    DESCRIPTION
        'clri'      clear the local queue of inputs
        'crcs'      enable/disable the reception of CRC challenges
        'lcrc'      to provide the current CRC as request by NetGameDist
        'lrcv'      local recv flow control. Set whether we are ready to receive or not.
        'lsnd'      local send flow control. Set whether we are ready to send or not.
        'maxi'      max window size clamp
        'mini'      min window size clamp
        'spam'      sets debug output verbosity (0...n)
    \endverbatim

    \Version 11/18/08 (jrainy)
*/
/*************************************************************************************************F*/
int32_t NetGameDistControl(NetGameDistRefT *pRef, int32_t iSelect, int32_t iValue, void *pValue)
{
    if (iSelect == 'clri')
    {
        NetGameDistInputClear(pRef);
        return(0);
    }
    if (iSelect == 'crcs')
    {
        pRef->bCRCChallenges = iValue;
        return(0);
    }
    if (iSelect == 'lcrc')
    {
        pRef->bUpdateFlowCtrl = TRUE;
        pRef->uLocalCRC = iValue;
        pRef->bLocalCRCValid = TRUE;
        return(0);
    }
    if (iSelect == 'lrcv')
    {
        if (iValue != pRef->bRdyRecv)
        {
            pRef->bUpdateFlowCtrl = TRUE;
            pRef->bRdyRecv = iValue;
            NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] flow control change -> %s to receive\n", pRef, (pRef->bRdyRecv ? "ready" : "not ready")));
        }
        return(pRef->bRdyRecv);
    }
    if (iSelect == 'lsnd')
    {
        if (iValue != pRef->bRdySend)
        {
            pRef->bUpdateFlowCtrl = TRUE;
            pRef->bRdySend = iValue;
            NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] flow control change -> %s to send\n", pRef, (pRef->bRdySend ? "ready" : "not ready")));
        }
        return(pRef->bRdySend);
    }
    if (iSelect == 'maxi')
    {
        if (iValue >= 0)
        {
            pRef->iInputMaxi = iValue;
        }
        return(pRef->iInputMaxi);
    }
    if (iSelect == 'mini')
    {
        if (iValue >= 0)
        {
            pRef->iInputMini = iValue;
        }
        return(pRef->iInputMini);
    }
    if (iSelect == 'spam')
    {
        pRef->uVerbose = iValue;
        return(0);
    }
    // no action
    return(-1);
}

/*F*************************************************************************************************/
/*!
    \Function    NetGameDistUpdate

    \Description
        Perform periodic tasks.

    \Input *pRef         - reference pointer

    \Output
        uint32_t        - current sequence number

    \Notes
        Application must call this every 100ms or so.

    \Version 12/20/00 (GWS)
*/
/*************************************************************************************************F*/
uint32_t NetGameDistUpdate(NetGameDistRefT *pRef)
{
    NetGamePacketT* pPacket = (NetGamePacketT*)&pRef->MaxPkt;
    unsigned char *pData;
    int32_t iNext, iCurrent;
    uint32_t uIndex, uPos;
    unsigned char *pExisting;
    unsigned char *pIncoming;

    // update the input rate if needed
    NetGameDistInputRate(pRef, 0);

    // send any pending game packets
	// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
    NetGameDistInputLocal(pRef, NULL, 0, NULL);
	// FIFA_END

    // grab incoming packets
    while (( GMDIST_Modulo(pRef->InpBufData.iEnd + 1, PACKET_WINDOW) != pRef->InpBufData.iBeg) && (_NetGameDistInputPeekRecv(pRef, pPacket, 1) > 0))
    {
        // dispatch the packet according to type
        if ((pPacket->head.kind == GAME_PACKET_INPUT) ||
            (pPacket->head.kind == GAME_PACKET_INPUT_MULTI) ||
            (pPacket->head.kind == GAME_PACKET_INPUT_MULTI_FAT))
        {
            pRef->iInboundPktCnt++;

            iNext = GMDIST_Modulo(pRef->InpBufData.iEnd + 1, PACKET_WINDOW);
            if (pRef->InpBufData.iBeg != pRef->InpBufData.iEnd && !pRef->bRecvMulti && pRef->bActAsServer && pRef->pDropProc)
            {
                iCurrent = GMDIST_Modulo(pRef->InpBufData.iEnd - 1, PACKET_WINDOW);
                pExisting = (pRef->InpBufData.pControllerIO + pRef->InpBufData.IOLookUp[iCurrent].uPos);
                pIncoming = pPacket->body.data;

                if (pRef->pDropProc(pRef, pExisting + 1, pIncoming + 1, pExisting[0], pIncoming[0]))
                {
                    iNext = pRef->InpBufData.iEnd;
                    pRef->iInboundDropPktCnt++;
                    pRef->InpBufData.iEnd = GMDIST_Modulo(pRef->InpBufData.iEnd - 1, PACKET_WINDOW);
                }
            }

            _ProcessPacketType(pRef, pPacket->head.kind);
            // check for overflow
            if (iNext == pRef->InpBufData.iBeg)
            {
                // ignore the packet
                NetPrintf(("netgamedist: [%p] NetGameDistUpdate() - buffer overflow (queue full)!\n", pRef));
                ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "NetGameDistUpdate, buffer overflow (queue full).");

                pRef->iErrCond = GMDIST_QUEUE_FULL;
                return((unsigned)(-1));
            }
            else
            {
                // point to data buffer

                // we currently write one byte past the buffer after reserving one extra byte
                // the len set in the data structure includes this extra byte past the packet data
                if (_SetDataPtr(pRef, &pRef->InpBufData, pPacket->head.len + 1))
                {
                    pData = pRef->InpBufData.pControllerIO+pRef->InpBufData.IOLookUp[pRef->InpBufData.iEnd].uPos;
                    pRef->InpBufData.IOLookUp[pRef->InpBufData.iEnd].uLen = pPacket->head.len + 1;
                    // copy into buffer
                    ds_memcpy(pData, pPacket->body.data, pPacket->head.len);
                    pData[pPacket->head.len] = pPacket->head.kind;

                    pRef->aPacketId[pRef->InpBufData.iEnd] = pRef->iInboundPktCnt;

// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
#ifdef ENABLED_LATENCY_METER
					ds_memcpy(pRef->InpBufData.IOLookUp[pRef->InpBufData.iEnd].aLatencyBuf, pPacket->head.latencybuf, sizeof(pRef->InpBufData.IOLookUp[pRef->InpBufData.iEnd].aLatencyBuf));
					pRef->InpBufData.IOLookUp[pRef->InpBufData.iEnd].aLatencyBuf[IN_NETGAMEDIST_QUEUE] = NetProcTime();
#endif
// FIFA_END

                    // update queue time
                    pRef->InpBufData.IOLookUp[pRef->InpBufData.iEnd].uInsertTime = pPacket->head.when;

                    // add item to buffer
                    pRef->InpBufData.iEnd = iNext;

                    // display number of packets in buffer
                    #if TIMING_DEBUG
                    NetPrintf(("netgamedist: [%p] NetGameDistUpdate() inpbuf=%d packets\n", pRef,
                        GMDIST_Modulo(pRef->InpBufData.iEnd - pRef->InpBufData.iBeg, PACKET_WINDOW)));
                    #endif
                }
                else
                {
                    NetPrintf(("netgamedist: [%p] NetGameDistUpdate() - buffer overflow (memory)!\n", pRef));
                    pRef->iErrCond = GMDIST_QUEUE_MEMORY;
                    return((unsigned)(-1));
                }
            }
        }
        else if (pPacket->head.kind == GAME_PACKET_STATS)
        {
            // clear the contents, if a player leaves the game we don't want their stats to be misrepresented
            ds_memclr(pRef->aRecvStats, sizeof(pRef->aRecvStats));

            for (uPos = 0, uIndex = 0; uPos < pPacket->head.len; uIndex += 1)
            {
                ds_memcpy(&pRef->aRecvStats[uIndex], pPacket->body.data + uPos, sizeof(*pRef->aRecvStats) );
                uPos += sizeof(*pRef->aRecvStats);

                pRef->aRecvStats[uIndex].late = SocketNtohs(pRef->aRecvStats[uIndex].late);
                pRef->aRecvStats[uIndex].bps = SocketNtohs(pRef->aRecvStats[uIndex].bps);
            }
        }
        else if (pPacket->head.kind == GAME_PACKET_INPUT_FLOW)
        {
            pRef->bRdySendRemote = pPacket->body.data[0];
            pRef->bRdyRecvRemote = pPacket->body.data[1];

            if (pPacket->head.len >= 7)
            {
                if (pRef->MaxPkt.body.data[2])
                {
                    pRef->bRemoteCRCValid = TRUE;
                    pRef->uRemoteCRC = (pRef->MaxPkt.body.data[6] |
                                        (pRef->MaxPkt.body.data[5] << 8) |
                                        (pRef->MaxPkt.body.data[4] << 16) |
                                        (pRef->MaxPkt.body.data[3] << 24));
                }
                #if DIRTYCODE_LOGGING
                NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] bRemoteCRCValid is %d, uRemoteCRC is %d\n", pRef, pRef->bRemoteCRCValid, pRef->uRemoteCRC));
                #endif
            }

            NetPrintf(("netgamedist: [%p] Got GAME_PACKET_INPUT_FLOW (send %d recv %d)\n", pRef, pRef->bRdySendRemote, pRef->bRdyRecvRemote));
        }
        else if (pPacket->head.kind == GAME_PACKET_INPUT_META)
        {
            // process meta-information about sparse multi-packets
            uint8_t uVer;
            uint32_t uMask;

            pRef->bGotMetaInfo = TRUE;

            uVer = GMDIST_Modulo(pPacket->body.data[0], GMDIST_META_ARRAY_SIZE);
            uMask = ((pPacket->body.data[1] << 24) +
                     (pPacket->body.data[2] << 16) +
                     (pPacket->body.data[3] << 8) +
                     (pPacket->body.data[4]));

            NetPrintf(("netgamedist: Got GAME_PACKET_INPUT_META (version %d mask 0x%08x)\n", uVer, uMask));

            pRef->aMetaInfo[uVer].uMask = uMask;
            pRef->aMetaInfo[uVer].uPlayerCount = _NetGameDistCountBits(pRef, uMask);
            pRef->aMetaInfo[uVer].uVer = uVer;
        }
    }

    // update link status
    NetGameDistStatus(pRef, 'stat', 0, NULL, 0);

    // show the history
    #if PING_DEBUG
    _PingHistory(&pRef->NetGameLinkStats);
    #endif

    // send meta-information as needed
    if (pRef->uUpdateMetaInfoBeg != pRef->uUpdateMetaInfoEnd)
    {
        _NetGameDistSendMetaInfo(pRef);
    }

    // send flow control updates as needed
    if (pRef->bUpdateFlowCtrl)
    {
        _NetGameDistSendFlowUpdate(pRef);
    }

    // return current sequence number
    return(pRef->uGlobalSeq);
}

/*F*************************************************************************************************/
/*!
    \Function    NetGameDistSetProc

    \Description
        Sets or override the various procedure pointers.

    \Input *pRef        - reference pointer
    \Input iKind        - kind
    \Input *pProc       - proc

    \Notes
        'drop' should only be used on game servers

    \Version 02/27/07 (jrainy)
*/
/*************************************************************************************************F*/
void NetGameDistSetProc(NetGameDistRefT *pRef, int32_t iKind, void *pProc)
{
    switch(iKind)
    {
    case 'drop':
        pRef->pDropProc = (NetGameDistDropProc *)pProc;
        break;
    case 'peek':
        pRef->pPeekProc = (NetGameDistPeekProc *)pProc;
        break;
    case 'recv':
        pRef->pRecvProc = (NetGameDistRecvProc *)pProc;
        break;
    case 'send':
        pRef->pSendProc = (NetGameDistSendProc *)pProc;
        break;
    case 'stat':
        pRef->pStatProc = (NetGameDistStatProc *)pProc;
        break;
    case 'link':
        pRef->pLinkCtrlProc = (NetGameDistLinkCtrlProc *)pProc;
        break;
    }
}

/*F*************************************************************************************************/
/*!
    \Function    NetGameDistSendStats

    \Description
        Send stats. Meant to be used by the game server to send stats regarding all clients

    \Input *pRef         - reference pointer
    \Input *pStats       - uTotalPlyrs-sized array of stats

    \Version 03/14/07 (jrainy)
*/
/*************************************************************************************************F*/
void NetGameDistSendStats(NetGameDistRefT *pRef, NetGameDistStatT *pStats)
{
    uint32_t uIndex;
    int32_t iPos;

    pRef->MaxPkt.head.kind = GAME_PACKET_STATS;
    pRef->MaxPkt.head.len = (uint16_t)sizeof(NetGameDistStatT) * pRef->uTotalPlyrs;

// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
#ifdef ENABLED_LATENCY_METER
	pRef->MaxPkt.head.latency = NULL;
#endif
// FIFA_END

    iPos = 0;
    for (uIndex = 0; uIndex<pRef->uTotalPlyrs; uIndex++)
    {
        ds_memcpy(pRef->MaxPkt.body.data + iPos, &pStats[uIndex], sizeof(NetGameDistStatT));
        iPos += sizeof(NetGameDistStatT);
    }

    pRef->pSendProc(pRef->pNetGameLinkRef, (NetGamePacketT*)&pRef->MaxPkt, 1);
}

/*F*************************************************************************************************/
/*!
    \Function    NetGameDistGetError

    \Description
        return non-zero if there ever was an overflow

    \Input *pRef     - reference pointer

    \Output
        int32_t      - 0 if there was no error, positive >0 otherwise.

    \Version 03/23/07 (jrainy)
*/
/*************************************************************************************************F*/
int32_t NetGameDistGetError(NetGameDistRefT *pRef)
{
    return(pRef->iErrCond);
}

/*F*************************************************************************************************/
/*!
    \Function    NetGameDistGetErrorText

    \Description
            copies into the passed buffer the last error text.

    \Input *pRef         - reference pointer
    \Input *pErrorBuffer - buffer to write into
    \Input iBufSize      - buf size

    \Version 08/15/08 (jrainy)
*/
/*************************************************************************************************F*/
void NetGameDistGetErrorText(NetGameDistRefT *pRef, char *pErrorBuffer, int32_t iBufSize)
{
    int32_t iMinSize = sizeof(pRef->strErrorText);

    if (iBufSize < iMinSize)
    {
        iMinSize = iBufSize;
    }

    strncpy(pErrorBuffer, pRef->strErrorText, iMinSize);
    pErrorBuffer[iMinSize - 1] = 0;
}

/*F*************************************************************************************************/
/*!
    \Function    NetGameDistResetError

    \Description
        reset the overflow error condition to 0.

    \Input *pRef    - reference pointer

    \Version 03/23/07 (jrainy)
*/
/*************************************************************************************************F*/
void NetGameDistResetError(NetGameDistRefT *pRef)
{
    pRef->iErrCond = 0;
}


// FIFA_BEGIN | DIVERGENCE | nmeyer | 2016-05-09 | Adding a function to provide detailed logging
void NetGameDistDebugLog(NetGameDistRefT *pRef)
{
#if DIRTYCODE_LOGGING
	int32_t curTick = NetTick();

	// Print packet information.
	//
	//	Basic Summary
	//		1. Input packets are coming from the remote peer or rebroadcaster.
	//		   These should be coming in at a rate of 30 Hz and will contain inputs for all players except yourself.
	//
	//		2. Output packets are sent from the local machine.
	//		   These should be sending at a rate of 30 Hz.
	//		   Dirtysock will save your outbound packets and pair them with the input packets when they arrive.
	//		   If the inbound packet window is full (default 64 packets), outbound packets will be dropped.
	//
	//		3. In rebroadcaster games, it's OK if the outbound packets are slower than 30 Hz. This just means you alone will have input lag.
	//		   In P2P games, it's NOT OK if the outbound packets are slower than 30 Hz. This will hang both you and your peer.
	//		   In P2P or rebroadcaster games, it's NOT OK to process the inbound packets slower than 30 Hz. This will cause the input buffer to overflow and result in incoming packets being dropped.
	// 		   Always try to call NetGameDistInputQueryMulti at 30Hz, or faster if you need to catchup.
	//
	//	How to interpret this data
	//		1. Input and output buffers are circular with a max count of PACKET_WINDOW, which by default is 64 packets.
	//		2. in.beg points to the index of the 1st inbound packet. Same for out.beg for outbound packets.
	//		3. in.end points to the index of the last inbound packet. Same for out.end for outbound packets.
	//		4. If the circular delta between beg and end is >= PACKET_WINDOW, the input/outbut buffer is full.
	//		5. Generally the outbound buffer will not have overflow issues because packets are queued and sent at the same time.
	//		6. As long as you keep pumping dirtysock at 30 Hz, the beginning index should be very close to the ending index (+/- 1 packet).
	//		7. If there was an unexpected hang or latency spike, the distance between the beginning and ending input indices will grow.
	//				Ex: Latency = 40 ms spike (~1 packet)
	//					in.beg = 12, in.end = 13
	//				Ex: Latency = 100 ms spike (~4 packets)
	//					in.beg = 12, in.end = 16
	//		7. send.last measures the time, in milliseconds, since the last send request. Generally, this should be around 30 Hz.
	//		8. recv.last measures the time, in milliseconds, since we last received and processed a packet. This should be around 30 Hz.
	//		9. plyrs.total keeps track of how many players are registered in the tunnel (including yourself).
	NetPrintf(("netgamedist: packet log - in.beg=%d, in.end=%d, out.beg=%d, out.end=%d, io.offset=%d, send.last=%d, recv.last=%d, plyrs.total=%d\n",
		pRef->InpBufData.iBeg, pRef->InpBufData.iEnd,
		pRef->OutBufData.iBeg, pRef->OutBufData.iEnd,
		pRef->iIOOffset,
		curTick - pRef->NetGameLinkStats.sentlast, curTick - pRef->NetGameLinkStats.rcvdlast,
		pRef->uTotalPlyrs));
#endif //#if DIRTYCODE_LOGGING
}
// FIFA_END