mirror of
https://github.com/smartcmd/MinecraftConsoles.git
synced 2026-08-20 09:57:09 +00:00
major refactor: auth v3
This commit is contained in:
parent
8a8b4b2573
commit
179d21d7b6
|
|
@ -3,6 +3,7 @@
|
|||
#include "Minecraft.h"
|
||||
#include "User.h"
|
||||
#include "..\Minecraft.World\AuthModule.h"
|
||||
#include "..\Minecraft.World\SessionAuthModule.h"
|
||||
#include "..\Minecraft.World\HttpClient.h"
|
||||
#include "..\Minecraft.World\StringHelpers.h"
|
||||
#include "Common/vendor/nlohmann/json.hpp"
|
||||
|
|
@ -12,24 +13,33 @@
|
|||
|
||||
using json = nlohmann::json;
|
||||
static constexpr auto PROFILES_FILE = L"auth_profiles.dat";
|
||||
static constexpr auto MS_CLIENT_ID = "00000000441cc96b";
|
||||
static constexpr auto MS_CLIENT_ID = "00000000402b5328";
|
||||
static json parseResponse(const HttpResponse &resp, int expectedStatus = 200);
|
||||
static bool msTokenExchange(const string &msAccessToken, string &mcToken, string &profId, string &profName);
|
||||
static bool msRefreshOAuth(const string &refreshToken, string &newAccessToken, string &newRefreshToken);
|
||||
static bool elybyValidate(const string &accessToken, const string &clientToken);
|
||||
static bool elybyRefresh(const string &accessToken, const string &clientToken, string &newAccessToken, string &newClientToken);
|
||||
static bool yggdrasilValidate(const string &accessToken, const string &clientToken, const string &validateUrl);
|
||||
static bool yggdrasilRefresh(const string &accessToken, const string &clientToken, string &newAccessToken, string &newClientToken, const string &refreshUrl);
|
||||
|
||||
vector<AuthProfile> AuthProfileManager::profiles;
|
||||
int AuthProfileManager::selectedProfile = -1;
|
||||
|
||||
static constexpr uint32_t PROFILE_MAGIC = 0x4D435032;
|
||||
|
||||
void AuthProfileManager::load()
|
||||
{
|
||||
profiles.clear();
|
||||
std::ifstream file(PROFILES_FILE, std::ios::binary);
|
||||
if (!file) return;
|
||||
|
||||
uint32_t header = 0;
|
||||
file.read(reinterpret_cast<char *>(&header), sizeof(header));
|
||||
bool hasVariation = (header == PROFILE_MAGIC);
|
||||
|
||||
uint32_t count = 0;
|
||||
file.read(reinterpret_cast<char *>(&count), sizeof(count));
|
||||
if (hasVariation)
|
||||
file.read(reinterpret_cast<char *>(&count), sizeof(count));
|
||||
else
|
||||
count = header;
|
||||
|
||||
for (uint32_t i = 0; i < count && file.good(); i++)
|
||||
{
|
||||
|
|
@ -52,6 +62,10 @@ void AuthProfileManager::load()
|
|||
p.username = readWstr();
|
||||
p.token = readWstr();
|
||||
p.clientToken = readWstr();
|
||||
if (hasVariation)
|
||||
p.variation = readWstr();
|
||||
if (p.variation.empty() && p.type == AuthProfile::YGGDRASIL)
|
||||
p.variation = L"elyby";
|
||||
profiles.push_back(std::move(p));
|
||||
}
|
||||
|
||||
|
|
@ -66,6 +80,9 @@ void AuthProfileManager::save()
|
|||
std::ofstream file(PROFILES_FILE, std::ios::binary | std::ios::trunc);
|
||||
if (!file) return;
|
||||
|
||||
uint32_t magic = PROFILE_MAGIC;
|
||||
file.write(reinterpret_cast<const char *>(&magic), sizeof(magic));
|
||||
|
||||
uint32_t count = static_cast<uint32_t>(profiles.size());
|
||||
file.write(reinterpret_cast<const char *>(&count), sizeof(count));
|
||||
|
||||
|
|
@ -83,16 +100,17 @@ void AuthProfileManager::save()
|
|||
writeWstr(p.username);
|
||||
writeWstr(p.token);
|
||||
writeWstr(p.clientToken);
|
||||
writeWstr(p.variation);
|
||||
}
|
||||
|
||||
int32_t idx = static_cast<int32_t>(selectedProfile);
|
||||
file.write(reinterpret_cast<const char *>(&idx), sizeof(idx));
|
||||
}
|
||||
|
||||
void AuthProfileManager::addProfile(AuthProfile::Type type, const wstring &username, const wstring &uid, const wstring &token, const wstring &clientToken)
|
||||
void AuthProfileManager::addProfile(AuthProfile::Type type, const wstring &username, const wstring &uid, const wstring &token, const wstring &clientToken, const wstring &variation)
|
||||
{
|
||||
wstring finalUid = uid.empty() ? L"offline_" + username : uid;
|
||||
profiles.push_back({type, finalUid, username, token, clientToken});
|
||||
profiles.push_back({type, finalUid, username, token, clientToken, variation});
|
||||
selectedProfile = static_cast<int>(profiles.size()) - 1;
|
||||
save();
|
||||
}
|
||||
|
|
@ -137,12 +155,14 @@ bool AuthProfileManager::applySelectedProfile()
|
|||
}
|
||||
}
|
||||
}
|
||||
else if (p.type == AuthProfile::ELYBY && !p.token.empty())
|
||||
else if (p.type == AuthProfile::YGGDRASIL && !p.token.empty())
|
||||
{
|
||||
if (!elybyValidate(narrowStr(p.token), narrowStr(p.clientToken)))
|
||||
auto *provider = YggdrasilRegistry::find(p.variation);
|
||||
if (!provider) provider = &YggdrasilRegistry::defaultProvider();
|
||||
if (!yggdrasilValidate(narrowStr(p.token), narrowStr(p.clientToken), provider->validateUrl()))
|
||||
{
|
||||
string newAccess, newClient;
|
||||
if (elybyRefresh(narrowStr(p.token), narrowStr(p.clientToken), newAccess, newClient))
|
||||
if (yggdrasilRefresh(narrowStr(p.token), narrowStr(p.clientToken), newAccess, newClient, provider->refreshUrl()))
|
||||
{
|
||||
p.token = convStringToWstring(newAccess);
|
||||
if (!newClient.empty()) p.clientToken = convStringToWstring(newClient);
|
||||
|
|
@ -169,7 +189,6 @@ bool AuthProfileManager::applySelectedProfile()
|
|||
return true;
|
||||
}
|
||||
|
||||
// --- AuthFlow ---
|
||||
|
||||
std::thread AuthFlow::workerThread;
|
||||
std::atomic<AuthFlowState> AuthFlow::state{AuthFlowState::IDLE};
|
||||
|
|
@ -177,6 +196,7 @@ std::atomic<bool> AuthFlow::cancelRequested{false};
|
|||
AuthResult AuthFlow::result;
|
||||
wstring AuthFlow::userCode;
|
||||
wstring AuthFlow::verificationUri;
|
||||
wstring AuthFlow::activeVariation;
|
||||
|
||||
void AuthFlow::reset()
|
||||
{
|
||||
|
|
@ -187,6 +207,7 @@ void AuthFlow::reset()
|
|||
result = {};
|
||||
userCode.clear();
|
||||
verificationUri.clear();
|
||||
activeVariation.clear();
|
||||
cancelRequested = false;
|
||||
}
|
||||
|
||||
|
|
@ -197,11 +218,15 @@ void AuthFlow::startMicrosoft()
|
|||
workerThread = std::thread(microsoftFlowThread);
|
||||
}
|
||||
|
||||
void AuthFlow::startElyBy(const wstring &username, const wstring &password)
|
||||
void AuthFlow::startYggdrasil(const wstring &username, const wstring &password, const wstring &providerName)
|
||||
{
|
||||
reset();
|
||||
state = AuthFlowState::EXCHANGING;
|
||||
workerThread = std::thread(elybyFlowThread, narrowStr(username), narrowStr(password));
|
||||
wstring resolvedName = providerName.empty() ? YggdrasilRegistry::defaultProvider().name : providerName;
|
||||
activeVariation = resolvedName;
|
||||
auto *provider = YggdrasilRegistry::find(resolvedName);
|
||||
string authUrl = provider ? provider->authenticateUrl() : YggdrasilRegistry::defaultProvider().authenticateUrl();
|
||||
workerThread = std::thread(yggdrasilFlowThread, narrowStr(username), narrowStr(password), authUrl);
|
||||
}
|
||||
|
||||
static void authFail(AuthResult &result, std::atomic<AuthFlowState> &state, const wchar_t *msg)
|
||||
|
|
@ -210,7 +235,6 @@ static void authFail(AuthResult &result, std::atomic<AuthFlowState> &state, cons
|
|||
state = AuthFlowState::FAILED;
|
||||
}
|
||||
|
||||
// parse json response body, return discarded json on bad status
|
||||
static json parseResponse(const HttpResponse &resp, int expectedStatus)
|
||||
{
|
||||
if (resp.statusCode != expectedStatus) return json::value_t::discarded;
|
||||
|
|
@ -273,19 +297,15 @@ static bool msRefreshOAuth(const string &refreshToken, string &newAccessToken, s
|
|||
newRefreshToken = j.value("refresh_token", "");
|
||||
return !newAccessToken.empty();
|
||||
}
|
||||
|
||||
// validate ely.by token via yggdrasil /validate endpoint
|
||||
static bool elybyValidate(const string &accessToken, const string &clientToken)
|
||||
static bool yggdrasilValidate(const string &accessToken, const string &clientToken, const string &validateUrl)
|
||||
{
|
||||
auto resp = HttpClient::post("https://authserver.ely.by/auth/validate",
|
||||
auto resp = HttpClient::post(validateUrl,
|
||||
json({{"accessToken", accessToken}, {"clientToken", clientToken}}).dump());
|
||||
return resp.statusCode == 200;
|
||||
}
|
||||
|
||||
// refresh ely.by token via yggdrasil /refresh endpoint
|
||||
static bool elybyRefresh(const string &accessToken, const string &clientToken, string &newAccessToken, string &newClientToken)
|
||||
static bool yggdrasilRefresh(const string &accessToken, const string &clientToken, string &newAccessToken, string &newClientToken, const string &refreshUrl)
|
||||
{
|
||||
auto resp = HttpClient::post("https://authserver.ely.by/auth/refresh",
|
||||
auto resp = HttpClient::post(refreshUrl,
|
||||
json({{"accessToken", accessToken}, {"clientToken", clientToken}}).dump());
|
||||
|
||||
auto j = parseResponse(resp);
|
||||
|
|
@ -403,9 +423,9 @@ void AuthFlow::microsoftFlowThread()
|
|||
state = AuthFlowState::COMPLETE;
|
||||
}
|
||||
|
||||
void AuthFlow::elybyFlowThread(const string &username, const string &password)
|
||||
void AuthFlow::yggdrasilFlowThread(const string &username, const string &password, const string &authenticateUrl)
|
||||
{
|
||||
auto resp = HttpClient::post("https://authserver.ely.by/auth/authenticate", json({
|
||||
auto resp = HttpClient::post(authenticateUrl, json({
|
||||
{"username", username},
|
||||
{"password", password},
|
||||
{"clientToken", "mcconsoles"},
|
||||
|
|
@ -416,7 +436,7 @@ void AuthFlow::elybyFlowThread(const string &username, const string &password)
|
|||
|
||||
if (resp.statusCode != 200 || respJson.is_discarded())
|
||||
{
|
||||
string msg = "Ely.by auth failed";
|
||||
string msg = "Yggdrasil auth failed";
|
||||
if (!respJson.is_discarded()) msg = respJson.value("errorMessage", msg);
|
||||
result = {false, {}, {}, {}, {}, convStringToWstring(msg)};
|
||||
state = AuthFlowState::FAILED;
|
||||
|
|
@ -424,16 +444,16 @@ void AuthFlow::elybyFlowThread(const string &username, const string &password)
|
|||
}
|
||||
|
||||
string accessToken = respJson.value("accessToken", "");
|
||||
string elyClientToken = respJson.value("clientToken", "");
|
||||
string yggClientToken = respJson.value("clientToken", "");
|
||||
string uuid, name;
|
||||
try { uuid = respJson["selectedProfile"].value("id", ""); name = respJson["selectedProfile"].value("name", ""); } catch (...) {}
|
||||
|
||||
if (accessToken.empty() || uuid.empty() || name.empty())
|
||||
{
|
||||
authFail(result, state, L"Ely.by response missing profile");
|
||||
authFail(result, state, L"Yggdrasil response missing profile");
|
||||
return;
|
||||
}
|
||||
|
||||
result = {true, convStringToWstring(name), convStringToWstring(uuid), convStringToWstring(accessToken), convStringToWstring(elyClientToken), {}};
|
||||
result = {true, convStringToWstring(name), convStringToWstring(uuid), convStringToWstring(accessToken), convStringToWstring(yggClientToken), {}};
|
||||
state = AuthFlowState::COMPLETE;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,13 +5,14 @@ using namespace std;
|
|||
|
||||
struct AuthProfile
|
||||
{
|
||||
enum Type : uint8_t { MICROSOFT, ELYBY, OFFLINE };
|
||||
enum Type : uint8_t { MICROSOFT, YGGDRASIL, OFFLINE };
|
||||
|
||||
Type type;
|
||||
wstring uid;
|
||||
wstring username;
|
||||
wstring token;
|
||||
wstring clientToken;
|
||||
wstring variation;
|
||||
};
|
||||
|
||||
class AuthProfileManager
|
||||
|
|
@ -23,7 +24,7 @@ private:
|
|||
public:
|
||||
static void load();
|
||||
static void save();
|
||||
static void addProfile(AuthProfile::Type type, const wstring &username, const wstring &uid = L"", const wstring &token = L"", const wstring &clientToken = L"");
|
||||
static void addProfile(AuthProfile::Type type, const wstring &username, const wstring &uid = L"", const wstring &token = L"", const wstring &clientToken = L"", const wstring &variation = L"");
|
||||
static void removeSelectedProfile();
|
||||
static bool applySelectedProfile();
|
||||
|
||||
|
|
@ -60,17 +61,17 @@ private:
|
|||
static AuthResult result;
|
||||
static wstring userCode;
|
||||
static wstring verificationUri;
|
||||
|
||||
static wstring activeVariation;
|
||||
static void microsoftFlowThread();
|
||||
static void elybyFlowThread(const string &username, const string &password);
|
||||
static void yggdrasilFlowThread(const string &username, const string &password, const string &authenticateUrl);
|
||||
|
||||
public:
|
||||
static void startMicrosoft();
|
||||
static void startElyBy(const wstring &username, const wstring &password);
|
||||
|
||||
static void startYggdrasil(const wstring &username, const wstring &password, const wstring &providerName = L"");
|
||||
static AuthFlowState getState() { return state.load(); }
|
||||
static const AuthResult &getResult() { return result; }
|
||||
static const wstring &getUserCode() { return userCode; }
|
||||
static const wstring &getVerificationUri() { return verificationUri; }
|
||||
static const wstring &getActiveVariation() { return activeVariation; }
|
||||
static void reset();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ set_source_files_properties(compat_shims.cpp PROPERTIES SKIP_PRECOMPILE_HEADERS
|
|||
|
||||
configure_compiler_target(Minecraft.Client)
|
||||
|
||||
target_link_options(Minecraft.Client PRIVATE "/MAP")
|
||||
|
||||
set_target_properties(Minecraft.Client PROPERTIES
|
||||
OUTPUT_NAME "Minecraft.Client"
|
||||
VS_DEBUGGER_WORKING_DIRECTORY "$<TARGET_FILE_DIR:Minecraft.Client>"
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@
|
|||
#include "DLCTexturePack.h"
|
||||
#include "..\Minecraft.World\HandshakeManager.h"
|
||||
#include "..\Minecraft.World\SessionAuthModule.h"
|
||||
#include "..\Minecraft.World\OfflineAuthModule.h"
|
||||
#include "AuthScreen.h"
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
|
|
@ -4141,21 +4140,33 @@ ClientConnection::DeferredEntityLinkPacket::DeferredEntityLinkPacket(shared_ptr<
|
|||
|
||||
void ClientConnection::beginAuth()
|
||||
{
|
||||
app.DebugPrintf("AUTH: beginAuth() starting\n");
|
||||
handshakeManager = new HandshakeManager(false);
|
||||
handshakeManager->registerModule(std::make_unique<SessionAuthModule>());
|
||||
handshakeManager->registerModule(std::make_unique<KeypairOfflineAuthModule>());
|
||||
handshakeManager->registerModule(std::make_unique<OfflineAuthModule>());
|
||||
|
||||
const auto &profiles = AuthProfileManager::getProfiles();
|
||||
int idx = AuthProfileManager::getSelectedIndex();
|
||||
bool isOffline = true;
|
||||
if (idx >= 0 && idx < static_cast<int>(profiles.size()))
|
||||
{
|
||||
const auto &p = profiles[idx];
|
||||
isOffline = (p.type == AuthProfile::OFFLINE);
|
||||
wstring variation;
|
||||
if (p.type == AuthProfile::MICROSOFT) variation = L"mojang";
|
||||
else if (p.type == AuthProfile::ELYBY) variation = L"elyby";
|
||||
else if (p.type == AuthProfile::YGGDRASIL) variation = p.variation;
|
||||
app.DebugPrintf("AUTH: profile type=%d variation=%ls username=%ls\n",
|
||||
(int)p.type, variation.c_str(), p.username.c_str());
|
||||
handshakeManager->setCredentials(p.token, p.uid, p.username, variation);
|
||||
}
|
||||
else
|
||||
{
|
||||
app.DebugPrintf("AUTH: no valid profile selected (idx=%d, count=%d)\n",
|
||||
idx, (int)profiles.size());
|
||||
}
|
||||
|
||||
if (!isOffline)
|
||||
handshakeManager->registerModule(std::make_unique<SessionAuthModule>());
|
||||
handshakeManager->registerModule(std::make_unique<KeypairOfflineAuthModule>());
|
||||
handshakeManager->registerModule(std::make_unique<OfflineAuthModule>());
|
||||
|
||||
auto initial = handshakeManager->createInitialPacket();
|
||||
if (initial) send(initial);
|
||||
|
|
|
|||
|
|
@ -413,6 +413,35 @@ bool CGameNetworkManager::StartNetworkGame(Minecraft *minecraft, LPVOID lpParame
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!g_NetworkManager.IsHost())
|
||||
{
|
||||
try {
|
||||
connection->beginAuth();
|
||||
while (!connection->authComplete && !connection->isClosed() && IsInSession() && !g_NetworkManager.IsLeavingGame())
|
||||
{
|
||||
connection->tick();
|
||||
Sleep(50);
|
||||
}
|
||||
} catch (...) {
|
||||
app.DebugPrintf("AUTH: exception during auth handshake\n");
|
||||
connection->close();
|
||||
delete connection;
|
||||
connection = nullptr;
|
||||
MinecraftServer::HaltServer();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!connection->authComplete)
|
||||
{
|
||||
app.DebugPrintf("Auth handshake did not complete, aborting connection\n");
|
||||
connection->close();
|
||||
delete connection;
|
||||
connection = nullptr;
|
||||
MinecraftServer::HaltServer();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
connection->send(std::make_shared<PreLoginPacket>(minecraft->user->name));
|
||||
|
||||
// Tick connection until we're ready to go. The stages involved in this are:
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include "..\..\..\Minecraft.World\Mth.h"
|
||||
#include "..\..\..\Minecraft.World\StringHelpers.h"
|
||||
#include "..\..\..\Minecraft.World\Random.h"
|
||||
#include "..\..\..\Minecraft.World\SessionAuthModule.h"
|
||||
#include "..\..\User.h"
|
||||
#include "..\..\MinecraftServer.h"
|
||||
#include "..\..\AuthScreen.h"
|
||||
|
|
@ -459,7 +460,7 @@ void UIScene_MainMenu::RunAction(int iPad)
|
|||
|
||||
static int s_authPad = 0;
|
||||
static void *s_authParam = nullptr;
|
||||
static wstring s_elybyUsername;
|
||||
static wstring s_yggdrasilUsername;
|
||||
static bool s_authFlowActive = false;
|
||||
|
||||
static wstring ReadKeyboardText(int maxLen)
|
||||
|
|
@ -509,11 +510,17 @@ static const wchar_t *BuildAuthProfileText()
|
|||
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]";
|
||||
wstring label;
|
||||
if (p.type == AuthProfile::MICROSOFT) label = L"[MS]";
|
||||
else if (p.type == AuthProfile::YGGDRASIL)
|
||||
{
|
||||
auto *provider = YggdrasilRegistry::find(p.variation);
|
||||
label = provider ? L"[" + provider->displayName + L"]" : L"[Ygg]";
|
||||
}
|
||||
else label = L"[Off]";
|
||||
if (i > 0) text += L"\n";
|
||||
text += (i == sel) ? L"> " : L" ";
|
||||
text += wstring(type) + L" " + p.username;
|
||||
text += label + L" " + p.username;
|
||||
}
|
||||
}
|
||||
return text.c_str();
|
||||
|
|
@ -531,7 +538,9 @@ void UIScene_MainMenu::ShowAuthMenu(int iPad, void *pClass)
|
|||
|
||||
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" };
|
||||
static wstring yggLabel;
|
||||
yggLabel = YggdrasilRegistry::defaultProvider().displayName + L" Auth";
|
||||
const wchar_t *addOptions[] = { L"Microsoft Auth", yggLabel.c_str(), L"Add Offline User", L"Back" };
|
||||
ShowAuthMessageBox(iPad, L"Add Profile", L"Select an auth type",
|
||||
addOptions, 4, &UIScene_MainMenu::AuthAddMenuReturned, pClass);
|
||||
}
|
||||
|
|
@ -602,15 +611,15 @@ int UIScene_MainMenu::AuthAddMenuReturned(LPVOID lpParam, int iPad, const C4JSto
|
|||
{
|
||||
#ifdef _WINDOWS64
|
||||
UIKeyboardInitData kbData;
|
||||
kbData.title = L"Ely.by Username";
|
||||
kbData.title = L"Username";
|
||||
kbData.defaultText = L"";
|
||||
kbData.maxChars = 64;
|
||||
kbData.callback = &UIScene_MainMenu::ElyByUsernameReturned;
|
||||
kbData.callback = &UIScene_MainMenu::YggdrasilUsernameReturned;
|
||||
kbData.lpParam = lpParam;
|
||||
kbData.pcMode = g_KBMInput.IsKBMActive();
|
||||
ui.NavigateToScene(iPad, eUIScene_Keyboard, &kbData);
|
||||
#else
|
||||
InputManager.RequestKeyboard(L"Ely.by Username", L"", (DWORD)0, 64, &UIScene_MainMenu::ElyByUsernameReturned, lpParam, C_4JInput::EKeyboardMode_Default);
|
||||
InputManager.RequestKeyboard(L"Username", L"", (DWORD)0, 64, &UIScene_MainMenu::YggdrasilUsernameReturned, lpParam, C_4JInput::EKeyboardMode_Default);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
|
@ -646,26 +655,26 @@ int UIScene_MainMenu::AuthMsFlowReturned(LPVOID lpParam, int iPad, const C4JStor
|
|||
return 0;
|
||||
}
|
||||
|
||||
int UIScene_MainMenu::ElyByUsernameReturned(LPVOID lpParam, const bool bRes)
|
||||
int UIScene_MainMenu::YggdrasilUsernameReturned(LPVOID lpParam, const bool bRes)
|
||||
{
|
||||
if (bRes)
|
||||
{
|
||||
wstring name = ReadKeyboardText(128);
|
||||
if (!name.empty())
|
||||
{
|
||||
s_elybyUsername = std::move(name);
|
||||
s_yggdrasilUsername = std::move(name);
|
||||
#ifdef _WINDOWS64
|
||||
UIKeyboardInitData kbData;
|
||||
kbData.title = L"Ely.by Password";
|
||||
kbData.title = L"Password";
|
||||
kbData.defaultText = L"";
|
||||
kbData.maxChars = 128;
|
||||
kbData.callback = &UIScene_MainMenu::ElyByPasswordReturned;
|
||||
kbData.callback = &UIScene_MainMenu::YggdrasilPasswordReturned;
|
||||
kbData.lpParam = lpParam;
|
||||
kbData.pcMode = g_KBMInput.IsKBMActive();
|
||||
kbData.keyboardMode = C_4JInput::EKeyboardMode_Password;
|
||||
ui.NavigateToScene(s_authPad, eUIScene_Keyboard, &kbData);
|
||||
#else
|
||||
InputManager.RequestKeyboard(L"Ely.by Password", L"", (DWORD)0, 128, &UIScene_MainMenu::ElyByPasswordReturned, lpParam, C_4JInput::EKeyboardMode_Password);
|
||||
InputManager.RequestKeyboard(L"Password", L"", (DWORD)0, 128, &UIScene_MainMenu::YggdrasilPasswordReturned, lpParam, C_4JInput::EKeyboardMode_Password);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -674,18 +683,20 @@ int UIScene_MainMenu::ElyByUsernameReturned(LPVOID lpParam, const bool bRes)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int UIScene_MainMenu::ElyByPasswordReturned(LPVOID lpParam, const bool bRes)
|
||||
int UIScene_MainMenu::YggdrasilPasswordReturned(LPVOID lpParam, const bool bRes)
|
||||
{
|
||||
if (bRes)
|
||||
{
|
||||
wstring pass = ReadKeyboardText(256);
|
||||
if (!pass.empty())
|
||||
{
|
||||
AuthFlow::startElyBy(s_elybyUsername, pass);
|
||||
AuthFlow::startYggdrasil(s_yggdrasilUsername, pass);
|
||||
s_authFlowActive = true;
|
||||
|
||||
static wstring loginTitle;
|
||||
loginTitle = YggdrasilRegistry::defaultProvider().displayName + L" Login";
|
||||
static const wchar_t *waitOptions[] = { L"Cancel" };
|
||||
ShowAuthMessageBox(s_authPad, L"Ely.by Login", L"Authenticating...",
|
||||
ShowAuthMessageBox(s_authPad, loginTitle.c_str(), L"Authenticating...",
|
||||
waitOptions, 1, &UIScene_MainMenu::AuthMsFlowReturned, lpParam, true);
|
||||
|
||||
SecureZeroMemory(pass.data(), pass.size() * sizeof(wchar_t));
|
||||
|
|
@ -2144,8 +2155,9 @@ void UIScene_MainMenu::tick()
|
|||
{
|
||||
s_authFlowActive = false;
|
||||
const auto &r = AuthFlow::getResult();
|
||||
auto type = AuthFlow::getUserCode().empty() ? AuthProfile::ELYBY : AuthProfile::MICROSOFT;
|
||||
AuthProfileManager::addProfile(type, r.username, r.uuid, r.accessToken, r.clientToken);
|
||||
auto type = AuthFlow::getUserCode().empty() ? AuthProfile::YGGDRASIL : AuthProfile::MICROSOFT;
|
||||
wstring variation = (type == AuthProfile::YGGDRASIL) ? AuthFlow::getActiveVariation() : L"";
|
||||
AuthProfileManager::addProfile(type, r.username, r.uuid, r.accessToken, r.clientToken, variation);
|
||||
AuthFlow::reset();
|
||||
if (scene) ui.NavigateBack(s_authPad);
|
||||
ShowAuthMenu(s_authPad, s_authParam);
|
||||
|
|
|
|||
|
|
@ -151,8 +151,8 @@ private:
|
|||
static int AuthAddMenuReturned(void *pParam, int iPad, C4JStorage::EMessageResult result);
|
||||
static int AuthKeyboardReturned(LPVOID lpParam, const bool bRes);
|
||||
static int AuthMsFlowReturned(void *pParam, int iPad, C4JStorage::EMessageResult result);
|
||||
static int ElyByUsernameReturned(LPVOID lpParam, const bool bRes);
|
||||
static int ElyByPasswordReturned(LPVOID lpParam, const bool bRes);
|
||||
static int YggdrasilUsernameReturned(LPVOID lpParam, const bool bRes);
|
||||
static int YggdrasilPasswordReturned(LPVOID lpParam, const bool bRes);
|
||||
|
||||
static void LoadTrial();
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
#include "Settings.h"
|
||||
#include "..\Minecraft.World\HandshakeManager.h"
|
||||
#include "..\Minecraft.World\SessionAuthModule.h"
|
||||
#include "..\Minecraft.World\OfflineAuthModule.h"
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
#include "..\Minecraft.Server\ServerLogManager.h"
|
||||
#include "..\Minecraft.Server\Access\Access.h"
|
||||
|
|
@ -105,8 +104,14 @@ void PendingConnection::disconnect(DisconnectPacket::eDisconnectReason reason)
|
|||
|
||||
void PendingConnection::handlePreLogin(shared_ptr<PreLoginPacket> packet)
|
||||
{
|
||||
if (handshakeManager && !authComplete)
|
||||
if (!authComplete)
|
||||
{
|
||||
if (!handshakeManager)
|
||||
{
|
||||
app.DebugPrintf("PendingConnection: PreLogin received without auth handshake, disconnecting\n");
|
||||
disconnect(DisconnectPacket::eDisconnect_AuthFailed);
|
||||
return;
|
||||
}
|
||||
app.DebugPrintf("PendingConnection: PreLogin received before auth complete, disconnecting\n");
|
||||
disconnect(DisconnectPacket::eDisconnect_Closed);
|
||||
return;
|
||||
|
|
@ -413,10 +418,12 @@ bool PendingConnection::isDisconnected()
|
|||
void PendingConnection::initAuth()
|
||||
{
|
||||
handshakeManager = new HandshakeManager(true);
|
||||
if (server->authMode == "session")
|
||||
handshakeManager->registerModule(std::make_unique<SessionAuthModule>());
|
||||
else
|
||||
handshakeManager->registerModule(std::make_unique<SessionAuthModule>());
|
||||
if (server->authMode != "session")
|
||||
{
|
||||
handshakeManager->registerModule(std::make_unique<KeypairOfflineAuthModule>());
|
||||
handshakeManager->registerModule(std::make_unique<OfflineAuthModule>());
|
||||
}
|
||||
}
|
||||
|
||||
void PendingConnection::handleAuth(const shared_ptr<AuthPacket> &packet)
|
||||
|
|
|
|||
|
|
@ -1296,6 +1296,34 @@ static Minecraft* InitialiseMinecraftRuntime()
|
|||
return pMinecraft;
|
||||
}
|
||||
|
||||
static LONG WINAPI CrashHandler(EXCEPTION_POINTERS *ep)
|
||||
{
|
||||
auto *record = ep->ExceptionRecord;
|
||||
auto *ctx = ep->ContextRecord;
|
||||
HMODULE hMod = GetModuleHandleA(nullptr);
|
||||
|
||||
wchar_t buf[1024];
|
||||
swprintf(buf, 1024,
|
||||
L"Unhandled exception!\n\nCode: 0x%08X\nAddress: 0x%p\nBase: 0x%p\nRVA: 0x%llX\n\nPlease report this crash info.",
|
||||
record->ExceptionCode, record->ExceptionAddress, hMod,
|
||||
(unsigned long long)((char *)record->ExceptionAddress - (char *)hMod));
|
||||
|
||||
FILE *clog = nullptr;
|
||||
if (fopen_s(&clog, "crash.log", "w") == 0 && clog)
|
||||
{
|
||||
fprintf(clog, "[CRASH] Code=0x%08X Addr=0x%p Base=0x%p RVA=0x%llX\n",
|
||||
record->ExceptionCode, record->ExceptionAddress, hMod,
|
||||
(unsigned long long)((char *)record->ExceptionAddress - (char *)hMod));
|
||||
fprintf(clog, " RIP=0x%llX RSP=0x%llX RBP=0x%llX\n", ctx->Rip, ctx->Rsp, ctx->Rbp);
|
||||
fprintf(clog, " RAX=0x%llX RBX=0x%llX RCX=0x%llX\n", ctx->Rax, ctx->Rbx, ctx->Rcx);
|
||||
fprintf(clog, " RDX=0x%llX RSI=0x%llX RDI=0x%llX\n", ctx->Rdx, ctx->Rsi, ctx->Rdi);
|
||||
fclose(clog);
|
||||
}
|
||||
|
||||
MessageBoxW(nullptr, buf, L"MinecraftConsoles Crash", MB_OK | MB_ICONERROR);
|
||||
return EXCEPTION_EXECUTE_HANDLER;
|
||||
}
|
||||
|
||||
int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
|
||||
_In_opt_ HINSTANCE hPrevInstance,
|
||||
_In_ LPTSTR lpCmdLine,
|
||||
|
|
@ -1304,6 +1332,8 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
|
|||
UNREFERENCED_PARAMETER(hPrevInstance);
|
||||
UNREFERENCED_PARAMETER(lpCmdLine);
|
||||
|
||||
SetUnhandledExceptionFilter(CrashHandler);
|
||||
|
||||
// 4J-Win64: set CWD to exe dir so asset paths resolve correctly
|
||||
{
|
||||
char szExeDir[MAX_PATH] = {};
|
||||
|
|
|
|||
|
|
@ -25,3 +25,21 @@ public:
|
|||
protected:
|
||||
bool extractIdentity(const vector<pair<wstring, wstring>> &fields, wstring &outUid, wstring &outUsername);
|
||||
};
|
||||
|
||||
class KeypairOfflineAuthModule : public AuthModule
|
||||
{
|
||||
public:
|
||||
const wchar_t *schemeName() override { return L"mcconsoles:keypair_offline"; }
|
||||
vector<wstring> supportedVariations() override { return {L"rsa2048", L"ed25519"}; }
|
||||
vector<pair<wstring, wstring>> getSettings(const wstring &variation) override { return {{L"key_type", variation}}; }
|
||||
bool onAuthData(const vector<pair<wstring, wstring>> &fields, wstring &outUid, wstring &outUsername) override { return extractIdentity(fields, outUid, outUsername); }
|
||||
};
|
||||
|
||||
class OfflineAuthModule : public AuthModule
|
||||
{
|
||||
public:
|
||||
const wchar_t *schemeName() override { return L"mcconsoles:offline"; }
|
||||
vector<wstring> supportedVariations() override { return {}; }
|
||||
vector<pair<wstring, wstring>> getSettings(const wstring &) override { return {}; }
|
||||
bool onAuthData(const vector<pair<wstring, wstring>> &fields, wstring &outUid, wstring &outUsername) override { return extractIdentity(fields, outUid, outUsername); }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -48,7 +48,16 @@ shared_ptr<AuthPacket> HandshakeManager::handlePacket(const shared_ptr<AuthPacke
|
|||
shared_ptr<AuthPacket> HandshakeManager::createInitialPacket()
|
||||
{
|
||||
state = HandshakeState::VERSION_SENT;
|
||||
return makePacket(AuthStage::ANNOUNCE_VERSION, {{L"version", PROTOCOL_VERSION}});
|
||||
wstring schemes;
|
||||
for (const auto &[name, mod] : modules)
|
||||
{
|
||||
if (!schemes.empty()) schemes += L",";
|
||||
schemes += name;
|
||||
}
|
||||
return makePacket(AuthStage::ANNOUNCE_VERSION, {
|
||||
{L"version", PROTOCOL_VERSION},
|
||||
{L"schemes", schemes}
|
||||
});
|
||||
}
|
||||
|
||||
shared_ptr<AuthPacket> HandshakeManager::handleServer(const shared_ptr<AuthPacket> &packet)
|
||||
|
|
@ -61,11 +70,32 @@ shared_ptr<AuthPacket> HandshakeManager::handleServer(const shared_ptr<AuthPacke
|
|||
if (protocolVersion != PROTOCOL_VERSION)
|
||||
return fail();
|
||||
|
||||
// Pick first registered module as the scheme
|
||||
if (modules.empty())
|
||||
return fail();
|
||||
|
||||
activeModule = modules.begin()->second.get();
|
||||
auto splitCsv = [](const wstring &s) {
|
||||
vector<wstring> out;
|
||||
for (size_t p = 0; p < s.size(); ) {
|
||||
size_t c = s.find(L',', p);
|
||||
if (c == wstring::npos) c = s.size();
|
||||
out.push_back(s.substr(p, c - p));
|
||||
p = c + 1;
|
||||
}
|
||||
return out;
|
||||
};
|
||||
auto supported = splitCsv(getField(packet->fields, L"schemes"));
|
||||
|
||||
activeModule = nullptr;
|
||||
for (auto &[name, mod] : modules)
|
||||
{
|
||||
if (supported.empty() || std::find(supported.begin(), supported.end(), name) != supported.end())
|
||||
{
|
||||
activeModule = mod.get();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!activeModule)
|
||||
return fail();
|
||||
state = HandshakeState::SCHEME_DECLARED;
|
||||
return makePacket(AuthStage::DECLARE_SCHEME, {
|
||||
{L"version", PROTOCOL_VERSION},
|
||||
|
|
@ -133,6 +163,8 @@ shared_ptr<AuthPacket> HandshakeManager::handleClient(const shared_ptr<AuthPacke
|
|||
protocolVersion = getField(packet->fields, L"version");
|
||||
wstring scheme = getField(packet->fields, L"scheme");
|
||||
|
||||
app.DebugPrintf("AUTH CLIENT: DECLARE_SCHEME scheme=%ls\n", scheme.c_str());
|
||||
|
||||
if (protocolVersion != PROTOCOL_VERSION)
|
||||
return fail();
|
||||
|
||||
|
|
@ -149,24 +181,39 @@ shared_ptr<AuthPacket> HandshakeManager::handleClient(const shared_ptr<AuthPacke
|
|||
else
|
||||
activeVariation = variations.empty() ? L"" : variations[0];
|
||||
|
||||
app.DebugPrintf("AUTH CLIENT: accepting variation=%ls\n", activeVariation.c_str());
|
||||
state = HandshakeState::SCHEME_ACCEPTED;
|
||||
return makePacket(AuthStage::ACCEPT_SCHEME, {{L"variation", activeVariation}});
|
||||
}
|
||||
|
||||
case AuthStage::SCHEME_SETTINGS:
|
||||
{
|
||||
if (!activeModule)
|
||||
return fail();
|
||||
wstring serverId = getField(packet->fields, L"serverId");
|
||||
wstring sessionEndpoint = getField(packet->fields, L"sessionEndpoint");
|
||||
wstring joinUrlW = getField(packet->fields, L"joinUrl");
|
||||
wstring scheme(activeModule->schemeName());
|
||||
if (scheme == L"mcconsoles:session" && !accessToken.empty())
|
||||
|
||||
app.DebugPrintf("AUTH CLIENT: SCHEME_SETTINGS joinUrl=%ls serverId=%ls\n", joinUrlW.c_str(), serverId.c_str());
|
||||
|
||||
if (scheme == L"mcconsoles:session" && !accessToken.empty() && !joinUrlW.empty())
|
||||
{
|
||||
nlohmann::json body = {
|
||||
{"accessToken", narrowStr(accessToken)},
|
||||
{"selectedProfile", narrowStr(clientUid)},
|
||||
{"serverId", narrowStr(serverId)}
|
||||
};
|
||||
auto resp = HttpClient::post(narrowStr(sessionEndpoint) + "/session/minecraft/join", body.dump());
|
||||
if (resp.statusCode != 204)
|
||||
string joinUrl = narrowStr(joinUrlW);
|
||||
app.DebugPrintf("AUTH CLIENT: POSTing join to %s\n", joinUrl.c_str());
|
||||
HttpResponse resp;
|
||||
try {
|
||||
resp = HttpClient::post(joinUrl, body.dump());
|
||||
} catch (...) {
|
||||
app.DebugPrintf("AUTH CLIENT: join POST threw exception\n");
|
||||
return fail();
|
||||
}
|
||||
app.DebugPrintf("AUTH CLIENT: join POST status=%d\n", resp.statusCode);
|
||||
if (resp.statusCode < 200 || resp.statusCode >= 300)
|
||||
return fail();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ MapItem::MapItem(int id) : ComplexItem(id)
|
|||
|
||||
shared_ptr<MapItemSavedData> MapItem::getSavedData(short idNum, Level *level)
|
||||
{
|
||||
if (!level) return nullptr;
|
||||
std::wstring id = wstring( L"map_" ) + std::to_wstring(idNum);
|
||||
shared_ptr<MapItemSavedData> mapItemSavedData = dynamic_pointer_cast<MapItemSavedData>(level->getSavedData(typeid(MapItemSavedData), id));
|
||||
|
||||
|
|
@ -42,6 +43,7 @@ shared_ptr<MapItemSavedData> MapItem::getSavedData(short idNum, Level *level)
|
|||
|
||||
shared_ptr<MapItemSavedData> MapItem::getSavedData(shared_ptr<ItemInstance> itemInstance, Level *level)
|
||||
{
|
||||
if (!level) return nullptr;
|
||||
MemSect(31);
|
||||
std::wstring id = wstring( L"map_" ) + std::to_wstring(itemInstance->getAuxValue() );
|
||||
MemSect(0);
|
||||
|
|
@ -254,6 +256,7 @@ void MapItem::update(Level *level, shared_ptr<Entity> player, shared_ptr<MapItem
|
|||
|
||||
void MapItem::inventoryTick(shared_ptr<ItemInstance> itemInstance, Level *level, shared_ptr<Entity> owner, int slot, bool selected)
|
||||
{
|
||||
if (!level) return;
|
||||
if (level->isClientSide) return;
|
||||
|
||||
shared_ptr<MapItemSavedData> data = getSavedData(itemInstance, level);
|
||||
|
|
@ -293,7 +296,10 @@ void MapItem::inventoryTick(shared_ptr<ItemInstance> itemInstance, Level *level,
|
|||
|
||||
shared_ptr<Packet> MapItem::getUpdatePacket(shared_ptr<ItemInstance> itemInstance, Level *level, shared_ptr<Player> player)
|
||||
{
|
||||
charArray data = MapItem::getSavedData(itemInstance, level)->getUpdatePacket(itemInstance, level, player);
|
||||
if (!level) return nullptr;
|
||||
auto savedData = MapItem::getSavedData(itemInstance, level);
|
||||
if (!savedData) return nullptr;
|
||||
charArray data = savedData->getUpdatePacket(itemInstance, level, player);
|
||||
|
||||
if (data.data == nullptr || data.length == 0) return nullptr;
|
||||
|
||||
|
|
@ -304,6 +310,7 @@ shared_ptr<Packet> MapItem::getUpdatePacket(shared_ptr<ItemInstance> itemInstanc
|
|||
|
||||
void MapItem::onCraftedBy(shared_ptr<ItemInstance> itemInstance, Level *level, shared_ptr<Player> player)
|
||||
{
|
||||
if (!level) return;
|
||||
wchar_t buf[64];
|
||||
|
||||
int mapScale = 3;
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
#include "stdafx.h"
|
||||
#include "OfflineAuthModule.h"
|
||||
|
||||
const wchar_t *KeypairOfflineAuthModule::schemeName() { return L"mcconsoles:keypair_offline"; }
|
||||
|
||||
vector<wstring> KeypairOfflineAuthModule::supportedVariations()
|
||||
{
|
||||
return {L"rsa2048", L"ed25519"};
|
||||
}
|
||||
|
||||
vector<pair<wstring, wstring>> KeypairOfflineAuthModule::getSettings(const wstring &variation)
|
||||
{
|
||||
return {{L"key_type", variation}};
|
||||
}
|
||||
|
||||
bool KeypairOfflineAuthModule::onAuthData(const vector<pair<wstring, wstring>> &fields, wstring &outUid, wstring &outUsername)
|
||||
{
|
||||
return extractIdentity(fields, outUid, outUsername);
|
||||
}
|
||||
|
||||
const wchar_t *OfflineAuthModule::schemeName() { return L"mcconsoles:offline"; }
|
||||
|
||||
vector<wstring> OfflineAuthModule::supportedVariations()
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
vector<pair<wstring, wstring>> OfflineAuthModule::getSettings(const wstring &variation)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
bool OfflineAuthModule::onAuthData(const vector<pair<wstring, wstring>> &fields, wstring &outUid, wstring &outUsername)
|
||||
{
|
||||
return extractIdentity(fields, outUid, outUsername);
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
#pragma once
|
||||
using namespace std;
|
||||
|
||||
#include "AuthModule.h"
|
||||
|
||||
class KeypairOfflineAuthModule : public AuthModule
|
||||
{
|
||||
public:
|
||||
const wchar_t *schemeName() override;
|
||||
vector<wstring> supportedVariations() override;
|
||||
vector<pair<wstring, wstring>> getSettings(const wstring &variation) override;
|
||||
bool onAuthData(const vector<pair<wstring, wstring>> &fields, wstring &outUid, wstring &outUsername) override;
|
||||
};
|
||||
|
||||
class OfflineAuthModule : public AuthModule
|
||||
{
|
||||
public:
|
||||
const wchar_t *schemeName() override;
|
||||
vector<wstring> supportedVariations() override;
|
||||
vector<pair<wstring, wstring>> getSettings(const wstring &variation) override;
|
||||
bool onAuthData(const vector<pair<wstring, wstring>> &fields, wstring &outUid, wstring &outUsername) override;
|
||||
};
|
||||
|
|
@ -4,6 +4,8 @@
|
|||
#include "StringHelpers.h"
|
||||
#include "Common/vendor/nlohmann/json.hpp"
|
||||
#include <random>
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
|
||||
static wstring generateServerId()
|
||||
{
|
||||
|
|
@ -14,50 +16,96 @@ static wstring generateServerId()
|
|||
return id;
|
||||
}
|
||||
|
||||
SessionAuthModule::SessionAuthModule()
|
||||
static vector<YggdrasilProviderConfig> s_registryProviders;
|
||||
static bool s_registryLoaded = false;
|
||||
|
||||
void YggdrasilRegistry::load()
|
||||
{
|
||||
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"};
|
||||
s_registryProviders.clear();
|
||||
|
||||
std::ifstream file("yggdrasil.json");
|
||||
if (file.is_open())
|
||||
{
|
||||
auto j = nlohmann::json::parse(file, nullptr, false);
|
||||
if (j.is_array())
|
||||
{
|
||||
for (const auto &entry : j)
|
||||
{
|
||||
if (!entry.is_object()) continue;
|
||||
YggdrasilProviderConfig cfg;
|
||||
cfg.name = convStringToWstring(entry.value("name", ""));
|
||||
cfg.displayName = convStringToWstring(entry.value("displayName", ""));
|
||||
cfg.authUrl = entry.value("authUrl", "");
|
||||
cfg.sessionUrl = entry.value("sessionUrl", "");
|
||||
if (!cfg.name.empty() && !cfg.sessionUrl.empty())
|
||||
s_registryProviders.push_back(std::move(cfg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (std::none_of(s_registryProviders.begin(), s_registryProviders.end(),
|
||||
[](const auto &p) { return p.name == L"elyby"; }))
|
||||
s_registryProviders.insert(s_registryProviders.begin(),
|
||||
{L"elyby", L"Ely.by", "https://authserver.ely.by/auth", "https://authserver.ely.by/session"});
|
||||
|
||||
s_registryProviders.push_back(
|
||||
{L"mojang", L"Mojang", "", "https://sessionserver.mojang.com/session/minecraft"});
|
||||
|
||||
s_registryLoaded = true;
|
||||
}
|
||||
|
||||
const vector<YggdrasilProviderConfig> &YggdrasilRegistry::providers()
|
||||
{
|
||||
if (!s_registryLoaded) load();
|
||||
return s_registryProviders;
|
||||
}
|
||||
|
||||
const YggdrasilProviderConfig *YggdrasilRegistry::find(const wstring &name)
|
||||
{
|
||||
for (const auto &p : providers())
|
||||
if (p.name == name) return &p;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const YggdrasilProviderConfig &YggdrasilRegistry::defaultProvider()
|
||||
{
|
||||
const auto &list = providers();
|
||||
for (const auto &p : list)
|
||||
if (p.name != L"mojang") return p;
|
||||
return list[0];
|
||||
}
|
||||
|
||||
const wchar_t *SessionAuthModule::schemeName() { return L"mcconsoles:session"; }
|
||||
|
||||
vector<wstring> SessionAuthModule::supportedVariations()
|
||||
{
|
||||
return {L"mojang", L"elyby"};
|
||||
vector<wstring> result;
|
||||
for (const auto &p : YggdrasilRegistry::providers())
|
||||
result.push_back(p.name);
|
||||
return result;
|
||||
}
|
||||
|
||||
vector<pair<wstring, wstring>> SessionAuthModule::getSettings(const wstring &variation)
|
||||
{
|
||||
auto it = endpoints.find(variation);
|
||||
if (it == endpoints.end()) return {};
|
||||
auto *p = YggdrasilRegistry::find(variation);
|
||||
if (!p) return {};
|
||||
|
||||
activeSessionEndpoint = it->second.sessionEndpoint;
|
||||
activeProvider = p;
|
||||
activeServerId = generateServerId();
|
||||
|
||||
return {
|
||||
{L"authEndpoint", it->second.authEndpoint},
|
||||
{L"sessionEndpoint", it->second.sessionEndpoint},
|
||||
{L"joinUrl", convStringToWstring(p->joinUrl())},
|
||||
{L"serverId", activeServerId}
|
||||
};
|
||||
}
|
||||
|
||||
bool SessionAuthModule::onAuthData(const vector<pair<wstring, wstring>> &fields, wstring &outUid, wstring &outUsername)
|
||||
bool SessionAuthModule::serverVerify(const YggdrasilProviderConfig &provider, const wstring &username, const wstring &serverId, wstring &outUid, wstring &outUsername)
|
||||
{
|
||||
wstring username;
|
||||
for (const auto &[k, v] : fields)
|
||||
{
|
||||
if (k == L"username") username = v;
|
||||
}
|
||||
|
||||
if (username.empty() || activeServerId.empty() || activeSessionEndpoint.empty())
|
||||
return false;
|
||||
|
||||
string url = narrowStr(activeSessionEndpoint)
|
||||
+ "/session/minecraft/hasJoined?username=" + narrowStr(username)
|
||||
+ "&serverId=" + narrowStr(activeServerId);
|
||||
string url = provider.hasJoinedUrl() + "?username=" + narrowStr(username) + "&serverId=" + narrowStr(serverId);
|
||||
|
||||
app.DebugPrintf("AUTH SERVER [%ls]: hasJoined GET %s\n", provider.name.c_str(), url.c_str());
|
||||
auto response = HttpClient::get(url);
|
||||
app.DebugPrintf("AUTH SERVER [%ls]: hasJoined status=%d\n", provider.name.c_str(), response.statusCode);
|
||||
|
||||
if (response.statusCode != 200)
|
||||
return false;
|
||||
|
||||
|
|
@ -67,12 +115,25 @@ bool SessionAuthModule::onAuthData(const vector<pair<wstring, wstring>> &fields,
|
|||
|
||||
string id = json.value("id", "");
|
||||
string name = json.value("name", "");
|
||||
|
||||
if (id.empty() || name.empty())
|
||||
return false;
|
||||
|
||||
outUid = convStringToWstring(id);
|
||||
outUsername = convStringToWstring(name);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SessionAuthModule::onAuthData(const vector<pair<wstring, wstring>> &fields, wstring &outUid, wstring &outUsername)
|
||||
{
|
||||
wstring username;
|
||||
for (const auto &[k, v] : fields)
|
||||
if (k == L"username") username = v;
|
||||
|
||||
if (username.empty() || activeServerId.empty() || !activeProvider)
|
||||
return false;
|
||||
|
||||
if (!serverVerify(*activeProvider, username, activeServerId, outUid, outUsername))
|
||||
return false;
|
||||
|
||||
return validate(outUid, outUsername);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,41 @@
|
|||
#pragma once
|
||||
using namespace std;
|
||||
|
||||
#include "AuthModule.h"
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
struct YggdrasilProviderConfig
|
||||
{
|
||||
wstring name;
|
||||
wstring displayName;
|
||||
string authUrl;
|
||||
string sessionUrl;
|
||||
string authenticateUrl() const { return authUrl + "/authenticate"; }
|
||||
string validateUrl() const { return authUrl + "/validate"; }
|
||||
string refreshUrl() const { return authUrl + "/refresh"; }
|
||||
string joinUrl() const { return sessionUrl + "/join"; }
|
||||
string hasJoinedUrl() const { return sessionUrl + "/hasJoined"; }
|
||||
};
|
||||
|
||||
class YggdrasilRegistry
|
||||
{
|
||||
public:
|
||||
static void load();
|
||||
static const vector<YggdrasilProviderConfig> &providers();
|
||||
static const YggdrasilProviderConfig *find(const wstring &name);
|
||||
static const YggdrasilProviderConfig &defaultProvider();
|
||||
};
|
||||
|
||||
class SessionAuthModule : public AuthModule
|
||||
{
|
||||
public:
|
||||
struct EndpointPair {
|
||||
wstring authEndpoint;
|
||||
wstring sessionEndpoint;
|
||||
};
|
||||
|
||||
private:
|
||||
unordered_map<wstring, EndpointPair> endpoints;
|
||||
wstring activeSessionEndpoint;
|
||||
const YggdrasilProviderConfig *activeProvider = nullptr;
|
||||
wstring activeServerId;
|
||||
|
||||
bool serverVerify(const YggdrasilProviderConfig &provider, const wstring &username, const wstring &serverId, wstring &outUid, wstring &outUsername);
|
||||
|
||||
public:
|
||||
SessionAuthModule();
|
||||
SessionAuthModule() = default;
|
||||
|
||||
const wchar_t *schemeName() override;
|
||||
vector<wstring> supportedVariations() override;
|
||||
|
|
|
|||
|
|
@ -263,8 +263,6 @@ set(_MINECRAFT_WORLD_COMMON_NET_MINECRAFT_NETWORK_PACKET
|
|||
"${CMAKE_CURRENT_SOURCE_DIR}/AuthModule.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/SessionAuthModule.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/SessionAuthModule.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/OfflineAuthModule.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/OfflineAuthModule.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/AuthPackets.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/AuthPackets.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/UUID.cpp"
|
||||
|
|
|
|||
8
yggdrasil.json
Normal file
8
yggdrasil.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"name": "elyby",
|
||||
"displayName": "Ely.by",
|
||||
"authUrl": "https://authserver.ely.by/auth",
|
||||
"sessionUrl": "https://authserver.ely.by/session"
|
||||
}
|
||||
]
|
||||
Loading…
Reference in a new issue