Files
Aerofoil/Aerofoil/GpFileSystem_Win32.cpp
2021-04-28 23:18:24 -04:00

557 lines
12 KiB
C++

#include "GpFileSystem_Win32.h"
#include "GpAllocator_C.h"
#include "GpApplicationName.h"
#include "GpFileStream_Win32.h"
#include "GpWindows.h"
#include "IGpAllocator.h"
#include "IGpDirectoryCursor.h"
#include <Shlwapi.h>
#include <ShlObj.h>
#include <commdlg.h>
#include <assert.h>
struct IGpAllocator;
extern GpWindowsGlobals g_gpWindowsGlobals;
class GpDirectoryCursor_Win32 final : public IGpDirectoryCursor
{
public:
static GpDirectoryCursor_Win32 *Create(IGpAllocator *alloc, const HANDLE &handle, const WIN32_FIND_DATAW &findData);
bool GetNext(const char *&outFileName) override;
void Destroy() override;
private:
GpDirectoryCursor_Win32(IGpAllocator *alloc, const HANDLE &handle, const WIN32_FIND_DATAW &findData);
~GpDirectoryCursor_Win32();
IGpAllocator *m_alloc;
HANDLE m_handle;
WIN32_FIND_DATAW m_findData;
char m_chars[MAX_PATH + 1];
bool m_haveNext;
};
GpDirectoryCursor_Win32 *GpDirectoryCursor_Win32::Create(IGpAllocator *alloc, const HANDLE &handle, const WIN32_FIND_DATAW &findData)
{
void *storage = alloc->Alloc(sizeof(GpDirectoryCursor_Win32));
if (!storage)
return nullptr;
return new (storage) GpDirectoryCursor_Win32(alloc, handle, findData);
}
bool GpDirectoryCursor_Win32::GetNext(const char *&outFileName)
{
while (m_haveNext)
{
bool haveResult = false;
bool hasInvalidChars = false;
for (const wchar_t *fnameScan = m_findData.cFileName; *fnameScan; fnameScan++)
{
const int32_t asInt = static_cast<int32_t>(*fnameScan);
if (asInt < 1 || asInt >= 128)
{
hasInvalidChars = true;
break;
}
}
if (!hasInvalidChars && wcscmp(m_findData.cFileName, L".") && wcscmp(m_findData.cFileName, L".."))
{
const size_t len = wcslen(m_findData.cFileName);
haveResult = true;
for (size_t i = 0; i <= len; i++)
m_chars[i] = static_cast<char>(m_findData.cFileName[i]);
}
m_haveNext = (FindNextFileW(m_handle, &m_findData) != FALSE);
if (haveResult)
{
outFileName = m_chars;
return true;
}
}
return false;
}
void GpDirectoryCursor_Win32::Destroy()
{
IGpAllocator *alloc = m_alloc;
this->~GpDirectoryCursor_Win32();
alloc->Release(this);
}
GpDirectoryCursor_Win32::GpDirectoryCursor_Win32(IGpAllocator *alloc, const HANDLE &handle, const WIN32_FIND_DATAW &findData)
: m_handle(handle)
, m_findData(findData)
, m_haveNext(true)
, m_alloc(alloc)
{
}
GpDirectoryCursor_Win32::~GpDirectoryCursor_Win32()
{
FindClose(m_handle);
}
GpFileSystem_Win32::GpFileSystem_Win32(IGpAllocator *alloc)
: m_alloc(alloc)
, m_prefsDir(alloc)
, m_scoresDir(alloc)
, m_packagedDir(alloc)
, m_housesDir(alloc)
, m_logsDir(alloc)
, m_userHousesDir(alloc)
, m_userSavesDir(alloc)
, m_resourcesDir(alloc)
{
m_executablePath[0] = 0;
}
void GpFileSystem_Win32::Destroy()
{
IGpAllocator *alloc = m_alloc;
this->~GpFileSystem_Win32();
alloc->Release(this);
}
bool GpFileSystem_Win32::Init()
{
PWSTR docsPath;
if (FAILED(SHGetKnownFolderPath(FOLDERID_Documents, KF_FLAG_DEFAULT, nullptr, &docsPath)))
return false;
if (!m_prefsDir.Set(docsPath))
{
CoTaskMemFree(docsPath);
return false;
}
CoTaskMemFree(docsPath);
if (!m_prefsDir.Append(L"\\" GP_APPLICATION_NAME_W))
return false;
if (!m_userHousesDir.Set(m_prefsDir) || !m_userHousesDir.Append(L"\\Houses"))
return false;
if (!m_userSavesDir.Set(m_prefsDir) || !m_userSavesDir.Append(L"\\SavedGames"))
return false;
if (!m_scoresDir.Set(m_prefsDir) || !m_scoresDir.Append(L"\\Scores"))
return false;
if (!m_logsDir.Set(m_prefsDir) || !m_logsDir.Append(L"\\Logs"))
return false;
CreateDirectoryW(m_prefsDir.Buffer(), nullptr);
CreateDirectoryW(m_scoresDir.Buffer(), nullptr);
CreateDirectoryW(m_userHousesDir.Buffer(), nullptr);
CreateDirectoryW(m_userSavesDir.Buffer(), nullptr);
CreateDirectoryW(m_logsDir.Buffer(), nullptr);
if (!m_prefsDir.Append(L"\\") ||
!m_scoresDir.Append(L"\\") ||
!m_userHousesDir.Append(L"\\") ||
!m_userSavesDir.Append(L"\\") ||
!m_logsDir.Append(L"\\") ||
!m_resourcesDir.Append(L"\\"))
return false;
DWORD modulePathSize = GetModuleFileNameW(nullptr, m_executablePath, MAX_PATH);
if (modulePathSize == MAX_PATH || modulePathSize == 0)
m_executablePath[0] = 0;
size_t currentPathLength = wcslen(m_executablePath);
for (;;)
{
while (currentPathLength > 0 && m_executablePath[currentPathLength - 1] != '\\')
currentPathLength--;
m_executablePath[currentPathLength] = 0;
if (currentPathLength + 11 > MAX_PATH)
{
// "Resources" append is a longer path than the executable
continue;
}
if (wcscat_s(m_executablePath, L"Packaged"))
{
currentPathLength = 0;
break;
}
if (PathFileExistsW(m_executablePath) && PathIsDirectoryW(m_executablePath))
{
m_executablePath[currentPathLength] = 0;
break;
}
else
currentPathLength--;
}
if (currentPathLength == 0)
return false;
if (!m_packagedDir.Set(m_executablePath) || !m_packagedDir.Append(L"Packaged\\"))
return false;
if (!m_housesDir.Set(m_executablePath) || !m_housesDir.Append(L"Packaged\\Houses\\"))
return false;
if (!m_resourcesDir.Set(m_executablePath) || !m_resourcesDir.Append(L"Resources\\"))
return false;
return true;
}
bool GpFileSystem_Win32::FileExists(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *path)
{
wchar_t winPath[MAX_PATH + 1];
if (!ResolvePath(virtualDirectory, &path, 1, winPath))
return false;
return PathFileExistsW(winPath) != 0;
}
bool GpFileSystem_Win32::FileLocked(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *path, bool &exists)
{
wchar_t winPath[MAX_PATH + 1];
if (!ResolvePath(virtualDirectory, &path, 1, winPath))
{
exists = false;
return false;
}
DWORD attribs = GetFileAttributesW(winPath);
if (attribs == INVALID_FILE_ATTRIBUTES)
{
exists = false;
return false;
}
exists = true;
return (attribs & FILE_ATTRIBUTE_READONLY) != 0;
}
GpIOStream *GpFileSystem_Win32::OpenFileNested(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths, bool writeAccess, GpFileCreationDisposition_t createDisposition)
{
wchar_t winPath[MAX_PATH + 1];
if (!ResolvePath(virtualDirectory, paths, numPaths, winPath))
return false;
const DWORD desiredAccess = writeAccess ? (GENERIC_WRITE | GENERIC_READ) : GENERIC_READ;
DWORD winCreationDisposition = 0;
switch (createDisposition)
{
case GpFileCreationDispositions::kCreateOrOverwrite:
winCreationDisposition = CREATE_ALWAYS;
break;
case GpFileCreationDispositions::kCreateNew:
winCreationDisposition = CREATE_NEW;
break;
case GpFileCreationDispositions::kCreateOrOpen:
winCreationDisposition = OPEN_ALWAYS;
break;
case GpFileCreationDispositions::kOpenExisting:
winCreationDisposition = OPEN_EXISTING;
break;
case GpFileCreationDispositions::kOverwriteExisting:
winCreationDisposition = TRUNCATE_EXISTING;
break;
default:
return false;
}
HANDLE h = CreateFileW(winPath, desiredAccess, FILE_SHARE_READ, nullptr, winCreationDisposition, FILE_ATTRIBUTE_NORMAL, nullptr);
if (h == INVALID_HANDLE_VALUE)
return false;
return new GpFileStream_Win32(h, true, writeAccess, true);
}
bool GpFileSystem_Win32::DeleteFile(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *path, bool &existed)
{
wchar_t winPath[MAX_PATH + 1];
if (!ResolvePath(virtualDirectory, &path, 1, winPath))
return false;
if (DeleteFileW(winPath))
{
existed = true;
return true;
}
DWORD err = GetLastError();
if (err == ERROR_FILE_NOT_FOUND)
existed = false;
else
existed = true;
return false;
}
IGpDirectoryCursor *GpFileSystem_Win32::ScanDirectoryNested(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths)
{
wchar_t winPath[MAX_PATH + 2];
const char **expandedPaths = static_cast<const char**>(m_alloc->Alloc(sizeof(const char*) * (numPaths + 1)));
if (!expandedPaths)
return nullptr;
for (size_t i = 0; i < numPaths; i++)
expandedPaths[i] = paths[i];
expandedPaths[numPaths] = "*";
const bool isPathResolved = ResolvePath(virtualDirectory, expandedPaths, numPaths + 1, winPath);
m_alloc->Release(expandedPaths);
if (!isPathResolved)
return nullptr;
WIN32_FIND_DATAW findData;
HANDLE ff = FindFirstFileW(winPath, &findData);
if (ff == INVALID_HANDLE_VALUE)
return nullptr;
return GpDirectoryCursor_Win32::Create(m_alloc, ff, findData);
}
bool GpFileSystem_Win32::ValidateFilePathUnicodeChar(uint32_t c) const
{
if (c >= '0' && c <= '9')
return true;
if (c == '_' || c == '\'')
return true;
if (c == ' ')
return true;
if (c >= 'a' && c <= 'z')
return true;
if (c >= 'A' && c <= 'Z')
return true;
return false;
}
void GpFileSystem_Win32::SetDelayCallback(GpFileSystem_Win32::DelayCallback_t delayCallback)
{
}
bool GpFileSystem_Win32::ValidateFilePath(const char *str, size_t length) const
{
for (size_t i = 0; i < length; i++)
{
const char c = str[i];
if (c >= '0' && c <= '9')
continue;
if (c == '_' || c == '.' || c == '\'' || c == '!')
continue;
if (c == ' ' && i != 0 && i != length - 1)
continue;
if (c >= 'a' && c <= 'z')
continue;
if (c >= 'A' && c <= 'Z')
continue;
return false;
}
const char *bannedNames[] =
{
"CON",
"PRN",
"AUX",
"NUL",
"COM1",
"COM2",
"COM3",
"COM4",
"COM5",
"COM6",
"COM7",
"COM8",
"COM9",
"LPT1",
"LPT2",
"LPT3",
"LPT4",
"LPT5",
"LPT6",
"LPT7",
"LPT8",
"LPT9"
};
size_t nameLengthWithoutExt = length;
for (size_t i = 0; i < length; i++)
{
if (str[i] == '.')
{
nameLengthWithoutExt = i;
break;
}
}
const size_t numBannedNames = sizeof(bannedNames) / sizeof(bannedNames[0]);
for (size_t i = 0; i < numBannedNames; i++)
{
const char *bannedName = bannedNames[i];
const size_t banLength = strlen(bannedName);
if (banLength == nameLengthWithoutExt)
{
bool isBanned = true;
for (size_t j = 0; j < banLength; j++)
{
char checkCH = str[j];
if (checkCH >= 'a' && checkCH <= 'z')
checkCH += ('A' - 'a');
if (bannedName[j] != checkCH)
{
isBanned = false;
break;
}
}
if (isBanned)
return false;
}
}
return true;
}
const wchar_t *GpFileSystem_Win32::GetBasePath() const
{
return m_executablePath;
}
GpFileSystem_Win32 *GpFileSystem_Win32::CreateInstance(IGpAllocator *alloc)
{
void *storage = alloc->Alloc(sizeof(GpFileSystem_Win32));
if (!storage)
return nullptr;
GpFileSystem_Win32 *fs = new (storage) GpFileSystem_Win32(alloc);
if (!fs->Init())
{
fs->Destroy();
return nullptr;
}
ms_instance = fs;
return fs;
}
GpFileSystem_Win32 *GpFileSystem_Win32::GetInstance()
{
return ms_instance;
}
bool GpFileSystem_Win32::ResolvePath(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths, wchar_t *outPath)
{
const wchar_t *baseDir = nullptr;
switch (virtualDirectory)
{
case PortabilityLayer::VirtualDirectories::kApplicationData:
baseDir = m_packagedDir.Buffer();
break;
case PortabilityLayer::VirtualDirectories::kGameData:
baseDir = m_housesDir.Buffer();
break;
case PortabilityLayer::VirtualDirectories::kUserData:
baseDir = m_userHousesDir.Buffer();
break;
case PortabilityLayer::VirtualDirectories::kUserSaves:
baseDir = m_userSavesDir.Buffer();
break;
case PortabilityLayer::VirtualDirectories::kPrefs:
baseDir = m_prefsDir.Buffer();
break;
case PortabilityLayer::VirtualDirectories::kFonts:
baseDir = m_resourcesDir.Buffer();
break;
case PortabilityLayer::VirtualDirectories::kHighScores:
baseDir = m_scoresDir.Buffer();
break;
case PortabilityLayer::VirtualDirectories::kLogs:
baseDir = m_logsDir.Buffer();
break;
default:
return false;
}
if (baseDir == nullptr)
return false;
const size_t baseDirLen = wcslen(baseDir);
memcpy(outPath, baseDir, sizeof(wchar_t) * baseDirLen);
outPath[baseDirLen] = static_cast<wchar_t>(0);
for (size_t i = 0; i < numPaths; i++)
{
size_t outDirLen = wcslen(outPath);
if (i != 0)
{
if (baseDirLen >= MAX_PATH || MAX_PATH - baseDirLen < 1)
return false;
outPath[outDirLen++] = '\\';
}
const char *path = paths[i];
const size_t pathLen = strlen(path);
if (baseDirLen >= MAX_PATH || MAX_PATH - baseDirLen < pathLen)
return false;
for (size_t j = 0; j < pathLen; j++)
{
char c = path[j];
if (c == '/')
c = '\\';
outPath[outDirLen + j] = static_cast<wchar_t>(c);
}
outPath[outDirLen + pathLen] = static_cast<wchar_t>(0);
}
return true;
}
GpFileSystem_Win32 *GpFileSystem_Win32::ms_instance;