add: implement authentication system ui and handshake.

This commit is contained in:
Matthew Toro 2026-03-28 04:48:18 -04:00
parent 7a24494d3c
commit d42da07721
23 changed files with 701 additions and 28 deletions

View file

@ -0,0 +1,101 @@
#include "stdafx.h"
#include "AuthScreen.h"
#include "Minecraft.h"
#include "User.h"
#include <fstream>
static constexpr auto PROFILES_FILE = L"auth_profiles.dat";
vector<AuthProfile> AuthProfileManager::profiles;
int AuthProfileManager::selectedProfile = -1;
void AuthProfileManager::load()
{
profiles.clear();
std::ifstream file(PROFILES_FILE, std::ios::binary);
if (!file) return;
uint32_t count = 0;
file.read(reinterpret_cast<char *>(&count), sizeof(count));
for (uint32_t i = 0; i < count && file.good(); i++)
{
AuthProfile p;
uint8_t type;
file.read(reinterpret_cast<char *>(&type), sizeof(type));
p.type = static_cast<AuthProfile::Type>(type);
auto readWstr = [&file]() -> wstring {
uint16_t len = 0;
file.read(reinterpret_cast<char *>(&len), sizeof(len));
wstring s(len, L'\0');
file.read(reinterpret_cast<char *>(s.data()), len * sizeof(wchar_t));
return s;
};
p.uid = readWstr();
p.username = readWstr();
p.token = readWstr();
profiles.push_back(std::move(p));
}
if (!profiles.empty())
selectedProfile = 0;
}
void AuthProfileManager::save()
{
std::ofstream file(PROFILES_FILE, std::ios::binary | std::ios::trunc);
if (!file) return;
uint32_t count = static_cast<uint32_t>(profiles.size());
file.write(reinterpret_cast<const char *>(&count), sizeof(count));
auto writeWstr = [&file](const wstring &s) {
uint16_t len = static_cast<uint16_t>(s.length());
file.write(reinterpret_cast<const char *>(&len), sizeof(len));
file.write(reinterpret_cast<const char *>(s.data()), len * sizeof(wchar_t));
};
for (const auto &p : profiles)
{
uint8_t type = static_cast<uint8_t>(p.type);
file.write(reinterpret_cast<const char *>(&type), sizeof(type));
writeWstr(p.uid);
writeWstr(p.username);
writeWstr(p.token);
}
}
void AuthProfileManager::addProfile(AuthProfile::Type type, const wstring &username)
{
profiles.push_back({type, L"offline_" + username, username, {}});
selectedProfile = static_cast<int>(profiles.size()) - 1;
save();
}
void AuthProfileManager::removeSelectedProfile()
{
if (selectedProfile < 0 || selectedProfile >= static_cast<int>(profiles.size()))
return;
profiles.erase(profiles.begin() + selectedProfile);
if (selectedProfile >= static_cast<int>(profiles.size()))
selectedProfile = static_cast<int>(profiles.size()) - 1;
save();
}
bool AuthProfileManager::applySelectedProfile()
{
if (selectedProfile < 0 || selectedProfile >= static_cast<int>(profiles.size()))
return false;
const auto &p = profiles[selectedProfile];
auto *mc = Minecraft::GetInstance();
if (mc->user)
delete mc->user;
mc->user = new User(p.username, p.token);
return true;
}

View file

@ -0,0 +1,30 @@
#pragma once
using namespace std;
struct AuthProfile
{
enum Type : uint8_t { MICROSOFT, ELYBY, OFFLINE };
Type type;
wstring uid;
wstring username;
wstring token;
};
class AuthProfileManager
{
private:
static vector<AuthProfile> profiles;
static int selectedProfile;
public:
static void load();
static void save();
static void addProfile(AuthProfile::Type type, const wstring &username);
static void removeSelectedProfile();
static bool applySelectedProfile();
static const vector<AuthProfile> &getProfiles() { return profiles; }
static int getSelectedIndex() { return selectedProfile; }
static void setSelectedIndex(int idx) { selectedProfile = idx; }
};

View file

@ -54,6 +54,8 @@
#include "PS3/Network/SonyVoiceChat.h"
#endif
#include "DLCTexturePack.h"
#include "..\Minecraft.World\HandshakeManager.h"
#include "..\Minecraft.World\AuthModule.h"
#ifdef _WINDOWS64
#include "Xbox\Network\NetworkPlayerXbox.h"
@ -140,6 +142,9 @@ ClientConnection::ClientConnection(Minecraft *minecraft, Socket *socket, int iUs
}
deferredEntityLinkPackets = vector<DeferredEntityLinkPacket>();
handshakeManager = nullptr;
authComplete = false;
}
bool ClientConnection::isPrimaryConnection() const
@ -202,6 +207,7 @@ ClientConnection::~ClientConnection()
delete connection;
delete random;
delete savedDataStorage;
delete handshakeManager;
}
void ClientConnection::tick()
@ -4129,3 +4135,34 @@ ClientConnection::DeferredEntityLinkPacket::DeferredEntityLinkPacket(shared_ptr<
m_recievedTick = GetTickCount();
m_packet = packet;
}
void ClientConnection::beginAuth()
{
handshakeManager = new HandshakeManager(false);
handshakeManager->registerModule(new MojangAuthModule());
handshakeManager->registerModule(new ElyByAuthModule());
handshakeManager->registerModule(new KeypairOfflineAuthModule());
handshakeManager->registerModule(new OfflineAuthModule());
auto initial = handshakeManager->createInitialPacket();
if (initial) send(initial);
}
void ClientConnection::handleAuth(const shared_ptr<AuthPacket> &packet)
{
if (done || authComplete) return;
if (!handshakeManager) return;
auto response = handshakeManager->handlePacket(packet);
if (response) send(response);
if (handshakeManager->isComplete())
{
authComplete = true;
}
else if (handshakeManager->isFailed())
{
done = true;
message = L"Auth handshake failed";
}
}

View file

@ -6,6 +6,7 @@ class MultiPlayerLevel;
class SavedDataStorage;
class Socket;
class MultiplayerLocalPlayer;
class HandshakeManager;
class ClientConnection : public PacketListener
{
@ -179,4 +180,10 @@ private:
static const int MAX_ENTITY_LINK_DEFERRAL_INTERVAL = 1000;
void checkDeferredEntityLinkPackets(int newEntityId);
public:
HandshakeManager *handshakeManager;
bool authComplete;
void beginAuth();
virtual void handleAuth(const shared_ptr<AuthPacket> &packet);
};

View file

@ -2948,7 +2948,7 @@ C4JStorage::EMessageResult UIController::RequestMessageBox(UINT uiTitle, UINT ui
int( *Func)(LPVOID,int,const C4JStorage::EMessageResult),LPVOID lpParam, WCHAR *pwchFormatString,DWORD dwFocusButton, bool bIsError)
{
MessageBoxInfo param;
MessageBoxInfo param = {};
param.uiTitle = uiTitle;
param.uiText = uiText;
param.uiOptionA = uiOptionA;

View file

@ -4,6 +4,7 @@
#include "..\..\..\Minecraft.World\Random.h"
#include "..\..\User.h"
#include "..\..\MinecraftServer.h"
#include "..\..\AuthScreen.h"
#include "UI.h"
#include "UIScene_MainMenu.h"
#ifdef __ORBIS__
@ -46,12 +47,12 @@ UIScene_MainMenu::UIScene_MainMenu(int iPad, void *initData, UILayer *parentLaye
if(ProfileManager.IsFullVersion())
{
m_bTrialVersion=false;
m_buttons[static_cast<int>(eControl_UnlockOrDLC)].init(IDS_DOWNLOADABLECONTENT,eControl_UnlockOrDLC);
m_buttons[static_cast<int>(eControl_UnlockOrDLC)].init(L"Auth",eControl_UnlockOrDLC);
}
else
{
m_bTrialVersion=true;
m_buttons[static_cast<int>(eControl_UnlockOrDLC)].init(IDS_UNLOCK_FULL_GAME,eControl_UnlockOrDLC);
m_buttons[static_cast<int>(eControl_UnlockOrDLC)].init(L"Auth",eControl_UnlockOrDLC);
}
#ifndef _DURANGO
@ -181,8 +182,8 @@ void UIScene_MainMenu::handleGainFocus(bool navBack)
if(navBack && ProfileManager.IsFullVersion())
{
// Replace the Unlock Full Game with Downloadable Content
m_buttons[static_cast<int>(eControl_UnlockOrDLC)].setLabel(IDS_DOWNLOADABLECONTENT);
// once again replacing the shop with auth. not a bad thing.
m_buttons[static_cast<int>(eControl_UnlockOrDLC)].setLabel(L"Auth");
}
#if TO_BE_IMPLEMENTED
@ -357,7 +358,7 @@ void UIScene_MainMenu::handlePress(F64 controlId, F64 childId)
ui.PlayUISFX(eSFX_Press);
m_eAction=eAction_RunUnlockOrDLC;
signInReturnedFunc = &UIScene_MainMenu::UnlockFullGame_SignInReturned;
RunAction(primaryPad);
break;
case eControl_Exit:
//CD - Added for audio
@ -438,8 +439,11 @@ void UIScene_MainMenu::RunAction(int iPad)
RunHelpAndOptions(iPad);
break;
case eAction_RunUnlockOrDLC:
RunUnlockOrDLC(iPad);
{
AuthProfileManager::load();
ShowAuthMenu(iPad, this);
break;
}
#ifdef _DURANGO
case eAction_RunXboxHelp:
// 4J: Launch the dummy xbox help application.
@ -450,6 +454,161 @@ void UIScene_MainMenu::RunAction(int iPad)
}
}
static int s_authPad = 0;
static const wchar_t *BuildAuthProfileText()
{
static wstring text;
const auto &profiles = AuthProfileManager::getProfiles();
int sel = AuthProfileManager::getSelectedIndex();
if (profiles.empty())
{
text = L"No profiles - press Add to create one";
}
else
{
text.clear();
for (int i = 0; i < static_cast<int>(profiles.size()); i++)
{
const auto &p = profiles[i];
const wchar_t *type = (p.type == AuthProfile::MICROSOFT) ? L"[MS]"
: (p.type == AuthProfile::ELYBY) ? L"[Ely]" : L"[Off]";
if (i > 0) text += L"\n";
text += (i == sel) ? L"> " : L" ";
text += wstring(type) + L" " + p.username;
}
}
return text.c_str();
}
void UIScene_MainMenu::ShowAuthMenu(int iPad, void *pClass)
{
s_authPad = iPad;
static const wchar_t *authOptions[] = { L"Next", L"Use", L"Add", L"Back" };
MessageBoxInfo param = {};
param.uiOptionC = 4;
param.dwPad = iPad;
param.Func = &UIScene_MainMenu::AuthMenuReturned;
param.lpParam = pClass;
param.rawTitle = L"Authentication";
param.rawText = BuildAuthProfileText();
param.rawOptions = authOptions;
ui.NavigateToScene(iPad, eUIScene_MessageBox, &param, eUILayer_Alert, eUIGroup_Fullscreen);
UIScene *scene = ui.FindScene(eUIScene_MessageBox);
if (scene)
static_cast<UIScene_MessageBox *>(scene)->setKeepOpen(true);
}
void UIScene_MainMenu::ShowAuthAddMenu(int iPad, void *pClass)
{
static const wchar_t *addOptions[] = { L"Microsoft Auth", L"Ely.by Auth", L"Add Offline User", L"Back" };
MessageBoxInfo param = {};
param.uiOptionC = 4;
param.dwPad = iPad;
param.Func = &UIScene_MainMenu::AuthAddMenuReturned;
param.lpParam = pClass;
param.rawTitle = L"Add Profile";
param.rawText = L"Select an auth type";
param.rawOptions = addOptions;
ui.NavigateToScene(iPad, eUIScene_MessageBox, &param, eUILayer_Alert, eUIGroup_Fullscreen);
}
int UIScene_MainMenu::AuthMenuReturned(LPVOID lpParam, int iPad, const C4JStorage::EMessageResult result)
{
const auto &profiles = AuthProfileManager::getProfiles();
switch (result)
{
case C4JStorage::EMessage_ResultAccept:
{
if (!profiles.empty())
{
int next = (AuthProfileManager::getSelectedIndex() + 1) % static_cast<int>(profiles.size());
AuthProfileManager::setSelectedIndex(next);
}
if (auto *scene = ui.FindScene(eUIScene_MessageBox))
static_cast<UIScene_MessageBox *>(scene)->updateContent(BuildAuthProfileText());
return 0;
}
case C4JStorage::EMessage_ResultDecline:
{
ui.NavigateBack(iPad);
AuthProfileManager::applySelectedProfile();
break;
}
case C4JStorage::EMessage_ResultThirdOption:
{
ui.NavigateBack(iPad);
ShowAuthAddMenu(iPad, lpParam);
break;
}
default:
{
ui.NavigateBack(iPad);
break;
}
}
return 0;
}
int UIScene_MainMenu::AuthAddMenuReturned(LPVOID lpParam, int iPad, const C4JStorage::EMessageResult result)
{
switch (result)
{
case C4JStorage::EMessage_ResultAccept:
AuthProfileManager::addProfile(AuthProfile::MICROSOFT, L"Player");
ShowAuthMenu(iPad, lpParam);
break;
case C4JStorage::EMessage_ResultDecline:
AuthProfileManager::addProfile(AuthProfile::ELYBY, L"Player");
ShowAuthMenu(iPad, lpParam);
break;
case C4JStorage::EMessage_ResultThirdOption:
{
#ifdef _WINDOWS64
UIKeyboardInitData kbData;
kbData.title = L"Enter Username";
kbData.defaultText = L"Player";
kbData.maxChars = 16;
kbData.callback = &UIScene_MainMenu::AuthKeyboardReturned;
kbData.lpParam = lpParam;
kbData.pcMode = g_KBMInput.IsKBMActive();
ui.NavigateToScene(iPad, eUIScene_Keyboard, &kbData);
#else
InputManager.RequestKeyboard(L"Enter Username", L"Player", (DWORD)0, 16, &UIScene_MainMenu::AuthKeyboardReturned, lpParam, C_4JInput::EKeyboardMode_Default);
#endif
break;
}
default:
ShowAuthMenu(iPad, lpParam);
break;
}
return 0;
}
int UIScene_MainMenu::AuthKeyboardReturned(LPVOID lpParam, const bool bRes)
{
if (bRes)
{
uint16_t ui16Text[128];
ZeroMemory(ui16Text, 128 * sizeof(uint16_t));
#ifdef _WINDOWS64
Win64_GetKeyboardText(ui16Text, 128);
#else
InputManager.GetText(ui16Text);
#endif
if (ui16Text[0] != 0)
{
wchar_t wName[128] = {};
for (int k = 0; k < 127 && ui16Text[k]; k++)
wName[k] = static_cast<wchar_t>(ui16Text[k]);
AuthProfileManager::addProfile(AuthProfile::OFFLINE, wName);
}
}
ShowAuthMenu(s_authPad, lpParam);
return 0;
}
void UIScene_MainMenu::customDraw(IggyCustomDrawCallbackRegion *region)
{
if(wcscmp((wchar_t *)region->name,L"Splash")==0)
@ -1946,7 +2105,7 @@ void UIScene_MainMenu::tick()
if(ProfileManager.IsFullVersion())
{
m_bTrialVersion=false;
m_buttons[(int)eControl_UnlockOrDLC].init(app.GetString(IDS_DOWNLOADABLECONTENT),eControl_UnlockOrDLC);
m_buttons[(int)eControl_UnlockOrDLC].init(L"Auth",eControl_UnlockOrDLC);
}
}
#endif

View file

@ -144,7 +144,13 @@ private:
void RunHelpAndOptions(int iPad);
void RunAction(int iPad);
static void ShowAuthMenu(int iPad, void *pClass);
static void ShowAuthAddMenu(int iPad, void *pClass);
static int AuthMenuReturned(void *pParam, int iPad, C4JStorage::EMessageResult result);
static int AuthAddMenuReturned(void *pParam, int iPad, C4JStorage::EMessageResult result);
static int AuthKeyboardReturned(LPVOID lpParam, const bool bRes);
static void LoadTrial();
#ifdef _XBOX_ONE

View file

@ -23,28 +23,29 @@ UIScene_MessageBox::UIScene_MessageBox(int iPad, void *initData, UILayer *parent
int buttonIndex = 0;
if(param->uiOptionC > 3)
{
m_buttonButtons[eControl_Button0].init(app.GetString(param->uiOptionA[buttonIndex]),buttonIndex);
m_buttonButtons[eControl_Button0].init(param->rawOptions ? param->rawOptions[buttonIndex] : app.GetString(param->uiOptionA[buttonIndex]),buttonIndex);
++buttonIndex;
}
if(param->uiOptionC > 2)
{
m_buttonButtons[eControl_Button1].init(app.GetString(param->uiOptionA[buttonIndex]),buttonIndex);
m_buttonButtons[eControl_Button1].init(param->rawOptions ? param->rawOptions[buttonIndex] : app.GetString(param->uiOptionA[buttonIndex]),buttonIndex);
++buttonIndex;
}
if(param->uiOptionC > 1)
{
m_buttonButtons[eControl_Button2].init(app.GetString(param->uiOptionA[buttonIndex]),buttonIndex);
m_buttonButtons[eControl_Button2].init(param->rawOptions ? param->rawOptions[buttonIndex] : app.GetString(param->uiOptionA[buttonIndex]),buttonIndex);
++buttonIndex;
}
m_buttonButtons[eControl_Button3].init(app.GetString(param->uiOptionA[buttonIndex]),buttonIndex);
m_buttonButtons[eControl_Button3].init(param->rawOptions ? param->rawOptions[buttonIndex] : app.GetString(param->uiOptionA[buttonIndex]),buttonIndex);
m_labelTitle.init(app.GetString(param->uiTitle));
m_labelContent.init(app.GetString(param->uiText));
m_labelTitle.init(param->rawTitle ? param->rawTitle : app.GetString(param->uiTitle));
m_labelContent.init(param->rawText ? param->rawText : app.GetString(param->uiText));
out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcAutoResize , 0 , nullptr );
m_Func = param->Func;
m_lpParam = param->lpParam;
m_keepOpen = false;
parentLayer->addComponent(iPad,eUIComponent_MenuBackground);
@ -99,7 +100,7 @@ void UIScene_MessageBox::handleInput(int iPad, int key, bool repeat, bool presse
case ACTION_MENU_CANCEL:
if(pressed)
{
navigateBack();
if(!m_keepOpen) navigateBack();
if(m_Func) m_Func(m_lpParam, iPad, C4JStorage::EMessage_Cancelled);
}
break;
@ -136,10 +137,15 @@ void UIScene_MessageBox::handlePress(F64 controlId, F64 childId)
break;
}
navigateBack();
if(!m_keepOpen) navigateBack();
if(m_Func) m_Func(m_lpParam, m_iPad, result);
}
void UIScene_MessageBox::updateContent(const wchar_t *text)
{
m_labelContent.init(text);
}
bool UIScene_MessageBox::hasFocus(int iPad)
{
// 4J-JEV: Fix for PS4 #5204 - [TRC][R4033] The application can be locked up by second user logging out of the system.

View file

@ -18,6 +18,7 @@ private:
int( *m_Func)(LPVOID,int,const C4JStorage::EMessageResult);
LPVOID m_lpParam;
int m_buttonCount;
bool m_keepOpen;
UIControl_Button m_buttonButtons[eControl_COUNT];
UIControl_Label m_labelTitle, m_labelContent;
@ -57,4 +58,8 @@ public:
protected:
void handlePress(F64 controlId, F64 childId);
public:
void updateContent(const wchar_t *text);
void setKeepOpen(bool keep) { m_keepOpen = keep; }
};

View file

@ -485,6 +485,9 @@ typedef struct _MessageBoxInfo
//C4JStringTable *pStringTable; // 4J Stu - We don't need this for our internal message boxes
WCHAR *pwchFormatString;
DWORD dwFocusButton;
const wchar_t *rawTitle;
const wchar_t *rawText;
const wchar_t **rawOptions;
} MessageBoxInfo;
typedef struct _DLCOffersParam

View file

@ -11,13 +11,14 @@
ConnectScreen::ConnectScreen(Minecraft *minecraft, const wstring& ip, int port)
{
aborted = false;
preLoginSent = false;
// System.out.println("Connecting to " + ip + ", " + port);
minecraft->setLevel(nullptr);
#if 1
// 4J - removed from separate thread, but need to investigate what we actually need here
connection = new ClientConnection(minecraft, ip, port);
if (aborted) return;
connection->send(std::make_shared<PreLoginPacket>(minecraft->user->name));
connection->beginAuth();
#else
new Thread() {
@ -47,6 +48,11 @@ void ConnectScreen::tick()
{
if (connection != nullptr)
{
if (!preLoginSent && connection->authComplete)
{
preLoginSent = true;
connection->send(std::make_shared<PreLoginPacket>(minecraft->user->name));
}
connection->tick();
}
}

View file

@ -10,6 +10,7 @@ class ConnectScreen : public Screen
private:
ClientConnection *connection;
bool aborted;
bool preLoginSent;
public:
ConnectScreen(Minecraft *minecraft, const wstring& ip, int port);
virtual void tick();

View file

@ -14,6 +14,8 @@
#include "..\Minecraft.World\net.minecraft.world.item.h"
#include "..\Minecraft.World\SharedConstants.h"
#include "Settings.h"
#include "..\Minecraft.World\HandshakeManager.h"
#include "..\Minecraft.World\AuthModule.h"
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
#include "..\Minecraft.Server\ServerLogManager.h"
#include "..\Minecraft.Server\Access\Access.h"
@ -59,10 +61,14 @@ PendingConnection::PendingConnection(MinecraftServer *server, Socket *socket, co
this->server = server;
connection = new Connection(socket, id, this);
connection->fakeLag = FAKE_LAG;
handshakeManager = nullptr;
authComplete = false;
}
PendingConnection::~PendingConnection()
{
delete handshakeManager;
delete connection;
}
@ -98,6 +104,13 @@ void PendingConnection::disconnect(DisconnectPacket::eDisconnectReason reason)
void PendingConnection::handlePreLogin(shared_ptr<PreLoginPacket> packet)
{
if (handshakeManager && !authComplete)
{
app.DebugPrintf("PendingConnection: PreLogin received before auth complete, disconnecting\n");
disconnect(DisconnectPacket::eDisconnect_Closed);
return;
}
if (packet->m_netcodeVersion != MINECRAFT_NET_VERSION)
{
app.DebugPrintf("Netcode version is %d not equal to %d\n", packet->m_netcodeVersion, MINECRAFT_NET_VERSION);
@ -394,3 +407,33 @@ bool PendingConnection::isDisconnected()
{
return done;
}
void PendingConnection::initAuth()
{
handshakeManager = new HandshakeManager(true);
handshakeManager->registerModule(new MojangAuthModule());
handshakeManager->registerModule(new ElyByAuthModule());
handshakeManager->registerModule(new KeypairOfflineAuthModule());
handshakeManager->registerModule(new OfflineAuthModule());
}
void PendingConnection::handleAuth(const shared_ptr<AuthPacket> &packet)
{
if (done || authComplete) return;
if (!handshakeManager)
initAuth();
auto response = handshakeManager->handlePacket(packet);
if (response) send(response);
if (handshakeManager->isComplete())
{
authComplete = true;
name = std::move(handshakeManager->finalUsername);
}
else if (handshakeManager->isFailed())
{
disconnect(DisconnectPacket::eDisconnect_Closed);
}
}

View file

@ -5,6 +5,7 @@ class Socket;
class LoginPacket;
class Connection;
class Random;
class HandshakeManager;
using namespace std;
class PendingConnection : public PacketListener
@ -43,6 +44,10 @@ public:
wstring getName();
virtual bool isServerPacketListener();
virtual bool isDisconnected();
HandshakeManager *handshakeManager;
bool authComplete;
void initAuth();
virtual void handleAuth(const shared_ptr<AuthPacket> &packet);
private:
void sendPreLoginResponse();

View file

@ -969,6 +969,8 @@ set(_MINECRAFT_CLIENT_COMMON_NET_MINECRAFT_CLIENT_SKINS
source_group("net/minecraft/client/skins" FILES ${_MINECRAFT_CLIENT_COMMON_NET_MINECRAFT_CLIENT_SKINS})
set(_MINECRAFT_CLIENT_COMMON_NET_MINECRAFT_CLIENT_TITLE
"${CMAKE_CURRENT_SOURCE_DIR}/AuthScreen.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/AuthScreen.h"
"${CMAKE_CURRENT_SOURCE_DIR}/TitleScreen.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TitleScreen.h"
)

View file

@ -5,6 +5,7 @@ set(_MINECRAFT_SERVER_COMMON_ROOT
"${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.Client/AllowAllCuller.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.Client/ArchiveFile.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.Client/ArrowRenderer.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.Client/AuthScreen.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.Client/BatModel.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.Client/BatRenderer.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.Client/BeaconRenderer.cpp"

View file

@ -50,8 +50,6 @@ vector<wstring> MojangAuthModule::supportedVariations()
return {L"java", L"bedrock"};
}
// --- KeypairOfflineAuthModule ---
const wchar_t *KeypairOfflineAuthModule::schemeName() { return L"mcconsoles:keypair_offline"; }
vector<wstring> KeypairOfflineAuthModule::supportedVariations()

View file

@ -26,10 +26,10 @@ void AuthPacket::write(DataOutputStream *dos)
{
dos->writeByte(static_cast<byte>(stage));
dos->writeShort(static_cast<short>(fields.size()));
for (const auto &field : fields)
for (const auto &[key, value] : fields)
{
writeUtf(field.first, dos);
writeUtf(field.second, dos);
writeUtf(key, dos);
writeUtf(value, dos);
}
}
@ -41,7 +41,7 @@ void AuthPacket::handle(PacketListener *listener)
int AuthPacket::getEstimatedSize()
{
int size = 1 + 2;
for (const auto &field : fields)
size += 4 + static_cast<int>((field.first.length() + field.second.length()) * sizeof(wchar_t));
for (const auto &[key, value] : fields)
size += 4 + static_cast<int>((key.length() + value.length()) * sizeof(wchar_t));
return size;
}

View file

@ -0,0 +1,205 @@
#include "stdafx.h"
#include "HandshakeManager.h"
#include "AuthModule.h"
static constexpr auto PROTOCOL_VERSION = L"1.0";
HandshakeManager::HandshakeManager(bool isServer)
: isServer(isServer), state(HandshakeState::IDLE), activeModule(nullptr)
{
}
HandshakeManager::~HandshakeManager()
{
for (auto &[name, module] : modules)
delete module;
}
void HandshakeManager::registerModule(AuthModule *module)
{
modules[module->schemeName()] = module;
}
shared_ptr<AuthPacket> HandshakeManager::handlePacket(const shared_ptr<AuthPacket> &packet)
{
return isServer ? handleServer(packet) : handleClient(packet);
}
shared_ptr<AuthPacket> HandshakeManager::createInitialPacket()
{
state = HandshakeState::VERSION_SENT;
return makePacket(AuthStage::ANNOUNCE_VERSION, {{L"version", PROTOCOL_VERSION}});
}
shared_ptr<AuthPacket> HandshakeManager::handleServer(const shared_ptr<AuthPacket> &packet)
{
switch (packet->stage)
{
case AuthStage::ANNOUNCE_VERSION:
{
protocolVersion = L"";
for (const auto &[k, v] : packet->fields)
if (k == L"version") protocolVersion = v;
if (protocolVersion != PROTOCOL_VERSION)
return fail();
// Pick first registered module as the scheme
if (modules.empty())
return fail();
activeModule = modules.begin()->second;
state = HandshakeState::SCHEME_DECLARED;
return makePacket(AuthStage::DECLARE_SCHEME, {
{L"version", PROTOCOL_VERSION},
{L"scheme", activeModule->schemeName()}
});
}
case AuthStage::ACCEPT_SCHEME:
{
wstring variation;
for (const auto &[k, v] : packet->fields)
if (k == L"variation") variation = v;
activeVariation = variation;
state = HandshakeState::SETTINGS_SENT;
auto settings = activeModule->getSettings(activeVariation);
return makePacket(AuthStage::SCHEME_SETTINGS, std::move(settings));
}
case AuthStage::BEGIN_AUTH:
{
state = HandshakeState::AUTH_IN_PROGRESS;
return nullptr;
}
case AuthStage::AUTH_DATA:
{
wstring uid, username;
if (!activeModule->onAuthData(packet->fields, uid, username))
return fail();
state = HandshakeState::AUTH_DATA_EXCHANGED;
return nullptr;
}
case AuthStage::AUTH_DONE:
{
wstring uid, username;
for (const auto &[k, v] : packet->fields)
{
if (k == L"uid") uid = v;
else if (k == L"username") username = v;
}
if (!activeModule->validate(uid, username))
return fail();
finalUid = uid;
finalUsername = username;
state = HandshakeState::IDENTITY_ASSIGNED;
return makePacket(AuthStage::ASSIGN_IDENTITY, {
{L"uid", finalUid},
{L"username", finalUsername}
});
}
case AuthStage::CONFIRM_IDENTITY:
{
wstring uid, username;
for (const auto &[k, v] : packet->fields)
{
if (k == L"uid") uid = v;
else if (k == L"username") username = v;
}
if (uid != finalUid || username != finalUsername)
return fail();
state = HandshakeState::COMPLETE;
return makePacket(AuthStage::AUTH_SUCCESS);
}
default:
return fail();
}
}
shared_ptr<AuthPacket> HandshakeManager::handleClient(const shared_ptr<AuthPacket> &packet)
{
switch (packet->stage)
{
case AuthStage::DECLARE_SCHEME:
{
wstring scheme;
for (const auto &[k, v] : packet->fields)
{
if (k == L"version") protocolVersion = v;
else if (k == L"scheme") scheme = v;
}
if (protocolVersion != PROTOCOL_VERSION)
return fail();
auto it = modules.find(scheme);
if (it == modules.end())
return fail();
activeModule = it->second;
auto variations = activeModule->supportedVariations();
activeVariation = variations.empty() ? L"" : variations[0];
state = HandshakeState::SCHEME_ACCEPTED;
return makePacket(AuthStage::ACCEPT_SCHEME, {{L"variation", activeVariation}});
}
case AuthStage::SCHEME_SETTINGS:
{
state = HandshakeState::AUTH_IN_PROGRESS;
return makePacket(AuthStage::BEGIN_AUTH);
}
case AuthStage::ASSIGN_IDENTITY:
{
for (const auto &[k, v] : packet->fields)
{
if (k == L"uid") finalUid = v;
else if (k == L"username") finalUsername = v;
}
state = HandshakeState::IDENTITY_CONFIRMED;
return makePacket(AuthStage::CONFIRM_IDENTITY, {
{L"uid", finalUid},
{L"username", finalUsername}
});
}
case AuthStage::AUTH_SUCCESS:
{
state = HandshakeState::COMPLETE;
return nullptr;
}
case AuthStage::AUTH_FAILURE:
{
state = HandshakeState::FAILED;
return nullptr;
}
default:
return fail();
}
}
shared_ptr<AuthPacket> HandshakeManager::makePacket(AuthStage stage, vector<pair<wstring, wstring>> fields)
{
return std::make_shared<AuthPacket>(stage, std::move(fields));
}
shared_ptr<AuthPacket> HandshakeManager::fail()
{
state = HandshakeState::FAILED;
return makePacket(AuthStage::AUTH_FAILURE);
}

View file

@ -0,0 +1,57 @@
#pragma once
using namespace std;
#include <string>
#include <unordered_map>
#include <memory>
#include "AuthPackets.h"
class AuthModule;
enum class HandshakeState : uint8_t
{
IDLE,
VERSION_SENT,
SCHEME_DECLARED,
SCHEME_ACCEPTED,
SETTINGS_SENT,
AUTH_IN_PROGRESS,
AUTH_DATA_EXCHANGED,
IDENTITY_ASSIGNED,
IDENTITY_CONFIRMED,
COMPLETE,
FAILED
};
class HandshakeManager
{
private:
bool isServer;
HandshakeState state;
unordered_map<wstring, AuthModule *> modules;
AuthModule *activeModule;
wstring activeVariation;
wstring protocolVersion;
public:
wstring finalUid;
wstring finalUsername;
HandshakeManager(bool isServer);
~HandshakeManager();
void registerModule(AuthModule *module);
shared_ptr<AuthPacket> handlePacket(const shared_ptr<AuthPacket> &packet);
shared_ptr<AuthPacket> createInitialPacket();
bool isComplete() const { return state == HandshakeState::COMPLETE; }
bool isFailed() const { return state == HandshakeState::FAILED; }
HandshakeState getState() const { return state; }
private:
shared_ptr<AuthPacket> handleServer(const shared_ptr<AuthPacket> &packet);
shared_ptr<AuthPacket> handleClient(const shared_ptr<AuthPacket> &packet);
shared_ptr<AuthPacket> makePacket(AuthStage stage, vector<pair<wstring, wstring>> fields = {});
shared_ptr<AuthPacket> fail();
};

View file

@ -492,7 +492,7 @@ void PacketListener::handleGameCommand(shared_ptr<GameCommandPacket> packet)
onUnhandledPacket( (shared_ptr<Packet> ) packet);
}
void PacketListener::handleAuth(shared_ptr<AuthPacket> packet)
void PacketListener::handleAuth(const shared_ptr<AuthPacket> &packet)
{
onUnhandledPacket( (shared_ptr<Packet> ) packet);
}

View file

@ -229,6 +229,5 @@ public:
virtual void handleXZ(shared_ptr<XZPacket> packet);
virtual void handleGameCommand(shared_ptr<GameCommandPacket> packet);
// MCConsoles Auth
virtual void handleAuth(shared_ptr<AuthPacket> packet);
virtual void handleAuth(const shared_ptr<AuthPacket> &packet);
};

View file

@ -318,6 +318,8 @@ set(_MINECRAFT_WORLD_COMMON_NET_MINECRAFT_NETWORK_PACKET
"${CMAKE_CURRENT_SOURCE_DIR}/GameEventPacket.h"
"${CMAKE_CURRENT_SOURCE_DIR}/GetInfoPacket.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/GetInfoPacket.h"
"${CMAKE_CURRENT_SOURCE_DIR}/HandshakeManager.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/HandshakeManager.h"
"${CMAKE_CURRENT_SOURCE_DIR}/InteractPacket.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/InteractPacket.h"
"${CMAKE_CURRENT_SOURCE_DIR}/KeepAlivePacket.cpp"