1090 lines
30 KiB
C
1090 lines
30 KiB
C
#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;
|
|
}
|
|
|