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.
360 lines
13 KiB
Markdown
360 lines
13 KiB
Markdown
# Authentication System - MinecraftConsoles
|
|
|
|
## Overview
|
|
|
|
3-packet handshake between client and server, after PreLogin and before LoginPacket. Three schemes: **Mojang** (online verification via sessionserver.mojang.com), **ely.by** (online verification via authserver.ely.by), and **offline** (server-generated UUID from username).
|
|
|
|
Replaces the old system that relied solely on console platform XUIDs (Xbox Live, PSN) and had no identity verification for the Windows x64 build.
|
|
|
|
---
|
|
|
|
## Packet Architecture
|
|
|
|
| Packet | ID | Direction | Description |
|
|
|---|---|---|---|
|
|
| `AuthSchemePacket` | 170 | Server -> Client | Server offers auth schemes + challenge |
|
|
| `AuthResponsePacket` | 171 | Client -> Server | Client picks a scheme and sends credentials |
|
|
| `AuthResultPacket` | 172 | Server -> Client | Server confirms/rejects + assigns UUID and skin |
|
|
|
|
The `LoginPacket` serves as the handshake acknowledgement — no dedicated ACK packet needed.
|
|
|
|
### Handshake Flow
|
|
|
|
```
|
|
CLIENT SERVER
|
|
| |
|
|
| ---- PreLoginPacket (netcode version) ----> |
|
|
| <--- PreLoginPacket (UGC, save info) ------ |
|
|
| |
|
|
| <--- AuthSchemePacket (170) --------------- | schemes=["mojang"|"elyby"|"offline"], serverId="a1b2c3..."
|
|
| |
|
|
| ---- AuthResponsePacket (171) ------------> | scheme="mojang", uuid, username
|
|
| |
|
|
| [Server verifies with Mojang on a |
|
|
| background thread + downloads skin] |
|
|
| |
|
|
| <--- AuthResultPacket (172) --------------- | success=true, assignedUuid, assignedUsername, skinData
|
|
| |
|
|
| ---- LoginPacket (1) ---------------------> | Completes auth handshake
|
|
| |
|
|
| <--- LoginPacket (1) --------------------- | Server responds with world data
|
|
| |
|
|
```
|
|
|
|
---
|
|
|
|
## Packet Details
|
|
|
|
### AuthSchemePacket (ID 170)
|
|
|
|
**Direction:** Server -> Client
|
|
|
|
Sent right after PreLogin response. Contains supported auth schemes and a random challenge.
|
|
|
|
```cpp
|
|
class AuthSchemePacket : public Packet {
|
|
vector<wstring> schemes; // "mojang", "elyby", "offline"
|
|
wstring serverId; // random hex challenge (20 chars)
|
|
};
|
|
```
|
|
|
|
**Wire format:**
|
|
| Field | Type | Notes |
|
|
|---|---|---|
|
|
| `count` | int32 | Number of schemes (max 16) |
|
|
| `schemes[i]` | UTF string | Scheme name (max 32 chars) |
|
|
| `serverId` | UTF string | Hex challenge (max 64 chars) |
|
|
|
|
**Read validation:**
|
|
- Scheme count capped at 16
|
|
- Only `"mojang"`, `"elyby"`, and `"offline"` accepted; unknown strings silently discarded
|
|
|
|
**Server behavior:**
|
|
- `onlineMode=true`: offers only `["mojang"]`
|
|
- `onlineMode=true, authProvider="mojang"`: offers `["mojang"]`
|
|
- `onlineMode=true, authProvider="elyby"`: offers `["elyby"]`
|
|
- `onlineMode=false`: offers `["mojang", "offline"]`
|
|
|
|
---
|
|
|
|
### AuthResponsePacket (ID 171)
|
|
|
|
**Direction:** Client -> Server
|
|
|
|
Client picks a scheme and sends credentials.
|
|
|
|
```cpp
|
|
class AuthResponsePacket : public Packet {
|
|
wstring chosenScheme; // "mojang", "elyby", or "offline"
|
|
wstring mojangUuid; // dashed 128-bit UUID
|
|
wstring username; // player username (max 16 chars)
|
|
};
|
|
```
|
|
|
|
**Wire format:**
|
|
| Field | Type | Notes |
|
|
|---|---|---|
|
|
| `chosenScheme` | UTF string | max 32 chars, validated against known values |
|
|
| `mojangUuid` | UTF string | max 64 chars |
|
|
| `username` | UTF string | max 16 chars |
|
|
|
|
**Read validation:**
|
|
- Scheme reset to `""` if not `"mojang"`, `"elyby"`, or `"offline"`
|
|
- Username validated char by char: only `[a-zA-Z0-9_]`. Invalid char clears the entire username
|
|
|
|
**Client side ("mojang" scheme):**
|
|
1. Calls `MCAuth::JoinServer(token, uuid, serverId)` to `sessionserver.mojang.com/session/minecraft/join`
|
|
2. On success, sends AuthResponsePacket with scheme `"mojang"`
|
|
3. On failure, disconnects with `eDisconnect_AuthFailed`
|
|
|
|
**Client side ("elyby" scheme):**
|
|
1. Calls `MCAuth::ElybyJoinServer(token, uuid, serverId)` to `authserver.ely.by/session/minecraft/join`
|
|
2. On success, sends AuthResponsePacket with scheme `"elyby"`
|
|
3. On failure, disconnects with `eDisconnect_AuthFailed`
|
|
|
|
**Client side ("offline" scheme):**
|
|
1. Sends AuthResponsePacket directly with UUID and username from the local session
|
|
|
|
---
|
|
|
|
### AuthResultPacket (ID 172)
|
|
|
|
**Direction:** Server -> Client
|
|
|
|
Server communicates the verification result and assigns the player's final identity.
|
|
|
|
```cpp
|
|
class AuthResultPacket : public Packet {
|
|
bool success;
|
|
wstring assignedUuid; // final UUID assigned by the server
|
|
wstring assignedUsername; // final username
|
|
wstring errorMessage; // empty if success=true
|
|
wstring skinKey; // memory-texture key (e.g. "mojang_skin_{uuid}.png")
|
|
vector<uint8_t> skinData; // raw PNG skin bytes
|
|
};
|
|
```
|
|
|
|
**Wire format:**
|
|
| Field | Type | Notes |
|
|
|---|---|---|
|
|
| `success` | bool | 1 byte |
|
|
| `assignedUuid` | UTF string | max 64 chars |
|
|
| `assignedUsername` | UTF string | max 64 chars |
|
|
| `errorMessage` | UTF string | max 256 chars |
|
|
| `skinKey` | UTF string | max 256 chars |
|
|
| `skinSize` | int32 | skin data length |
|
|
| `skinData` | byte[] | raw PNG (max 32KB) |
|
|
|
|
**Skin validation:**
|
|
- Max 32KB (a 64x64 RGBA skin PNG is ~4KB compressed)
|
|
- Excess bytes consumed to keep stream synchronized but not stored
|
|
- Client validates with `MCAuth::ValidateSkinPng()` (PNG magic, 64x32 or 64x64 dimensions)
|
|
|
|
---
|
|
|
|
## Server-Side Verification Flow
|
|
|
|
### "mojang" Scheme (Online Mode)
|
|
|
|
```
|
|
PendingConnection::handleAuthResponse()
|
|
|
|
|
+--> m_authState = eAuth_Verifying
|
|
+--> Spawns detached std::thread:
|
|
|
|
|
+--> MCAuth::HasJoined(username, serverId)
|
|
| [GET sessionserver.mojang.com/session/minecraft/hasJoined]
|
|
|
|
|
+--> On success: MCAuth::FetchSkinPng(skinUrl)
|
|
| [Downloads skin PNG from Mojang CDN]
|
|
|
|
|
+--> Writes result to AuthVerifyResult (mutex-protected)
|
|
+--> ready.store(true)
|
|
|
|
PendingConnection::tick() [main thread]
|
|
|
|
|
+--> Checks m_authVerifyResult->ready
|
|
+--> On success:
|
|
| +--> Sends AuthResultPacket(success=true, uuid, username, skinKey, skinData)
|
|
| +--> m_authState = eAuth_WaitingAck
|
|
|
|
|
+--> On failure:
|
|
+--> Sends AuthResultPacket(success=false, errorMessage)
|
|
+--> Disconnects client
|
|
```
|
|
|
|
### "offline" Scheme
|
|
|
|
```
|
|
PendingConnection::handleAuthResponse()
|
|
|
|
|
+--> Checks that server->onlineMode == false
|
|
+--> Generates offline UUID: GameUUID::generateOffline(username)
|
|
| [UUID v3, namespace "OfflinePlayer:" + username]
|
|
+--> Sends AuthResultPacket(success=true, offlineUuid, username)
|
|
+--> m_authState = eAuth_WaitingAck
|
|
```
|
|
|
|
In offline mode, the server generates the UUID itself from the username. The client-sent UUID is ignored, preventing UUID forgery.
|
|
|
|
---
|
|
|
|
## State Machine (Server - PendingConnection)
|
|
|
|
```
|
|
eAuth_None
|
|
|
|
|
+--> sendPreLoginResponse() sends AuthSchemePacket
|
|
|
|
|
v
|
|
eAuth_WaitingResponse
|
|
|
|
|
+--> handleAuthResponse() received
|
|
|
|
|
v
|
|
eAuth_Verifying (only for "mojang" scheme)
|
|
|
|
|
+--> tick() detects result ready
|
|
|
|
|
v
|
|
eAuth_WaitingAck
|
|
|
|
|
+--> handleLogin() received
|
|
|
|
|
v
|
|
eAuth_Done
|
|
|
|
|
+--> handleLogin() proceeds normally
|
|
```
|
|
|
|
If a `LoginPacket` arrives in state `eAuth_None`, `eAuth_WaitingResponse`, or `eAuth_Verifying`, the connection is closed with `eDisconnect_AuthFailed`.
|
|
|
|
---
|
|
|
|
## Channel Security
|
|
|
|
The game protocol is **plaintext** — no AES, no TLS on the game socket. `SharedKeyPacket` (vanilla Java's encryption setup) is disabled. This is fine because no secrets travel over the game channel.
|
|
|
|
What travels on the game socket (plaintext):
|
|
- UUID, username, skin PNG, serverId challenge, gamestate
|
|
|
|
What **never** travels on the game socket:
|
|
- Access tokens, refresh tokens, session tokens
|
|
|
|
The Mojang access token stays local on the client. It's sent only to `sessionserver.mojang.com` via HTTPS (`WINHTTP_FLAG_SECURE` in `MCAuthHttp.cpp`). The game server never sees it — it verifies the player by calling `HasJoined` on Mojang's side, also over HTTPS.
|
|
|
|
The `serverId` challenge is random per connection (20 hex chars, generated in `PendingConnection`). A captured serverId is useless — Mojang consumes the `(username, serverId)` binding on the first `JoinServer` call. Replay is not possible.
|
|
|
|
On console platforms (Xbox/QNet, PS/SQR), the transport layer provides its own integrity. On Windows64 it's raw TCP — an attacker on the same network can see gamestate (positions, chat, inventory), but that's a cheat/sniffing concern, not an auth one. Same situation as vanilla Java pre-1.7.
|
|
|
|
---
|
|
|
|
## Mojang Skin Handling
|
|
|
|
The server downloads skins and distributes them inline:
|
|
|
|
1. Server downloads the skin during verification (background thread)
|
|
2. AuthResultPacket contains the raw PNG bytes
|
|
3. Client registers the texture in memory via `app.AddMemoryTextureFile()`
|
|
4. Server distributes the skin to all connected clients via `TextureAndGeometryPacket`
|
|
5. New clients receive existing skins at join time
|
|
|
|
---
|
|
|
|
## MCAuth Library
|
|
|
|
The authentication backend lives in `MCAuth/`.
|
|
|
|
### JavaAuthManager
|
|
- Login via Device Code Flow (OAuth 2.0) — user visits `microsoft.com/link`
|
|
- Automatic token refresh
|
|
- Token save/load to disk (JSON)
|
|
|
|
### Verification Functions
|
|
| Function | Usage |
|
|
|---|---|
|
|
| `JoinServer()` | Client: POST to sessionserver to announce join |
|
|
| `HasJoined()` | Server: GET to sessionserver to verify identity |
|
|
| `FetchSkinPng()` | Downloads skin PNG from CDN |
|
|
| `FetchProfileSkinUrl()` | Gets skin URL from Mojang profile |
|
|
| `ValidateSkinPng()` | Validates PNG (magic bytes + 64x32 or 64x64 dimensions) |
|
|
|
|
### UUID Utilities
|
|
| Function | Usage |
|
|
|---|---|
|
|
| `GenerateOfflineUuid()` | UUID v3 from username (vanilla-compatible) |
|
|
| `DashUuid()` / `UndashUuid()` | UUID format conversion |
|
|
| `MakeSkinKey()` | Generates memory texture key (e.g. `"mojang_skin_069a79f4.png"`) |
|
|
|
|
### MCAuthManager (Singleton)
|
|
- Multi-account with splitscreen support (up to `XUSER_MAX_COUNT` slots)
|
|
- Each slot has its own `JavaAuthManager` and independent session
|
|
- Background workers: detached threads capturing `shared_ptr`, invalidated by generation counter
|
|
- Cancellation via `RequestCancel()`
|
|
- Account persistence in `java_accounts.json`
|
|
|
|
---
|
|
|
|
## Thread Safety
|
|
|
|
### AuthVerifyResult (PendingConnection)
|
|
```cpp
|
|
struct AuthVerifyResult {
|
|
std::atomic<bool> ready{false};
|
|
std::atomic<bool> cancelled{false};
|
|
std::mutex mutex;
|
|
bool success;
|
|
std::string username, uuid, skinUrl;
|
|
std::vector<uint8_t> skinData;
|
|
};
|
|
```
|
|
|
|
- Background thread writes under mutex, then `ready.store(true, memory_order_release)`
|
|
- Main thread checks `ready` with `memory_order_acquire`, then reads under mutex
|
|
- `cancelled` flag lets PendingConnection destructor abort unnecessary work
|
|
- `shared_ptr` prevents use-after-free if PendingConnection is destroyed before the thread finishes
|
|
|
|
### MCAuthManager Slots
|
|
- Each slot has its own mutex
|
|
- Atomic `generation` counter invalidates stale workers
|
|
- Workers capture `shared_ptr<JavaAuthManager>` by value — if the slot is reset, the old engine stays alive until the thread exits
|
|
|
|
---
|
|
|
|
## Files
|
|
|
|
### Packets (Minecraft.World/)
|
|
| File | Description |
|
|
|---|---|
|
|
| `AuthSchemePacket.h/.cpp` | Scheme offer packet |
|
|
| `AuthResponsePacket.h/.cpp` | Client response packet |
|
|
| `AuthResultPacket.h/.cpp` | Result packet + skin data |
|
|
| `DisconnectPacket.h` | `eDisconnect_AuthFailed = 31` |
|
|
| `PacketListener.h` | Auth packet handlers |
|
|
| `GameUUID.h/.cpp` | UUID utilities (fromDashed, generateOffline) |
|
|
|
|
### Server (Minecraft.Client/)
|
|
| File | Description |
|
|
|---|---|
|
|
| `PendingConnection.h/.cpp` | Auth state machine, Mojang verification, skin distribution |
|
|
| `PlayerList.cpp` | Ban check against auth UUID |
|
|
|
|
### Client (Minecraft.Client/)
|
|
| File | Description |
|
|
|---|---|
|
|
| `ClientConnection.h/.cpp` | handleAuthScheme, handleAuthResult, sendLoginPacketAfterAuth |
|
|
| `UIScene_MSAuth.h/.cpp` | Account management UI (list, device code, offline) |
|
|
|
|
### Auth Library (MCAuth/)
|
|
| File | Description |
|
|
|---|---|
|
|
| `MCAuth.h` | Public API: login, verification, skin, UUID |
|
|
| `MCAuthManager.h` | Multi-account singleton with splitscreen support |
|
|
| `MCAuth.cpp` | UUID utilities, skin validation |
|
|
| `MCAuthJava.cpp` | Java Edition auth flow (MSA -> XBL -> Mojang) |
|
|
| `MCAuthElyby.cpp` | Ely.by Yggdrasil auth (login, refresh, validate, token persistence) |
|
|
| `MCAuthSession.cpp` | Session verification (JoinServer, HasJoined, Elyby variants) |
|
|
| `MCAuthHttp.cpp/.h` | HTTP client (WinHTTP) |
|
|
| `MCAuthCrypto.cpp/.h` | Base64, SHA, JSON parsing |
|
|
| `MCAuthPlatform.cpp` | File paths, persistence |
|