mirror of
https://gitea.tendokyu.moe/Hay1tsme/segatools.git
synced 2026-05-07 05:45:47 -05:00
This adds full support for the Taisen series of games, namely Sangokushi Taisen and Eiketsu Taisen. Games added: * Sangokushi Taisen (SDDD) * Eiketsu Taisen (SDGY) Devices added: * CHC-320 printer (SGT) * "Printer camera" (SGT, unsure what this actually really is) * CX-7000 printer (EKT) * Y3CR BD SIE F720MM (SGT, EKT) Notable changes in the codebase: * Renamed everything printer specific to seperate between CHC and CX. * Many new function and registry hooks were added across the board. * An error is now logged when segatools.ini (or the path in `SEGATOOLS_CONFIG_PATH`) cannot be found. * Netenv now redirects UDP broadcasts targeted at the subnet that is specified in the keychip configuration. The terminal announces it's presence by broadcasting UDP to 192.168.189.255, this will be redirected to 255.255.255.255. * Vfs now seperates between absolute and relative paths in `vfs_fixup_path` via an environment variable called `SEGATOOLS_VFS_RELATIVE_PATH`. This is needed because amcapture accesses files as workingdirectory-relative. * The Y3 board emulation has support for external Y3 I/O dlls. The default implementation (y3ws) that comes with this is a websocket implementation. The docs are available under `doc\y3ws.txt` and a sample card player .html file is under `dist\ekt\card_player.html`. I already know one person that is hosting a massively improved version of it. * For websockets, my own websocket implementation is used as a subproject (MIT license): https://github.com/akechi-haruka/cwinwebsocket * For JSON, cJSON was embedded (MIT license): https://github.com/DaveGamble/cJSON * y3ws reads all printed cards from `DEVICE\print` by default including card back sides. It's up to the client to merge or skip them. Remarks: * SGT takes ~8 minutes to load. This seems to be intentional. * SGT uses some weird TCP network implementation like IDZ. I have not bothered reversing that yet and I have confirmed everything working from the test menu. * EKT will throw a network error if no terminal is found. You must run the terminal on another computer to be able to launch the satellite. * EKT has a very bizzare speed glitch that will speed up the ingame unit movement by several 1000% and also slows down cutscene animations by 90%. When this effect is active, you also take a ton more damage than usual. I do not know what causes this and it seems PC specific. * EKT is very stutter sensitive and will throw error 6401 (I/O timeout) at random when trying to alt+tab or have other things running. * EKT features a livestream system called Enbu (or "Dojo Upload"). While you are in a match, regardless of vs. AI or another player, the game will record your screen and live-stream it to the Enbu server as defined by the game server's startup response. The application responsible for that, "AM Capture" will not limit it's recording to the game window, but also anything that overlays the game window (notifications, popups, alt+tabbed windows, web browsers, etc). Since this is live-streamed, killing the process will have no effect afterwards, as the frames showing unwanted things will already have been transmitted. To make people aware of this, a one-time dialog message will pop up when starting EKT. The flag for that is stored in the DEVICE folder. Closes #25. Co-authored-by: Dniel97 <Dniel97@noreply.gitea.tendokyu.moe> Reviewed-on: https://gitea.tendokyu.moe/TeamTofuShop/segatools/pulls/85 Co-authored-by: kyoubate-haruka <46010460+kyoubate-haruka@users.noreply.github.com> Co-committed-by: kyoubate-haruka <46010460+kyoubate-haruka@users.noreply.github.com>
677 lines
17 KiB
C
677 lines
17 KiB
C
#include <windows.h>
|
|
#include <shlwapi.h>
|
|
|
|
#include <assert.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "hooklib/path.h"
|
|
#include "hooklib/reg.h"
|
|
|
|
#include "hook/procaddr.h"
|
|
#include "hook/table.h"
|
|
|
|
#include "platform/vfs.h"
|
|
|
|
#include "ewf.h"
|
|
#include "util/dprintf.h"
|
|
|
|
static void vfs_fixup_path(wchar_t *path, size_t max_count, bool use_relative_envvar);
|
|
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_path_hook_apm(
|
|
const wchar_t *src,
|
|
wchar_t *dest,
|
|
size_t *count);
|
|
static HRESULT vfs_custom_path_hook(
|
|
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* hook_System_getAppRootPath();
|
|
static wchar_t* (*next_System_getAppRootPath)();
|
|
|
|
static wchar_t* hook_AppImage_getOptionMountRootPath();
|
|
static wchar_t* (*next_AppImage_getOptionMountRootPath)();
|
|
|
|
static const struct hook_symbol amdaemon_syms[] = {
|
|
{
|
|
.name = "System_getAppRootPath",
|
|
.patch = hook_System_getAppRootPath,
|
|
.link = (void **) &next_System_getAppRootPath,
|
|
},
|
|
{
|
|
.name = "AppImage_getOptionMountRootPath",
|
|
.patch = hook_AppImage_getOptionMountRootPath,
|
|
.link = (void **) &next_AppImage_getOptionMountRootPath,
|
|
},
|
|
};
|
|
|
|
static wchar_t game[5] = {0};
|
|
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 wchar_t vfs_apm3[] = L"C:\\Mount\\Apm";
|
|
static const size_t vfs_apm3_len = _countof(vfs_apm3) - 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;
|
|
|
|
const wchar_t* get_vfs_relative_envvar() {
|
|
static wchar_t path[MAX_PATH];
|
|
if (!GetEnvironmentVariableW(L"SEGATOOLS_VFS_RELATIVE_PATH", path, MAX_PATH)) {
|
|
return NULL;
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
HRESULT vfs_hook_init(const struct vfs_config *config, const char* game_id)
|
|
{
|
|
wchar_t temp[MAX_PATH];
|
|
size_t nthome_len;
|
|
DWORD home_ok;
|
|
HRESULT hr;
|
|
|
|
assert(config != NULL);
|
|
|
|
if (!config->enable) {
|
|
return S_FALSE;
|
|
}
|
|
|
|
mbstowcs(game, game_id, 4);
|
|
|
|
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");
|
|
} else if (!PathFileExistsW(config->option)) {
|
|
dprintf("Vfs: FATAL: OPTION path does not exist\n");
|
|
dprintf(" Configured: \"%ls\"\n", config->option);
|
|
GetFullPathNameW(config->option, _countof(temp), temp, NULL);
|
|
dprintf(" Expanded: \"%ls\"\n", temp);
|
|
|
|
return E_FAIL;
|
|
} else if (!(GetFileAttributesW(config->option) & FILE_ATTRIBUTE_DIRECTORY)) {
|
|
dprintf("Vfs: FATAL: OPTION path doesn't point to a directory\n");
|
|
dprintf(" Configured: \"%ls\"\n", config->option);
|
|
GetFullPathNameW(config->option, _countof(temp), temp, NULL);
|
|
dprintf(" Expanded: \"%ls\"\n", temp);
|
|
|
|
return E_FAIL;
|
|
}
|
|
|
|
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), false);
|
|
vfs_fixup_path(vfs_config.amfs, _countof(vfs_config.amfs), true);
|
|
vfs_fixup_path(vfs_config.appdata, _countof(vfs_config.appdata), true);
|
|
|
|
if (vfs_config.option[0] != L'\0') {
|
|
vfs_fixup_path(vfs_config.option, _countof(vfs_config.option), true);
|
|
}
|
|
|
|
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 = path_hook_push(vfs_path_hook_apm);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
hr = path_hook_push(vfs_custom_path_hook);
|
|
|
|
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;
|
|
}
|
|
|
|
proc_addr_table_push(
|
|
NULL,
|
|
"amdaemon_api.dll",
|
|
amdaemon_syms,
|
|
_countof(amdaemon_syms)
|
|
);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static void vfs_fixup_path(wchar_t *path, size_t max_count, bool use_adjustment_envvar)
|
|
{
|
|
size_t count;
|
|
wchar_t abspath[MAX_PATH];
|
|
|
|
assert(path != NULL);
|
|
/* Requirement for PathIsRelativeW */
|
|
assert(max_count <= MAX_PATH);
|
|
|
|
if (PathIsRelativeW(path)) {
|
|
const wchar_t* append = get_vfs_relative_envvar();
|
|
if (append != NULL && wcslen(append) > 0 && use_adjustment_envvar) {
|
|
wchar_t temp[MAX_PATH];
|
|
swprintf_s(temp, MAX_PATH, L"%ls\\%ls", append, path);
|
|
count = GetFullPathNameW(temp, _countof(abspath), abspath, NULL);
|
|
} else {
|
|
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'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_path_hook_apm(
|
|
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_apm */
|
|
|
|
if (path_compare_w(src, vfs_apm3, vfs_apm3_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_apm3_len]) &&
|
|
src[vfs_apm3_len] != L'\0')
|
|
{
|
|
return S_FALSE;
|
|
}
|
|
|
|
/* Cut off the matched <prefix>\, add the replaced prefix, count NUL */
|
|
|
|
shift = path_is_separator_w(src[vfs_apm3_len]) ? 1 : 0;
|
|
redir_len = wcslen(vfs_config.option);
|
|
required = wcslen(src) - vfs_apm3_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_apm3_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:\\");
|
|
}
|
|
|
|
static wchar_t* hook_System_getAppRootPath()
|
|
{
|
|
wchar_t *path = malloc(sizeof(wchar_t) * MAX_PATH);
|
|
wcscpy_s(path, MAX_PATH, vfs_config.appdata);
|
|
wcscat_s(path, MAX_PATH, game);
|
|
wcscat_s(path, MAX_PATH, L"\\");
|
|
|
|
return path;
|
|
}
|
|
|
|
static wchar_t* hook_AppImage_getOptionMountRootPath()
|
|
{
|
|
wchar_t *path = malloc(sizeof(wchar_t) * MAX_PATH);
|
|
wcscpy_s(path, MAX_PATH, vfs_config.option);
|
|
|
|
return path;
|
|
}
|
|
|
|
static HRESULT vfs_custom_path_hook(
|
|
const wchar_t *src,
|
|
wchar_t *dest,
|
|
size_t *count){
|
|
|
|
assert(src != NULL);
|
|
assert(count != NULL);
|
|
|
|
/* Case-insensitive check to see if src starts with one of our custom paths */
|
|
|
|
for (int i = 0; i < MAX_REDIRECTIONS; i++){
|
|
|
|
wchar_t* from = vfs_config.redirections_from[i];
|
|
wchar_t* to = vfs_config.redirections_to[i];
|
|
|
|
if (from[0] == '\0' || to[0] == '\0'){
|
|
return S_FALSE;
|
|
}
|
|
|
|
if (path_compare_w(src, from, vfs_config.redirections_from_len[i]) != 0) {
|
|
continue;
|
|
}
|
|
|
|
size_t required = wcslen(to) + 1;
|
|
|
|
#if defined(LOG_CUSTOM_VFS)
|
|
dprintf("Vfs: Redirection matched: %ls -> %ls\n", from, to);
|
|
#endif
|
|
|
|
if (dest != NULL) {
|
|
|
|
if (required > *count) {
|
|
return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
|
|
}
|
|
|
|
wcscpy_s(dest, *count, to);
|
|
}
|
|
|
|
*count = required;
|
|
|
|
break;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|