diff --git a/Minecraft.Client/AuthScreen.cpp b/Minecraft.Client/AuthScreen.cpp new file mode 100644 index 00000000..706d454b --- /dev/null +++ b/Minecraft.Client/AuthScreen.cpp @@ -0,0 +1,101 @@ +#include "stdafx.h" +#include "AuthScreen.h" +#include "Minecraft.h" +#include "User.h" +#include + +static constexpr auto PROFILES_FILE = L"auth_profiles.dat"; + +vector 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(&count), sizeof(count)); + + for (uint32_t i = 0; i < count && file.good(); i++) + { + AuthProfile p; + uint8_t type; + file.read(reinterpret_cast(&type), sizeof(type)); + p.type = static_cast(type); + + auto readWstr = [&file]() -> wstring { + uint16_t len = 0; + file.read(reinterpret_cast(&len), sizeof(len)); + wstring s(len, L'\0'); + file.read(reinterpret_cast(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(profiles.size()); + file.write(reinterpret_cast(&count), sizeof(count)); + + auto writeWstr = [&file](const wstring &s) { + uint16_t len = static_cast(s.length()); + file.write(reinterpret_cast(&len), sizeof(len)); + file.write(reinterpret_cast(s.data()), len * sizeof(wchar_t)); + }; + + for (const auto &p : profiles) + { + uint8_t type = static_cast(p.type); + file.write(reinterpret_cast(&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(profiles.size()) - 1; + save(); +} + +void AuthProfileManager::removeSelectedProfile() +{ + if (selectedProfile < 0 || selectedProfile >= static_cast(profiles.size())) + return; + + profiles.erase(profiles.begin() + selectedProfile); + if (selectedProfile >= static_cast(profiles.size())) + selectedProfile = static_cast(profiles.size()) - 1; + save(); +} + +bool AuthProfileManager::applySelectedProfile() +{ + if (selectedProfile < 0 || selectedProfile >= static_cast(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; +} diff --git a/Minecraft.Client/AuthScreen.h b/Minecraft.Client/AuthScreen.h new file mode 100644 index 00000000..f31f294a --- /dev/null +++ b/Minecraft.Client/AuthScreen.h @@ -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 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 &getProfiles() { return profiles; } + static int getSelectedIndex() { return selectedProfile; } + static void setSelectedIndex(int idx) { selectedProfile = idx; } +}; diff --git a/Minecraft.Client/ClientConnection.cpp b/Minecraft.Client/ClientConnection.cpp index a80af5d2..bdace064 100644 --- a/Minecraft.Client/ClientConnection.cpp +++ b/Minecraft.Client/ClientConnection.cpp @@ -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(); + + 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 &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"; + } +} diff --git a/Minecraft.Client/ClientConnection.h b/Minecraft.Client/ClientConnection.h index 3448496d..69ed2805 100644 --- a/Minecraft.Client/ClientConnection.h +++ b/Minecraft.Client/ClientConnection.h @@ -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 &packet); }; \ No newline at end of file diff --git a/Minecraft.Client/Common/UI/UIController.cpp b/Minecraft.Client/Common/UI/UIController.cpp index b12ea5e7..bf81472c 100644 --- a/Minecraft.Client/Common/UI/UIController.cpp +++ b/Minecraft.Client/Common/UI/UIController.cpp @@ -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; diff --git a/Minecraft.Client/Common/UI/UIScene_MainMenu.cpp b/Minecraft.Client/Common/UI/UIScene_MainMenu.cpp index 93f1edf1..f9dbc6eb 100644 --- a/Minecraft.Client/Common/UI/UIScene_MainMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_MainMenu.cpp @@ -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(eControl_UnlockOrDLC)].init(IDS_DOWNLOADABLECONTENT,eControl_UnlockOrDLC); + m_buttons[static_cast(eControl_UnlockOrDLC)].init(L"Auth",eControl_UnlockOrDLC); } else { m_bTrialVersion=true; - m_buttons[static_cast(eControl_UnlockOrDLC)].init(IDS_UNLOCK_FULL_GAME,eControl_UnlockOrDLC); + m_buttons[static_cast(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(eControl_UnlockOrDLC)].setLabel(IDS_DOWNLOADABLECONTENT); + // once again replacing the shop with auth. not a bad thing. + m_buttons[static_cast(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(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, ¶m, eUILayer_Alert, eUIGroup_Fullscreen); + UIScene *scene = ui.FindScene(eUIScene_MessageBox); + if (scene) + static_cast(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, ¶m, 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(profiles.size()); + AuthProfileManager::setSelectedIndex(next); + } + if (auto *scene = ui.FindScene(eUIScene_MessageBox)) + static_cast(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(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 diff --git a/Minecraft.Client/Common/UI/UIScene_MainMenu.h b/Minecraft.Client/Common/UI/UIScene_MainMenu.h index 2b49a44b..4a059cce 100644 --- a/Minecraft.Client/Common/UI/UIScene_MainMenu.h +++ b/Minecraft.Client/Common/UI/UIScene_MainMenu.h @@ -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 diff --git a/Minecraft.Client/Common/UI/UIScene_MessageBox.cpp b/Minecraft.Client/Common/UI/UIScene_MessageBox.cpp index 7a0749d4..9767b83b 100644 --- a/Minecraft.Client/Common/UI/UIScene_MessageBox.cpp +++ b/Minecraft.Client/Common/UI/UIScene_MessageBox.cpp @@ -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. diff --git a/Minecraft.Client/Common/UI/UIScene_MessageBox.h b/Minecraft.Client/Common/UI/UIScene_MessageBox.h index c10f6ab8..213a825d 100644 --- a/Minecraft.Client/Common/UI/UIScene_MessageBox.h +++ b/Minecraft.Client/Common/UI/UIScene_MessageBox.h @@ -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; } }; \ No newline at end of file diff --git a/Minecraft.Client/Common/UI/UIStructs.h b/Minecraft.Client/Common/UI/UIStructs.h index 99c3d7bd..28fcbcd1 100644 --- a/Minecraft.Client/Common/UI/UIStructs.h +++ b/Minecraft.Client/Common/UI/UIStructs.h @@ -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 diff --git a/Minecraft.Client/ConnectScreen.cpp b/Minecraft.Client/ConnectScreen.cpp index 18d50a52..0a19291d 100644 --- a/Minecraft.Client/ConnectScreen.cpp +++ b/Minecraft.Client/ConnectScreen.cpp @@ -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(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(minecraft->user->name)); + } connection->tick(); } } diff --git a/Minecraft.Client/ConnectScreen.h b/Minecraft.Client/ConnectScreen.h index 07e65cbb..76424a07 100644 --- a/Minecraft.Client/ConnectScreen.h +++ b/Minecraft.Client/ConnectScreen.h @@ -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(); diff --git a/Minecraft.Client/PendingConnection.cpp b/Minecraft.Client/PendingConnection.cpp index f24086c1..c10edb90 100644 --- a/Minecraft.Client/PendingConnection.cpp +++ b/Minecraft.Client/PendingConnection.cpp @@ -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 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 &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); + } +} diff --git a/Minecraft.Client/PendingConnection.h b/Minecraft.Client/PendingConnection.h index e8a493b0..53724896 100644 --- a/Minecraft.Client/PendingConnection.h +++ b/Minecraft.Client/PendingConnection.h @@ -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 &packet); private: void sendPreLoginResponse(); diff --git a/Minecraft.Client/cmake/sources/Common.cmake b/Minecraft.Client/cmake/sources/Common.cmake index 3936a9c3..de727931 100644 --- a/Minecraft.Client/cmake/sources/Common.cmake +++ b/Minecraft.Client/cmake/sources/Common.cmake @@ -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" ) diff --git a/Minecraft.Server/cmake/sources/Common.cmake b/Minecraft.Server/cmake/sources/Common.cmake index 06aa0bfe..0d6f382b 100644 --- a/Minecraft.Server/cmake/sources/Common.cmake +++ b/Minecraft.Server/cmake/sources/Common.cmake @@ -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" diff --git a/Minecraft.World/AuthModule.cpp b/Minecraft.World/AuthModule.cpp index 0d5ffb0f..35bd3e85 100644 --- a/Minecraft.World/AuthModule.cpp +++ b/Minecraft.World/AuthModule.cpp @@ -50,8 +50,6 @@ vector MojangAuthModule::supportedVariations() return {L"java", L"bedrock"}; } -// --- KeypairOfflineAuthModule --- - const wchar_t *KeypairOfflineAuthModule::schemeName() { return L"mcconsoles:keypair_offline"; } vector KeypairOfflineAuthModule::supportedVariations() diff --git a/Minecraft.World/AuthPackets.cpp b/Minecraft.World/AuthPackets.cpp index 4bd2bb95..4490b264 100644 --- a/Minecraft.World/AuthPackets.cpp +++ b/Minecraft.World/AuthPackets.cpp @@ -26,10 +26,10 @@ void AuthPacket::write(DataOutputStream *dos) { dos->writeByte(static_cast(stage)); dos->writeShort(static_cast(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((field.first.length() + field.second.length()) * sizeof(wchar_t)); + for (const auto &[key, value] : fields) + size += 4 + static_cast((key.length() + value.length()) * sizeof(wchar_t)); return size; } diff --git a/Minecraft.World/HandshakeManager.cpp b/Minecraft.World/HandshakeManager.cpp new file mode 100644 index 00000000..2da686db --- /dev/null +++ b/Minecraft.World/HandshakeManager.cpp @@ -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 HandshakeManager::handlePacket(const shared_ptr &packet) +{ + return isServer ? handleServer(packet) : handleClient(packet); +} + +shared_ptr HandshakeManager::createInitialPacket() +{ + state = HandshakeState::VERSION_SENT; + return makePacket(AuthStage::ANNOUNCE_VERSION, {{L"version", PROTOCOL_VERSION}}); +} + +shared_ptr HandshakeManager::handleServer(const shared_ptr &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 HandshakeManager::handleClient(const shared_ptr &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 HandshakeManager::makePacket(AuthStage stage, vector> fields) +{ + return std::make_shared(stage, std::move(fields)); +} + +shared_ptr HandshakeManager::fail() +{ + state = HandshakeState::FAILED; + return makePacket(AuthStage::AUTH_FAILURE); +} diff --git a/Minecraft.World/HandshakeManager.h b/Minecraft.World/HandshakeManager.h new file mode 100644 index 00000000..9e9da1c8 --- /dev/null +++ b/Minecraft.World/HandshakeManager.h @@ -0,0 +1,57 @@ +#pragma once +using namespace std; + +#include +#include +#include +#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 modules; + AuthModule *activeModule; + wstring activeVariation; + wstring protocolVersion; + +public: + wstring finalUid; + wstring finalUsername; + + HandshakeManager(bool isServer); + ~HandshakeManager(); + + void registerModule(AuthModule *module); + shared_ptr handlePacket(const shared_ptr &packet); + shared_ptr createInitialPacket(); + + bool isComplete() const { return state == HandshakeState::COMPLETE; } + bool isFailed() const { return state == HandshakeState::FAILED; } + HandshakeState getState() const { return state; } + +private: + shared_ptr handleServer(const shared_ptr &packet); + shared_ptr handleClient(const shared_ptr &packet); + + shared_ptr makePacket(AuthStage stage, vector> fields = {}); + shared_ptr fail(); +}; diff --git a/Minecraft.World/PacketListener.cpp b/Minecraft.World/PacketListener.cpp index 490922c7..bd340add 100644 --- a/Minecraft.World/PacketListener.cpp +++ b/Minecraft.World/PacketListener.cpp @@ -492,7 +492,7 @@ void PacketListener::handleGameCommand(shared_ptr packet) onUnhandledPacket( (shared_ptr ) packet); } -void PacketListener::handleAuth(shared_ptr packet) +void PacketListener::handleAuth(const shared_ptr &packet) { onUnhandledPacket( (shared_ptr ) packet); } diff --git a/Minecraft.World/PacketListener.h b/Minecraft.World/PacketListener.h index 9f5b1b9d..bf2ebdad 100644 --- a/Minecraft.World/PacketListener.h +++ b/Minecraft.World/PacketListener.h @@ -229,6 +229,5 @@ public: virtual void handleXZ(shared_ptr packet); virtual void handleGameCommand(shared_ptr packet); - // MCConsoles Auth - virtual void handleAuth(shared_ptr packet); + virtual void handleAuth(const shared_ptr &packet); }; diff --git a/Minecraft.World/cmake/sources/Common.cmake b/Minecraft.World/cmake/sources/Common.cmake index eb4a6379..6f45dfdf 100644 --- a/Minecraft.World/cmake/sources/Common.cmake +++ b/Minecraft.World/cmake/sources/Common.cmake @@ -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"