MinecraftConsoles/Minecraft.Client/Common/UI/UIScene_MSAuth.h
MrTheShy 4f2352361a Add Mojang/Ely.by/offline authentication system
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.
2026-03-23 01:14:23 +01:00

159 lines
5.5 KiB
C++

#pragma once
/*
* UIScene_MSAuth — Microsoft Account Manager.
*
* Two views:
* 1. Account List — shows saved accounts, select/remove/add.
* 2. Device Code — device code flow for adding a new account.
*
* Native overlay (no SWF).
*/
#include "UIScene.h"
#include "NativeUIRenderer.h"
#include "../../../MCAuth/include/MCAuthManager.h"
#include <atomic>
#include <vector>
#include <string>
#include <unordered_map>
// Control IDs for UIScene_MSAuth focus list.
// File-scope so static render helpers in the .cpp can reference them directly.
namespace MSAuthUI
{
enum EControls {
eBtn_Back = 0,
eBtn_AddAccount = 1,
eBtn_AddOffline = 2,
eBtn_OfflineConfirm = 3,
eBtn_ConfirmYes = 4,
eBtn_ConfirmNo = 5,
eBtn_OfflineTextBox = 6,
eBtn_AddElyby = 7,
eBtn_ElybyUsername = 8,
eBtn_ElybyPassword = 9,
eBtn_ElybySignIn = 10,
eBtn_ElybyCancel = 11,
eBtn_Elyby2FACode = 12,
eBtn_Elyby2FASubmit = 13,
eBtn_Elyby2FACancel = 14,
eBtn_RemoveBase = 50, // 50 + index = remove button for account i
eAccountBase = 100, // 100 + index = account row for account i
eLink_URL = 200,
};
}
class UIScene_MSAuth : public UIScene
{
private:
// View mode
enum EView { eView_AccountList, eView_DeviceCode, eView_OfflineInput, eView_ElybyInput, eView_Elyby2FA };
EView m_view = eView_AccountList;
// Shared flags survive scene destruction (prevent UAF from async callback)
struct AuthFlags {
std::atomic<bool> done { false };
std::atomic<bool> success{ false };
std::atomic<bool> need2FA{ false };
};
std::shared_ptr<AuthFlags> m_authFlags = std::make_shared<AuthFlags>();
int m_closeCountdown = 0;
int m_spinnerTick = 0;
NativeUI::NineSlice m_panel;
NativeUI::NineSlice m_recessPanel;
bool m_panelLoaded = false;
NativeUI::FocusList m_focus;
std::string m_cachedUri;
// Cached account list (refreshed each tick)
std::vector<MCAuthManager::JavaAccountInfo> m_accounts;
int m_activeIdx = -1;
// Scroll offset for account list
int m_scrollOffset = 0;
void StartAddAccount();
void SwitchToAccountList();
void SwitchToDeviceCode();
void SwitchToOfflineInput();
void SwitchToElybyInput();
void SwitchToElyby2FA();
void ConfirmOfflineAccount();
void SubmitElybyLogin();
void SubmitElyby2FA();
// Offline username input state
std::string m_offlineUsername;
int m_offlineCursorBlink = 0;
bool m_textInputActive = false; // true = text box has focus and is accepting keyboard input
// Ely.by login state
std::string m_elybyUsername;
std::string m_elybyPassword;
std::string m_elyby2FACode;
// (2FA flag lives in m_authFlags->need2FA for async safety)
int m_elybyActiveField = 0; // 0=username, 1=password, 2=2fa code
// Target slot for splitscreen (0 = primary player, 1-3 = splitscreen).
// When > 0, account selection binds to that slot instead of slot 0.
int m_targetSlot = 0;
// Input guard: ignore input for the first N ticks after opening to avoid
// processing the button press that triggered the scene to open.
int m_inputGuardTicks = 6;
// Remove confirmation dialog (-1 = not showing, >=0 = account index pending removal)
int m_pendingRemoveIdx = -1;
std::string m_pendingRemoveUuid; // UUID captured at click time for stale-index safety
public:
// Shared buffer for virtual keyboard callback (public so the file-static
// callback function can access it; prevents UAF if scene is destroyed
// while the keyboard is open on a different UI group).
struct PendingKeyboardResult {
std::string value;
std::atomic<bool> ready{false};
std::atomic<bool> valid{true};
};
// Skin texture cache entry (public so static render helpers can access it)
struct SkinEntry {
std::atomic<int> textureId{-2}; // -2=not started, -1=downloading/failed, -3=file ready, >=0=texture ID
std::string filePath;
};
private:
std::shared_ptr<PendingKeyboardResult> m_pendingKBResult;
// Skin texture cache (key = UUID string)
std::unordered_map<std::string, std::shared_ptr<SkinEntry>> m_skinCache;
void EnsureSkinLoaded(const std::string& uuid);
public:
UIScene_MSAuth(int iPad, void* initData, UILayer* parentLayer);
~UIScene_MSAuth();
virtual EUIScene getSceneType() override { return eUIScene_MSAuth; }
virtual wstring getMoviePath() override { return L""; }
virtual bool hidesLowerScenes() override { return true; }
virtual bool blocksInput() override { return true; }
virtual bool hasFocus(int iPad) override { return bHasFocus; }
virtual bool needsReloaded() override { return false; }
virtual void updateTooltips() override;
virtual void tick() override;
virtual void render(S32 width, S32 height,
C4JRender::eViewportType viewport) override;
virtual void handleInput(int iPad, int key, bool repeat,
bool pressed, bool released,
bool& handled) override;
virtual void handlePress(F64 controlId, F64 childId) override;
#ifdef _WINDOWS64
virtual bool handleMouseClick(F32 x, F32 y) override;
#endif
};