Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/gamepatches/boot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ UINT64 PreprocessCommandLineHook(PVOID pGame) {
g_timestampLogs = TRUE;
} else if (lstrcmpW(arg, L"-upnp") == 0) {
g_upnpEnabled = TRUE;
} else if (lstrcmpW(arg, L"-legacy-log-names") == 0) {
g_legacyLogNames = TRUE;
} else if (lstrcmpW(arg, L"-config") == 0 || lstrcmpW(arg, L"-config-path") == 0) {
if (i + 1 < argc) {
WideCharToMultiByte(CP_UTF8, 0, argv[i + 1], -1, g_customConfigPath, MAX_PATH, NULL, NULL);
Expand Down
4 changes: 4 additions & 0 deletions src/gamepatches/cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
BOOL g_isServer = FALSE;
BOOL g_isOffline = FALSE;
BOOL g_isWindowed = FALSE;
BOOL g_legacyLogNames = FALSE;
CHAR g_customConfigPath[MAX_PATH] = {0};
CHAR g_regionOverride[64] = {0};

Expand Down Expand Up @@ -53,6 +54,9 @@ UINT64 BuildCmdLineSyntaxDefinitionsHook(PVOID pGame, PVOID pArgSyntax) {
EchoVR::AddArgSyntax(pArgSyntax, "-upnp", 0, 0, FALSE);
EchoVR::AddArgHelpString(pArgSyntax, "-upnp", "[NEVR] Enable UPnP port forwarding");

EchoVR::AddArgSyntax(pArgSyntax, "-legacy-log-names", 0, 0, FALSE);
EchoVR::AddArgHelpString(pArgSyntax, "-legacy-log-names", "[NEVR] Keep EchoVR native log filename format ([r14(server)]-[date]_[time]_N.log)");

// Backwards compat: accept deprecated flags without error (they're silently ignored)
EchoVR::AddArgSyntax(pArgSyntax, "-timestep", 1, 1, FALSE);
EchoVR::AddArgSyntax(pArgSyntax, "-fixedtimestep", 0, 0, FALSE);
Expand Down
5 changes: 5 additions & 0 deletions src/gamepatches/cli.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ extern CHAR g_customConfigPath[MAX_PATH];
/// Region override from -region or -serverregion CLI args. Empty = default.
extern CHAR g_regionOverride[64];

/// When TRUE, game log files in _local\r14logs\ keep the native EchoVR naming
/// convention ([r14(server)]-[MM-DD-YYYY]_...). When FALSE (default), NEVR
/// redirects them to nevr-server-YYYYMMDD-HHMMSS.log.
extern BOOL g_legacyLogNames;

/// A detour hook for the game's method it uses to build CLI argument definitions.
/// Adds additional definitions to the structure, so that they may be parsed successfully without error.
UINT64 BuildCmdLineSyntaxDefinitionsHook(PVOID pGame, PVOID pArgSyntax);
61 changes: 61 additions & 0 deletions src/gamepatches/mode_patches.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,49 @@ static VOID BugSplatCrashHandlerHook(INT64 exitCode) {
OriginalBugSplatCrashHandler(exitCode);
}

// ============================================================================
// Game log file rename hook
// ============================================================================
//
// The EchoVR logging system writes to _local\r14logs\[r14(server)]-[MM-DD-YYYY]_[HH-MM-SS]_N.log.
// This hook intercepts CreateFileA calls that target that directory and redirects
// them to nevr-server-YYYYMMDD-HHMMSS.log, which is sortable and grep-friendly.
// Active only in server mode and only when -legacy-log-names is not set.

typedef HANDLE (WINAPI *CreateFileA_t)(LPCSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE);
static CreateFileA_t orig_CreateFileA = nullptr;

static bool IsGameLogPath(const char* path) {
if (!path) return false;
const char* p = strstr(path, "r14logs");
if (!p) return false;
p += 7;
if (*p == '\\' || *p == '/') p++;
return p[0] == '['; // native EVR format starts with '[r14...'
}

static HANDLE WINAPI CreateFileAHook(
LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes, HANDLE hTemplateFile)
{
if (IsGameLogPath(lpFileName)) {
const char* r14 = strstr(lpFileName, "r14logs");
std::string dir(lpFileName, static_cast<size_t>(r14 - lpFileName) + 7);

char ts[16];
SYSTEMTIME st;
GetSystemTime(&st);
snprintf(ts, sizeof(ts), "%04d%02d%02d-%02d%02d%02d", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);

std::string newPath = dir + "\\nevr-server-" + ts + ".log";
Log(EchoVR::LogLevel::Info,
"[NEVR.PATCH] Renaming game log: %s -> %s", lpFileName, newPath.c_str());
return orig_CreateFileA(newPath.c_str(), dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
}
return orig_CreateFileA(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
}

// ============================================================================
// PatchEnableServer — force dedicated server mode
// ============================================================================
Expand Down Expand Up @@ -354,6 +397,24 @@ VOID PatchEnableServer() {
static_assert(sizeof(spectatorStreamCheck) == SPECTATORSTREAM_CHECK_SIZE,
"SPECTATORSTREAM_CHECK patch size mismatch");
ApplyPatch(SPECTATORSTREAM_CHECK, spectatorStreamCheck, sizeof(spectatorStreamCheck));

// Redirect game log filenames from the native EchoVR bracketed format to
// nevr-server-YYYYMMDD-HHMMSS.log, unless -legacy-log-names was passed.
if (!g_legacyLogNames) {
PVOID pTarget = reinterpret_cast<PVOID>(
GetProcAddress(GetModuleHandleA("kernel32"), "CreateFileA"));
if (pTarget) {
orig_CreateFileA = reinterpret_cast<CreateFileA_t>(pTarget);
if (Hooking::Attach(&pTarget, reinterpret_cast<PVOID>(CreateFileAHook))) {
orig_CreateFileA = reinterpret_cast<CreateFileA_t>(pTarget);
Log(EchoVR::LogLevel::Info,
"[NEVR.PATCH] Game log rename active — files will use nevr-server-YYYYMMDD-HHMMSS.log");
} else {
Log(EchoVR::LogLevel::Warning, "[NEVR.PATCH] Failed to hook CreateFileA for log rename");
orig_CreateFileA = nullptr;
}
}
}
}

// ============================================================================
Expand Down
3 changes: 2 additions & 1 deletion tests/system/patches_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ func TestPatches_ServerMode(t *testing.T) {
// - Server broadcast port is exposed
// - Loading tips system is disabled (reduces log spam)
// - "allow_incoming" is forced to true (accepts connections)
// - Log files use "r14(server)" subject instead of "r14netserver"
// - Game log files in _local\r14logs\ are named nevr-server-YYYYMMDD-HHMMSS.log
// (pass -legacy-log-names to revert to [r14(server)]-[MM-DD-YYYY]_[HH-MM-SS]_N.log)
//
// Verification approach:
// 1. Start game with: echovr.exe -server -headless
Expand Down