feat: Implement LCELive username handling and logging for Windows64 client

This commit is contained in:
veroxsity
2026-04-25 21:08:29 +01:00
parent 4d054cb5b1
commit aaf3ad2f99
6 changed files with 207 additions and 4 deletions

View File

@@ -48,6 +48,104 @@
#include "../DLCTexturePack.h"
#include "DLC/DLCPack.h"
#include "../StringTable.h"
#if defined(_WINDOWS64) && !defined(MINECRAFT_SERVER_BUILD)
namespace
{
FILE *g_clientDebugLogFile = nullptr;
CRITICAL_SECTION g_clientDebugLogLock;
INIT_ONCE g_clientDebugLogOnce = INIT_ONCE_STATIC_INIT;
static bool BuildExeRelativeLogPath(const char *fileName, char *outPath, size_t outPathSize)
{
if (fileName == nullptr || outPath == nullptr || outPathSize == 0)
return false;
outPath[0] = 0;
char exePath[MAX_PATH] = {};
DWORD len = GetModuleFileNameA(nullptr, exePath, MAX_PATH);
if (len == 0 || len >= MAX_PATH)
return false;
char *lastSlash = strrchr(exePath, '\\');
if (lastSlash != nullptr)
*(lastSlash + 1) = 0;
if (strcpy_s(outPath, outPathSize, exePath) != 0)
return false;
if (strcat_s(outPath, outPathSize, "logs\\") != 0)
return false;
CreateDirectoryA(outPath, nullptr);
if (strcat_s(outPath, outPathSize, fileName) != 0)
return false;
return true;
}
static bool OpenClientDebugLog()
{
char logPath[MAX_PATH] = {};
char previousPath[MAX_PATH] = {};
if (!BuildExeRelativeLogPath("client.log", logPath, sizeof(logPath)) ||
!BuildExeRelativeLogPath("client.previous.log", previousPath, sizeof(previousPath)))
{
strcpy_s(logPath, sizeof(logPath), "client.log");
strcpy_s(previousPath, sizeof(previousPath), "client.previous.log");
}
DeleteFileA(previousPath);
MoveFileExA(logPath, previousPath, MOVEFILE_REPLACE_EXISTING);
if (fopen_s(&g_clientDebugLogFile, logPath, "w") != 0 || g_clientDebugLogFile == nullptr)
{
if (fopen_s(&g_clientDebugLogFile, "client.log", "w") != 0)
g_clientDebugLogFile = nullptr;
}
if (g_clientDebugLogFile == nullptr)
return false;
fprintf(g_clientDebugLogFile, "Minecraft client debug log started. pid=%lu\n",
static_cast<unsigned long>(GetCurrentProcessId()));
fflush(g_clientDebugLogFile);
return true;
}
static BOOL CALLBACK InitializeClientDebugLog(PINIT_ONCE, PVOID, PVOID *)
{
InitializeCriticalSection(&g_clientDebugLogLock);
OpenClientDebugLog();
return TRUE;
}
static void WriteClientDebugLogLine(const char *message)
{
if (message == nullptr || message[0] == 0)
return;
InitOnceExecuteOnce(&g_clientDebugLogOnce, &InitializeClientDebugLog, nullptr, nullptr);
if (g_clientDebugLogFile == nullptr)
return;
EnterCriticalSection(&g_clientDebugLogLock);
fprintf(g_clientDebugLogFile, "[%10llu][T%lu] %s",
static_cast<unsigned long long>(GetTickCount64()),
static_cast<unsigned long>(GetCurrentThreadId()),
message);
const size_t length = strlen(message);
if (length == 0 || (message[length - 1] != '\n' && message[length - 1] != '\r'))
fputc('\n', g_clientDebugLogFile);
fflush(g_clientDebugLogFile);
LeaveCriticalSection(&g_clientDebugLogLock);
}
}
#endif
#ifndef _XBOX
#include "../ArchiveFile.h"
#endif
@@ -243,6 +341,15 @@ CMinecraftApp::CMinecraftApp()
void CMinecraftApp::DebugPrintf(const char *szFormat, ...)
{
#if defined(_WINDOWS64) && !defined(MINECRAFT_SERVER_BUILD)
va_list fileAp;
va_start(fileAp, szFormat);
char fileBuf[1024];
vsnprintf(fileBuf, sizeof(fileBuf), szFormat, fileAp);
va_end(fileAp);
WriteClientDebugLogLine(fileBuf);
#endif
#ifndef _FINAL_BUILD
va_list ap;
va_start(ap, szFormat);
@@ -265,9 +372,22 @@ void CMinecraftApp::DebugPrintf(const char *szFormat, ...)
void CMinecraftApp::DebugPrintf(int user, const char *szFormat, ...)
{
#ifndef _FINAL_BUILD
if(user == USER_NONE)
return;
#if defined(_WINDOWS64) && !defined(MINECRAFT_SERVER_BUILD)
va_list fileAp;
va_start(fileAp, szFormat);
char fileBuf[1024];
vsnprintf(fileBuf, sizeof(fileBuf), szFormat, fileAp);
va_end(fileAp);
char taggedFileBuf[1100];
_snprintf_s(taggedFileBuf, sizeof(taggedFileBuf), _TRUNCATE, "[user=%d] %s", user, fileBuf);
WriteClientDebugLogLine(taggedFileBuf);
#endif
#ifndef _FINAL_BUILD
va_list ap;
va_start(ap, szFormat);
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)

View File

@@ -1110,6 +1110,22 @@ namespace Win64LceLive
return token;
}
bool GetSignedInUsername(std::wstring *outUsername)
{
if (outUsername == nullptr)
return false;
EnsureInitialized();
outUsername->clear();
EnterCriticalSection(&g_state.lock);
const bool hasUsername = g_state.session.valid && !g_state.session.username.empty();
if (hasUsername)
*outUsername = Utf8ToWide(g_state.session.username);
LeaveCriticalSection(&g_state.lock);
return hasUsername;
}
TicketResult RequestJoinTicketSync()
{
EnsureInitialized();

View File

@@ -114,6 +114,10 @@ namespace Win64LceLive
// Synchronous — reads from cached state, never blocks.
std::string GetAccessToken();
// Returns the signed-in LCELive username if a cached session is active.
// Synchronous; reads from cached state, never blocks.
bool GetSignedInUsername(std::wstring* outUsername);
// Requests a short-lived join ticket from the LceLive API.
// Synchronous blocking call (~localhost RTT). Call from a non-UI thread.
TicketResult RequestJoinTicketSync();

View File

@@ -205,6 +205,9 @@ static bool g_bResizeReady = false;
char g_Win64Username[17] = { 0 };
wchar_t g_Win64UsernameW[17] = { 0 };
static char g_Win64FallbackUsername[17] = { 0 };
static const char *g_Win64FallbackUsernameSource = "default";
static bool g_Win64NameLockedToLaunchOverride = false;
// Fullscreen toggle state
static bool g_isFullscreen = false;
@@ -214,6 +217,7 @@ struct Win64LaunchOptions
{
int screenMode;
bool fullscreen;
bool usernameOverride;
};
static void CopyWideArgToAnsi(LPCWSTR source, char* dest, size_t destSize)
@@ -229,6 +233,53 @@ static void CopyWideArgToAnsi(LPCWSTR source, char* dest, size_t destSize)
dest[destSize - 1] = 0;
}
static void ApplyWin64Username(const char *username, const char *source, bool updateRuntime)
{
if (username == nullptr || username[0] == 0)
return;
char nextUsername[sizeof(g_Win64Username)] = {};
strncpy_s(nextUsername, sizeof(nextUsername), username, _TRUNCATE);
if (nextUsername[0] == 0)
return;
const bool changed = strcmp(g_Win64Username, nextUsername) != 0;
strncpy_s(g_Win64Username, sizeof(g_Win64Username), nextUsername, _TRUNCATE);
MultiByteToWideChar(CP_ACP, 0, g_Win64Username, -1, g_Win64UsernameW, static_cast<int>(_countof(g_Win64UsernameW)));
if (changed)
GameLog("[IDENTITY] Using %s username: %s\n", source != nullptr ? source : "configured", g_Win64Username);
if (updateRuntime)
{
wcsncpy_s(IQNet::m_player[0].m_gamertag, 32, g_Win64UsernameW, _TRUNCATE);
Minecraft *minecraft = Minecraft::GetInstance();
if (minecraft != nullptr && minecraft->user != nullptr)
minecraft->user->name = g_Win64UsernameW;
}
}
static void RefreshWin64UsernameFromIdentity(bool updateRuntime)
{
if (!g_Win64NameLockedToLaunchOverride)
{
std::wstring lceLiveUsername;
if (Win64LceLive::GetSignedInUsername(&lceLiveUsername) && !lceLiveUsername.empty())
{
char lceLiveUsernameAnsi[sizeof(g_Win64Username)] = {};
CopyWideArgToAnsi(lceLiveUsername.c_str(), lceLiveUsernameAnsi, sizeof(lceLiveUsernameAnsi));
if (lceLiveUsernameAnsi[0] != 0)
{
ApplyWin64Username(lceLiveUsernameAnsi, "LCELive", updateRuntime);
return;
}
}
}
ApplyWin64Username(g_Win64FallbackUsername, g_Win64FallbackUsernameSource, updateRuntime);
}
// ---------- Persistent options (options.txt next to exe) ----------
static void GetOptionsFilePath(char *out, size_t outSize)
{
@@ -318,6 +369,7 @@ static Win64LaunchOptions ParseLaunchOptions()
if (_wcsicmp(argv[i], L"-name") == 0 && (i + 1) < argc)
{
CopyWideArgToAnsi(argv[++i], g_Win64Username, sizeof(g_Win64Username));
options.usernameOverride = g_Win64Username[0] != 0;
}
else if (_wcsicmp(argv[i], L"-ip") == 0 && (i + 1) < argc)
{
@@ -1535,6 +1587,7 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
if (len > 0)
{
strncpy_s(g_Win64Username, sizeof(g_Win64Username), buf, _TRUNCATE);
g_Win64FallbackUsernameSource = "username.txt";
}
}
fclose(f);
@@ -1542,6 +1595,9 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
// Load stuff from launch options, including username
const Win64LaunchOptions launchOptions = ParseLaunchOptions();
g_Win64NameLockedToLaunchOverride = launchOptions.usernameOverride;
if (launchOptions.usernameOverride)
g_Win64FallbackUsernameSource = "launch argument";
ApplyScreenMode(launchOptions.screenMode);
// Ensure uid.dat exists from startup (before any multiplayer/login path).
@@ -1552,9 +1608,11 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
{
// Default username will be "Player"
strncpy_s(g_Win64Username, sizeof(g_Win64Username), "Player", _TRUNCATE);
g_Win64FallbackUsernameSource = "default";
}
MultiByteToWideChar(CP_ACP, 0, g_Win64Username, -1, g_Win64UsernameW, 17);
strncpy_s(g_Win64FallbackUsername, sizeof(g_Win64FallbackUsername), g_Win64Username, _TRUNCATE);
RefreshWin64UsernameFromIdentity(false);
// convert servers.txt to servers.db
if (GetFileAttributesA("servers.txt") != INVALID_FILE_ATTRIBUTES &&
@@ -1855,6 +1913,7 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
// LceLive platform service ticks — keep auth token fresh, drive P2P + signaling.
Win64LceLive::Tick();
RefreshWin64UsernameFromIdentity(true);
Win64LceLiveP2P::P2PTick();
Win64LceLiveSignaling::Tick();

View File

@@ -103,6 +103,8 @@ Important: launch from the output directory so relative asset paths resolve corr
| `-name <username>` | Override in-game username |
| `-fullscreen` | Launch in fullscreen |
When signed in to LCELive, the Windows64 client uses your LCELive username in game. If you are signed out, it falls back to `username.txt`, then `Player`. The `-name` launch argument still overrides both for local testing.
Example:
```text
@@ -121,10 +123,12 @@ Logs are written next to the executable in `logs`:
- `logs/game.log`
- `logs/game.previous.log`
- `logs/client.log`
- `logs/client.previous.log`
- `logs/lcelive.log`
- `logs/lcelive.previous.log`
If networking fails, inspect `logs/lcelive.log` first.
`client.log` is enabled for Windows64 client release builds too. It captures the game-wide `app.DebugPrintf` stream: world, packet, storage, audio, UI, and gameplay diagnostics. If networking fails, inspect `logs/lcelive.log` first.
## Troubleshooting

View File

@@ -1,7 +1,7 @@
#pragma once
#define VER_PRODUCTBUILD 560
#define VER_PRODUCTVERSION_STR_W L"1.1.0.0"
#define VER_PRODUCTVERSION_STR_W L"1.2.0.0"
#define VER_FILEVERSION_STR_W VER_PRODUCTVERSION_STR_W
#define VER_BRANCHVERSION_STR_W L"UNKNOWN BRANCH"
#define VER_NETWORK VER_PRODUCTBUILD