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.
231 lines
8.5 KiB
C++
231 lines
8.5 KiB
C++
#pragma once
|
|
/*
|
|
* MCAuth - Microsoft/Xbox Live Authentication library for Minecraft Java Edition
|
|
* C++ port of the MinecraftAuth Java library by RaphiMC
|
|
*
|
|
* Usage:
|
|
* MCAuth::JavaAuthManager auth;
|
|
* MCAuth::JavaSession session;
|
|
* std::string error;
|
|
*
|
|
* bool ok = auth.Login(
|
|
* [](const MCAuth::DeviceCodeInfo& dc) {
|
|
* // Show dc.userCode and dc.directUri to the user
|
|
* },
|
|
* session, error
|
|
* );
|
|
*/
|
|
|
|
#include <string>
|
|
#include <functional>
|
|
#include <cstdint>
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
namespace MCAuth {
|
|
|
|
// ----- Error codes -----
|
|
|
|
enum class AuthErrorCode {
|
|
None = 0,
|
|
NetworkTimeout, // connection timed out or DNS failure
|
|
NetworkError, // general HTTP/TLS failure (send/receive)
|
|
HttpError, // unexpected HTTP status code
|
|
InvalidCredentials, // wrong token, expired refresh token, account not authorised
|
|
TokenExpired, // access token expired (needs refresh)
|
|
ServerUnavailable, // Mojang/MS service returned 5xx
|
|
RateLimited, // HTTP 429
|
|
ProfileNotOwned, // MS account exists but doesn't own MC Java Edition
|
|
Cancelled, // operation was cancelled (e.g. device code timeout)
|
|
InternalError, // unexpected exception / crypto failure
|
|
};
|
|
|
|
struct AuthError {
|
|
AuthErrorCode code = AuthErrorCode::None;
|
|
std::string message;
|
|
int httpStatus = 0; // 0 if not applicable
|
|
|
|
bool ok() const { return code == AuthErrorCode::None; }
|
|
explicit operator bool() const { return !ok(); } // true means error
|
|
};
|
|
|
|
// ----- Constants ----
|
|
|
|
// Java Edition title ID (Win32, used for SISU auth flow)
|
|
static constexpr const char* JAVA_CLIENT_ID = "00000000402b5328";
|
|
|
|
static constexpr const char* MSA_SCOPE = "service::user.auth.xboxlive.com::MBI_SSL";
|
|
|
|
// ----- Public data types -----
|
|
|
|
struct DeviceCodeInfo {
|
|
std::string userCode; // e.g. "ABCD1234"
|
|
std::string verificationUri; // e.g. "https://www.microsoft.com/link"
|
|
std::string directUri; // verificationUri + "?otc=" + userCode
|
|
std::string deviceCode; // internal polling token
|
|
int64_t expiresMs; // absolute timestamp (ms since epoch)
|
|
int64_t intervalMs; // polling interval in ms
|
|
};
|
|
|
|
using DeviceCodeCallback = std::function<void(const DeviceCodeInfo&)>;
|
|
|
|
struct JavaSession {
|
|
std::string username; // Minecraft username (from /minecraft/profile)
|
|
std::string uuid; // Player UUID (dashed, e.g. "069a79f4-44e9-4726-a5be-fca90e38aaf5")
|
|
std::string accessToken; // Full Authorization header value ("Bearer <token>")
|
|
int64_t expireMs; // When the access token expires (ms since epoch)
|
|
};
|
|
|
|
// ----- Java Edition auth manager -----
|
|
|
|
class JavaAuthManager {
|
|
public:
|
|
JavaAuthManager();
|
|
~JavaAuthManager();
|
|
|
|
JavaAuthManager(const JavaAuthManager&) = delete;
|
|
JavaAuthManager& operator=(const JavaAuthManager&) = delete;
|
|
|
|
// Synchronous device-code login (blocks until complete or timeout).
|
|
bool Login(DeviceCodeCallback onDeviceCode,
|
|
JavaSession& outSession,
|
|
std::string& error,
|
|
int timeoutSeconds = 300);
|
|
|
|
// Refresh using the saved MSA refresh token.
|
|
bool Refresh(JavaSession& outSession, std::string& error);
|
|
|
|
bool IsLoggedIn() const;
|
|
void Logout();
|
|
|
|
// Signal a blocking Login() to abort as soon as possible.
|
|
// Thread-safe — can be called from any thread while Login() is running.
|
|
void RequestCancel();
|
|
|
|
bool SaveTokens(const std::string& filePath) const;
|
|
bool LoadTokens(const std::string& filePath);
|
|
|
|
private:
|
|
struct Impl;
|
|
std::unique_ptr<Impl> m_impl;
|
|
};
|
|
|
|
// ----- Ely.by Yggdrasil auth (free functions, no class) -----
|
|
|
|
struct ElybyTokens {
|
|
std::string accessToken;
|
|
std::string clientToken;
|
|
std::string uuid; // undashed
|
|
std::string username;
|
|
};
|
|
|
|
// POST https://authserver.ely.by/auth/authenticate
|
|
// On 2FA requirement, error is set to "elyby_2fa_required"; caller retries with password = "pass:totp_code"
|
|
bool ElybyLogin(const std::string& username, const std::string& password,
|
|
ElybyTokens& outTokens, std::string& error);
|
|
|
|
// POST https://authserver.ely.by/auth/refresh
|
|
bool ElybyRefresh(ElybyTokens& tokens, std::string& error);
|
|
|
|
// POST https://authserver.ely.by/auth/validate
|
|
bool ElybyValidate(const std::string& accessToken, std::string& error);
|
|
|
|
// Token persistence — simple JSON: {accessToken, clientToken, uuid, username}
|
|
bool ElybyLoadTokens(const std::string& path, ElybyTokens& out);
|
|
bool ElybySaveTokens(const std::string& path, const ElybyTokens& tokens);
|
|
|
|
// ----- Skin key utility -----
|
|
|
|
// Build the canonical memory-texture key for a Mojang skin.
|
|
// uuid can be dashed or undashed — dashes are stripped automatically.
|
|
// Returns e.g. "mojang_skin_069a79f444e94726a5befca90e38aaf5.png"
|
|
std::string MakeSkinKey(const std::string& uuid);
|
|
|
|
// ----- UUID utilities -----
|
|
|
|
// Remove dashes from a UUID string: "069a79f4-44e9-..." → "069a79f444e9..."
|
|
std::string UndashUuid(const std::string& dashed);
|
|
|
|
// Insert dashes into a 32-char hex UUID: "069a79f444e9..." → "069a79f4-44e9-..."
|
|
std::string DashUuid(const std::string& undashed);
|
|
|
|
// Generate a Minecraft-compatible offline UUID (UUID v3, namespace "OfflinePlayer:" + username).
|
|
// Returns dashed format.
|
|
std::string GenerateOfflineUuid(const std::string& username);
|
|
|
|
// Parse a dashed UUID into two 64-bit halves (big-endian).
|
|
struct Uuid128 {
|
|
uint64_t hi = 0;
|
|
uint64_t lo = 0;
|
|
bool isValid() const { return hi != 0 || lo != 0; }
|
|
};
|
|
Uuid128 ParseUuid128(const std::string& dashed);
|
|
|
|
// ----- Session verification (Mojang sessionserver) -----
|
|
|
|
// Client-side: call BEFORE connecting to an online-mode server.
|
|
// Posts to sessionserver.mojang.com/session/minecraft/join.
|
|
// accessToken = raw token (without "Bearer " prefix).
|
|
// undashedUuid = 32-char hex UUID of the player.
|
|
// serverId = the server's challenge string.
|
|
// Returns true on success (HTTP 204).
|
|
bool JoinServer(const std::string& accessToken,
|
|
const std::string& undashedUuid,
|
|
const std::string& serverId,
|
|
std::string& error);
|
|
|
|
// Server-side result from HasJoined verification.
|
|
struct HasJoinedResult {
|
|
bool success = false;
|
|
AuthError error; // structured error (code + message + httpStatus)
|
|
std::string username; // verified username from Mojang
|
|
std::string uuid; // undashed UUID
|
|
std::string skinUrl; // Mojang skin texture URL (from base64 properties)
|
|
std::string capeUrl; // Mojang cape texture URL (from base64 properties)
|
|
};
|
|
|
|
// Server-side: verify that a player has called /join.
|
|
// GETs sessionserver.mojang.com/session/minecraft/hasJoined?username=X&serverId=Y.
|
|
// Returns success=true with username+uuid on HTTP 200, success=false on 204 (not joined).
|
|
HasJoinedResult HasJoined(const std::string& username,
|
|
const std::string& serverId,
|
|
std::string& error);
|
|
|
|
// Download a skin/cape PNG from a URL.
|
|
// Returns the raw PNG bytes, or empty vector on failure.
|
|
std::vector<uint8_t> FetchSkinPng(const std::string& url, std::string& error);
|
|
|
|
// Fetch the skin URL for a player UUID from Mojang's session server.
|
|
// uuid can be dashed or undashed.
|
|
// Returns the skin texture URL, or empty string on failure.
|
|
std::string FetchProfileSkinUrl(const std::string& uuid, std::string& error);
|
|
|
|
// Download a skin PNG and return the raw bytes WITHOUT cropping to 64x32.
|
|
// Use this when you need the full 64x64 texture (e.g. for rendering the head).
|
|
std::vector<uint8_t> FetchSkinPngRaw(const std::string& url, std::string& error);
|
|
|
|
// ----- Ely.by session functions (same Yggdrasil protocol, different URLs) -----
|
|
|
|
bool ElybyJoinServer(const std::string& accessToken,
|
|
const std::string& undashedUuid,
|
|
const std::string& serverId,
|
|
std::string& error);
|
|
|
|
HasJoinedResult ElybyHasJoined(const std::string& username,
|
|
const std::string& serverId,
|
|
std::string& error);
|
|
|
|
std::string ElybyFetchProfileSkinUrl(const std::string& uuid, std::string& error);
|
|
|
|
std::string MakeElybySkinKey(const std::string& uuid);
|
|
|
|
// ----- Skin validation -----
|
|
|
|
// Maximum allowed skin PNG size in bytes (32KB — a 64x64 RGBA PNG is ~4KB compressed)
|
|
static constexpr size_t kMaxSkinBytes = 32768;
|
|
|
|
// Check PNG magic, size cap, and 64x32 or 64x64 dimensions.
|
|
bool ValidateSkinPng(const uint8_t* data, size_t size);
|
|
|
|
} // namespace MCAuth
|