Merge df3dd69a0d into 29edc4aff0
This commit is contained in:
commit
49ff61e29a
69 changed files with 2245 additions and 35 deletions
|
|
@ -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$<$<CONFIG:Debug>: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$<$<CONFIG:Debug>:Debug>")
|
||||
|
||||
function(configure_compiler_target target)
|
||||
# MSVC and compatible compilers (like Clang-cl)
|
||||
|
|
|
|||
459
Minecraft.Client/AuthScreen.cpp
Normal file
459
Minecraft.Client/AuthScreen.cpp
Normal file
|
|
@ -0,0 +1,459 @@
|
|||
#include "stdafx.h"
|
||||
#include "AuthScreen.h"
|
||||
#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"
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <shellapi.h>
|
||||
|
||||
using json = nlohmann::json;
|
||||
static constexpr auto PROFILES_FILE = L"auth_profiles.dat";
|
||||
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 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;
|
||||
if (hasVariation)
|
||||
file.read(reinterpret_cast<char *>(&count), sizeof(count));
|
||||
else
|
||||
count = header;
|
||||
|
||||
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));
|
||||
if (!file || len > 4096) return {};
|
||||
wstring s(len, L'\0');
|
||||
file.read(reinterpret_cast<char *>(s.data()), len * sizeof(wchar_t));
|
||||
if (!file) return {};
|
||||
return s;
|
||||
};
|
||||
|
||||
p.uid = readWstr();
|
||||
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));
|
||||
}
|
||||
|
||||
int32_t savedIdx = 0;
|
||||
file.read(reinterpret_cast<char *>(&savedIdx), sizeof(savedIdx));
|
||||
if (!profiles.empty())
|
||||
selectedProfile = (savedIdx >= 0 && savedIdx < static_cast<int>(profiles.size())) ? savedIdx : 0;
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
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);
|
||||
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, const wstring &variation)
|
||||
{
|
||||
wstring finalUid = uid.empty() ? L"offline_" + username : uid;
|
||||
profiles.push_back({type, finalUid, username, token, clientToken, variation});
|
||||
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;
|
||||
|
||||
auto &p = profiles[selectedProfile];
|
||||
|
||||
if (p.type == AuthProfile::MICROSOFT && !p.clientToken.empty())
|
||||
{
|
||||
auto checkResp = HttpClient::get("https://api.minecraftservices.com/minecraft/profile",
|
||||
{"Authorization: Bearer " + narrowStr(p.token)});
|
||||
|
||||
if (checkResp.statusCode != 200)
|
||||
{
|
||||
string newMsAccess, newMsRefresh;
|
||||
if (msRefreshOAuth(narrowStr(p.clientToken), newMsAccess, newMsRefresh))
|
||||
{
|
||||
string mcToken, profId, profName;
|
||||
if (msTokenExchange(newMsAccess, mcToken, profId, profName))
|
||||
{
|
||||
p.token = convStringToWstring(mcToken);
|
||||
p.clientToken = convStringToWstring(newMsRefresh);
|
||||
p.username = convStringToWstring(profName);
|
||||
p.uid = convStringToWstring(profId);
|
||||
save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (p.type == AuthProfile::YGGDRASIL && !p.token.empty())
|
||||
{
|
||||
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 (yggdrasilRefresh(narrowStr(p.token), narrowStr(p.clientToken), newAccess, newClient, provider->refreshUrl()))
|
||||
{
|
||||
p.token = convStringToWstring(newAccess);
|
||||
if (!newClient.empty()) p.clientToken = convStringToWstring(newClient);
|
||||
save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto *mc = Minecraft::GetInstance();
|
||||
|
||||
if (mc->user)
|
||||
delete mc->user;
|
||||
|
||||
mc->user = new User(p.username, p.token);
|
||||
|
||||
// push auth name into the platform globals so ProfileManager.GetGamertag() picks it up
|
||||
// instead of returning the default "Player"
|
||||
extern char g_Win64Username[17];
|
||||
extern wchar_t g_Win64UsernameW[17];
|
||||
string narrow = narrowStr(p.username);
|
||||
strncpy_s(g_Win64Username, sizeof(g_Win64Username), narrow.c_str(), _TRUNCATE);
|
||||
wcsncpy_s(g_Win64UsernameW, 17, p.username.c_str(), _TRUNCATE);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
std::thread AuthFlow::workerThread;
|
||||
std::atomic<AuthFlowState> AuthFlow::state{AuthFlowState::IDLE};
|
||||
std::atomic<bool> AuthFlow::cancelRequested{false};
|
||||
AuthResult AuthFlow::result;
|
||||
wstring AuthFlow::userCode;
|
||||
wstring AuthFlow::verificationUri;
|
||||
wstring AuthFlow::activeVariation;
|
||||
|
||||
void AuthFlow::reset()
|
||||
{
|
||||
cancelRequested = true;
|
||||
if (workerThread.joinable())
|
||||
workerThread.detach();
|
||||
state = AuthFlowState::IDLE;
|
||||
result = {};
|
||||
userCode.clear();
|
||||
verificationUri.clear();
|
||||
activeVariation.clear();
|
||||
cancelRequested = false;
|
||||
}
|
||||
|
||||
void AuthFlow::startMicrosoft()
|
||||
{
|
||||
reset();
|
||||
state = AuthFlowState::WAITING_FOR_USER;
|
||||
workerThread = std::thread(microsoftFlowThread);
|
||||
}
|
||||
|
||||
void AuthFlow::startYggdrasil(const wstring &username, const wstring &password, const wstring &providerName)
|
||||
{
|
||||
reset();
|
||||
state = AuthFlowState::EXCHANGING;
|
||||
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)
|
||||
{
|
||||
result = {false, {}, {}, {}, {}, msg};
|
||||
state = AuthFlowState::FAILED;
|
||||
}
|
||||
|
||||
static json parseResponse(const HttpResponse &resp, int expectedStatus)
|
||||
{
|
||||
if (resp.statusCode != expectedStatus) return json::value_t::discarded;
|
||||
return json::parse(resp.body, nullptr, false);
|
||||
}
|
||||
static bool msTokenExchange(const string &msAccessToken, string &mcToken, string &profId, string &profName)
|
||||
{
|
||||
auto xblResp = HttpClient::post("https://user.auth.xboxlive.com/user/authenticate", json({
|
||||
{"Properties", {{"AuthMethod", "RPS"}, {"SiteName", "user.auth.xboxlive.com"}, {"RpsTicket", msAccessToken}}},
|
||||
{"RelyingParty", "http://auth.xboxlive.com"},
|
||||
{"TokenType", "JWT"}
|
||||
}).dump());
|
||||
|
||||
auto xblJson = parseResponse(xblResp);
|
||||
if (xblJson.is_discarded()) return false;
|
||||
|
||||
string xblToken = xblJson.value("Token", "");
|
||||
string userHash;
|
||||
try { userHash = xblJson["DisplayClaims"]["xui"][0].value("uhs", ""); } catch (...) {}
|
||||
if (xblToken.empty() || userHash.empty()) return false;
|
||||
|
||||
auto xstsResp = HttpClient::post("https://xsts.auth.xboxlive.com/xsts/authorize", json({
|
||||
{"Properties", {{"SandboxId", "RETAIL"}, {"UserTokens", {xblToken}}}},
|
||||
{"RelyingParty", "rp://api.minecraftservices.com/"},
|
||||
{"TokenType", "JWT"}
|
||||
}).dump());
|
||||
|
||||
auto xstsJson = parseResponse(xstsResp);
|
||||
string xstsToken = xstsJson.is_discarded() ? "" : xstsJson.value("Token", "");
|
||||
if (xstsToken.empty()) return false;
|
||||
|
||||
auto mcResp = HttpClient::post("https://api.minecraftservices.com/authentication/login_with_xbox",
|
||||
json({{"identityToken", "XBL3.0 x=" + userHash + ";" + xstsToken}}).dump());
|
||||
|
||||
auto mcJson = parseResponse(mcResp);
|
||||
mcToken = mcJson.is_discarded() ? "" : mcJson.value("access_token", "");
|
||||
if (mcToken.empty()) return false;
|
||||
|
||||
auto profResp = HttpClient::get("https://api.minecraftservices.com/minecraft/profile",
|
||||
{"Authorization: Bearer " + mcToken});
|
||||
|
||||
auto profJson = parseResponse(profResp);
|
||||
if (profJson.is_discarded()) return false;
|
||||
|
||||
profId = profJson.value("id", "");
|
||||
profName = profJson.value("name", "");
|
||||
return !profId.empty() && !profName.empty();
|
||||
}
|
||||
|
||||
static bool msRefreshOAuth(const string &refreshToken, string &newAccessToken, string &newRefreshToken)
|
||||
{
|
||||
auto resp = HttpClient::post("https://login.live.com/oauth20_token.srf",
|
||||
"client_id=" + string(MS_CLIENT_ID) + "&refresh_token=" + refreshToken + "&grant_type=refresh_token&scope=service::user.auth.xboxlive.com::MBI_SSL",
|
||||
"application/x-www-form-urlencoded");
|
||||
|
||||
auto j = parseResponse(resp);
|
||||
if (j.is_discarded()) return false;
|
||||
|
||||
newAccessToken = j.value("access_token", "");
|
||||
newRefreshToken = j.value("refresh_token", "");
|
||||
return !newAccessToken.empty();
|
||||
}
|
||||
static bool yggdrasilValidate(const string &accessToken, const string &clientToken, const string &validateUrl)
|
||||
{
|
||||
auto resp = HttpClient::post(validateUrl,
|
||||
json({{"accessToken", accessToken}, {"clientToken", clientToken}}).dump());
|
||||
return resp.statusCode == 200;
|
||||
}
|
||||
static bool yggdrasilRefresh(const string &accessToken, const string &clientToken, string &newAccessToken, string &newClientToken, const string &refreshUrl)
|
||||
{
|
||||
auto resp = HttpClient::post(refreshUrl,
|
||||
json({{"accessToken", accessToken}, {"clientToken", clientToken}}).dump());
|
||||
|
||||
auto j = parseResponse(resp);
|
||||
if (j.is_discarded()) return false;
|
||||
|
||||
newAccessToken = j.value("accessToken", "");
|
||||
newClientToken = j.value("clientToken", "");
|
||||
return !newAccessToken.empty();
|
||||
}
|
||||
|
||||
void AuthFlow::microsoftFlowThread()
|
||||
{
|
||||
auto dcResp = HttpClient::post(
|
||||
"https://login.live.com/oauth20_connect.srf",
|
||||
"client_id=" + string(MS_CLIENT_ID) + "&scope=service::user.auth.xboxlive.com::MBI_SSL&response_type=device_code",
|
||||
"application/x-www-form-urlencoded"
|
||||
);
|
||||
|
||||
auto dcJson = parseResponse(dcResp);
|
||||
if (dcJson.is_discarded())
|
||||
{
|
||||
authFail(result, state, L"Failed to get device code");
|
||||
return;
|
||||
}
|
||||
|
||||
string deviceCode = dcJson.value("device_code", "");
|
||||
string uCode = dcJson.value("user_code", "");
|
||||
string vUri = dcJson.value("verification_uri", "");
|
||||
int interval = dcJson.value("interval", 5);
|
||||
|
||||
if (deviceCode.empty() || uCode.empty())
|
||||
{
|
||||
authFail(result, state, L"Missing device code fields");
|
||||
return;
|
||||
}
|
||||
|
||||
userCode = convStringToWstring(uCode);
|
||||
verificationUri = convStringToWstring(vUri);
|
||||
|
||||
// copy code to clipboard so the user can just paste it
|
||||
if (OpenClipboard(nullptr))
|
||||
{
|
||||
EmptyClipboard();
|
||||
size_t bytes = (uCode.size() + 1) * sizeof(char);
|
||||
HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, bytes);
|
||||
if (hMem)
|
||||
{
|
||||
memcpy(GlobalLock(hMem), uCode.c_str(), bytes);
|
||||
GlobalUnlock(hMem);
|
||||
SetClipboardData(CF_TEXT, hMem);
|
||||
}
|
||||
CloseClipboard();
|
||||
}
|
||||
|
||||
if (!vUri.empty())
|
||||
ShellExecuteW(nullptr, L"open", verificationUri.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
|
||||
|
||||
state = AuthFlowState::POLLING;
|
||||
string msAccessToken;
|
||||
string msRefreshToken;
|
||||
const string pollBody = "client_id=" + string(MS_CLIENT_ID) + "&device_code=" + deviceCode + "&grant_type=urn:ietf:params:oauth:grant-type:device_code";
|
||||
|
||||
for (int attempt = 0; attempt < 180; attempt++)
|
||||
{
|
||||
for (int ms = 0; ms < interval * 1000; ms += 250)
|
||||
{
|
||||
if (cancelRequested) return;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
||||
}
|
||||
|
||||
auto pollResp = HttpClient::post(
|
||||
"https://login.live.com/oauth20_token.srf",
|
||||
pollBody,
|
||||
"application/x-www-form-urlencoded"
|
||||
);
|
||||
|
||||
auto pollJson = json::parse(pollResp.body, nullptr, false);
|
||||
if (pollJson.is_discarded()) continue;
|
||||
|
||||
if (pollResp.statusCode == 200)
|
||||
{
|
||||
msAccessToken = pollJson.value("access_token", "");
|
||||
msRefreshToken = pollJson.value("refresh_token", "");
|
||||
if (!msAccessToken.empty()) break;
|
||||
}
|
||||
|
||||
string err = pollJson.value("error", "");
|
||||
if (err == "authorization_pending") continue;
|
||||
if (err == "slow_down") { interval += 5; continue; }
|
||||
if (!err.empty())
|
||||
{
|
||||
result = {false, {}, {}, {}, {}, convStringToWstring("Auth error: " + err)};
|
||||
state = AuthFlowState::FAILED;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (msAccessToken.empty())
|
||||
{
|
||||
authFail(result, state, L"Timed out waiting for login");
|
||||
return;
|
||||
}
|
||||
|
||||
state = AuthFlowState::EXCHANGING;
|
||||
if (cancelRequested) return;
|
||||
|
||||
string mcAccessToken, profId, profName;
|
||||
if (!msTokenExchange(msAccessToken, mcAccessToken, profId, profName))
|
||||
{
|
||||
authFail(result, state, L"Token exchange failed");
|
||||
return;
|
||||
}
|
||||
|
||||
result = {true, convStringToWstring(profName), convStringToWstring(profId), convStringToWstring(mcAccessToken), convStringToWstring(msRefreshToken), {}};
|
||||
state = AuthFlowState::COMPLETE;
|
||||
}
|
||||
|
||||
void AuthFlow::yggdrasilFlowThread(const string &username, const string &password, const string &authenticateUrl)
|
||||
{
|
||||
auto resp = HttpClient::post(authenticateUrl, json({
|
||||
{"username", username},
|
||||
{"password", password},
|
||||
{"clientToken", "mcconsoles"},
|
||||
{"agent", {{"name", "Minecraft"}, {"version", 1}}}
|
||||
}).dump());
|
||||
|
||||
auto respJson = json::parse(resp.body, nullptr, false);
|
||||
|
||||
if (resp.statusCode != 200 || respJson.is_discarded())
|
||||
{
|
||||
string msg = "Yggdrasil auth failed";
|
||||
if (!respJson.is_discarded()) msg = respJson.value("errorMessage", msg);
|
||||
result = {false, {}, {}, {}, {}, convStringToWstring(msg)};
|
||||
state = AuthFlowState::FAILED;
|
||||
return;
|
||||
}
|
||||
|
||||
string accessToken = respJson.value("accessToken", "");
|
||||
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"Yggdrasil response missing profile");
|
||||
return;
|
||||
}
|
||||
|
||||
result = {true, convStringToWstring(name), convStringToWstring(uuid), convStringToWstring(accessToken), convStringToWstring(yggClientToken), {}};
|
||||
state = AuthFlowState::COMPLETE;
|
||||
}
|
||||
77
Minecraft.Client/AuthScreen.h
Normal file
77
Minecraft.Client/AuthScreen.h
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#pragma once
|
||||
using namespace std;
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
|
||||
struct AuthProfile
|
||||
{
|
||||
enum Type : uint8_t { MICROSOFT, YGGDRASIL, OFFLINE };
|
||||
|
||||
Type type;
|
||||
wstring uid;
|
||||
wstring username;
|
||||
wstring token;
|
||||
wstring clientToken;
|
||||
wstring variation;
|
||||
};
|
||||
|
||||
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, const wstring &uid = L"", const wstring &token = L"", const wstring &clientToken = L"", const wstring &variation = L"");
|
||||
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; }
|
||||
};
|
||||
struct AuthResult
|
||||
{
|
||||
bool success;
|
||||
wstring username;
|
||||
wstring uuid;
|
||||
wstring accessToken;
|
||||
wstring clientToken;
|
||||
wstring error;
|
||||
};
|
||||
|
||||
enum class AuthFlowState : uint8_t
|
||||
{
|
||||
IDLE,
|
||||
WAITING_FOR_USER,
|
||||
POLLING,
|
||||
EXCHANGING,
|
||||
COMPLETE,
|
||||
FAILED
|
||||
};
|
||||
|
||||
class AuthFlow
|
||||
{
|
||||
private:
|
||||
static std::thread workerThread;
|
||||
static std::atomic<AuthFlowState> state;
|
||||
static std::atomic<bool> cancelRequested;
|
||||
static AuthResult result;
|
||||
static wstring userCode;
|
||||
static wstring verificationUri;
|
||||
static wstring activeVariation;
|
||||
static void microsoftFlowThread();
|
||||
static void yggdrasilFlowThread(const string &username, const string &password, const string &authenticateUrl);
|
||||
|
||||
public:
|
||||
static void startMicrosoft();
|
||||
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>"
|
||||
|
|
|
|||
|
|
@ -54,6 +54,9 @@
|
|||
#include "PS3/Network/SonyVoiceChat.h"
|
||||
#endif
|
||||
#include "DLCTexturePack.h"
|
||||
#include "..\Minecraft.World\HandshakeManager.h"
|
||||
#include "..\Minecraft.World\SessionAuthModule.h"
|
||||
#include "AuthScreen.h"
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
#include "Xbox\Network\NetworkPlayerXbox.h"
|
||||
|
|
@ -140,6 +143,9 @@ ClientConnection::ClientConnection(Minecraft *minecraft, Socket *socket, int iUs
|
|||
}
|
||||
|
||||
deferredEntityLinkPackets = vector<DeferredEntityLinkPacket>();
|
||||
|
||||
handshakeManager = nullptr;
|
||||
authComplete = false;
|
||||
}
|
||||
|
||||
bool ClientConnection::isPrimaryConnection() const
|
||||
|
|
@ -202,6 +208,7 @@ ClientConnection::~ClientConnection()
|
|||
delete connection;
|
||||
delete random;
|
||||
delete savedDataStorage;
|
||||
delete handshakeManager;
|
||||
}
|
||||
|
||||
void ClientConnection::tick()
|
||||
|
|
@ -870,6 +877,7 @@ void ClientConnection::handleAddPlayer(shared_ptr<AddPlayerPacket> packet)
|
|||
player->yRotp = packet->yRot;
|
||||
player->yHeadRot = packet->yHeadRot * 360 / 256.0f;
|
||||
player->setXuid(packet->xuid);
|
||||
player->setGameUUID(packet->gameUuid);
|
||||
|
||||
#ifdef _DURANGO
|
||||
// On Durango request player display name from network manager
|
||||
|
|
@ -4129,3 +4137,64 @@ ClientConnection::DeferredEntityLinkPacket::DeferredEntityLinkPacket(shared_ptr<
|
|||
m_recievedTick = GetTickCount();
|
||||
m_packet = packet;
|
||||
}
|
||||
|
||||
void ClientConnection::beginAuth()
|
||||
{
|
||||
app.DebugPrintf("AUTH: beginAuth() starting\n");
|
||||
handshakeManager = new HandshakeManager(false);
|
||||
|
||||
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::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);
|
||||
}
|
||||
|
||||
void ClientConnection::handleAuth(const shared_ptr<AuthPacket> &packet)
|
||||
{
|
||||
if (done || authComplete) return;
|
||||
if (!handshakeManager) return;
|
||||
|
||||
auto response = handshakeManager->handlePacket(packet);
|
||||
if (response) send(response);
|
||||
|
||||
for (auto &p : handshakeManager->drainPendingPackets())
|
||||
send(p);
|
||||
if (handshakeManager->isComplete())
|
||||
{
|
||||
authComplete = true;
|
||||
const wstring &authName = handshakeManager->finalUsername;
|
||||
minecraft->user->name = authName;
|
||||
extern char g_Win64Username[17];
|
||||
extern wchar_t g_Win64UsernameW[17];
|
||||
wcsncpy_s(g_Win64UsernameW, authName.c_str(), 16);
|
||||
wcstombs_s(nullptr, g_Win64Username, g_Win64UsernameW, 16);
|
||||
}
|
||||
else if (handshakeManager->isFailed())
|
||||
{
|
||||
done = true;
|
||||
message = L"Auth handshake failed";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
|
@ -4589,6 +4589,9 @@ int CMinecraftApp::SignoutExitWorldThreadProc( void* lpParameter )
|
|||
case DisconnectPacket::eDisconnect_OutdatedClient:
|
||||
exitReasonStringId = IDS_DISCONNECTED_CLIENT_OLD;
|
||||
break;
|
||||
case DisconnectPacket::eDisconnect_AuthFailed:
|
||||
exitReasonStringId = IDS_DISCONNECTED_AUTH_FAILED;
|
||||
break;
|
||||
default:
|
||||
exitReasonStringId = IDS_DISCONNECTED;
|
||||
}
|
||||
|
|
@ -4649,6 +4652,10 @@ int CMinecraftApp::SignoutExitWorldThreadProc( void* lpParameter )
|
|||
break;
|
||||
case DisconnectPacket::eDisconnect_OutdatedClient:
|
||||
exitReasonStringId = IDS_DISCONNECTED_CLIENT_OLD;
|
||||
break;
|
||||
case DisconnectPacket::eDisconnect_AuthFailed:
|
||||
exitReasonStringId = IDS_DISCONNECTED_AUTH_FAILED;
|
||||
break;
|
||||
default:
|
||||
exitReasonStringId = IDS_DISCONNECTED;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -508,6 +508,10 @@ void IUIScene_PauseMenu::_ExitWorld(LPVOID lpParameter)
|
|||
exitReasonTitleId = IDS_CONNECTION_FAILED;
|
||||
break;
|
||||
#endif
|
||||
case DisconnectPacket::eDisconnect_AuthFailed:
|
||||
exitReasonStringId = IDS_DISCONNECTED_AUTH_FAILED;
|
||||
exitReasonTitleId = IDS_CONNECTION_FAILED;
|
||||
break;
|
||||
default:
|
||||
exitReasonStringId = IDS_CONNECTION_LOST_SERVER;
|
||||
}
|
||||
|
|
@ -609,6 +613,10 @@ void IUIScene_PauseMenu::_ExitWorld(LPVOID lpParameter)
|
|||
exitReasonTitleId = IDS_CONNECTION_FAILED;
|
||||
break;
|
||||
#endif
|
||||
case DisconnectPacket::eDisconnect_AuthFailed:
|
||||
exitReasonStringId = IDS_DISCONNECTED_AUTH_FAILED;
|
||||
exitReasonTitleId = IDS_CONNECTION_FAILED;
|
||||
break;
|
||||
default:
|
||||
exitReasonStringId = IDS_DISCONNECTED;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -133,6 +133,9 @@ void UIScene_ConnectingProgress::tick()
|
|||
case DisconnectPacket::eDisconnect_Banned:
|
||||
exitReasonStringId = IDS_DISCONNECTED_KICKED;
|
||||
break;
|
||||
case DisconnectPacket::eDisconnect_AuthFailed:
|
||||
exitReasonStringId = IDS_DISCONNECTED_AUTH_FAILED;
|
||||
break;
|
||||
default:
|
||||
exitReasonStringId = IDS_CONNECTION_LOST_SERVER;
|
||||
break;
|
||||
|
|
@ -277,6 +280,9 @@ void UIScene_ConnectingProgress::handleTimerComplete(int id)
|
|||
exitReasonStringId = IDS_DISCONNECTED_NAT_TYPE_MISMATCH;
|
||||
break;
|
||||
#endif
|
||||
case DisconnectPacket::eDisconnect_AuthFailed:
|
||||
exitReasonStringId = IDS_DISCONNECTED_AUTH_FAILED;
|
||||
break;
|
||||
default:
|
||||
exitReasonStringId = IDS_CONNECTION_LOST_SERVER;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ UIScene_Keyboard::UIScene_Keyboard(int iPad, void *initData, UILayer *parentLaye
|
|||
const wchar_t* defaultText = L"";
|
||||
|
||||
m_bPCMode = false;
|
||||
m_eKeyboardMode = C_4JInput::EKeyboardMode_Default;
|
||||
if (initData)
|
||||
{
|
||||
UIKeyboardInitData* kbData = static_cast<UIKeyboardInitData *>(initData);
|
||||
|
|
@ -36,6 +37,7 @@ UIScene_Keyboard::UIScene_Keyboard(int iPad, void *initData, UILayer *parentLaye
|
|||
if (kbData->defaultText) defaultText = kbData->defaultText;
|
||||
m_win64MaxChars = kbData->maxChars;
|
||||
m_bPCMode = kbData->pcMode;
|
||||
m_eKeyboardMode = kbData->keyboardMode;
|
||||
}
|
||||
|
||||
m_win64TextBuffer = defaultText;
|
||||
|
|
@ -171,7 +173,7 @@ void UIScene_Keyboard::tick()
|
|||
// Sync our buffer from Flash so we pick up changes made via controller/on-screen buttons.
|
||||
// Without this, switching between controller and keyboard would use stale text.
|
||||
// In PC mode we own the buffer — skip sync to preserve cursor position.
|
||||
if (!m_bPCMode)
|
||||
if (!m_bPCMode && m_eKeyboardMode != C_4JInput::EKeyboardMode_Password)
|
||||
{
|
||||
const wchar_t* flashText = m_KeyboardTextInput.getLabel();
|
||||
if (flashText)
|
||||
|
|
@ -276,7 +278,12 @@ void UIScene_Keyboard::tick()
|
|||
}
|
||||
|
||||
if (changed)
|
||||
m_KeyboardTextInput.setLabel(m_win64TextBuffer.c_str(), true /*instant*/);
|
||||
{
|
||||
if (m_eKeyboardMode == C_4JInput::EKeyboardMode_Password)
|
||||
m_KeyboardTextInput.setLabel(wstring(m_win64TextBuffer.length(), L'*').c_str(), true);
|
||||
else
|
||||
m_KeyboardTextInput.setLabel(m_win64TextBuffer.c_str(), true /*instant*/);
|
||||
}
|
||||
|
||||
if (m_bPCMode)
|
||||
{
|
||||
|
|
@ -410,8 +417,12 @@ void UIScene_Keyboard::KeyboardDonePressed()
|
|||
// Use getLabel() here — this is a timer callback (not an Iggy callback) so it's safe.
|
||||
// getLabel() reflects both physical keyboard input (pushed via setLabel) and
|
||||
// on-screen button input (set directly by Flash ActionScript).
|
||||
const wchar_t* finalText = m_KeyboardTextInput.getLabel();
|
||||
app.DebugPrintf("UI Keyboard - DONE - [%ls]\n", finalText);
|
||||
// in password mode the label is masked with asterisks, so use the real buffer instead
|
||||
const wchar_t* finalText = (m_eKeyboardMode == C_4JInput::EKeyboardMode_Password)
|
||||
? m_win64TextBuffer.c_str()
|
||||
: m_KeyboardTextInput.getLabel();
|
||||
app.DebugPrintf("UI Keyboard - DONE - [%ls]\n",
|
||||
(m_eKeyboardMode == C_4JInput::EKeyboardMode_Password) ? L"<password>" : finalText);
|
||||
|
||||
// Store the typed text so callbacks can retrieve it via Win64_GetKeyboardText()
|
||||
wcsncpy_s(g_Win64KeyboardResult, 256, finalText, _TRUNCATE);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ private:
|
|||
wstring m_win64TextBuffer;
|
||||
int m_win64MaxChars;
|
||||
bool m_bPCMode; // Hides on-screen keyboard buttons; physical keyboard only
|
||||
C_4JInput::EKeyboardMode m_eKeyboardMode;
|
||||
int m_iCursorPos;
|
||||
#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@
|
|||
#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"
|
||||
#include "UI.h"
|
||||
#include "UIScene_MainMenu.h"
|
||||
#ifdef __ORBIS__
|
||||
|
|
@ -32,6 +34,9 @@ UIScene_MainMenu::UIScene_MainMenu(int iPad, void *initData, UILayer *parentLaye
|
|||
m_eAction=eAction_None;
|
||||
m_bIgnorePress=false;
|
||||
|
||||
// auto-apply saved auth profile on startup
|
||||
AuthProfileManager::load();
|
||||
AuthProfileManager::applySelectedProfile();
|
||||
|
||||
m_buttons[static_cast<int>(eControl_PlayGame)].init(IDS_PLAY_GAME,eControl_PlayGame);
|
||||
|
||||
|
|
@ -46,12 +51,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"Switch User",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"Switch User",eControl_UnlockOrDLC);
|
||||
}
|
||||
|
||||
#ifndef _DURANGO
|
||||
|
|
@ -181,8 +186,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"Switch User");
|
||||
}
|
||||
|
||||
#if TO_BE_IMPLEMENTED
|
||||
|
|
@ -357,7 +362,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 +443,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 +458,267 @@ void UIScene_MainMenu::RunAction(int iPad)
|
|||
}
|
||||
}
|
||||
|
||||
static int s_authPad = 0;
|
||||
static void *s_authParam = nullptr;
|
||||
static wstring s_yggdrasilUsername;
|
||||
static bool s_authFlowActive = false;
|
||||
|
||||
static wstring ReadKeyboardText(int maxLen)
|
||||
{
|
||||
vector<uint16_t> buf(maxLen, 0);
|
||||
#ifdef _WINDOWS64
|
||||
Win64_GetKeyboardText(buf.data(), maxLen);
|
||||
#else
|
||||
InputManager.GetText(buf.data());
|
||||
#endif
|
||||
wstring result;
|
||||
for (int i = 0; i < maxLen && buf[i]; i++)
|
||||
result += static_cast<wchar_t>(buf[i]);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void ShowAuthMessageBox(int iPad, const wchar_t *title, const wchar_t *text,
|
||||
const wchar_t **options, int optionCount,
|
||||
int(*func)(LPVOID, int, C4JStorage::EMessageResult), void *lpParam, bool keepOpen = false)
|
||||
{
|
||||
MessageBoxInfo param = {};
|
||||
param.uiOptionC = optionCount;
|
||||
param.dwPad = iPad;
|
||||
param.Func = func;
|
||||
param.lpParam = lpParam;
|
||||
param.rawTitle = title;
|
||||
param.rawText = text;
|
||||
param.rawOptions = options;
|
||||
ui.NavigateToScene(iPad, eUIScene_MessageBox, ¶m, eUILayer_Alert, eUIGroup_Fullscreen);
|
||||
if (keepOpen)
|
||||
if (auto *scene = ui.FindScene(eUIScene_MessageBox))
|
||||
static_cast<UIScene_MessageBox *>(scene)->setKeepOpen(true);
|
||||
}
|
||||
|
||||
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];
|
||||
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 += label + L" " + p.username;
|
||||
}
|
||||
}
|
||||
return text.c_str();
|
||||
}
|
||||
|
||||
void UIScene_MainMenu::ShowAuthMenu(int iPad, void *pClass)
|
||||
{
|
||||
s_authPad = iPad;
|
||||
s_authParam = pClass;
|
||||
|
||||
static const wchar_t *authOptions[] = { L"Next", L"Use", L"Add", L"Remove" };
|
||||
ShowAuthMessageBox(iPad, L"Authentication", BuildAuthProfileText(),
|
||||
authOptions, 4, &UIScene_MainMenu::AuthMenuReturned, pClass, true);
|
||||
}
|
||||
|
||||
void UIScene_MainMenu::ShowAuthAddMenu(int iPad, void *pClass)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
AuthProfileManager::save();
|
||||
break;
|
||||
}
|
||||
case C4JStorage::EMessage_ResultThirdOption:
|
||||
{
|
||||
ui.NavigateBack(iPad);
|
||||
ShowAuthAddMenu(iPad, lpParam);
|
||||
break;
|
||||
}
|
||||
case C4JStorage::EMessage_ResultFourthOption:
|
||||
{
|
||||
if (!profiles.empty())
|
||||
{
|
||||
AuthProfileManager::removeSelectedProfile();
|
||||
if (auto *scene = ui.FindScene(eUIScene_MessageBox))
|
||||
static_cast<UIScene_MessageBox *>(scene)->updateContent(BuildAuthProfileText());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
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:
|
||||
{
|
||||
AuthFlow::startMicrosoft();
|
||||
s_authFlowActive = true;
|
||||
|
||||
static const wchar_t *cancelOptions[] = { L"Cancel" };
|
||||
ShowAuthMessageBox(iPad, L"Microsoft Login", L"Starting...",
|
||||
cancelOptions, 1, &UIScene_MainMenu::AuthMsFlowReturned, lpParam, true);
|
||||
break;
|
||||
}
|
||||
case C4JStorage::EMessage_ResultDecline:
|
||||
{
|
||||
#ifdef _WINDOWS64
|
||||
UIKeyboardInitData kbData;
|
||||
kbData.title = L"Username";
|
||||
kbData.defaultText = L"";
|
||||
kbData.maxChars = 64;
|
||||
kbData.callback = &UIScene_MainMenu::YggdrasilUsernameReturned;
|
||||
kbData.lpParam = lpParam;
|
||||
kbData.pcMode = g_KBMInput.IsKBMActive();
|
||||
ui.NavigateToScene(iPad, eUIScene_Keyboard, &kbData);
|
||||
#else
|
||||
InputManager.RequestKeyboard(L"Username", L"", (DWORD)0, 64, &UIScene_MainMenu::YggdrasilUsernameReturned, lpParam, C_4JInput::EKeyboardMode_Default);
|
||||
#endif
|
||||
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::AuthMsFlowReturned(LPVOID lpParam, int iPad, const C4JStorage::EMessageResult result)
|
||||
{
|
||||
s_authFlowActive = false;
|
||||
AuthFlow::reset();
|
||||
ui.NavigateBack(iPad);
|
||||
ShowAuthMenu(iPad, lpParam);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int UIScene_MainMenu::YggdrasilUsernameReturned(LPVOID lpParam, const bool bRes)
|
||||
{
|
||||
if (bRes)
|
||||
{
|
||||
wstring name = ReadKeyboardText(128);
|
||||
if (!name.empty())
|
||||
{
|
||||
s_yggdrasilUsername = std::move(name);
|
||||
#ifdef _WINDOWS64
|
||||
UIKeyboardInitData kbData;
|
||||
kbData.title = L"Password";
|
||||
kbData.defaultText = L"";
|
||||
kbData.maxChars = 128;
|
||||
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"Password", L"", (DWORD)0, 128, &UIScene_MainMenu::YggdrasilPasswordReturned, lpParam, C_4JInput::EKeyboardMode_Password);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
ShowAuthMenu(s_authPad, lpParam);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int UIScene_MainMenu::YggdrasilPasswordReturned(LPVOID lpParam, const bool bRes)
|
||||
{
|
||||
if (bRes)
|
||||
{
|
||||
wstring pass = ReadKeyboardText(256);
|
||||
if (!pass.empty())
|
||||
{
|
||||
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, loginTitle.c_str(), L"Authenticating...",
|
||||
waitOptions, 1, &UIScene_MainMenu::AuthMsFlowReturned, lpParam, true);
|
||||
|
||||
SecureZeroMemory(pass.data(), pass.size() * sizeof(wchar_t));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
ShowAuthMenu(s_authPad, lpParam);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int UIScene_MainMenu::AuthKeyboardReturned(LPVOID lpParam, const bool bRes)
|
||||
{
|
||||
if (bRes)
|
||||
{
|
||||
wstring name = ReadKeyboardText(128);
|
||||
if (!name.empty())
|
||||
AuthProfileManager::addProfile(AuthProfile::OFFLINE, name);
|
||||
}
|
||||
ShowAuthMenu(s_authPad, lpParam);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void UIScene_MainMenu::customDraw(IggyCustomDrawCallbackRegion *region)
|
||||
{
|
||||
if(wcscmp((wchar_t *)region->name,L"Splash")==0)
|
||||
|
|
@ -1857,6 +2126,55 @@ void UIScene_MainMenu::RunUnlockOrDLC(int iPad)
|
|||
void UIScene_MainMenu::tick()
|
||||
{
|
||||
UIScene::tick();
|
||||
if (s_authFlowActive)
|
||||
{
|
||||
auto flowState = AuthFlow::getState();
|
||||
auto *scene = ui.FindScene(eUIScene_MessageBox);
|
||||
auto *msgBox = scene ? static_cast<UIScene_MessageBox *>(scene) : nullptr;
|
||||
|
||||
switch (flowState)
|
||||
{
|
||||
case AuthFlowState::WAITING_FOR_USER:
|
||||
case AuthFlowState::POLLING:
|
||||
{
|
||||
if (msgBox && !AuthFlow::getUserCode().empty())
|
||||
{
|
||||
static wstring statusText;
|
||||
statusText = L"Go to: " + AuthFlow::getVerificationUri() + L"\nEnter code: " + AuthFlow::getUserCode();
|
||||
if (flowState == AuthFlowState::POLLING) statusText += L"\n\nWaiting for login...";
|
||||
msgBox->updateContent(statusText.c_str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AuthFlowState::EXCHANGING:
|
||||
{
|
||||
if (msgBox) msgBox->updateContent(L"Exchanging tokens...");
|
||||
break;
|
||||
}
|
||||
case AuthFlowState::COMPLETE:
|
||||
{
|
||||
s_authFlowActive = false;
|
||||
const auto &r = AuthFlow::getResult();
|
||||
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);
|
||||
break;
|
||||
}
|
||||
case AuthFlowState::FAILED:
|
||||
{
|
||||
s_authFlowActive = false;
|
||||
const auto &r = AuthFlow::getResult();
|
||||
if (msgBox) msgBox->updateContent(r.error.empty() ? L"Authentication failed" : r.error.c_str());
|
||||
AuthFlow::reset();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( (eNavigateWhenReady >= 0) )
|
||||
{
|
||||
|
|
@ -1946,7 +2264,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"Switch User",eControl_UnlockOrDLC);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -144,7 +144,16 @@ 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 int AuthMsFlowReturned(void *pParam, int iPad, C4JStorage::EMessageResult result);
|
||||
static int YggdrasilUsernameReturned(LPVOID lpParam, const bool bRes);
|
||||
static int YggdrasilPasswordReturned(LPVOID lpParam, const bool bRes);
|
||||
|
||||
static void LoadTrial();
|
||||
|
||||
#ifdef _XBOX_ONE
|
||||
|
|
|
|||
|
|
@ -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,17 @@ 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);
|
||||
IggyDataValue result;
|
||||
IggyPlayerCallMethodRS(getMovie(), &result, IggyPlayerRootPath(getMovie()), m_funcAutoResize, 0, nullptr);
|
||||
}
|
||||
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
};
|
||||
|
|
@ -295,8 +295,9 @@ typedef struct _UIKeyboardInitData
|
|||
int(*callback)(LPVOID, const bool);
|
||||
LPVOID lpParam;
|
||||
bool pcMode; // When true, disables on-screen keyboard buttons (PC keyboard users only need the text field)
|
||||
C_4JInput::EKeyboardMode keyboardMode;
|
||||
|
||||
_UIKeyboardInitData() : title(nullptr), defaultText(nullptr), maxChars(25), callback(nullptr), lpParam(nullptr), pcMode(false) {}
|
||||
_UIKeyboardInitData() : title(nullptr), defaultText(nullptr), maxChars(25), callback(nullptr), lpParam(nullptr), pcMode(false), keyboardMode(C_4JInput::EKeyboardMode_Default) {}
|
||||
} UIKeyboardInitData;
|
||||
|
||||
// Stores the text typed in UIScene_Keyboard so callbacks can retrieve it
|
||||
|
|
@ -485,6 +486,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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@
|
|||
#include "PlayerChunkMap.h"
|
||||
#include "Common\Telemetry\TelemetryManager.h"
|
||||
#include "PlayerConnection.h"
|
||||
#include "AuthScreen.h"
|
||||
#ifdef _XBOX_ONE
|
||||
#include "Durango\Network\NetworkPlayerDurango.h"
|
||||
#endif
|
||||
|
|
@ -648,6 +649,19 @@ bool MinecraftServer::initServer(int64_t seed, NetworkGameInitData *initData, DW
|
|||
//motd = settings->getString(L"motd", L"A Minecraft Server");
|
||||
//motd.replace('<27>', '$');
|
||||
|
||||
if (ShouldUseDedicatedServerProperties())
|
||||
{
|
||||
wstring am = GetDedicatedServerString(settings, L"auth-mode", L"session");
|
||||
authMode = (am == L"offline") ? "offline" : "session";
|
||||
}
|
||||
else
|
||||
{
|
||||
int idx = AuthProfileManager::getSelectedIndex();
|
||||
const auto &profiles = AuthProfileManager::getProfiles();
|
||||
authMode = (idx >= 0 && idx < static_cast<int>(profiles.size()) &&
|
||||
profiles[idx].type != AuthProfile::OFFLINE) ? "session" : "offline";
|
||||
}
|
||||
|
||||
setAnimals(GetDedicatedServerBool(settings, L"spawn-animals", true));
|
||||
setNpcsEnabled(GetDedicatedServerBool(settings, L"spawn-npcs", true));
|
||||
setPvpAllowed(app.GetGameHostOption( eGameHostOption_PvP )>0?true:false);
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ private:
|
|||
CRITICAL_SECTION m_consoleInputCS;
|
||||
public:
|
||||
bool onlineMode;
|
||||
std::string authMode;
|
||||
bool animals;
|
||||
bool npcs;
|
||||
bool pvp;
|
||||
|
|
|
|||
|
|
@ -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\SessionAuthModule.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,19 @@ void PendingConnection::disconnect(DisconnectPacket::eDisconnectReason reason)
|
|||
|
||||
void PendingConnection::handlePreLogin(shared_ptr<PreLoginPacket> packet)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
if (packet->m_netcodeVersion != MINECRAFT_NET_VERSION)
|
||||
{
|
||||
app.DebugPrintf("Netcode version is %d not equal to %d\n", packet->m_netcodeVersion, MINECRAFT_NET_VERSION);
|
||||
|
|
@ -112,7 +131,8 @@ void PendingConnection::handlePreLogin(shared_ptr<PreLoginPacket> packet)
|
|||
return;
|
||||
}
|
||||
// printf("Server: handlePreLogin\n");
|
||||
name = packet->loginKey; // 4J Stu - Change from the login packet as we know better on client end during the pre-login packet
|
||||
if (!authComplete)
|
||||
name = packet->loginKey;
|
||||
sendPreLoginResponse();
|
||||
}
|
||||
|
||||
|
|
@ -394,3 +414,35 @@ bool PendingConnection::isDisconnected()
|
|||
{
|
||||
return done;
|
||||
}
|
||||
|
||||
void PendingConnection::initAuth()
|
||||
{
|
||||
handshakeManager = new HandshakeManager(true);
|
||||
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)
|
||||
{
|
||||
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_AuthFailed);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -1298,6 +1298,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,
|
||||
|
|
@ -1306,6 +1334,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] = {};
|
||||
|
|
|
|||
|
|
@ -5867,6 +5867,10 @@ Press{*CONTROLLER_VK_B*} if you already know about Fireworks.</value>
|
|||
<value>You cannot join this game as the player you are trying to join is running a newer version of the game.</value>
|
||||
</data>
|
||||
|
||||
<data name="IDS_DISCONNECTED_AUTH_FAILED">
|
||||
<value>Authentication required. This server requires you to be signed in.</value>
|
||||
</data>
|
||||
|
||||
<data name="IDS_DEFAULT_SAVENAME">
|
||||
<value>New World</value>
|
||||
</data>
|
||||
|
|
|
|||
|
|
@ -2290,3 +2290,4 @@
|
|||
#define IDS_RICHPRESENCESTATE_BREWING 2284
|
||||
#define IDS_RICHPRESENCESTATE_ANVIL 2285
|
||||
#define IDS_RICHPRESENCESTATE_TRADING 2286
|
||||
#define IDS_DISCONNECTED_AUTH_FAILED 2287
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -371,7 +371,10 @@ namespace ServerRuntime
|
|||
}
|
||||
|
||||
auto whitelistManager = std::make_shared<WhitelistManager>(*current);
|
||||
const WhitelistedPlayerEntry entry = { formatted, name, metadata };
|
||||
WhitelistedPlayerEntry entry;
|
||||
entry.xuid = formatted;
|
||||
entry.name = name;
|
||||
entry.metadata = metadata;
|
||||
if (!whitelistManager->AddPlayer(entry))
|
||||
{
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
#include "..\Common\NetworkUtils.h"
|
||||
#include "..\Common\StringUtils.h"
|
||||
#include "..\ServerLogger.h"
|
||||
#include "..\vendor\nlohmann\json.hpp"
|
||||
#include "Common/vendor/nlohmann/json.hpp"
|
||||
#include "..\..\Minecraft.World\UUID.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdio.h>
|
||||
|
|
@ -121,9 +122,19 @@ namespace ServerRuntime
|
|||
{
|
||||
return false;
|
||||
}
|
||||
bool dirty = false;
|
||||
for (auto &entry : players)
|
||||
{
|
||||
if (!entry.uuid.empty() || entry.xuid.empty()) continue;
|
||||
try {
|
||||
uint64_t xuid = std::stoull(entry.xuid, nullptr, 0);
|
||||
if (xuid) { entry.uuid = GameUUID::fromXuid(xuid).toString(); dirty = true; }
|
||||
} catch (...) {}
|
||||
}
|
||||
|
||||
m_bannedPlayers.swap(players);
|
||||
m_bannedIps.swap(ips);
|
||||
if (dirty) Save();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -199,6 +210,7 @@ namespace ServerRuntime
|
|||
}
|
||||
|
||||
AccessStorageUtils::TryGetStringField(object, "name", &entry.name);
|
||||
AccessStorageUtils::TryGetStringField(object, "uuid", &entry.uuid);
|
||||
AccessStorageUtils::TryGetStringField(object, "created", &entry.metadata.created);
|
||||
AccessStorageUtils::TryGetStringField(object, "source", &entry.metadata.source);
|
||||
AccessStorageUtils::TryGetStringField(object, "expires", &entry.metadata.expires);
|
||||
|
|
@ -302,6 +314,8 @@ namespace ServerRuntime
|
|||
{
|
||||
OrderedJson object = OrderedJson::object();
|
||||
object["xuid"] = AccessStorageUtils::NormalizeXuid(entry.xuid);
|
||||
if (!entry.uuid.empty())
|
||||
object["uuid"] = entry.uuid;
|
||||
object["name"] = entry.name;
|
||||
object["created"] = entry.metadata.created;
|
||||
object["source"] = entry.metadata.source;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ namespace ServerRuntime
|
|||
struct BannedPlayerEntry
|
||||
{
|
||||
std::string xuid;
|
||||
std::string uuid;
|
||||
std::string name;
|
||||
BanMetadata metadata;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@
|
|||
#include "..\Common\FileUtils.h"
|
||||
#include "..\Common\StringUtils.h"
|
||||
#include "..\ServerLogger.h"
|
||||
#include "..\vendor\nlohmann\json.hpp"
|
||||
#include "Common/vendor/nlohmann/json.hpp"
|
||||
#include "..\..\Minecraft.World\UUID.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
|
|
@ -44,8 +45,18 @@ namespace ServerRuntime
|
|||
{
|
||||
return false;
|
||||
}
|
||||
bool dirty = false;
|
||||
for (auto &entry : players)
|
||||
{
|
||||
if (!entry.uuid.empty() || entry.xuid.empty()) continue;
|
||||
try {
|
||||
uint64_t xuid = std::stoull(entry.xuid, nullptr, 0);
|
||||
if (xuid) { entry.uuid = GameUUID::fromXuid(xuid).toString(); dirty = true; }
|
||||
} catch (...) {}
|
||||
}
|
||||
|
||||
m_whitelistedPlayers.swap(players);
|
||||
if (dirty) Save();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -117,6 +128,7 @@ namespace ServerRuntime
|
|||
}
|
||||
|
||||
AccessStorageUtils::TryGetStringField(object, "name", &entry.name);
|
||||
AccessStorageUtils::TryGetStringField(object, "uuid", &entry.uuid);
|
||||
AccessStorageUtils::TryGetStringField(object, "created", &entry.metadata.created);
|
||||
AccessStorageUtils::TryGetStringField(object, "source", &entry.metadata.source);
|
||||
NormalizeMetadata(&entry.metadata);
|
||||
|
|
@ -134,6 +146,8 @@ namespace ServerRuntime
|
|||
{
|
||||
OrderedJson object = OrderedJson::object();
|
||||
object["xuid"] = AccessStorageUtils::NormalizeXuid(entry.xuid);
|
||||
if (!entry.uuid.empty())
|
||||
object["uuid"] = entry.uuid;
|
||||
object["name"] = entry.name;
|
||||
object["created"] = entry.metadata.created;
|
||||
object["source"] = entry.metadata.source;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ namespace ServerRuntime
|
|||
struct WhitelistedPlayerEntry
|
||||
{
|
||||
std::string xuid;
|
||||
std::string uuid;
|
||||
std::string name;
|
||||
WhitelistMetadata metadata;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
#include "FileUtils.h"
|
||||
#include "StringUtils.h"
|
||||
|
||||
#include "..\vendor\nlohmann\json.hpp"
|
||||
#include "Common/vendor/nlohmann/json.hpp"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
|
|
|
|||
|
|
@ -196,6 +196,7 @@ namespace ServerRuntime
|
|||
case DisconnectPacket::eDisconnect_Banned: return "banned";
|
||||
case DisconnectPacket::eDisconnect_NotFriendsWithHost: return "not-friends-with-host";
|
||||
case DisconnectPacket::eDisconnect_NATMismatch: return "nat-mismatch";
|
||||
case DisconnectPacket::eDisconnect_AuthFailed: return "auth-failed";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ static const ServerPropertyDefault kServerPropertyDefaults[] =
|
|||
{
|
||||
{ "allow-flight", "true" },
|
||||
{ "allow-nether", "true" },
|
||||
{ "auth-mode", "session" },
|
||||
{ "autosave-interval", "60" },
|
||||
{ "bedrock-fog", "true" },
|
||||
{ "bonus-chest", "false" },
|
||||
|
|
@ -864,6 +865,9 @@ ServerPropertiesConfig LoadServerPropertiesConfig()
|
|||
|
||||
config.maxBuildHeight = ReadNormalizedIntProperty(&merged, "max-build-height", 256, 64, 256, &shouldWrite);
|
||||
config.motd = ReadNormalizedStringProperty(&merged, "motd", "A Minecraft Server", 255, &shouldWrite);
|
||||
config.authMode = ReadNormalizedStringProperty(&merged, "auth-mode", "session", 16, &shouldWrite);
|
||||
if (config.authMode != "session" && config.authMode != "offline")
|
||||
config.authMode = "session";
|
||||
|
||||
if (shouldWrite)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ namespace ServerRuntime
|
|||
bool doTileDrops;
|
||||
bool naturalRegeneration;
|
||||
bool doDaylightCycle;
|
||||
|
||||
std::string authMode;
|
||||
/** other MinecraftServer runtime settings */
|
||||
int maxBuildHeight;
|
||||
std::string levelType;
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ AddPlayerPacket::AddPlayerPacket(shared_ptr<Player> player, PlayerUID xuid, Play
|
|||
|
||||
this->xuid = xuid;
|
||||
this->OnlineXuid = OnlineXuid;
|
||||
this->gameUuid = player->getGameUUID();
|
||||
m_playerIndex = static_cast<BYTE>(player->getPlayerIndex());
|
||||
m_skinId = player->getCustomSkin();
|
||||
m_capeId = player->getCustomCape();
|
||||
|
|
@ -77,6 +78,8 @@ void AddPlayerPacket::read(DataInputStream *dis) //throws IOException
|
|||
carriedItem = dis->readShort();
|
||||
xuid = dis->readPlayerUID();
|
||||
OnlineXuid = dis->readPlayerUID();
|
||||
gameUuid.msb = dis->readLong();
|
||||
gameUuid.lsb = dis->readLong();
|
||||
m_playerIndex = dis->readByte();
|
||||
INT skinId = dis->readInt();
|
||||
m_skinId = *(DWORD *)&skinId;
|
||||
|
|
@ -102,6 +105,8 @@ void AddPlayerPacket::write(DataOutputStream *dos) //throws IOException
|
|||
dos->writeShort(carriedItem);
|
||||
dos->writePlayerUID(xuid);
|
||||
dos->writePlayerUID(OnlineXuid);
|
||||
dos->writeLong(gameUuid.msb);
|
||||
dos->writeLong(gameUuid.lsb);
|
||||
dos->writeByte(m_playerIndex);
|
||||
dos->writeInt(m_skinId);
|
||||
dos->writeInt(m_capeId);
|
||||
|
|
@ -117,7 +122,7 @@ void AddPlayerPacket::handle(PacketListener *listener)
|
|||
|
||||
int AddPlayerPacket::getEstimatedSize()
|
||||
{
|
||||
int iSize= sizeof(int) + Player::MAX_NAME_LENGTH + sizeof(int) + sizeof(int) + sizeof(int) + sizeof(BYTE) + sizeof(BYTE) +sizeof(short) + sizeof(PlayerUID) + sizeof(PlayerUID) + sizeof(int) + sizeof(BYTE) + sizeof(unsigned int) + sizeof(byte);
|
||||
int iSize= sizeof(int) + Player::MAX_NAME_LENGTH + sizeof(int) + sizeof(int) + sizeof(int) + sizeof(BYTE) + sizeof(BYTE) +sizeof(short) + sizeof(PlayerUID) + sizeof(PlayerUID) + sizeof(GameUUID) + sizeof(int) + sizeof(BYTE) + sizeof(unsigned int) + sizeof(byte);
|
||||
|
||||
if( entityData != nullptr )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using namespace std;
|
|||
|
||||
#include "Packet.h"
|
||||
#include "SynchedEntityData.h"
|
||||
#include "UUID.h"
|
||||
|
||||
class Player;
|
||||
|
||||
|
|
@ -21,6 +22,7 @@ public:
|
|||
int carriedItem;
|
||||
PlayerUID xuid; // 4J Added
|
||||
PlayerUID OnlineXuid; // 4J Added
|
||||
GameUUID gameUuid;
|
||||
BYTE m_playerIndex; // 4J Added
|
||||
DWORD m_skinId; // 4J Added
|
||||
DWORD m_capeId; // 4J Added
|
||||
|
|
|
|||
17
Minecraft.World/AuthModule.cpp
Normal file
17
Minecraft.World/AuthModule.cpp
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#include "stdafx.h"
|
||||
#include "AuthModule.h"
|
||||
|
||||
bool AuthModule::validate(const wstring &uid, const wstring &username)
|
||||
{
|
||||
return !uid.empty() && !username.empty() && username.length() <= 16;
|
||||
}
|
||||
|
||||
bool AuthModule::extractIdentity(const vector<pair<wstring, wstring>> &fields, wstring &outUid, wstring &outUsername)
|
||||
{
|
||||
for (const auto &[key, value] : fields)
|
||||
{
|
||||
if (key == L"uid") outUid = value;
|
||||
else if (key == L"username") outUsername = value;
|
||||
}
|
||||
return validate(outUid, outUsername);
|
||||
}
|
||||
45
Minecraft.World/AuthModule.h
Normal file
45
Minecraft.World/AuthModule.h
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
using namespace std;
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
inline string narrowStr(const wstring &w)
|
||||
{
|
||||
return string(w.begin(), w.end());
|
||||
}
|
||||
|
||||
class AuthModule
|
||||
{
|
||||
public:
|
||||
virtual ~AuthModule() = default;
|
||||
|
||||
virtual const wchar_t *schemeName() = 0;
|
||||
virtual vector<wstring> supportedVariations() = 0;
|
||||
virtual vector<pair<wstring, wstring>> getSettings(const wstring &variation) = 0;
|
||||
virtual bool onAuthData(const vector<pair<wstring, wstring>> &fields, wstring &outUid, wstring &outUsername) = 0;
|
||||
|
||||
bool validate(const wstring &uid, const wstring &username);
|
||||
|
||||
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); }
|
||||
};
|
||||
47
Minecraft.World/AuthPackets.cpp
Normal file
47
Minecraft.World/AuthPackets.cpp
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#include "stdafx.h"
|
||||
#include "InputOutputStream.h"
|
||||
#include "PacketListener.h"
|
||||
#include "AuthPackets.h"
|
||||
|
||||
AuthPacket::AuthPacket(AuthStage stage, vector<pair<wstring, wstring>> fields)
|
||||
: stage(stage), fields(std::move(fields))
|
||||
{
|
||||
}
|
||||
|
||||
void AuthPacket::read(DataInputStream *dis)
|
||||
{
|
||||
stage = static_cast<AuthStage>(dis->readByte());
|
||||
short count = dis->readShort();
|
||||
fields.clear();
|
||||
fields.reserve(count);
|
||||
for (short i = 0; i < count; i++)
|
||||
{
|
||||
wstring key = readUtf(dis, 256);
|
||||
wstring value = readUtf(dis, 4096);
|
||||
fields.emplace_back(std::move(key), std::move(value));
|
||||
}
|
||||
}
|
||||
|
||||
void AuthPacket::write(DataOutputStream *dos)
|
||||
{
|
||||
dos->writeByte(static_cast<byte>(stage));
|
||||
dos->writeShort(static_cast<short>(fields.size()));
|
||||
for (const auto &[key, value] : fields)
|
||||
{
|
||||
writeUtf(key, dos);
|
||||
writeUtf(value, dos);
|
||||
}
|
||||
}
|
||||
|
||||
void AuthPacket::handle(PacketListener *listener)
|
||||
{
|
||||
listener->handleAuth(shared_from_this());
|
||||
}
|
||||
|
||||
int AuthPacket::getEstimatedSize()
|
||||
{
|
||||
int size = 1 + 2;
|
||||
for (const auto &[key, value] : fields)
|
||||
size += 4 + static_cast<int>((key.length() + value.length()) * sizeof(wchar_t));
|
||||
return size;
|
||||
}
|
||||
36
Minecraft.World/AuthPackets.h
Normal file
36
Minecraft.World/AuthPackets.h
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
using namespace std;
|
||||
|
||||
#include "Packet.h"
|
||||
|
||||
enum class AuthStage : uint8_t
|
||||
{
|
||||
ANNOUNCE_VERSION,
|
||||
DECLARE_SCHEME,
|
||||
ACCEPT_SCHEME,
|
||||
SCHEME_SETTINGS,
|
||||
BEGIN_AUTH,
|
||||
AUTH_DATA,
|
||||
AUTH_DONE,
|
||||
ASSIGN_IDENTITY,
|
||||
CONFIRM_IDENTITY,
|
||||
AUTH_SUCCESS,
|
||||
AUTH_FAILURE
|
||||
};
|
||||
|
||||
class AuthPacket : public Packet, public enable_shared_from_this<AuthPacket>
|
||||
{
|
||||
public:
|
||||
AuthStage stage;
|
||||
vector<pair<wstring, wstring>> fields;
|
||||
|
||||
AuthPacket(AuthStage stage = AuthStage::ANNOUNCE_VERSION, vector<pair<wstring, wstring>> fields = {});
|
||||
|
||||
virtual void read(DataInputStream *dis);
|
||||
virtual void write(DataOutputStream *dos);
|
||||
virtual void handle(PacketListener *listener);
|
||||
virtual int getEstimatedSize();
|
||||
|
||||
static shared_ptr<Packet> create() { return std::make_shared<AuthPacket>(); }
|
||||
virtual int getId() { return 72; }
|
||||
};
|
||||
|
|
@ -27,4 +27,6 @@ target_compile_definitions(Minecraft.World PRIVATE
|
|||
)
|
||||
target_precompile_headers(Minecraft.World PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:stdafx.h>")
|
||||
|
||||
target_link_libraries(Minecraft.World PUBLIC CURL::libcurl)
|
||||
|
||||
configure_compiler_target(Minecraft.World)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@
|
|||
#include "LevelData.h"
|
||||
#include "DirectoryLevelStorage.h"
|
||||
#include "ConsoleSaveFileIO.h"
|
||||
#include "UUID.h"
|
||||
#include "StringHelpers.h"
|
||||
|
||||
const wstring DirectoryLevelStorage::sc_szPlayerDir(L"players/");
|
||||
|
||||
|
|
@ -168,6 +170,8 @@ DirectoryLevelStorage::DirectoryLevelStorage(ConsoleSaveFile *saveFile, const Fi
|
|||
#ifdef _LARGE_WORLDS
|
||||
m_usedMappings = byteArray(MAXIMUM_MAP_SAVE_DATA/8);
|
||||
#endif
|
||||
|
||||
migratePlayerXuidsToUuids();
|
||||
}
|
||||
|
||||
DirectoryLevelStorage::~DirectoryLevelStorage()
|
||||
|
|
@ -398,7 +402,7 @@ void DirectoryLevelStorage::save(shared_ptr<Player> player)
|
|||
#elif defined(_DURANGO)
|
||||
ConsoleSavePath realFile = ConsoleSavePath( playerDir.getName() + player->getXuid().toString() + L".dat" );
|
||||
#else
|
||||
const ConsoleSavePath realFile = ConsoleSavePath( playerDir.getName() + std::to_wstring( player->getXuid() ) + L".dat" );
|
||||
const ConsoleSavePath realFile = ConsoleSavePath( playerDir.getName() + player->getGameUUID().toWString() + L".dat" );
|
||||
#endif
|
||||
// If saves are disabled (e.g. because we are writing the save buffer to disk) then cache this player data
|
||||
if(StorageManager.GetSaveDisabled())
|
||||
|
|
@ -447,7 +451,7 @@ CompoundTag *DirectoryLevelStorage::loadPlayerDataTag(PlayerUID xuid)
|
|||
#elif defined(_DURANGO)
|
||||
ConsoleSavePath realFile = ConsoleSavePath( playerDir.getName() + xuid.toString() + L".dat" );
|
||||
#else
|
||||
const ConsoleSavePath realFile = ConsoleSavePath( playerDir.getName() + std::to_wstring( xuid ) + L".dat" );
|
||||
const ConsoleSavePath realFile = ConsoleSavePath( playerDir.getName() + GameUUID::fromXuid(xuid).toWString() + L".dat" );
|
||||
#endif
|
||||
const auto it = m_cachedSaveData.find(realFile.getName());
|
||||
if(it != m_cachedSaveData.end() )
|
||||
|
|
@ -818,3 +822,42 @@ void DirectoryLevelStorage::saveAllCachedData()
|
|||
}
|
||||
m_mapFilesToDelete.clear();
|
||||
}
|
||||
void DirectoryLevelStorage::migratePlayerXuidsToUuids()
|
||||
{
|
||||
if (!m_saveFile) return;
|
||||
|
||||
const ConsoleSavePath marker(L"migration_v1.dat");
|
||||
if (m_saveFile->doesFileExist(marker)) return;
|
||||
|
||||
if (vector<FileEntry *> *playerFiles = m_saveFile->getFilesWithPrefix(playerDir.getName()))
|
||||
{
|
||||
for (FileEntry *file : *playerFiles)
|
||||
{
|
||||
wstring stem = replaceAll(replaceAll(file->data.filename, playerDir.getName(), L""), L".dat", L"");
|
||||
|
||||
if (stem.empty() || !std::all_of(stem.begin(), stem.end(), [](wchar_t c) { return c >= L'0' && c <= L'9'; })) continue;
|
||||
|
||||
const PlayerUID xuid = _fromString<PlayerUID>(stem);
|
||||
if (xuid == INVALID_XUID) continue;
|
||||
|
||||
ConsoleSaveFileInputStream fis(m_saveFile, file);
|
||||
CompoundTag *tag = NbtIo::readCompressed(&fis);
|
||||
if (!tag) continue;
|
||||
|
||||
const wstring uuidStr = GameUUID::fromXuid(xuid).toWString();
|
||||
tag->putString(L"UUID", uuidStr);
|
||||
|
||||
ConsoleSaveFileOutputStream fos(m_saveFile, ConsoleSavePath(playerDir.getName() + uuidStr + L".dat"));
|
||||
NbtIo::writeCompressed(tag, &fos);
|
||||
delete tag;
|
||||
|
||||
m_saveFile->deleteFile(file);
|
||||
app.DebugPrintf("migrated player %ls -> %ls\n", stem.c_str(), uuidStr.c_str());
|
||||
}
|
||||
delete playerFiles;
|
||||
}
|
||||
|
||||
DWORD written = 0;
|
||||
const uint8_t ver = 1;
|
||||
m_saveFile->writeFile(m_saveFile->createFile(marker), &ver, 1, &written);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@ public:
|
|||
static wstring getPlayerDir() { return sc_szPlayerDir; }
|
||||
|
||||
private:
|
||||
void migratePlayerXuidsToUuids();
|
||||
void dontSaveMapMappingForPlayer(PlayerUID xuid);
|
||||
void deleteMapFilesForPlayer(PlayerUID xuid);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ public:
|
|||
eDisconnect_Banned,
|
||||
eDisconnect_NotFriendsWithHost,
|
||||
eDisconnect_NATMismatch,
|
||||
eDisconnect_AuthFailed,
|
||||
#ifdef __ORBIS__
|
||||
eDisconnect_NetworkError,
|
||||
#endif
|
||||
|
|
|
|||
271
Minecraft.World/HandshakeManager.cpp
Normal file
271
Minecraft.World/HandshakeManager.cpp
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
#include "stdafx.h"
|
||||
#include "HandshakeManager.h"
|
||||
#include "AuthModule.h"
|
||||
#include "HttpClient.h"
|
||||
#include "StringHelpers.h"
|
||||
#include "Common/vendor/nlohmann/json.hpp"
|
||||
|
||||
static constexpr auto PROTOCOL_VERSION = L"1.0";
|
||||
|
||||
static wstring getField(const vector<pair<wstring, wstring>> &fields, const wchar_t *key)
|
||||
{
|
||||
for (const auto &[k, v] : fields)
|
||||
if (k == key) return v;
|
||||
return {};
|
||||
}
|
||||
|
||||
HandshakeManager::HandshakeManager(bool isServer)
|
||||
: isServer(isServer), state(HandshakeState::IDLE), activeModule(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void HandshakeManager::registerModule(unique_ptr<AuthModule> module)
|
||||
{
|
||||
wstring name = module->schemeName();
|
||||
modules[std::move(name)] = std::move(module);
|
||||
}
|
||||
|
||||
void HandshakeManager::setCredentials(const wstring &token, const wstring &uid, const wstring &username, const wstring &variation)
|
||||
{
|
||||
accessToken = token;
|
||||
clientUid = uid;
|
||||
clientUsername = username;
|
||||
preferredVariation = variation;
|
||||
}
|
||||
|
||||
vector<shared_ptr<AuthPacket>> HandshakeManager::drainPendingPackets()
|
||||
{
|
||||
auto out = std::move(pendingPackets);
|
||||
pendingPackets.clear();
|
||||
return out;
|
||||
}
|
||||
|
||||
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;
|
||||
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)
|
||||
{
|
||||
switch (packet->stage)
|
||||
{
|
||||
case AuthStage::ANNOUNCE_VERSION:
|
||||
{
|
||||
protocolVersion = getField(packet->fields, L"version");
|
||||
if (protocolVersion != PROTOCOL_VERSION)
|
||||
return fail();
|
||||
|
||||
if (modules.empty())
|
||||
return fail();
|
||||
|
||||
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},
|
||||
{L"scheme", activeModule->schemeName()}
|
||||
});
|
||||
}
|
||||
|
||||
case AuthStage::ACCEPT_SCHEME:
|
||||
{
|
||||
activeVariation = getField(packet->fields, L"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();
|
||||
finalUid = uid;
|
||||
finalUsername = username;
|
||||
state = HandshakeState::AUTH_DATA_EXCHANGED;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
case AuthStage::AUTH_DONE:
|
||||
{
|
||||
if (getField(packet->fields, L"uid") != finalUid || getField(packet->fields, L"username") != finalUsername)
|
||||
return fail();
|
||||
|
||||
state = HandshakeState::IDENTITY_ASSIGNED;
|
||||
return makePacket(AuthStage::ASSIGN_IDENTITY, {
|
||||
{L"uid", finalUid},
|
||||
{L"username", finalUsername}
|
||||
});
|
||||
}
|
||||
|
||||
case AuthStage::CONFIRM_IDENTITY:
|
||||
{
|
||||
if (getField(packet->fields, L"uid") != finalUid || getField(packet->fields, L"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:
|
||||
{
|
||||
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();
|
||||
|
||||
auto it = modules.find(scheme);
|
||||
if (it == modules.end())
|
||||
return fail();
|
||||
|
||||
activeModule = it->second.get();
|
||||
|
||||
auto variations = activeModule->supportedVariations();
|
||||
if (!preferredVariation.empty() &&
|
||||
std::find(variations.begin(), variations.end(), preferredVariation) != variations.end())
|
||||
activeVariation = preferredVariation;
|
||||
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 joinUrlW = getField(packet->fields, L"joinUrl");
|
||||
wstring scheme(activeModule->schemeName());
|
||||
|
||||
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)}
|
||||
};
|
||||
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();
|
||||
}
|
||||
|
||||
state = HandshakeState::AUTH_IN_PROGRESS;
|
||||
pendingPackets.push_back(makePacket(AuthStage::BEGIN_AUTH));
|
||||
pendingPackets.push_back(makePacket(AuthStage::AUTH_DATA, {
|
||||
{L"uid", clientUid},
|
||||
{L"username", clientUsername}
|
||||
}));
|
||||
pendingPackets.push_back(makePacket(AuthStage::AUTH_DONE, {
|
||||
{L"uid", clientUid},
|
||||
{L"username", clientUsername}
|
||||
}));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
case AuthStage::ASSIGN_IDENTITY:
|
||||
{
|
||||
finalUid = getField(packet->fields, L"uid");
|
||||
finalUsername = getField(packet->fields, L"username");
|
||||
|
||||
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);
|
||||
}
|
||||
66
Minecraft.World/HandshakeManager.h
Normal file
66
Minecraft.World/HandshakeManager.h
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
#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, unique_ptr<AuthModule>> modules;
|
||||
AuthModule *activeModule;
|
||||
wstring activeVariation;
|
||||
wstring protocolVersion;
|
||||
|
||||
wstring accessToken;
|
||||
wstring clientUid;
|
||||
wstring clientUsername;
|
||||
wstring preferredVariation;
|
||||
|
||||
vector<shared_ptr<AuthPacket>> pendingPackets;
|
||||
|
||||
public:
|
||||
wstring finalUid;
|
||||
wstring finalUsername;
|
||||
|
||||
HandshakeManager(bool isServer);
|
||||
~HandshakeManager() = default;
|
||||
|
||||
void registerModule(unique_ptr<AuthModule> module);
|
||||
void setCredentials(const wstring &token, const wstring &uid, const wstring &username, const wstring &variation = L"");
|
||||
shared_ptr<AuthPacket> handlePacket(const shared_ptr<AuthPacket> &packet);
|
||||
shared_ptr<AuthPacket> createInitialPacket();
|
||||
vector<shared_ptr<AuthPacket>> drainPendingPackets();
|
||||
|
||||
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();
|
||||
};
|
||||
66
Minecraft.World/HttpClient.cpp
Normal file
66
Minecraft.World/HttpClient.cpp
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
#include "stdafx.h"
|
||||
#include "HttpClient.h"
|
||||
#include <curl/curl.h>
|
||||
|
||||
static size_t writeCallback(char *data, size_t size, size_t nmemb, void *userdata)
|
||||
{
|
||||
auto *buf = static_cast<std::string *>(userdata);
|
||||
buf->append(data, size * nmemb);
|
||||
return size * nmemb;
|
||||
}
|
||||
|
||||
static HttpResponse performRequest(CURL *curl, struct curl_slist *extraHeaders = nullptr)
|
||||
{
|
||||
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);
|
||||
if (extraHeaders)
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, extraHeaders);
|
||||
|
||||
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);
|
||||
if (extraHeaders)
|
||||
curl_slist_free_all(extraHeaders);
|
||||
return {statusCode, std::move(responseBody)};
|
||||
}
|
||||
|
||||
static struct curl_slist *buildHeaders(const std::vector<std::string> &headers)
|
||||
{
|
||||
struct curl_slist *list = nullptr;
|
||||
for (const auto &h : headers)
|
||||
list = curl_slist_append(list, h.c_str());
|
||||
return list;
|
||||
}
|
||||
|
||||
HttpResponse HttpClient::get(const std::string &url, const std::vector<std::string> &headers)
|
||||
{
|
||||
CURL *curl = curl_easy_init();
|
||||
if (!curl)
|
||||
return {0, ""};
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
return performRequest(curl, headers.empty() ? nullptr : buildHeaders(headers));
|
||||
}
|
||||
|
||||
HttpResponse HttpClient::post(const std::string &url, const std::string &body, const std::string &contentType, const std::vector<std::string> &headers)
|
||||
{
|
||||
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<long>(body.size()));
|
||||
|
||||
auto *headerList = buildHeaders(headers);
|
||||
headerList = curl_slist_append(headerList, ("Content-Type: " + contentType).c_str());
|
||||
|
||||
return performRequest(curl, headerList);
|
||||
}
|
||||
17
Minecraft.World/HttpClient.h
Normal file
17
Minecraft.World/HttpClient.h
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct HttpResponse
|
||||
{
|
||||
int statusCode;
|
||||
std::string body;
|
||||
};
|
||||
|
||||
class HttpClient
|
||||
{
|
||||
public:
|
||||
static HttpResponse get(const std::string &url, const std::vector<std::string> &headers = {});
|
||||
static HttpResponse post(const std::string &url, const std::string &body, const std::string &contentType = "application/json", const std::vector<std::string> &headers = {});
|
||||
};
|
||||
|
|
@ -111,6 +111,8 @@ void LoginPacket::read(DataInputStream *dis) //throws IOException
|
|||
maxPlayers = dis->readByte();
|
||||
m_offlineXuid = dis->readPlayerUID();
|
||||
m_onlineXuid = dis->readPlayerUID();
|
||||
m_gameUuid.msb = dis->readLong();
|
||||
m_gameUuid.lsb = dis->readLong();
|
||||
m_friendsOnlyUGC = dis->readBoolean();
|
||||
m_ugcPlayersVersion = dis->readInt();
|
||||
difficulty = dis->readByte();
|
||||
|
|
@ -150,6 +152,8 @@ void LoginPacket::write(DataOutputStream *dos) //throws IOException
|
|||
dos->writeByte(maxPlayers);
|
||||
dos->writePlayerUID(m_offlineXuid);
|
||||
dos->writePlayerUID(m_onlineXuid);
|
||||
dos->writeLong(m_gameUuid.msb);
|
||||
dos->writeLong(m_gameUuid.lsb);
|
||||
dos->writeBoolean(m_friendsOnlyUGC);
|
||||
dos->writeInt(m_ugcPlayersVersion);
|
||||
dos->writeByte(difficulty);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
using namespace std;
|
||||
|
||||
#include "Packet.h"
|
||||
#include "UUID.h"
|
||||
class LevelType;
|
||||
|
||||
class LoginPacket : public Packet, public enable_shared_from_this<LoginPacket>
|
||||
|
|
@ -12,6 +13,7 @@ public:
|
|||
int64_t seed;
|
||||
char dimension;
|
||||
PlayerUID m_offlineXuid, m_onlineXuid; // 4J Added
|
||||
GameUUID m_gameUuid;
|
||||
char difficulty; // 4J Added
|
||||
bool m_friendsOnlyUGC; // 4J Added
|
||||
DWORD m_ugcPlayersVersion; // 4J Added
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -150,6 +150,7 @@ void Packet::staticCtor()
|
|||
//map(253, true, false, ServerAuthDataPacket.class);
|
||||
map(254, false, true, false, false, typeid(GetInfoPacket), GetInfoPacket::create); // TODO New for 1.8.2 - Needs sendToAny?
|
||||
map(255, true, true, true, false, typeid(DisconnectPacket), DisconnectPacket::create);
|
||||
map(72, true, true, true, false, typeid(AuthPacket), AuthPacket::create);
|
||||
}
|
||||
|
||||
IllegalArgumentException::IllegalArgumentException(const wstring& information)
|
||||
|
|
|
|||
|
|
@ -491,3 +491,8 @@ void PacketListener::handleGameCommand(shared_ptr<GameCommandPacket> packet)
|
|||
{
|
||||
onUnhandledPacket( (shared_ptr<Packet> ) packet);
|
||||
}
|
||||
|
||||
void PacketListener::handleAuth(const shared_ptr<AuthPacket> &packet)
|
||||
{
|
||||
onUnhandledPacket( (shared_ptr<Packet> ) packet);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ class KickPlayerPacket;
|
|||
class AdditionalModelPartsPacket;
|
||||
class XZPacket;
|
||||
class GameCommandPacket;
|
||||
class AuthPacket;
|
||||
|
||||
class PacketListener
|
||||
{
|
||||
|
|
@ -227,4 +228,6 @@ public:
|
|||
virtual void handleKickPlayer(shared_ptr<KickPlayerPacket> packet);
|
||||
virtual void handleXZ(shared_ptr<XZPacket> packet);
|
||||
virtual void handleGameCommand(shared_ptr<GameCommandPacket> packet);
|
||||
|
||||
virtual void handleAuth(const shared_ptr<AuthPacket> &packet);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -762,6 +762,8 @@ unsigned int Player::getSkinAnimOverrideBitmask(DWORD skinId)
|
|||
void Player::setXuid(PlayerUID xuid)
|
||||
{
|
||||
m_xuid = xuid;
|
||||
if (m_gameUuid.isNil() && xuid != INVALID_XUID)
|
||||
m_gameUuid = GameUUID::fromXuid(xuid);
|
||||
#ifdef _XBOX_ONE
|
||||
// 4J Stu - For XboxOne (and probably in the future all other platforms) we store a UUID for the player to use as the owner key for tamed animals
|
||||
// This should just be a string version of the xuid
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using namespace std;
|
|||
#include "PlayerEnderChestContainer.h"
|
||||
#include "CommandSender.h"
|
||||
#include "ScoreHolder.h"
|
||||
#include "UUID.h"
|
||||
|
||||
class AbstractContainerMenu;
|
||||
class Stats;
|
||||
|
|
@ -417,7 +418,8 @@ public:
|
|||
PlayerUID getXuid() { return m_xuid; }
|
||||
void setOnlineXuid(PlayerUID xuid) { m_OnlineXuid = xuid; }
|
||||
PlayerUID getOnlineXuid() { return m_OnlineXuid; }
|
||||
|
||||
void setGameUUID(const GameUUID& uuid) { m_gameUuid = uuid; }
|
||||
GameUUID getGameUUID() const { return m_gameUuid; }
|
||||
void setPlayerIndex(DWORD dwIndex) { m_playerIndex = dwIndex; }
|
||||
DWORD getPlayerIndex() { return m_playerIndex; }
|
||||
|
||||
|
|
@ -431,6 +433,7 @@ public:
|
|||
private:
|
||||
PlayerUID m_xuid;
|
||||
PlayerUID m_OnlineXuid;
|
||||
GameUUID m_gameUuid;
|
||||
|
||||
protected:
|
||||
bool m_bShownOnMaps;
|
||||
|
|
|
|||
139
Minecraft.World/SessionAuthModule.cpp
Normal file
139
Minecraft.World/SessionAuthModule.cpp
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
#include "stdafx.h"
|
||||
#include "SessionAuthModule.h"
|
||||
#include "HttpClient.h"
|
||||
#include "StringHelpers.h"
|
||||
#include "Common/vendor/nlohmann/json.hpp"
|
||||
#include <random>
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static vector<YggdrasilProviderConfig> s_registryProviders;
|
||||
static bool s_registryLoaded = false;
|
||||
|
||||
void YggdrasilRegistry::load()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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 *p = YggdrasilRegistry::find(variation);
|
||||
if (!p) return {};
|
||||
|
||||
activeProvider = p;
|
||||
activeServerId = generateServerId();
|
||||
return {
|
||||
{L"joinUrl", convStringToWstring(p->joinUrl())},
|
||||
{L"serverId", activeServerId}
|
||||
};
|
||||
}
|
||||
|
||||
bool SessionAuthModule::serverVerify(const YggdrasilProviderConfig &provider, const wstring &username, const wstring &serverId, wstring &outUid, wstring &outUsername)
|
||||
{
|
||||
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;
|
||||
|
||||
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 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);
|
||||
}
|
||||
44
Minecraft.World/SessionAuthModule.h
Normal file
44
Minecraft.World/SessionAuthModule.h
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#pragma once
|
||||
using namespace std;
|
||||
#include "AuthModule.h"
|
||||
#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
|
||||
{
|
||||
private:
|
||||
const YggdrasilProviderConfig *activeProvider = nullptr;
|
||||
wstring activeServerId;
|
||||
|
||||
bool serverVerify(const YggdrasilProviderConfig &provider, const wstring &username, const wstring &serverId, wstring &outUid, wstring &outUsername);
|
||||
|
||||
public:
|
||||
SessionAuthModule() = default;
|
||||
|
||||
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;
|
||||
};
|
||||
144
Minecraft.World/UUID.cpp
Normal file
144
Minecraft.World/UUID.cpp
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
#include "stdafx.h"
|
||||
#include "UUID.h"
|
||||
#include "Random.h"
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
static void sha1_block(uint32_t h[5], const uint8_t block[64])
|
||||
{
|
||||
uint32_t w[80];
|
||||
for (int i = 0; i < 16; i++)
|
||||
w[i] = (block[i * 4] << 24) | (block[i * 4 + 1] << 16) | (block[i * 4 + 2] << 8) | block[i * 4 + 3];
|
||||
for (int i = 16; i < 80; i++) {
|
||||
uint32_t x = w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16];
|
||||
w[i] = (x << 1) | (x >> 31);
|
||||
}
|
||||
uint32_t a = h[0], b = h[1], c = h[2], d = h[3], e = h[4];
|
||||
for (int i = 0; i < 80; i++) {
|
||||
uint32_t f, k;
|
||||
if (i < 20) { f = (b & c) | (~b & d); k = 0x5A827999; }
|
||||
else if (i < 40) { f = b ^ c ^ d; k = 0x6ED9EBA1; }
|
||||
else if (i < 60) { f = (b & c) | (b & d) | (c & d); k = 0x8F1BBCDC; }
|
||||
else { f = b ^ c ^ d; k = 0xCA62C1D6; }
|
||||
uint32_t tmp = ((a << 5) | (a >> 27)) + f + e + k + w[i];
|
||||
e = d; d = c; c = (b << 30) | (b >> 2); b = a; a = tmp;
|
||||
}
|
||||
h[0] += a; h[1] += b; h[2] += c; h[3] += d; h[4] += e;
|
||||
}
|
||||
|
||||
static void sha1(const uint8_t* data, size_t len, uint8_t out[20])
|
||||
{
|
||||
uint32_t h[5] = { 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0 };
|
||||
uint64_t bitLen = len * 8;
|
||||
|
||||
size_t fullBlocks = len / 64;
|
||||
for (size_t blk = 0; blk < fullBlocks; blk++)
|
||||
sha1_block(h, data + blk * 64);
|
||||
|
||||
uint8_t tail[128] = {};
|
||||
size_t rem = len - fullBlocks * 64;
|
||||
if (rem) memcpy(tail, data + fullBlocks * 64, rem);
|
||||
tail[rem] = 0x80;
|
||||
size_t tailLen = (rem < 56) ? 64 : 128;
|
||||
for (int i = 0; i < 8; i++)
|
||||
tail[tailLen - 1 - i] = (uint8_t)(bitLen >> (i * 8));
|
||||
|
||||
for (size_t off = 0; off < tailLen; off += 64)
|
||||
sha1_block(h, tail + off);
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
out[i * 4] = (uint8_t)(h[i] >> 24);
|
||||
out[i * 4 + 1] = (uint8_t)(h[i] >> 16);
|
||||
out[i * 4 + 2] = (uint8_t)(h[i] >> 8);
|
||||
out[i * 4 + 3] = (uint8_t)(h[i]);
|
||||
}
|
||||
}
|
||||
static constexpr char HEX[] = "0123456789abcdef";
|
||||
static constexpr uint8_t hexVal(char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9') return c - '0';
|
||||
if (c >= 'a' && c <= 'f') return 10 + c - 'a';
|
||||
if (c >= 'A' && c <= 'F') return 10 + c - 'A';
|
||||
return 0;
|
||||
}
|
||||
|
||||
void GameUUID::toBytes(uint8_t out[16]) const
|
||||
{
|
||||
for (int i = 7; i >= 0; i--) out[7 - i] = (uint8_t)(msb >> (i * 8));
|
||||
for (int i = 7; i >= 0; i--) out[15 - i] = (uint8_t)(lsb >> (i * 8));
|
||||
}
|
||||
|
||||
GameUUID GameUUID::fromBytes(const uint8_t b[16])
|
||||
{
|
||||
GameUUID u;
|
||||
for (int i = 0; i < 8; i++) u.msb = (u.msb << 8) | b[i];
|
||||
for (int i = 0; i < 8; i++) u.lsb = (u.lsb << 8) | b[8 + i];
|
||||
return u;
|
||||
}
|
||||
std::string GameUUID::toString() const
|
||||
{
|
||||
uint8_t b[16];
|
||||
toBytes(b);
|
||||
char buf[37];
|
||||
int p = 0;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (i == 4 || i == 6 || i == 8 || i == 10) buf[p++] = '-';
|
||||
buf[p++] = HEX[b[i] >> 4];
|
||||
buf[p++] = HEX[b[i] & 0xf];
|
||||
}
|
||||
buf[p] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
std::wstring GameUUID::toWString() const
|
||||
{
|
||||
std::string s = toString();
|
||||
return { s.begin(), s.end() };
|
||||
}
|
||||
|
||||
GameUUID GameUUID::fromString(const std::string& s)
|
||||
{
|
||||
uint8_t b[16] = {};
|
||||
int bi = 0;
|
||||
for (size_t i = 0; i < s.size() && bi < 16; i++) {
|
||||
if (s[i] == '-') continue;
|
||||
if (i + 1 >= s.size()) break;
|
||||
b[bi++] = (hexVal(s[i]) << 4) | hexVal(s[i + 1]);
|
||||
i++;
|
||||
}
|
||||
return fromBytes(b);
|
||||
}
|
||||
|
||||
GameUUID GameUUID::fromWString(const std::wstring& s)
|
||||
{
|
||||
return fromString({ s.begin(), s.end() });
|
||||
}
|
||||
|
||||
GameUUID GameUUID::v4(uint64_t high, uint64_t low)
|
||||
{
|
||||
return { (high & ~0xF000ULL) | 0x4000ULL, (low & ~0xC000000000000000ULL) | 0x8000000000000000ULL };
|
||||
}
|
||||
|
||||
GameUUID GameUUID::v5(const GameUUID& ns, const std::string& name)
|
||||
{
|
||||
std::vector<uint8_t> input(16 + name.size());
|
||||
ns.toBytes(input.data());
|
||||
memcpy(input.data() + 16, name.data(), name.size());
|
||||
|
||||
uint8_t hash[20];
|
||||
sha1(input.data(), input.size(), hash);
|
||||
GameUUID u = fromBytes(hash);
|
||||
u.msb = (u.msb & ~0xF000ULL) | 0x5000ULL;
|
||||
u.lsb = (u.lsb & ~0xC000000000000000ULL) | 0x8000000000000000ULL;
|
||||
return u;
|
||||
}
|
||||
|
||||
GameUUID GameUUID::fromXuid(uint64_t xuid)
|
||||
{
|
||||
return v5(MCCONSOLES_NAMESPACE_UUID, std::to_string(xuid));
|
||||
}
|
||||
|
||||
GameUUID GameUUID::random()
|
||||
{
|
||||
Random r;
|
||||
return v4(r.nextLong(), r.nextLong());
|
||||
}
|
||||
27
Minecraft.World/UUID.h
Normal file
27
Minecraft.World/UUID.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
struct GameUUID {
|
||||
uint64_t msb = 0;
|
||||
uint64_t lsb = 0;
|
||||
|
||||
constexpr bool isNil() const { return msb == 0 && lsb == 0; }
|
||||
constexpr bool operator==(const GameUUID& o) const { return msb == o.msb && lsb == o.lsb; }
|
||||
constexpr bool operator!=(const GameUUID& o) const { return !(*this == o); }
|
||||
constexpr bool operator<(const GameUUID& o) const { return msb < o.msb || (msb == o.msb && lsb < o.lsb); }
|
||||
|
||||
void toBytes(uint8_t out[16]) const;
|
||||
std::string toString() const;
|
||||
std::wstring toWString() const;
|
||||
|
||||
static GameUUID fromBytes(const uint8_t b[16]);
|
||||
static GameUUID fromString(const std::string& s);
|
||||
static GameUUID fromWString(const std::wstring& s);
|
||||
static GameUUID v4(uint64_t high, uint64_t low);
|
||||
static GameUUID v5(const GameUUID& ns, const std::string& name);
|
||||
static GameUUID fromXuid(uint64_t xuid);
|
||||
static GameUUID random();
|
||||
};
|
||||
|
||||
inline constexpr GameUUID MCCONSOLES_NAMESPACE_UUID = { 0x4d696e6563726166ULL, 0x74436f6e736f6c65ULL };
|
||||
|
|
@ -259,6 +259,14 @@ set(_MINECRAFT_WORLD_COMMON_NET_MINECRAFT_NETWORK_PACKET
|
|||
"${CMAKE_CURRENT_SOURCE_DIR}/AddPlayerPacket.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/AnimatePacket.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/AnimatePacket.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/AuthModule.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/AuthModule.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/SessionAuthModule.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/SessionAuthModule.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/AuthPackets.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/AuthPackets.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/UUID.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/UUID.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/AwardStatPacket.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/AwardStatPacket.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/BlockRegionUpdatePacket.cpp"
|
||||
|
|
@ -314,6 +322,10 @@ 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}/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"
|
||||
|
|
|
|||
|
|
@ -108,4 +108,5 @@
|
|||
#include "KickPlayerPacket.h"
|
||||
#include "XZPacket.h"
|
||||
#include "GameCommandPacket.h"
|
||||
#include "AuthPackets.h"
|
||||
|
||||
|
|
|
|||
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…
Add table
Add a link
Reference in a new issue