forked from Dniel97/segatools
482 lines
11 KiB
C
482 lines
11 KiB
C
#include <windows.h>
|
|
#include <shlwapi.h>
|
|
|
|
#include <assert.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "hooklib/path.h"
|
|
#include "hooklib/reg.h"
|
|
|
|
#include "platform/vfs.h"
|
|
|
|
#include "util/dprintf.h"
|
|
|
|
static void vfs_fixup_path(wchar_t *path, size_t max_count);
|
|
static HRESULT vfs_mkdir_rec(const wchar_t *path);
|
|
static HRESULT vfs_path_hook(const wchar_t *src, wchar_t *dest, size_t *count);
|
|
static HRESULT vfs_path_hook_nthome(
|
|
const wchar_t *src,
|
|
wchar_t *dest,
|
|
size_t *count);
|
|
static HRESULT vfs_path_hook_w10home(
|
|
const wchar_t *src,
|
|
wchar_t *dest,
|
|
size_t *count);
|
|
static HRESULT vfs_path_hook_option(
|
|
const wchar_t *src,
|
|
wchar_t *dest,
|
|
size_t *count);
|
|
static HRESULT vfs_reg_read_amfs(void *bytes, uint32_t *nbytes);
|
|
static HRESULT vfs_reg_read_appdata(void *bytes, uint32_t *nbytes);
|
|
|
|
static wchar_t vfs_nthome_real[MAX_PATH];
|
|
static const wchar_t vfs_nthome[] = L"C:\\Documents and Settings\\AppUser";
|
|
static const size_t vfs_nthome_len = _countof(vfs_nthome) - 1;
|
|
|
|
static const wchar_t vfs_w10home[] = L"C:\\Users\\AppUser";
|
|
static const size_t vfs_w10home_len = _countof(vfs_w10home) - 1;
|
|
|
|
static const wchar_t vfs_option[] = L"C:\\Mount\\Option";
|
|
static const size_t vfs_option_len = _countof(vfs_option) - 1;
|
|
|
|
static const struct reg_hook_val vfs_reg_vals[] = {
|
|
{
|
|
.name = L"AMFS",
|
|
.read = vfs_reg_read_amfs,
|
|
.type = REG_SZ,
|
|
}, {
|
|
.name = L"APPDATA",
|
|
.read = vfs_reg_read_appdata,
|
|
.type = REG_SZ
|
|
},
|
|
};
|
|
|
|
static struct vfs_config vfs_config;
|
|
|
|
HRESULT vfs_hook_init(const struct vfs_config *config)
|
|
{
|
|
wchar_t temp[MAX_PATH];
|
|
size_t nthome_len;
|
|
DWORD home_ok;
|
|
HRESULT hr;
|
|
|
|
assert(config != NULL);
|
|
|
|
if (!config->enable) {
|
|
return S_FALSE;
|
|
}
|
|
|
|
if (config->amfs[0] == L'\0') {
|
|
dprintf("Vfs: FATAL: AMFS path not specified in INI file\n");
|
|
|
|
return E_FAIL;
|
|
}
|
|
|
|
if (config->appdata[0] == L'\0') {
|
|
dprintf("Vfs: FATAL: APPDATA path not specified in INI file\n");
|
|
|
|
return E_FAIL;
|
|
}
|
|
|
|
if (config->option[0] == L'\0') {
|
|
dprintf("Vfs: WARNING: OPTION path not specified in INI file\n");
|
|
}
|
|
|
|
home_ok = GetEnvironmentVariableW(
|
|
L"USERPROFILE",
|
|
vfs_nthome_real,
|
|
_countof(vfs_nthome_real));
|
|
|
|
if (!home_ok) {
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
dprintf("Vfs: Failed to query %%USERPROFILE%% env var: %x\n",
|
|
(int) hr);
|
|
|
|
return hr;
|
|
}
|
|
|
|
memcpy(&vfs_config, config, sizeof(*config));
|
|
|
|
vfs_fixup_path(vfs_nthome_real, _countof(vfs_nthome_real));
|
|
vfs_fixup_path(vfs_config.amfs, _countof(vfs_config.amfs));
|
|
vfs_fixup_path(vfs_config.appdata, _countof(vfs_config.appdata));
|
|
|
|
if (vfs_config.option[0] != L'\0') {
|
|
vfs_fixup_path(vfs_config.option, _countof(vfs_config.option));
|
|
}
|
|
|
|
hr = vfs_mkdir_rec(vfs_config.amfs);
|
|
|
|
if (FAILED(hr)) {
|
|
dprintf("Vfs: Failed to create AMFS dir %S: %x\n",
|
|
config->amfs,
|
|
(int) hr);
|
|
}
|
|
|
|
hr = vfs_mkdir_rec(vfs_config.appdata);
|
|
|
|
if (FAILED(hr)) {
|
|
dprintf("Vfs: Failed to create APPDATA dir %S: %x\n",
|
|
config->appdata,
|
|
(int) hr);
|
|
|
|
dprintf("Vfs: NOTE: SEGA Y: drive APPDATA, not Windows %%APPDATA%%.\n");
|
|
}
|
|
|
|
/* Need to create the temp subdirectory, not just nthome itself */
|
|
|
|
nthome_len = wcslen(vfs_nthome_real);
|
|
wcscpy_s(temp, _countof(temp), vfs_nthome_real);
|
|
wcscpy_s(temp + nthome_len, _countof(temp) - nthome_len, L"temp");
|
|
|
|
hr = vfs_mkdir_rec(temp);
|
|
|
|
if (FAILED(hr)) {
|
|
dprintf("Vfs: Failed to create %S: %x\n", temp, (int) hr);
|
|
}
|
|
|
|
/* Not auto-creating option directory as it is normally a read-only mount */
|
|
|
|
hr = path_hook_push(vfs_path_hook);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
hr = path_hook_push(vfs_path_hook_nthome);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
hr = path_hook_push(vfs_path_hook_w10home);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
if (vfs_config.option[0] != L'\0') {
|
|
hr = path_hook_push(vfs_path_hook_option);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
hr = reg_hook_push_key(
|
|
HKEY_LOCAL_MACHINE,
|
|
L"SYSTEM\\SEGA\\SystemProperty\\mount",
|
|
vfs_reg_vals,
|
|
_countof(vfs_reg_vals));
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static void vfs_fixup_path(wchar_t *path, size_t max_count)
|
|
{
|
|
size_t count;
|
|
wchar_t abspath[MAX_PATH];
|
|
|
|
assert(path != NULL);
|
|
/* Requirement for PathIsRelativeW */
|
|
assert(max_count <= MAX_PATH);
|
|
|
|
if (PathIsRelativeW(path)) {
|
|
count = GetFullPathNameW(path, _countof(abspath), abspath, NULL);
|
|
|
|
/* GetFullPathName's length return value is tricky, because it includes
|
|
the NUL terminator on failure, but doesn't on success.
|
|
Check if it fits the temp buf (else it's a failure and includes NUL
|
|
anyway), then if it fits the target buf, NUL included. */
|
|
if (count == 0 || count > _countof(abspath) || count >= max_count) {
|
|
goto fail;
|
|
}
|
|
|
|
wcscpy_s(path, max_count, abspath);
|
|
} else {
|
|
count = wcslen(path);
|
|
}
|
|
|
|
if (path_is_separator_w(path[count - 1])) {
|
|
return;
|
|
}
|
|
|
|
if (count + 2 > max_count) {
|
|
goto fail;
|
|
}
|
|
|
|
path[count + 0] = L'\\';
|
|
path[count + 1] = L'\0';
|
|
return;
|
|
|
|
fail:
|
|
dprintf("Vfs: FATAL: Path too long: %S\n", path);
|
|
abort();
|
|
}
|
|
|
|
static HRESULT vfs_mkdir_rec(const wchar_t *path)
|
|
{
|
|
wchar_t *copy;
|
|
wchar_t *pos;
|
|
wchar_t wc;
|
|
HRESULT hr;
|
|
DWORD attr;
|
|
BOOL ok;
|
|
|
|
assert(path != NULL);
|
|
|
|
copy = _wcsdup(path);
|
|
|
|
if (copy == NULL) {
|
|
hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY);
|
|
|
|
goto end;
|
|
}
|
|
|
|
pos = copy;
|
|
|
|
do {
|
|
wc = *pos;
|
|
|
|
if (wc == L'\0' || wc == L'/' || wc == L'\\') {
|
|
*pos = L'\0';
|
|
attr = GetFileAttributesW(copy);
|
|
|
|
if (attr == INVALID_FILE_ATTRIBUTES) {
|
|
ok = CreateDirectoryW(copy, NULL);
|
|
|
|
if (!ok) {
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
*pos = wc;
|
|
}
|
|
|
|
pos++;
|
|
} while (wc != L'\0');
|
|
|
|
hr = S_OK;
|
|
|
|
end:
|
|
free(copy);
|
|
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT vfs_path_hook(const wchar_t *src, wchar_t *dest, size_t *count)
|
|
{
|
|
const wchar_t *redir;
|
|
size_t required;
|
|
size_t redir_len;
|
|
size_t src_len;
|
|
|
|
assert(src != NULL);
|
|
assert(count != NULL);
|
|
|
|
if (src[0] == L'\0' || src[1] != L':') {
|
|
return S_FALSE;
|
|
}
|
|
|
|
switch (src[0]) {
|
|
// case L'D': // later AMDaemon versions default to D: for AMFS if it can't find it
|
|
// case L'd':
|
|
case L'e':
|
|
case L'E':
|
|
redir = vfs_config.amfs;
|
|
|
|
break;
|
|
|
|
case L'y':
|
|
case L'Y':
|
|
redir = vfs_config.appdata;
|
|
|
|
break;
|
|
|
|
default:
|
|
return S_FALSE;
|
|
}
|
|
|
|
/* GetFileAttributesW would request the src "E:", so fix the src_len in
|
|
in order to redirect the drive letter successfully */
|
|
|
|
src_len = path_is_separator_w(src[2]) ? 3 : 2;
|
|
|
|
/* Cut off <prefix>\, replace with redir path, count NUL terminator */
|
|
|
|
redir_len = wcslen(redir);
|
|
required = wcslen(src) - src_len + redir_len + 1;
|
|
|
|
if (dest != NULL) {
|
|
if (required > *count) {
|
|
return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
|
|
}
|
|
|
|
wcscpy_s(dest, *count, redir);
|
|
wcscpy_s(dest + redir_len, *count - redir_len, src + src_len);
|
|
}
|
|
|
|
*count = required;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT vfs_path_hook_nthome(
|
|
const wchar_t *src,
|
|
wchar_t *dest,
|
|
size_t *count)
|
|
{
|
|
size_t required;
|
|
size_t redir_len;
|
|
size_t shift;
|
|
|
|
assert(src != NULL);
|
|
assert(count != NULL);
|
|
|
|
/* Case-insensitive check to see if src starts with vfs_nthome */
|
|
|
|
if (path_compare_w(src, vfs_nthome, vfs_nthome_len) != 0) {
|
|
return S_FALSE;
|
|
}
|
|
|
|
/* Check if the character after vfs_nthome is a separator or the end of
|
|
the string */
|
|
|
|
if (!path_is_separator_w(src[vfs_nthome_len]) &&
|
|
src[vfs_nthome_len] != L'\0')
|
|
{
|
|
return S_FALSE;
|
|
}
|
|
|
|
/* Cut off the matched <prefix>\, add the replaced prefix, count NUL */
|
|
|
|
shift = path_is_separator_w(src[vfs_nthome_len]) ? 1 : 0;
|
|
redir_len = wcslen(vfs_nthome_real);
|
|
required = wcslen(src) - vfs_nthome_len - shift + redir_len + 1;
|
|
|
|
if (dest != NULL) {
|
|
if (required > *count) {
|
|
return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
|
|
}
|
|
|
|
wcscpy_s(dest, *count, vfs_nthome_real);
|
|
wcscpy_s(dest + redir_len, *count - redir_len, src + vfs_nthome_len + shift);
|
|
}
|
|
|
|
*count = required;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT vfs_path_hook_w10home(
|
|
const wchar_t *src,
|
|
wchar_t *dest,
|
|
size_t *count)
|
|
{
|
|
size_t required;
|
|
size_t redir_len;
|
|
size_t shift;
|
|
|
|
assert(src != NULL);
|
|
assert(count != NULL);
|
|
|
|
/* Case-insensitive check to see if src starts with vfs_w10home */
|
|
|
|
if (path_compare_w(src, vfs_w10home, vfs_w10home_len) != 0) {
|
|
return S_FALSE;
|
|
}
|
|
|
|
/* Check if the character after vfs_w10home is a separator or the end of
|
|
the string */
|
|
|
|
if (!path_is_separator_w(src[vfs_w10home_len]) &&
|
|
src[vfs_w10home_len] != L'\0')
|
|
{
|
|
return S_FALSE;
|
|
}
|
|
|
|
/* Cut off the matched <prefix>\, add the replaced prefix, count NUL */
|
|
|
|
shift = path_is_separator_w(src[vfs_w10home_len]) ? 1 : 0;
|
|
redir_len = wcslen(vfs_nthome_real);
|
|
required = wcslen(src) - vfs_w10home_len - shift + redir_len + 1;
|
|
|
|
if (dest != NULL) {
|
|
if (required > *count) {
|
|
return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
|
|
}
|
|
|
|
wcscpy_s(dest, *count, vfs_nthome_real);
|
|
wcscpy_s(dest + redir_len, *count - redir_len, src + vfs_w10home_len + shift);
|
|
}
|
|
|
|
*count = required;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT vfs_path_hook_option(
|
|
const wchar_t *src,
|
|
wchar_t *dest,
|
|
size_t *count)
|
|
{
|
|
size_t required;
|
|
size_t redir_len;
|
|
size_t shift;
|
|
|
|
assert(src != NULL);
|
|
assert(count != NULL);
|
|
|
|
/* Case-insensitive check to see if src starts with vfs_option */
|
|
|
|
if (path_compare_w(src, vfs_option, vfs_option_len) != 0) {
|
|
return S_FALSE;
|
|
}
|
|
|
|
/* Check if the character after vfs_nthome is a separator or the end of
|
|
the string */
|
|
|
|
if (!path_is_separator_w(src[vfs_option_len]) &&
|
|
src[vfs_option_len] != L'\0')
|
|
{
|
|
return S_FALSE;
|
|
}
|
|
|
|
/* Cut off the matched <prefix>\, add the replaced prefix, count NUL */
|
|
|
|
shift = path_is_separator_w(src[vfs_option_len]) ? 1 : 0;
|
|
redir_len = wcslen(vfs_config.option);
|
|
required = wcslen(src) - vfs_option_len - shift + redir_len + 1;
|
|
|
|
if (dest != NULL) {
|
|
if (required > *count) {
|
|
return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
|
|
}
|
|
|
|
wcscpy_s(dest, *count, vfs_config.option);
|
|
wcscpy_s(dest + redir_len, *count - redir_len, src + vfs_option_len + shift);
|
|
}
|
|
|
|
*count = required;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT vfs_reg_read_amfs(void *bytes, uint32_t *nbytes)
|
|
{
|
|
return reg_hook_read_wstr(bytes, nbytes, L"E:\\");
|
|
}
|
|
|
|
static HRESULT vfs_reg_read_appdata(void *bytes, uint32_t *nbytes)
|
|
{
|
|
return reg_hook_read_wstr(bytes, nbytes, L"Y:\\");
|
|
}
|