mirror of
https://github.com/smartcmd/MinecraftConsoles.git
synced 2026-10-22 19:22:04 +00:00
The old Windows64 port had no real player identity — it used hardcoded fake XUIDs, so anyone could impersonate anyone. This replaces that with proper auth supporting Mojang, Ely.by, and offline accounts. MCAuth library (new, MCAuth/): Mojang auth via MSA device code flow (XBL, SISU, MC services), Ely.by via Yggdrasil with 2FA, offline UUID generation matching Java Edition (MD5 v3 from "OfflinePlayer:<name>"). Multi-account manager with background token refresh, per-slot sessions, and on-disk token persistence. Server-side session verification via Mojang/Ely.by hasJoined API. Skin fetching and PNG validation from texture servers. Network protocol (version bumped to 80): Three new packets (AuthScheme, AuthResponse, AuthResult) implement a server-driven auth handshake before login completes. Player identity migrated from 64-bit XUID to 128-bit GameUUID backed by two uint64 fields (hi/lo). readPlayerUID/writePlayerUID now serialize 16 bytes on the wire. Old and new clients cannot connect to each other — version mismatch is rejected at PreLogin. Save migration: Map data mappings auto-migrate from old format: the old 64-bit XUID is placed in hi, lo is set to 0 as a sentinel. On first access by the real player, the sentinel entry is upgraded in-place to the full 128-bit UUID. Format detection is by file size (2080, 2112, or 4160 bytes). Player .dat filenames inside saveData.ms change from decimal XUID to dashed UUID — old saves need manual entry renaming in the archive. UI: NativeUIRenderer: immediate-mode drawing system (quads, text, 9-slice panels, scrollbars, focus lists) for rendering auth screens without Flash/Scaleform. UIScene_MSAuth handles device code display, Ely.by credential input with 2FA, per-account skin head preview, and multi-account add/remove/switch. Server: online-mode and auth-provider (mojang/elyby) in server.properties. Whitelist and ban checks validate against the server-verified UUID. Incompatible auth scheme logs which provider the server expects vs what the client is using. Also fixes a pre-existing exploit where any client could send a DebugOptionsPacket to grant themselves CraftAnything and other debug privileges on any server — now requires OP status server-side.
212 lines
8.4 KiB
C++
212 lines
8.4 KiB
C++
#pragma once
|
|
/*
|
|
* MCAuthManager - Thread-safe singleton that wraps JavaAuthManager with
|
|
* multi-account and per-slot session support (for splitscreen).
|
|
*
|
|
* Architecture:
|
|
*
|
|
* - Account list (m_javaAccounts) is SHARED — one global list of saved accounts.
|
|
* - Each player slot (0..XUSER_MAX_COUNT-1) has its own AuthSlot containing:
|
|
* - A shared_ptr<JavaAuthManager> (ownership shared with background worker)
|
|
* - Its own session, mutex, generation counter
|
|
* - Slot 0 is the primary player. Slots 1-3 are splitscreen.
|
|
*
|
|
* Thread model (fire-and-forget):
|
|
* - Background workers are detached threads that capture a shared_ptr copy
|
|
* of the auth engine they operate on.
|
|
* - To cancel: increment generation + RequestCancel on the old auth engine,
|
|
* then create a fresh shared_ptr<JavaAuthManager> for the slot.
|
|
* - The old thread runs to completion harmlessly — generation mismatch causes
|
|
* it to discard results, and the old auth engine is freed when the thread
|
|
* exits (shared_ptr ref-count drops to zero).
|
|
* - The main thread NEVER calls join(). No blocking. Ever.
|
|
*
|
|
* Usage:
|
|
* auto& mgr = MCAuthManager::Get();
|
|
* mgr.LoadJavaAccountIndex();
|
|
* mgr.TryRestoreActiveJavaAccount(); // restore slot 0
|
|
*
|
|
* mgr.SetAccountForSlot(0, accountIndex); // switch slot 0's account
|
|
* auto session = mgr.GetSlotSession(0); // get slot 0's session
|
|
*/
|
|
|
|
#include "MCAuth.h"
|
|
#include <functional>
|
|
#include <mutex>
|
|
#include <atomic>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <memory>
|
|
#include <condition_variable>
|
|
|
|
#ifndef XUSER_MAX_COUNT
|
|
#define XUSER_MAX_COUNT 4
|
|
#endif
|
|
|
|
class MCAuthManager {
|
|
public:
|
|
enum class State {
|
|
Idle,
|
|
WaitingForCode, // device code displayed, waiting for user
|
|
Authenticating, // polling / running auth chain
|
|
Success,
|
|
Failed,
|
|
};
|
|
|
|
// Per-account info stored in the index.
|
|
struct JavaAccountInfo {
|
|
std::string username; // Minecraft username
|
|
std::string uuid; // Player UUID (dashed)
|
|
std::string tokenFile; // e.g. "java_auth_0.json" (empty for offline)
|
|
bool isOffline = false;
|
|
std::string authProvider; // "mojang", "elyby", "offline" (default: "mojang")
|
|
};
|
|
|
|
// Per-player-slot auth state. Each slot has its own auth engine + session.
|
|
struct AuthSlot {
|
|
// Auth engine is heap-allocated and shared with background worker threads.
|
|
// When we cancel, we abandon the old shared_ptr and create a fresh one.
|
|
// The old thread's copy prevents use-after-free; it finishes harmlessly.
|
|
std::shared_ptr<MCAuth::JavaAuthManager> auth;
|
|
|
|
MCAuth::ElybyTokens elybyTokens; // Ely.by tokens (no engine class needed)
|
|
MCAuth::JavaSession session; // current session data
|
|
std::atomic<int> accountIndex{-1}; // index into m_javaAccounts (-1 = none)
|
|
mutable std::mutex mutex;
|
|
std::atomic<uint32_t> generation{0}; // invalidates stale workers
|
|
std::atomic<State> state{State::Idle};
|
|
mutable std::condition_variable cv; // signals when state changes
|
|
std::string lastError;
|
|
|
|
// No std::thread member — workers are detached (fire-and-forget).
|
|
|
|
bool hasSession() const { return !session.uuid.empty(); }
|
|
|
|
AuthSlot() : auth(std::make_shared<MCAuth::JavaAuthManager>()) {}
|
|
};
|
|
|
|
using DeviceCodeCb = MCAuth::DeviceCodeCallback;
|
|
using JavaCompleteCb = std::function<void(bool, const MCAuth::JavaSession&, const std::string&)>;
|
|
|
|
static MCAuthManager& Get();
|
|
|
|
MCAuthManager(const MCAuthManager&) = delete;
|
|
MCAuthManager& operator=(const MCAuthManager&) = delete;
|
|
|
|
// ---- Java Edition — Account Index ----
|
|
|
|
bool LoadJavaAccountIndex();
|
|
bool SaveJavaAccountIndex() const;
|
|
|
|
std::vector<JavaAccountInfo> GetJavaAccounts() const;
|
|
|
|
// ---- Java Edition — Per-Slot Session Management ----
|
|
|
|
// Get the auth slot for a player index (0 = primary, 1-3 = splitscreen).
|
|
const AuthSlot& GetSlot(int slot) const;
|
|
|
|
// Set which account a slot uses. Loads tokens + refreshes in background.
|
|
// The old session remains valid until the new one is ready (no empty window).
|
|
bool SetAccountForSlot(int slot, int accountIndex);
|
|
|
|
// Clear a slot (e.g. when a splitscreen player leaves).
|
|
void ClearSlot(int slot);
|
|
|
|
// Get the session for a specific slot.
|
|
MCAuth::JavaSession GetSlotSession(int slot) const;
|
|
|
|
// Check if a slot has a valid logged-in session.
|
|
bool IsSlotLoggedIn(int slot) const;
|
|
|
|
// Check if the slot's token is expiring soon (within 60s).
|
|
bool IsTokenExpiringSoon(int slot) const;
|
|
|
|
// Proactively refresh a slot's token in the background.
|
|
void RefreshSlot(int slot);
|
|
|
|
// Block until a slot's auth settles (state leaves Authenticating/WaitingForCode).
|
|
// Returns true if login succeeded, false on timeout or failure.
|
|
bool WaitForSlotReady(int slot, int timeoutMs = 15000) const;
|
|
|
|
// Check if an online account is already assigned to another slot.
|
|
bool IsAccountInUseByOtherSlot(int slot, int accountIndex) const;
|
|
|
|
// ---- Java Edition — Slot 0 Convenience (backward compat) ----
|
|
|
|
int GetActiveJavaAccountIndex() const;
|
|
bool SetActiveJavaAccount(int index) { return SetAccountForSlot(0, index); }
|
|
MCAuth::JavaSession GetJavaSession() const;
|
|
bool IsJavaLoggedIn() const;
|
|
State GetState() const { return m_slots[0].state.load(); }
|
|
State GetSlotState(int slot) const {
|
|
if (slot < 0 || slot >= XUSER_MAX_COUNT) slot = 0;
|
|
return m_slots[slot].state.load();
|
|
}
|
|
bool WaitForAuthReady(int timeoutMs = 15000) const { return WaitForSlotReady(0, timeoutMs); }
|
|
|
|
// ---- Java Edition — Account Management ----
|
|
|
|
// Add a new account via device code flow (background thread on slot 0).
|
|
void BeginAddJavaAccount(DeviceCodeCb onDeviceCode, JavaCompleteCb onComplete,
|
|
int timeoutSeconds = 300);
|
|
|
|
// Add an Ely.by account via username+password.
|
|
using ElybyCompleteCb = std::function<void(bool ok, const MCAuth::JavaSession& session, const std::string& error)>;
|
|
using Elyby2FACb = std::function<void()>;
|
|
void BeginAddElybyAccount(const std::string& username, const std::string& password,
|
|
ElybyCompleteCb onComplete, Elyby2FACb on2FA = nullptr);
|
|
|
|
// Add an offline account. Returns new account index or -1.
|
|
int AddOfflineJavaAccount(const std::string& username);
|
|
|
|
// Remove an account by index. Deletes its token file.
|
|
bool RemoveJavaAccount(int index);
|
|
|
|
// Restore the active account from disk at startup (slot 0).
|
|
void TryRestoreActiveJavaAccount();
|
|
|
|
// ---- Device code info (valid during add-account flow) ----
|
|
|
|
std::string GetJavaDeviceCode() const;
|
|
std::string GetJavaDirectUri() const;
|
|
std::string GetLastError() const;
|
|
|
|
// File paths
|
|
static constexpr const char* kJavaAccountsFile = "java_accounts.json";
|
|
|
|
private:
|
|
MCAuthManager();
|
|
~MCAuthManager();
|
|
|
|
// Generate a unique token filename for a new account.
|
|
std::string AllocTokenFile(const std::string& uuid) const;
|
|
|
|
// Synthesize an offline session from account info.
|
|
static MCAuth::JavaSession SynthesizeOfflineSession(const JavaAccountInfo& acct);
|
|
|
|
// Abandon the current auth engine for a slot and create a fresh one.
|
|
// Calls RequestCancel on the old engine so its worker exits promptly.
|
|
// Returns the fresh shared_ptr for capturing in the new worker lambda.
|
|
std::shared_ptr<MCAuth::JavaAuthManager> ResetSlotAuth(AuthSlot& s);
|
|
|
|
// Shared Ely.by refresh logic used by RefreshSlot and SetAccountForSlot.
|
|
// If failOpen is true, sets state to Success even when refresh fails (fail-open).
|
|
// If alwaysSaveIndex is true, calls SaveJavaAccountIndex even on failure.
|
|
void RunElybyRefresh(int slot, uint32_t gen, const std::string& tokenFile,
|
|
bool failOpen, bool alwaysSaveIndex);
|
|
|
|
// Per-slot auth state
|
|
AuthSlot m_slots[XUSER_MAX_COUNT];
|
|
|
|
// Shared account list (protected by m_accountsMutex)
|
|
mutable std::mutex m_accountsMutex;
|
|
std::vector<JavaAccountInfo> m_javaAccounts;
|
|
|
|
// Device code state (for BeginAddJavaAccount, always on slot 0)
|
|
mutable std::mutex m_deviceCodeMutex;
|
|
std::string m_javaDeviceCode;
|
|
std::string m_javaDirectUri;
|
|
|
|
bool m_javaRestoreAttempted = false;
|
|
};
|