micetools/src/micetools/dll/micefs.c

432 lines
16 KiB
C

#include "micefs.h"
#pragma comment(lib, "Shlwapi.lib")
#include <shlwapi.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "hooks/files.h"
static char miceFSCwd[MAX_PATH + 1];
/* Filesystem layer storage. When locating files, the first found result will be used. New items are
* inserted at the start of the list. */
static MICE_FS_LAYER miceFsRoot = {
.m_lpMountPoint = NULL,
.m_lpTargetPath = NULL,
.m_Next = NULL,
};
static volatile LONG miceFsLockCount = 0;
#define MiceTakeLockExclusive(lock) \
do { \
LONG __miceLockCount = (lock); \
if (__miceLockCount == 0) { \
if (InterlockedCompareExchange(&(lock), -1, 0) == 0) break; \
} \
SwitchToThread(); \
} while (1)
#define MiceReleaseLockExclusive(lock) \
do { \
(lock) = 0; \
} while (0)
#define MiceTakeLockShared(lock) \
do { \
LONG __miceLockCount = (lock); \
if (__miceLockCount != -1) { \
if (InterlockedCompareExchange(&(lock), __miceLockCount + 1, __miceLockCount) == \
__miceLockCount) \
break; \
} \
SwitchToThread(); \
} while (1)
#define MiceReleaseLockShared(lock) \
do { \
(lock)--; \
} while (0)
BOOL MiceFSInit(VOID) {
// By not using MiceFSSetCwd we can save a buffer allocation
_GetCurrentDirectoryA(sizeof miceFSCwd, miceFSCwd);
return TRUE;
}
LPCSTR MiceFSGetCwd(VOID) { return miceFSCwd; }
VOID MiceFSSetCwd(LPCSTR lpNewCwd) { strcpy_s(miceFSCwd, sizeof miceFSCwd, lpNewCwd); }
BOOL MiceFSAddLayer(LPCSTR lpMountPoint, LPCSTR lpTargetPath) {
DWORD dwMountLength = strlen(lpMountPoint) + 1;
DWORD dwTargetlength = strlen(lpTargetPath) + 1;
if (dwMountLength <= 1 || dwTargetlength <= 1) {
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
PMICE_FS_LAYER layer = malloc(sizeof *layer);
if (layer == NULL) return FALSE;
LPSTR szMountPoint = malloc(MAX_PATH + 1);
if (szMountPoint == NULL) {
free(layer);
return FALSE;
}
LPSTR szTargetPath = malloc(MAX_PATH + 1);
if (szTargetPath == NULL) {
free(szMountPoint);
free(layer);
return FALSE;
}
ZeroMemory(szMountPoint, MAX_PATH + 1);
if (!GetFullPathNameA(lpMountPoint, MAX_PATH + 1, szMountPoint, NULL)) {
free(szTargetPath);
free(szMountPoint);
free(layer);
return FALSE;
}
ZeroMemory(szTargetPath, MAX_PATH + 1);
if (!GetFullPathNameA(lpTargetPath, MAX_PATH + 1, szTargetPath, NULL)) {
free(szTargetPath);
free(szMountPoint);
free(layer);
return FALSE;
}
// Mount and target are promoted to LPCSTR from LPSTR here
layer->m_lpMountPoint = szMountPoint;
layer->m_lpTargetPath = szTargetPath;
// Commit ourselves to the FS
MiceTakeLockExclusive(miceFsLockCount);
layer->m_Next = miceFsRoot.m_Next;
miceFsRoot.m_Next = layer;
MiceReleaseLockExclusive(miceFsLockCount);
SetLastError(ERROR_SUCCESS);
return TRUE;
}
BOOL MiceFSAddDevLayers(VOID) {
CHAR szMountPoint[4];
CHAR szTargetPath[MAX_PATH + 1];
ZeroMemory(szTargetPath, sizeof szTargetPath);
strcpy_s(szTargetPath, sizeof szTargetPath, MiceIpcRelativePath("dev\\x\\"));
DWORD index = strlen(szTargetPath) - 2;
CHAR szCurrentDir[MAX_PATH + 1];
_GetCurrentDirectoryA(sizeof szCurrentDir, szCurrentDir);
// See below TODO
if (szCurrentDir[0] == RING_MOUNT_APPDATA[0]) {
log_error(plfBoot,
"Booting on the %s drive is currently unsupported. There's a potential for it to "
"do nasty things to your computer at the moment.",
RING_MOUNT_APPDATA);
exit(0);
}
BOOL success = TRUE;
for (int i = 0; i < 26; i++) {
// TODO: DON'T SKIP C
/**
* Note: This is currently skipped because things such as DLLs, COM
* registrations, etc. are all stored on C.
* I have, however, run into an issue with a game (I forget which)
* where _not_ hooking C caused issues.
* This will likely require fine-grained whitelists.
*/
if (i == 2) continue;
// TODO: Cleaner way to not skip the current drive?
// Currently skipped because of games that compile shaders at runtime (eg SDCM), however
// this could cause issues if a game is running on a drive letter that we really need to
// hook. _Are_ there any drive letters that would prove fatal? Appdata, probably. To
// protect users from themselves, we're currently rejecting the use of Y: as a boot drive
// entirely. This is ugly, and I hate it very much, but I have bigger fish to fry for now.
if (i == szCurrentDir[0] - 'A' || i == szCurrentDir[0] - 'a') continue;
szMountPoint[0] = 'A' + (char)i;
szMountPoint[1] = ':';
szMountPoint[2] = '\\';
szMountPoint[3] = '\0';
szTargetPath[index] = 'a' + (char)i;
success &= MiceFSAddLayer(szMountPoint, szTargetPath);
}
return success;
}
BOOL MiceFSAddRingedgeLayers(BOOL bIsOsupdate) {
/**
* Note: Layers are searched in reverse order. Most lookups will be served by:
* 1. original
* 2. pathch
* 3. extend
* so those are added last!
*/
BOOL success = TRUE;
// TODO: Configurable!
MiceFSAddLayer("R:", "H:\\Arcades\\USBs\\FiNALE_DL\\usb");
MiceFSAddLayer("S:", "C:\\Users\\Nathan\\Desktop\\ac_research\\sega\\S-Drive");
MiceFSAddLayer("C:\\System\\Execute", "C:\\Users\\Nathan\\Desktop\\ac_research\\sega\\S-Drive");
// Windows file systems
// TODO: See note about the C: drive above
// success &= MiceFSAddLayer(RING_MOUNT_OS, MiceIpcRelativePath("mount\\os"));
// success &= MiceFSAddLayer(RING_MOUNT_APPDATA, MiceIpcRelativePath("mount\\extend"));
success &= MiceFSAddLayer(RING_MOUNT_RECOVERY, MiceIpcRelativePath("mount\\os_recovery"));
// External media
success &= MiceFSAddLayer(RING_MOUNT_APM, MiceIpcRelativePath("mount\\apm"));
success &= MiceFSAddLayer(RING_MOUNT_AM_LOG, MiceIpcRelativePath("mount\\am_log"));
success &= MiceFSAddLayer(RING_MOUNT_DVD, MiceIpcRelativePath("mount\\dvd"));
success &= MiceFSAddLayer(RING_MOUNT_DEV, MiceIpcRelativePath("mount\\dev"));
success &= MiceFSAddLayer("C:\\Documents and Settings\\AppUser",
MiceIpcRelativePath("mount\\appuser"));
success &= MiceFSAddLayer("C:\\Documents and Settings\\SystemUser",
MiceIpcRelativePath("mount\\systemuser"));
success &=
MiceFSAddLayer("C:\\DOCUME~1\\SYSTEM~1\\LOCALS~1\\Temp\\", MiceIpcRelativePath("temp"));
// TrueCrypt Volumes
success &= MiceFSAddLayer(RING_MOUNT_SYSTEM, MiceIpcRelativePath("mount\\system"));
if (bIsOsupdate) {
success &= MiceFSAddLayer(RING_MOUNT_OS_UPDATE, MiceIpcRelativePath("mount\\os_update"));
success &= MiceFSAddLayer(RING_MOUNT_OS_DEFAULT_DRVIERS,
MiceIpcRelativePath("mount\\os_default_drivers"));
} else {
// success &= MiceFSAddLayer(RING_MOUNT_EXTEND_VOL,
// MiceIpcRelativePath("mount\\extend/extend")); success &=
// MiceFSAddLayer(RING_MOUNT_EXTEND2_VOL, MiceIpcRelativePath("mount\\extend/extend2"));
}
success &= MiceFSAddLayer(RING_MOUNT_PATCH, MiceIpcRelativePath("mount\\patch"));
success &= MiceFSAddLayer(RING_MOUNT_ORIGINAL, MiceIpcRelativePath("mount\\original"));
// geminifs
success &= MiceFSAddLayer(RING_MOUNT_GAME, MiceIpcRelativePath("mount\\patch"));
success &= MiceFSAddLayer(RING_MOUNT_GAME, MiceIpcRelativePath("mount\\original"));
// Make directories that we're going to need
make_dirs(MiceIpcRelativePath("mount\\appuser\\temp"));
make_dirs(MiceIpcRelativePath("temp"));
// Directories games read and write
make_dirs(MiceIpcRelativePath("dev\\w"));
make_dirs(MiceIpcRelativePath("dev\\v"));
make_dirs(MiceIpcRelativePath("dev\\y"));
make_dirs(MiceIpcRelativePath("dev\\x"));
return success;
}
LPSTR MiceFSPathTokA(LPSTR lpPath, DWORD dwPath, PMICE_FS_PATH_TOK lpWork) {
DWORD dwWorkPtr = 0;
LPSTR lpPathStart;
if (lpPath != NULL) {
// A zero-length string has no tokens (as oppoed to a single empty token)
if (lpPath[0] == '\0') {
SetLastError(ERROR_SUCCESS);
return NULL;
}
lpWork->m_lpPath = lpPath;
lpPathStart = lpPath;
lpWork->m_dwWorkPtr = dwWorkPtr;
lpWork->m_dwPath = dwPath;
} else {
// We just finished processing the last token!
if (lpWork->m_cSave == '\0') {
SetLastError(ERROR_SUCCESS);
return NULL;
}
dwWorkPtr = lpWork->m_dwWorkPtr;
lpPath = lpWork->m_lpPath;
lpPath[dwWorkPtr++] = lpWork->m_cSave;
lpPathStart = lpPath + dwWorkPtr;
}
DWORD dwComponentStart = dwWorkPtr;
for (; dwWorkPtr < lpWork->m_dwPath; dwWorkPtr++) {
CHAR cWork = lpPath[dwWorkPtr];
if (cWork == '\0' || cWork == '\\' || cWork == '/') {
// Omit 0-length components, with the exception of the very first position in the
// string, as that would indicate a double-backslash prefix, which is syntax for a path
// namespace and should not be treated the same as a single backslash there.
if (dwWorkPtr == dwComponentStart && dwWorkPtr != 0) {
// Special case: Trailing slashes
if (cWork == '\0') {
SetLastError(ERROR_SUCCESS);
return NULL;
}
dwComponentStart++;
continue;
}
lpWork->m_dwComponentStart = dwComponentStart;
lpWork->m_dwWorkPtr = dwWorkPtr;
lpWork->m_cSave = cWork;
lpPath[dwWorkPtr] = '\0';
return lpPathStart;
}
}
// Will only occur if lpPath was not null terminated within dwPath bytes. lpPathStart at this
// point does not point to a null-terminated string, and therefore we cannot return it.
// This is technically an error condition, so allow diligent users to catch this
SetLastError(ERROR_BUFFER_OVERFLOW);
return NULL;
}
VOID MiceFSPathTokRestA(PMICE_FS_PATH_TOK lpWork, LPSTR lpPath, DWORD dwPath) {
lpWork->m_lpPath[lpWork->m_dwWorkPtr] = lpWork->m_cSave;
memcpy_s(lpPath, dwPath, lpWork->m_lpPath + lpWork->m_dwComponentStart,
lpWork->m_dwPath - lpWork->m_dwComponentStart);
}
BOOL MiceFSMatchPathA(LPCSTR lpPath, LPCSTR lpPrefix, LPSTR lpTail, DWORD dwTail) {
CHAR szPath[MAX_PATH + 1];
CHAR szPrefix[MAX_PATH + 1];
strcpy_s(szPath, sizeof szPath, lpPath);
strcpy_s(szPrefix, sizeof szPrefix, lpPrefix);
MICE_FS_PATH_TOK pathTok;
MICE_FS_PATH_TOK matchTok;
LPSTR pathComponent = MiceFSPathTokA(szPath, _countof(szPath), &pathTok);
LPSTR prefixComponent = MiceFSPathTokA(szPrefix, _countof(szPrefix), &matchTok);
while (pathComponent && prefixComponent) {
if (lstrcmpiA(pathComponent, prefixComponent) != 0) return FALSE;
pathComponent = MiceFSPathTokNextA(&pathTok);
prefixComponent = MiceFSPathTokNextA(&matchTok);
}
// We still had parts of the prefic left!
if (prefixComponent) return FALSE;
if (lpTail && dwTail) {
if (pathComponent)
MiceFSPathTokRestA(&pathTok, lpTail, dwTail);
else
lpTail[0] = '\0';
}
return TRUE;
}
inline static DWORD MiceFSGetFullPathNameA(LPCSTR lpFileName, DWORD nBufferLength, LPSTR lpBuffer,
LPSTR* lpFilePart) {
if (PathIsRelativeA(lpFileName)) {
CHAR newPath[MAX_PATH + 1];
PathCombineA(newPath, miceFSCwd, lpFileName);
return GetFullPathNameA(newPath, nBufferLength, lpBuffer, lpFilePart);
}
return GetFullPathNameA(lpFileName, nBufferLength, lpBuffer, lpFilePart);
}
// TODO: Ewwwwwwww why are these globals (it's a bodge to not need free)
static char _redirected_path[MAX_PATH + 1];
static wchar_t _redirected_path_w[MAX_PATH + 1];
BOOL MiceFSRedirectPathA(LPCSTR lpFilename, LPCSTR* pszRedirected) {
CHAR szExpanded[MAX_PATH + 1];
if (!MiceFSGetFullPathNameA(lpFilename, sizeof szExpanded, szExpanded, NULL)) return FALSE;
MiceTakeLockShared(miceFsLockCount);
PMICE_FS_LAYER layer = miceFsRoot.m_Next;
CHAR szTail[MAX_PATH + 1];
wchar_t szwPath[MAX_PATH + 1];
BOOL bFound = FALSE;
while (layer) {
if (MiceFSMatchPathA(szExpanded, layer->m_lpMountPoint, szTail, sizeof szTail)) {
_redirected_path[0] = '\0';
PathCombineA(_redirected_path, layer->m_lpTargetPath, szTail);
if (pszRedirected) *pszRedirected = _redirected_path;
MultiByteToWideChar(0, 0, _redirected_path, -1, szwPath, _countof(szwPath));
if (FileExistsW(szwPath)) {
MiceReleaseLockShared(miceFsLockCount);
return TRUE;
}
bFound = TRUE;
}
layer = layer->m_Next;
}
MiceReleaseLockShared(miceFsLockCount);
return bFound;
}
// TODO: This is kinda awful, because we do two roundtrips through string conversion...
BOOL MiceFSRedirectPathW(LPCWSTR lpFileName, LPCWSTR* pszRedirected) {
char szFileName[MAX_PATH] = { 0 };
WideCharToMultiByte(CP_ACP, 0, lpFileName, wcslen(lpFileName), szFileName, sizeof szFileName,
NULL, NULL);
LPCSTR szRedirected;
BOOL bRedirected = MiceFSRedirectPathA(szFileName, &szRedirected);
if (!bRedirected) return FALSE;
MultiByteToWideChar(CP_ACP, 0, szRedirected, -1, _redirected_path_w, sizeof _redirected_path_w);
if (pszRedirected) *pszRedirected = _redirected_path_w;
return bRedirected;
}
void MiceFSDebugPrintLayers(void) {
MiceTakeLockShared(miceFsLockCount);
PMICE_FS_LAYER lpLayer = &miceFsRoot;
while (lpLayer = lpLayer->m_Next) {
printf("%s -> %s\n", lpLayer->m_lpMountPoint, lpLayer->m_lpTargetPath);
}
MiceReleaseLockShared(miceFsLockCount);
}
BOOL MiceFSDebugTraceRedirectPathA(LPCSTR lpFilename, LPCSTR* pszRedirected) {
printf("Tracing redirect of %s:\n", lpFilename);
CHAR szExpanded[MAX_PATH + 1];
if (!MiceFSGetFullPathNameA(lpFilename, sizeof szExpanded, szExpanded, NULL)) {
printf(":: Redirect ended early with no action taken.");
return FALSE;
}
MiceTakeLockShared(miceFsLockCount);
PMICE_FS_LAYER layer = miceFsRoot.m_Next;
CHAR szTail[MAX_PATH + 1];
wchar_t szwPath[MAX_PATH + 1];
BOOL bFound = FALSE;
while (layer) {
if (MiceFSMatchPathA(szExpanded, layer->m_lpMountPoint, szTail, sizeof szTail)) {
_redirected_path[0] = '\0';
PathCombineA(_redirected_path, layer->m_lpTargetPath, szTail);
if (pszRedirected) *pszRedirected = _redirected_path;
MultiByteToWideChar(0, 0, _redirected_path, -1, szwPath, _countof(szwPath));
printf("-> %s\n", _redirected_path);
if (FileExistsW(szwPath)) {
MiceReleaseLockShared(miceFsLockCount);
printf(":: Redirect ended with valid redirection found.");
return TRUE;
}
bFound = TRUE;
}
layer = layer->m_Next;
}
MiceReleaseLockShared(miceFsLockCount);
printf(":: Redirect ended with no redirection found.");
return FALSE;
}