// ---------------------------------------------------------------------------------------
// halw.cpp
//
// Copyright (C) Microsoft Corporation
// ---------------------------------------------------------------------------------------

#include "xnp.h"
#include "xnver.h"

#ifdef XNET_FEATURE_WINDOWS

#include <stdlib.h>
#include <stdio.h>
#include <malloc.h>
#include <rsa.h>
#include <rc4.h>
#include <sha.h>
#include <modes.h>
#include <des.h>
#include <tripldes.h>
#include <benaloh.h>
#include <shahmac.h>
#include <cryptkeys.h>

// ---------------------------------------------------------------------------------------
// Hal Dpc Support
// ---------------------------------------------------------------------------------------

struct CHalDpc
{
    LONG                _lInitLock;             // Spin lock to synchronize VLanInit/VLanTerm
    UINT                _cRefs;                 // Number of times HalDpcInit called
    HANDLE              _hEvent;                // Handle to signalling event for dispatch thread
    HANDLE              _hThread;               // Thread handle for dispatch thread
    BOOL                _fShutdown;             // TRUE to shutdown the dispatch thread
    DWORD               _dwThreadId;            // Thread Id of dispatch thread
    DWORD               _dwDpcThreadId;         // Thread Id of thread currently at dispatch level
    INT                 _iThreadPriority;       // Old thread priority of thread at dispatch level
    LIST_ENTRY          _dpcq;                  // Queued KDPCs
    LIST_ENTRY          _timerq;                // Queued KTIMERs
    CRITICAL_SECTION    _cs;                    // Synchronization
};

CHalDpc                 g_HalDpc;

DWORD WINAPI HalDpcThreadProc(void *)
{
    PKDPC       pkdpc;
    PKTIMER     pktimer;
    DWORD       dwNow;
    DWORD       dwWait;
    LONG        lWait;

    while (1)
    {
        EnterCriticalSection(&g_HalDpc._cs);

        if (g_HalDpc._fShutdown)
            break;

        Assert(g_HalDpc._dwThreadId == GetCurrentThreadId());
        Assert(g_HalDpc._dwDpcThreadId == 0);
        g_HalDpc._dwDpcThreadId = g_HalDpc._dwThreadId;
        Assert(g_HalDpc._dwDpcThreadId != 0);

        while (!IsListEmpty(&g_HalDpc._dpcq))
        {
            pkdpc = (PKDPC)RemoveHeadList(&g_HalDpc._dpcq);
            pkdpc->DpcListEntry.Flink = NULL;
            pkdpc->DeferredRoutine(pkdpc,
                                   pkdpc->DeferredContext,
                                   pkdpc->SystemArgument1,
                                   pkdpc->SystemArgument2);
        }

        dwWait = INFINITE;

        if (!IsListEmpty(&g_HalDpc._timerq))
        {
            while (1)
            {
                dwNow   = GetTickCount();
                dwWait  = INFINITE;
                pktimer = (PKTIMER)g_HalDpc._timerq.Flink;
                pkdpc   = NULL;

                while (pktimer != (PKTIMER)&g_HalDpc._timerq)
                {
                    lWait = (LONG)(pktimer->DueTime - dwNow);

                    if (lWait <= 0)
                    {
                        pktimer->DueTime = dwNow + pktimer->Period;
                        pkdpc = pktimer->Dpc;
                        break;
                    }

                    if (dwWait > (DWORD)lWait)
                        dwWait = (DWORD)lWait;

                    pktimer = (PKTIMER)pktimer->TimerListEntry.Flink;
                }

                if (pkdpc == NULL)
                    break;

                pkdpc->DeferredRoutine(pkdpc,
                                       pkdpc->DeferredContext,
                                       pkdpc->SystemArgument1,
                                       pkdpc->SystemArgument2);
            }
        }

        Assert(g_HalDpc._dwThreadId == GetCurrentThreadId());
        Assert(g_HalDpc._dwDpcThreadId == g_HalDpc._dwThreadId);
        g_HalDpc._dwDpcThreadId = 0;
        LeaveCriticalSection(&g_HalDpc._cs);
        WaitForSingleObject(g_HalDpc._hEvent, dwWait);
    }

    return(0);
}

void HalDpcCleanup()
{
    BOOL fResult = FALSE;

    if (g_HalDpc._hThread)
    {
        Assert(g_HalDpc._dwDpcThreadId == GetCurrentThreadId());
        g_HalDpc._fShutdown = TRUE;
        LeaveCriticalSection(&g_HalDpc._cs);
        SetEvent(g_HalDpc._hEvent);
        WaitForSingleObject(g_HalDpc._hThread, INFINITE);
        CloseHandle(g_HalDpc._hThread);
    }

    if (g_HalDpc._hEvent)
    {
        CloseHandle(g_HalDpc._hEvent);
    }

    DeleteCriticalSection(&g_HalDpc._cs);

    memset(&g_HalDpc, 0, sizeof(g_HalDpc));
}

BOOL HalDpcInit()
{
    BOOL fResult = FALSE;

    while (InterlockedCompareExchange(&g_HalDpc._lInitLock, 1, 0))
    {
        Sleep(10);
    }

    if (g_HalDpc._cRefs == 0)
    {
        InitializeCriticalSection(&g_HalDpc._cs);
        InitializeListHead(&g_HalDpc._dpcq);
        InitializeListHead(&g_HalDpc._timerq);

        g_HalDpc._hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

        if (g_HalDpc._hEvent == NULL)
        {
            TraceSz1(Warning, "HalDpcInit - CreateEvent failed (%d)", GetLastError());
            goto ret;
        }

        g_HalDpc._hThread = CreateThread(NULL, 0, HalDpcThreadProc, NULL, 0, &g_HalDpc._dwThreadId);

        if (g_HalDpc._hThread == NULL)
        {
            TraceSz1(Warning, "HalDpcInit - CreateThread failed (%d)", GetLastError());
            goto ret;
        }

        // Increase the priority of the DPC thread so that is preempts any user thread, 
        // and also preempts the VLan thread (which runs at THREAD_PRIORITY_ABOVE_NORMAL).

        SetThreadPriority(g_HalDpc._hThread, THREAD_PRIORITY_HIGHEST);
    }

    g_HalDpc._cRefs += 1;
    fResult = TRUE;

ret:

    if (!fResult)
    {
        HalDpcCleanup();
    }

    g_HalDpc._lInitLock = 0;

    return(fResult);
}

BOOL HalDpcTerm()
{
    BOOL fResult = FALSE;

    while (InterlockedCompareExchange(&g_HalDpc._lInitLock, 1, 0))
    {
        Sleep(10);
    }

    Assert(g_HalDpc._cRefs > 0);

    if (--g_HalDpc._cRefs == 0)
    {
        HalDpcCleanup();
        fResult = TRUE;
    }

    g_HalDpc._lInitLock = 0;

    return(fResult);
}

// ---------------------------------------------------------------------------------------
// CXnHal
// ---------------------------------------------------------------------------------------

NTSTATUS CXnHal::HalInit(XNetInitParams * pxnip)
{
    TCHECK(USER);

    if (!HalDpcInit())
    {
        return(NETERR_MEMORY);
    }

    strncpy(_achXbox, pxnip->pszXbox ? pxnip->pszXbox : "xb1@Lan", sizeof(_achXbox) - 1);

    SetInitFlag(INITF_HAL);

    return(NETERR_OK);
}

void CXnHal::HalTerm()
{
    TCHECK(UDPC);

    SetInitFlag(INITF_HAL_TERM);

    if (TestInitFlag(INITF_HAL))
    {
        if (HalDpcTerm())
        {
            SetInitFlag(INITF_HAL_STOP);
        }
    }
}

KIRQL CXnHal::KeGetCurrentIrql()
{
    ICHECK(HAL, USER|UDPC|SDPC);

    DWORD dwThreadId = GetCurrentThreadId();

    if (dwThreadId == g_HalDpc._dwDpcThreadId)
        return(DISPATCH_LEVEL);
    else
    {
        Assert(dwThreadId != g_HalDpc._dwThreadId);
        return(PASSIVE_LEVEL);
    }
}

KIRQL CXnHal::KeRaiseIrqlToDpcLevel()
{
    ICHECK(HAL, USER|UDPC|SDPC);

    DWORD dwThreadId = GetCurrentThreadId();

    EnterCriticalSection(&g_HalDpc._cs);

    Assert(dwThreadId != g_HalDpc._dwThreadId || dwThreadId == g_HalDpc._dwDpcThreadId);

    if (dwThreadId == g_HalDpc._dwDpcThreadId)
    {
        LeaveCriticalSection(&g_HalDpc._cs);
        Assert(DISPATCH_LEVEL != PASSIVE_LEVEL);
        return(DISPATCH_LEVEL);
    }

    g_HalDpc._dwDpcThreadId = dwThreadId;
    Assert(g_HalDpc._dwDpcThreadId != 0);
    Assert(dwThreadId != g_HalDpc._dwThreadId);

    if (dwThreadId != g_HalDpc._dwThreadId)
    {
        g_HalDpc._iThreadPriority = GetThreadPriority(GetCurrentThread());
        SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
    }

    return(PASSIVE_LEVEL);
}

void CXnHal::KeLowerIrql(KIRQL kirql)
{
    TCHECK(USER|UDPC|SDPC);

    if (TestInitFlag(INITF_HAL_STOP))
    {
        SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);
    }
    else if (kirql == PASSIVE_LEVEL)
    {
        DWORD dwThreadId = GetCurrentThreadId();
        Assert(dwThreadId != g_HalDpc._dwThreadId);
        Assert(dwThreadId == g_HalDpc._dwDpcThreadId);
        g_HalDpc._dwDpcThreadId = 0;

        if (dwThreadId != g_HalDpc._dwThreadId)
        {
            SetThreadPriority(GetCurrentThread(), g_HalDpc._iThreadPriority);
        }

        LeaveCriticalSection(&g_HalDpc._cs);
    }
}

void CXnHal::KeInitializeDpc(PRKDPC Dpc, PKDEFERRED_ROUTINE DeferredRoutine, PVOID DeferredContext)
{
    ICHECK(HAL, USER|UDPC|SDPC);

    Dpc->DeferredRoutine    = DeferredRoutine;
    Dpc->DeferredContext    = DeferredContext;
    Dpc->DpcListEntry.Flink = NULL;
}

BOOLEAN CXnHal::KeInsertQueueDpc(PRKDPC Dpc, PVOID SystemArgument1, PVOID SystemArgument2)
{
    ICHECK(HAL, USER|UDPC|SDPC);

    if (Dpc->DpcListEntry.Flink == NULL)
    {
        EnterCriticalSection(&g_HalDpc._cs);

        if (Dpc->DpcListEntry.Flink == NULL)
        {
            Dpc->SystemArgument1 = SystemArgument1;
            Dpc->SystemArgument2 = SystemArgument2;
            AssertList(&g_HalDpc._dpcq);
            InsertTailList(&g_HalDpc._dpcq, &Dpc->DpcListEntry);
        }

        LeaveCriticalSection(&g_HalDpc._cs);

        SetEvent(g_HalDpc._hEvent);
    }

    return(TRUE);
}

BOOLEAN CXnHal::KeRemoveQueueDpc(PRKDPC Dpc)
{
    ICHECK(HAL, USER|UDPC|SDPC);

    if (Dpc->DpcListEntry.Flink != NULL)
    {
        EnterCriticalSection(&g_HalDpc._cs);

        if (Dpc->DpcListEntry.Flink != NULL)
        {
            AssertListEntry(&g_HalDpc._dpcq, &Dpc->DpcListEntry);
            RemoveEntryList(&Dpc->DpcListEntry);
            Dpc->DpcListEntry.Flink = NULL;
        }

        LeaveCriticalSection(&g_HalDpc._cs);
    }

    return(TRUE);
}

void CXnHal::KeInitializeTimer(PKTIMER Timer)
{
    ICHECK(HAL, USER|UDPC|SDPC);

    Timer->TimerListEntry.Flink = NULL;
}

BOOLEAN CXnHal::KeSetTimerEx(PKTIMER Timer, LARGE_INTEGER DueTime, LONG Period, PKDPC Dpc)
{
    ICHECK(HAL, USER|UDPC|SDPC);

    EnterCriticalSection(&g_HalDpc._cs);

    if (Timer->TimerListEntry.Flink == NULL)
    {
        AssertList(&g_HalDpc._timerq);
        Timer->DueTime = GetTickCount() + (DWORD)(DueTime.QuadPart / -10000);
        Timer->Period  = Period;
        Timer->Dpc     = Dpc;
        InsertTailList(&g_HalDpc._timerq, &Timer->TimerListEntry);
    }

    LeaveCriticalSection(&g_HalDpc._cs);

    SetEvent(g_HalDpc._hEvent);

    return(TRUE);
}

BOOLEAN CXnHal::KeCancelTimer(PKTIMER Timer)
{
    ICHECK(HAL, USER|UDPC|SDPC);

    if (Timer->TimerListEntry.Flink != NULL)
    {
        EnterCriticalSection(&g_HalDpc._cs);

        if (Timer->TimerListEntry.Flink != NULL)
        {
            AssertListEntry(&g_HalDpc._timerq, &Timer->TimerListEntry);
            RemoveEntryList(&Timer->TimerListEntry);
            Timer->TimerListEntry.Flink = NULL;
        }

        LeaveCriticalSection(&g_HalDpc._cs);
    }

    return(TRUE);
}

CRaiseToDpc::CRaiseToDpc(CXnHal * pXnHal)
{
    _pXnHal = pXnHal;
    _irql = pXnHal->KeRaiseIrqlToDpcLevel();
    Assert(_irql == PASSIVE_LEVEL || _irql == DISPATCH_LEVEL);
    Assert(GetCurrentThreadId() != g_HalDpc._dwThreadId || _irql == DISPATCH_LEVEL);
}

CRaiseToDpc::~CRaiseToDpc()
{
    Assert(GetCurrentThreadId() != g_HalDpc._dwThreadId || _irql == DISPATCH_LEVEL);
    Assert(_irql == PASSIVE_LEVEL || _irql == DISPATCH_LEVEL);
    _pXnHal->KeLowerIrql((KIRQL)_irql);
}

#ifdef XNET_FEATURE_ASSERT

BOOL CXnHal::HalThreadCheck(UINT uiCheck)
{
    DWORD dwThreadId = GetCurrentThreadId();

    if (dwThreadId != g_HalDpc._dwDpcThreadId)
        return(uiCheck & USER);
    else if (dwThreadId == g_HalDpc._dwThreadId)
        return(uiCheck & SDPC);
    else
        return(uiCheck & UDPC);
}

#endif

void CXnHal::HalEnterDpc()
{
    EnterCriticalSection(&g_HalDpc._cs);
}

BOOL CXnHal::HalTryEnterDpc()
{
    return(TryEnterCriticalSection(&g_HalDpc._cs));
}

void CXnHal::HalLeaveDpc()
{
    LeaveCriticalSection(&g_HalDpc._cs);
}

// ---------------------------------------------------------------------------------------
// CXnHal::SetInitFlag
// ---------------------------------------------------------------------------------------

void CXnHal::SetInitFlag(DWORD dwFlag)
{
    DWORD dwFlagsOld, dwFlagsNew;

    while (1)
    {
        dwFlagsOld = _dwInitFlags;

        Assert((dwFlagsOld & dwFlag) == 0);

        dwFlagsNew = dwFlagsOld | dwFlag;

        if (InterlockedCompareExchange((LONG *)&_dwInitFlags, (LONG)dwFlagsNew, (LONG)dwFlagsOld) == (LONG)dwFlagsOld)
            break;

        TraceSz(Warning, "CXnHal::SetInitFlag: Thread/DPC contention.  Retrying.");
    }
}

// ---------------------------------------------------------------------------------------
// Hal System Memory
// ---------------------------------------------------------------------------------------

DefineTag(VMem, 0);
DefineTag(VMemFront, 0);

DWORD g_dwVMem = 0;

BOOL VMemIsEnabled()
{
#ifdef XNET_FEATURE_VMEM
    if (g_dwVMem == 0)
    {
        char * pszVMem = getenv("VMEM");

        g_dwVMem = VMEM_CHECKED;

        if (pszVMem)
        {
            g_dwVMem |= VMEM_ENABLED;

            if (lstrcmpiA(pszVMem, "frontside") != 0)
            {
                g_dwVMem |= VMEM_BACKSIDESTRICT;
            }
        }
        else if (Tag(VMem))
        {
            g_dwVMem |= VMEM_ENABLED;

            if (!Tag(VMemFront))
                g_dwVMem |= VMEM_BACKSIDESTRICT;

        }

        if (g_dwVMem & VMEM_ENABLED)
        {
            TraceSz1(VMem, "Enabled for %s byte faults", (g_dwVMem & VMEM_BACKSIDESTRICT) ? "next" : "prev");
        }
    }

    return(g_dwVMem & VMEM_ENABLED);
#else
    return(FALSE);
#endif
}

void * HalAlloc(size_t cb, ULONG tag)
{
#ifdef XNET_FEATURE_VMEM
    if (VMemIsEnabled())
    {
        return(VMemAlloc(cb));
    }
#endif

    return(LocalAlloc(LMEM_FIXED, cb));
}

void * HalAllocZ(size_t cb, ULONG tag)
{
#ifdef XNET_FEATURE_VMEM
    if (VMemIsEnabled())
    {
        void * pv = VMemAlloc(cb);

        if (pv)
        {
            memset(pv, 0, cb);
        }

        return(pv);
    }
#endif

    return(LocalAlloc(LPTR, cb));
}

void HalFree(void * pv)
{
#ifdef XNET_FEATURE_VMEM
    if (VMemIsEnabled())
    {
        VMemFree(pv);
        return;
    }
#endif

    if (pv)
    {
        LocalFree(pv);
    }
}

// ---------------------------------------------------------------------------------------
// Hal Randomness
// ---------------------------------------------------------------------------------------

UINT CXnHal::HalRandGather(BYTE * pb, UINT cb)
{
    memset(pb, 0xAA, cb);
    QueryPerformanceCounter((LARGE_INTEGER *)pb);
    GetSystemTimeAsFileTime((FILETIME *)(pb + sizeof(LARGE_INTEGER)));
    return(cb);
}

// ---------------------------------------------------------------------------------------
// Hal Crypto
// ---------------------------------------------------------------------------------------

void
XcSHAInit(
    IN PUCHAR pbSHAContext
    )
{
    A_SHAInit((A_SHA_CTX*)pbSHAContext);
}
   
void
XcSHAUpdate(
    IN PUCHAR pbSHAContext,
    IN PUCHAR pbInput,
    IN ULONG dwInputLength
    )
{
    A_SHAUpdate((A_SHA_CTX*)pbSHAContext, pbInput, dwInputLength);
}
   
void
XcSHAFinal(
    IN PUCHAR pbSHAContext,
    IN PUCHAR pbDigest
    )
{
    A_SHAFinal((A_SHA_CTX*)pbSHAContext, pbDigest);
}
   
void
XcRC4Key(
    IN PUCHAR pbKeyStruct,
    IN ULONG dwKeyLength,
    IN PUCHAR pbKey
    )
{
    rc4_key((RC4_KEYSTRUCT*)pbKeyStruct, dwKeyLength, pbKey);
}
   
void
XcRC4Crypt(
    IN PUCHAR pbKeyStruct,
    IN ULONG dwInputLength,
    IN PUCHAR pbInput
    )
{
    rc4((RC4_KEYSTRUCT*)pbKeyStruct, dwInputLength, pbInput);
}
   
void
XcHMAC(
    IN PBYTE pbKeyMaterial,
    IN ULONG cbKeyMaterial,
    IN PBYTE pbData,
    IN ULONG cbData,
    IN PBYTE pbData2,
    IN ULONG cbData2,
    OUT PBYTE HmacData // length must be A_SHA_DIGEST_LEN
    )
{
#define HMAC_K_PADSIZE              64
#define HMAC_KI_XOR_FACTOR            ((DWORD)0x36363636)
#define HMAC_KO_XOR_FACTOR            ((DWORD)0x5C5C5C5C)
    BYTE Kipad[HMAC_K_PADSIZE];
    BYTE Kopad[HMAC_K_PADSIZE];
    BYTE HMACTmp[HMAC_K_PADSIZE+A_SHA_DIGEST_LEN];
    ULONG dwBlock;
    A_SHA_CTX shaHash;

    // truncate
    if (cbKeyMaterial > HMAC_K_PADSIZE)
        cbKeyMaterial = HMAC_K_PADSIZE;

    RtlZeroMemory(Kipad, HMAC_K_PADSIZE);
    RtlCopyMemory(Kipad, pbKeyMaterial, cbKeyMaterial);

    RtlZeroMemory(Kopad, HMAC_K_PADSIZE);
    RtlCopyMemory(Kopad, pbKeyMaterial, cbKeyMaterial);

    //
    // Kipad, Kopad are padded sMacKey. Now XOR across...
    //
    for(dwBlock=0; dwBlock<HMAC_K_PADSIZE/sizeof(DWORD); dwBlock++)
    {
        ((DWORD*)Kipad)[dwBlock] ^= HMAC_KI_XOR_FACTOR;
        ((DWORD*)Kopad)[dwBlock] ^= HMAC_KO_XOR_FACTOR;
    }

    //
    // prepend Kipad to data, Hash to get H1
    //

    A_SHAInit(&shaHash);
    A_SHAUpdate(&shaHash, Kipad, HMAC_K_PADSIZE);
    if (cbData != 0)
    {
        A_SHAUpdate(&shaHash, pbData, cbData);
    }
    if (cbData2 != 0)
    {
        A_SHAUpdate(&shaHash, pbData2, cbData2);
    }

    // Finish off the hash
    A_SHAFinal(&shaHash,HMACTmp+HMAC_K_PADSIZE);

    // prepend Kopad to H1, hash to get HMAC
    RtlCopyMemory(HMACTmp, Kopad, HMAC_K_PADSIZE);

    // final hash: output value into passed-in buffer
    A_SHAInit(&shaHash);
    A_SHAUpdate(&shaHash,HMACTmp, sizeof(HMACTmp));
    A_SHAFinal(&shaHash,HmacData);
}

// compute A = B ^ C mod D, N = len of params in DWORDs
ULONG
XcModExp(
    IN LPDWORD pA,
    IN LPDWORD pB,
    IN LPDWORD pC,
    IN LPDWORD pD,
    IN ULONG dwN
    )
{
    //
    // compute A = B ^ C mod D
    //
    return BenalohModExp(pA, pB, pC, pD, dwN);
}
   
void
XcDESKeyParity(
    IN PUCHAR pbKey,
    IN ULONG dwKeyLength
    )
{
    desparityonkey(pbKey, dwKeyLength);
}
   
void
XcKeyTable(
    IN ULONG dwCipher,
    OUT PUCHAR pbKeyTable,
    IN PUCHAR pbKey
    )
{
    if (dwCipher == XC_SERVICE_DES_CIPHER)
    {
        deskey((DESTable*)pbKeyTable, pbKey);
    }
    else
    {
        // Assume XC_SERVICE_DES3_CIPHER:
        tripledes3key((PDES3TABLE)pbKeyTable, pbKey);
    }
}
   
void
XcBlockCrypt(
    IN ULONG dwCipher,
    IN PUCHAR pbOutput,
    IN PUCHAR pbInput,
    IN PUCHAR pbKeyTable,
    IN ULONG dwOp
    )
{
    void (RSA32API *pCipher)(BYTE *, BYTE *, void *, int) = (dwCipher == XC_SERVICE_DES_CIPHER) ? des : tripledes;
    (*pCipher)( pbOutput, pbInput, pbKeyTable, dwOp );
}
   
void
XcBlockCryptCBC(
    IN ULONG dwCipher,
    IN ULONG dwInputLength,
    IN PUCHAR pbOutput,
    IN PUCHAR pbInput,
    IN PUCHAR pbKeyTable,
    IN ULONG dwOp,
    IN PUCHAR pbFeedback
    )
{
    BYTE *pbInputEnd = pbInput + dwInputLength;
    void (RSA32API *pCipher)(BYTE *, BYTE *, void *, int) = (dwCipher == XC_SERVICE_DES_CIPHER) ? des : tripledes;
    while (pbInput < pbInputEnd)
    {
        CBC(pCipher, XC_SERVICE_DES_BLOCKLEN, pbOutput, pbInput, pbKeyTable, dwOp, pbFeedback);
        pbInput += XC_SERVICE_DES_BLOCKLEN;
        pbOutput += XC_SERVICE_DES_BLOCKLEN;
    }
}

DWORD
XcCalcKeyLen(
    IN  PBYTE  pbPublicKey
    )
{
    LPBSAFE_PUB_KEY pPubKey = (LPBSAFE_PUB_KEY)pbPublicKey;

    return pPubKey->keylen;
}

//
// Reverse ASN.1 Encodings of possible hash identifiers.  
//
static char * shaEncodings[] = {
            //      1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18
            "\x0f\x14\x04\x00\x05\x1a\x02\x03\x0e\x2b\x05\x06\x09\x30\x21\x30",
            "\x0d\x14\x04\x1a\x02\x03\x0e\x2b\x05\x06\x07\x30\x1f\x30",
            "\x00" };

BOOL XcVerifyPKCS1SigningFmt(
    IN  BSAFE_PUB_KEY* pKey,
    IN  BYTE* pbDigest,
    IN  BYTE* pbPKCS1Format
    )
{
    BYTE**    rgEncOptions;
    BYTE      rgbTmpHash[A_SHA_DIGEST_LEN + 16];
    DWORD     i;
    DWORD     cb;
    BYTE*     pbStart;
    DWORD     cbTmp;

    rgEncOptions = (BYTE **)shaEncodings;

    // 
    // reverse the hash to match the signature.
    //
    for (i = 0; i < A_SHA_DIGEST_LEN; i++) {
        rgbTmpHash[i] = pbDigest[A_SHA_DIGEST_LEN - (i + 1)];
    }

    // 
    // see if it matches.
    //
    if (memcmp(rgbTmpHash, pbPKCS1Format, A_SHA_DIGEST_LEN)) {
        return FALSE;
    }

    cb = A_SHA_DIGEST_LEN;

    //
    // check for any signature type identifiers
    //
    for (i = 0; 0 != *rgEncOptions[i]; i += 1) {
        pbStart = (LPBYTE)rgEncOptions[i];
        cbTmp = *pbStart++;
        if (0 == memcmp(&pbPKCS1Format[cb], pbStart, cbTmp)) {
            // adjust the end of the hash data. 
            cb += cbTmp;   
            break;
        }
    }

    // 
    // check to make sure the rest of the PKCS #1 padding is correct
    //
    if ((0x00 != pbPKCS1Format[cb]) || (0x00 != pbPKCS1Format[pKey->datalen]) ||
         (0x1 != pbPKCS1Format[pKey->datalen - 1])) {
        return FALSE;
    }

    for (i = cb + 1; i < (DWORD)pKey->datalen - 1; i++) {
        if (0xff != pbPKCS1Format[i]) {
            return FALSE;
        }
    }

    return TRUE;
}

BOOLEAN
XcVerifyDigest(
    IN   PBYTE   pbSig,
    IN   PBYTE   pbPublicKey,
    IN   PBYTE   pbWorkspace,
    IN   PBYTE   pbCompareDigest
    )
{
    LPBSAFE_PUB_KEY pPubKey = (LPBSAFE_PUB_KEY)pbPublicKey;
    PBYTE           pbOutput;
    PBYTE           pbInput;
    DWORD           dwSigLen;

    dwSigLen = (pPubKey->bitlen + 7) / 8;

    pbOutput = pbWorkspace;
    pbInput = (PBYTE)((ULONG_PTR)pbWorkspace + pPubKey->keylen);

    memset(pbInput, 0, pPubKey->keylen);
    memcpy(pbInput, pbSig, dwSigLen);

    if (!BSafeEncPublic(pPubKey, pbInput, pbOutput)) {
        return FALSE;
    }


    if (!XcVerifyPKCS1SigningFmt(pPubKey, pbCompareDigest, pbOutput)) {
        return FALSE;
    }

    return TRUE;
}

ULONG
XcPKGetKeyLen(
    IN PUCHAR pbPubKey
    )
{
    return XcCalcKeyLen(pbPubKey);
}

BOOLEAN
XcVerifyPKCS1Signature(
    IN PUCHAR pbSig,
    IN PUCHAR pbPubKey,
    IN PUCHAR pbDigest
    )
{
    BYTE* pbWorkspace = (BYTE *)_alloca( 2 * XcCalcKeyLen(pbPubKey) );
    if ( pbWorkspace == NULL )
    {
        return FALSE;
    }
    return XcVerifyDigest(pbSig, pbPubKey, pbWorkspace, pbDigest);
}
   
VOID WINAPI XShaHmacInitialize(
            IN PBYTE                pbKey,
            IN DWORD                cbKey,
            IN OUT XSHAHMAC_CONTEXT Shactx
            )
{
    HRESULT        hr = S_OK;
    BYTE        rgbKipad[HMAC_K_PADSIZE];
    ULONG        dwBlock;

    // Shorten length if longer than our K padding
    if (cbKey > HMAC_K_PADSIZE)
        cbKey = HMAC_K_PADSIZE;

    // Build our Kipad
    memset(rgbKipad, 0, HMAC_K_PADSIZE);
    memcpy(rgbKipad, pbKey, cbKey);
    for (dwBlock = 0; 
         dwBlock < (HMAC_K_PADSIZE/sizeof(DWORD)); 
         dwBlock++)
    {
        ((DWORD*)rgbKipad)[dwBlock] ^= HMAC_KI_XOR_FACTOR;
    }

    // Initialize our SHA1 Hmac context
    XcSHAInit(Shactx);

    // Run our Kipad through this ...
    XcSHAUpdate(Shactx, rgbKipad, HMAC_K_PADSIZE);
}

//
// Function to update the Hmac
//
VOID WINAPI XShaHmacUpdate(
            IN XSHAHMAC_CONTEXT Shactx,
            IN PBYTE            pbData,
            IN DWORD            cbData
            )
{
    XcSHAUpdate(Shactx, pbData, cbData);
}
            
//
// Function to calculate the final Hmac
//
VOID WINAPI XShaHmacComputeFinal(
            IN XSHAHMAC_CONTEXT Shactx,
            IN PBYTE            pbKey,
            IN DWORD            cbKey,
            OUT PBYTE           pbHmac
            )
{
    HRESULT        hr = S_OK;
    BYTE        rgbKopad[HMAC_K_PADSIZE + A_SHA_DIGEST_LEN];
    ULONG        dwBlock;

    // Shorten length if longer than our K padding
    if (cbKey > HMAC_K_PADSIZE)
        cbKey = HMAC_K_PADSIZE;

    // Build our Kipad
    memset(rgbKopad, 0, HMAC_K_PADSIZE);
    memcpy(rgbKopad, pbKey, cbKey);
    for (dwBlock = 0; 
         dwBlock < (HMAC_K_PADSIZE/sizeof(DWORD)); 
         dwBlock++)
    {
        ((DWORD*)rgbKopad)[dwBlock] ^= HMAC_KO_XOR_FACTOR;
    }

    // Finish off the hash, and place the result right
    // after the Kopad data
    XcSHAFinal(Shactx, rgbKopad + HMAC_K_PADSIZE);

    // Do another hash, now with the Kopad data
    XcSHAInit(Shactx);
    XcSHAUpdate(Shactx, rgbKopad, sizeof(rgbKopad));
    XcSHAFinal(Shactx, pbHmac);
}            

// ---------------------------------------------------------------------------------------
// VMem
//
// VMem allocates memory using the operating system's low-level virtual allocator. It
// arranges for an allocation to start at the very beginning of a page, with a non-
// accessable page just before it, or for it to end at the very end of a page, with a
// non-accessible page just after it.  The idea is to catch memory overwrites quickly.
//
// The layout of an allocation is as follows:
//
//  +--- page VMEMINFO structure
//  |
//  |                 +--- pv if using front-side-strict memory allocations
//  |                 |
//  |                 |                   +--- filled with pattern to detect back-side overwrite
//  v                 v                   v
// +--------+--------+--------+--------+--------+--------+
// |VMEMINFO|   NO   |[User's memory area]XXXXXX|   NO   |
// |        | ACCESS |XXXXXX[User's memory area]| ACCESS |
// +--------+--------+--------+--------+--------+--------+
//                    ^     ^
//                    |     +--- pv if using back-side-strict memory allocations
//                    |
//                    +--- filled with pattern to detect front-side overwrite
//
// ---------------------------------------------------------------------------------------

#ifdef XNET_FEATURE_VMEM

#define PAGE_SIZE       4096

DWORD
VMemQueryProtect(void * pv, DWORD cb)
{
    MEMORY_BASIC_INFORMATION mbi = { 0 };
    VirtualQuery(pv, &mbi, sizeof(mbi));
    return (mbi.Protect ? mbi.Protect : mbi.AllocationProtect);
}

VMEMINFO *
VMemIsValid(void * pv)
{
    VMEMINFO * pvmi;
    BYTE * pb;
    UINT cb;

    if (pv == NULL)
    {
        return NULL;
    }

    pvmi = (VMEMINFO *)(((DWORD_PTR)pv & ~(PAGE_SIZE - 1)) - PAGE_SIZE * 2);

    if (VMemQueryProtect(pvmi, PAGE_SIZE) != PAGE_READONLY)
    {
        Rip("VMemIsValid - VMEMINFO page is not marked READONLY");
        return NULL;
    }

    if (pv != pvmi->pv)
    {
        Rip("VMemIsValid - VMEMINFO doesn't point back to pv");
        return NULL;
    }

    if (VMemQueryProtect((BYTE *)pvmi + PAGE_SIZE, PAGE_SIZE) != PAGE_NOACCESS)
    {
        Rip("VMemIsValid - can't detect first no-access page");
        return NULL;
    }

    if (VMemQueryProtect((BYTE *)pvmi + PAGE_SIZE * 2, pvmi->cbFill1 + pvmi->cb + pvmi->cbFill2) != PAGE_READWRITE)
    {
        Rip("VMemIsValid - user memory block is not all writable");
        return NULL;
    }

    if (pvmi->cbFill1)
    {
        pb = (BYTE *)pvmi + PAGE_SIZE * 2;
        cb = pvmi->cbFill1;

        for (; cb > 0; --cb, ++pb)
        {
            if (*pb != 0x1A)
            {
                Rip("VMemIsValid - detected user memory pre-data overwrite");
                return NULL;
            }
        }
    }

    if (pvmi->cbFill2)
    {
        pb = (BYTE *)pvmi + PAGE_SIZE * 2 + pvmi->cbFill1 + pvmi->cb;
        cb = pvmi->cbFill2;

        for (; cb > 0; --cb, ++pb)
        {
            if (*pb != 0x3A)
            {
                Rip("VMemIsValid - detected user memory post-data overwrite");
                return NULL;
            }
        }
    }

    if (VMemQueryProtect((BYTE *)pvmi + PAGE_SIZE * 2 + pvmi->cbFill1 + pvmi->cb + pvmi->cbFill2, PAGE_SIZE) != PAGE_NOACCESS)
    {
        Rip("VMemIsValid - can't detect second no-access page");
        return NULL;
    }

    return(pvmi);
}

void *
VMemAlloc(size_t cb)
{
    DWORD dwFlags = g_dwVMem;
    void * pv1, * pv2, * pv3;
    size_t cbUser, cbPage;
    DWORD dwOldProtect;
    VMEMINFO * pvmi;

    if (cb == 0)
    {
        cb = 1;
    }

    if (    (dwFlags & VMEM_BACKSIDESTRICT)
        &&  (dwFlags & VMEM_BACKSIDEALIGN8))
        cbUser = (cb + 7) & ~7;
    else
        cbUser = cb;

    cbPage = (cbUser + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);

	pv1 = VirtualAlloc(0, cbPage + PAGE_SIZE * 3, MEM_RESERVE, PAGE_NOACCESS);

    if (pv1 == NULL)
    {
        return(NULL);
    }

	pv2 = VirtualAlloc(pv1, PAGE_SIZE, MEM_COMMIT, PAGE_READWRITE);

    if (pv2 == NULL)
    {
        return(NULL);
    }

    pvmi          = (VMEMINFO *)pv2;
    pvmi->cb      = cb;
    pvmi->dwFlags = dwFlags;

    pv3 = VirtualAlloc((BYTE *)pv1 + PAGE_SIZE * 2, cbPage, MEM_COMMIT, PAGE_READWRITE);

    if (pv3 == NULL)
    {
        return(NULL);
    }

    if (dwFlags & VMEM_BACKSIDESTRICT)
    {
        pvmi->cbFill1 = cbPage - cbUser;
        pvmi->cbFill2 = cbUser - cb;
    }
    else
    {
        pvmi->cbFill1 = 0;
        pvmi->cbFill2 = cbPage - cbUser;
    }

    Assert(pvmi->cbFill1 + cb + pvmi->cbFill2 == cbPage);

    if (pvmi->cbFill1)
    {
        memset((BYTE *)pv3, 0x1A, pvmi->cbFill1);
    }

    memset((BYTE *)pv3 + pvmi->cbFill1, 0x2A, cb);

    if (pvmi->cbFill2)
    {
        memset((BYTE *)pv3 + pvmi->cbFill1 + cb, 0x3A, pvmi->cbFill2);
    }

    pvmi->pv = (BYTE *)pv3 + pvmi->cbFill1;

    VirtualProtect(pv1, PAGE_SIZE, PAGE_READONLY, &dwOldProtect);

    Assert(VMemIsValid(pvmi->pv));

    return(pvmi->pv);
}

void
VMemFree(void * pv)
{
    VMEMINFO * pvmi = VMemIsValid(pv);

    if (pvmi)
    {
        if (!VirtualFree(pvmi, 0, MEM_RELEASE))
        {
            Rip("VmemFree - VirtualFree failed");
        }
    }
}

#endif // XNET_FEATURE_VMEM

// ---------------------------------------------------------------------------------------
// Assert support
// ---------------------------------------------------------------------------------------

#ifdef XNET_FEATURE_ASSERT

struct MBOT { char * psz; int id; };

DWORD WINAPI DbgAssertPopOnThreadFn(MBOT * pmbot)
{
    pmbot->id = MessageBoxExA(NULL, pmbot->psz, "Assert",
                              MB_SYSTEMMODAL|MB_SETFOREGROUND|MB_OKCANCEL|MB_ICONWARNING|MB_DEFBUTTON2,
                              0);
    return(0);
}

int DbgAssertPop(const char * pszExpr, const char * pszFile, int iLine, const char * pszMsg)
{
    char ach[256];

    if (pszMsg)
        _snprintf(ach, sizeof(ach), "%s\n%s\n%s (%d)", pszExpr, pszMsg, pszFile, iLine);
    else
        _snprintf(ach, sizeof(ach), "%s\n%s (%d)", pszExpr, pszFile, iLine);

    MBOT  mbot = { ach, 0 };
    DWORD dwThread;
    HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)DbgAssertPopOnThreadFn,
                                  &mbot, 0, &dwThread);
    if (!hThread)
        DbgAssertPopOnThreadFn(&mbot);
    else
    {
        WaitForSingleObject(hThread, INFINITE);
        CloseHandle(hThread);
    }

    return(mbot.id == IDCANCEL);
}

#endif

// ---------------------------------------------------------------------------------------
// Trace support
// ---------------------------------------------------------------------------------------

#ifdef XNET_FEATURE_TRACE

CTagInit::CTagInit(int * pi, const char * pszTag)
{
    int i = GetPrivateProfileIntA("tags", pszTag, -1, "\\xnettags.ini");

    if (i != -1)
    {
        *pi = i;
    }
}

#endif

// ---------------------------------------------------------------------------------------
// XBox File System Abstraction Layer
// ---------------------------------------------------------------------------------------

BOOL HalCreateDirectory(const char * pszDir, BOOL fHidden, BOOL * pfAlreadyExists)
{
    WIN32_FIND_DATA fd;
    HANDLE          hFind = FindFirstFile(pszDir, &fd);

    *pfAlreadyExists = FALSE;

    if (hFind == INVALID_HANDLE_VALUE)
    {
        if (GetLastError() != ERROR_FILE_NOT_FOUND)
        {
            TraceSz2(Warning, "HalCreateDirectory: FindFirstFile '%s' failed (%08lX)", pszDir, GetLastError());
            return(FALSE);
        }

        if (!CreateDirectory(pszDir, NULL))
        {
            TraceSz2(Warning, "HalCreateDirectory: CreateDirectory '%s' failed (%08lX)", pszDir, GetLastError());
            return(FALSE);
        }

        if (fHidden && !SetFileAttributes(pszDir, FILE_ATTRIBUTE_HIDDEN))
        {
            TraceSz2(Warning, "HalCreateDirectory: SetFileAttributes '%s' failed (%08lX)", pszDir, GetLastError());
            return(FALSE);
        }
    }
    else
    {
        if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
        {
            TraceSz1(Warning, "HalCreateDirectory: Cannot create directory '%s' because file exists there", pszDir);
            return(FALSE);
        }

        FindClose(hFind);

        *pfAlreadyExists = TRUE;
    }

    return(TRUE);
}

BOOL HalGetPath(const char * pszXbox, const char * pszPathIn, char * pszPathOut)
{
    char achXbox[MAX_PATH];
    char achRoot[MAX_PATH];
    char achTemp[MAX_PATH];
    char * pch;
    BOOL fAlreadyExists;

    strncpy(achXbox, pszXbox, sizeof(achXbox));
    for (pch = achXbox; *pch && *pch != '@'; ++pch) ;
    *pch = 0;

    // Make sure directory c:\$xbox exists

    strcpy(achRoot, "c:\\$xbox");
    HalCreateDirectory(achRoot, TRUE, &fAlreadyExists);

    // Make sure directory c:\$xbox\<pszXbox> exists

    strcat(achRoot, "\\");
    strcat(achRoot, achXbox);
    HalCreateDirectory(achRoot, FALSE, &fAlreadyExists);

    if (!fAlreadyExists)
    {
        char achDir[MAX_PATH];

        strcpy(achDir, achRoot);
        strcat(achDir, "\\partition1");
        HalCreateDirectory(achDir, FALSE, &fAlreadyExists);

        strcpy(achDir, achRoot);
        strcat(achDir, "\\partition1\\TDATA");
        HalCreateDirectory(achDir, FALSE, &fAlreadyExists);

        strcpy(achDir, achRoot);
        strcat(achDir, "\\partition1\\CACHE");
        HalCreateDirectory(achDir, FALSE, &fAlreadyExists);

        strcpy(achDir, achRoot);
        strcat(achDir, "\\partition1\\devkit");
        HalCreateDirectory(achDir, FALSE, &fAlreadyExists);

        strcpy(achDir, achRoot);
        strcat(achDir, "\\partition2");
        HalCreateDirectory(achDir, FALSE, &fAlreadyExists);
    }

    // Handle special "eeprom" path

    if (lstrcmpiA(pszPathIn, "eeprom") == 0)
    {
        sprintf(pszPathOut, "%s\\eeprom.bin", achRoot);
        return(TRUE);
    }

    // Handle special "volume" path

    if (lstrcmpiA(pszPathIn, "\\Device\\Harddisk0\\partition0") == 0)
    {
        sprintf(pszPathOut, "%s\\config.bin", achRoot);

        // Make sure this file exists and has room for all the config sectors

        HANDLE hFile = CreateFile(pszPathOut, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

        if (hFile == INVALID_HANDLE_VALUE)
        {
            TraceSz2(Warning, "HalGetPath: CreateFile '%s' failed (%08lX)", pszPathOut, GetLastError());
            return(FALSE);
        }

        DWORD dwSize   = GetFileSize(hFile, NULL);
        DWORD dwExpect = (XBOX_CONFIG_SECTOR_INDEX + XBOX_NUM_CONFIG_SECTORS) * XBOX_HD_SECTOR_SIZE;

        if (dwSize != dwExpect)
        {
            if (dwSize != 0)
            {
                TraceSz3(Warning, "HalGetPath: '%s' has incorrect size (%d bytes); should be %d bytes.  Repairing.",
                         pszPathOut, dwSize, dwExpect);
            }

            BYTE ab[(XBOX_CONFIG_SECTOR_INDEX + XBOX_NUM_CONFIG_SECTORS) * XBOX_HD_SECTOR_SIZE];
            memset(ab, 0, sizeof(ab));

            if (SetFilePointer(hFile, 0, NULL, FILE_BEGIN) == 0xFFFFFFFF)
            {
                TraceSz2(Warning, "HalGetPath: SetFilePointer '%s' failed (%08lX)", pszPathOut, GetLastError());
                CloseHandle(hFile);
                return(FALSE);
            }

            if (!WriteFile(hFile, ab, sizeof(ab), &dwExpect, NULL))
            {
                TraceSz2(Warning, "HalGetPath: WriteFile '%s' failed (%08lX)", pszPathOut, GetLastError());
                CloseHandle(hFile);
                return(FALSE);
            }

            Assert(dwExpect == sizeof(ab));
        }

        CloseHandle(hFile);
        return(TRUE);
    }

    // Handle Harddisk0 paths

    if (_strnicmp(pszPathIn, "\\Device\\Harddisk0\\", 18) == 0)
    {
        sprintf(pszPathOut, "%s\\%s", achRoot, pszPathIn + 18);
        return(TRUE);
    }

    TraceSz1(Warning, "HalGetPath: Don't understand path '%s'", pszPathIn);
    return(FALSE);
}

HANDLE HalCreateFile(char * pszXbox, LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile)
{
    NTSTATUS    status = STATUS_UNSUCCESSFUL;
    char        achPath[MAX_PATH];

    if (!HalGetPath(pszXbox, lpFileName, achPath))
    {
        TraceSz2(Warning, "HalCreateFile: HalGetPath('%s','%s') failed", pszXbox, lpFileName);
        return(INVALID_HANDLE_VALUE);
    }
    
    return(CreateFile(achPath, dwDesiredAccess, dwShareMode, lpSecurityAttributes,
                      dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile));
}

NTSTATUS HalNtOpenFile(char * pszXbox, PHANDLE FileHandle, ACCESS_MASK DesiredAccess,
                       POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock,
                       ULONG ShareAccess, ULONG OpenOptions)
{
    NTSTATUS    status = STATUS_UNSUCCESSFUL;
    char        achPath[MAX_PATH];

    if (!HalGetPath(pszXbox, (char *)ObjectAttributes->ObjectName->Buffer, achPath))
    {
        TraceSz2(Warning, "HalNtOpenFile: HalGetPath('%s','%s') failed",
                 pszXbox, (char *)ObjectAttributes->ObjectName->Buffer);
        *FileHandle = INVALID_HANDLE_VALUE;
    }
    else
    {
        *FileHandle = CreateFile(achPath, DesiredAccess & (GENERIC_READ|GENERIC_WRITE),
                                 ShareAccess & (FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE),
                                 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    }

    if (*FileHandle != INVALID_HANDLE_VALUE)
    {
        status = STATUS_SUCCESS;
    }

    return(status);
}

NTSTATUS HalNtCreateFile(char * pszXbox, PHANDLE FileHandle, ACCESS_MASK DesiredAccess,
                         POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock,
                         PLARGE_INTEGER AllocationSize, ULONG FileAttributes,
                         ULONG ShareAccess, ULONG CreateDisposition, ULONG CreateOptions)
{
    NTSTATUS    status = STATUS_UNSUCCESSFUL;
    char        achPath[MAX_PATH];

    if (!HalGetPath(pszXbox, (char *)ObjectAttributes->ObjectName->Buffer, achPath))
    {
        TraceSz2(Warning, "HalNtCreateFile: HalGetPath('%s','%s') failed",
                 pszXbox, (char *)ObjectAttributes->ObjectName->Buffer);
        *FileHandle = INVALID_HANDLE_VALUE;
    }
    else
    {
        DWORD dwDisposition = CREATE_ALWAYS;

        switch (CreateDisposition)
        {
            case FILE_SUPERSEDE:    dwDisposition = TRUNCATE_EXISTING;  break;
            case FILE_OPEN:         dwDisposition = OPEN_ALWAYS;        break;
            case FILE_CREATE:       dwDisposition = CREATE_NEW;         break;
            case FILE_OPEN_IF:      dwDisposition = CREATE_ALWAYS;      break;
            case FILE_OVERWRITE:    dwDisposition = CREATE_ALWAYS;      break;
            case FILE_OVERWRITE_IF: dwDisposition = TRUNCATE_EXISTING;  break;
        }

        *FileHandle = CreateFile(achPath, DesiredAccess & (GENERIC_READ|GENERIC_WRITE),
                                 ShareAccess & (FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE),
                                 NULL, dwDisposition, FILE_ATTRIBUTE_NORMAL, NULL);
    }

    if (*FileHandle != INVALID_HANDLE_VALUE)
    {
        status = STATUS_SUCCESS;
    }

    return(status);
}

NTSTATUS HalNtReadFile(char * pszXbox, HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine,
                       PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer, ULONG Length,
                       PLARGE_INTEGER ByteOffset)
{
    if (SetFilePointer(FileHandle, ByteOffset->LowPart, &ByteOffset->HighPart, FILE_BEGIN) == 0xFFFFFFFF)
    {
        TraceSz1(Warning, "HalNtReadFile: SetFilePointer failed (%08lX)", GetLastError());
        return(STATUS_UNSUCCESSFUL);
    }

    DWORD dwRead;

    if (!ReadFile(FileHandle, Buffer, Length, &dwRead, NULL))
    {
        TraceSz1(Warning, "HalNtReadFile: ReadFile failed (%08lX)", GetLastError());
        return(STATUS_UNSUCCESSFUL);
    }

    if (dwRead != Length)
    {
        TraceSz2(Warning, "HalNtReadFile: ReadFile returned only %d bytes (%d requested)", dwRead, Length);
        return(STATUS_UNSUCCESSFUL);
    }

    return(STATUS_SUCCESS);
}

NTSTATUS HalNtWriteFile(char * pszXbox, HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine,
                        PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer,
                        ULONG Length, PLARGE_INTEGER ByteOffset)
{
    if (SetFilePointer(FileHandle, ByteOffset->LowPart, &ByteOffset->HighPart, FILE_BEGIN) == 0xFFFFFFFF)
    {
        TraceSz1(Warning, "HalNtWriteFile: SetFilePointer failed (%08lX)", GetLastError());
        return(STATUS_UNSUCCESSFUL);
    }

    DWORD dwWrite;

    if (!WriteFile(FileHandle, Buffer, Length, &dwWrite, NULL))
    {
        TraceSz1(Warning, "HalNtWriteFile: WriteFile failed (%08lX)", GetLastError());
        return(STATUS_UNSUCCESSFUL);
    }

    if (dwWrite != Length)
    {
        TraceSz2(Warning, "HalNtWriteFile: WriteFile wrote only %d bytes (%d requested)", dwWrite, Length);
        return(STATUS_UNSUCCESSFUL);
    }

    return(STATUS_SUCCESS);
}

NTSTATUS HalNtClose(char * pszXbox, HANDLE Handle)
{
    if (Handle != INVALID_HANDLE_VALUE)
    {
        CloseHandle(Handle);
    }

    return(STATUS_SUCCESS);
}

// ---------------------------------------------------------------------------------------
// EEPROM
// ---------------------------------------------------------------------------------------

BOOL HalSaveEEPROM(char * pszXbox, EEPROM_LAYOUT * peeprom)
{
    HANDLE  hFile   = INVALID_HANDLE_VALUE;
    BOOL    fResult = FALSE;
    DWORD   dwSize;
    char    achPath[MAX_PATH];

    if (!HalGetPath(pszXbox, "eeprom", achPath))
    {
        TraceSz1(Warning, "HalSaveEEPROM: HalGetPath('%s','eeprom') failed", pszXbox);
        goto ret;
    }

    hFile = CreateFile(achPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,  NULL);

    if (hFile == INVALID_HANDLE_VALUE)
    {
        TraceSz2(Warning, "HalSaveEEPROM: CreateFile '%s' failed (%08lX)", achPath, GetLastError());
        goto ret;
    }

    if (!WriteFile(hFile, peeprom, sizeof(EEPROM_LAYOUT), &dwSize, NULL))
    {
        TraceSz2(Warning, "HalSaveEEPROM: WriteFile '%s' failed (%08lX)", achPath, GetLastError());
        goto ret;
    }

    fResult = TRUE;

ret:

    if (hFile != INVALID_HANDLE_VALUE)
    {
        CloseHandle(hFile);
    }

    return(fResult);
}

BOOL HalLoadEEPROM(char * pszXbox, EEPROM_LAYOUT * peeprom)
{
    HANDLE  hFile   = INVALID_HANDLE_VALUE;
    BOOL    fResult = FALSE;
    DWORD   dwSize;
    char    achPath[MAX_PATH];

    if (!HalGetPath(pszXbox, "eeprom", achPath))
    {
        TraceSz1(Warning, "HalLoadEEPROM: HalGetPath('%s','eeprom') failed", pszXbox);
        goto ret;
    }

    hFile = CreateFile(achPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL,  NULL);

    if (hFile == INVALID_HANDLE_VALUE)
    {
        TraceSz2(Warning, "HalLoadEEPROM: CreateFile '%s' failed (%08lX)", achPath, GetLastError());
        goto ret;
    }

    if (SetFilePointer(hFile, 0, NULL, FILE_BEGIN) == 0xFFFFFFFF)
    {
        TraceSz2(Warning, "HalLoadEEPROM: SetFilePointer '%s' failed (%08lX)", achPath, GetLastError());
        goto ret;
    }

    dwSize = GetFileSize(hFile, NULL);

    if (dwSize == sizeof(EEPROM_LAYOUT))
    {
        if (!ReadFile(hFile, peeprom, sizeof(EEPROM_LAYOUT), &dwSize, NULL))
        {
            TraceSz2(Warning, "HalLoadEEPROM: ReadFile '%s' failed (%08lX)", achPath, GetLastError());
            goto ret;
        }
    }
    else
    {
        CloseHandle(hFile);
        hFile = INVALID_HANDLE_VALUE;

        if (dwSize != 0)
        {
            TraceSz3(Warning, "HalLoadEEPROM: '%s' is incorrect size (%d bytes); should be %d bytes.  Repairing.",
                     achPath, dwSize, sizeof(EEPROM_LAYOUT));
        }

        memset(peeprom, 0, sizeof(EEPROM_LAYOUT));

        XBOX_ENCRYPTED_SETTINGS *   pes = (XBOX_ENCRYPTED_SETTINGS *)&peeprom->EncryptedSection;
        XBOX_FACTORY_SETTINGS *     pfs = (XBOX_FACTORY_SETTINGS *)&peeprom->FactorySection;

        memset(pfs->SerialNumber, '9', sizeof(pfs->SerialNumber));

        Assert(sizeof(pfs->OnlineKey) == DEVKIT_ONLINE_RAND_KEY_LEN);
        memcpy(pfs->OnlineKey, DEVKIT_ONLINE_RAND_KEY, sizeof(pfs->OnlineKey));

        BYTE abHdKey[16] = { 0 };

        MorphKeyByHardDriveKeyEx(abHdKey, pfs->OnlineKey, sizeof(pfs->OnlineKey));

        HalSaveEEPROM(pszXbox, peeprom);
    }

    fResult = TRUE;

ret:

    if (hFile != INVALID_HANDLE_VALUE)
    {
        CloseHandle(hFile);
    }

    return(fResult);
}

NTSTATUS HalExQueryNonVolatileSetting(char * pszXbox, ULONG ValueIndex, ULONG * Type, VOID * Value, ULONG ValueLength, ULONG * ResultLength)
{
    EEPROM_LAYOUT   eeprom;
    NTSTATUS        status = STATUS_UNSUCCESSFUL;

    *Type = 0;
    *ResultLength = 0;

    if (HalLoadEEPROM(pszXbox, &eeprom))
    {
        XBOX_FACTORY_SETTINGS * pfs = (XBOX_FACTORY_SETTINGS *)&eeprom.FactorySection;

        switch (ValueIndex)
        {
            case XC_FACTORY_ETHERNET_ADDR:
            {
                if (ValueLength == sizeof(pfs->EthernetAddr))
                {
                    memcpy(Value, pfs->EthernetAddr, ValueLength);
                    *Type = REG_BINARY;
                    *ResultLength = ValueLength;
                    status = STATUS_SUCCESS;
                }
                break;
            }

            case XC_FACTORY_SERIAL_NUMBER:
            {
                if (ValueLength == sizeof(pfs->SerialNumber))
                {
                    memcpy(Value, pfs->SerialNumber, ValueLength);
                    *Type = REG_BINARY;
                    *ResultLength = ValueLength;
                    status = STATUS_SUCCESS;
                }
                break;
            }

            case XC_FACTORY_ONLINE_KEY:
            {
                if (ValueLength == sizeof(pfs->OnlineKey))
                {
                    memcpy(Value, pfs->OnlineKey, ValueLength);
                    *Type = REG_BINARY;
                    *ResultLength = ValueLength;
                    status = STATUS_SUCCESS;
                }
                break;
            }
        }

        AssertSz1(status == STATUS_SUCCESS, "HalExQueryNonVolatileSetting: Unrecognized ValueIndex (%d)", ValueIndex);
    }

    return(STATUS_SUCCESS);
}

NTSTATUS HalExSaveNonVolatileSetting(char * pszXbox, ULONG ValueIndex, ULONG Type, const VOID * Value, ULONG ValueLength)
{
    EEPROM_LAYOUT   eeprom;
    NTSTATUS        status = STATUS_UNSUCCESSFUL;

    if (HalLoadEEPROM(pszXbox, &eeprom))
    {
        XBOX_FACTORY_SETTINGS * pfs = (XBOX_FACTORY_SETTINGS *)&eeprom.FactorySection;

        switch (ValueIndex)
        {
            case XC_FACTORY_ETHERNET_ADDR:
            {
                if (Type == REG_BINARY && ValueLength == sizeof(pfs->EthernetAddr))
                {
                    memcpy(pfs->EthernetAddr, Value, ValueLength);
                    status = STATUS_SUCCESS;
                }
                break;
            }

            case XC_FACTORY_SERIAL_NUMBER:
            {
                if (Type == REG_BINARY && ValueLength == sizeof(pfs->SerialNumber))
                {
                    memcpy(pfs->SerialNumber, Value, ValueLength);
                    status = STATUS_SUCCESS;
                }
                break;
            }

            case XC_FACTORY_ONLINE_KEY:
            {
                if (Type == REG_BINARY && ValueLength == sizeof(pfs->OnlineKey))
                {
                    memcpy(pfs->OnlineKey, Value, ValueLength);
                    status = STATUS_SUCCESS;
                }
                break;
            }
        }

        AssertSz2(status == STATUS_SUCCESS, "HalExSaveNonVolatileSetting: Unrecognized ValueIndex (%d) Type (%d)", ValueIndex, Type);
    }

    if (status == STATUS_SUCCESS && !HalSaveEEPROM(pszXbox, &eeprom))
    {
        status = STATUS_UNSUCCESSFUL;
    }

    return(status);
}

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

#endif
