diff --git a/CMakeLists.txt b/CMakeLists.txt index 63071a0e..979dd2a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,21 @@ project(MinecraftConsoles LANGUAGES C CXX RC ASM_MASM) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +include(FetchContent) +set(BUILD_CURL_EXE OFF CACHE BOOL "" FORCE) +set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) +set(CURL_USE_SCHANNEL ON CACHE BOOL "" FORCE) +set(BUILD_TESTING OFF CACHE BOOL "" FORCE) +set(CURL_DISABLE_LDAP ON CACHE BOOL "" FORCE) +set(CURL_DISABLE_LDAPS ON CACHE BOOL "" FORCE) +FetchContent_Declare( + curl + URL https://github.com/curl/curl/releases/download/curl-8_11_1/curl-8.11.1.tar.xz + URL_HASH SHA256=c7ca7db48b0909743eaef34250da02c19bc61d4f1dcedd6603f109409536ab56 + FIND_PACKAGE_ARGS +) +FetchContent_MakeAvailable(curl) if(NOT WIN32) message(FATAL_ERROR "This CMake build currently supports Windows only.") @@ -18,7 +33,6 @@ set(CMAKE_CONFIGURATION_TYPES "Release" CACHE STRING "" FORCE ) -set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") function(configure_compiler_target target) # MSVC and compatible compilers (like Clang-cl) diff --git a/Minecraft.Client/ClientConnection.cpp b/Minecraft.Client/ClientConnection.cpp index bdace064..92402784 100644 --- a/Minecraft.Client/ClientConnection.cpp +++ b/Minecraft.Client/ClientConnection.cpp @@ -4139,8 +4139,7 @@ ClientConnection::DeferredEntityLinkPacket::DeferredEntityLinkPacket(shared_ptr< void ClientConnection::beginAuth() { handshakeManager = new HandshakeManager(false); - handshakeManager->registerModule(new MojangAuthModule()); - handshakeManager->registerModule(new ElyByAuthModule()); + handshakeManager->registerModule(new SessionAuthModule()); handshakeManager->registerModule(new KeypairOfflineAuthModule()); handshakeManager->registerModule(new OfflineAuthModule()); diff --git a/Minecraft.Client/PendingConnection.cpp b/Minecraft.Client/PendingConnection.cpp index c10edb90..88fdb969 100644 --- a/Minecraft.Client/PendingConnection.cpp +++ b/Minecraft.Client/PendingConnection.cpp @@ -411,8 +411,7 @@ bool PendingConnection::isDisconnected() void PendingConnection::initAuth() { handshakeManager = new HandshakeManager(true); - handshakeManager->registerModule(new MojangAuthModule()); - handshakeManager->registerModule(new ElyByAuthModule()); + handshakeManager->registerModule(new SessionAuthModule()); handshakeManager->registerModule(new KeypairOfflineAuthModule()); handshakeManager->registerModule(new OfflineAuthModule()); } diff --git a/Minecraft.World/AuthModule.cpp b/Minecraft.World/AuthModule.cpp index 35bd3e85..e283e2b3 100644 --- a/Minecraft.World/AuthModule.cpp +++ b/Minecraft.World/AuthModule.cpp @@ -1,5 +1,23 @@ #include "stdafx.h" #include "AuthModule.h" +#include "HttpClient.h" +#include "StringHelpers.h" +#include "Common/vendor/nlohmann/json.hpp" +#include + +static string narrowStr(const wstring &w) +{ + return string(w.begin(), w.end()); +} + +static wstring generateServerId() +{ + static constexpr wchar_t hex[] = L"0123456789abcdef"; + static std::mt19937 rng(std::random_device{}()); + wstring id(16, L'0'); + for (auto &c : id) c = hex[rng() & 0xF]; + return id; +} bool AuthModule::validate(const wstring &uid, const wstring &username) { @@ -16,38 +34,67 @@ bool AuthModule::extractIdentity(const vector> &fields, w return validate(outUid, outUsername); } -ElyByAuthModule::ElyByAuthModule(const wstring &endpoint) - : endpoint(endpoint) +SessionAuthModule::SessionAuthModule() { + endpoints[L"mojang"] = {L"https://authserver.mojang.com", L"https://sessionserver.mojang.com"}; + endpoints[L"elyby"] = {L"https://authserver.ely.by", L"https://authserver.ely.by"}; } -const wchar_t *ElyByAuthModule::schemeName() { return L"mcconsoles:elyby"; } +const wchar_t *SessionAuthModule::schemeName() { return L"mcconsoles:session"; } -vector ElyByAuthModule::supportedVariations() +vector SessionAuthModule::supportedVariations() { - return {L"java"}; + return {L"mojang", L"elyby"}; } -vector> ElyByAuthModule::getSettings(const wstring &variation) +vector> SessionAuthModule::getSettings(const wstring &variation) { - return {{L"endpoint", endpoint}}; + auto it = endpoints.find(variation); + if (it == endpoints.end()) return {}; + + activeSessionEndpoint = it->second.sessionEndpoint; + activeServerId = generateServerId(); + + return { + {L"authEndpoint", it->second.authEndpoint}, + {L"sessionEndpoint", it->second.sessionEndpoint}, + {L"serverId", activeServerId} + }; } -bool ElyByAuthModule::onAuthData(const vector> &fields, wstring &outUid, wstring &outUsername) +bool SessionAuthModule::onAuthData(const vector> &fields, wstring &outUid, wstring &outUsername) { - return extractIdentity(fields, outUid, outUsername); -} + wstring username; + for (const auto &[k, v] : fields) + { + if (k == L"username") username = v; + } -MojangAuthModule::MojangAuthModule() - : ElyByAuthModule(L"https://sessionserver.mojang.com") -{ -} + if (username.empty() || activeServerId.empty() || activeSessionEndpoint.empty()) + return false; -const wchar_t *MojangAuthModule::schemeName() { return L"mcconsoles:mojang"; } + string url = narrowStr(activeSessionEndpoint) + + "/session/minecraft/hasJoined?username=" + narrowStr(username) + + "&serverId=" + narrowStr(activeServerId); -vector MojangAuthModule::supportedVariations() -{ - return {L"java", L"bedrock"}; + auto response = HttpClient::get(url); + if (response.statusCode != 200) + return false; + + auto json = nlohmann::json::parse(response.body, nullptr, false); + if (json.is_discarded()) + return false; + + string id = json.value("id", ""); + string name = json.value("name", ""); + + if (id.empty() || name.empty()) + return false; + + outUid = convStringToWstring(id); + outUsername = convStringToWstring(name); + + return validate(outUid, outUsername); } const wchar_t *KeypairOfflineAuthModule::schemeName() { return L"mcconsoles:keypair_offline"; } diff --git a/Minecraft.World/AuthModule.h b/Minecraft.World/AuthModule.h index 4de6efa1..851b1260 100644 --- a/Minecraft.World/AuthModule.h +++ b/Minecraft.World/AuthModule.h @@ -4,6 +4,7 @@ using namespace std; #include #include #include +#include class AuthModule { @@ -21,13 +22,21 @@ protected: bool extractIdentity(const vector> &fields, wstring &outUid, wstring &outUsername); }; -class ElyByAuthModule : public AuthModule +class SessionAuthModule : public AuthModule { -protected: - wstring endpoint; +public: + struct EndpointPair { + wstring authEndpoint; + wstring sessionEndpoint; + }; + +private: + unordered_map endpoints; + wstring activeSessionEndpoint; + wstring activeServerId; public: - ElyByAuthModule(const wstring &endpoint = L"https://authserver.ely.by"); + SessionAuthModule(); const wchar_t *schemeName() override; vector supportedVariations() override; @@ -35,15 +44,6 @@ public: bool onAuthData(const vector> &fields, wstring &outUid, wstring &outUsername) override; }; -class MojangAuthModule : public ElyByAuthModule -{ -public: - MojangAuthModule(); - - const wchar_t *schemeName() override; - vector supportedVariations() override; -}; - class KeypairOfflineAuthModule : public AuthModule { public: diff --git a/Minecraft.World/CMakeLists.txt b/Minecraft.World/CMakeLists.txt index e397bf29..d6490314 100644 --- a/Minecraft.World/CMakeLists.txt +++ b/Minecraft.World/CMakeLists.txt @@ -27,4 +27,6 @@ target_compile_definitions(Minecraft.World PRIVATE ) target_precompile_headers(Minecraft.World PRIVATE "$<$:stdafx.h>") +target_link_libraries(Minecraft.World PUBLIC CURL::libcurl) + configure_compiler_target(Minecraft.World) diff --git a/Minecraft.World/HandshakeManager.cpp b/Minecraft.World/HandshakeManager.cpp index 2da686db..8b6b87d2 100644 --- a/Minecraft.World/HandshakeManager.cpp +++ b/Minecraft.World/HandshakeManager.cpp @@ -79,7 +79,8 @@ shared_ptr HandshakeManager::handleServer(const shared_ptronAuthData(packet->fields, uid, username)) return fail(); - + finalUid = uid; + finalUsername = username; state = HandshakeState::AUTH_DATA_EXCHANGED; return nullptr; } @@ -93,11 +94,9 @@ shared_ptr HandshakeManager::handleServer(const shared_ptrvalidate(uid, username)) + if (uid != finalUid || username != finalUsername) return fail(); - finalUid = uid; - finalUsername = username; state = HandshakeState::IDENTITY_ASSIGNED; return makePacket(AuthStage::ASSIGN_IDENTITY, { {L"uid", finalUid}, diff --git a/Minecraft.World/HttpClient.cpp b/Minecraft.World/HttpClient.cpp new file mode 100644 index 00000000..b59d1b1f --- /dev/null +++ b/Minecraft.World/HttpClient.cpp @@ -0,0 +1,57 @@ +#include "stdafx.h" +#include "HttpClient.h" +#include + +static size_t writeCallback(char *data, size_t size, size_t nmemb, void *userdata) +{ + auto *buf = static_cast(userdata); + buf->append(data, size * nmemb); + return size * nmemb; +} + +static HttpResponse performRequest(CURL *curl) +{ + std::string responseBody; + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseBody); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + + CURLcode res = curl_easy_perform(curl); + + int statusCode = 0; + if (res == CURLE_OK) + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &statusCode); + + curl_easy_cleanup(curl); + return {statusCode, std::move(responseBody)}; +} + +HttpResponse HttpClient::get(const std::string &url) +{ + CURL *curl = curl_easy_init(); + if (!curl) + return {0, ""}; + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + return performRequest(curl); +} + +HttpResponse HttpClient::post(const std::string &url, const std::string &body, const std::string &contentType) +{ + CURL *curl = curl_easy_init(); + if (!curl) + return {0, ""}; + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast(body.size())); + + struct curl_slist *headers = nullptr; + headers = curl_slist_append(headers, ("Content-Type: " + contentType).c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + + HttpResponse resp = performRequest(curl); + curl_slist_free_all(headers); + return resp; +} diff --git a/Minecraft.World/HttpClient.h b/Minecraft.World/HttpClient.h new file mode 100644 index 00000000..317941c0 --- /dev/null +++ b/Minecraft.World/HttpClient.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +struct HttpResponse +{ + int statusCode; + std::string body; +}; + +class HttpClient +{ +public: + static HttpResponse get(const std::string &url); + static HttpResponse post(const std::string &url, const std::string &body, const std::string &contentType = "application/json"); +}; diff --git a/Minecraft.World/cmake/sources/Common.cmake b/Minecraft.World/cmake/sources/Common.cmake index 6f45dfdf..07b322d9 100644 --- a/Minecraft.World/cmake/sources/Common.cmake +++ b/Minecraft.World/cmake/sources/Common.cmake @@ -320,6 +320,8 @@ set(_MINECRAFT_WORLD_COMMON_NET_MINECRAFT_NETWORK_PACKET "${CMAKE_CURRENT_SOURCE_DIR}/GetInfoPacket.h" "${CMAKE_CURRENT_SOURCE_DIR}/HandshakeManager.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/HandshakeManager.h" + "${CMAKE_CURRENT_SOURCE_DIR}/HttpClient.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/HttpClient.h" "${CMAKE_CURRENT_SOURCE_DIR}/InteractPacket.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/InteractPacket.h" "${CMAKE_CURRENT_SOURCE_DIR}/KeepAlivePacket.cpp"