#include "basedll.h"
#include "xmeta.h"

// Closest prime number to 2^48:
// 2^48 - 59 =  281474976710597 =  0xFFFFFFFFFFC5

#define SAVE_DIR_HASH_M 0xFFFFFFFFFFC5

#define HASHED_SAVE_GAME_CHARACTERS 12


__inline CHAR HexDigitToChar(INT d)
{
    return (d <= 9) ? (d + '0') : (d - 0xA + 'A');
}

//
// Unicode save game names are hashed into 48 bit numbers (12 8-bit hex characters)
//

VOID
XapiSaveGameHashEncode(
    PCWSTR pszSource,
    PSTR pszDestination,
    int cchDestination)
{
    int nChar = 0;
    DWORDLONG qwHashValue = 0;

    ASSERT(pszSource);
    ASSERT(pszDestination);
    ASSERT(cchDestination > HASHED_SAVE_GAME_CHARACTERS);

    while (pszSource[nChar])
    {
        //
        // This is effectively what we are doing below:
        //
        // qwHashValue = (((0x10000) * qwHashValue) + pszSource[nChar]) % SAVE_DIR_HASH_M;
        //

        qwHashValue = ((qwHashValue << 16) + pszSource[nChar]) % SAVE_DIR_HASH_M;
        nChar++;
    }

    for (nChar = (HASHED_SAVE_GAME_CHARACTERS - 1); nChar >= 0; nChar--)
    {
        pszDestination[nChar] =
            HexDigitToChar(
                ((INT) (qwHashValue >> (4 * ((HASHED_SAVE_GAME_CHARACTERS - 1) - nChar)))) & 0xF);
    }

    pszDestination[HASHED_SAVE_GAME_CHARACTERS] = '\0';
}

#if 0

//
// Note: hMetaFile should have been opened without FILE_SHARE_READ or
// FILE_SHARE_WRITE set, because we are potentially rewriting the entire file
// in this function
//

BOOL
XapiDeleteValueInMetaFile(
    HANDLE hMetaFile,
    LPCWSTR pszTag)
{
    PBYTE pBuffer;
    BOOL fRet = FALSE;
    DWORD dwSize = GetFileSize(hMetaFile, NULL);

    ASSERT(pszTag);

    if (dwSize)
    {
        pBuffer = (PBYTE) LocalAlloc(LMEM_FIXED, dwSize + sizeof(WCHAR));
        if (pBuffer)
        {
            PBYTE pCurrent = pBuffer;
            DWORD dwBytesRead;

            //
            // Place a NULL at the end of the buffer that should never
            // be overwritten - this makes it safe to use the wcsstr()
            // function later without worrying about running of the end
            //

            *((PWSTR) (&pBuffer[dwSize])) = UNICODE_NULL;

            SetFilePointer(hMetaFile, 0, NULL, FILE_BEGIN);

            while (ReadFile(hMetaFile,
                            pCurrent,
                            pBuffer - pCurrent - sizeof(WCHAR),
                            &dwBytesRead,
                            NULL))
            {
                if (0 == dwBytesRead)
                {
                    //
                    // The whole file has been read in
                    //

                    fRet = TRUE;
                    break;
                }
                pCurrent += dwBytesRead;

                if (pBuffer - pCurrent < sizeof(WCHAR))
                {
                    //
                    // Hmm.. the file is bigger than it used to be
                    //

                    break;
                }
            }

            if (fRet)
            {
                //
                // Scan the buffer for the tag/value pairs
                //

                DWORD dwNewSize = dwSize;
                PWSTR pszMatchTag, pszCRLF;
                int nTagLength = wcslen(pszTag);

                pCurrent = pBuffer;

                while ((pszMatchTag = wcsstr((PWSTR) pCurrent, pszTag)) &&
                       (pszCRLF = wcsstr((PWSTR) pCurrent, g_cszCRLF)))
                {
                    if ((pszMatchTag == (PWSTR) pCurrent) &&
                        (g_chEqual == pszMatchTag[nTagLength]))
                    {
                        //
                        // Got one
                        //

                        DWORD dwCutSize = sizeof(WCHAR) * (pszCRLF + g_cchCRLF - pszMatchTag);

                        ASSERT(dwNewSize >= dwCutSize);

                        dwNewSize -= dwCutSize;

                        //
                        // If we're not at the end of the buffer, slide the remaining
                        // buffer back to remove the part we just cut out
                        //

                        if (dwNewSize > (DWORD) (pCurrent - pBuffer))
                        {
                            RtlMoveMemory(pCurrent,
                                          pCurrent + dwCutSize,
                                          dwNewSize - (pCurrent - pBuffer));
                        }
                    }
                    else
                    {
                        pCurrent = (PBYTE) ((PWSTR) (pszCRLF + g_cchCRLF));
                    }

                    if ((DWORD) (pCurrent - pBuffer) >= dwNewSize)
                    {
                        //
                        // We've hit the end of the valid buffer
                        //

                        break;
                    }
                }

                if (dwNewSize != dwSize)
                {
                    DWORD dwBytesWritten;

                    //
                    // We've shrunk the size, so we need to write the file out
                    //

                    SetFilePointer(hMetaFile, 0, NULL, FILE_BEGIN);
                    SetEndOfFile(hMetaFile);

                    fRet = FALSE;

                    pCurrent = pBuffer;

                    while (WriteFile(hMetaFile,
                                     pCurrent,
                                     pBuffer + dwNewSize - pCurrent,
                                     &dwBytesWritten,
                                     NULL))
                    {
                        pCurrent += dwBytesWritten;

                        if ((DWORD) (pCurrent - pBuffer) >= dwNewSize)
                        {
                            fRet = TRUE;
                            break;
                        }
                    }
                }
            }

            SetFilePointer(hMetaFile, 0, NULL, FILE_BEGIN);

            LocalFree(pBuffer);
        }
    }

    return fRet;
}

#endif // 0

BOOL
XapiValidateAndSkipUnicodeSignature(
    HANDLE hMetaFile)
{
    WCHAR wchSig;
    DWORD dwRead;
    
    ASSERT(INVALID_HANDLE_VALUE != hMetaFile);
    ASSERT(hMetaFile);

    SetFilePointer(hMetaFile, 0, NULL, FILE_BEGIN);

    return (ReadFile(hMetaFile, &wchSig, sizeof(wchSig), &dwRead, NULL) &&
            (dwRead == sizeof(wchSig)) ||
            (wchSig == g_chUnicodeSignature));
}

BOOL
XapiFindValueInMetaFile(
    HANDLE hMetaFile,
    LPCWSTR pszTag,
    LPWSTR pszValue,
    int cchValue)
{
    BOOL  fRet = FALSE;
    DWORD dwBytesRead;
    WCHAR szBuffer[MAX_METADATA_LINE];
    PWSTR pszNextRead = szBuffer;
    UINT  cchRead = ARRAYSIZE(szBuffer) - 1;
    BOOL  fSkipThroughNextCRLF = FALSE;
    BOOL  fReuseBuffer = FALSE;
    int   nTagLength = wcslen(pszTag);

    //
    // Null terminate the end of the read buffer - we should never overwrite this
    //

    szBuffer[cchRead] = UNICODE_NULL;

    while (fReuseBuffer ||
           (ReadFile(hMetaFile,
                     pszNextRead,
                     cchRead * sizeof(WCHAR),
                     &dwBytesRead,
                     NULL) &&
            (0 != dwBytesRead)))
    {
        DWORD dwBytesValid = dwBytesRead + ((pszNextRead - szBuffer) * sizeof(WCHAR));

        fReuseBuffer = FALSE;

        if (fSkipThroughNextCRLF)
        {
            PWSTR pszCRLF = wcsstr(szBuffer, g_cszCRLF);
            pszNextRead = szBuffer;
            cchRead = ARRAYSIZE(szBuffer) - 1;

            if (pszCRLF)
            {
                DWORD dwBytesSkip;

                pszCRLF += g_cchCRLF;
                dwBytesSkip = ((PBYTE) pszCRLF - (PBYTE) szBuffer);

                ASSERT(dwBytesValid >= dwBytesSkip);

                if (dwBytesSkip < dwBytesValid)
                {
                    //
                    // move the next line of data that we just read to
                    // the beginning of the buffer
                    //

                    DWORD dwBytesMove = dwBytesValid - dwBytesSkip;

                    RtlMoveMemory(szBuffer,
                                  ((PBYTE) szBuffer) + dwBytesSkip,
                                  dwBytesMove);

                    pszNextRead = (PWSTR) (((PBYTE) szBuffer) + dwBytesMove);
                    cchRead = ARRAYSIZE(szBuffer) - 1 - (dwBytesMove >> 1);
                }

                fSkipThroughNextCRLF = FALSE;
            }
        }
        else
        {
            PWSTR pszMatchTag = wcsstr(szBuffer, pszTag);
            if (pszMatchTag && (g_chEqual == pszMatchTag[nTagLength]))
            {
                PWSTR pszCRLF;

                pszMatchTag += (nTagLength + 1);
                pszCRLF = wcsstr(pszMatchTag, g_cszCRLF);

                ASSERT(cchValue > 0);

                if (pszCRLF)
                {
                    DWORD dwBytesUsed;
                    int cchCopy = min(cchValue - 1, (pszCRLF - pszMatchTag));

                    //
                    // Copy the value string to the output buffer
                    //

                    wcsncpy(pszValue, pszMatchTag, cchCopy);

                    //
                    // Null terminate the output buffer
                    //

                    pszValue[cchCopy] = UNICODE_NULL;

                    pszCRLF += g_cchCRLF;

                    dwBytesUsed = ((PBYTE) pszCRLF - (PBYTE) szBuffer);

                    ASSERT(dwBytesValid >= dwBytesUsed);

                    if (dwBytesUsed < dwBytesValid)
                    {
                        //
                        // Move the file pointer back if we didn't use all of
                        // the data that we read
                        //

                        SetFilePointer(hMetaFile,
                                       (dwBytesUsed - dwBytesValid),
                                       NULL,
                                       FILE_CURRENT);
                    }

                    fRet = TRUE;

                    break;
                }
                else
                {
                    fSkipThroughNextCRLF = TRUE;
                    pszNextRead = szBuffer;
                    cchRead = ARRAYSIZE(szBuffer) - 1;
                }
            }
            else
            {
                fSkipThroughNextCRLF = TRUE;
                fReuseBuffer = TRUE;
            }
        }
    }

    return fRet;
}

BOOL
XapiFillInSaveGameData(
    PXGAME_FIND_DATA pFindGameData,
    PCOSTR pszRootDir,
    int cchRootDir)
{
    int nNameLen;
    int iCurIdentity;
    BOOL fRet = FALSE;

    ASSERT(pFindGameData);

    if (0 == (pFindGameData->wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
    {
        XDBGWRN("XAPI", "XFindSaveGame: Unexpected file found on save game drive");
        return FALSE;
    }

    nNameLen = ocslen(pFindGameData->wfd.cFileName);

    ASSERT(cchRootDir + nNameLen < ARRAYSIZE(pFindGameData->szSaveGameDirectory));

    ocscpy(pFindGameData->szSaveGameDirectory, pszRootDir);
    ocscpy(&(pFindGameData->szSaveGameDirectory[cchRootDir]),
           pFindGameData->wfd.cFileName);

    //
    // Append a backslash if there is room
    //

    if (cchRootDir + nNameLen < ARRAYSIZE(pFindGameData->szSaveGameDirectory) - 1)
    {
        pFindGameData->szSaveGameDirectory[cchRootDir + nNameLen] = OTEXT('\\');
        pFindGameData->szSaveGameDirectory[cchRootDir + nNameLen + 1] = OBJECT_NULL;
    }

    //
    // Attempt to open the metadata file
    //

    if (cchRootDir + nNameLen < (int) ARRAYSIZE(pFindGameData->szSaveGameDirectory) - g_cchSaveMetaFileName)
    {
        HANDLE hMetaFile;

        //
        // Borrow the output buffer temporarily to append the metadata filename
        //

        ocscpy(&(pFindGameData->szSaveGameDirectory[cchRootDir + nNameLen]), g_cszSaveMetaFileName);

        hMetaFile = CreateFile(pFindGameData->szSaveGameDirectory,
                               GENERIC_READ,
                               FILE_SHARE_READ,
                               NULL,
                               OPEN_EXISTING,
                               FILE_ATTRIBUTE_NORMAL,
                               NULL);

        if (INVALID_HANDLE_VALUE != hMetaFile)
        {
            fRet = XapiValidateAndSkipUnicodeSignature(hMetaFile);

            if (fRet)
            {
                fRet = XapiFindValueInMetaFile(hMetaFile,
                                               g_cszNameTag,
                                               pFindGameData->szSaveGameName,
                                               ARRAYSIZE(pFindGameData->szSaveGameName));
            }

            CloseHandle(hMetaFile);
        }

        //
        // Restore the buffer (truncate to remove the metadata filename)
        //

        pFindGameData->szSaveGameDirectory[cchRootDir + nNameLen + 1] = OBJECT_NULL;
    }

    return fRet;
}

DWORD
XapiVerifyGameName(
    PCOSTR lpMetaFilePath,
    LPCWSTR lpSaveGameName)
{
    DWORD dwRet;

    HANDLE hMetaFile = CreateFile( lpMetaFilePath,
                                   SYNCHRONIZE | GENERIC_READ,
                                   0,
                                   NULL,
                                   OPEN_ALWAYS,
                                   FILE_ATTRIBUTE_SYSTEM,
                                   NULL);

    if (INVALID_HANDLE_VALUE != hMetaFile)
    {
        WCHAR szName[MAX_GAMENAME];

        if (XapiValidateAndSkipUnicodeSignature(hMetaFile) &&
            XapiFindValueInMetaFile(hMetaFile, g_cszNameTag, szName, ARRAYSIZE(szName)) &&
            (0 == wcscmp(szName, lpSaveGameName)))
        {
            dwRet = ERROR_SUCCESS;
        }
        else
        {
            dwRet = ERROR_NO_MATCH;
        }

        CloseHandle(hMetaFile);
    }
    else
    {
        dwRet = GetLastError();
    }

    return dwRet;
}

VOID
XapiTouchDirectoryTimestamp(
    LPCSTR pcszDir
    )
{
    HANDLE hDir = CreateFile(pcszDir,
                             GENERIC_READ | GENERIC_WRITE,
                             FILE_SHARE_READ | FILE_SHARE_WRITE,
                             NULL,
                             OPEN_EXISTING,
                             FILE_FLAG_BACKUP_SEMANTICS,
                             NULL);

    if (INVALID_HANDLE_VALUE != hDir)
    {
        FILE_BASIC_INFORMATION BasicInfo;
        IO_STATUS_BLOCK IoStatusBlock;

        //
        // Zero all the time values we can set.
        //

        RtlZeroMemory(&BasicInfo, sizeof(BasicInfo));

        //
        // Set the last write times
        //

        KeQuerySystemTime(&BasicInfo.LastWriteTime);

        NtSetInformationFile(
                    hDir,
                    &IoStatusBlock,
                    &BasicInfo,
                    sizeof(BasicInfo),
                    FileBasicInformation
                    );

        CloseHandle(hDir);
    }
}

DWORD
WINAPI
XCreateSaveGame(
    IN PCOSTR lpRootPathName,
    IN LPCWSTR lpSaveGameName,
    IN DWORD dwCreationDisposition,
    IN DWORD dwCreateFlags,
    OUT POSTR lpPathBuffer,
    IN UINT uSize)
{
    OCHAR szDirName[MAX_PATH];
    int nPathLength;
    int iCurIdentity;
    HANDLE hMetaFile;
    BOOL fNewCreation;
    DWORD dwNameVerifyResult;

#if DBG
    if ((NULL == lpSaveGameName) ||
        (wcslen(lpSaveGameName) >= MAX_GAMENAME) ||
        (NULL != wcsstr(lpSaveGameName, g_cszCRLF)) ||
        (NULL == lpRootPathName) ||
        (OTEXT('\0') == lpRootPathName[0]) ||
        (OTEXT(':') != lpRootPathName[1]) ||
        (OTEXT('\\') != lpRootPathName[2]) ||
        (OTEXT('\0') != lpRootPathName[3]) ||
        ((CREATE_NEW != dwCreationDisposition) &&
         (OPEN_EXISTING != dwCreationDisposition) &&
         (OPEN_ALWAYS != dwCreationDisposition)))
    {
        RIP("XCreateSaveGame() invalid parameter");
    }

    RIP_ON_NOT_TRUE("XCreateSaveGame()",
                    ((0 == dwCreateFlags) ||
                     ((XSAVEGAME_NOCOPY == dwCreateFlags) && (OPEN_EXISTING != dwCreationDisposition))));

    {
        //
        // Removing the 0x20 bit will make lower case characters uppercase
        //

        OCHAR chDrive = lpRootPathName[0] & (~0x20);

        if (((chDrive < MU_FIRST_DRIVE) || (chDrive > MU_LAST_DRIVE)) &&
            (HD_UDATA_DRIVE != chDrive) &&
            (HD_ALT_UDATA_DRIVE != chDrive))
        {
            RIP("XCreateSaveGame() invalid drive letter parameter");
        }
    }

#endif // DBG

    ocscpy(szDirName, lpRootPathName);
    nPathLength = ocslen(szDirName);
    XapiSaveGameHashEncode(lpSaveGameName,
                           &(szDirName[nPathLength]),
                           ARRAYSIZE(szDirName) - nPathLength - g_cchSaveMetaFileName);

    nPathLength = ocslen(szDirName);

    ASSERT(nPathLength < (int) ARRAYSIZE(szDirName) - g_cchSaveMetaFileName);

    if (OPEN_EXISTING == dwCreationDisposition)
    {
        ocscpy(&(szDirName[nPathLength]), g_cszSaveMetaFileName);
        dwNameVerifyResult = XapiVerifyGameName(szDirName, lpSaveGameName);
        szDirName[nPathLength] = OBJECT_NULL;

        if (ERROR_SUCCESS == dwNameVerifyResult)
        {
            //
            // The existing directory has a metadata file with the same game name,
            // so we're done
            //
            fNewCreation = FALSE;
        }
        else
        {
            return dwNameVerifyResult;
        }
    }
    else
    {
        if (CreateDirectory(szDirName, NULL))
        {
            fNewCreation = TRUE;
        }
        else
        {
            DWORD dwErr = GetLastError();

            dwNameVerifyResult = ERROR_NO_MATCH;

            if (ERROR_ALREADY_EXISTS == dwErr)
            {
                ocscpy(&(szDirName[nPathLength]), g_cszSaveMetaFileName);
                dwNameVerifyResult = XapiVerifyGameName(szDirName, lpSaveGameName);
                szDirName[nPathLength] = OBJECT_NULL;

                if (ERROR_SUCCESS == dwNameVerifyResult)
                {
                    //
                    // This is the right directory - touch the timestamp
                    // so that the dashboard will see this as the most recently
                    // used save game
                    //

                    XapiTouchDirectoryTimestamp(szDirName);
                }
                else
                {
                    return ERROR_CANNOT_MAKE;
                }
            }

            if ((CREATE_NEW != dwCreationDisposition) && (ERROR_SUCCESS == dwNameVerifyResult))
            {
                //
                // The save game name in the metadata file matched, so we're ok to
                // continue with the rest of this function
                //
                fNewCreation = FALSE;
            }
            else
            {
                return dwErr;
            }
        }
    }

    if (fNewCreation)
    {
        OBJECT_ATTRIBUTES objectAttributes;
        IO_STATUS_BLOCK   ioStatusBlock;
        LARGE_INTEGER     allocationSize;
        OBJECT_STRING     metaFilePathString;
        NTSTATUS          status;

        //
        // Create SAVEMETA.XBX file underneath the save game directory and
        // write metadata information there
        //
        ocscpy(&(szDirName[nPathLength]), g_cszSaveMetaFileName);

        RtlInitObjectString(&metaFilePathString, szDirName);
        
        InitializeObjectAttributes(
            &objectAttributes,
            &metaFilePathString,
            OBJ_CASE_INSENSITIVE,
            ObDosDevicesDirectory(),
            NULL
            );

        allocationSize.QuadPart = 1;

        status = NtCreateFile(
                      &hMetaFile,
                      SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE,
                      &objectAttributes,
                      &ioStatusBlock,
                      &allocationSize,
                      FILE_ATTRIBUTE_SYSTEM,
                      0,
                      FILE_OPEN_IF,
                      FILE_WRITE_THROUGH | FILE_SYNCHRONOUS_IO_NONALERT
                      );

        szDirName[nPathLength] = OBJECT_NULL;

        if(FAILED(status))
        {
            DWORD dwErr = RtlNtStatusToDosError(status);

            //
            // We failed, so try to remove the directory that we just created
            // so we don't leave an orphan empty directory in the file system.
            //

            RemoveDirectory(szDirName);

            return dwErr;
        }
        else
        {
            DWORD dwBytesWritten = 0;
            DWORD dwSize;

            //
            // One signature WCHAR plus a line of meta data
            //
            
            WCHAR szBuffer[1 + MAX_METADATA_LINE + g_cchNoCopyTrue];

            //
            // Only write the name into the metadata file when we first create it
            //

            _snwprintf(szBuffer,
                       ARRAYSIZE(szBuffer),
                       (XSAVEGAME_NOCOPY & dwCreateFlags) ?
                           L"%lc%ls%lc%ls%ls%ls" :
                           L"%lc%ls%lc%ls%ls",
                       g_chUnicodeSignature,
                       g_cszNameTag,
                       g_chEqual,
                       lpSaveGameName,
                       g_cszCRLF,
                       g_cszNoCopyTrue);

            dwSize = sizeof(WCHAR) * wcslen(szBuffer);

            while (dwSize)
            {
                DWORD dwWrittenNow;
                
                if (!WriteFile(hMetaFile,
                               (PBYTE) szBuffer + dwBytesWritten,
                               dwSize,
                               &dwWrittenNow,
                               NULL))
                {
                    DWORD dwErr = GetLastError();

                    CloseHandle(hMetaFile);
                    
                    //
                    // We failed, so try to remove the directory that we just created
                    // so we don't leave an orphan empty directory in the file system.
                    //
                    
                    RemoveDirectory(szDirName);

                    return dwErr;
                }

                dwSize -= dwWrittenNow;
            }

            CloseHandle(hMetaFile);
        }
    }
#if DBG
    else if (0 != dwCreateFlags)
    {
        XDBGWRN("XAPI",
                "XCreateSaveGame() ignoring dwCreateFlags because save game %ls already exists",
                lpSaveGameName);
    }
#endif // DBG

    if (NULL != lpPathBuffer)
    {
        lstrcpynO(lpPathBuffer, szDirName, uSize);

        if ((UINT) nPathLength < (uSize - 1))
        {
            //
            // Append a backslash (if there is room)
            //

            lpPathBuffer[nPathLength] = OTEXT('\\');
            lpPathBuffer[nPathLength + 1] = OBJECT_NULL;
        }
    }

    return ERROR_SUCCESS;
}

DWORD
WINAPI
XDeleteSaveGame(
    IN PCOSTR lpRootPathName,
    IN LPCWSTR lpSaveGameName)
{
    OCHAR szDirName[64 + MAX_PATH];
    int nPathLength;
    DWORD dwNameVerifyResult;
    int nPrefixLength;
    NTSTATUS Status;

#if DBG
    if ((NULL == lpSaveGameName) ||
        (wcslen(lpSaveGameName) >= MAX_GAMENAME) ||
        (NULL != wcsstr(lpSaveGameName, g_cszCRLF)) ||
        (NULL == lpRootPathName) ||
        (OTEXT('\0') == lpRootPathName[0]) ||
        (OTEXT(':')  != lpRootPathName[1]) ||
        (OTEXT('\\') != lpRootPathName[2]) ||
        (OTEXT('\0') != lpRootPathName[3]))
    {
        RIP("XDeleteSaveGame() invalid parameter");
    }

    {
        //
        // Removing the 0x20 bit will make lower case characters uppercase
        //

        OCHAR chDrive = lpRootPathName[0] & (~0x20);

        if (((chDrive < MU_FIRST_DRIVE) || (chDrive > MU_LAST_DRIVE)) &&
            (HD_UDATA_DRIVE != chDrive) &&
            (HD_ALT_UDATA_DRIVE != chDrive))
        {
            RIP("XDeleteSaveGame() invalid drive letter parameter");
        }
    }
#endif // DBG

    ocscpy(szDirName, OTEXT("\\??\\"));
    nPrefixLength = ocslen(szDirName);

    ocscpy(&(szDirName[nPrefixLength]), lpRootPathName);
    nPathLength = ocslen(szDirName);

    XapiSaveGameHashEncode(lpSaveGameName,
                           &(szDirName[nPathLength]),
                           ARRAYSIZE(szDirName) - nPathLength - g_cchSaveMetaFileName);

    nPathLength = ocslen(szDirName);

    ASSERT(nPathLength < (int) ARRAYSIZE(szDirName) - g_cchSaveMetaFileName);

    ocscpy(&(szDirName[nPathLength]), g_cszSaveMetaFileName);
    dwNameVerifyResult = XapiVerifyGameName(&(szDirName[nPrefixLength]), lpSaveGameName);
    szDirName[nPathLength] = OBJECT_NULL;

    if (ERROR_SUCCESS != dwNameVerifyResult)
    {
        return dwNameVerifyResult;
    }

    //
    // The directory exists with a metadata file in it with a matching game
    // name, so it is safe to proceed
    //
    
    szDirName[nPathLength] = OTEXT('\\');
    szDirName[nPathLength + 1] = OBJECT_NULL;

    Status = XapiNukeDirectory(szDirName);

    return RtlNtStatusToDosError(Status);
}

HANDLE
WINAPI
XFindFirstSaveGame(
    IN PCOSTR lpRootPathName,
    OUT PXGAME_FIND_DATA pFindGameData)
{
    OCHAR szDirName[MAX_PATH];
    int cchRootDir;
    HANDLE hRet;

#if DBG
    if ((NULL == pFindGameData) ||
        (NULL == lpRootPathName) ||
        (OTEXT('\0') == lpRootPathName[0]) ||
        (OTEXT(':')  != lpRootPathName[1]) ||
        (OTEXT('\\') != lpRootPathName[2]) ||
        (OTEXT('\0') != lpRootPathName[3]))
    {
        RIP("XFindFirstSaveGame() invalid parameter");
    }

    {
        //
        // Removing the 0x20 bit will make lower case characters uppercase
        //

        OCHAR chDrive = lpRootPathName[0] & (~0x20);

        if (((chDrive < MU_FIRST_DRIVE) || (chDrive > MU_LAST_DRIVE)) &&
            (HD_UDATA_DRIVE != chDrive) &&
            (HD_ALT_UDATA_DRIVE != chDrive))
        {
            RIP("XFindFirstSaveGame() invalid drive letter parameter");
        }
    }
#endif // DBG

    //
    // Create <path>\<*.*> in our own buffer
    //

    ocscpy(szDirName, lpRootPathName);
    cchRootDir = ocslen(szDirName);

    if (cchRootDir > ((int) ARRAYSIZE(szDirName) - 1 - g_cchStar))
    {
        RIP("XFindFirstSaveGame() lpRootPathName parameter too long");
    }

    ocscpy(&(szDirName[cchRootDir]), g_cszStar);

    hRet = FindFirstFile(szDirName, (PWIN32_FIND_DATA) pFindGameData);

    if (INVALID_HANDLE_VALUE != hRet)
    {
        if (!XapiFillInSaveGameData(pFindGameData, lpRootPathName, cchRootDir))
        {
            BOOL fFound;

            while (fFound = FindNextFile(hRet, (PWIN32_FIND_DATA) pFindGameData))
            {
                if (XapiFillInSaveGameData(pFindGameData, lpRootPathName, cchRootDir))
                {
                    break;
                }
            }

            if (!fFound)
            {
                //
                // We didn't find a save game directory
                //

                FindClose(hRet);
                hRet = INVALID_HANDLE_VALUE;
                SetLastError(ERROR_NO_MORE_FILES);
            }
        }
    }

    if (INVALID_HANDLE_VALUE != hRet)
    {
        PFINDGAME_HANDLE pFindGame = LocalAlloc(LMEM_FIXED, sizeof(FINDGAME_HANDLE));

        if (NULL != pFindGame)
        {
            pFindGame->dwSignature = FH_SIG_SAVEGAME;
            pFindGame->hFindFile = hRet;
            pFindGame->cchRootDir = cchRootDir;
            lstrcpynO(pFindGame->szRootDir, lpRootPathName, ARRAYSIZE(pFindGame->szRootDir));
            hRet = (HANDLE) pFindGame;
        }
        else
        {
            FindClose(hRet);
            hRet = INVALID_HANDLE_VALUE;
            SetLastError(ERROR_NOT_ENOUGH_MEMORY);
        }
    }

    return hRet;
}

BOOL
WINAPI
XFindNextSaveGame(
    IN HANDLE hFindGame,
    OUT PXGAME_FIND_DATA pFindGameData)
{
    BOOL bRet = FALSE;

#if DBG
    if ((NULL == hFindGame) ||
        (INVALID_HANDLE_VALUE == hFindGame) ||
        (FH_SIG_SAVEGAME != ((PFINDGAME_HANDLE) hFindGame)->dwSignature) ||
        (NULL == pFindGameData))
    {
        RIP("XFindNextSaveGame() invalid parameter");
    }
#endif // DBG

    {
        PFINDGAME_HANDLE pFindGame = (PFINDGAME_HANDLE) hFindGame;

        while (bRet = FindNextFile(pFindGame->hFindFile, (PWIN32_FIND_DATA) pFindGameData))
        {
            if (XapiFillInSaveGameData(pFindGameData, pFindGame->szRootDir, pFindGame->cchRootDir))
            {
                break;
            }
        }
    }

    return bRet;
}

BOOL
WINAPI
XFindClose(
    IN HANDLE hFindGame)
{
    BOOL fRet = FALSE;

    //
    // Note that all XFind handles begin with a DWORD signature that
    // identifies the type of XFind handle we're dealing with
    //

#if DBG
    if ((INVALID_HANDLE_VALUE == hFindGame) ||
        (NULL == hFindGame) ||
        ((FH_SIG_SAVEGAME != *((PDWORD) hFindGame)) &&
         (FH_SIG_NICKNAME != *((PDWORD) hFindGame)) &&
         (FH_SIG_CONTENT!= *((PDWORD) hFindGame)) &&
         (FH_SIG_SOUNDTRACK != *((PDWORD) hFindGame))))
    {
        RIP("XFindClose() invalid parameter (hFindGame)");
    }
#endif // DBG

    switch (*((PDWORD) hFindGame))
    {
        case FH_SIG_SAVEGAME:
        {
            PFINDGAME_HANDLE pFindGame = (PFINDGAME_HANDLE) hFindGame;
            HANDLE hFindFile = pFindGame->hFindFile;
            LocalFree(pFindGame);

            fRet = FindClose(hFindFile);
            break;
        }

        case FH_SIG_CONTENT:
        {
            PFINDCONTENT_HANDLE pFindCont = (PFINDCONTENT_HANDLE) hFindGame;
            HANDLE hFindFile = pFindCont->hFindFile;
            LocalFree(pFindCont);

            fRet = FindClose(hFindFile);
            break;
        }

        case FH_SIG_SOUNDTRACK:
        {
            PSNDTRK_ENUMSTATE pst = (PSNDTRK_ENUMSTATE) hFindGame;
            HANDLE hFindFile = pst->DbHandle;
            LocalFree(pst);

            fRet = FindClose(hFindFile);
            break;
        }

        case FH_SIG_NICKNAME:
        {
            LocalFree(hFindGame);
            fRet = TRUE;
            break;
        }
    }

    return fRet;
}

