diff --git a/Minecraft.Client/Common/Consoles_App.cpp b/Minecraft.Client/Common/Consoles_App.cpp index f733fd43..164890fc 100644 --- a/Minecraft.Client/Common/Consoles_App.cpp +++ b/Minecraft.Client/Common/Consoles_App.cpp @@ -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(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(GetTickCount64()), + static_cast(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) diff --git a/Minecraft.Client/Windows64/Windows64_LceLive.cpp b/Minecraft.Client/Windows64/Windows64_LceLive.cpp index 1a85b523..35f0000f 100644 --- a/Minecraft.Client/Windows64/Windows64_LceLive.cpp +++ b/Minecraft.Client/Windows64/Windows64_LceLive.cpp @@ -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(); diff --git a/Minecraft.Client/Windows64/Windows64_LceLive.h b/Minecraft.Client/Windows64/Windows64_LceLive.h index 3ed431b8..86f1e175 100644 --- a/Minecraft.Client/Windows64/Windows64_LceLive.h +++ b/Minecraft.Client/Windows64/Windows64_LceLive.h @@ -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(); diff --git a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp index fe1b9aff..5d9f8bff 100644 --- a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp +++ b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp @@ -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(_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(); diff --git a/README.md b/README.md index 7903c13e..9c59942c 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,8 @@ Important: launch from the output directory so relative asset paths resolve corr | `-name ` | 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 diff --git a/include/Common/BuildVer.h b/include/Common/BuildVer.h index e2e229fe..b98b2c7f 100644 --- a/include/Common/BuildVer.h +++ b/include/Common/BuildVer.h @@ -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