mirror of
https://github.com/smartcmd/MinecraftConsoles.git
synced 2026-08-20 09:57:09 +00:00
feat: adds auth, implicit support for 64x64 skins,
This commit is contained in:
parent
73d713878c
commit
5987473f5d
|
|
@ -5,6 +5,68 @@ set(CMAKE_CXX_STANDARD 17)
|
|||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
function(configure_msvc_system_includes)
|
||||
if(NOT MSVC)
|
||||
return()
|
||||
endif()
|
||||
|
||||
get_filename_component(_msvc_bin_dir "${CMAKE_CXX_COMPILER}" DIRECTORY)
|
||||
get_filename_component(_msvc_root "${_msvc_bin_dir}/../../.." ABSOLUTE)
|
||||
set(_msvc_include_dir "${_msvc_root}/include")
|
||||
|
||||
set(_sdk_root "C:/Program Files (x86)/Windows Kits/10/Include")
|
||||
set(_sdk_lib_root "C:/Program Files (x86)/Windows Kits/10/Lib")
|
||||
set(_sdk_include_dirs)
|
||||
set(_sdk_lib_dirs)
|
||||
if(EXISTS "${_sdk_root}")
|
||||
file(GLOB _sdk_versions LIST_DIRECTORIES true "${_sdk_root}/*")
|
||||
if(_sdk_versions)
|
||||
list(SORT _sdk_versions COMPARE NATURAL ORDER DESCENDING)
|
||||
list(GET _sdk_versions 0 _sdk_version_dir)
|
||||
foreach(_sdk_subdir ucrt shared um winrt cppwinrt)
|
||||
if(EXISTS "${_sdk_version_dir}/${_sdk_subdir}")
|
||||
list(APPEND _sdk_include_dirs "${_sdk_version_dir}/${_sdk_subdir}")
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(EXISTS "${_sdk_lib_root}")
|
||||
file(GLOB _sdk_lib_versions LIST_DIRECTORIES true "${_sdk_lib_root}/*")
|
||||
if(_sdk_lib_versions)
|
||||
list(SORT _sdk_lib_versions COMPARE NATURAL ORDER DESCENDING)
|
||||
list(GET _sdk_lib_versions 0 _sdk_lib_version_dir)
|
||||
foreach(_sdk_lib_subdir ucrt um)
|
||||
if(EXISTS "${_sdk_lib_version_dir}/${_sdk_lib_subdir}/x64")
|
||||
list(APPEND _sdk_lib_dirs "${_sdk_lib_version_dir}/${_sdk_lib_subdir}/x64")
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(_system_include_dirs)
|
||||
if(EXISTS "${_msvc_include_dir}")
|
||||
list(APPEND _system_include_dirs "${_msvc_include_dir}")
|
||||
endif()
|
||||
list(APPEND _system_include_dirs ${_sdk_include_dirs})
|
||||
|
||||
set(_system_lib_dirs)
|
||||
if(EXISTS "${_msvc_root}/lib/x64")
|
||||
list(APPEND _system_lib_dirs "${_msvc_root}/lib/x64")
|
||||
endif()
|
||||
list(APPEND _system_lib_dirs ${_sdk_lib_dirs})
|
||||
|
||||
if(_system_include_dirs)
|
||||
list(REMOVE_DUPLICATES _system_include_dirs)
|
||||
include_directories(${_system_include_dirs})
|
||||
endif()
|
||||
|
||||
if(_system_lib_dirs)
|
||||
list(REMOVE_DUPLICATES _system_lib_dirs)
|
||||
link_directories(${_system_lib_dirs})
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
if(NOT WIN32)
|
||||
message(FATAL_ERROR "This CMake build currently supports Windows only.")
|
||||
endif()
|
||||
|
|
@ -13,6 +75,8 @@ if(NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
|
|||
message(FATAL_ERROR "Use a 64-bit generator/toolchain (x64).")
|
||||
endif()
|
||||
|
||||
configure_msvc_system_includes()
|
||||
|
||||
set(CMAKE_CONFIGURATION_TYPES
|
||||
"Debug"
|
||||
"Release"
|
||||
|
|
@ -77,6 +141,7 @@ list(APPEND MINECRAFT_SHARED_DEFINES ${PLATFORM_DEFINES})
|
|||
# Sources
|
||||
# ---
|
||||
add_subdirectory(Minecraft.World)
|
||||
add_subdirectory(newauth)
|
||||
add_subdirectory(Minecraft.Client)
|
||||
if(PLATFORM_NAME STREQUAL "Windows64") # Server is only supported on Windows for now
|
||||
add_subdirectory(Minecraft.Server)
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ set_target_properties(Minecraft.Client PROPERTIES
|
|||
|
||||
target_link_libraries(Minecraft.Client PRIVATE
|
||||
Minecraft.World
|
||||
newauth
|
||||
d3d11
|
||||
d3dcompiler
|
||||
XInput9_1_0
|
||||
|
|
|
|||
|
|
@ -58,6 +58,12 @@
|
|||
#ifdef _WINDOWS64
|
||||
#include "Xbox\Network\NetworkPlayerXbox.h"
|
||||
#include "Common\Network\PlatformNetworkManagerStub.h"
|
||||
#include "..\newauth\include\newauth.h"
|
||||
#include "..\newauth\include\newauthManager.h"
|
||||
#include "..\Minecraft.World\AuthSchemePacket.h"
|
||||
#include "..\Minecraft.World\AuthResponsePacket.h"
|
||||
#include "..\Minecraft.World\AuthResultPacket.h"
|
||||
#include "..\Minecraft.World\GameUUID.h"
|
||||
#endif
|
||||
|
||||
|
||||
|
|
@ -368,6 +374,14 @@ void ClientConnection::handleLogin(shared_ptr<LoginPacket> packet)
|
|||
minecraft->player->setCustomSkin( app.GetPlayerSkinId(m_userIndex) );
|
||||
minecraft->player->setCustomCape( app.GetPlayerCapeId(m_userIndex) );
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
// gives me a skin.
|
||||
if (!m_authSkinKey.empty() && app.IsFileInMemoryTextures(m_authSkinKey))
|
||||
{
|
||||
minecraft->player->customTextureUrl = m_authSkinKey;
|
||||
app.DebugPrintf("Auth: Applied MS skin '%ls' to local player\n", m_authSkinKey.c_str());
|
||||
}
|
||||
#endif
|
||||
|
||||
minecraft->createPrimaryLocalPlayer(ProfileManager.GetPrimaryPad());
|
||||
|
||||
|
|
@ -751,6 +765,45 @@ void ClientConnection::handleAddEntity(shared_ptr<AddEntityPacket> packet)
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
bool ClientConnection::ensureRemoteNetworkPlayer(BYTE smallId, const std::wstring *playerName, PlayerUID xuid)
|
||||
{
|
||||
if (smallId >= MINECRAFT_NET_MAX_PLAYERS)
|
||||
return false;
|
||||
|
||||
extern CPlatformNetworkManagerStub* g_pPlatformNetworkManager;
|
||||
if (g_pPlatformNetworkManager == nullptr)
|
||||
return false;
|
||||
|
||||
INetworkPlayer *networkPlayer = g_NetworkManager.GetPlayerBySmallId(smallId);
|
||||
IQNetPlayer *qnetPlayer = nullptr;
|
||||
if (networkPlayer != nullptr)
|
||||
{
|
||||
qnetPlayer = static_cast<NetworkPlayerXbox *>(networkPlayer)->GetQNetPlayer();
|
||||
}
|
||||
|
||||
if (qnetPlayer == nullptr)
|
||||
{
|
||||
qnetPlayer = &IQNet::m_player[smallId];
|
||||
extern void Win64_SetupRemoteQNetPlayer(IQNetPlayer * player, BYTE smallId, bool isHost, bool isLocal);
|
||||
Win64_SetupRemoteQNetPlayer(qnetPlayer, smallId, false, false);
|
||||
g_pPlatformNetworkManager->NotifyPlayerJoined(qnetPlayer);
|
||||
}
|
||||
|
||||
if (playerName != nullptr && !playerName->empty())
|
||||
{
|
||||
wcsncpy_s(qnetPlayer->m_gamertag, 32, playerName->c_str(), _TRUNCATE);
|
||||
}
|
||||
|
||||
if (xuid != INVALID_XUID)
|
||||
{
|
||||
qnetPlayer->m_resolvedXuid = xuid;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void ClientConnection::handleAddExperienceOrb(shared_ptr<AddExperienceOrbPacket> packet)
|
||||
{
|
||||
shared_ptr<Entity> e = std::make_shared<ExperienceOrb>(level, packet->x / 32.0, packet->y / 32.0, packet->z / 32.0, packet->value);
|
||||
|
|
@ -882,6 +935,15 @@ void ClientConnection::handleAddPlayer(shared_ptr<AddPlayerPacket> packet)
|
|||
|
||||
#ifdef _WINDOWS64
|
||||
{
|
||||
BYTE pendingSmallId = 0xFF;
|
||||
auto pendingInfo = m_pendingRemotePlayerSmallIds.find(packet->id);
|
||||
if (pendingInfo != m_pendingRemotePlayerSmallIds.end())
|
||||
{
|
||||
pendingSmallId = pendingInfo->second;
|
||||
ensureRemoteNetworkPlayer(pendingSmallId, &packet->name, player->getXuid());
|
||||
m_pendingRemotePlayerSmallIds.erase(pendingInfo);
|
||||
}
|
||||
|
||||
IQNetPlayer* matchedQNetPlayer = nullptr;
|
||||
PlayerUID pktXuid = player->getXuid();
|
||||
const PlayerUID WIN64_XUID_BASE = (PlayerUID)0xe000d45248242f2e;
|
||||
|
|
@ -897,6 +959,16 @@ void ClientConnection::handleAddPlayer(shared_ptr<AddPlayerPacket> packet)
|
|||
}
|
||||
}
|
||||
|
||||
if (matchedQNetPlayer == nullptr && pendingSmallId != 0xFF)
|
||||
{
|
||||
INetworkPlayer* np = g_NetworkManager.GetPlayerBySmallId(pendingSmallId);
|
||||
if (np != nullptr)
|
||||
{
|
||||
NetworkPlayerXbox* npx = (NetworkPlayerXbox*)np;
|
||||
matchedQNetPlayer = npx->GetQNetPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
// Current Win64 path: identify QNet player by name and attach packet XUID.
|
||||
if (matchedQNetPlayer == nullptr)
|
||||
{
|
||||
|
|
@ -1087,6 +1159,8 @@ void ClientConnection::handleRemoveEntity(shared_ptr<RemoveEntitiesPacket> packe
|
|||
{
|
||||
for (int i = 0; i < packet->ids.length; i++)
|
||||
{
|
||||
m_pendingRemotePlayerSmallIds.erase(packet->ids[i]);
|
||||
|
||||
shared_ptr<Entity> entity = getEntity(packet->ids[i]);
|
||||
if (entity != nullptr && entity->GetType() == eTYPE_PLAYER)
|
||||
{
|
||||
|
|
@ -2004,8 +2078,15 @@ void ClientConnection::handlePreLogin(shared_ptr<PreLoginPacket> packet)
|
|||
// 4J - Check that we can play with all the players already in the game who have Friends-Only UGC set
|
||||
BOOL canPlay = TRUE;
|
||||
BOOL canPlayLocal = TRUE;
|
||||
#ifdef _WINDOWS64
|
||||
// yes, this looks wrong, but on Win64 we don't have the API to check friends of friends, so just assume that there -
|
||||
// - is at least one friend and that we are friends with the host. fuck xbl. we can remedy this with discord in the future or some shit -- mattsumi
|
||||
BOOL isAtLeastOneFriend = TRUE;
|
||||
BOOL isFriendsWithHost = TRUE;
|
||||
#else
|
||||
BOOL isAtLeastOneFriend = g_NetworkManager.IsHost();
|
||||
BOOL isFriendsWithHost = TRUE;
|
||||
#endif
|
||||
BOOL cantPlayContentRestricted = FALSE;
|
||||
|
||||
if(!g_NetworkManager.IsHost())
|
||||
|
|
@ -2443,8 +2524,14 @@ void ClientConnection::handlePreLogin(shared_ptr<PreLoginPacket> packet)
|
|||
}
|
||||
BOOL allAllowed, friendsAllowed;
|
||||
ProfileManager.AllowedPlayerCreatedContent(m_userIndex,true,&allAllowed,&friendsAllowed);
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
// Save ugcPlayersVersion for the deferred LoginPacket sent after auth handshake
|
||||
m_preLoginUgcPlayersVersion = packet->m_ugcPlayersVersion;
|
||||
#else
|
||||
send(std::make_shared<LoginPacket>(minecraft->user->name, SharedConstants::NETWORK_PROTOCOL_VERSION, offlineXUID, onlineXUID, (allAllowed != TRUE && friendsAllowed == TRUE),
|
||||
packet->m_ugcPlayersVersion, app.GetPlayerSkinId(m_userIndex), app.GetPlayerCapeId(m_userIndex), ProfileManager.IsGuest(m_userIndex)));
|
||||
#endif
|
||||
|
||||
if(!g_NetworkManager.IsHost() )
|
||||
{
|
||||
|
|
@ -3556,9 +3643,31 @@ bool ClientConnection::isServerPacketListener()
|
|||
|
||||
void ClientConnection::handlePlayerInfo(shared_ptr<PlayerInfoPacket> packet)
|
||||
{
|
||||
unsigned int startingPrivileges = app.GetPlayerPrivileges(packet->m_networkSmallId);
|
||||
BYTE networkSmallId = static_cast<BYTE>(packet->m_networkSmallId);
|
||||
unsigned int startingPrivileges = app.GetPlayerPrivileges(networkSmallId);
|
||||
|
||||
INetworkPlayer *networkPlayer = g_NetworkManager.GetPlayerBySmallId(packet->m_networkSmallId);
|
||||
INetworkPlayer *networkPlayer = g_NetworkManager.GetPlayerBySmallId(networkSmallId);
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
if (networkPlayer == nullptr && packet->m_entityId >= 0)
|
||||
{
|
||||
shared_ptr<Entity> entity = getEntity(packet->m_entityId);
|
||||
if (entity != nullptr && entity->instanceof(eTYPE_PLAYER))
|
||||
{
|
||||
shared_ptr<Player> player = dynamic_pointer_cast<Player>(entity);
|
||||
if (player != nullptr)
|
||||
{
|
||||
std::wstring playerName = player->getName();
|
||||
ensureRemoteNetworkPlayer(networkSmallId, &playerName, player->getXuid());
|
||||
networkPlayer = g_NetworkManager.GetPlayerBySmallId(networkSmallId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pendingRemotePlayerSmallIds[packet->m_entityId] = networkSmallId;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if(networkPlayer != nullptr && networkPlayer->IsHost())
|
||||
{
|
||||
|
|
@ -3568,7 +3677,7 @@ void ClientConnection::handlePlayerInfo(shared_ptr<PlayerInfoPacket> packet)
|
|||
}
|
||||
|
||||
// 4J Stu - Repurposed this packet for player info that we want
|
||||
app.UpdatePlayerInfo(packet->m_networkSmallId, packet->m_playerColourIndex, packet->m_playerPrivileges);
|
||||
app.UpdatePlayerInfo(networkSmallId, packet->m_playerColourIndex, packet->m_playerPrivileges);
|
||||
|
||||
shared_ptr<Entity> entity = getEntity(packet->m_entityId);
|
||||
if(entity != nullptr && entity->instanceof(eTYPE_PLAYER))
|
||||
|
|
@ -4129,3 +4238,301 @@ ClientConnection::DeferredEntityLinkPacket::DeferredEntityLinkPacket(shared_ptr<
|
|||
m_recievedTick = GetTickCount();
|
||||
m_packet = packet;
|
||||
}
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
void ClientConnection::handleAuthScheme(shared_ptr<AuthSchemePacket> packet)
|
||||
{
|
||||
app.DebugPrintf("Auth: Received AuthSchemePacket with %d scheme(s)\n", (int)packet->schemes.size());
|
||||
|
||||
newauthManager& authMgr = newauthManager::Get();
|
||||
std::string serverId(packet->serverId.begin(), packet->serverId.end());
|
||||
wstring chosenScheme;
|
||||
bool isElyby = false;
|
||||
bool offlineAvailable = false;
|
||||
bool wantsOnlineAuth = false;
|
||||
for (const auto& scheme : packet->schemes)
|
||||
{
|
||||
if (scheme == L"offline")
|
||||
offlineAvailable = true;
|
||||
}
|
||||
|
||||
if (!g_NetworkManager.IsHost())
|
||||
{
|
||||
auto accounts = authMgr.GetJavaAccounts();
|
||||
int activeIdx = authMgr.GetActiveJavaAccountIndex();
|
||||
if (activeIdx >= 0 && activeIdx < (int)accounts.size())
|
||||
{
|
||||
bool acctIsOffline = accounts[activeIdx].isOffline || accounts[activeIdx].authProvider == "offline";
|
||||
bool acctIsElyby = (accounts[activeIdx].authProvider == "elyby");
|
||||
for (const auto& scheme : packet->schemes)
|
||||
{
|
||||
if (scheme == L"mojang" && !acctIsOffline && !acctIsElyby)
|
||||
{
|
||||
chosenScheme = L"mojang";
|
||||
wantsOnlineAuth = true;
|
||||
break;
|
||||
}
|
||||
else if (scheme == L"elyby" && !acctIsOffline && acctIsElyby)
|
||||
{
|
||||
chosenScheme = L"elyby";
|
||||
isElyby = true;
|
||||
wantsOnlineAuth = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (chosenScheme.empty() && offlineAvailable)
|
||||
{
|
||||
chosenScheme = L"offline";
|
||||
}
|
||||
|
||||
if (chosenScheme.empty())
|
||||
{
|
||||
app.DebugPrintf("Auth: No compatible scheme found, disconnecting\n");
|
||||
app.SetDisconnectReason(DisconnectPacket::eDisconnect_AuthFailed);
|
||||
app.SetAction(ProfileManager.GetPrimaryPad(), eAppAction_ExitWorld, (void*)TRUE);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string chosenStr(chosenScheme.begin(), chosenScheme.end());
|
||||
app.DebugPrintf("Auth: Chose scheme '%s'\n", chosenStr.c_str());
|
||||
|
||||
if (chosenScheme == L"offline")
|
||||
{
|
||||
newauth::JavaSession session = authMgr.GetSlotSession((int)m_userIndex);
|
||||
wstring uuid, username;
|
||||
if (!session.uuid.empty())
|
||||
{
|
||||
uuid = wstring(session.uuid.begin(), session.uuid.end());
|
||||
username = wstring(session.username.begin(), session.username.end());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto accounts = authMgr.GetJavaAccounts();
|
||||
int activeIdx = authMgr.GetActiveJavaAccountIndex();
|
||||
if (activeIdx >= 0 && activeIdx < (int)accounts.size() && !accounts[activeIdx].uuid.empty())
|
||||
{
|
||||
const auto& acct = accounts[activeIdx];
|
||||
uuid = wstring(acct.uuid.begin(), acct.uuid.end());
|
||||
username = wstring(acct.username.begin(), acct.username.end());
|
||||
}
|
||||
else
|
||||
{
|
||||
username = minecraft->user->name;
|
||||
std::string narrowName(username.begin(), username.end());
|
||||
std::string offUuid = GameUUID::generateOffline(narrowName).toDashed();
|
||||
uuid = wstring(offUuid.begin(), offUuid.end());
|
||||
}
|
||||
}
|
||||
send(std::make_shared<AuthResponsePacket>(L"offline", uuid, username));
|
||||
return;
|
||||
}
|
||||
newauth::JavaSession session = authMgr.GetSlotSession((int)m_userIndex);
|
||||
if (wantsOnlineAuth && (session.accessToken.empty() || session.uuid.empty() || session.username.empty()))
|
||||
{
|
||||
app.DebugPrintf("Auth: Waiting for slot %d online session to become ready\n", (int)m_userIndex);
|
||||
authMgr.WaitForSlotReady((int)m_userIndex, 5000);
|
||||
session = authMgr.GetSlotSession((int)m_userIndex);
|
||||
if ((session.accessToken.empty() || session.uuid.empty() || session.username.empty()) && m_userIndex != 0)
|
||||
{
|
||||
app.DebugPrintf("Auth: Slot %d session still empty, retrying with primary slot session\n", (int)m_userIndex);
|
||||
authMgr.WaitForSlotReady(0, 5000);
|
||||
session = authMgr.GetSlotSession(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (session.accessToken.empty() || session.uuid.empty() || session.username.empty())
|
||||
{
|
||||
app.DebugPrintf("Auth: Online session unavailable for chosen scheme '%s'\n", chosenStr.c_str());
|
||||
if (offlineAvailable)
|
||||
{
|
||||
app.DebugPrintf("Auth: Falling back to offline scheme because no online session is available\n");
|
||||
wstring fbUsername = minecraft->user->name;
|
||||
std::string narrowName(fbUsername.begin(), fbUsername.end());
|
||||
std::string offUuid = GameUUID::generateOffline(narrowName).toDashed();
|
||||
wstring fbUuid(offUuid.begin(), offUuid.end());
|
||||
send(std::make_shared<AuthResponsePacket>(L"offline", fbUuid, fbUsername));
|
||||
return;
|
||||
}
|
||||
app.SetDisconnectReason(DisconnectPacket::eDisconnect_AuthFailed);
|
||||
app.SetAction(ProfileManager.GetPrimaryPad(), eAppAction_ExitWorld, (void*)TRUE);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string token = session.accessToken;
|
||||
std::string uuid = session.uuid;
|
||||
std::string undashedUuid = newauth::UndashUuid(uuid);
|
||||
|
||||
std::string joinError;
|
||||
bool joinOk = false;
|
||||
if (isElyby)
|
||||
joinOk = newauth::ElybyJoinServer(token, undashedUuid, serverId, joinError);
|
||||
else
|
||||
joinOk = newauth::JoinServer(token, undashedUuid, serverId, joinError);
|
||||
|
||||
if (!joinOk)
|
||||
{
|
||||
app.DebugPrintf("Auth: JoinServer failed: %s\n", joinError.c_str());
|
||||
if (offlineAvailable)
|
||||
{
|
||||
app.DebugPrintf("Auth: Falling back to offline scheme\n");
|
||||
newauth::JavaSession fallbackSession = authMgr.GetSlotSession((int)m_userIndex);
|
||||
wstring fbUuid, fbUsername;
|
||||
if (!fallbackSession.uuid.empty())
|
||||
{
|
||||
fbUuid = wstring(fallbackSession.uuid.begin(), fallbackSession.uuid.end());
|
||||
fbUsername = wstring(fallbackSession.username.begin(), fallbackSession.username.end());
|
||||
}
|
||||
else
|
||||
{
|
||||
fbUsername = minecraft->user->name;
|
||||
std::string narrowName(fbUsername.begin(), fbUsername.end());
|
||||
std::string offUuid = GameUUID::generateOffline(narrowName).toDashed();
|
||||
fbUuid = wstring(offUuid.begin(), offUuid.end());
|
||||
}
|
||||
send(std::make_shared<AuthResponsePacket>(L"offline", fbUuid, fbUsername));
|
||||
return;
|
||||
}
|
||||
app.SetDisconnectReason(DisconnectPacket::eDisconnect_AuthFailed);
|
||||
app.SetAction(ProfileManager.GetPrimaryPad(), eAppAction_ExitWorld, (void*)TRUE);
|
||||
return;
|
||||
}
|
||||
std::string dashedUuid = newauth::DashUuid(uuid);
|
||||
wstring wUuid(dashedUuid.begin(), dashedUuid.end());
|
||||
wstring wUsername(session.username.begin(), session.username.end());
|
||||
|
||||
send(std::make_shared<AuthResponsePacket>(chosenScheme, wUuid, wUsername));
|
||||
app.DebugPrintf("Auth: Sent AuthResponsePacket\n");
|
||||
}
|
||||
|
||||
void ClientConnection::handleAuthResult(shared_ptr<AuthResultPacket> packet)
|
||||
{
|
||||
if (!packet->success)
|
||||
{
|
||||
std::wstring errMsg = packet->errorMessage;
|
||||
app.DebugPrintf("Auth: Server rejected authentication: %ls\n", errMsg.c_str());
|
||||
app.SetDisconnectReason(DisconnectPacket::eDisconnect_AuthFailed);
|
||||
app.SetAction(ProfileManager.GetPrimaryPad(), eAppAction_ExitWorld, (void*)TRUE);
|
||||
return;
|
||||
}
|
||||
|
||||
m_authAssignedUuid = packet->assignedUuid;
|
||||
m_authAssignedUsername = packet->assignedUsername;
|
||||
|
||||
if (!m_authAssignedUsername.empty())
|
||||
{
|
||||
minecraft->user->name = m_authAssignedUsername;
|
||||
extern char g_Win64Username[17];
|
||||
extern wchar_t g_Win64UsernameW[17];
|
||||
wcsncpy_s(g_Win64UsernameW, 17, m_authAssignedUsername.c_str(), _TRUNCATE);
|
||||
WideCharToMultiByte(CP_ACP, 0, g_Win64UsernameW, -1, g_Win64Username, 17, nullptr, nullptr);
|
||||
|
||||
IQNetPlayer* localQNetPlayer = nullptr;
|
||||
INetworkPlayer* localNetworkPlayer = g_NetworkManager.GetLocalPlayerByUserIndex(m_userIndex);
|
||||
if (localNetworkPlayer != nullptr)
|
||||
{
|
||||
localQNetPlayer = static_cast<NetworkPlayerXbox*>(localNetworkPlayer)->GetQNetPlayer();
|
||||
}
|
||||
if (localQNetPlayer == nullptr && m_userIndex >= 0 && m_userIndex < MINECRAFT_NET_MAX_PLAYERS)
|
||||
{
|
||||
localQNetPlayer = &IQNet::m_player[m_userIndex];
|
||||
}
|
||||
if (localQNetPlayer != nullptr)
|
||||
{
|
||||
wcsncpy_s(localQNetPlayer->m_gamertag, 32, m_authAssignedUsername.c_str(), _TRUNCATE);
|
||||
}
|
||||
}
|
||||
|
||||
app.DebugPrintf("Auth: Success! uuid=%ls username=%ls\n",
|
||||
m_authAssignedUuid.c_str(), m_authAssignedUsername.c_str());
|
||||
|
||||
if (!packet->skinKey.empty() && !packet->skinData.empty())
|
||||
{
|
||||
if (newauth::ValidateSkinPng(packet->skinData.data(), packet->skinData.size()))
|
||||
{
|
||||
DWORD skinBytes = (DWORD)packet->skinData.size();
|
||||
PBYTE skinCopy = new BYTE[skinBytes];
|
||||
memcpy(skinCopy, packet->skinData.data(), skinBytes);
|
||||
app.AddMemoryTextureFile(packet->skinKey,
|
||||
skinCopy,
|
||||
skinBytes);
|
||||
m_authSkinKey = packet->skinKey;
|
||||
app.DebugPrintf("Auth: Registered skin texture '%ls' (%d bytes)\n",
|
||||
packet->skinKey.c_str(), (int)packet->skinData.size());
|
||||
}
|
||||
}
|
||||
else if (!m_authAssignedUuid.empty())
|
||||
{
|
||||
std::string uuid(m_authAssignedUuid.begin(), m_authAssignedUuid.end());
|
||||
newauthManager& authMgr = newauthManager::Get();
|
||||
auto accounts = authMgr.GetJavaAccounts();
|
||||
int activeIdx = authMgr.GetActiveJavaAccountIndex();
|
||||
bool isElyby = (activeIdx >= 0 && activeIdx < (int)accounts.size()
|
||||
&& accounts[activeIdx].authProvider == "elyby");
|
||||
|
||||
std::string skinKey = isElyby
|
||||
? newauth::MakeElybySkinKey(uuid)
|
||||
: newauth::MakeSkinKey(uuid);
|
||||
std::string skinErr;
|
||||
std::string skinUrl = isElyby
|
||||
? newauth::ElybyFetchProfileSkinUrl(uuid, skinErr)
|
||||
// honbestlky, this makes me reconsider elyby considering it could just be used to generate alts but whatever
|
||||
: newauth::FetchProfileSkinUrl(uuid, skinErr);
|
||||
if (!skinUrl.empty())
|
||||
{
|
||||
std::string fetchErr;
|
||||
auto skinPng = newauth::FetchSkinPng(skinUrl, fetchErr);
|
||||
if (!skinPng.empty() && newauth::ValidateSkinPng(skinPng.data(), skinPng.size()))
|
||||
{
|
||||
DWORD skinBytes = (DWORD)skinPng.size();
|
||||
PBYTE skinCopy = new BYTE[skinBytes];
|
||||
memcpy(skinCopy, skinPng.data(), skinBytes);
|
||||
std::wstring wSkinKey(skinKey.begin(), skinKey.end());
|
||||
app.AddMemoryTextureFile(wSkinKey, skinCopy, skinBytes);
|
||||
m_authSkinKey = wSkinKey;
|
||||
app.DebugPrintf("Auth: Client-side skin fetch for uuid=%s (%d bytes)\n",
|
||||
uuid.c_str(), (int)skinPng.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendLoginPacketAfterAuth();
|
||||
}
|
||||
|
||||
void ClientConnection::sendLoginPacketAfterAuth()
|
||||
{
|
||||
PlayerUID offlineXUID = INVALID_XUID;
|
||||
PlayerUID onlineXUID = INVALID_XUID;
|
||||
|
||||
if (ProfileManager.IsSignedInLive(m_userIndex))
|
||||
{
|
||||
ProfileManager.GetXUID(m_userIndex, &onlineXUID, true);
|
||||
}
|
||||
if (!ProfileManager.IsGuest(m_userIndex))
|
||||
{
|
||||
ProfileManager.GetXUID(m_userIndex, &offlineXUID, false);
|
||||
}
|
||||
|
||||
BOOL allAllowed, friendsAllowed;
|
||||
ProfileManager.AllowedPlayerCreatedContent(m_userIndex, true, &allAllowed, &friendsAllowed);
|
||||
|
||||
send(std::make_shared<LoginPacket>(
|
||||
minecraft->user->name,
|
||||
SharedConstants::NETWORK_PROTOCOL_VERSION,
|
||||
offlineXUID, onlineXUID,
|
||||
(allAllowed != TRUE && friendsAllowed == TRUE),
|
||||
m_preLoginUgcPlayersVersion,
|
||||
app.GetPlayerSkinId(m_userIndex),
|
||||
app.GetPlayerCapeId(m_userIndex),
|
||||
ProfileManager.IsGuest(m_userIndex)));
|
||||
|
||||
if (!g_NetworkManager.IsHost())
|
||||
{
|
||||
Minecraft::GetInstance()->progressRenderer->progressStagePercentage((eCCLoginSent * 100) / (eCCConnected));
|
||||
}
|
||||
|
||||
app.DebugPrintf("Auth: Sent LoginPacket after auth\n");
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#pragma once
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include "..\Minecraft.World\net.minecraft.network.h"
|
||||
class Minecraft;
|
||||
|
|
@ -6,6 +7,8 @@ class MultiPlayerLevel;
|
|||
class SavedDataStorage;
|
||||
class Socket;
|
||||
class MultiplayerLocalPlayer;
|
||||
class AuthSchemePacket;
|
||||
class AuthResultPacket;
|
||||
|
||||
class ClientConnection : public PacketListener
|
||||
{
|
||||
|
|
@ -14,6 +17,7 @@ private:
|
|||
{
|
||||
eCCPreLoginSent = 0,
|
||||
eCCPreLoginReceived,
|
||||
eCCAuthWaiting,
|
||||
eCCLoginSent,
|
||||
eCCLoginReceived,
|
||||
eCCConnected
|
||||
|
|
@ -48,6 +52,7 @@ private:
|
|||
|
||||
std::unordered_set<int> m_trackedEntityIds;
|
||||
std::unordered_set<int64_t> m_visibleChunks;
|
||||
std::unordered_map<int, BYTE> m_pendingRemotePlayerSmallIds;
|
||||
|
||||
static int64_t chunkKey(int x, int z) { return ((int64_t)x << 32) | ((int64_t)z & 0xFFFFFFFF); }
|
||||
|
||||
|
|
@ -55,6 +60,9 @@ private:
|
|||
bool shouldProcessForEntity(int entityId) const;
|
||||
bool shouldProcessForPosition(int blockX, int blockZ) const;
|
||||
bool anyOtherConnectionHasChunk(int x, int z) const;
|
||||
#ifdef _WINDOWS64
|
||||
bool ensureRemoteNetworkPlayer(BYTE smallId, const std::wstring *playerName, PlayerUID xuid);
|
||||
#endif
|
||||
|
||||
public:
|
||||
bool isTrackingEntity(int entityId) const { return m_trackedEntityIds.count(entityId) > 0; }
|
||||
|
|
@ -164,6 +172,16 @@ public:
|
|||
virtual void handleParticleEvent(shared_ptr<LevelParticlesPacket> packet);
|
||||
virtual void handleUpdateAttributes(shared_ptr<UpdateAttributesPacket> packet);
|
||||
|
||||
// fuck ass shit to make the skins work waaah waaah waah
|
||||
virtual void handleAuthScheme(shared_ptr<AuthSchemePacket> packet);
|
||||
virtual void handleAuthResult(shared_ptr<AuthResultPacket> packet);
|
||||
private:
|
||||
void sendLoginPacketAfterAuth();
|
||||
std::wstring m_authAssignedUuid;
|
||||
std::wstring m_authAssignedUsername;
|
||||
std::wstring m_authSkinKey;
|
||||
DWORD m_preLoginUgcPlayersVersion = 0;
|
||||
|
||||
private:
|
||||
// 4J: Entity link packet deferred
|
||||
class DeferredEntityLinkPacket
|
||||
|
|
|
|||
BIN
Minecraft.Client/Common/Media/platformskin.swf
Normal file
BIN
Minecraft.Client/Common/Media/platformskin.swf
Normal file
Binary file not shown.
BIN
Minecraft.Client/Common/Media/platformskinHD.swf
Normal file
BIN
Minecraft.Client/Common/Media/platformskinHD.swf
Normal file
Binary file not shown.
|
|
@ -19,6 +19,7 @@ CPlatformNetworkManagerStub *g_pPlatformNetworkManager;
|
|||
void CPlatformNetworkManagerStub::NotifyPlayerJoined(IQNetPlayer *pQNetPlayer )
|
||||
{
|
||||
const char * pszDescription;
|
||||
bool exposePlayer = Win64_ShouldExposeSessionPlayer(pQNetPlayer);
|
||||
|
||||
// 4J Stu - We create a fake socket for every where that we need an INBOUND queue of game data. Outbound
|
||||
// is all handled by QNet so we don't need that. Therefore each client player has one, and the host has one
|
||||
|
|
@ -79,7 +80,10 @@ void CPlatformNetworkManagerStub::NotifyPlayerJoined(IQNetPlayer *pQNetPlayer )
|
|||
m_machineQNetPrimaryPlayers.push_back( pQNetPlayer );
|
||||
}
|
||||
}
|
||||
g_NetworkManager.PlayerJoining( networkPlayer );
|
||||
if (exposePlayer)
|
||||
{
|
||||
g_NetworkManager.PlayerJoining( networkPlayer );
|
||||
}
|
||||
|
||||
if( createFakeSocket == true && !m_bHostChanged )
|
||||
{
|
||||
|
|
@ -101,10 +105,13 @@ void CPlatformNetworkManagerStub::NotifyPlayerJoined(IQNetPlayer *pQNetPlayer )
|
|||
SystemFlagAddPlayer( networkPlayer );
|
||||
}
|
||||
|
||||
for( int idx = 0; idx < XUSER_MAX_COUNT; ++idx)
|
||||
if (exposePlayer)
|
||||
{
|
||||
if(playerChangedCallback[idx] != nullptr)
|
||||
playerChangedCallback[idx]( playerChangedCallbackParam[idx], networkPlayer, false );
|
||||
for( int idx = 0; idx < XUSER_MAX_COUNT; ++idx)
|
||||
{
|
||||
if(playerChangedCallback[idx] != nullptr)
|
||||
playerChangedCallback[idx]( playerChangedCallbackParam[idx], networkPlayer, false );
|
||||
}
|
||||
}
|
||||
|
||||
if(m_pIQNet->GetState() == QNET_STATE_GAME_PLAY)
|
||||
|
|
@ -142,12 +149,18 @@ void CPlatformNetworkManagerStub::NotifyPlayerLeaving(IQNetPlayer* pQNetPlayer)
|
|||
SystemFlagRemovePlayer(networkPlayer);
|
||||
}
|
||||
|
||||
g_NetworkManager.PlayerLeaving(networkPlayer);
|
||||
|
||||
for (int idx = 0; idx < XUSER_MAX_COUNT; ++idx)
|
||||
if (Win64_ShouldExposeSessionPlayer(pQNetPlayer))
|
||||
{
|
||||
if (playerChangedCallback[idx] != nullptr)
|
||||
playerChangedCallback[idx](playerChangedCallbackParam[idx], networkPlayer, true);
|
||||
g_NetworkManager.PlayerLeaving(networkPlayer);
|
||||
}
|
||||
|
||||
if (Win64_ShouldExposeSessionPlayer(pQNetPlayer))
|
||||
{
|
||||
for (int idx = 0; idx < XUSER_MAX_COUNT; ++idx)
|
||||
{
|
||||
if (playerChangedCallback[idx] != nullptr)
|
||||
playerChangedCallback[idx](playerChangedCallbackParam[idx], networkPlayer, true);
|
||||
}
|
||||
}
|
||||
|
||||
removeNetworkPlayer(pQNetPlayer);
|
||||
|
|
@ -544,6 +557,7 @@ int CPlatformNetworkManagerStub::JoinGame(FriendSessionInfo* searchResult, int l
|
|||
m_bLeaveGameOnTick = false;
|
||||
IQNet::s_isHosting = false;
|
||||
m_pIQNet->ClientJoinGame();
|
||||
Win64_SetJoinedDedicatedServer(searchResult->data.isDedicatedServer);
|
||||
|
||||
IQNet::m_player[0].m_smallId = 0;
|
||||
IQNet::m_player[0].m_isRemote = true;
|
||||
|
|
@ -865,6 +879,7 @@ void CPlatformNetworkManagerStub::SearchForGames()
|
|||
info->data.subTexturePackId = lanSessions[i].subTexturePackId;
|
||||
info->data.isReadyToJoin = lanSessions[i].isJoinable;
|
||||
info->data.isJoinable = lanSessions[i].isJoinable;
|
||||
info->data.isDedicatedServer = lanSessions[i].isDedicatedServer;
|
||||
strncpy_s(info->data.hostIP, sizeof(info->data.hostIP), lanSessions[i].hostIP, _TRUNCATE);
|
||||
info->data.hostPort = lanSessions[i].hostPort;
|
||||
wcsncpy_s(info->data.hostName, XUSER_NAME_SIZE, lanSessions[i].hostName, _TRUNCATE);
|
||||
|
|
@ -916,6 +931,7 @@ void CPlatformNetworkManagerStub::SearchForGames()
|
|||
info->displayLabelViewableStartIndex = 0;
|
||||
info->data.isReadyToJoin = true;
|
||||
info->data.isJoinable = true;
|
||||
info->data.isDedicatedServer = true;
|
||||
strncpy_s(info->data.hostIP, sizeof(info->data.hostIP), ipBuf, _TRUNCATE);
|
||||
info->data.hostPort = port;
|
||||
info->sessionId = static_cast<uint64_t>(inet_addr(ipBuf)) | static_cast<uint64_t>(port) << 32;
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ typedef struct _GameSessionData
|
|||
|
||||
bool isReadyToJoin;
|
||||
bool isJoinable;
|
||||
bool isDedicatedServer;
|
||||
|
||||
char hostIP[64];
|
||||
int hostPort;
|
||||
|
|
@ -85,6 +86,7 @@ typedef struct _GameSessionData
|
|||
subTexturePackId = 0;
|
||||
isReadyToJoin = false;
|
||||
isJoinable = true;
|
||||
isDedicatedServer = false;
|
||||
memset(hostIP, 0, sizeof(hostIP));
|
||||
hostPort = 0;
|
||||
memset(hostName, 0, sizeof(hostName));
|
||||
|
|
|
|||
1868
Minecraft.Client/Common/UI/NativeUIRenderer.cpp
Normal file
1868
Minecraft.Client/Common/UI/NativeUIRenderer.cpp
Normal file
File diff suppressed because it is too large
Load diff
219
Minecraft.Client/Common/UI/NativeUIRenderer.h
Normal file
219
Minecraft.Client/Common/UI/NativeUIRenderer.h
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace NativeUI
|
||||
{
|
||||
enum : uint32_t
|
||||
{
|
||||
ALIGN_LEFT = 0x00u,
|
||||
ALIGN_RIGHT = 0x01u,
|
||||
ALIGN_CENTER_X = 0x02u,
|
||||
ALIGN_CENTER_Y = 0x04u,
|
||||
ALIGN_BOTTOM = 0x08u,
|
||||
};
|
||||
|
||||
void BeginFrame();
|
||||
void EndFrame();
|
||||
void Shutdown();
|
||||
bool GetMouseVirtual(float& outX, float& outY);
|
||||
void DrawRect(float x, float y, float w, float h, uint32_t color);
|
||||
void DrawRectFullscreen(uint32_t color);
|
||||
void DrawRoundedRect(float x, float y, float w, float h,
|
||||
float radius, uint32_t color);
|
||||
void DrawRoundedBorder(float x, float y, float w, float h,
|
||||
float radius, float thickness, uint32_t color);
|
||||
|
||||
void DrawBorder(float x, float y, float w, float h,
|
||||
float thickness, uint32_t color);
|
||||
|
||||
void DrawLine(float x, float y, float length, float thickness,
|
||||
uint32_t color);
|
||||
|
||||
void DrawLineV(float x, float y, float length, float thickness,
|
||||
uint32_t color);
|
||||
|
||||
void DrawGradientRect(float x, float y, float w, float h,
|
||||
uint32_t topColor, uint32_t bottomColor);
|
||||
|
||||
void DrawGradientRoundedRect(float x, float y, float w, float h,
|
||||
float radius,
|
||||
uint32_t topColor, uint32_t bottomColor);
|
||||
|
||||
void DrawDropShadow(float x, float y, float w, float h,
|
||||
float offset = 4.0f, float spread = 6.0f,
|
||||
uint32_t color = 0x60000000u);
|
||||
|
||||
void DrawPanel(float x, float y, float w, float h,
|
||||
float radius = 8.0f,
|
||||
uint32_t bgColor = 0xF0181818u,
|
||||
uint32_t borderColor = 0xFF333333u,
|
||||
float borderThick = 1.0f,
|
||||
bool shadow = true);
|
||||
|
||||
void DrawDivider(float x, float y, float w,
|
||||
uint32_t color = 0xFF333333u,
|
||||
float thickness = 1.0f);
|
||||
|
||||
void DrawText(float x, float y, const wchar_t* text,
|
||||
uint32_t color, float size = 14.0f,
|
||||
uint32_t align = ALIGN_LEFT);
|
||||
|
||||
void DrawShadowText(float x, float y, const wchar_t* text,
|
||||
uint32_t color, float size = 14.0f,
|
||||
uint32_t align = ALIGN_LEFT);
|
||||
|
||||
float DrawTextWrapped(float x, float y, const wchar_t* text,
|
||||
float maxWidth, uint32_t color,
|
||||
float size = 14.0f, uint32_t align = ALIGN_LEFT);
|
||||
|
||||
void MeasureText(const wchar_t* text, float size,
|
||||
float* outWidth, float* outHeight);
|
||||
|
||||
float LineHeight(float size);
|
||||
|
||||
void PushClipRect(float x, float y, float w, float h);
|
||||
void PopClipRect();
|
||||
|
||||
void DrawButton(float x, float y, float w, float h,
|
||||
const wchar_t* label, bool focused,
|
||||
bool hovered = false, float labelSize = 16.0f);
|
||||
|
||||
void DrawTextBox(float x, float y, float w, float h,
|
||||
uint32_t tint = 0xFFFFFFFFu);
|
||||
|
||||
void DrawLink(float x, float y, const wchar_t* text,
|
||||
const char* url, bool focused, bool hovered = false,
|
||||
float size = 14.0f, uint32_t align = ALIGN_LEFT,
|
||||
float* outX = nullptr, float* outY = nullptr,
|
||||
float* outW = nullptr, float* outH = nullptr);
|
||||
|
||||
void DrawProgressBar(float x, float y, float w, float h,
|
||||
float progress,
|
||||
uint32_t fillColor = 0xFF1A71D1u,
|
||||
uint32_t trackColor = 0xFF333333u);
|
||||
|
||||
void DrawSpinner(float cx, float cy, float radius, int tick,
|
||||
uint32_t color = 0xFFFFFFFFu);
|
||||
|
||||
void DrawCheckbox(float x, float y, float size,
|
||||
bool checked, bool focused,
|
||||
bool hovered = false);
|
||||
|
||||
void DrawSlider(float x, float y, float w, float h,
|
||||
float value, bool focused,
|
||||
bool hovered = false,
|
||||
uint32_t fillColor = 0xFF1A71D1u,
|
||||
uint32_t trackColor = 0xFF333333u);
|
||||
|
||||
void DrawTooltip(float x, float y, const wchar_t* text,
|
||||
float size = 12.0f);
|
||||
|
||||
void OpenURL(const char* url);
|
||||
|
||||
int LoadTexture(const wchar_t* path);
|
||||
|
||||
int LoadTextureByName(int textureName);
|
||||
|
||||
int LoadTextureFromFileDirect(const char* filePath,
|
||||
int* outWidth = nullptr, int* outHeight = nullptr);
|
||||
|
||||
int LoadTextureFromFile(const char* filePath,
|
||||
int* outWidth = nullptr, int* outHeight = nullptr);
|
||||
|
||||
struct NineSlice
|
||||
{
|
||||
int tl, tm, tr;
|
||||
int ml, mm, mr;
|
||||
int bl, bm, br;
|
||||
int cornerW, cornerH;
|
||||
bool valid;
|
||||
};
|
||||
|
||||
NineSlice LoadNineSlice(const char* basePath);
|
||||
|
||||
void DrawNineSlice(float x, float y, float w, float h,
|
||||
const NineSlice& ns, uint32_t tint = 0xFFFFFFFFu);
|
||||
|
||||
struct ThreeSlice
|
||||
{
|
||||
int left, mid, right;
|
||||
int capW, capH;
|
||||
bool valid;
|
||||
};
|
||||
|
||||
ThreeSlice LoadThreeSlice(const char* basePath);
|
||||
|
||||
void DrawThreeSlice(float x, float y, float w, float h,
|
||||
const ThreeSlice& ts, uint32_t tint = 0xFFFFFFFFu);
|
||||
|
||||
void DrawTexture(float x, float y, float w, float h,
|
||||
int textureId, uint32_t tint = 0xFFFFFFFFu);
|
||||
|
||||
void DrawTextureUV(float x, float y, float w, float h,
|
||||
int textureId,
|
||||
float u0, float v0, float u1, float v1,
|
||||
uint32_t tint = 0xFFFFFFFFu);
|
||||
|
||||
void DrawTextureRounded(float x, float y, float w, float h,
|
||||
int textureId, float radius,
|
||||
uint32_t tint = 0xFFFFFFFFu);
|
||||
|
||||
void DrawTextureFit(float x, float y, float w, float h,
|
||||
int textureId, int texW, int texH,
|
||||
uint32_t tint = 0xFFFFFFFFu);
|
||||
|
||||
class FocusList
|
||||
{
|
||||
public:
|
||||
struct Entry { int id; float x, y, w, h; };
|
||||
|
||||
void Clear() { m_entries.clear(); m_hoveredId = -1; }
|
||||
void Add(int id, float x, float y, float w, float h);
|
||||
int GetFocused() const;
|
||||
int GetHovered() const { return m_hoveredId; }
|
||||
bool IsFocused(int id) const { return GetFocused() == id; }
|
||||
bool IsHovered(int id) const { return m_lastDevice == eDevice_Mouse && m_hoveredId == id; }
|
||||
bool IsActive(int id) const { return IsFocused(id) || IsHovered(id); }
|
||||
|
||||
bool ShowFocus(int id) const
|
||||
{
|
||||
if (m_lastDevice == eDevice_Mouse)
|
||||
return m_hoveredId >= 0 && m_hoveredId == id;
|
||||
return IsFocused(id);
|
||||
}
|
||||
|
||||
void MoveNext();
|
||||
void MovePrev();
|
||||
void SetFocus(int id);
|
||||
bool HitTest(float mx, float my, int& outId) const;
|
||||
bool UpdateHover(float mx, float my);
|
||||
void ClearHover() { m_hoveredId = -1; }
|
||||
int Count() const { return (int)m_entries.size(); }
|
||||
|
||||
void TickMouse();
|
||||
|
||||
bool IsMouseConsumed() const { return m_mouseConsumed; }
|
||||
|
||||
static constexpr int RESULT_NAVIGATED = -1;
|
||||
static constexpr int RESULT_UNHANDLED = -2;
|
||||
|
||||
int HandleMenuKey(int key, int backId = 0,
|
||||
float panelX = 0, float panelY = 0,
|
||||
float panelW = 0, float panelH = 0);
|
||||
|
||||
enum ELastDevice { eDevice_None, eDevice_Gamepad, eDevice_Mouse };
|
||||
ELastDevice GetLastDevice() const { return m_lastDevice; }
|
||||
|
||||
private:
|
||||
std::vector<Entry> m_entries;
|
||||
int m_focusIdx = 0;
|
||||
int m_hoveredId = -1;
|
||||
int m_lastHoveredId = -1;
|
||||
bool m_mouseConsumed = false;
|
||||
ELastDevice m_lastDevice = eDevice_None;
|
||||
float m_lastMouseX = -1.0f;
|
||||
float m_lastMouseY = -1.0f;
|
||||
};
|
||||
}
|
||||
|
|
@ -67,6 +67,9 @@
|
|||
#include "UIScene_Intro.h"
|
||||
#include "UIScene_SaveMessage.h"
|
||||
#include "UIScene_MainMenu.h"
|
||||
#ifndef MINECRAFT_SERVER_BUILD
|
||||
#include "UIScene_MSAuth.h"
|
||||
#endif
|
||||
#include "UIScene_LoadMenu.h"
|
||||
#include "UIScene_JoinMenu.h"
|
||||
#include "UIScene_LoadOrJoinMenu.h"
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ enum EUIScene
|
|||
|
||||
#ifndef _XBOX
|
||||
// Anything non-xbox should be added here. The ordering of scenes above is required for sentient reporting on xbox 360 to continue to be accurate
|
||||
eUIScene_MSAuth,
|
||||
eUIComponent_Panorama,
|
||||
eUIComponent_Logo,
|
||||
eUIComponent_DebugUIConsole,
|
||||
|
|
|
|||
|
|
@ -367,6 +367,11 @@ bool UILayer::NavigateToScene(int iPad, EUIScene scene, void *initData)
|
|||
case eUIScene_MainMenu:
|
||||
newScene = new UIScene_MainMenu(iPad, initData, this);
|
||||
break;
|
||||
case eUIScene_MSAuth:
|
||||
#ifndef MINECRAFT_SERVER_BUILD
|
||||
newScene = new UIScene_MSAuth(iPad, initData, this);
|
||||
#endif
|
||||
break;
|
||||
case eUIScene_LoadOrJoinMenu:
|
||||
newScene = new UIScene_LoadOrJoinMenu(iPad, initData, this);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -1000,7 +1000,7 @@ void UIScene::gainFocus()
|
|||
updateTooltips();
|
||||
updateComponents();
|
||||
|
||||
if(!m_bFocussedOnce)
|
||||
if(swf && !m_bFocussedOnce)
|
||||
{
|
||||
IggyDataValue result;
|
||||
IggyDataValue value[1];
|
||||
|
|
|
|||
1719
Minecraft.Client/Common/UI/UIScene_MSAuth.cpp
Normal file
1719
Minecraft.Client/Common/UI/UIScene_MSAuth.cpp
Normal file
File diff suppressed because it is too large
Load diff
154
Minecraft.Client/Common/UI/UIScene_MSAuth.h
Normal file
154
Minecraft.Client/Common/UI/UIScene_MSAuth.h
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
#pragma once
|
||||
/*
|
||||
* account manager ui
|
||||
* two views: account list and device code flow
|
||||
* no swf, pure native overlay
|
||||
*/
|
||||
|
||||
#include "UIScene.h"
|
||||
#include "NativeUIRenderer.h"
|
||||
#include "../../../newauth/include/newauthManager.h"
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
// control ids for the focus list
|
||||
// file scope so the static render helpers can use them
|
||||
namespace MSAuthUI
|
||||
{
|
||||
enum EControls {
|
||||
eBtn_Back = 0,
|
||||
eBtn_AddAccount = 1,
|
||||
eBtn_AddOffline = 2,
|
||||
eBtn_OfflineConfirm = 3,
|
||||
eBtn_ConfirmYes = 4,
|
||||
eBtn_ConfirmNo = 5,
|
||||
eBtn_OfflineTextBox = 6,
|
||||
eBtn_AddElyby = 7,
|
||||
eBtn_ElybyUsername = 8,
|
||||
eBtn_ElybyPassword = 9,
|
||||
eBtn_ElybySignIn = 10,
|
||||
eBtn_ElybyCancel = 11,
|
||||
eBtn_Elyby2FACode = 12,
|
||||
eBtn_Elyby2FASubmit = 13,
|
||||
eBtn_Elyby2FACancel = 14,
|
||||
eBtn_RemoveBase = 50, // 50 + idx = remove btn
|
||||
eAccountBase = 100, // 100 + idx = account row
|
||||
eLink_URL = 200,
|
||||
};
|
||||
}
|
||||
|
||||
class UIScene_MSAuth : public UIScene
|
||||
{
|
||||
private:
|
||||
// what were looking at
|
||||
enum EView { eView_AccountList, eView_DeviceCode, eView_OfflineInput, eView_ElybyInput, eView_Elyby2FA };
|
||||
EView m_view = eView_AccountList;
|
||||
|
||||
// shared flags that survive scene destruction (async callback safety)
|
||||
struct AuthFlags {
|
||||
std::atomic<bool> done { false };
|
||||
std::atomic<bool> success{ false };
|
||||
std::atomic<bool> need2FA{ false };
|
||||
};
|
||||
std::shared_ptr<AuthFlags> m_authFlags = std::make_shared<AuthFlags>();
|
||||
|
||||
int m_closeCountdown = 0;
|
||||
int m_spinnerTick = 0;
|
||||
ULONGLONG m_lastAnimTime = 0; // throttle animations to 30 FPS
|
||||
NativeUI::NineSlice m_panel;
|
||||
NativeUI::NineSlice m_recessPanel;
|
||||
bool m_panelLoaded = false;
|
||||
NativeUI::FocusList m_focus;
|
||||
std::string m_cachedUri;
|
||||
|
||||
// accounts snapshot, refreshed every tick
|
||||
std::vector<newauthManager::JavaAccountInfo> m_accounts;
|
||||
int m_activeIdx = -1;
|
||||
|
||||
// scroll pos for the list
|
||||
int m_scrollOffset = 0;
|
||||
|
||||
void StartAddAccount();
|
||||
void SwitchToAccountList();
|
||||
void SwitchToDeviceCode();
|
||||
void SwitchToOfflineInput();
|
||||
void SwitchToElybyInput();
|
||||
void SwitchToElyby2FA();
|
||||
void ConfirmOfflineAccount();
|
||||
void SubmitElybyLogin();
|
||||
void SubmitElyby2FA();
|
||||
|
||||
// offline name entry
|
||||
std::string m_offlineUsername;
|
||||
int m_offlineCursorBlink = 0;
|
||||
bool m_textInputActive = false; // typing mode on/off
|
||||
|
||||
// ely.by creds
|
||||
std::string m_elybyUsername;
|
||||
std::string m_elybyPassword;
|
||||
std::string m_elyby2FACode;
|
||||
// (2fa flag in m_authFlags->need2FA cuz async)
|
||||
int m_elybyActiveField = 0; // 0=user, 1=pass, 2=2fa
|
||||
|
||||
// which splitscreen slot this is for (0 = main, 1-3 = controllers)
|
||||
int m_targetSlot = 0;
|
||||
|
||||
// eat input for a few ticks when opening so we dont accidentally
|
||||
// process the button press that opened this damn scene
|
||||
int m_inputGuardTicks = 6;
|
||||
|
||||
// remove confirm dialog (-1 = hidden, >= 0 = which account to nuke)
|
||||
int m_pendingRemoveIdx = -1;
|
||||
std::string m_pendingRemoveUuid; // saved uuid in case the index changes under us
|
||||
|
||||
public:
|
||||
// shared buffer for virtual keyboard (public because the static
|
||||
// callback func needs it, prevents use-after-free if scene dies
|
||||
// while keyboard is up on another ui group)
|
||||
struct PendingKeyboardResult {
|
||||
std::string value;
|
||||
std::atomic<bool> ready{false};
|
||||
std::atomic<bool> valid{true};
|
||||
};
|
||||
|
||||
// skin cache entry (public for static render helpers)
|
||||
struct SkinEntry {
|
||||
std::atomic<int> textureId{-2}; // -2=nope, -1=loading/failed, -3=file ready, >=0=loaded
|
||||
std::string filePath;
|
||||
};
|
||||
|
||||
private:
|
||||
std::shared_ptr<PendingKeyboardResult> m_pendingKBResult;
|
||||
|
||||
// skin texture cache keyed by uuid
|
||||
std::unordered_map<std::string, std::shared_ptr<SkinEntry>> m_skinCache;
|
||||
void EnsureSkinLoaded(const std::string& uuid);
|
||||
|
||||
public:
|
||||
UIScene_MSAuth(int iPad, void* initData, UILayer* parentLayer);
|
||||
~UIScene_MSAuth();
|
||||
|
||||
virtual EUIScene getSceneType() override { return eUIScene_MSAuth; }
|
||||
virtual wstring getMoviePath() override { return L""; }
|
||||
virtual bool hidesLowerScenes() override { return true; }
|
||||
virtual bool blocksInput() override { return true; }
|
||||
virtual bool hasFocus(int iPad) override { return bHasFocus; }
|
||||
virtual bool needsReloaded() override { return false; }
|
||||
|
||||
virtual void updateTooltips() override;
|
||||
virtual void tick() override;
|
||||
virtual void render(S32 width, S32 height,
|
||||
C4JRender::eViewportType viewport) override;
|
||||
|
||||
virtual void handleInput(int iPad, int key, bool repeat,
|
||||
bool pressed, bool released,
|
||||
bool& handled) override;
|
||||
|
||||
virtual void handlePress(F64 controlId, F64 childId) override;
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
virtual bool handleMouseClick(F32 x, F32 y) override;
|
||||
#endif
|
||||
};
|
||||
|
|
@ -33,6 +33,9 @@ UIScene_MainMenu::UIScene_MainMenu(int iPad, void *initData, UILayer *parentLaye
|
|||
m_bIgnorePress=false;
|
||||
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
m_bPendingPlayAfterAuth = false;
|
||||
#endif
|
||||
m_buttons[static_cast<int>(eControl_PlayGame)].init(IDS_PLAY_GAME,eControl_PlayGame);
|
||||
|
||||
#ifdef _XBOX_ONE
|
||||
|
|
@ -43,6 +46,10 @@ UIScene_MainMenu::UIScene_MainMenu(int iPad, void *initData, UILayer *parentLaye
|
|||
m_buttons[static_cast<int>(eControl_Leaderboards)].init(IDS_LEADERBOARDS,eControl_Leaderboards);
|
||||
m_buttons[static_cast<int>(eControl_Achievements)].init( (UIString)IDS_ACHIEVEMENTS,eControl_Achievements);
|
||||
m_buttons[static_cast<int>(eControl_HelpAndOptions)].init(IDS_HELP_AND_OPTIONS,eControl_HelpAndOptions);
|
||||
#ifdef _WINDOWS64
|
||||
m_bTrialVersion=false;
|
||||
m_buttons[static_cast<int>(eControl_UnlockOrDLC)].init(L"Auth / Accounts",eControl_UnlockOrDLC);
|
||||
#else
|
||||
if(ProfileManager.IsFullVersion())
|
||||
{
|
||||
m_bTrialVersion=false;
|
||||
|
|
@ -53,6 +60,7 @@ UIScene_MainMenu::UIScene_MainMenu(int iPad, void *initData, UILayer *parentLaye
|
|||
m_bTrialVersion=true;
|
||||
m_buttons[static_cast<int>(eControl_UnlockOrDLC)].init(IDS_UNLOCK_FULL_GAME,eControl_UnlockOrDLC);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef _DURANGO
|
||||
m_buttons[static_cast<int>(eControl_Exit)].init(app.GetString(IDS_EXIT_GAME),eControl_Exit);
|
||||
|
|
@ -153,6 +161,15 @@ void UIScene_MainMenu::handleGainFocus(bool navBack)
|
|||
ui.ShowPlayerDisplayname(false);
|
||||
m_bIgnorePress=false;
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
if (navBack && m_bPendingPlayAfterAuth)
|
||||
{
|
||||
m_bPendingPlayAfterAuth = false;
|
||||
proceedToScene(ProfileManager.GetPrimaryPad(), eUIScene_LoadOrJoinMenu);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (eNavigateWhenReady >= 0)
|
||||
{
|
||||
return;
|
||||
|
|
@ -181,8 +198,10 @@ void UIScene_MainMenu::handleGainFocus(bool navBack)
|
|||
|
||||
if(navBack && ProfileManager.IsFullVersion())
|
||||
{
|
||||
#ifndef _WINDOWS64
|
||||
// Replace the Unlock Full Game with Downloadable Content
|
||||
m_buttons[static_cast<int>(eControl_UnlockOrDLC)].setLabel(IDS_DOWNLOADABLECONTENT);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if TO_BE_IMPLEMENTED
|
||||
|
|
@ -356,8 +375,12 @@ void UIScene_MainMenu::handlePress(F64 controlId, F64 childId)
|
|||
//CD - Added for audio
|
||||
ui.PlayUISFX(eSFX_Press);
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
ui.NavigateToScene(primaryPad, eUIScene_MSAuth);
|
||||
#else
|
||||
m_eAction=eAction_RunUnlockOrDLC;
|
||||
signInReturnedFunc = &UIScene_MainMenu::UnlockFullGame_SignInReturned;
|
||||
#endif
|
||||
break;
|
||||
case eControl_Exit:
|
||||
//CD - Added for audio
|
||||
|
|
@ -1419,7 +1442,22 @@ void UIScene_MainMenu::RunPlayGame(int iPad)
|
|||
#ifdef _XBOX_ONE
|
||||
ui.ShowPlayerDisplayname(true);
|
||||
#endif
|
||||
#ifdef _WINDOWS64
|
||||
{
|
||||
const auto& slot = newauthManager::Get().GetSlot(0);
|
||||
if (slot.accountIndex < 0 && newauthManager::Get().GetJavaAccounts().empty())
|
||||
{
|
||||
m_bPendingPlayAfterAuth = true;
|
||||
ui.NavigateToScene(ProfileManager.GetPrimaryPad(), eUIScene_MSAuth);
|
||||
}
|
||||
else
|
||||
{
|
||||
proceedToScene(ProfileManager.GetPrimaryPad(), eUIScene_LoadOrJoinMenu);
|
||||
}
|
||||
}
|
||||
#else
|
||||
proceedToScene(ProfileManager.GetPrimaryPad(), eUIScene_LoadOrJoinMenu);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
else
|
||||
|
|
@ -2129,7 +2167,9 @@ void UIScene_MainMenu::LoadTrial(void)
|
|||
|
||||
void UIScene_MainMenu::handleUnlockFullVersion()
|
||||
{
|
||||
#ifndef _WINDOWS64
|
||||
m_buttons[static_cast<int>(eControl_UnlockOrDLC)].setLabel(IDS_DOWNLOADABLECONTENT,true);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include "UIScene.h"
|
||||
#ifdef _WINDOWS64
|
||||
#include "../../../newauth/include/newauthManager.h"
|
||||
#endif
|
||||
|
||||
class UIScene_MainMenu : public UIScene
|
||||
{
|
||||
|
|
@ -56,6 +59,9 @@ private:
|
|||
#ifdef _XBOX_ONE
|
||||
bool m_bWaitingForDLCInfo;
|
||||
#endif
|
||||
#ifdef _WINDOWS64
|
||||
bool m_bPendingPlayAfterAuth;
|
||||
#endif
|
||||
|
||||
float m_fScreenWidth,m_fScreenHeight;
|
||||
float m_fRawWidth,m_fRawHeight;
|
||||
|
|
|
|||
|
|
@ -238,9 +238,32 @@ ULONG_PTR IQNetPlayer::GetCustomDataValue() {
|
|||
IQNetPlayer IQNet::m_player[MINECRAFT_NET_MAX_PLAYERS];
|
||||
DWORD IQNet::s_playerCount = 1;
|
||||
bool IQNet::s_isHosting = true;
|
||||
static bool s_joinedDedicatedServer = false;
|
||||
// tldr checking if its a dedicated server or a singleplayer
|
||||
|
||||
QNET_STATE _iQNetStubState = QNET_STATE_IDLE;
|
||||
|
||||
void Win64_SetJoinedDedicatedServer(bool isDedicated)
|
||||
{
|
||||
s_joinedDedicatedServer = isDedicated;
|
||||
}
|
||||
|
||||
bool Win64_IsJoinedDedicatedServer()
|
||||
{
|
||||
return s_joinedDedicatedServer;
|
||||
}
|
||||
|
||||
bool Win64_ShouldExposeSessionPlayer(IQNetPlayer *player)
|
||||
{
|
||||
if (player == nullptr)
|
||||
return false;
|
||||
|
||||
if (!IQNet::s_isHosting && s_joinedDedicatedServer && player->m_isRemote && player->m_isHostPlayer)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Win64_SetupRemoteQNetPlayer(IQNetPlayer * player, BYTE smallId, bool isHost, bool isLocal)
|
||||
{
|
||||
player->m_smallId = smallId;
|
||||
|
|
@ -317,7 +340,7 @@ IQNetPlayer* IQNet::GetPlayerByIndex(DWORD dwPlayerIndex)
|
|||
DWORD found = 0;
|
||||
for (DWORD i = 0; i < s_playerCount; i++)
|
||||
{
|
||||
if (Win64_IsActivePlayer(&m_player[i], i))
|
||||
if (Win64_IsActivePlayer(&m_player[i], i) && Win64_ShouldExposeSessionPlayer(&m_player[i]))
|
||||
{
|
||||
if (found == dwPlayerIndex) return &m_player[i];
|
||||
found++;
|
||||
|
|
@ -351,7 +374,7 @@ DWORD IQNet::GetPlayerCount()
|
|||
DWORD count = 0;
|
||||
for (DWORD i = 0; i < s_playerCount; i++)
|
||||
{
|
||||
if (Win64_IsActivePlayer(&m_player[i], i)) count++;
|
||||
if (Win64_IsActivePlayer(&m_player[i], i) && Win64_ShouldExposeSessionPlayer(&m_player[i])) count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
|
@ -362,13 +385,15 @@ void IQNet::HostGame()
|
|||
{
|
||||
_iQNetStubState = QNET_STATE_SESSION_STARTING;
|
||||
s_isHosting = true;
|
||||
// Host slot keeps legacy XUID so old host player data remains addressable.
|
||||
s_joinedDedicatedServer = false;
|
||||
// linking xuid to new uuid, lazy but it works.
|
||||
m_player[0].m_resolvedXuid = Win64Xuid::GetLegacyEmbeddedHostXuid();
|
||||
}
|
||||
void IQNet::ClientJoinGame()
|
||||
{
|
||||
_iQNetStubState = QNET_STATE_SESSION_STARTING;
|
||||
s_isHosting = false;
|
||||
s_joinedDedicatedServer = false;
|
||||
|
||||
for (int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; i++)
|
||||
{
|
||||
|
|
@ -384,6 +409,7 @@ void IQNet::EndGame()
|
|||
{
|
||||
_iQNetStubState = QNET_STATE_IDLE;
|
||||
s_isHosting = false;
|
||||
s_joinedDedicatedServer = false;
|
||||
s_playerCount = 1;
|
||||
for (int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; i++)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -93,6 +93,15 @@ public:
|
|||
void setEnforceUnicodeSheet(bool enforceUnicodeSheet);
|
||||
void setBidirectional(bool bidirectional);
|
||||
|
||||
int getCols() const { return m_cols; }
|
||||
int getRows() const { return m_rows; }
|
||||
int getCharWidth() const { return m_charWidth; }
|
||||
int getCharHeight() const { return m_charHeight; }
|
||||
ResourceLocation* getTextureLocation() const { return m_textureLocation; }
|
||||
Textures* getTextures() const { return textures; }
|
||||
wchar_t mapChar(wchar_t c) { return (wchar_t)MapCharacter(c); }
|
||||
int getCharPixelWidth(wchar_t c) const { return charWidths[(int)(unsigned short)c]; }
|
||||
|
||||
// 4J-PB - check for invalid player name - Japanese local name
|
||||
bool AllCharactersValid(const wstring &str);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
#include "..\Minecraft.World\net.minecraft.world.damagesource.h"
|
||||
#ifdef _WINDOWS64
|
||||
#include "Windows64\Network\WinsockNetLayer.h"
|
||||
#include "..\newauth\include\newauthManager.h"
|
||||
#endif
|
||||
#include <sstream>
|
||||
#ifdef SPLIT_SAVES
|
||||
|
|
@ -644,10 +645,33 @@ bool MinecraftServer::initServer(int64_t seed, NetworkGameInitData *initData, DW
|
|||
|
||||
// 4J - Unused
|
||||
//localIp = settings->getString(L"server-ip", L"");
|
||||
//onlineMode = settings->getBoolean(L"online-mode", true);
|
||||
//motd = settings->getString(L"motd", L"A Minecraft Server");
|
||||
//motd.replace('<27>', '$');
|
||||
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
{
|
||||
wstring wProvider = settings->getString(L"auth-provider", L"mojang");
|
||||
authProvider = std::string(wProvider.begin(), wProvider.end());
|
||||
if (authProvider != "elyby" && authProvider != "offline")
|
||||
authProvider = "mojang";
|
||||
}
|
||||
#elif defined(_WINDOWS64)
|
||||
// // singleplayer worlds should always be in offline mode right?
|
||||
// // fuck ts makes me upset considering LAN worlds too
|
||||
// yeah nvm that shit works now
|
||||
{
|
||||
newauthManager& authMgr = newauthManager::Get();
|
||||
auto accounts = authMgr.GetJavaAccounts();
|
||||
int activeIdx = authMgr.GetActiveJavaAccountIndex();
|
||||
if (activeIdx >= 0 && activeIdx < (int)accounts.size() && !accounts[activeIdx].authProvider.empty())
|
||||
authProvider = accounts[activeIdx].authProvider;
|
||||
else
|
||||
authProvider = "mojang";
|
||||
}
|
||||
#else
|
||||
authProvider = "mojang";
|
||||
#endif
|
||||
|
||||
setAnimals(GetDedicatedServerBool(settings, L"spawn-animals", true));
|
||||
setNpcsEnabled(GetDedicatedServerBool(settings, L"spawn-npcs", true));
|
||||
setPvpAllowed(app.GetGameHostOption( eGameHostOption_PvP )>0?true:false);
|
||||
|
|
@ -680,11 +704,11 @@ bool MinecraftServer::initServer(int64_t seed, NetworkGameInitData *initData, DW
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!onlineMode) {
|
||||
if (authProvider == "offline") {
|
||||
logger.warning("**** SERVER IS RUNNING IN OFFLINE/INSECURE MODE!");
|
||||
logger.warning("The server will make no attempt to authenticate usernames. Beware.");
|
||||
logger.warning("While this makes the game possible to play without internet access, it also opens up the ability for hackers to connect with any username they choose.");
|
||||
logger.warning("To change this, set \"online-mode\" to \"true\" in the server.settings file.");
|
||||
logger.warning("To change this, set \"auth-provider\" to \"mojang\" or \"elyby\" in server.properties.");
|
||||
}
|
||||
#endif
|
||||
setPlayers(new PlayerList(this));
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ private:
|
|||
vector<ConsoleInput *> consoleInput; // 4J - was synchronizedList - TODO - investigate
|
||||
CRITICAL_SECTION m_consoleInputCS;
|
||||
public:
|
||||
bool onlineMode;
|
||||
std::string authProvider; // mojang, elyby, or offline. not recommended.
|
||||
bool animals;
|
||||
bool npcs;
|
||||
bool pvp;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,15 @@
|
|||
#include "..\Minecraft.World\net.minecraft.world.level.storage.h"
|
||||
#include "..\Minecraft.World\net.minecraft.world.item.h"
|
||||
#include "..\Minecraft.World\SharedConstants.h"
|
||||
#include "..\Minecraft.World\GameUUID.h"
|
||||
#include "..\Minecraft.World\AuthSchemePacket.h"
|
||||
#include "..\Minecraft.World\AuthResponsePacket.h"
|
||||
#include "..\Minecraft.World\AuthResultPacket.h"
|
||||
#include "Settings.h"
|
||||
#include <thread>
|
||||
#ifdef _WINDOWS64
|
||||
#include "..\newauth\include\newauth.h"
|
||||
#endif
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
#include "..\Minecraft.Server\ServerLogManager.h"
|
||||
#include "..\Minecraft.Server\Access\Access.h"
|
||||
|
|
@ -63,6 +71,8 @@ PendingConnection::PendingConnection(MinecraftServer *server, Socket *socket, co
|
|||
|
||||
PendingConnection::~PendingConnection()
|
||||
{
|
||||
if (m_authVerifyResult)
|
||||
m_authVerifyResult->cancelled.store(true, std::memory_order_release);
|
||||
delete connection;
|
||||
}
|
||||
|
||||
|
|
@ -73,6 +83,71 @@ void PendingConnection::tick()
|
|||
this->handleAcceptedLogin(acceptedLogin);
|
||||
acceptedLogin = nullptr;
|
||||
}
|
||||
|
||||
if (m_authState == eAuth_Verifying && m_authVerifyResult &&
|
||||
m_authVerifyResult->ready.load(std::memory_order_acquire))
|
||||
{
|
||||
auto verifyResult = m_authVerifyResult;
|
||||
bool authSuccess = false;
|
||||
std::string uuid;
|
||||
std::string uname;
|
||||
std::vector<uint8_t> skinData;
|
||||
std::string skinUuid;
|
||||
std::string errMsg;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(verifyResult->mutex);
|
||||
authSuccess = verifyResult->success;
|
||||
if (authSuccess)
|
||||
{
|
||||
uuid = verifyResult->uuid;
|
||||
uname = verifyResult->username;
|
||||
skinData = std::move(verifyResult->skinData);
|
||||
skinUuid = verifyResult->skinUuid;
|
||||
}
|
||||
else
|
||||
{
|
||||
errMsg = verifyResult->errorMessage;
|
||||
}
|
||||
}
|
||||
|
||||
m_authVerifyResult.reset();
|
||||
|
||||
if (authSuccess)
|
||||
{
|
||||
if (m_authAssignedUuid.empty())
|
||||
{
|
||||
m_authAssignedUuid = std::wstring(uuid.begin(), uuid.end());
|
||||
m_authAssignedUsername = std::wstring(uname.begin(), uname.end());
|
||||
}
|
||||
|
||||
std::string skinKeyUuid = skinUuid.empty() ? uuid : skinUuid;
|
||||
std::string skinKey;
|
||||
#ifdef _WINDOWS64
|
||||
if (server->authProvider == "elyby")
|
||||
skinKey = newauth::MakeElybySkinKey(skinKeyUuid);
|
||||
else
|
||||
skinKey = newauth::MakeSkinKey(skinKeyUuid);
|
||||
#endif
|
||||
m_authSkinKey = std::wstring(skinKey.begin(), skinKey.end());
|
||||
m_authSkinData = std::move(skinData);
|
||||
|
||||
name = m_authAssignedUsername;
|
||||
|
||||
send(std::make_shared<AuthResultPacket>(true, m_authAssignedUuid,
|
||||
m_authAssignedUsername, L"", m_authSkinKey, m_authSkinData));
|
||||
m_authState = eAuth_WaitingAck;
|
||||
app.DebugPrintf("Auth: Verification succeeded for %ls\n", m_authAssignedUsername.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
std::wstring wErrMsg(errMsg.begin(), errMsg.end());
|
||||
app.DebugPrintf("Auth: Verification failed: %s\n", errMsg.c_str());
|
||||
send(std::make_shared<AuthResultPacket>(false, L"", L"", wErrMsg));
|
||||
disconnect(DisconnectPacket::eDisconnect_AuthFailed);
|
||||
}
|
||||
}
|
||||
|
||||
if (_tick++ == MAX_TICKS_BEFORE_LOGIN)
|
||||
{
|
||||
disconnect(DisconnectPacket::eDisconnect_LoginTooLong);
|
||||
|
|
@ -150,8 +225,8 @@ void PendingConnection::sendPreLoginResponse()
|
|||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
if (false)// server->onlineMode) // 4J - removed
|
||||
#if 0
|
||||
if (false) // 4J - removed
|
||||
{
|
||||
loginKey = L"TOIMPLEMENT"; // 4J - todo Long.toHexString(random.nextLong());
|
||||
connection->send( shared_ptr<PreLoginPacket>( new PreLoginPacket(loginKey, ugcXuids, ugcXuidCount, ugcFriendsOnlyBits, server->m_ugcPlayersVersion, szUniqueMapName,app.GetGameHostOption(eGameHostOption_All),hostIndex) ) );
|
||||
|
|
@ -163,10 +238,259 @@ void PendingConnection::sendPreLoginResponse()
|
|||
BYTE cappedHostIndex = (hostIndex >= 255u) ? 254 : static_cast<BYTE>(hostIndex);
|
||||
connection->send(std::make_shared<PreLoginPacket>(L"-", ugcXuids, cappedCount, ugcFriendsOnlyBits, server->m_ugcPlayersVersion, szUniqueMapName, app.GetGameHostOption(eGameHostOption_All), cappedHostIndex, server->m_texturePackId));
|
||||
}
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
sendAuthScheme();
|
||||
#endif
|
||||
}
|
||||
|
||||
void PendingConnection::sendAuthScheme()
|
||||
{
|
||||
std::string hexId = GameUUID::generateV4().toUndashed();
|
||||
if (hexId.size() > 20) hexId.resize(20);
|
||||
m_authServerId = hexId;
|
||||
|
||||
vector<wstring> schemes;
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
if (server->authProvider == "elyby")
|
||||
{
|
||||
schemes.push_back(L"elyby");
|
||||
}
|
||||
else if (server->authProvider == "offline")
|
||||
{
|
||||
schemes.push_back(L"offline");
|
||||
}
|
||||
else
|
||||
{
|
||||
schemes.push_back(L"mojang");
|
||||
}
|
||||
#else
|
||||
{
|
||||
schemes.push_back(L"mojang");
|
||||
schemes.push_back(L"elyby");
|
||||
schemes.push_back(L"offline");
|
||||
}
|
||||
#endif
|
||||
|
||||
wstring wServerId(m_authServerId.begin(), m_authServerId.end());
|
||||
send(std::make_shared<AuthSchemePacket>(schemes, wServerId));
|
||||
m_authState = eAuth_WaitingResponse;
|
||||
|
||||
app.DebugPrintf("Auth: Sent AuthSchemePacket with %d scheme(s), serverId=%s\n",
|
||||
(int)schemes.size(), m_authServerId.c_str());
|
||||
}
|
||||
|
||||
void PendingConnection::handleAuthResponse(shared_ptr<AuthResponsePacket> packet)
|
||||
{
|
||||
if (m_authState != eAuth_WaitingResponse)
|
||||
{
|
||||
app.DebugPrintf("Auth: Received AuthResponsePacket in wrong state %d\n", m_authState);
|
||||
disconnect(DisconnectPacket::eDisconnect_AuthFailed);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string chosenScheme(packet->chosenScheme.begin(), packet->chosenScheme.end());
|
||||
std::string clientUsername(packet->username.begin(), packet->username.end());
|
||||
std::string clientUuid(packet->mojangUuid.begin(), packet->mojangUuid.end());
|
||||
|
||||
app.DebugPrintf("Auth: Client chose scheme '%s', username='%s'\n",
|
||||
chosenScheme.c_str(), clientUsername.c_str());
|
||||
|
||||
if (chosenScheme == "offline")
|
||||
{
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
bool offlineAllowed = (server->authProvider == "offline");
|
||||
#else
|
||||
bool offlineAllowed = true;
|
||||
#endif
|
||||
if (!offlineAllowed)
|
||||
{
|
||||
send(std::make_shared<AuthResultPacket>(false, L"", L"",
|
||||
L"Server does not allow offline authentication"));
|
||||
disconnect(DisconnectPacket::eDisconnect_AuthFailed);
|
||||
return;
|
||||
}
|
||||
|
||||
#if !defined(MINECRAFT_SERVER_BUILD)
|
||||
// assumed that offline/singleplayer or lan worlds will auto-trust uuids.
|
||||
std::string uuid = clientUuid;
|
||||
if (uuid.empty())
|
||||
{
|
||||
GameUUID offlineUuid = GameUUID::generateOffline(clientUsername);
|
||||
uuid = offlineUuid.toDashed();
|
||||
}
|
||||
|
||||
m_authAssignedUuid = std::wstring(uuid.begin(), uuid.end());
|
||||
m_authAssignedUsername = std::wstring(clientUsername.begin(), clientUsername.end());
|
||||
name = m_authAssignedUsername;
|
||||
|
||||
send(std::make_shared<AuthResultPacket>(true, m_authAssignedUuid,
|
||||
m_authAssignedUsername, L""));
|
||||
m_authState = eAuth_WaitingAck;
|
||||
app.DebugPrintf("Auth: Offline auth accepted (single-player), uuid=%s\n", uuid.c_str());
|
||||
return;
|
||||
#else
|
||||
// for ded server to regenerate uuid serverside to prevent uuid forgery
|
||||
GameUUID offlineUuid = GameUUID::generateOffline(clientUsername);
|
||||
std::string dashedUuid = offlineUuid.toDashed();
|
||||
|
||||
m_authAssignedUuid = std::wstring(dashedUuid.begin(), dashedUuid.end());
|
||||
m_authAssignedUsername = std::wstring(clientUsername.begin(), clientUsername.end());
|
||||
name = m_authAssignedUsername;
|
||||
if (!clientUuid.empty())
|
||||
{
|
||||
m_authState = eAuth_Verifying;
|
||||
m_authVerifyResult = std::make_shared<AuthVerifyResult>();
|
||||
m_authVerifyResult->success = true;
|
||||
m_authVerifyResult->username = clientUsername;
|
||||
m_authVerifyResult->uuid = dashedUuid;
|
||||
m_authVerifyResult->skinUuid = clientUuid; // just use the user's uuid if auth'd with mojang or ely.by. ez.
|
||||
|
||||
std::shared_ptr<AuthVerifyResult> result = m_authVerifyResult;
|
||||
std::string fetchUuid = clientUuid;
|
||||
|
||||
std::thread([result, fetchUuid]() {
|
||||
try {
|
||||
if (result->cancelled.load(std::memory_order_acquire))
|
||||
return;
|
||||
std::string fetchErr;
|
||||
std::string skinUrl = newauth::FetchProfileSkinUrl(fetchUuid, fetchErr);
|
||||
if (!skinUrl.empty())
|
||||
{
|
||||
auto skinPng = newauth::FetchSkinPng(skinUrl, fetchErr);
|
||||
if (!skinPng.empty() && newauth::ValidateSkinPng(skinPng.data(), skinPng.size()))
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(result->mutex);
|
||||
result->skinUrl = skinUrl;
|
||||
result->skinData = std::move(skinPng);
|
||||
}
|
||||
}
|
||||
result->ready.store(true, std::memory_order_release);
|
||||
} catch (...) {
|
||||
result->ready.store(true, std::memory_order_release);
|
||||
}
|
||||
}).detach();
|
||||
return;
|
||||
}
|
||||
|
||||
send(std::make_shared<AuthResultPacket>(true, m_authAssignedUuid,
|
||||
m_authAssignedUsername, L""));
|
||||
m_authState = eAuth_WaitingAck;
|
||||
app.DebugPrintf("Auth: Offline auth success, uuid=%s\n", dashedUuid.c_str());
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (chosenScheme == "mojang" || chosenScheme == "elyby")
|
||||
{
|
||||
bool isElyby = (chosenScheme == "elyby");
|
||||
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
if (server->authProvider == "offline")
|
||||
{
|
||||
send(std::make_shared<AuthResultPacket>(false, L"", L"",
|
||||
L"Server does not allow online authentication"));
|
||||
disconnect(DisconnectPacket::eDisconnect_AuthFailed);
|
||||
return;
|
||||
}
|
||||
|
||||
bool wantElyby = (server->authProvider == "elyby");
|
||||
if (isElyby != wantElyby)
|
||||
{
|
||||
app.DebugPrintf("Auth: Client chose '%s' but server expects '%s'\n",
|
||||
chosenScheme.c_str(), server->authProvider.c_str());
|
||||
send(std::make_shared<AuthResultPacket>(false, L"", L"",
|
||||
L"Auth scheme mismatch"));
|
||||
disconnect(DisconnectPacket::eDisconnect_AuthFailed);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
m_authState = eAuth_Verifying;
|
||||
m_authVerifyResult = std::make_shared<AuthVerifyResult>();
|
||||
|
||||
std::string serverId = m_authServerId;
|
||||
std::string username = clientUsername;
|
||||
std::shared_ptr<AuthVerifyResult> result = m_authVerifyResult;
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
std::thread([result, username, serverId, isElyby]() {
|
||||
try {
|
||||
if (result->cancelled.load(std::memory_order_acquire))
|
||||
return;
|
||||
|
||||
std::string error;
|
||||
newauth::HasJoinedResult hjResult;
|
||||
|
||||
if (isElyby)
|
||||
hjResult = newauth::ElybyHasJoined(username, serverId, error);
|
||||
else
|
||||
hjResult = newauth::HasJoined(username, serverId, error);
|
||||
|
||||
if (result->cancelled.load(std::memory_order_acquire))
|
||||
return;
|
||||
|
||||
std::vector<uint8_t> skin;
|
||||
if (hjResult.success && !hjResult.skinUrl.empty())
|
||||
{
|
||||
std::string skinErr;
|
||||
skin = newauth::FetchSkinPng(hjResult.skinUrl, skinErr);
|
||||
if (!skin.empty() && !newauth::ValidateSkinPng(skin.data(), skin.size()))
|
||||
skin.clear();
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(result->mutex);
|
||||
if (hjResult.success)
|
||||
{
|
||||
result->success = true;
|
||||
result->username = hjResult.username;
|
||||
result->uuid = hjResult.uuid;
|
||||
result->skinUrl = hjResult.skinUrl;
|
||||
if (!skin.empty())
|
||||
result->skinData = std::move(skin);
|
||||
}
|
||||
else
|
||||
{
|
||||
result->success = false;
|
||||
result->errorMessage = error.empty() ? "Authentication failed" : error;
|
||||
}
|
||||
}
|
||||
result->ready.store(true, std::memory_order_release);
|
||||
} catch (...) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(result->mutex);
|
||||
result->success = false;
|
||||
result->errorMessage = "Internal auth error";
|
||||
}
|
||||
result->ready.store(true, std::memory_order_release);
|
||||
}
|
||||
}).detach();
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
// only really a problem if the client is modified. You Know Who You
|
||||
app.DebugPrintf("Auth: Unknown scheme '%s'\n", chosenScheme.c_str());
|
||||
send(std::make_shared<AuthResultPacket>(false, L"", L"", L"Unknown auth scheme"));
|
||||
disconnect(DisconnectPacket::eDisconnect_AuthFailed);
|
||||
}
|
||||
|
||||
void PendingConnection::handleLogin(shared_ptr<LoginPacket> packet)
|
||||
{
|
||||
#ifdef _WINDOWS64
|
||||
if (m_authState != eAuth_WaitingAck && m_authState != eAuth_Done)
|
||||
{
|
||||
app.DebugPrintf("Auth: LoginPacket received in state %d, rejecting\n", m_authState);
|
||||
disconnect(DisconnectPacket::eDisconnect_AuthFailed);
|
||||
return;
|
||||
}
|
||||
if (m_authState == eAuth_WaitingAck)
|
||||
{
|
||||
m_authState = eAuth_Done;
|
||||
}
|
||||
#endif
|
||||
|
||||
// printf("Server: handleLogin\n");
|
||||
//name = packet->userName;
|
||||
if (packet->clientVersion != SharedConstants::NETWORK_PROTOCOL_VERSION)
|
||||
|
|
@ -183,11 +507,8 @@ void PendingConnection::handleLogin(shared_ptr<LoginPacket> packet)
|
|||
return;
|
||||
}
|
||||
|
||||
//if (true)// 4J removed !server->onlineMode)
|
||||
bool sentDisconnect = false;
|
||||
|
||||
// Use the same Xuid choice as handleAcceptedLogin (offline first, online fallback).
|
||||
//
|
||||
PlayerUID loginXuid = packet->m_offlineXuid;
|
||||
if (loginXuid == INVALID_XUID) loginXuid = packet->m_onlineXuid;
|
||||
|
||||
|
|
@ -336,6 +657,22 @@ void PendingConnection::handleAcceptedLogin(shared_ptr<LoginPacket> packet)
|
|||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
ServerRuntime::ServerLogManager::OnAcceptedPlayerLogin(GetPendingConnectionSmallId(connection), name);
|
||||
#endif
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
// Apply auth-verified skin to the player so that placeNewPlayer can
|
||||
// distribute it to all clients via the existing texture mechanism.
|
||||
if (!m_authSkinKey.empty() && !m_authSkinData.empty())
|
||||
{
|
||||
DWORD skinBytes = (DWORD)m_authSkinData.size();
|
||||
PBYTE skinCopy = new BYTE[skinBytes];
|
||||
memcpy(skinCopy, m_authSkinData.data(), skinBytes);
|
||||
app.AddMemoryTextureFile(m_authSkinKey, skinCopy, skinBytes);
|
||||
playerEntity->customTextureUrl = m_authSkinKey;
|
||||
app.DebugPrintf("Auth: Registered server-side skin '%ls' (%d bytes) for %ls\n",
|
||||
m_authSkinKey.c_str(), (int)skinBytes, name.c_str());
|
||||
}
|
||||
#endif
|
||||
|
||||
server->getPlayers()->placeNewPlayer(connection, playerEntity, packet);
|
||||
connection = nullptr; // We've moved responsibility for this over to the new PlayerConnection, nullptr so we don't delete our reference to it here in our dtor
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,43 @@
|
|||
#pragma once
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include "..\Minecraft.World\PacketListener.h"
|
||||
class MinecraftServer;
|
||||
class Socket;
|
||||
class LoginPacket;
|
||||
class AuthSchemePacket;
|
||||
class AuthResponsePacket;
|
||||
class Connection;
|
||||
class Random;
|
||||
using namespace std;
|
||||
|
||||
class PendingConnection : public PacketListener
|
||||
{
|
||||
public:
|
||||
enum eAuthState
|
||||
{
|
||||
eAuth_None = 0,
|
||||
eAuth_WaitingResponse,
|
||||
eAuth_Verifying,
|
||||
eAuth_WaitingAck,
|
||||
eAuth_Done
|
||||
};
|
||||
|
||||
struct AuthVerifyResult
|
||||
{
|
||||
std::atomic<bool> ready{false};
|
||||
std::atomic<bool> cancelled{false};
|
||||
std::mutex mutex;
|
||||
bool success = false;
|
||||
std::string username;
|
||||
std::string uuid;
|
||||
std::string skinUuid; // UUID to use for skin key (may differ from uuid for offline auth)
|
||||
std::string skinUrl;
|
||||
std::vector<uint8_t> skinData;
|
||||
std::string errorMessage;
|
||||
};
|
||||
|
||||
private:
|
||||
static const int FAKE_LAG = 0;
|
||||
static const int MAX_TICKS_BEFORE_LOGIN = 20 * 30;
|
||||
|
|
@ -27,6 +56,14 @@ private:
|
|||
shared_ptr<LoginPacket> acceptedLogin;
|
||||
wstring loginKey;
|
||||
|
||||
eAuthState m_authState = eAuth_None;
|
||||
std::string m_authServerId;
|
||||
std::wstring m_authAssignedUuid;
|
||||
std::wstring m_authAssignedUsername;
|
||||
std::wstring m_authSkinKey;
|
||||
std::vector<uint8_t> m_authSkinData;
|
||||
std::shared_ptr<AuthVerifyResult> m_authVerifyResult;
|
||||
|
||||
public:
|
||||
PendingConnection(MinecraftServer *server, Socket *socket, const wstring& id);
|
||||
~PendingConnection();
|
||||
|
|
@ -35,6 +72,7 @@ public:
|
|||
virtual void handlePreLogin(shared_ptr<PreLoginPacket> packet);
|
||||
virtual void handleLogin(shared_ptr<LoginPacket> packet);
|
||||
virtual void handleAcceptedLogin(shared_ptr<LoginPacket> packet);
|
||||
virtual void handleAuthResponse(shared_ptr<AuthResponsePacket> packet);
|
||||
virtual void onDisconnect(DisconnectPacket::eDisconnectReason reason, void *reasonObjects);
|
||||
virtual void handleGetInfo(shared_ptr<GetInfoPacket> packet);
|
||||
virtual void handleKeepAlive(shared_ptr<KeepAlivePacket> packet);
|
||||
|
|
@ -46,4 +84,5 @@ public:
|
|||
|
||||
private:
|
||||
void sendPreLoginResponse();
|
||||
void sendAuthScheme();
|
||||
};
|
||||
|
|
@ -290,6 +290,9 @@ public:
|
|||
public:
|
||||
void clearLastBoundId();
|
||||
|
||||
// i guess the nativeui wasn't a terrible idea. it just needed to be written right.
|
||||
int loadTextureByPath(const wstring& path) { return loadTexture(TN_COUNT, path); }
|
||||
|
||||
private:
|
||||
int loadTexture(TEXTURE_NAME texId, const wstring& resourceName);
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -1317,6 +1317,7 @@ bool WinsockNetLayer::StartAdvertising(int gamePort, const wchar_t* hostName, un
|
|||
s_advertiseData.texturePackParentId = texPackId;
|
||||
s_advertiseData.subTexturePackId = subTexId;
|
||||
s_advertiseData.isJoinable = 0;
|
||||
s_advertiseData.isDedicatedServer = g_Win64DedicatedServer ? 1 : 0;
|
||||
s_hostGamePort = gamePort;
|
||||
LeaveCriticalSection(&s_advertiseLock);
|
||||
|
||||
|
|
@ -1519,6 +1520,7 @@ DWORD WINAPI WinsockNetLayer::DiscoveryThreadProc(LPVOID param)
|
|||
s_discoveredSessions[i].texturePackParentId = broadcast->texturePackParentId;
|
||||
s_discoveredSessions[i].subTexturePackId = broadcast->subTexturePackId;
|
||||
s_discoveredSessions[i].isJoinable = (broadcast->isJoinable != 0);
|
||||
s_discoveredSessions[i].isDedicatedServer = (broadcast->isDedicatedServer != 0);
|
||||
s_discoveredSessions[i].lastSeenTick = now;
|
||||
found = true;
|
||||
break;
|
||||
|
|
@ -1539,6 +1541,7 @@ DWORD WINAPI WinsockNetLayer::DiscoveryThreadProc(LPVOID param)
|
|||
session.texturePackParentId = broadcast->texturePackParentId;
|
||||
session.subTexturePackId = broadcast->subTexturePackId;
|
||||
session.isJoinable = (broadcast->isJoinable != 0);
|
||||
session.isDedicatedServer = (broadcast->isDedicatedServer != 0);
|
||||
session.lastSeenTick = now;
|
||||
s_discoveredSessions.push_back(session);
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ struct Win64LANBroadcast
|
|||
DWORD texturePackParentId;
|
||||
BYTE subTexturePackId;
|
||||
BYTE isJoinable;
|
||||
BYTE isDedicatedServer;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
|
|
@ -51,6 +52,7 @@ struct Win64LANSession
|
|||
unsigned int texturePackParentId;
|
||||
unsigned char subTexturePackId;
|
||||
bool isJoinable;
|
||||
bool isDedicatedServer;
|
||||
DWORD lastSeenTick;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@
|
|||
#include "Network\WinsockNetLayer.h"
|
||||
#include "Windows64_Xuid.h"
|
||||
#include "Common/UI/UI.h"
|
||||
#include "..\\..\\newauth\\include\\newauthManager.h"
|
||||
|
||||
// Forward-declare the internal Renderer class and its global instance from 4J_Render_PC_d.lib.
|
||||
// C4JRender (RenderManager) is a stateless wrapper — all D3D state lives in InternalRenderManager.
|
||||
|
|
@ -1276,6 +1277,10 @@ static Minecraft* InitialiseMinecraftRuntime()
|
|||
|
||||
ProfileManager.SetDebugFullOverride(true);
|
||||
|
||||
// Temporary isolation: bypass startup auth restore to verify whether
|
||||
// release startup crash is caused by newauth initialization.
|
||||
//newauthManager::Get().TryRestoreActiveJavaAccount();
|
||||
|
||||
Tesselator::CreateNewThreadStorage(1024 * 1024);
|
||||
AABB::CreateNewThreadStorage();
|
||||
Vec3::CreateNewThreadStorage();
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ set(_MINECRAFT_CLIENT_WINDOWS_COMMON_UI
|
|||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UIGroup.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UILayer.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UILayer.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/NativeUIRenderer.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/NativeUIRenderer.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UIScene.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UIScene.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UITTFFont.cpp"
|
||||
|
|
@ -167,6 +169,8 @@ set(_MINECRAFT_CLIENT_WINDOWS_COMMON_UI_SCENES_FRONTEND_MENU_SCREENS
|
|||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UIScene_LoadOrJoinMenu.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UIScene_MainMenu.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UIScene_MainMenu.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UIScene_MSAuth.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UIScene_MSAuth.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UIScene_NewUpdateMessage.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UIScene_NewUpdateMessage.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UIScene_SaveMessage.cpp"
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ set_target_properties(Minecraft.Server PROPERTIES
|
|||
|
||||
target_link_libraries(Minecraft.Server PRIVATE
|
||||
Minecraft.World
|
||||
newauth
|
||||
d3d11
|
||||
d3dcompiler
|
||||
XInput9_1_0
|
||||
|
|
|
|||
|
|
@ -80,7 +80,8 @@ static const ServerPropertyDefault kServerPropertyDefaults[] =
|
|||
{ "spawn-monsters", "true" },
|
||||
{ "spawn-npcs", "true" },
|
||||
{ "tnt", "true" },
|
||||
{ "trust-players", "true" }
|
||||
{ "trust-players", "true" },
|
||||
{ "auth-provider", "mojang" }
|
||||
};
|
||||
|
||||
static std::string BoolToString(bool value)
|
||||
|
|
@ -783,6 +784,11 @@ ServerPropertiesConfig LoadServerPropertiesConfig()
|
|||
merged[it->first] = it->second;
|
||||
}
|
||||
|
||||
if (merged.erase("online-mode") > 0)
|
||||
{
|
||||
shouldWrite = true;
|
||||
}
|
||||
|
||||
std::string worldName = TrimAscii(merged["level-name"]);
|
||||
if (worldName.empty())
|
||||
{
|
||||
|
|
@ -904,6 +910,8 @@ bool SaveServerPropertiesConfig(const ServerPropertiesConfig &config)
|
|||
}
|
||||
}
|
||||
|
||||
merged.erase("online-mode");
|
||||
|
||||
std::string worldName = TrimAscii(WideToUtf8(config.worldName));
|
||||
if (worldName.empty())
|
||||
{
|
||||
|
|
|
|||
54
Minecraft.World/AuthResponsePacket.cpp
Normal file
54
Minecraft.World/AuthResponsePacket.cpp
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
#include "stdafx.h"
|
||||
#include "InputOutputStream.h"
|
||||
#include "PacketListener.h"
|
||||
#include "AuthResponsePacket.h"
|
||||
|
||||
AuthResponsePacket::AuthResponsePacket()
|
||||
{
|
||||
}
|
||||
|
||||
AuthResponsePacket::AuthResponsePacket(const wstring& chosenScheme, const wstring& mojangUuid, const wstring& username)
|
||||
{
|
||||
this->chosenScheme = chosenScheme;
|
||||
this->mojangUuid = mojangUuid;
|
||||
this->username = username;
|
||||
}
|
||||
|
||||
void AuthResponsePacket::read(DataInputStream *dis)
|
||||
{
|
||||
chosenScheme = readUtf(dis, 32);
|
||||
mojangUuid = readUtf(dis, 64);
|
||||
username = readUtf(dis, 16);
|
||||
|
||||
if (chosenScheme != L"mojang" && chosenScheme != L"offline" && chosenScheme != L"elyby")
|
||||
chosenScheme = L"";
|
||||
|
||||
// only allow normal mc username characters
|
||||
for (wchar_t c : username)
|
||||
{
|
||||
if (!((c >= L'a' && c <= L'z') || (c >= L'A' && c <= L'Z') ||
|
||||
(c >= L'0' && c <= L'9') || c == L'_'))
|
||||
{
|
||||
username = L"";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AuthResponsePacket::write(DataOutputStream *dos)
|
||||
{
|
||||
writeUtf(chosenScheme, dos);
|
||||
writeUtf(mojangUuid, dos);
|
||||
writeUtf(username, dos);
|
||||
}
|
||||
|
||||
void AuthResponsePacket::handle(PacketListener *listener)
|
||||
{
|
||||
listener->handleAuthResponse(shared_from_this());
|
||||
}
|
||||
|
||||
int AuthResponsePacket::getEstimatedSize()
|
||||
{
|
||||
return static_cast<int>(3 * sizeof(short) +
|
||||
(chosenScheme.length() + mojangUuid.length() + username.length()) * sizeof(wchar_t));
|
||||
}
|
||||
22
Minecraft.World/AuthResponsePacket.h
Normal file
22
Minecraft.World/AuthResponsePacket.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
#include "Packet.h"
|
||||
using namespace std;
|
||||
|
||||
class AuthResponsePacket : public Packet, public enable_shared_from_this<AuthResponsePacket>
|
||||
{
|
||||
public:
|
||||
wstring chosenScheme;
|
||||
wstring mojangUuid;
|
||||
wstring username;
|
||||
|
||||
AuthResponsePacket();
|
||||
AuthResponsePacket(const wstring& chosenScheme, const wstring& mojangUuid, const wstring& username);
|
||||
|
||||
virtual void read(DataInputStream *dis);
|
||||
virtual void write(DataOutputStream *dos);
|
||||
virtual void handle(PacketListener *listener);
|
||||
virtual int getEstimatedSize();
|
||||
|
||||
static shared_ptr<Packet> create() { return make_shared<AuthResponsePacket>(); }
|
||||
virtual int getId() { return 171; }
|
||||
};
|
||||
76
Minecraft.World/AuthResultPacket.cpp
Normal file
76
Minecraft.World/AuthResultPacket.cpp
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
#include "stdafx.h"
|
||||
#include "InputOutputStream.h"
|
||||
#include "PacketListener.h"
|
||||
#include "AuthResultPacket.h"
|
||||
|
||||
AuthResultPacket::AuthResultPacket()
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
|
||||
AuthResultPacket::AuthResultPacket(bool success, const wstring& assignedUuid, const wstring& assignedUsername,
|
||||
const wstring& errorMessage, const wstring& skinKey,
|
||||
std::vector<uint8_t> skinData)
|
||||
{
|
||||
this->success = success;
|
||||
this->assignedUuid = assignedUuid;
|
||||
this->assignedUsername = assignedUsername;
|
||||
this->errorMessage = errorMessage;
|
||||
this->skinKey = skinKey;
|
||||
this->skinData = std::move(skinData);
|
||||
}
|
||||
|
||||
void AuthResultPacket::read(DataInputStream *dis)
|
||||
{
|
||||
success = dis->readBoolean();
|
||||
assignedUuid = readUtf(dis, 64);
|
||||
assignedUsername = readUtf(dis, 64);
|
||||
errorMessage = readUtf(dis, 256);
|
||||
skinKey = readUtf(dis, 256);
|
||||
|
||||
// read the skin blob (length + bytes)
|
||||
// cap at 32kb, a real skin png is like 4kb tops
|
||||
int skinSize = dis->readInt();
|
||||
if (skinSize > 0 && skinSize <= 32768)
|
||||
{
|
||||
skinData.resize(static_cast<size_t>(skinSize));
|
||||
for (int i = 0; i < skinSize; i++)
|
||||
skinData[i] = dis->readByte();
|
||||
}
|
||||
else
|
||||
{
|
||||
skinData.clear();
|
||||
// eat the bytes anyway so the stream doesnt get fucked up
|
||||
if (skinSize > 0)
|
||||
{
|
||||
for (int i = 0; i < skinSize; i++)
|
||||
dis->readByte();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AuthResultPacket::write(DataOutputStream *dos)
|
||||
{
|
||||
dos->writeBoolean(success);
|
||||
writeUtf(assignedUuid, dos);
|
||||
writeUtf(assignedUsername, dos);
|
||||
writeUtf(errorMessage, dos);
|
||||
writeUtf(skinKey, dos);
|
||||
|
||||
int skinSize = static_cast<int>(skinData.size());
|
||||
dos->writeInt(skinSize);
|
||||
for (int i = 0; i < skinSize; i++)
|
||||
dos->writeByte(skinData[i]);
|
||||
}
|
||||
|
||||
void AuthResultPacket::handle(PacketListener *listener)
|
||||
{
|
||||
listener->handleAuthResult(shared_from_this());
|
||||
}
|
||||
|
||||
int AuthResultPacket::getEstimatedSize()
|
||||
{
|
||||
return static_cast<int>(sizeof(bool) + 4 * sizeof(short) +
|
||||
(assignedUuid.length() + assignedUsername.length() + errorMessage.length() + skinKey.length()) * sizeof(wchar_t)
|
||||
+ sizeof(int) + skinData.size());
|
||||
}
|
||||
28
Minecraft.World/AuthResultPacket.h
Normal file
28
Minecraft.World/AuthResultPacket.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
#include "Packet.h"
|
||||
#include <vector>
|
||||
using namespace std;
|
||||
|
||||
class AuthResultPacket : public Packet, public enable_shared_from_this<AuthResultPacket>
|
||||
{
|
||||
public:
|
||||
bool success;
|
||||
wstring assignedUuid;
|
||||
wstring assignedUsername;
|
||||
wstring errorMessage;
|
||||
wstring skinKey;
|
||||
std::vector<uint8_t> skinData;
|
||||
|
||||
AuthResultPacket();
|
||||
AuthResultPacket(bool success, const wstring& assignedUuid, const wstring& assignedUsername,
|
||||
const wstring& errorMessage, const wstring& skinKey = L"",
|
||||
std::vector<uint8_t> skinData = {});
|
||||
|
||||
virtual void read(DataInputStream *dis);
|
||||
virtual void write(DataOutputStream *dos);
|
||||
virtual void handle(PacketListener *listener);
|
||||
virtual int getEstimatedSize();
|
||||
|
||||
static shared_ptr<Packet> create() { return make_shared<AuthResultPacket>(); }
|
||||
virtual int getId() { return 172; }
|
||||
};
|
||||
56
Minecraft.World/AuthSchemePacket.cpp
Normal file
56
Minecraft.World/AuthSchemePacket.cpp
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
#include "stdafx.h"
|
||||
#include "InputOutputStream.h"
|
||||
#include "PacketListener.h"
|
||||
#include "AuthSchemePacket.h"
|
||||
|
||||
AuthSchemePacket::AuthSchemePacket()
|
||||
{
|
||||
}
|
||||
|
||||
AuthSchemePacket::AuthSchemePacket(const vector<wstring>& schemes, const wstring& serverId)
|
||||
{
|
||||
this->schemes = schemes;
|
||||
this->serverId = serverId;
|
||||
}
|
||||
|
||||
void AuthSchemePacket::read(DataInputStream *dis)
|
||||
{
|
||||
int count = dis->readInt();
|
||||
if (count < 0 || count > 16) count = 0; // dont let someone send us a billion schemes
|
||||
schemes.clear();
|
||||
schemes.reserve(static_cast<size_t>(count));
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
wstring scheme = readUtf(dis, 32);
|
||||
// only take scheme names we actually know about
|
||||
if (scheme == L"mojang" || scheme == L"offline" || scheme == L"elyby")
|
||||
schemes.push_back(std::move(scheme));
|
||||
}
|
||||
serverId = readUtf(dis, 64);
|
||||
}
|
||||
|
||||
void AuthSchemePacket::write(DataOutputStream *dos)
|
||||
{
|
||||
dos->writeInt(static_cast<int>(schemes.size()));
|
||||
for (auto& s : schemes)
|
||||
{
|
||||
writeUtf(s, dos);
|
||||
}
|
||||
writeUtf(serverId, dos);
|
||||
}
|
||||
|
||||
void AuthSchemePacket::handle(PacketListener *listener)
|
||||
{
|
||||
listener->handleAuthScheme(shared_from_this());
|
||||
}
|
||||
|
||||
int AuthSchemePacket::getEstimatedSize()
|
||||
{
|
||||
int size = sizeof(int);
|
||||
for (auto& s : schemes)
|
||||
{
|
||||
size += sizeof(short) + static_cast<int>(s.length()) * sizeof(wchar_t);
|
||||
}
|
||||
size += sizeof(short) + static_cast<int>(serverId.length()) * sizeof(wchar_t);
|
||||
return size;
|
||||
}
|
||||
22
Minecraft.World/AuthSchemePacket.h
Normal file
22
Minecraft.World/AuthSchemePacket.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
#include "Packet.h"
|
||||
using namespace std;
|
||||
|
||||
class AuthSchemePacket : public Packet, public enable_shared_from_this<AuthSchemePacket>
|
||||
{
|
||||
public:
|
||||
// "mojang", "offline" etc
|
||||
vector<wstring> schemes;
|
||||
wstring serverId; // hex challenge (20 chars), empty for offline
|
||||
|
||||
AuthSchemePacket();
|
||||
AuthSchemePacket(const vector<wstring>& schemes, const wstring& serverId);
|
||||
|
||||
virtual void read(DataInputStream *dis);
|
||||
virtual void write(DataOutputStream *dos);
|
||||
virtual void handle(PacketListener *listener);
|
||||
virtual int getEstimatedSize();
|
||||
|
||||
static shared_ptr<Packet> create() { return make_shared<AuthSchemePacket>(); }
|
||||
virtual int getId() { return 170; }
|
||||
};
|
||||
|
|
@ -50,6 +50,7 @@ public:
|
|||
#ifdef _XBOX_ONE
|
||||
eDisconnect_ExitedGame,
|
||||
#endif
|
||||
eDisconnect_AuthFailed,
|
||||
};
|
||||
|
||||
// 4J Stu - The reason was a string, but we need to send a non-locale specific reason
|
||||
|
|
|
|||
228
Minecraft.World/GameUUID.cpp
Normal file
228
Minecraft.World/GameUUID.cpp
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
#include "stdafx.h"
|
||||
#include "GameUUID.h"
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#include <bcrypt.h>
|
||||
#pragma comment(lib, "bcrypt.lib")
|
||||
#endif
|
||||
|
||||
static uint8_t hexVal(char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9') return (uint8_t)(c - '0');
|
||||
if (c >= 'a' && c <= 'f') return (uint8_t)(c - 'a' + 10);
|
||||
if (c >= 'A' && c <= 'F') return (uint8_t)(c - 'A' + 10);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string GameUUID::toDashed() const
|
||||
{
|
||||
char buf[37];
|
||||
sprintf_s(buf, sizeof(buf),
|
||||
"%08x-%04x-%04x-%04x-%012llx",
|
||||
(unsigned int)(hi >> 32),
|
||||
(unsigned int)((hi >> 16) & 0xFFFF),
|
||||
(unsigned int)(hi & 0xFFFF),
|
||||
(unsigned int)(lo >> 48),
|
||||
(unsigned long long)(lo & 0x0000FFFFFFFFFFFFULL));
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
std::string GameUUID::toUndashed() const
|
||||
{
|
||||
char buf[33];
|
||||
sprintf_s(buf, sizeof(buf),
|
||||
"%016llx%016llx",
|
||||
(unsigned long long)hi,
|
||||
(unsigned long long)lo);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
std::wstring GameUUID::toWDashed() const
|
||||
{
|
||||
std::string s = toDashed();
|
||||
return std::wstring(s.begin(), s.end());
|
||||
}
|
||||
|
||||
GameUUID GameUUID::fromDashed(const std::string& s)
|
||||
{
|
||||
std::string undashed;
|
||||
undashed.reserve(32);
|
||||
for (size_t i = 0; i < s.size(); i++)
|
||||
{
|
||||
if (s[i] != '-')
|
||||
undashed.push_back(s[i]);
|
||||
}
|
||||
return fromUndashed(undashed);
|
||||
}
|
||||
|
||||
GameUUID GameUUID::fromUndashed(const std::string& s)
|
||||
{
|
||||
GameUUID uuid;
|
||||
if (s.size() < 32)
|
||||
return uuid;
|
||||
|
||||
uuid.hi = 0;
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
uuid.hi = (uuid.hi << 4) | hexVal(s[i]);
|
||||
}
|
||||
uuid.lo = 0;
|
||||
for (int i = 16; i < 32; i++)
|
||||
{
|
||||
uuid.lo = (uuid.lo << 4) | hexVal(s[i]);
|
||||
}
|
||||
return uuid;
|
||||
}
|
||||
GameUUID GameUUID::generateV4()
|
||||
{
|
||||
GameUUID uuid;
|
||||
uint8_t bytes[16] = {};
|
||||
|
||||
#ifdef _WIN32
|
||||
NTSTATUS status = BCryptGenRandom(NULL, bytes, sizeof(bytes), BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
||||
if (!BCRYPT_SUCCESS(status))
|
||||
{
|
||||
for (int i = 0; i < 16; i++) bytes[i] = (uint8_t)(rand() & 0xFF);
|
||||
}
|
||||
#else
|
||||
static uint64_t counter = 0;
|
||||
uint64_t seed = (uint64_t)time(NULL) ^ (++counter * 6364136223846793005ULL);
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
seed = seed * 6364136223846793005ULL + 1442695040888963407ULL;
|
||||
bytes[i] = (uint8_t)(seed >> 56);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Set version 4 (bits 48-51 of hi = 0100)
|
||||
bytes[6] = (bytes[6] & 0x0F) | 0x40;
|
||||
// Set variant 1 (bits 0-1 of byte 8 = 10)
|
||||
bytes[8] = (bytes[8] & 0x3F) | 0x80;
|
||||
|
||||
uuid.hi = 0;
|
||||
for (int i = 0; i < 8; i++)
|
||||
uuid.hi = (uuid.hi << 8) | bytes[i];
|
||||
|
||||
uuid.lo = 0;
|
||||
for (int i = 8; i < 16; i++)
|
||||
uuid.lo = (uuid.lo << 8) | bytes[i];
|
||||
|
||||
return uuid;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
struct MD5State
|
||||
{
|
||||
uint32_t state[4];
|
||||
uint64_t count;
|
||||
uint8_t buffer[64];
|
||||
};
|
||||
|
||||
static const uint32_t md5_T[64] = {
|
||||
0xd76aa478,0xe8c7b756,0x242070db,0xc1bdceee,0xf57c0faf,0x4787c62a,0xa8304613,0xfd469501,
|
||||
0x698098d8,0x8b44f7af,0xffff5bb1,0x895cd7be,0x6b901122,0xfd987193,0xa679438e,0x49b40821,
|
||||
0xf61e2562,0xc040b340,0x265e5a51,0xe9b6c7aa,0xd62f105d,0x02441453,0xd8a1e681,0xe7d3fbc8,
|
||||
0x21e1cde6,0xc33707d6,0xf4d50d87,0x455a14ed,0xa9e3e905,0xfcefa3f8,0x676f02d9,0x8d2a4c8a,
|
||||
0xfffa3942,0x8771f681,0x6d9d6122,0xfde5380c,0xa4beea44,0x4bdecfa9,0xf6bb4b60,0xbebfbc70,
|
||||
0x289b7ec6,0xeaa127fa,0xd4ef3085,0x04881d05,0xd9d4d039,0xe6db99e5,0x1fa27cf8,0xc4ac5665,
|
||||
0xf4292244,0x432aff97,0xab9423a7,0xfc93a039,0x655b59c3,0x8f0ccc92,0xffeff47d,0x85845dd1,
|
||||
0x6fa87e4f,0xfe2ce6e0,0xa3014314,0x4e0811a1,0xf7537e82,0xbd3af235,0x2ad7d2bb,0xeb86d391
|
||||
};
|
||||
|
||||
static const int md5_S[64] = {
|
||||
7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22,
|
||||
5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,
|
||||
4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,
|
||||
6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21
|
||||
};
|
||||
|
||||
static inline uint32_t rotl32(uint32_t x, int n) { return (x << n) | (x >> (32 - n)); }
|
||||
|
||||
static void md5_transform(uint32_t state[4], const uint8_t block[64])
|
||||
{
|
||||
uint32_t M[16];
|
||||
for (int i = 0; i < 16; i++)
|
||||
M[i] = (uint32_t)block[i * 4] | ((uint32_t)block[i * 4 + 1] << 8) |
|
||||
((uint32_t)block[i * 4 + 2] << 16) | ((uint32_t)block[i * 4 + 3] << 24);
|
||||
|
||||
uint32_t a = state[0], b = state[1], c = state[2], d = state[3];
|
||||
|
||||
for (int i = 0; i < 64; i++)
|
||||
{
|
||||
uint32_t f;
|
||||
int g;
|
||||
if (i < 16) { f = (b & c) | (~b & d); g = i; }
|
||||
else if (i < 32) { f = (d & b) | (~d & c); g = (5 * i + 1) % 16; }
|
||||
else if (i < 48) { f = b ^ c ^ d; g = (3 * i + 5) % 16; }
|
||||
else { f = c ^ (b | ~d); g = (7 * i) % 16; }
|
||||
|
||||
uint32_t temp = d;
|
||||
d = c;
|
||||
c = b;
|
||||
b = b + rotl32(a + f + md5_T[i] + M[g], md5_S[i]);
|
||||
a = temp;
|
||||
}
|
||||
|
||||
state[0] += a; state[1] += b; state[2] += c; state[3] += d;
|
||||
}
|
||||
|
||||
static void md5(const uint8_t* data, size_t len, uint8_t digest[16])
|
||||
{
|
||||
uint32_t state[4] = { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 };
|
||||
|
||||
size_t i = 0;
|
||||
for (; i + 64 <= len; i += 64)
|
||||
md5_transform(state, data + i);
|
||||
|
||||
uint8_t block[64] = {};
|
||||
size_t remain = len - i;
|
||||
if (remain > 0)
|
||||
memcpy(block, data + i, remain);
|
||||
|
||||
block[remain] = 0x80;
|
||||
|
||||
if (remain >= 56)
|
||||
{
|
||||
md5_transform(state, block);
|
||||
memset(block, 0, 64);
|
||||
}
|
||||
|
||||
uint64_t bitLen = (uint64_t)len * 8;
|
||||
for (int b = 0; b < 8; b++)
|
||||
block[56 + b] = (uint8_t)(bitLen >> (b * 8));
|
||||
|
||||
md5_transform(state, block);
|
||||
|
||||
for (int b = 0; b < 4; b++)
|
||||
{
|
||||
digest[b * 4 + 0] = (uint8_t)(state[b]);
|
||||
digest[b * 4 + 1] = (uint8_t)(state[b] >> 8);
|
||||
digest[b * 4 + 2] = (uint8_t)(state[b] >> 16);
|
||||
digest[b * 4 + 3] = (uint8_t)(state[b] >> 24);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GameUUID GameUUID::generateOffline(const std::string& playerName)
|
||||
{
|
||||
std::string input = "OfflinePlayer:" + playerName;
|
||||
|
||||
uint8_t digest[16];
|
||||
md5((const uint8_t*)input.c_str(), input.size(), digest);
|
||||
digest[6] = (digest[6] & 0x0F) | 0x30;
|
||||
digest[8] = (digest[8] & 0x3F) | 0x80;
|
||||
GameUUID uuid;
|
||||
uuid.hi = 0;
|
||||
for (int i = 0; i < 8; i++)
|
||||
uuid.hi = (uuid.hi << 8) | digest[i];
|
||||
uuid.lo = 0;
|
||||
for (int i = 8; i < 16; i++)
|
||||
uuid.lo = (uuid.lo << 8) | digest[i];
|
||||
|
||||
return uuid;
|
||||
}
|
||||
45
Minecraft.World/GameUUID.h
Normal file
45
Minecraft.World/GameUUID.h
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <istream>
|
||||
#include <functional>
|
||||
|
||||
struct GameUUID {
|
||||
uint64_t hi = 0;
|
||||
uint64_t lo = 0;
|
||||
bool isValid() const { return hi != 0 || lo != 0; }
|
||||
std::string toDashed() const;
|
||||
std::string toUndashed() const;
|
||||
std::wstring toWDashed() const;
|
||||
static GameUUID fromDashed(const std::string& s);
|
||||
static GameUUID fromUndashed(const std::string& s);
|
||||
static GameUUID generateV4();
|
||||
static GameUUID generateOffline(const std::string& playerName);
|
||||
bool operator==(const GameUUID& o) const { return hi == o.hi && lo == o.lo; }
|
||||
bool operator!=(const GameUUID& o) const { return !(*this == o); }
|
||||
bool operator<(const GameUUID& o) const { return hi < o.hi || (hi == o.hi && lo < o.lo); }
|
||||
|
||||
struct Hash {
|
||||
size_t operator()(const GameUUID& u) const {
|
||||
size_t h = std::hash<uint64_t>{}(u.hi);
|
||||
h ^= std::hash<uint64_t>{}(u.lo) + 0x9e3779b9 + (h << 6) + (h >> 2);
|
||||
return h;
|
||||
}
|
||||
};
|
||||
};
|
||||
inline const GameUUID INVALID_UUID = {};
|
||||
inline std::wistream& operator>>(std::wistream& is, GameUUID& uuid)
|
||||
{
|
||||
std::wstring ws;
|
||||
is >> ws;
|
||||
std::string s(ws.begin(), ws.end());
|
||||
uuid = GameUUID::fromDashed(s);
|
||||
return is;
|
||||
}
|
||||
namespace std {
|
||||
template<> struct hash<GameUUID> {
|
||||
size_t operator()(const GameUUID& u) const {
|
||||
return GameUUID::Hash{}(u);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -131,6 +131,10 @@ void Packet::staticCtor()
|
|||
map(166, true, true, false, false, typeid(XZPacket), XZPacket::create);
|
||||
map(167, false, true, false, false, typeid(GameCommandPacket), GameCommandPacket::create);
|
||||
|
||||
map(170, true, false, true, false, typeid(AuthSchemePacket), AuthSchemePacket::create); // Server -> Client
|
||||
map(171, false, true, false, false, typeid(AuthResponsePacket), AuthResponsePacket::create); // Client -> Server
|
||||
map(172, true, false, true, false, typeid(AuthResultPacket), AuthResultPacket::create); // Server -> Client
|
||||
|
||||
map(200, true, false, true, false, typeid(AwardStatPacket), AwardStatPacket::create);
|
||||
map(201, true, true, false, false, typeid(PlayerInfoPacket), PlayerInfoPacket::create); // TODO New for 1.8.2 - Repurposed by 4J
|
||||
map(202, true, true, true, false, typeid(PlayerAbilitiesPacket), PlayerAbilitiesPacket::create);
|
||||
|
|
|
|||
|
|
@ -491,3 +491,18 @@ void PacketListener::handleGameCommand(shared_ptr<GameCommandPacket> packet)
|
|||
{
|
||||
onUnhandledPacket( (shared_ptr<Packet> ) packet);
|
||||
}
|
||||
|
||||
void PacketListener::handleAuthScheme(shared_ptr<AuthSchemePacket> packet)
|
||||
{
|
||||
onUnhandledPacket( (shared_ptr<Packet> ) packet);
|
||||
}
|
||||
|
||||
void PacketListener::handleAuthResponse(shared_ptr<AuthResponsePacket> packet)
|
||||
{
|
||||
onUnhandledPacket( (shared_ptr<Packet> ) packet);
|
||||
}
|
||||
|
||||
void PacketListener::handleAuthResult(shared_ptr<AuthResultPacket> packet)
|
||||
{
|
||||
onUnhandledPacket( (shared_ptr<Packet> ) packet);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,6 +111,9 @@ class KickPlayerPacket;
|
|||
class AdditionalModelPartsPacket;
|
||||
class XZPacket;
|
||||
class GameCommandPacket;
|
||||
class AuthSchemePacket;
|
||||
class AuthResponsePacket;
|
||||
class AuthResultPacket;
|
||||
|
||||
class PacketListener
|
||||
{
|
||||
|
|
@ -227,4 +230,7 @@ public:
|
|||
virtual void handleKickPlayer(shared_ptr<KickPlayerPacket> packet);
|
||||
virtual void handleXZ(shared_ptr<XZPacket> packet);
|
||||
virtual void handleGameCommand(shared_ptr<GameCommandPacket> packet);
|
||||
virtual void handleAuthScheme(shared_ptr<AuthSchemePacket> packet);
|
||||
virtual void handleAuthResponse(shared_ptr<AuthResponsePacket> packet);
|
||||
virtual void handleAuthResult(shared_ptr<AuthResultPacket> packet);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -151,6 +151,8 @@ set(_MINECRAFT_WORLD_COMMON_NET_MINECRAFT
|
|||
"${CMAKE_CURRENT_SOURCE_DIR}/Direction.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Facing.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Facing.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/GameUUID.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/GameUUID.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Pos.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Pos.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/SharedConstants.cpp"
|
||||
|
|
@ -259,6 +261,12 @@ 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}/AuthResponsePacket.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/AuthResponsePacket.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/AuthResultPacket.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/AuthResultPacket.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/AuthSchemePacket.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/AuthSchemePacket.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/AwardStatPacket.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/AwardStatPacket.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/BlockRegionUpdatePacket.cpp"
|
||||
|
|
|
|||
|
|
@ -108,4 +108,7 @@
|
|||
#include "KickPlayerPacket.h"
|
||||
#include "XZPacket.h"
|
||||
#include "GameCommandPacket.h"
|
||||
#include "AuthSchemePacket.h"
|
||||
#include "AuthResponsePacket.h"
|
||||
#include "AuthResultPacket.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -132,6 +132,10 @@ void XMemSet(void *a, int t, size_t s);
|
|||
void XMemSet128(void *a, int t, size_t s);
|
||||
void *XPhysicalAlloc(SIZE_T a, ULONG_PTR b, ULONG_PTR c, DWORD d);
|
||||
void XPhysicalFree(void *a);
|
||||
class IQNetPlayer;
|
||||
void Win64_SetJoinedDedicatedServer(bool isDedicated);
|
||||
bool Win64_IsJoinedDedicatedServer();
|
||||
bool Win64_ShouldExposeSessionPlayer(IQNetPlayer *player);
|
||||
|
||||
class DLCManager;
|
||||
|
||||
|
|
|
|||
61
newauth/CMakeLists.txt
Normal file
61
newauth/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
sqlite3
|
||||
URL "https://www.sqlite.org/2024/sqlite-amalgamation-3470200.zip"
|
||||
URL_HASH SHA256=aa73d8748095808471deaa8e6f34aa700e37f2f787f4425744f53fdd15a89c40
|
||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||
)
|
||||
FetchContent_MakeAvailable(sqlite3)
|
||||
|
||||
# build sqlite3 as a static library
|
||||
add_library(sqlite3_lib STATIC "${sqlite3_SOURCE_DIR}/sqlite3.c")
|
||||
target_include_directories(sqlite3_lib PUBLIC "${sqlite3_SOURCE_DIR}")
|
||||
target_compile_definitions(sqlite3_lib PRIVATE SQLITE_THREADSAFE=1)
|
||||
if(MSVC)
|
||||
target_compile_options(sqlite3_lib PRIVATE /W0)
|
||||
endif()
|
||||
configure_compiler_target(sqlite3_lib)
|
||||
|
||||
set(NEWAUTH_SOURCES
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/newauthCrypto.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/newauthCrypto.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/newauthHttp.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/newauthHttp.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/newauthInternal.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/newauthJava.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/newauthManager.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/newauthElyby.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/newauthSession.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/newauthDb.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/newauthDb.h"
|
||||
)
|
||||
source_group("src" FILES ${NEWAUTH_SOURCES})
|
||||
|
||||
set(NEWAUTH_HEADERS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/include/newauth.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/include/newauthManager.h"
|
||||
)
|
||||
source_group("include" FILES ${NEWAUTH_HEADERS})
|
||||
|
||||
add_library(newauth STATIC ${NEWAUTH_SOURCES} ${NEWAUTH_HEADERS})
|
||||
|
||||
target_include_directories(newauth
|
||||
PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/include"
|
||||
PRIVATE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src"
|
||||
)
|
||||
|
||||
target_compile_definitions(newauth PRIVATE
|
||||
_LIB
|
||||
$<$<CONFIG:Debug>:_DEBUG>
|
||||
_CRT_SECURE_NO_WARNINGS
|
||||
)
|
||||
|
||||
configure_compiler_target(newauth)
|
||||
|
||||
target_link_libraries(newauth PRIVATE
|
||||
winhttp
|
||||
bcrypt
|
||||
sqlite3_lib
|
||||
)
|
||||
163
newauth/include/newauth.h
Normal file
163
newauth/include/newauth.h
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace newauth {
|
||||
|
||||
enum class AuthErrorCode {
|
||||
None = 0,
|
||||
NetworkTimeout,
|
||||
NetworkError,
|
||||
HttpError,
|
||||
InvalidCredentials,
|
||||
TokenExpired,
|
||||
ServerUnavailable,
|
||||
RateLimited,
|
||||
ProfileNotOwned,
|
||||
Cancelled,
|
||||
InternalError,
|
||||
};
|
||||
|
||||
struct AuthError {
|
||||
AuthErrorCode code = AuthErrorCode::None;
|
||||
std::string message;
|
||||
int httpStatus = 0;
|
||||
|
||||
bool ok() const { return code == AuthErrorCode::None; }
|
||||
explicit operator bool() const { return !ok(); }
|
||||
};
|
||||
|
||||
static constexpr const char* JAVA_CLIENT_ID = "00000000402b5328";
|
||||
|
||||
static constexpr const char* MSA_SCOPE = "service::user.auth.xboxlive.com::MBI_SSL";
|
||||
|
||||
struct DeviceCodeInfo {
|
||||
std::string userCode;
|
||||
std::string verificationUri;
|
||||
std::string directUri;
|
||||
std::string deviceCode;
|
||||
int64_t expiresMs;
|
||||
int64_t intervalMs;
|
||||
};
|
||||
|
||||
using DeviceCodeCallback = std::function<void(const DeviceCodeInfo&)>;
|
||||
|
||||
struct JavaSession {
|
||||
std::string username;
|
||||
std::string uuid;
|
||||
std::string accessToken;
|
||||
int64_t expireMs;
|
||||
};
|
||||
|
||||
class JavaAuthManager {
|
||||
public:
|
||||
JavaAuthManager();
|
||||
~JavaAuthManager();
|
||||
|
||||
JavaAuthManager(const JavaAuthManager&) = delete;
|
||||
JavaAuthManager& operator=(const JavaAuthManager&) = delete;
|
||||
|
||||
bool Login(DeviceCodeCallback onDeviceCode,
|
||||
JavaSession& outSession,
|
||||
std::string& error,
|
||||
int timeoutSeconds = 300);
|
||||
|
||||
bool Refresh(JavaSession& outSession, std::string& error);
|
||||
|
||||
bool IsLoggedIn() const;
|
||||
void Logout();
|
||||
|
||||
void RequestCancel();
|
||||
|
||||
bool SaveTokens(const std::string& filePath) const;
|
||||
bool LoadTokens(const std::string& filePath);
|
||||
|
||||
std::string SerializeTokens() const;
|
||||
bool DeserializeTokens(const std::string& json);
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
};
|
||||
|
||||
struct ElybyTokens {
|
||||
std::string accessToken;
|
||||
std::string clientToken;
|
||||
std::string uuid;
|
||||
std::string username;
|
||||
};
|
||||
|
||||
bool ElybyLogin(const std::string& username, const std::string& password,
|
||||
ElybyTokens& outTokens, std::string& error);
|
||||
|
||||
bool ElybyRefresh(ElybyTokens& tokens, std::string& error);
|
||||
|
||||
bool ElybyValidate(const std::string& accessToken, std::string& error);
|
||||
|
||||
bool ElybyLoadTokens(const std::string& path, ElybyTokens& out);
|
||||
bool ElybySaveTokens(const std::string& path, const ElybyTokens& tokens);
|
||||
|
||||
std::string ElybySerializeTokens(const ElybyTokens& tokens);
|
||||
bool ElybyDeserializeTokens(const std::string& json, ElybyTokens& out);
|
||||
|
||||
std::string MakeSkinKey(const std::string& uuid);
|
||||
|
||||
std::string UndashUuid(const std::string& dashed);
|
||||
|
||||
std::string DashUuid(const std::string& undashed);
|
||||
|
||||
std::string GenerateOfflineUuid(const std::string& username);
|
||||
|
||||
struct Uuid128 {
|
||||
uint64_t hi = 0;
|
||||
uint64_t lo = 0;
|
||||
bool isValid() const { return hi != 0 || lo != 0; }
|
||||
};
|
||||
Uuid128 ParseUuid128(const std::string& dashed);
|
||||
|
||||
bool JoinServer(const std::string& accessToken,
|
||||
const std::string& undashedUuid,
|
||||
const std::string& serverId,
|
||||
std::string& error);
|
||||
|
||||
struct HasJoinedResult {
|
||||
bool success = false;
|
||||
AuthError error;
|
||||
std::string username;
|
||||
std::string uuid;
|
||||
std::string skinUrl;
|
||||
std::string capeUrl;
|
||||
};
|
||||
|
||||
HasJoinedResult HasJoined(const std::string& username,
|
||||
const std::string& serverId,
|
||||
std::string& error);
|
||||
|
||||
std::vector<uint8_t> FetchSkinPng(const std::string& url, std::string& error);
|
||||
|
||||
std::string FetchProfileSkinUrl(const std::string& uuid, std::string& error);
|
||||
|
||||
std::vector<uint8_t> FetchSkinPngRaw(const std::string& url, std::string& error);
|
||||
|
||||
bool ElybyJoinServer(const std::string& accessToken,
|
||||
const std::string& undashedUuid,
|
||||
const std::string& serverId,
|
||||
std::string& error);
|
||||
|
||||
HasJoinedResult ElybyHasJoined(const std::string& username,
|
||||
const std::string& serverId,
|
||||
std::string& error);
|
||||
|
||||
std::string ElybyFetchProfileSkinUrl(const std::string& uuid, std::string& error);
|
||||
|
||||
std::string MakeElybySkinKey(const std::string& uuid);
|
||||
|
||||
static constexpr size_t kMaxSkinBytes = 32768;
|
||||
|
||||
bool ValidateSkinPng(const uint8_t* data, size_t size);
|
||||
|
||||
}
|
||||
136
newauth/include/newauthManager.h
Normal file
136
newauth/include/newauthManager.h
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
#pragma once
|
||||
|
||||
#include "newauth.h"
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <condition_variable>
|
||||
|
||||
#ifndef XUSER_MAX_COUNT
|
||||
#define XUSER_MAX_COUNT 4
|
||||
#endif
|
||||
|
||||
class newauthManager {
|
||||
public:
|
||||
enum class State {
|
||||
Idle,
|
||||
WaitingForCode,
|
||||
Authenticating,
|
||||
Success,
|
||||
Failed,
|
||||
};
|
||||
|
||||
struct JavaAccountInfo {
|
||||
std::string username;
|
||||
std::string uuid;
|
||||
std::string tokenFile;
|
||||
bool isOffline = false;
|
||||
std::string authProvider;
|
||||
};
|
||||
|
||||
struct AuthSlot {
|
||||
std::shared_ptr<newauth::JavaAuthManager> auth;
|
||||
|
||||
newauth::ElybyTokens elybyTokens;
|
||||
newauth::JavaSession session;
|
||||
std::atomic<int> accountIndex{-1};
|
||||
mutable std::mutex mutex;
|
||||
std::atomic<uint32_t> generation{0};
|
||||
std::atomic<State> state{State::Idle};
|
||||
mutable std::condition_variable cv;
|
||||
std::string lastError;
|
||||
|
||||
bool hasSession() const { return !session.uuid.empty(); }
|
||||
|
||||
AuthSlot() : auth(std::make_shared<newauth::JavaAuthManager>()) {}
|
||||
};
|
||||
|
||||
using DeviceCodeCb = newauth::DeviceCodeCallback;
|
||||
using JavaCompleteCb = std::function<void(bool, const newauth::JavaSession&, const std::string&)>;
|
||||
|
||||
static newauthManager& Get();
|
||||
|
||||
newauthManager(const newauthManager&) = delete;
|
||||
newauthManager& operator=(const newauthManager&) = delete;
|
||||
|
||||
bool LoadJavaAccountIndex();
|
||||
bool SaveJavaAccountIndex() const;
|
||||
|
||||
std::vector<JavaAccountInfo> GetJavaAccounts() const;
|
||||
|
||||
const AuthSlot& GetSlot(int slot) const;
|
||||
|
||||
bool SetAccountForSlot(int slot, int accountIndex);
|
||||
|
||||
void ClearSlot(int slot);
|
||||
|
||||
newauth::JavaSession GetSlotSession(int slot) const;
|
||||
|
||||
bool IsSlotLoggedIn(int slot) const;
|
||||
|
||||
bool IsTokenExpiringSoon(int slot) const;
|
||||
|
||||
void RefreshSlot(int slot);
|
||||
|
||||
bool WaitForSlotReady(int slot, int timeoutMs = 15000) const;
|
||||
|
||||
bool IsAccountInUseByOtherSlot(int slot, int accountIndex) const;
|
||||
|
||||
int GetActiveJavaAccountIndex() const;
|
||||
bool SetActiveJavaAccount(int index) { return SetAccountForSlot(0, index); }
|
||||
newauth::JavaSession GetJavaSession() const;
|
||||
bool IsJavaLoggedIn() const;
|
||||
State GetState() const { return m_slots[0].state.load(); }
|
||||
State GetSlotState(int slot) const {
|
||||
if (slot < 0 || slot >= XUSER_MAX_COUNT) slot = 0;
|
||||
return m_slots[slot].state.load();
|
||||
}
|
||||
bool WaitForAuthReady(int timeoutMs = 15000) const { return WaitForSlotReady(0, timeoutMs); }
|
||||
|
||||
void BeginAddJavaAccount(DeviceCodeCb onDeviceCode, JavaCompleteCb onComplete,
|
||||
int timeoutSeconds = 300);
|
||||
|
||||
using ElybyCompleteCb = std::function<void(bool ok, const newauth::JavaSession& session, const std::string& error)>;
|
||||
using Elyby2FACb = std::function<void()>;
|
||||
void BeginAddElybyAccount(const std::string& username, const std::string& password,
|
||||
ElybyCompleteCb onComplete, Elyby2FACb on2FA = nullptr);
|
||||
|
||||
int AddOfflineJavaAccount(const std::string& username);
|
||||
|
||||
bool RemoveJavaAccount(int index);
|
||||
|
||||
void TryRestoreActiveJavaAccount();
|
||||
|
||||
std::string GetJavaDeviceCode() const;
|
||||
std::string GetJavaDirectUri() const;
|
||||
std::string GetLastError() const;
|
||||
|
||||
static constexpr const char* kJavaAccountsFile = "java_accounts.json";
|
||||
|
||||
private:
|
||||
newauthManager();
|
||||
~newauthManager();
|
||||
|
||||
std::string AllocTokenFile(const std::string& uuid) const;
|
||||
|
||||
static newauth::JavaSession SynthesizeOfflineSession(const JavaAccountInfo& acct);
|
||||
|
||||
std::shared_ptr<newauth::JavaAuthManager> ResetSlotAuth(AuthSlot& s);
|
||||
|
||||
void RunElybyRefresh(int slot, uint32_t gen, const std::string& tokenFile,
|
||||
bool failOpen, bool alwaysSaveIndex);
|
||||
|
||||
AuthSlot m_slots[XUSER_MAX_COUNT];
|
||||
|
||||
mutable std::mutex m_accountsMutex;
|
||||
std::vector<JavaAccountInfo> m_javaAccounts;
|
||||
|
||||
mutable std::mutex m_deviceCodeMutex;
|
||||
std::string m_javaDeviceCode;
|
||||
std::string m_javaDirectUri;
|
||||
|
||||
bool m_javaRestoreAttempted = false;
|
||||
};
|
||||
156
newauth/newauth.vcxproj
Normal file
156
newauth/newauth.vcxproj
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>newauth</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<OutDir>$(SolutionDir)bin\$(Configuration)\$(Platform)\</OutDir>
|
||||
<IntDir>$(SolutionDir)obj\$(ProjectName)\$(Configuration)\$(Platform)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<OutDir>$(SolutionDir)bin\$(Configuration)\$(Platform)\</OutDir>
|
||||
<IntDir>$(SolutionDir)obj\$(ProjectName)\$(Configuration)\$(Platform)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<OutDir>$(SolutionDir)bin\$(Configuration)\$(Platform)\</OutDir>
|
||||
<IntDir>$(SolutionDir)obj\$(ProjectName)\$(Configuration)\$(Platform)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<OutDir>$(SolutionDir)bin\$(Configuration)\$(Platform)\</OutDir>
|
||||
<IntDir>$(SolutionDir)obj\$(ProjectName)\$(Configuration)\$(Platform)\</IntDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Lib>
|
||||
<OutputFile>$(OutDir)newauth.lib</OutputFile>
|
||||
</Lib>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Lib>
|
||||
<OutputFile>$(OutDir)newauth.lib</OutputFile>
|
||||
</Lib>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Lib>
|
||||
<OutputFile>$(OutDir)newauth.lib</OutputFile>
|
||||
</Lib>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Lib>
|
||||
<OutputFile>$(OutDir)newauth.lib</OutputFile>
|
||||
</Lib>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ClInclude Include="include\newauth.h" />
|
||||
<ClInclude Include="include\newauthManager.h" />
|
||||
<ClInclude Include="src\newauthCrypto.h" />
|
||||
<ClInclude Include="src\newauthHttp.h" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ClCompile Include="src\newauthCrypto.cpp" />
|
||||
<ClCompile Include="src\newauthHttp.cpp" />
|
||||
<ClCompile Include="src\newauth.cpp" />
|
||||
<ClCompile Include="src\newauthJava.cpp" />
|
||||
<ClCompile Include="src\newauthManager.cpp" />
|
||||
<ClCompile Include="src\newauthSession.cpp" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
</Project>
|
||||
347
newauth/src/newauthCrypto.cpp
Normal file
347
newauth/src/newauthCrypto.cpp
Normal file
|
|
@ -0,0 +1,347 @@
|
|||
#include "newauthCrypto.h"
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <bcrypt.h>
|
||||
#pragma comment(lib, "bcrypt.lib")
|
||||
|
||||
#include <stdexcept>
|
||||
#include <chrono>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
|
||||
namespace newauth {
|
||||
|
||||
static void ThrowIfFailed(NTSTATUS status, const char* ctx) {
|
||||
if (!BCRYPT_SUCCESS(status)) {
|
||||
char buf[128];
|
||||
snprintf(buf, sizeof(buf), "BCrypt error 0x%08X in %s", (unsigned)status, ctx);
|
||||
throw std::runtime_error(buf);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> BuildSubjectPublicKeyInfoDER(
|
||||
int bitSize,
|
||||
const std::vector<uint8_t>& x,
|
||||
const std::vector<uint8_t>& y)
|
||||
{
|
||||
static const uint8_t OID_EC_PUBLIC_KEY[] = { 0x2a,0x86,0x48,0xce,0x3d,0x02,0x01 };
|
||||
static const uint8_t OID_P256[] = { 0x2a,0x86,0x48,0xce,0x3d,0x03,0x01,0x07 };
|
||||
static const uint8_t OID_P384[] = { 0x2b,0x81,0x04,0x00,0x22 };
|
||||
|
||||
size_t coordSize = (bitSize == 256) ? 32 : 48;
|
||||
const uint8_t* curveOid = (bitSize == 256) ? OID_P256 : OID_P384;
|
||||
size_t curveOidLen = (bitSize == 256) ? 8 : 5;
|
||||
|
||||
size_t algIdInnerLen = 2 + sizeof(OID_EC_PUBLIC_KEY) + 2 + curveOidLen;
|
||||
|
||||
size_t bitStringContent = 1 + 1 + coordSize + coordSize;
|
||||
size_t bitStringLen = bitStringContent;
|
||||
|
||||
size_t outerContent = (2 + algIdInnerLen) + (2 + bitStringLen);
|
||||
|
||||
std::vector<uint8_t> der;
|
||||
auto appendLen = [&](size_t len) {
|
||||
if (len < 128) {
|
||||
der.push_back((uint8_t)len);
|
||||
} else {
|
||||
der.push_back(0x82);
|
||||
der.push_back((uint8_t)(len >> 8));
|
||||
der.push_back((uint8_t)(len & 0xFF));
|
||||
}
|
||||
};
|
||||
|
||||
der.push_back(0x30);
|
||||
appendLen(outerContent);
|
||||
|
||||
der.push_back(0x30);
|
||||
appendLen(algIdInnerLen);
|
||||
der.push_back(0x06); der.push_back((uint8_t)sizeof(OID_EC_PUBLIC_KEY));
|
||||
der.insert(der.end(), OID_EC_PUBLIC_KEY, OID_EC_PUBLIC_KEY + sizeof(OID_EC_PUBLIC_KEY));
|
||||
der.push_back(0x06); der.push_back((uint8_t)curveOidLen);
|
||||
der.insert(der.end(), curveOid, curveOid + curveOidLen);
|
||||
|
||||
der.push_back(0x03);
|
||||
appendLen(bitStringLen + 1);
|
||||
der.push_back(0x00);
|
||||
der.push_back(0x04);
|
||||
der.insert(der.end(), x.begin(), x.end());
|
||||
der.insert(der.end(), y.begin(), y.end());
|
||||
|
||||
return der;
|
||||
}
|
||||
|
||||
static ECKeyPair GenerateKeyPair(int bitSize) {
|
||||
LPCWSTR algId = (bitSize == 256) ? BCRYPT_ECDSA_P256_ALGORITHM
|
||||
: BCRYPT_ECDSA_P384_ALGORITHM;
|
||||
|
||||
BCRYPT_ALG_HANDLE hAlg = nullptr;
|
||||
BCRYPT_KEY_HANDLE hKey = nullptr;
|
||||
|
||||
try {
|
||||
ThrowIfFailed(BCryptOpenAlgorithmProvider(&hAlg, algId, nullptr, 0), "OpenAlgProvider");
|
||||
|
||||
ThrowIfFailed(BCryptGenerateKeyPair(hAlg, &hKey, bitSize, 0), "GenerateKeyPair");
|
||||
ThrowIfFailed(BCryptFinalizeKeyPair(hKey, 0), "FinalizeKeyPair");
|
||||
|
||||
ULONG privBlobLen = 0;
|
||||
ThrowIfFailed(BCryptExportKey(hKey, nullptr, BCRYPT_ECCPRIVATE_BLOB, nullptr, 0, &privBlobLen, 0), "ExportPriv(len)");
|
||||
std::vector<uint8_t> privBlob(privBlobLen);
|
||||
ThrowIfFailed(BCryptExportKey(hKey, nullptr, BCRYPT_ECCPRIVATE_BLOB, privBlob.data(), privBlobLen, &privBlobLen, 0), "ExportPriv");
|
||||
|
||||
ULONG pubBlobLen = 0;
|
||||
ThrowIfFailed(BCryptExportKey(hKey, nullptr, BCRYPT_ECCPUBLIC_BLOB, nullptr, 0, &pubBlobLen, 0), "ExportPub(len)");
|
||||
std::vector<uint8_t> pubBlob(pubBlobLen);
|
||||
ThrowIfFailed(BCryptExportKey(hKey, nullptr, BCRYPT_ECCPUBLIC_BLOB, pubBlob.data(), pubBlobLen, &pubBlobLen, 0), "ExportPub");
|
||||
|
||||
BCryptDestroyKey(hKey);
|
||||
BCryptCloseAlgorithmProvider(hAlg, 0);
|
||||
|
||||
const BCRYPT_ECCKEY_BLOB* hdr = reinterpret_cast<const BCRYPT_ECCKEY_BLOB*>(pubBlob.data());
|
||||
ULONG cbKey = hdr->cbKey;
|
||||
|
||||
std::vector<uint8_t> x(pubBlob.begin() + 8, pubBlob.begin() + 8 + cbKey);
|
||||
std::vector<uint8_t> y(pubBlob.begin() + 8 + cbKey, pubBlob.begin() + 8 + cbKey * 2);
|
||||
|
||||
ECKeyPair kp;
|
||||
kp.bitSize = bitSize;
|
||||
kp.privateBlob = std::move(privBlob);
|
||||
kp.publicBlob = std::move(pubBlob);
|
||||
kp.x = x;
|
||||
kp.y = y;
|
||||
kp.publicKeyDER = BuildSubjectPublicKeyInfoDER(bitSize, x, y);
|
||||
return kp;
|
||||
|
||||
} catch (...) {
|
||||
if (hKey) BCryptDestroyKey(hKey);
|
||||
if (hAlg) BCryptCloseAlgorithmProvider(hAlg, 0);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
ECKeyPair GenerateP256KeyPair() { return GenerateKeyPair(256); }
|
||||
ECKeyPair GenerateP384KeyPair() { return GenerateKeyPair(384); }
|
||||
|
||||
std::vector<uint8_t> SignSHA256P256(const ECKeyPair& kp,
|
||||
const uint8_t* data,
|
||||
size_t len)
|
||||
{
|
||||
if (kp.bitSize != 256)
|
||||
throw std::runtime_error("SignSHA256P256: key is not P-256");
|
||||
|
||||
BCRYPT_ALG_HANDLE hAlg = nullptr;
|
||||
BCRYPT_KEY_HANDLE hKey = nullptr;
|
||||
BCRYPT_ALG_HANDLE hHashAlg = nullptr;
|
||||
BCRYPT_HASH_HANDLE hHash = nullptr;
|
||||
|
||||
try {
|
||||
ThrowIfFailed(BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_ECDSA_P256_ALGORITHM, nullptr, 0), "Sign/OpenAlg");
|
||||
|
||||
ThrowIfFailed(BCryptImportKeyPair(hAlg, nullptr, BCRYPT_ECCPRIVATE_BLOB,
|
||||
&hKey,
|
||||
const_cast<uint8_t*>(kp.privateBlob.data()),
|
||||
(ULONG)kp.privateBlob.size(), 0), "Sign/ImportKey");
|
||||
|
||||
ThrowIfFailed(BCryptOpenAlgorithmProvider(&hHashAlg, BCRYPT_SHA256_ALGORITHM, nullptr, 0), "Sign/OpenHashAlg");
|
||||
|
||||
ThrowIfFailed(BCryptCreateHash(hHashAlg, &hHash, nullptr, 0, nullptr, 0, 0), "Sign/CreateHash");
|
||||
ThrowIfFailed(BCryptHashData(hHash, const_cast<uint8_t*>(data), (ULONG)len, 0), "Sign/HashData");
|
||||
|
||||
uint8_t digest[32];
|
||||
ThrowIfFailed(BCryptFinishHash(hHash, digest, sizeof(digest), 0), "Sign/FinishHash");
|
||||
BCryptDestroyHash(hHash); hHash = nullptr;
|
||||
BCryptCloseAlgorithmProvider(hHashAlg, 0); hHashAlg = nullptr;
|
||||
|
||||
ULONG sigLen = 0;
|
||||
ThrowIfFailed(BCryptSignHash(hKey, nullptr, digest, sizeof(digest), nullptr, 0, &sigLen, 0), "Sign/GetSigLen");
|
||||
std::vector<uint8_t> sig(sigLen);
|
||||
ThrowIfFailed(BCryptSignHash(hKey, nullptr, digest, sizeof(digest), sig.data(), sigLen, &sigLen, 0), "Sign/SignHash");
|
||||
|
||||
BCryptDestroyKey(hKey);
|
||||
BCryptCloseAlgorithmProvider(hAlg, 0);
|
||||
|
||||
sig.resize(sigLen);
|
||||
return sig;
|
||||
|
||||
} catch (...) {
|
||||
if (hHash) BCryptDestroyHash(hHash);
|
||||
if (hHashAlg) BCryptCloseAlgorithmProvider(hHashAlg, 0);
|
||||
if (hKey) BCryptDestroyKey(hKey);
|
||||
if (hAlg) BCryptCloseAlgorithmProvider(hAlg, 0);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
static const char kB64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
static const char kB64UrlChars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
||||
|
||||
static std::string Base64EncodeInternal(const uint8_t* data, size_t len, const char* alpha, bool pad) {
|
||||
std::string out;
|
||||
out.reserve(((len + 2) / 3) * 4);
|
||||
for (size_t i = 0; i < len; i += 3) {
|
||||
uint32_t v = (uint32_t)data[i] << 16;
|
||||
if (i + 1 < len) v |= (uint32_t)data[i + 1] << 8;
|
||||
if (i + 2 < len) v |= (uint32_t)data[i + 2];
|
||||
|
||||
out += alpha[(v >> 18) & 0x3F];
|
||||
out += alpha[(v >> 12) & 0x3F];
|
||||
out += (i + 1 < len) ? alpha[(v >> 6) & 0x3F] : (pad ? '=' : '\0');
|
||||
out += (i + 2 < len) ? alpha[(v >> 0) & 0x3F] : (pad ? '=' : '\0');
|
||||
}
|
||||
while (!out.empty() && out.back() == '\0') out.pop_back();
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string Base64Encode(const uint8_t* data, size_t len) {
|
||||
return Base64EncodeInternal(data, len, kB64Chars, true);
|
||||
}
|
||||
std::string Base64Encode(const std::vector<uint8_t>& data) {
|
||||
return Base64Encode(data.data(), data.size());
|
||||
}
|
||||
std::string Base64UrlEncode(const uint8_t* data, size_t len) {
|
||||
return Base64EncodeInternal(data, len, kB64UrlChars, false);
|
||||
}
|
||||
std::string Base64UrlEncode(const std::vector<uint8_t>& data) {
|
||||
return Base64UrlEncode(data.data(), data.size());
|
||||
}
|
||||
|
||||
std::string BuildProofKeyJson(const ECKeyPair& p256) {
|
||||
std::string xEnc = Base64UrlEncode(p256.x);
|
||||
std::string yEnc = Base64UrlEncode(p256.y);
|
||||
return "{\"kty\":\"EC\",\"alg\":\"ES256\",\"crv\":\"P-256\",\"use\":\"sig\","
|
||||
"\"x\":\"" + xEnc + "\",\"y\":\"" + yEnc + "\"}";
|
||||
}
|
||||
|
||||
static void AppendBE32(std::vector<uint8_t>& buf, uint32_t v) {
|
||||
buf.push_back((uint8_t)(v >> 24));
|
||||
buf.push_back((uint8_t)(v >> 16));
|
||||
buf.push_back((uint8_t)(v >> 8));
|
||||
buf.push_back((uint8_t)(v ));
|
||||
}
|
||||
static void AppendBE64(std::vector<uint8_t>& buf, uint64_t v) {
|
||||
buf.push_back((uint8_t)(v >> 56));
|
||||
buf.push_back((uint8_t)(v >> 48));
|
||||
buf.push_back((uint8_t)(v >> 40));
|
||||
buf.push_back((uint8_t)(v >> 32));
|
||||
buf.push_back((uint8_t)(v >> 24));
|
||||
buf.push_back((uint8_t)(v >> 16));
|
||||
buf.push_back((uint8_t)(v >> 8));
|
||||
buf.push_back((uint8_t)(v ));
|
||||
}
|
||||
|
||||
std::string BuildXblSignatureHeader(const ECKeyPair& p256,
|
||||
const std::string& method,
|
||||
const std::string& urlPath,
|
||||
const std::string& body,
|
||||
const std::string& authHdr)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
int64_t epochSec = duration_cast<seconds>(
|
||||
system_clock::now().time_since_epoch()).count();
|
||||
uint64_t winTs = (uint64_t)(epochSec + 11644473600LL) * 10000000ULL;
|
||||
|
||||
std::vector<uint8_t> content;
|
||||
content.reserve(512);
|
||||
|
||||
AppendBE32(content, 1);
|
||||
content.push_back(0x00);
|
||||
AppendBE64(content, winTs);
|
||||
content.push_back(0x00);
|
||||
content.insert(content.end(), method.begin(), method.end());
|
||||
content.push_back(0x00);
|
||||
content.insert(content.end(), urlPath.begin(), urlPath.end());
|
||||
content.push_back(0x00);
|
||||
content.insert(content.end(), authHdr.begin(), authHdr.end());
|
||||
content.push_back(0x00);
|
||||
content.insert(content.end(), body.begin(), body.end());
|
||||
content.push_back(0x00);
|
||||
|
||||
std::vector<uint8_t> sig = SignSHA256P256(p256, content.data(), content.size());
|
||||
|
||||
std::vector<uint8_t> hdrBlob;
|
||||
hdrBlob.reserve(4 + 8 + sig.size());
|
||||
AppendBE32(hdrBlob, 1);
|
||||
AppendBE64(hdrBlob, winTs);
|
||||
hdrBlob.insert(hdrBlob.end(), sig.begin(), sig.end());
|
||||
|
||||
return Base64Encode(hdrBlob);
|
||||
}
|
||||
|
||||
std::string GenerateUUID() {
|
||||
uint8_t bytes[16];
|
||||
NTSTATUS st = BCryptGenRandom(nullptr, bytes, sizeof(bytes), BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
||||
if (!BCRYPT_SUCCESS(st)) {
|
||||
for (auto& b : bytes) b = (uint8_t)(rand() & 0xFF);
|
||||
}
|
||||
bytes[6] = (bytes[6] & 0x0F) | 0x40;
|
||||
bytes[8] = (bytes[8] & 0x3F) | 0x80;
|
||||
|
||||
char buf[37];
|
||||
snprintf(buf, sizeof(buf),
|
||||
"%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
|
||||
bytes[0], bytes[1], bytes[2], bytes[3],
|
||||
bytes[4], bytes[5],
|
||||
bytes[6], bytes[7],
|
||||
bytes[8], bytes[9],
|
||||
bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15]);
|
||||
return buf;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Base64Decode(const std::string& s) {
|
||||
static const int kInv[256] = {
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,
|
||||
52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,
|
||||
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
|
||||
15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,
|
||||
-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
|
||||
41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1,
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
||||
};
|
||||
std::vector<uint8_t> out;
|
||||
int buf = 0, bits = 0;
|
||||
for (unsigned char c : s) {
|
||||
if (c == '=' || kInv[c] < 0) continue;
|
||||
buf = (buf << 6) | kInv[c];
|
||||
bits += 6;
|
||||
if (bits >= 8) {
|
||||
bits -= 8;
|
||||
out.push_back((uint8_t)(buf >> bits));
|
||||
buf &= (1 << bits) - 1;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string Base64DecodeStr(const std::string& encoded) {
|
||||
auto bytes = Base64Decode(encoded);
|
||||
return std::string(bytes.begin(), bytes.end());
|
||||
}
|
||||
|
||||
std::vector<uint8_t> ComputeMD5(const void* data, size_t len) {
|
||||
std::vector<uint8_t> result(16, 0);
|
||||
BCRYPT_ALG_HANDLE hAlg = nullptr;
|
||||
BCRYPT_HASH_HANDLE hHash = nullptr;
|
||||
|
||||
if (BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_MD5_ALGORITHM, nullptr, 0) == 0) {
|
||||
if (BCryptCreateHash(hAlg, &hHash, nullptr, 0, nullptr, 0, 0) == 0) {
|
||||
if (BCryptHashData(hHash, (PUCHAR)data, (ULONG)len, 0) == 0) {
|
||||
BCryptFinishHash(hHash, result.data(), 16, 0);
|
||||
}
|
||||
BCryptDestroyHash(hHash);
|
||||
}
|
||||
BCryptCloseAlgorithmProvider(hAlg, 0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
50
newauth/src/newauthCrypto.h
Normal file
50
newauth/src/newauthCrypto.h
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
namespace newauth {
|
||||
|
||||
struct ECKeyPair {
|
||||
int bitSize;
|
||||
std::vector<uint8_t> privateBlob;
|
||||
std::vector<uint8_t> publicBlob;
|
||||
std::vector<uint8_t> x;
|
||||
std::vector<uint8_t> y;
|
||||
std::vector<uint8_t> publicKeyDER;
|
||||
};
|
||||
|
||||
ECKeyPair GenerateP256KeyPair();
|
||||
ECKeyPair GenerateP384KeyPair();
|
||||
|
||||
std::vector<uint8_t> SignSHA256P256(const ECKeyPair& kp,
|
||||
const uint8_t* data,
|
||||
size_t len);
|
||||
|
||||
std::string Base64Encode(const uint8_t* data, size_t len);
|
||||
std::string Base64Encode(const std::vector<uint8_t>& data);
|
||||
|
||||
std::string Base64UrlEncode(const uint8_t* data, size_t len);
|
||||
std::string Base64UrlEncode(const std::vector<uint8_t>& data);
|
||||
|
||||
std::string BuildProofKeyJson(const ECKeyPair& p256);
|
||||
|
||||
std::string BuildXblSignatureHeader(const ECKeyPair& p256,
|
||||
const std::string& method,
|
||||
const std::string& urlPath,
|
||||
const std::string& body,
|
||||
const std::string& authHdr = "");
|
||||
|
||||
std::string GenerateUUID();
|
||||
|
||||
std::vector<uint8_t> Base64Decode(const std::string& b64);
|
||||
std::string Base64DecodeStr(const std::string& b64);
|
||||
|
||||
std::vector<uint8_t> ComputeMD5(const void* data, size_t len);
|
||||
|
||||
std::vector<uint8_t> BuildSubjectPublicKeyInfoDER(
|
||||
int bitSize,
|
||||
const std::vector<uint8_t>& x,
|
||||
const std::vector<uint8_t>& y);
|
||||
|
||||
}
|
||||
314
newauth/src/newauthDb.cpp
Normal file
314
newauth/src/newauthDb.cpp
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#include "newauthDb.h"
|
||||
#include "newauthInternal.h"
|
||||
#include "../include/newauth.h"
|
||||
#include "sqlite3.h"
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
static void DbLog(const char* fmt, ...) {
|
||||
char buf[512];
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||
va_end(ap);
|
||||
#ifdef _WIN32
|
||||
OutputDebugStringA(buf);
|
||||
#endif
|
||||
}
|
||||
#define DB_LOG(msg, ...) DbLog("[authdb] " msg "\n", ##__VA_ARGS__)
|
||||
|
||||
namespace newauth {
|
||||
|
||||
AuthDb& AuthDb::Get() {
|
||||
static AuthDb instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
AuthDb::AuthDb() = default;
|
||||
|
||||
AuthDb::~AuthDb() {
|
||||
Close();
|
||||
}
|
||||
|
||||
bool AuthDb::Open(const char* dbPath) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (m_db) return true;
|
||||
|
||||
int rc = sqlite3_open(dbPath, &m_db);
|
||||
if (rc != SQLITE_OK) {
|
||||
DB_LOG("sqlite3_open('%s') failed: %s", dbPath, sqlite3_errmsg(m_db));
|
||||
sqlite3_close(m_db);
|
||||
m_db = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
sqlite3_exec(m_db, "PRAGMA journal_mode=WAL;", nullptr, nullptr, nullptr);
|
||||
|
||||
if (!EnsureTables()) {
|
||||
DB_LOG("EnsureTables failed");
|
||||
sqlite3_close(m_db);
|
||||
m_db = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
DB_LOG("opened '%s'", dbPath);
|
||||
return true;
|
||||
}
|
||||
|
||||
void AuthDb::Close() {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (m_db) {
|
||||
sqlite3_close(m_db);
|
||||
m_db = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool AuthDb::EnsureTables() {
|
||||
const char* sql =
|
||||
"CREATE TABLE IF NOT EXISTS accounts ("
|
||||
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
" username TEXT NOT NULL,"
|
||||
" uuid TEXT NOT NULL,"
|
||||
" token_key TEXT,"
|
||||
" is_offline INTEGER DEFAULT 0,"
|
||||
" auth_provider TEXT DEFAULT 'mojang'"
|
||||
");"
|
||||
"CREATE TABLE IF NOT EXISTS config ("
|
||||
" key TEXT PRIMARY KEY,"
|
||||
" value TEXT"
|
||||
");"
|
||||
"CREATE TABLE IF NOT EXISTS tokens ("
|
||||
" key TEXT PRIMARY KEY,"
|
||||
" blob TEXT NOT NULL"
|
||||
");";
|
||||
|
||||
char* err = nullptr;
|
||||
int rc = sqlite3_exec(m_db, sql, nullptr, nullptr, &err);
|
||||
if (rc != SQLITE_OK) {
|
||||
DB_LOG("EnsureTables error: %s", err ? err : "unknown");
|
||||
sqlite3_free(err);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AuthDb::LoadAccounts(int& activeIndex, std::vector<AccountRow>& accounts) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (!m_db) return false;
|
||||
|
||||
accounts.clear();
|
||||
activeIndex = -1;
|
||||
|
||||
{
|
||||
sqlite3_stmt* stmt = nullptr;
|
||||
int rc = sqlite3_prepare_v2(m_db,
|
||||
"SELECT value FROM config WHERE key='activeIndex'", -1, &stmt, nullptr);
|
||||
if (rc == SQLITE_OK && sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char* v = (const char*)sqlite3_column_text(stmt, 0);
|
||||
if (v) activeIndex = atoi(v);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
{
|
||||
sqlite3_stmt* stmt = nullptr;
|
||||
int rc = sqlite3_prepare_v2(m_db,
|
||||
"SELECT id, username, uuid, token_key, is_offline, auth_provider "
|
||||
"FROM accounts ORDER BY id", -1, &stmt, nullptr);
|
||||
if (rc != SQLITE_OK) {
|
||||
DB_LOG("LoadAccounts prepare failed: %s", sqlite3_errmsg(m_db));
|
||||
return false;
|
||||
}
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
AccountRow row;
|
||||
row.id = sqlite3_column_int(stmt, 0);
|
||||
row.username = (const char*)sqlite3_column_text(stmt, 1);
|
||||
row.uuid = (const char*)sqlite3_column_text(stmt, 2);
|
||||
const char* tk = (const char*)sqlite3_column_text(stmt, 3);
|
||||
row.tokenKey = tk ? tk : "";
|
||||
row.isOffline = sqlite3_column_int(stmt, 4) != 0;
|
||||
const char* ap = (const char*)sqlite3_column_text(stmt, 5);
|
||||
row.authProvider = ap ? ap : "mojang";
|
||||
accounts.push_back(std::move(row));
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
DB_LOG("LoadAccounts: %d accounts, activeIndex=%d", (int)accounts.size(), activeIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AuthDb::SaveAccounts(int activeIndex, const std::vector<AccountRow>& accounts) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (!m_db) return false;
|
||||
|
||||
char* err = nullptr;
|
||||
sqlite3_exec(m_db, "BEGIN TRANSACTION;", nullptr, nullptr, nullptr);
|
||||
|
||||
sqlite3_exec(m_db, "DELETE FROM accounts;", nullptr, nullptr, nullptr);
|
||||
|
||||
sqlite3_stmt* stmt = nullptr;
|
||||
int rc = sqlite3_prepare_v2(m_db,
|
||||
"INSERT INTO accounts (username, uuid, token_key, is_offline, auth_provider) "
|
||||
"VALUES (?, ?, ?, ?, ?)", -1, &stmt, nullptr);
|
||||
if (rc != SQLITE_OK) {
|
||||
DB_LOG("SaveAccounts prepare failed: %s", sqlite3_errmsg(m_db));
|
||||
sqlite3_exec(m_db, "ROLLBACK;", nullptr, nullptr, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto& a : accounts) {
|
||||
sqlite3_reset(stmt);
|
||||
sqlite3_bind_text(stmt, 1, a.username.c_str(), -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_text(stmt, 2, a.uuid.c_str(), -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_text(stmt, 3, a.tokenKey.c_str(), -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_int(stmt, 4, a.isOffline ? 1 : 0);
|
||||
sqlite3_bind_text(stmt, 5, a.authProvider.c_str(), -1, SQLITE_TRANSIENT);
|
||||
sqlite3_step(stmt);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
rc = sqlite3_prepare_v2(m_db,
|
||||
"INSERT OR REPLACE INTO config (key, value) VALUES ('activeIndex', ?)",
|
||||
-1, &stmt, nullptr);
|
||||
if (rc == SQLITE_OK) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "%d", activeIndex);
|
||||
sqlite3_bind_text(stmt, 1, buf, -1, SQLITE_TRANSIENT);
|
||||
sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
sqlite3_exec(m_db, "COMMIT;", nullptr, nullptr, &err);
|
||||
if (err) { DB_LOG("SaveAccounts commit: %s", err); sqlite3_free(err); return false; }
|
||||
|
||||
DB_LOG("SaveAccounts: saved %d accounts, activeIndex=%d", (int)accounts.size(), activeIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AuthDb::SaveTokenBlob(const std::string& key, const std::string& blob) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (!m_db) return false;
|
||||
|
||||
sqlite3_stmt* stmt = nullptr;
|
||||
int rc = sqlite3_prepare_v2(m_db,
|
||||
"INSERT OR REPLACE INTO tokens (key, blob) VALUES (?, ?)",
|
||||
-1, &stmt, nullptr);
|
||||
if (rc != SQLITE_OK) return false;
|
||||
|
||||
sqlite3_bind_text(stmt, 1, key.c_str(), -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_text(stmt, 2, blob.c_str(), -1, SQLITE_TRANSIENT);
|
||||
rc = sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
return rc == SQLITE_DONE;
|
||||
}
|
||||
|
||||
std::string AuthDb::LoadTokenBlob(const std::string& key) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (!m_db) return "";
|
||||
|
||||
sqlite3_stmt* stmt = nullptr;
|
||||
int rc = sqlite3_prepare_v2(m_db,
|
||||
"SELECT blob FROM tokens WHERE key=?", -1, &stmt, nullptr);
|
||||
if (rc != SQLITE_OK) return "";
|
||||
|
||||
sqlite3_bind_text(stmt, 1, key.c_str(), -1, SQLITE_TRANSIENT);
|
||||
std::string result;
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char* v = (const char*)sqlite3_column_text(stmt, 0);
|
||||
if (v) result = v;
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool AuthDb::DeleteTokenBlob(const std::string& key) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (!m_db) return false;
|
||||
|
||||
sqlite3_stmt* stmt = nullptr;
|
||||
int rc = sqlite3_prepare_v2(m_db,
|
||||
"DELETE FROM tokens WHERE key=?", -1, &stmt, nullptr);
|
||||
if (rc != SQLITE_OK) return false;
|
||||
|
||||
sqlite3_bind_text(stmt, 1, key.c_str(), -1, SQLITE_TRANSIENT);
|
||||
rc = sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
return rc == SQLITE_DONE;
|
||||
}
|
||||
|
||||
bool AuthDb::MigrateFromJson(const char* jsonPath) {
|
||||
std::ifstream f(jsonPath);
|
||||
if (!f) return false;
|
||||
|
||||
DB_LOG("MigrateFromJson: found '%s', importing...", jsonPath);
|
||||
|
||||
std::string content((std::istreambuf_iterator<char>(f)),
|
||||
std::istreambuf_iterator<char>());
|
||||
f.close();
|
||||
|
||||
int activeIndex = (int)JsonGetInt(content, "activeIndex");
|
||||
|
||||
std::vector<AccountRow> accounts;
|
||||
std::vector<std::string> objects;
|
||||
{
|
||||
size_t pos = content.find("\"accounts\"");
|
||||
if (pos != std::string::npos) {
|
||||
pos = content.find('[', pos);
|
||||
if (pos != std::string::npos) {
|
||||
int depth = 0; size_t start = pos;
|
||||
for (; pos < content.size(); ++pos) {
|
||||
if (content[pos] == '[') ++depth;
|
||||
else if (content[pos] == ']') { --depth; if (depth == 0) { ++pos; break; } }
|
||||
}
|
||||
std::string arr = content.substr(start, pos - start);
|
||||
objects = JsonArrayObjects(arr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& obj : objects) {
|
||||
AccountRow row;
|
||||
row.username = JsonGetString(obj, "username");
|
||||
row.uuid = JsonGetString(obj, "uuid");
|
||||
row.tokenKey = JsonGetString(obj, "tokenFile");
|
||||
row.isOffline = (JsonRawValue(obj, "isOffline") == "true");
|
||||
row.authProvider = JsonGetString(obj, "authProvider");
|
||||
if (row.authProvider.empty())
|
||||
row.authProvider = row.isOffline ? "offline" : "mojang";
|
||||
if (!row.tokenKey.empty() || row.isOffline)
|
||||
accounts.push_back(std::move(row));
|
||||
}
|
||||
|
||||
if (!SaveAccounts(activeIndex, accounts)) {
|
||||
DB_LOG("MigrateFromJson: SaveAccounts failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto& acct : accounts) {
|
||||
if (acct.tokenKey.empty() || acct.isOffline) continue;
|
||||
std::ifstream tf(acct.tokenKey);
|
||||
if (!tf) continue;
|
||||
std::string tokenContent((std::istreambuf_iterator<char>(tf)),
|
||||
std::istreambuf_iterator<char>());
|
||||
tf.close();
|
||||
if (!tokenContent.empty()) {
|
||||
SaveTokenBlob(acct.tokenKey, tokenContent);
|
||||
DB_LOG("MigrateFromJson: imported token '%s'", acct.tokenKey.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
std::string backupPath = std::string(jsonPath) + ".migrated";
|
||||
std::rename(jsonPath, backupPath.c_str());
|
||||
DB_LOG("MigrateFromJson: done, renamed '%s' -> '%s'", jsonPath, backupPath.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
49
newauth/src/newauthDb.h
Normal file
49
newauth/src/newauthDb.h
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
struct sqlite3;
|
||||
|
||||
namespace newauth {
|
||||
|
||||
struct AccountRow {
|
||||
int id = 0;
|
||||
std::string username;
|
||||
std::string uuid;
|
||||
std::string tokenKey;
|
||||
bool isOffline = false;
|
||||
std::string authProvider;
|
||||
};
|
||||
|
||||
class AuthDb {
|
||||
public:
|
||||
static AuthDb& Get();
|
||||
|
||||
AuthDb(const AuthDb&) = delete;
|
||||
AuthDb& operator=(const AuthDb&) = delete;
|
||||
|
||||
bool Open(const char* dbPath = "newauth.db");
|
||||
void Close();
|
||||
|
||||
bool LoadAccounts(int& activeIndex, std::vector<AccountRow>& accounts);
|
||||
bool SaveAccounts(int activeIndex, const std::vector<AccountRow>& accounts);
|
||||
|
||||
bool SaveTokenBlob(const std::string& key, const std::string& blob);
|
||||
std::string LoadTokenBlob(const std::string& key);
|
||||
bool DeleteTokenBlob(const std::string& key);
|
||||
|
||||
bool MigrateFromJson(const char* jsonPath = "java_accounts.json");
|
||||
|
||||
private:
|
||||
AuthDb();
|
||||
~AuthDb();
|
||||
|
||||
bool EnsureTables();
|
||||
|
||||
sqlite3* m_db = nullptr;
|
||||
std::mutex m_mutex;
|
||||
};
|
||||
|
||||
}
|
||||
160
newauth/src/newauthElyby.cpp
Normal file
160
newauth/src/newauthElyby.cpp
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
#include "../include/newauth.h"
|
||||
#include "newauthCrypto.h"
|
||||
#include "newauthHttp.h"
|
||||
#include "newauthInternal.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
namespace newauth {
|
||||
|
||||
using namespace newauth;
|
||||
|
||||
bool ElybyLogin(const std::string& username, const std::string& password,
|
||||
ElybyTokens& outTokens, std::string& error)
|
||||
{
|
||||
std::string clientToken = outTokens.clientToken;
|
||||
if (clientToken.empty())
|
||||
clientToken = GenerateUUID();
|
||||
|
||||
std::string body = "{\"agent\":{\"name\":\"Minecraft\",\"version\":1}"
|
||||
",\"username\":" + JsonStr(username) +
|
||||
",\"password\":" + JsonStr(password) +
|
||||
",\"clientToken\":" + JsonStr(clientToken) + "}";
|
||||
|
||||
try {
|
||||
auto resp = HttpPost("https://authserver.ely.by/auth/authenticate",
|
||||
body, "application/json");
|
||||
|
||||
if (resp.statusCode == 200 && !resp.body.empty()) {
|
||||
outTokens.accessToken = JsonGetString(resp.body, "accessToken");
|
||||
outTokens.clientToken = JsonGetString(resp.body, "clientToken");
|
||||
|
||||
std::string profile = JsonRawValue(resp.body, "selectedProfile");
|
||||
if (!profile.empty()) {
|
||||
outTokens.uuid = JsonGetString(profile, "id");
|
||||
outTokens.username = JsonGetString(profile, "name");
|
||||
}
|
||||
|
||||
if (outTokens.accessToken.empty()) {
|
||||
error = "ElybyLogin: empty accessToken in response";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (resp.statusCode == 401) {
|
||||
std::string errMsg = JsonGetString(resp.body, "errorMessage");
|
||||
if (errMsg.find("two factor auth") != std::string::npos ||
|
||||
errMsg.find("Account protected with two factor auth") != std::string::npos) {
|
||||
error = "elyby_2fa_required";
|
||||
return false;
|
||||
}
|
||||
error = "ElybyLogin: invalid credentials";
|
||||
if (!errMsg.empty()) error += " (" + errMsg + ")";
|
||||
return false;
|
||||
}
|
||||
|
||||
error = "ElybyLogin HTTP " + std::to_string(resp.statusCode);
|
||||
if (!resp.body.empty()) {
|
||||
std::string msg = JsonGetString(resp.body, "errorMessage");
|
||||
if (!msg.empty()) error += ": " + msg;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
error = std::string("ElybyLogin network error: ") + e.what();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ElybyRefresh(ElybyTokens& tokens, std::string& error)
|
||||
{
|
||||
std::string body = "{\"accessToken\":" + JsonStr(tokens.accessToken) +
|
||||
",\"clientToken\":" + JsonStr(tokens.clientToken) + "}";
|
||||
|
||||
try {
|
||||
auto resp = HttpPost("https://authserver.ely.by/auth/refresh",
|
||||
body, "application/json");
|
||||
|
||||
if (resp.statusCode == 200 && !resp.body.empty()) {
|
||||
tokens.accessToken = JsonGetString(resp.body, "accessToken");
|
||||
tokens.clientToken = JsonGetString(resp.body, "clientToken");
|
||||
|
||||
std::string profile = JsonRawValue(resp.body, "selectedProfile");
|
||||
if (!profile.empty()) {
|
||||
tokens.uuid = JsonGetString(profile, "id");
|
||||
tokens.username = JsonGetString(profile, "name");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
error = "ElybyRefresh HTTP " + std::to_string(resp.statusCode);
|
||||
if (!resp.body.empty()) {
|
||||
std::string msg = JsonGetString(resp.body, "errorMessage");
|
||||
if (!msg.empty()) error += ": " + msg;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
error = std::string("ElybyRefresh network error: ") + e.what();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ElybyValidate(const std::string& accessToken, std::string& error)
|
||||
{
|
||||
std::string body = "{\"accessToken\":" + JsonStr(accessToken) + "}";
|
||||
|
||||
try {
|
||||
auto resp = HttpPost("https://authserver.ely.by/auth/validate",
|
||||
body, "application/json");
|
||||
return (resp.statusCode == 204 || resp.statusCode == 200);
|
||||
} catch (const std::exception& e) {
|
||||
error = std::string("ElybyValidate network error: ") + e.what();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ElybyLoadTokens(const std::string& path, ElybyTokens& out)
|
||||
{
|
||||
std::ifstream f(path);
|
||||
if (!f) return false;
|
||||
std::string content((std::istreambuf_iterator<char>(f)),
|
||||
std::istreambuf_iterator<char>());
|
||||
if (content.empty()) return false;
|
||||
|
||||
out.accessToken = JsonGetString(content, "accessToken");
|
||||
out.clientToken = JsonGetString(content, "clientToken");
|
||||
out.uuid = JsonGetString(content, "uuid");
|
||||
out.username = JsonGetString(content, "username");
|
||||
return !out.accessToken.empty();
|
||||
}
|
||||
|
||||
bool ElybySaveTokens(const std::string& path, const ElybyTokens& tokens)
|
||||
{
|
||||
std::ofstream f(path);
|
||||
if (!f) return false;
|
||||
f << ElybySerializeTokens(tokens);
|
||||
return f.good();
|
||||
}
|
||||
|
||||
std::string ElybySerializeTokens(const ElybyTokens& tokens)
|
||||
{
|
||||
std::ostringstream o;
|
||||
o << "{\n";
|
||||
o << "\"accessToken\":" << JsonStr(tokens.accessToken) << ",\n";
|
||||
o << "\"clientToken\":" << JsonStr(tokens.clientToken) << ",\n";
|
||||
o << "\"uuid\":" << JsonStr(tokens.uuid) << ",\n";
|
||||
o << "\"username\":" << JsonStr(tokens.username) << "\n";
|
||||
o << "}";
|
||||
return o.str();
|
||||
}
|
||||
|
||||
bool ElybyDeserializeTokens(const std::string& json, ElybyTokens& out)
|
||||
{
|
||||
if (json.empty()) return false;
|
||||
out.accessToken = JsonGetString(json, "accessToken");
|
||||
out.clientToken = JsonGetString(json, "clientToken");
|
||||
out.uuid = JsonGetString(json, "uuid");
|
||||
out.username = JsonGetString(json, "username");
|
||||
return !out.accessToken.empty();
|
||||
}
|
||||
|
||||
}
|
||||
152
newauth/src/newauthHttp.cpp
Normal file
152
newauth/src/newauthHttp.cpp
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
#include "newauthHttp.h"
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <winhttp.h>
|
||||
#pragma comment(lib, "winhttp.lib")
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace newauth {
|
||||
|
||||
struct ParsedUrl {
|
||||
std::wstring scheme;
|
||||
std::wstring host;
|
||||
INTERNET_PORT port;
|
||||
std::wstring path;
|
||||
};
|
||||
|
||||
static std::wstring Utf8ToWide(const std::string& s) {
|
||||
if (s.empty()) return {};
|
||||
int len = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), (int)s.size(), nullptr, 0);
|
||||
std::wstring w(len, L'\0');
|
||||
MultiByteToWideChar(CP_UTF8, 0, s.c_str(), (int)s.size(), w.data(), len);
|
||||
return w;
|
||||
}
|
||||
|
||||
static std::string WideToUtf8(const std::wstring& w) {
|
||||
if (w.empty()) return {};
|
||||
int len = WideCharToMultiByte(CP_UTF8, 0, w.c_str(), (int)w.size(), nullptr, 0, nullptr, nullptr);
|
||||
std::string s(len, '\0');
|
||||
WideCharToMultiByte(CP_UTF8, 0, w.c_str(), (int)w.size(), s.data(), len, nullptr, nullptr);
|
||||
return s;
|
||||
}
|
||||
|
||||
static ParsedUrl ParseUrl(const std::string& url) {
|
||||
std::wstring wurl = Utf8ToWide(url);
|
||||
|
||||
URL_COMPONENTS uc = {};
|
||||
uc.dwStructSize = sizeof(uc);
|
||||
|
||||
wchar_t scheme[16] = {}, host[256] = {}, path[2048] = {};
|
||||
uc.lpszScheme = scheme; uc.dwSchemeLength = _countof(scheme);
|
||||
uc.lpszHostName = host; uc.dwHostNameLength = _countof(host);
|
||||
uc.lpszUrlPath = path; uc.dwUrlPathLength = _countof(path);
|
||||
|
||||
if (!WinHttpCrackUrl(wurl.c_str(), (DWORD)wurl.size(), 0, &uc))
|
||||
throw std::runtime_error("ParseUrl: WinHttpCrackUrl failed for: " + url);
|
||||
|
||||
ParsedUrl p;
|
||||
p.scheme = scheme;
|
||||
p.host = host;
|
||||
p.port = uc.nPort;
|
||||
p.path = path;
|
||||
if (p.path.empty()) p.path = L"/";
|
||||
return p;
|
||||
}
|
||||
|
||||
struct WinHttpHandle {
|
||||
HINTERNET h = nullptr;
|
||||
WinHttpHandle() = default;
|
||||
explicit WinHttpHandle(HINTERNET handle) : h(handle) {}
|
||||
~WinHttpHandle() { if (h) WinHttpCloseHandle(h); }
|
||||
WinHttpHandle(const WinHttpHandle&) = delete;
|
||||
WinHttpHandle& operator=(const WinHttpHandle&) = delete;
|
||||
WinHttpHandle(WinHttpHandle&& o) noexcept : h(o.h) { o.h = nullptr; }
|
||||
operator HINTERNET() const { return h; }
|
||||
explicit operator bool() const { return h != nullptr; }
|
||||
};
|
||||
|
||||
static HttpResponse DoRequest(const std::wstring& method,
|
||||
const ParsedUrl& parsed,
|
||||
const std::string& body,
|
||||
const std::string& contentType,
|
||||
const std::map<std::string, std::string>& headers,
|
||||
const std::string& urlForErrors)
|
||||
{
|
||||
bool isHttps = (parsed.scheme == L"https");
|
||||
|
||||
WinHttpHandle session(WinHttpOpen(
|
||||
L"newauth/1.0",
|
||||
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
|
||||
WINHTTP_NO_PROXY_NAME,
|
||||
WINHTTP_NO_PROXY_BYPASS,
|
||||
0));
|
||||
if (!session)
|
||||
throw std::runtime_error("WinHttpOpen failed");
|
||||
|
||||
WinHttpSetTimeouts(session, 10000, 10000, 10000, 15000);
|
||||
|
||||
WinHttpHandle connect(WinHttpConnect(session, parsed.host.c_str(), parsed.port, 0));
|
||||
if (!connect)
|
||||
throw std::runtime_error("WinHttpConnect failed for host: " + WideToUtf8(parsed.host));
|
||||
|
||||
DWORD flags = isHttps ? WINHTTP_FLAG_SECURE : 0;
|
||||
WinHttpHandle request(WinHttpOpenRequest(
|
||||
connect, method.c_str(), parsed.path.c_str(),
|
||||
nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, flags));
|
||||
if (!request)
|
||||
throw std::runtime_error("WinHttpOpenRequest failed");
|
||||
|
||||
if (!contentType.empty()) {
|
||||
std::wstring ct = L"Content-Type: " + Utf8ToWide(contentType);
|
||||
WinHttpAddRequestHeaders(request, ct.c_str(), (DWORD)-1, WINHTTP_ADDREQ_FLAG_ADD);
|
||||
}
|
||||
|
||||
for (auto& kv : headers) {
|
||||
std::wstring h = Utf8ToWide(kv.first) + L": " + Utf8ToWide(kv.second);
|
||||
WinHttpAddRequestHeaders(request, h.c_str(), (DWORD)-1, WINHTTP_ADDREQ_FLAG_ADD);
|
||||
}
|
||||
|
||||
BOOL sent = WinHttpSendRequest(
|
||||
request,
|
||||
WINHTTP_NO_ADDITIONAL_HEADERS, 0,
|
||||
body.empty() ? WINHTTP_NO_REQUEST_DATA : const_cast<char*>(body.c_str()),
|
||||
(DWORD)body.size(), (DWORD)body.size(), 0);
|
||||
|
||||
if (!sent || !WinHttpReceiveResponse(request, nullptr))
|
||||
throw std::runtime_error("request failed for: " + urlForErrors);
|
||||
|
||||
DWORD statusCode = 0, statusLen = sizeof(statusCode);
|
||||
WinHttpQueryHeaders(request,
|
||||
WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
|
||||
WINHTTP_HEADER_NAME_BY_INDEX,
|
||||
&statusCode, &statusLen, WINHTTP_NO_HEADER_INDEX);
|
||||
|
||||
std::string responseBody;
|
||||
DWORD bytesAvailable = 0;
|
||||
while (WinHttpQueryDataAvailable(request, &bytesAvailable) && bytesAvailable > 0) {
|
||||
std::string chunk(bytesAvailable, '\0');
|
||||
DWORD bytesRead = 0;
|
||||
WinHttpReadData(request, chunk.data(), bytesAvailable, &bytesRead);
|
||||
responseBody.append(chunk.data(), bytesRead);
|
||||
}
|
||||
|
||||
return { (int)statusCode, std::move(responseBody) };
|
||||
}
|
||||
|
||||
HttpResponse HttpPost(const std::string& url,
|
||||
const std::string& body,
|
||||
const std::string& contentType,
|
||||
const std::map<std::string, std::string>& headers)
|
||||
{
|
||||
return DoRequest(L"POST", ParseUrl(url), body, contentType, headers, url);
|
||||
}
|
||||
|
||||
HttpResponse HttpGet(const std::string& url,
|
||||
const std::map<std::string, std::string>& headers)
|
||||
{
|
||||
return DoRequest(L"GET", ParseUrl(url), "", "", headers, url);
|
||||
}
|
||||
|
||||
}
|
||||
20
newauth/src/newauthHttp.h
Normal file
20
newauth/src/newauthHttp.h
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
namespace newauth {
|
||||
|
||||
struct HttpResponse {
|
||||
int statusCode;
|
||||
std::string body;
|
||||
};
|
||||
|
||||
HttpResponse HttpPost(const std::string& url,
|
||||
const std::string& body,
|
||||
const std::string& contentType,
|
||||
const std::map<std::string, std::string>& headers = {});
|
||||
|
||||
HttpResponse HttpGet(const std::string& url,
|
||||
const std::map<std::string, std::string>& headers = {});
|
||||
|
||||
}
|
||||
205
newauth/src/newauthInternal.h
Normal file
205
newauth/src/newauthInternal.h
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <cstdint>
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <cstdio>
|
||||
#include <cctype>
|
||||
|
||||
namespace newauth {
|
||||
|
||||
inline std::string JsonRawValue(const std::string& json, const std::string& key) {
|
||||
std::string search = "\"" + key + "\"";
|
||||
size_t pos = 0;
|
||||
while ((pos = json.find(search, pos)) != std::string::npos) {
|
||||
size_t colon = json.find_first_not_of(" \t\r\n", pos + search.size());
|
||||
if (colon == std::string::npos || json[colon] != ':') { pos++; continue; }
|
||||
size_t vstart = json.find_first_not_of(" \t\r\n", colon + 1);
|
||||
if (vstart == std::string::npos) return "";
|
||||
char c = json[vstart];
|
||||
if (c == '"') {
|
||||
size_t end = vstart + 1;
|
||||
while (end < json.size()) {
|
||||
if (json[end] == '\\') { end += 2; continue; }
|
||||
if (json[end] == '"') { end++; break; }
|
||||
end++;
|
||||
}
|
||||
return json.substr(vstart, end - vstart);
|
||||
} else if (c == '{' || c == '[') {
|
||||
char open = c, close = (c == '{') ? '}' : ']';
|
||||
int depth = 1;
|
||||
size_t end = vstart + 1;
|
||||
while (end < json.size() && depth > 0) {
|
||||
if (json[end] == open) depth++;
|
||||
else if (json[end] == close) depth--;
|
||||
else if (json[end] == '"') {
|
||||
end++;
|
||||
while (end < json.size() && json[end] != '"') {
|
||||
if (json[end] == '\\') end++;
|
||||
end++;
|
||||
}
|
||||
}
|
||||
end++;
|
||||
}
|
||||
return json.substr(vstart, end - vstart);
|
||||
} else {
|
||||
size_t end = json.find_first_of(",}\r\n", vstart);
|
||||
if (end == std::string::npos) end = json.size();
|
||||
std::string v = json.substr(vstart, end - vstart);
|
||||
while (!v.empty() && isspace((unsigned char)v.back())) v.pop_back();
|
||||
return v;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
inline std::string JsonUnquote(const std::string& s) {
|
||||
if (s.size() >= 2 && s.front() == '"' && s.back() == '"')
|
||||
return s.substr(1, s.size() - 2);
|
||||
return s;
|
||||
}
|
||||
|
||||
inline std::string JsonGetString(const std::string& json, const std::string& key) {
|
||||
return JsonUnquote(JsonRawValue(json, key));
|
||||
}
|
||||
|
||||
inline int64_t JsonGetInt(const std::string& json, const std::string& key) {
|
||||
auto s = JsonUnquote(JsonRawValue(json, key));
|
||||
if (s.empty()) return 0;
|
||||
try { return std::stoll(s); } catch (...) { return 0; }
|
||||
}
|
||||
|
||||
inline std::string JsonFirstArrayObject(const std::string& arrStr) {
|
||||
size_t pos = arrStr.find('[');
|
||||
if (pos == std::string::npos) return "";
|
||||
pos = arrStr.find_first_not_of(" \t\r\n", pos + 1);
|
||||
if (pos == std::string::npos || arrStr[pos] == ']') return "";
|
||||
if (arrStr[pos] != '{') return "";
|
||||
int depth = 1;
|
||||
size_t end = pos + 1;
|
||||
while (end < arrStr.size() && depth > 0) {
|
||||
if (arrStr[end] == '{') depth++;
|
||||
else if (arrStr[end] == '}') depth--;
|
||||
else if (arrStr[end] == '"') {
|
||||
end++;
|
||||
while (end < arrStr.size() && arrStr[end] != '"') {
|
||||
if (arrStr[end] == '\\') end++;
|
||||
end++;
|
||||
}
|
||||
}
|
||||
end++;
|
||||
}
|
||||
return arrStr.substr(pos, end - pos);
|
||||
}
|
||||
|
||||
inline std::string JsonNthArrayString(const std::string& arrStr, int n) {
|
||||
size_t pos = arrStr.find('[');
|
||||
if (pos == std::string::npos) return "";
|
||||
pos++;
|
||||
int idx = 0;
|
||||
while (pos < arrStr.size()) {
|
||||
pos = arrStr.find_first_not_of(" \t\r\n,", pos);
|
||||
if (pos == std::string::npos || arrStr[pos] == ']') break;
|
||||
if (arrStr[pos] == '"') {
|
||||
size_t end = pos + 1;
|
||||
while (end < arrStr.size()) {
|
||||
if (arrStr[end] == '\\') { end += 2; continue; }
|
||||
if (arrStr[end] == '"') { end++; break; }
|
||||
end++;
|
||||
}
|
||||
if (idx == n) return arrStr.substr(pos + 1, end - pos - 2);
|
||||
pos = end;
|
||||
idx++;
|
||||
} else {
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
inline std::vector<std::string> JsonArrayObjects(const std::string& json) {
|
||||
std::vector<std::string> result;
|
||||
size_t pos = json.find('[');
|
||||
if (pos == std::string::npos) return result;
|
||||
++pos;
|
||||
while (pos < json.size()) {
|
||||
pos = json.find('{', pos);
|
||||
if (pos == std::string::npos) break;
|
||||
int depth = 0;
|
||||
size_t start = pos;
|
||||
for (; pos < json.size(); ++pos) {
|
||||
if (json[pos] == '{') ++depth;
|
||||
else if (json[pos] == '}') { --depth; if (depth == 0) { ++pos; break; } }
|
||||
}
|
||||
result.push_back(json.substr(start, pos - start));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::string UrlEncode(const std::string& s) {
|
||||
std::string out;
|
||||
out.reserve(s.size() * 3);
|
||||
for (unsigned char c : s) {
|
||||
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
|
||||
out += c;
|
||||
} else {
|
||||
char buf[4];
|
||||
snprintf(buf, sizeof(buf), "%%%02X", c);
|
||||
out += buf;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
inline int64_t NowMs() {
|
||||
using namespace std::chrono;
|
||||
return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
inline std::string BuildFormBody(const std::map<std::string, std::string>& params) {
|
||||
std::string body;
|
||||
for (auto& kv : params) {
|
||||
if (!body.empty()) body += '&';
|
||||
body += UrlEncode(kv.first) + '=' + UrlEncode(kv.second);
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
inline std::string JsonEscape(const std::string& s) {
|
||||
std::string out;
|
||||
for (char c : s) {
|
||||
if (c == '"') out += "\\\"";
|
||||
else if (c == '\\') out += "\\\\";
|
||||
else if (c == '\n') out += "\\n";
|
||||
else if (c == '\r') out += "\\r";
|
||||
else out += c;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
inline std::string JsonStr(const std::string& s) {
|
||||
return "\"" + JsonEscape(s) + "\"";
|
||||
}
|
||||
|
||||
inline int64_t ParseIso8601Ms(const std::string& s) {
|
||||
int Y=0, M=0, D=0, h=0, m=0;
|
||||
double sec=0.0;
|
||||
if (sscanf_s(s.c_str(), "%d-%d-%dT%d:%d:%lf", &Y, &M, &D, &h, &m, &sec) < 6)
|
||||
return 0;
|
||||
struct tm t = {};
|
||||
t.tm_year = Y - 1900;
|
||||
t.tm_mon = M - 1;
|
||||
t.tm_mday = D;
|
||||
t.tm_hour = h;
|
||||
t.tm_min = m;
|
||||
t.tm_sec = (int)sec;
|
||||
t.tm_isdst = 0;
|
||||
time_t epoch = _mkgmtime(&t);
|
||||
if (epoch == (time_t)-1) return 0;
|
||||
int ms = (int)((sec - (int)sec) * 1000.0);
|
||||
return (int64_t)epoch * 1000LL + ms;
|
||||
}
|
||||
|
||||
}
|
||||
423
newauth/src/newauthJava.cpp
Normal file
423
newauth/src/newauthJava.cpp
Normal file
|
|
@ -0,0 +1,423 @@
|
|||
#include "../include/newauth.h"
|
||||
#include "newauthCrypto.h"
|
||||
#include "newauthHttp.h"
|
||||
#include "newauthInternal.h"
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <bcrypt.h>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <ctime>
|
||||
#include <map>
|
||||
|
||||
namespace newauth {
|
||||
|
||||
using namespace newauth;
|
||||
|
||||
struct JavaAuthManager::Impl {
|
||||
|
||||
struct MsaState {
|
||||
std::string accessToken, refreshToken;
|
||||
int64_t expireMs = 0;
|
||||
bool IsExpired() const { return NowMs() >= expireMs - 60000; }
|
||||
} msa;
|
||||
|
||||
ECKeyPair deviceKeyP256;
|
||||
std::string deviceId;
|
||||
|
||||
struct XblDeviceState {
|
||||
std::string token, did;
|
||||
int64_t expireMs = 0;
|
||||
bool IsExpired() const { return NowMs() >= expireMs - 60000; }
|
||||
} xblDevice;
|
||||
|
||||
struct JavaXstsState {
|
||||
std::string token, uhs;
|
||||
int64_t expireMs = 0;
|
||||
bool IsExpired() const { return NowMs() >= expireMs - 60000; }
|
||||
} javaXsts;
|
||||
|
||||
struct MinecraftTokenState {
|
||||
std::string tokenType, accessToken;
|
||||
int64_t expireMs = 0;
|
||||
std::string AuthHeader() const { return tokenType + " " + accessToken; }
|
||||
bool IsExpired() const { return NowMs() >= expireMs - 60000; }
|
||||
} mcToken;
|
||||
|
||||
std::atomic<bool> loggedIn{false};
|
||||
|
||||
std::atomic<bool> cancelRequested{false};
|
||||
std::mutex cancelMutex;
|
||||
std::condition_variable cancelCv;
|
||||
|
||||
bool SleepOrCancel(int64_t ms) {
|
||||
std::unique_lock<std::mutex> lock(cancelMutex);
|
||||
return cancelCv.wait_for(lock, std::chrono::milliseconds(ms), [this] {
|
||||
return cancelRequested.load(std::memory_order_relaxed);
|
||||
});
|
||||
}
|
||||
|
||||
Impl() {
|
||||
deviceKeyP256 = GenerateP256KeyPair();
|
||||
deviceId = GenerateUUID();
|
||||
}
|
||||
|
||||
struct DevCodeState {
|
||||
std::string deviceCode, userCode, verificationUri;
|
||||
int64_t expiresMs, intervalMs;
|
||||
};
|
||||
|
||||
DevCodeState RequestDeviceCode() {
|
||||
auto resp = HttpPost(
|
||||
"https://login.live.com/oauth20_connect.srf",
|
||||
BuildFormBody({{"client_id", JAVA_CLIENT_ID},
|
||||
{"scope", MSA_SCOPE},
|
||||
{"response_type", "device_code"}}),
|
||||
"application/x-www-form-urlencoded");
|
||||
|
||||
if (resp.statusCode != 200)
|
||||
throw std::runtime_error("Java DeviceCode HTTP " +
|
||||
std::to_string(resp.statusCode) + ": " + resp.body);
|
||||
|
||||
DevCodeState dc;
|
||||
dc.deviceCode = JsonGetString(resp.body, "device_code");
|
||||
dc.userCode = JsonGetString(resp.body, "user_code");
|
||||
dc.verificationUri = JsonGetString(resp.body, "verification_uri");
|
||||
if (dc.deviceCode.empty() || dc.userCode.empty() || dc.verificationUri.empty())
|
||||
throw std::runtime_error("Java DeviceCode: missing required fields in response");
|
||||
int64_t expiresIn = JsonGetInt(resp.body, "expires_in");
|
||||
int64_t intervalIn = JsonGetInt(resp.body, "interval");
|
||||
dc.expiresMs = NowMs() + (expiresIn > 0 ? expiresIn : 300) * 1000;
|
||||
dc.intervalMs = (intervalIn > 0 ? intervalIn : 5) * 1000;
|
||||
return dc;
|
||||
}
|
||||
|
||||
bool PollMsaToken(const DevCodeState& dc, int timeoutSec, std::string& error) {
|
||||
int64_t deadline = NowMs() + (int64_t)timeoutSec * 1000;
|
||||
int64_t intervalMs = dc.intervalMs > 0 ? dc.intervalMs : 5000;
|
||||
while (NowMs() < deadline && NowMs() < dc.expiresMs) {
|
||||
if (SleepOrCancel(intervalMs)) {
|
||||
error = "cancelled";
|
||||
return false;
|
||||
}
|
||||
|
||||
HttpResponse resp;
|
||||
try {
|
||||
resp = HttpPost(
|
||||
"https://login.live.com/oauth20_token.srf",
|
||||
BuildFormBody({{"client_id", JAVA_CLIENT_ID},
|
||||
{"scope", MSA_SCOPE},
|
||||
{"grant_type", "device_code"},
|
||||
{"device_code", dc.deviceCode}}),
|
||||
"application/x-www-form-urlencoded");
|
||||
} catch (const std::exception& ex) {
|
||||
error = std::string("MSA poll network error: ") + ex.what();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (resp.statusCode == 200) {
|
||||
msa.accessToken = JsonGetString(resp.body, "access_token");
|
||||
msa.refreshToken = JsonGetString(resp.body, "refresh_token");
|
||||
msa.expireMs = NowMs() + JsonGetInt(resp.body, "expires_in") * 1000;
|
||||
return true;
|
||||
}
|
||||
std::string err = JsonGetString(resp.body, "error");
|
||||
if (err == "authorization_pending") continue;
|
||||
if (err == "slow_down") { intervalMs += 5000; continue; }
|
||||
error = "MSA poll error: " + err;
|
||||
return false;
|
||||
}
|
||||
error = "MSA device code login timed out";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RefreshMsaToken(std::string& error) {
|
||||
if (msa.refreshToken.empty()) { error = "No refresh token"; return false; }
|
||||
HttpResponse resp;
|
||||
try {
|
||||
resp = HttpPost(
|
||||
"https://login.live.com/oauth20_token.srf",
|
||||
BuildFormBody({{"client_id", JAVA_CLIENT_ID},
|
||||
{"scope", MSA_SCOPE},
|
||||
{"grant_type", "refresh_token"},
|
||||
{"refresh_token", msa.refreshToken}}),
|
||||
"application/x-www-form-urlencoded");
|
||||
} catch (const std::exception& ex) {
|
||||
error = std::string("MSA refresh network error: ") + ex.what();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (resp.statusCode != 200) {
|
||||
error = "MSA refresh HTTP " + std::to_string(resp.statusCode);
|
||||
return false;
|
||||
}
|
||||
msa.accessToken = JsonGetString(resp.body, "access_token");
|
||||
std::string nr = JsonGetString(resp.body, "refresh_token");
|
||||
if (!nr.empty()) msa.refreshToken = nr;
|
||||
msa.expireMs = NowMs() + JsonGetInt(resp.body, "expires_in") * 1000;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AuthXblDevice(std::string& error) {
|
||||
std::string proofKey = BuildProofKeyJson(deviceKeyP256);
|
||||
std::string bodyJson =
|
||||
"{\"Properties\":{"
|
||||
"\"DeviceType\":\"Win32\","
|
||||
"\"Id\":\"{" + deviceId + "}\","
|
||||
"\"AuthMethod\":\"ProofOfPossession\","
|
||||
"\"ProofKey\":" + proofKey +
|
||||
"},\"RelyingParty\":\"http://auth.xboxlive.com\","
|
||||
"\"TokenType\":\"JWT\"}";
|
||||
|
||||
std::string sig = BuildXblSignatureHeader(
|
||||
deviceKeyP256, "POST", "/device/authenticate", bodyJson);
|
||||
|
||||
auto resp = HttpPost(
|
||||
"https://device.auth.xboxlive.com/device/authenticate",
|
||||
bodyJson, "application/json",
|
||||
{{"x-xbl-contract-version","1"},{"Signature", sig}});
|
||||
|
||||
if (resp.statusCode != 200) {
|
||||
error = "XBL device auth HTTP " + std::to_string(resp.statusCode);
|
||||
return false;
|
||||
}
|
||||
xblDevice.token = JsonGetString(resp.body, "Token");
|
||||
xblDevice.expireMs = ParseIso8601Ms(JsonGetString(resp.body, "NotAfter"));
|
||||
std::string xdi = JsonRawValue(JsonRawValue(resp.body, "DisplayClaims"), "xdi");
|
||||
xblDevice.did = JsonGetString(xdi, "did");
|
||||
return true;
|
||||
}
|
||||
|
||||
// god this protocol is convoluted
|
||||
bool AuthSisu(std::string& error) {
|
||||
std::string proofKey = BuildProofKeyJson(deviceKeyP256);
|
||||
std::string bodyJson =
|
||||
"{\"Sandbox\":\"RETAIL\","
|
||||
"\"UseModernGamertag\":true,"
|
||||
"\"AppId\":\"" + std::string(JAVA_CLIENT_ID) + "\","
|
||||
"\"AccessToken\":\"t=" + msa.accessToken + "\","
|
||||
"\"DeviceToken\":\"" + xblDevice.token + "\","
|
||||
"\"ProofKey\":" + proofKey + ","
|
||||
"\"RelyingParty\":\"rp://api.minecraftservices.com/\"}";
|
||||
|
||||
std::string sig = BuildXblSignatureHeader(
|
||||
deviceKeyP256, "POST", "/authorize", bodyJson);
|
||||
|
||||
auto resp = HttpPost(
|
||||
"https://sisu.xboxlive.com/authorize",
|
||||
bodyJson, "application/json", {{"Signature", sig}});
|
||||
|
||||
if (resp.statusCode != 200) {
|
||||
error = "SISU authorize HTTP " + std::to_string(resp.statusCode)
|
||||
+ ": " + resp.body;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string authObj = JsonRawValue(resp.body, "AuthorizationToken");
|
||||
javaXsts.token = JsonGetString(authObj, "Token");
|
||||
javaXsts.expireMs = ParseIso8601Ms(JsonGetString(authObj, "NotAfter"));
|
||||
|
||||
std::string disp = JsonRawValue(authObj, "DisplayClaims");
|
||||
std::string xui = JsonRawValue(disp, "xui");
|
||||
std::string xui0 = JsonFirstArrayObject(xui);
|
||||
javaXsts.uhs = JsonGetString(xui0, "uhs");
|
||||
if (javaXsts.token.empty() || javaXsts.uhs.empty()) {
|
||||
error = "SISU response missing XSTS token or uhs";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AuthMcLauncherLogin(std::string& error) {
|
||||
std::string xtoken = "XBL3.0 x=" + javaXsts.uhs + ";" + javaXsts.token;
|
||||
std::string bodyJson =
|
||||
"{\"platform\":\"PC_LAUNCHER\","
|
||||
"\"xtoken\":\"" + xtoken + "\"}";
|
||||
|
||||
auto resp = HttpPost(
|
||||
"https://api.minecraftservices.com/launcher/login",
|
||||
bodyJson, "application/json");
|
||||
|
||||
if (resp.statusCode != 200) {
|
||||
error = "Launcher login HTTP " + std::to_string(resp.statusCode)
|
||||
+ ": " + resp.body;
|
||||
return false;
|
||||
}
|
||||
|
||||
mcToken.tokenType = JsonGetString(resp.body, "token_type");
|
||||
mcToken.accessToken = JsonGetString(resp.body, "access_token");
|
||||
mcToken.expireMs = NowMs() + JsonGetInt(resp.body, "expires_in") * 1000;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FetchProfile(JavaSession& out, std::string& error) {
|
||||
auto resp = HttpGet(
|
||||
"https://api.minecraftservices.com/minecraft/profile",
|
||||
{{"Authorization", mcToken.AuthHeader()}});
|
||||
|
||||
if (resp.statusCode == 404) {
|
||||
error = "This Microsoft account does not own Minecraft Java Edition";
|
||||
return false;
|
||||
}
|
||||
if (resp.statusCode != 200) {
|
||||
error = "Profile HTTP " + std::to_string(resp.statusCode)
|
||||
+ ": " + resp.body;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string rawId = JsonGetString(resp.body, "id");
|
||||
out.uuid = DashUuid(rawId);
|
||||
out.username = JsonGetString(resp.body, "name");
|
||||
out.accessToken = mcToken.accessToken;
|
||||
out.expireMs = mcToken.expireMs;
|
||||
if (out.uuid.empty() || out.username.empty()) {
|
||||
error = "Minecraft profile response missing uuid or username";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DoFullAuth(DeviceCodeCallback onDeviceCode,
|
||||
JavaSession& out, std::string& error, int timeoutSec) {
|
||||
cancelRequested.store(false, std::memory_order_relaxed);
|
||||
DevCodeState dc;
|
||||
try { dc = RequestDeviceCode(); }
|
||||
catch (const std::exception& ex) {
|
||||
error = std::string("DeviceCode network error: ") + ex.what();
|
||||
return false;
|
||||
}
|
||||
|
||||
DeviceCodeInfo info;
|
||||
info.userCode = dc.userCode;
|
||||
info.verificationUri = dc.verificationUri;
|
||||
info.directUri = dc.verificationUri + "?otc=" + dc.userCode;
|
||||
info.deviceCode = dc.deviceCode;
|
||||
info.expiresMs = dc.expiresMs;
|
||||
info.intervalMs = dc.intervalMs;
|
||||
onDeviceCode(info);
|
||||
|
||||
if (!PollMsaToken(dc, timeoutSec, error)) return false;
|
||||
return DoAuthChain(out, error);
|
||||
}
|
||||
|
||||
bool DoAuthChain(JavaSession& out, std::string& error) {
|
||||
try {
|
||||
if (!AuthXblDevice(error)) return false;
|
||||
if (!AuthSisu(error)) return false;
|
||||
if (!AuthMcLauncherLogin(error)) return false;
|
||||
if (!FetchProfile(out, error)) return false;
|
||||
} catch (const std::runtime_error& ex) {
|
||||
error = ex.what();
|
||||
return false;
|
||||
} catch (const std::exception& ex) {
|
||||
error = std::string("Unexpected auth error: ") + ex.what();
|
||||
return false;
|
||||
} catch (...) {
|
||||
error = "Unknown internal error during auth chain";
|
||||
return false;
|
||||
}
|
||||
loggedIn = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string Serialise() const {
|
||||
std::ostringstream o;
|
||||
o << "{\n"
|
||||
<< "\"msaAccessToken\":" << newauth::JsonStr(msa.accessToken) << ",\n"
|
||||
<< "\"msaRefreshToken\":" << newauth::JsonStr(msa.refreshToken) << ",\n"
|
||||
<< "\"msaExpireMs\":" << msa.expireMs << ",\n"
|
||||
<< "\"deviceId\":" << newauth::JsonStr(deviceId) << ",\n"
|
||||
<< "\"deviceKeyPriv\":" << newauth::JsonStr(Base64Encode(deviceKeyP256.privateBlob)) << ",\n"
|
||||
<< "\"deviceKeyPub\":" << newauth::JsonStr(Base64Encode(deviceKeyP256.publicBlob)) << "\n"
|
||||
<< "}";
|
||||
return o.str();
|
||||
}
|
||||
|
||||
static ECKeyPair RebuildP256(const std::vector<uint8_t>& priv,
|
||||
const std::vector<uint8_t>& pub) {
|
||||
if (pub.size() < sizeof(BCRYPT_ECCKEY_BLOB)) return {};
|
||||
const BCRYPT_ECCKEY_BLOB* hdr =
|
||||
reinterpret_cast<const BCRYPT_ECCKEY_BLOB*>(pub.data());
|
||||
ULONG cbKey = hdr->cbKey;
|
||||
if (pub.size() < 8 + (size_t)cbKey * 2) return {};
|
||||
std::vector<uint8_t> x(pub.begin()+8, pub.begin()+8+cbKey);
|
||||
std::vector<uint8_t> y(pub.begin()+8+cbKey, pub.begin()+8+cbKey*2);
|
||||
|
||||
ECKeyPair kp;
|
||||
kp.bitSize = 256; kp.privateBlob = priv; kp.publicBlob = pub;
|
||||
kp.x = x; kp.y = y;
|
||||
kp.publicKeyDER = BuildSubjectPublicKeyInfoDER(256, x, y);
|
||||
return kp;
|
||||
}
|
||||
|
||||
bool Deserialise(const std::string& json) {
|
||||
msa.accessToken = JsonGetString(json, "msaAccessToken");
|
||||
msa.refreshToken = JsonGetString(json, "msaRefreshToken");
|
||||
msa.expireMs = JsonGetInt(json, "msaExpireMs");
|
||||
deviceId = JsonGetString(json, "deviceId");
|
||||
auto priv = Base64Decode(JsonGetString(json, "deviceKeyPriv"));
|
||||
auto pub = Base64Decode(JsonGetString(json, "deviceKeyPub"));
|
||||
if (priv.empty() || pub.empty()) return false;
|
||||
deviceKeyP256 = RebuildP256(priv, pub);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
JavaAuthManager::JavaAuthManager() : m_impl(std::make_unique<Impl>()) {}
|
||||
JavaAuthManager::~JavaAuthManager() = default;
|
||||
|
||||
bool JavaAuthManager::Login(DeviceCodeCallback onDeviceCode,
|
||||
JavaSession& out, std::string& error, int timeoutSec)
|
||||
{
|
||||
return m_impl->DoFullAuth(onDeviceCode, out, error, timeoutSec);
|
||||
}
|
||||
|
||||
bool JavaAuthManager::Refresh(JavaSession& out, std::string& error) {
|
||||
m_impl->cancelRequested.store(false, std::memory_order_relaxed);
|
||||
if (!m_impl->RefreshMsaToken(error)) return false;
|
||||
return m_impl->DoAuthChain(out, error);
|
||||
}
|
||||
|
||||
bool JavaAuthManager::IsLoggedIn() const { return m_impl->loggedIn; }
|
||||
|
||||
void JavaAuthManager::Logout() {
|
||||
m_impl->msa = {};
|
||||
m_impl->javaXsts = {};
|
||||
m_impl->mcToken = {};
|
||||
m_impl->loggedIn = false;
|
||||
}
|
||||
|
||||
void JavaAuthManager::RequestCancel() {
|
||||
m_impl->cancelRequested.store(true, std::memory_order_release);
|
||||
m_impl->cancelCv.notify_all();
|
||||
}
|
||||
|
||||
bool JavaAuthManager::SaveTokens(const std::string& path) const {
|
||||
std::ofstream f(path);
|
||||
if (!f) return false;
|
||||
f << m_impl->Serialise();
|
||||
return f.good();
|
||||
}
|
||||
|
||||
bool JavaAuthManager::LoadTokens(const std::string& path) {
|
||||
std::ifstream f(path);
|
||||
if (!f) return false;
|
||||
std::string content((std::istreambuf_iterator<char>(f)),
|
||||
std::istreambuf_iterator<char>());
|
||||
return m_impl->Deserialise(content);
|
||||
}
|
||||
|
||||
std::string JavaAuthManager::SerializeTokens() const {
|
||||
return m_impl->Serialise();
|
||||
}
|
||||
|
||||
bool JavaAuthManager::DeserializeTokens(const std::string& json) {
|
||||
return m_impl->Deserialise(json);
|
||||
}
|
||||
|
||||
}
|
||||
956
newauth/src/newauthManager.cpp
Normal file
956
newauth/src/newauthManager.cpp
Normal file
|
|
@ -0,0 +1,956 @@
|
|||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#include "../include/newauthManager.h"
|
||||
#include "newauthInternal.h"
|
||||
#include "newauthDb.h"
|
||||
#include <cstdio>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
static void AuthLogImpl(const char* fmt, ...) {
|
||||
char buf[512];
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||
va_end(ap);
|
||||
#ifdef _WIN32
|
||||
OutputDebugStringA(buf);
|
||||
#endif
|
||||
FILE* f = _fsopen("newauth_debug.log", "a", _SH_DENYWR);
|
||||
if (f) { fputs(buf, f); fclose(f); }
|
||||
}
|
||||
#define AUTH_LOG(msg, ...) AuthLogImpl("[newauth] " msg "\n", ##__VA_ARGS__)
|
||||
|
||||
using namespace newauth;
|
||||
|
||||
newauthManager& newauthManager::Get() {
|
||||
static newauthManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
newauthManager::newauthManager() = default;
|
||||
|
||||
newauthManager::~newauthManager() {
|
||||
for (int i = 0; i < XUSER_MAX_COUNT; ++i) {
|
||||
++m_slots[i].generation;
|
||||
if (m_slots[i].auth)
|
||||
m_slots[i].auth->RequestCancel();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<newauth::JavaAuthManager> newauthManager::ResetSlotAuth(AuthSlot& s) {
|
||||
if (s.auth)
|
||||
s.auth->RequestCancel();
|
||||
|
||||
auto fresh = std::make_shared<newauth::JavaAuthManager>();
|
||||
s.auth = fresh;
|
||||
return fresh;
|
||||
}
|
||||
|
||||
newauth::JavaSession newauthManager::SynthesizeOfflineSession(const JavaAccountInfo& acct) {
|
||||
newauth::JavaSession s;
|
||||
s.username = acct.username;
|
||||
s.uuid = acct.uuid;
|
||||
s.accessToken = "";
|
||||
s.expireMs = 0;
|
||||
return s;
|
||||
}
|
||||
|
||||
bool newauthManager::LoadJavaAccountIndex() {
|
||||
auto& db = AuthDb::Get();
|
||||
if (!db.Open()) {
|
||||
AUTH_LOG("LoadJavaAccountIndex: DB open failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
db.MigrateFromJson(kJavaAccountsFile);
|
||||
|
||||
int activeIndex = -1;
|
||||
std::vector<AccountRow> rows;
|
||||
if (!db.LoadAccounts(activeIndex, rows)) {
|
||||
AUTH_LOG("LoadJavaAccountIndex: DB LoadAccounts failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_accountsMutex);
|
||||
m_javaAccounts.clear();
|
||||
|
||||
for (auto& row : rows) {
|
||||
JavaAccountInfo info;
|
||||
info.username = row.username;
|
||||
info.uuid = row.uuid;
|
||||
info.tokenFile = row.tokenKey;
|
||||
info.isOffline = row.isOffline;
|
||||
info.authProvider = row.authProvider;
|
||||
if (info.authProvider.empty())
|
||||
info.authProvider = info.isOffline ? "offline" : "mojang";
|
||||
if (!info.tokenFile.empty() || info.isOffline)
|
||||
m_javaAccounts.push_back(std::move(info));
|
||||
}
|
||||
|
||||
if (activeIndex >= 0 && activeIndex < (int)m_javaAccounts.size())
|
||||
m_slots[0].accountIndex = activeIndex;
|
||||
else
|
||||
m_slots[0].accountIndex = m_javaAccounts.empty() ? -1 : 0;
|
||||
|
||||
AUTH_LOG("LoadJavaAccountIndex: loaded %d accounts, active=%d",
|
||||
(int)m_javaAccounts.size(), m_slots[0].accountIndex.load());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool newauthManager::SaveJavaAccountIndex() const {
|
||||
std::lock_guard<std::mutex> lock(m_accountsMutex);
|
||||
|
||||
std::vector<AccountRow> rows;
|
||||
for (auto& a : m_javaAccounts) {
|
||||
AccountRow row;
|
||||
row.username = a.username;
|
||||
row.uuid = a.uuid;
|
||||
row.tokenKey = a.tokenFile;
|
||||
row.isOffline = a.isOffline;
|
||||
row.authProvider = a.authProvider.empty() ? "mojang" : a.authProvider;
|
||||
rows.push_back(std::move(row));
|
||||
}
|
||||
|
||||
int activeIndex = m_slots[0].accountIndex.load();
|
||||
if (!AuthDb::Get().SaveAccounts(activeIndex, rows)) {
|
||||
AUTH_LOG("SaveJavaAccountIndex: DB SaveAccounts failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
AUTH_LOG("SaveJavaAccountIndex: saved %d accounts, active=%d",
|
||||
(int)m_javaAccounts.size(), activeIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<newauthManager::JavaAccountInfo> newauthManager::GetJavaAccounts() const {
|
||||
std::lock_guard<std::mutex> lock(m_accountsMutex);
|
||||
return m_javaAccounts;
|
||||
}
|
||||
|
||||
std::string newauthManager::AllocTokenFile(const std::string& uuid) const {
|
||||
if (!uuid.empty())
|
||||
return uuid + ".json";
|
||||
for (int i = 0; ; ++i) {
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "java_auth_%d.json", i);
|
||||
std::string name(buf);
|
||||
bool taken = false;
|
||||
for (auto& a : m_javaAccounts) {
|
||||
if (a.tokenFile == name) { taken = true; break; }
|
||||
}
|
||||
if (!taken) return name;
|
||||
}
|
||||
}
|
||||
|
||||
const newauthManager::AuthSlot& newauthManager::GetSlot(int slot) const {
|
||||
if (slot < 0 || slot >= XUSER_MAX_COUNT) slot = 0;
|
||||
return m_slots[slot];
|
||||
}
|
||||
|
||||
newauth::JavaSession newauthManager::GetSlotSession(int slot) const {
|
||||
if (slot < 0 || slot >= XUSER_MAX_COUNT) slot = 0;
|
||||
auto& s = m_slots[slot];
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> alock(m_accountsMutex);
|
||||
if (s.accountIndex >= 0 && s.accountIndex < (int)m_javaAccounts.size()
|
||||
&& m_javaAccounts[s.accountIndex].isOffline)
|
||||
return SynthesizeOfflineSession(m_javaAccounts[s.accountIndex]);
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
return s.session;
|
||||
}
|
||||
|
||||
bool newauthManager::IsSlotLoggedIn(int slot) const {
|
||||
if (slot < 0 || slot >= XUSER_MAX_COUNT) return false;
|
||||
auto& s = m_slots[slot];
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> alock(m_accountsMutex);
|
||||
if (s.accountIndex >= 0 && s.accountIndex < (int)m_javaAccounts.size()) {
|
||||
if (m_javaAccounts[s.accountIndex].isOffline)
|
||||
return true;
|
||||
if (m_javaAccounts[s.accountIndex].authProvider == "elyby") {
|
||||
std::lock_guard<std::mutex> slock(s.mutex);
|
||||
return !s.elybyTokens.accessToken.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return s.auth && s.auth->IsLoggedIn();
|
||||
}
|
||||
|
||||
bool newauthManager::IsTokenExpiringSoon(int slot) const {
|
||||
if (slot < 0 || slot >= XUSER_MAX_COUNT) return false;
|
||||
auto& s = m_slots[slot];
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
if (s.session.expireMs <= 0) return false;
|
||||
return NowMs() > s.session.expireMs - 60000;
|
||||
}
|
||||
|
||||
bool newauthManager::IsAccountInUseByOtherSlot(int slot, int accountIndex) const {
|
||||
std::lock_guard<std::mutex> alock(m_accountsMutex);
|
||||
if (accountIndex < 0 || accountIndex >= (int)m_javaAccounts.size()) return false;
|
||||
|
||||
for (int i = 0; i < XUSER_MAX_COUNT; ++i) {
|
||||
if (i == slot) continue;
|
||||
if (m_slots[i].accountIndex == accountIndex) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void newauthManager::RunElybyRefresh(int slot, uint32_t gen, const std::string& tokenFile,
|
||||
bool failOpen, bool alwaysSaveIndex) {
|
||||
auto& s = m_slots[slot];
|
||||
std::string error;
|
||||
newauth::ElybyTokens tokens;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
tokens = s.elybyTokens;
|
||||
}
|
||||
bool ok = newauth::ElybyRefresh(tokens, error);
|
||||
if (s.generation != gen) return;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
if (ok) {
|
||||
s.elybyTokens = tokens;
|
||||
s.session.username = tokens.username;
|
||||
s.session.uuid = newauth::DashUuid(tokens.uuid);
|
||||
s.session.accessToken = tokens.accessToken;
|
||||
s.lastError.clear();
|
||||
s.state = State::Success;
|
||||
} else {
|
||||
s.lastError = error;
|
||||
s.state = failOpen ? State::Success : State::Failed;
|
||||
}
|
||||
s.cv.notify_all();
|
||||
}
|
||||
if (ok) {
|
||||
if (!tokenFile.empty())
|
||||
AuthDb::Get().SaveTokenBlob(tokenFile, newauth::ElybySerializeTokens(tokens));
|
||||
{
|
||||
std::lock_guard<std::mutex> alock(m_accountsMutex);
|
||||
if (s.accountIndex >= 0 && s.accountIndex < (int)m_javaAccounts.size()) {
|
||||
m_javaAccounts[s.accountIndex].username = tokens.username;
|
||||
m_javaAccounts[s.accountIndex].uuid = newauth::DashUuid(tokens.uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ok || alwaysSaveIndex)
|
||||
SaveJavaAccountIndex();
|
||||
}
|
||||
|
||||
void newauthManager::RefreshSlot(int slot) {
|
||||
if (slot < 0 || slot >= XUSER_MAX_COUNT) return;
|
||||
auto& s = m_slots[slot];
|
||||
|
||||
std::string provider;
|
||||
std::string elyTokenFile;
|
||||
{
|
||||
std::lock_guard<std::mutex> alock(m_accountsMutex);
|
||||
if (s.accountIndex >= 0 && s.accountIndex < (int)m_javaAccounts.size()) {
|
||||
provider = m_javaAccounts[s.accountIndex].authProvider;
|
||||
elyTokenFile = m_javaAccounts[s.accountIndex].tokenFile;
|
||||
}
|
||||
}
|
||||
|
||||
if (provider == "elyby") {
|
||||
uint32_t gen = ++s.generation;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
s.state = State::Authenticating;
|
||||
s.cv.notify_all();
|
||||
}
|
||||
std::thread([this, slot, gen, elyTokenFile]() {
|
||||
RunElybyRefresh(slot, gen, elyTokenFile, /*failOpen=*/false, /*alwaysSaveIndex=*/false);
|
||||
}).detach();
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t gen = ++s.generation;
|
||||
|
||||
auto authCopy = s.auth;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
s.state = State::Authenticating;
|
||||
s.cv.notify_all();
|
||||
}
|
||||
|
||||
std::thread([this, slot, gen, authCopy]() {
|
||||
auto& s = m_slots[slot];
|
||||
std::string error;
|
||||
newauth::JavaSession session;
|
||||
bool ok = false;
|
||||
try {
|
||||
ok = authCopy->Refresh(session, error);
|
||||
} catch (const std::exception& ex) {
|
||||
error = std::string("RefreshSlot network error: ") + ex.what();
|
||||
} catch (...) {
|
||||
error = "RefreshSlot unknown error";
|
||||
}
|
||||
|
||||
if (s.generation != gen) {
|
||||
AUTH_LOG("RefreshSlot(%d): generation mismatch (%u vs %u), discarding",
|
||||
slot, gen, s.generation.load());
|
||||
return;
|
||||
}
|
||||
|
||||
std::string tokenFile;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
if (ok) {
|
||||
s.session = session;
|
||||
s.lastError.clear();
|
||||
s.state = State::Success;
|
||||
} else {
|
||||
s.lastError = error;
|
||||
s.state = State::Failed;
|
||||
}
|
||||
s.cv.notify_all();
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
std::lock_guard<std::mutex> alock(m_accountsMutex);
|
||||
if (s.accountIndex >= 0 && s.accountIndex < (int)m_javaAccounts.size()) {
|
||||
m_javaAccounts[s.accountIndex].username = session.username;
|
||||
m_javaAccounts[s.accountIndex].uuid = session.uuid;
|
||||
tokenFile = m_javaAccounts[s.accountIndex].tokenFile;
|
||||
}
|
||||
}
|
||||
|
||||
AUTH_LOG("RefreshSlot(%d): ok=%d, username='%s', error='%s'",
|
||||
slot, (int)ok, ok ? session.username.c_str() : "", error.c_str());
|
||||
|
||||
if (ok && !tokenFile.empty()) {
|
||||
AuthDb::Get().SaveTokenBlob(tokenFile, authCopy->SerializeTokens());
|
||||
SaveJavaAccountIndex();
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
|
||||
bool newauthManager::SetAccountForSlot(int slot, int accountIndex) {
|
||||
if (slot < 0 || slot >= XUSER_MAX_COUNT) return false;
|
||||
auto& s = m_slots[slot];
|
||||
|
||||
bool isOffline = false;
|
||||
bool isElyby = false;
|
||||
std::string tokenFile;
|
||||
{
|
||||
std::lock_guard<std::mutex> alock(m_accountsMutex);
|
||||
if (accountIndex < 0 || accountIndex >= (int)m_javaAccounts.size()) return false;
|
||||
isOffline = m_javaAccounts[accountIndex].isOffline;
|
||||
isElyby = (m_javaAccounts[accountIndex].authProvider == "elyby");
|
||||
tokenFile = m_javaAccounts[accountIndex].tokenFile;
|
||||
}
|
||||
|
||||
uint32_t gen = ++s.generation;
|
||||
|
||||
if (isOffline) {
|
||||
newauth::JavaSession offlineSession;
|
||||
std::string offlineName;
|
||||
{
|
||||
std::lock_guard<std::mutex> alock(m_accountsMutex);
|
||||
offlineSession = SynthesizeOfflineSession(m_javaAccounts[accountIndex]);
|
||||
offlineName = m_javaAccounts[accountIndex].username;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
s.accountIndex = accountIndex;
|
||||
s.session = offlineSession;
|
||||
s.state = State::Success;
|
||||
s.cv.notify_all();
|
||||
}
|
||||
AUTH_LOG("SetAccountForSlot(%d): switched to offline account %d '%s'",
|
||||
slot, accountIndex, offlineName.c_str());
|
||||
(void)gen;
|
||||
} else if (isElyby) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
s.accountIndex = accountIndex;
|
||||
}
|
||||
|
||||
AUTH_LOG("SetAccountForSlot(%d): switching to elyby account %d, tokenFile='%s'",
|
||||
slot, accountIndex, tokenFile.c_str());
|
||||
|
||||
newauth::ElybyTokens elyTokens;
|
||||
{
|
||||
std::string blob = AuthDb::Get().LoadTokenBlob(tokenFile);
|
||||
if (blob.empty() || !newauth::ElybyDeserializeTokens(blob, elyTokens)) {
|
||||
AUTH_LOG("SetAccountForSlot(%d): ElybyLoadTokens from DB failed", slot);
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
s.state = State::Failed;
|
||||
s.cv.notify_all();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
s.elybyTokens = elyTokens;
|
||||
s.session.username = elyTokens.username;
|
||||
s.session.uuid = newauth::DashUuid(elyTokens.uuid);
|
||||
s.session.accessToken = elyTokens.accessToken;
|
||||
s.session.expireMs = 0;
|
||||
s.state = State::Authenticating;
|
||||
s.cv.notify_all();
|
||||
}
|
||||
|
||||
uint32_t elyGen = ++s.generation;
|
||||
std::thread([this, slot, elyGen, tokenFile]() {
|
||||
RunElybyRefresh(slot, elyGen, tokenFile, /*failOpen=*/true, /*alwaysSaveIndex=*/true);
|
||||
}).detach();
|
||||
} else {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
s.accountIndex = accountIndex;
|
||||
}
|
||||
|
||||
AUTH_LOG("SetAccountForSlot(%d): switching to online account %d, tokenFile='%s'",
|
||||
slot, accountIndex, tokenFile.c_str());
|
||||
|
||||
auto freshAuth = ResetSlotAuth(s);
|
||||
|
||||
{
|
||||
std::string blob = AuthDb::Get().LoadTokenBlob(tokenFile);
|
||||
if (blob.empty() || !freshAuth->DeserializeTokens(blob)) {
|
||||
AUTH_LOG("SetAccountForSlot(%d): LoadTokens from DB failed", slot);
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
s.state = State::Failed;
|
||||
s.cv.notify_all();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
RefreshSlot(slot);
|
||||
}
|
||||
|
||||
SaveJavaAccountIndex();
|
||||
return true;
|
||||
}
|
||||
|
||||
void newauthManager::ClearSlot(int slot) {
|
||||
if (slot < 0 || slot >= XUSER_MAX_COUNT) return;
|
||||
auto& s = m_slots[slot];
|
||||
|
||||
++s.generation;
|
||||
|
||||
ResetSlotAuth(s);
|
||||
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
s.session = {};
|
||||
s.accountIndex = -1;
|
||||
s.state = State::Idle;
|
||||
s.lastError.clear();
|
||||
s.cv.notify_all();
|
||||
}
|
||||
|
||||
bool newauthManager::WaitForSlotReady(int slot, int timeoutMs) const {
|
||||
if (slot < 0 || slot >= XUSER_MAX_COUNT) return false;
|
||||
auto& s = m_slots[slot];
|
||||
std::unique_lock<std::mutex> lock(s.mutex);
|
||||
bool ok = s.cv.wait_for(lock, std::chrono::milliseconds(timeoutMs), [&] {
|
||||
State st = s.state.load();
|
||||
return st != State::Authenticating && st != State::WaitingForCode;
|
||||
});
|
||||
return ok && (s.state == State::Success);
|
||||
}
|
||||
|
||||
int newauthManager::GetActiveJavaAccountIndex() const {
|
||||
return m_slots[0].accountIndex;
|
||||
}
|
||||
|
||||
newauth::JavaSession newauthManager::GetJavaSession() const {
|
||||
return GetSlotSession(0);
|
||||
}
|
||||
|
||||
bool newauthManager::IsJavaLoggedIn() const {
|
||||
return IsSlotLoggedIn(0);
|
||||
}
|
||||
|
||||
int newauthManager::AddOfflineJavaAccount(const std::string& username) {
|
||||
if (username.empty()) return -1;
|
||||
|
||||
std::string uuid = newauth::GenerateOfflineUuid(username);
|
||||
if (uuid.empty()) return -1;
|
||||
|
||||
std::lock_guard<std::mutex> alock(m_accountsMutex);
|
||||
|
||||
for (int i = 0; i < (int)m_javaAccounts.size(); ++i) {
|
||||
if (m_javaAccounts[i].uuid == uuid) {
|
||||
m_javaAccounts[i].username = username;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_slots[0].mutex);
|
||||
m_slots[0].accountIndex = i;
|
||||
m_slots[0].session = SynthesizeOfflineSession(m_javaAccounts[i]);
|
||||
m_slots[0].state = State::Success;
|
||||
m_slots[0].cv.notify_all();
|
||||
}
|
||||
|
||||
AUTH_LOG("AddOfflineJavaAccount: updated existing offline account idx=%d '%s'",
|
||||
i, username.c_str());
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
JavaAccountInfo info;
|
||||
info.username = username;
|
||||
info.uuid = uuid;
|
||||
info.tokenFile = "";
|
||||
info.isOffline = true;
|
||||
info.authProvider = "offline";
|
||||
m_javaAccounts.push_back(std::move(info));
|
||||
int idx = (int)m_javaAccounts.size() - 1;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_slots[0].mutex);
|
||||
m_slots[0].accountIndex = idx;
|
||||
m_slots[0].session = SynthesizeOfflineSession(m_javaAccounts[idx]);
|
||||
m_slots[0].state = State::Success;
|
||||
m_slots[0].cv.notify_all();
|
||||
}
|
||||
|
||||
AUTH_LOG("AddOfflineJavaAccount: added new offline account idx=%d '%s' uuid='%s'",
|
||||
idx, username.c_str(), uuid.c_str());
|
||||
return idx;
|
||||
}
|
||||
|
||||
void newauthManager::BeginAddJavaAccount(DeviceCodeCb onDeviceCode,
|
||||
JavaCompleteCb onComplete,
|
||||
int timeoutSeconds)
|
||||
{
|
||||
auto& s = m_slots[0];
|
||||
|
||||
uint32_t gen = ++s.generation;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_deviceCodeMutex);
|
||||
m_javaDeviceCode.clear();
|
||||
m_javaDirectUri.clear();
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
s.state = State::WaitingForCode;
|
||||
s.cv.notify_all();
|
||||
}
|
||||
|
||||
auto freshAuth = ResetSlotAuth(s);
|
||||
|
||||
std::thread([this, freshAuth, onDeviceCode, onComplete, timeoutSeconds, gen]() {
|
||||
try {
|
||||
auto& s = m_slots[0];
|
||||
|
||||
newauth::JavaSession session;
|
||||
std::string error;
|
||||
|
||||
bool ok = freshAuth->Login(
|
||||
[&](const newauth::DeviceCodeInfo& dc) {
|
||||
if (s.generation != gen) return;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
s.state = State::WaitingForCode;
|
||||
s.cv.notify_all();
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_deviceCodeMutex);
|
||||
m_javaDeviceCode = dc.userCode;
|
||||
m_javaDirectUri = dc.directUri;
|
||||
}
|
||||
if (onDeviceCode) onDeviceCode(dc);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
s.state = State::Authenticating;
|
||||
s.cv.notify_all();
|
||||
}
|
||||
},
|
||||
session, error, timeoutSeconds);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
if (s.generation != gen) {
|
||||
AUTH_LOG("BeginAddJavaAccount: gen %u stale (current %u), discarding",
|
||||
gen, s.generation.load());
|
||||
if (onComplete) onComplete(false, {}, "cancelled");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
std::string tokenFile;
|
||||
{
|
||||
std::lock_guard<std::mutex> alock(m_accountsMutex);
|
||||
|
||||
int existingIdx = -1;
|
||||
for (int i = 0; i < (int)m_javaAccounts.size(); ++i) {
|
||||
if (m_javaAccounts[i].uuid == session.uuid) {
|
||||
existingIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingIdx >= 0) {
|
||||
m_javaAccounts[existingIdx].username = session.username;
|
||||
tokenFile = m_javaAccounts[existingIdx].tokenFile;
|
||||
s.accountIndex = existingIdx;
|
||||
AUTH_LOG("BeginAddJavaAccount: updated existing account idx=%d '%s'",
|
||||
existingIdx, session.username.c_str());
|
||||
} else {
|
||||
JavaAccountInfo info;
|
||||
info.username = session.username;
|
||||
info.uuid = session.uuid;
|
||||
info.tokenFile = AllocTokenFile(session.uuid);
|
||||
info.authProvider = "mojang";
|
||||
tokenFile = info.tokenFile;
|
||||
m_javaAccounts.push_back(std::move(info));
|
||||
s.accountIndex = (int)m_javaAccounts.size() - 1;
|
||||
AUTH_LOG("BeginAddJavaAccount: added new account idx=%d '%s' file='%s'",
|
||||
s.accountIndex.load(), session.username.c_str(),
|
||||
m_javaAccounts.back().tokenFile.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (!tokenFile.empty())
|
||||
AuthDb::Get().SaveTokenBlob(tokenFile, freshAuth->SerializeTokens());
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
s.session = session;
|
||||
s.lastError.clear();
|
||||
s.state = State::Success;
|
||||
s.cv.notify_all();
|
||||
}
|
||||
|
||||
SaveJavaAccountIndex();
|
||||
} else {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
s.lastError = error;
|
||||
s.state = State::Failed;
|
||||
s.cv.notify_all();
|
||||
}
|
||||
|
||||
std::string reloadFile;
|
||||
{
|
||||
std::lock_guard<std::mutex> alock(m_accountsMutex);
|
||||
if (s.accountIndex >= 0 && s.accountIndex < (int)m_javaAccounts.size())
|
||||
reloadFile = m_javaAccounts[s.accountIndex].tokenFile;
|
||||
}
|
||||
if (!reloadFile.empty()) {
|
||||
std::string blob = AuthDb::Get().LoadTokenBlob(reloadFile);
|
||||
if (!blob.empty()) freshAuth->DeserializeTokens(blob);
|
||||
}
|
||||
}
|
||||
|
||||
if (onComplete) onComplete(ok, session, error);
|
||||
} catch (const std::exception& ex) {
|
||||
AUTH_LOG("BeginAddJavaAccount: uncaught exception: %s", ex.what());
|
||||
auto& s = m_slots[0];
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
s.lastError = std::string("Auth network error: ") + ex.what();
|
||||
s.state = State::Failed;
|
||||
s.cv.notify_all();
|
||||
}
|
||||
if (onComplete) onComplete(false, {}, s.lastError);
|
||||
} catch (...) {
|
||||
AUTH_LOG("BeginAddJavaAccount: unknown uncaught exception");
|
||||
auto& s = m_slots[0];
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
s.lastError = "Unknown auth error";
|
||||
s.state = State::Failed;
|
||||
s.cv.notify_all();
|
||||
}
|
||||
if (onComplete) onComplete(false, {}, "Unknown auth error");
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
|
||||
void newauthManager::BeginAddElybyAccount(const std::string& username, const std::string& password,
|
||||
ElybyCompleteCb onComplete, Elyby2FACb on2FA)
|
||||
{
|
||||
auto& s = m_slots[0];
|
||||
uint32_t gen = ++s.generation;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
s.state = State::Authenticating;
|
||||
s.cv.notify_all();
|
||||
}
|
||||
|
||||
std::thread([this, username, password, onComplete, on2FA, gen]() {
|
||||
try {
|
||||
auto& s = m_slots[0];
|
||||
newauth::ElybyTokens tokens;
|
||||
std::string error;
|
||||
|
||||
bool ok = newauth::ElybyLogin(username, password, tokens, error);
|
||||
|
||||
if (!ok && error == "elyby_2fa_required") {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
s.state = State::WaitingForCode;
|
||||
s.cv.notify_all();
|
||||
}
|
||||
if (on2FA) on2FA();
|
||||
if (onComplete) onComplete(false, {}, "elyby_2fa_required");
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
if (s.generation != gen) {
|
||||
if (onComplete) onComplete(false, {}, "cancelled");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
std::string dashedUuid = newauth::DashUuid(tokens.uuid);
|
||||
std::string tokenFile;
|
||||
{
|
||||
std::lock_guard<std::mutex> alock(m_accountsMutex);
|
||||
|
||||
int existingIdx = -1;
|
||||
for (int i = 0; i < (int)m_javaAccounts.size(); ++i) {
|
||||
if (m_javaAccounts[i].uuid == dashedUuid) {
|
||||
existingIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingIdx >= 0) {
|
||||
m_javaAccounts[existingIdx].username = tokens.username;
|
||||
m_javaAccounts[existingIdx].authProvider = "elyby";
|
||||
tokenFile = m_javaAccounts[existingIdx].tokenFile;
|
||||
s.accountIndex = existingIdx;
|
||||
} else {
|
||||
JavaAccountInfo info;
|
||||
info.username = tokens.username;
|
||||
info.uuid = dashedUuid;
|
||||
info.tokenFile = AllocTokenFile(dashedUuid);
|
||||
info.isOffline = false;
|
||||
info.authProvider = "elyby";
|
||||
tokenFile = info.tokenFile;
|
||||
m_javaAccounts.push_back(std::move(info));
|
||||
s.accountIndex = (int)m_javaAccounts.size() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!tokenFile.empty())
|
||||
AuthDb::Get().SaveTokenBlob(tokenFile, newauth::ElybySerializeTokens(tokens));
|
||||
|
||||
newauth::JavaSession session;
|
||||
session.username = tokens.username;
|
||||
session.uuid = dashedUuid;
|
||||
session.accessToken = tokens.accessToken;
|
||||
session.expireMs = 0;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
s.elybyTokens = tokens;
|
||||
s.session = session;
|
||||
s.lastError.clear();
|
||||
s.state = State::Success;
|
||||
s.cv.notify_all();
|
||||
}
|
||||
|
||||
SaveJavaAccountIndex();
|
||||
if (onComplete) onComplete(true, session, "");
|
||||
} else {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
s.lastError = error;
|
||||
s.state = State::Failed;
|
||||
s.cv.notify_all();
|
||||
}
|
||||
if (onComplete) onComplete(false, {}, error);
|
||||
}
|
||||
} catch (const std::exception& ex) {
|
||||
auto& s = m_slots[0];
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
s.lastError = std::string("Elyby auth error: ") + ex.what();
|
||||
s.state = State::Failed;
|
||||
s.cv.notify_all();
|
||||
}
|
||||
if (onComplete) onComplete(false, {}, s.lastError);
|
||||
} catch (...) {
|
||||
auto& s = m_slots[0];
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
s.lastError = "Unknown elyby auth error";
|
||||
s.state = State::Failed;
|
||||
s.cv.notify_all();
|
||||
}
|
||||
if (onComplete) onComplete(false, {}, "Unknown elyby auth error");
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
|
||||
bool newauthManager::RemoveJavaAccount(int index) {
|
||||
std::lock_guard<std::mutex> alock(m_accountsMutex);
|
||||
if (index < 0 || index >= (int)m_javaAccounts.size()) return false;
|
||||
|
||||
AUTH_LOG("RemoveJavaAccount: removing index %d ('%s')",
|
||||
index, m_javaAccounts[index].username.c_str());
|
||||
|
||||
std::remove(m_javaAccounts[index].tokenFile.c_str());
|
||||
m_javaAccounts.erase(m_javaAccounts.begin() + index);
|
||||
|
||||
for (int i = 0; i < XUSER_MAX_COUNT; ++i) {
|
||||
auto& s = m_slots[i];
|
||||
if (s.accountIndex == index) {
|
||||
if (m_javaAccounts.empty()) {
|
||||
s.accountIndex = -1;
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
s.session = {};
|
||||
s.state = State::Idle;
|
||||
s.cv.notify_all();
|
||||
} else {
|
||||
s.accountIndex = 0;
|
||||
}
|
||||
} else if (s.accountIndex > index) {
|
||||
--s.accountIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void newauthManager::TryRestoreActiveJavaAccount() {
|
||||
AUTH_LOG("TryRestoreActiveJavaAccount called (already attempted=%d)",
|
||||
(int)m_javaRestoreAttempted);
|
||||
|
||||
if (m_javaRestoreAttempted) return;
|
||||
m_javaRestoreAttempted = true;
|
||||
|
||||
if (!LoadJavaAccountIndex()) {
|
||||
auto& s = m_slots[0];
|
||||
std::string legacyBlob;
|
||||
{
|
||||
std::ifstream lf("java_auth.json");
|
||||
if (lf) legacyBlob.assign((std::istreambuf_iterator<char>(lf)),
|
||||
std::istreambuf_iterator<char>());
|
||||
}
|
||||
if (!legacyBlob.empty() && s.auth->DeserializeTokens(legacyBlob)) {
|
||||
AUTH_LOG("TryRestoreActiveJavaAccount: migrating legacy java_auth.json");
|
||||
std::lock_guard<std::mutex> alock(m_accountsMutex);
|
||||
JavaAccountInfo info;
|
||||
info.username = "";
|
||||
info.uuid = "";
|
||||
info.tokenFile = "legacy_migrated";
|
||||
AuthDb::Get().SaveTokenBlob(info.tokenFile, s.auth->SerializeTokens());
|
||||
m_javaAccounts.push_back(std::move(info));
|
||||
s.accountIndex = 0;
|
||||
SaveJavaAccountIndex();
|
||||
std::remove("java_auth.json");
|
||||
} else {
|
||||
AUTH_LOG("TryRestoreActiveJavaAccount: no accounts found");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto& s = m_slots[0];
|
||||
int activeIdx = s.accountIndex;
|
||||
|
||||
std::string tokenFile;
|
||||
bool isOffline = false;
|
||||
bool isElyby = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> alock(m_accountsMutex);
|
||||
if (activeIdx < 0 || activeIdx >= (int)m_javaAccounts.size()) {
|
||||
AUTH_LOG("TryRestoreActiveJavaAccount: no active account");
|
||||
return;
|
||||
}
|
||||
isOffline = m_javaAccounts[activeIdx].isOffline;
|
||||
isElyby = (m_javaAccounts[activeIdx].authProvider == "elyby");
|
||||
tokenFile = m_javaAccounts[activeIdx].tokenFile;
|
||||
}
|
||||
|
||||
if (isElyby) {
|
||||
AUTH_LOG("TryRestoreActiveJavaAccount: elyby account %d, loading tokens from '%s'",
|
||||
activeIdx, tokenFile.c_str());
|
||||
newauth::ElybyTokens elyTokens;
|
||||
{
|
||||
std::string blob = AuthDb::Get().LoadTokenBlob(tokenFile);
|
||||
if (blob.empty() || !newauth::ElybyDeserializeTokens(blob, elyTokens)) {
|
||||
AUTH_LOG("TryRestoreActiveJavaAccount: ElybyLoadTokens from DB failed for '%s'", tokenFile.c_str());
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
s.state = State::Failed;
|
||||
s.lastError = "Failed to load ely.by tokens: " + tokenFile;
|
||||
s.cv.notify_all();
|
||||
return;
|
||||
}
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
s.elybyTokens = elyTokens;
|
||||
s.session.username = elyTokens.username;
|
||||
s.session.uuid = newauth::DashUuid(elyTokens.uuid);
|
||||
s.session.accessToken = elyTokens.accessToken;
|
||||
s.session.expireMs = 0;
|
||||
s.state = State::Success;
|
||||
s.cv.notify_all();
|
||||
}
|
||||
RefreshSlot(0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isOffline) {
|
||||
AUTH_LOG("TryRestoreActiveJavaAccount: offline account %d, no refresh needed", activeIdx);
|
||||
newauth::JavaSession offlineSession;
|
||||
{
|
||||
std::lock_guard<std::mutex> alock(m_accountsMutex);
|
||||
offlineSession = SynthesizeOfflineSession(m_javaAccounts[activeIdx]);
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
s.session = offlineSession;
|
||||
s.state = State::Success;
|
||||
s.cv.notify_all();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
AUTH_LOG("TryRestoreActiveJavaAccount: loading tokenFile='%s' for account %d",
|
||||
tokenFile.c_str(), activeIdx);
|
||||
|
||||
{
|
||||
std::string blob = AuthDb::Get().LoadTokenBlob(tokenFile);
|
||||
if (blob.empty() || !s.auth->DeserializeTokens(blob)) {
|
||||
AUTH_LOG("TryRestoreActiveJavaAccount: LoadTokens from DB failed for '%s'", tokenFile.c_str());
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
s.state = State::Failed;
|
||||
s.lastError = "Failed to load token: " + tokenFile;
|
||||
s.cv.notify_all();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
RefreshSlot(0);
|
||||
}
|
||||
|
||||
std::string newauthManager::GetJavaDeviceCode() const {
|
||||
std::lock_guard<std::mutex> lock(m_deviceCodeMutex);
|
||||
return m_javaDeviceCode;
|
||||
}
|
||||
|
||||
std::string newauthManager::GetJavaDirectUri() const {
|
||||
std::lock_guard<std::mutex> lock(m_deviceCodeMutex);
|
||||
return m_javaDirectUri;
|
||||
}
|
||||
|
||||
std::string newauthManager::GetLastError() const {
|
||||
auto& s = m_slots[0];
|
||||
std::lock_guard<std::mutex> lock(s.mutex);
|
||||
return s.lastError;
|
||||
}
|
||||
410
newauth/src/newauthSession.cpp
Normal file
410
newauth/src/newauthSession.cpp
Normal file
|
|
@ -0,0 +1,410 @@
|
|||
#include "../include/newauth.h"
|
||||
#include "newauthCrypto.h"
|
||||
#include "newauthHttp.h"
|
||||
#include "newauthInternal.h"
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <bcrypt.h>
|
||||
#include <wincodec.h>
|
||||
#include <sstream>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#pragma comment(lib, "bcrypt.lib")
|
||||
#pragma comment(lib, "windowscodecs.lib")
|
||||
#pragma comment(lib, "ole32.lib")
|
||||
|
||||
namespace newauth {
|
||||
|
||||
using namespace newauth;
|
||||
|
||||
std::string MakeSkinKey(const std::string& uuid) {
|
||||
return "mojang_skin_" + UndashUuid(uuid) + ".png";
|
||||
}
|
||||
|
||||
std::string UndashUuid(const std::string& dashed) {
|
||||
std::string out;
|
||||
out.reserve(32);
|
||||
for (char c : dashed) {
|
||||
if (c != '-') out += c;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string DashUuid(const std::string& u) {
|
||||
if (u.size() < 32) return u;
|
||||
return u.substr(0, 8) + "-" + u.substr(8, 4) + "-" + u.substr(12, 4)
|
||||
+ "-" + u.substr(16, 4) + "-" + u.substr(20);
|
||||
}
|
||||
|
||||
std::string GenerateOfflineUuid(const std::string& username) {
|
||||
std::string input = "OfflinePlayer:" + username;
|
||||
auto md5 = ComputeMD5(input.data(), input.size());
|
||||
if (md5.size() < 16) return "";
|
||||
uint8_t hash[16];
|
||||
memcpy(hash, md5.data(), 16);
|
||||
|
||||
hash[6] = (hash[6] & 0x0F) | 0x30;
|
||||
hash[8] = (hash[8] & 0x3F) | 0x80;
|
||||
|
||||
char hex[33];
|
||||
for (int i = 0; i < 16; i++)
|
||||
snprintf(hex + i * 2, 3, "%02x", hash[i]);
|
||||
hex[32] = '\0';
|
||||
|
||||
return DashUuid(std::string(hex, 32));
|
||||
}
|
||||
|
||||
static uint8_t HexNibble(char c) {
|
||||
if (c >= '0' && c <= '9') return (uint8_t)(c - '0');
|
||||
if (c >= 'a' && c <= 'f') return (uint8_t)(c - 'a' + 10);
|
||||
if (c >= 'A' && c <= 'F') return (uint8_t)(c - 'A' + 10);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Uuid128 ParseUuid128(const std::string& dashed) {
|
||||
std::string hex = UndashUuid(dashed);
|
||||
if (hex.size() != 32) return {};
|
||||
|
||||
Uuid128 result;
|
||||
result.hi = 0;
|
||||
result.lo = 0;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
uint8_t byte = (HexNibble(hex[i * 2]) << 4) | HexNibble(hex[i * 2 + 1]);
|
||||
if (i < 8)
|
||||
result.hi = (result.hi << 8) | byte;
|
||||
else
|
||||
result.lo = (result.lo << 8) | byte;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static AuthErrorCode ClassifyHttpStatus(int status) {
|
||||
if (status == 401 || status == 403) return AuthErrorCode::InvalidCredentials;
|
||||
if (status == 429) return AuthErrorCode::RateLimited;
|
||||
if (status >= 500 && status < 600) return AuthErrorCode::ServerUnavailable;
|
||||
return AuthErrorCode::HttpError;
|
||||
}
|
||||
|
||||
static bool JoinServerImpl(const std::string& url,
|
||||
const std::string& accessToken,
|
||||
const std::string& undashedUuid,
|
||||
const std::string& serverId,
|
||||
const std::string& errorPrefix,
|
||||
std::string& error)
|
||||
{
|
||||
std::string body = "{\"accessToken\":\"" + JsonEscape(accessToken)
|
||||
+ "\",\"selectedProfile\":\"" + JsonEscape(undashedUuid)
|
||||
+ "\",\"serverId\":\"" + JsonEscape(serverId) + "\"}";
|
||||
|
||||
try {
|
||||
auto resp = HttpPost(url, body, "application/json");
|
||||
|
||||
if (resp.statusCode == 204 || resp.statusCode == 200)
|
||||
return true;
|
||||
|
||||
error = errorPrefix + " HTTP " + std::to_string(resp.statusCode);
|
||||
if (!resp.body.empty()) {
|
||||
std::string msg = JsonGetString(resp.body, "errorMessage");
|
||||
if (!msg.empty()) error += ": " + msg;
|
||||
else error += " body: " + resp.body.substr(0, 200);
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
error = errorPrefix + " network error: " + e.what();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool JoinServer(const std::string& accessToken,
|
||||
const std::string& undashedUuid,
|
||||
const std::string& serverId,
|
||||
std::string& error)
|
||||
{
|
||||
return JoinServerImpl(
|
||||
"https://sessionserver.mojang.com/session/minecraft/join",
|
||||
accessToken, undashedUuid, serverId, "JoinServer", error);
|
||||
}
|
||||
|
||||
static void ParseTextureProperties(const std::string& body, std::string& skinUrl, std::string& capeUrl) {
|
||||
size_t propsPos = body.find("\"properties\"");
|
||||
if (propsPos == std::string::npos) return;
|
||||
|
||||
size_t texNamePos = body.find("\"textures\"", propsPos);
|
||||
if (texNamePos == std::string::npos) return;
|
||||
|
||||
std::string b64Value = JsonGetString(body.substr(texNamePos), "value");
|
||||
if (b64Value.empty()) return;
|
||||
|
||||
std::string decoded = Base64DecodeStr(b64Value);
|
||||
if (decoded.empty()) return;
|
||||
|
||||
std::string skinObj = JsonRawValue(decoded, "SKIN");
|
||||
if (!skinObj.empty())
|
||||
skinUrl = JsonGetString(skinObj, "url");
|
||||
|
||||
std::string capeObj = JsonRawValue(decoded, "CAPE");
|
||||
if (!capeObj.empty())
|
||||
capeUrl = JsonGetString(capeObj, "url");
|
||||
}
|
||||
|
||||
static HasJoinedResult HasJoinedImpl(const std::string& baseUrl,
|
||||
const std::string& username,
|
||||
const std::string& serverId,
|
||||
const std::string& errorPrefix,
|
||||
std::string& error)
|
||||
{
|
||||
std::string url = baseUrl + "?username="
|
||||
+ UrlEncode(username) + "&serverId=" + UrlEncode(serverId);
|
||||
|
||||
HasJoinedResult result;
|
||||
|
||||
try {
|
||||
auto resp = HttpGet(url);
|
||||
|
||||
if (resp.statusCode == 200 && !resp.body.empty()) {
|
||||
result.success = true;
|
||||
result.uuid = JsonGetString(resp.body, "id");
|
||||
result.username = JsonGetString(resp.body, "name");
|
||||
ParseTextureProperties(resp.body, result.skinUrl, result.capeUrl);
|
||||
return result;
|
||||
}
|
||||
|
||||
result.success = false;
|
||||
if (resp.statusCode == 204) {
|
||||
error = "Player has not joined (session not found)";
|
||||
result.error = { AuthErrorCode::InvalidCredentials, error, 204 };
|
||||
} else {
|
||||
error = errorPrefix + " HTTP " + std::to_string(resp.statusCode);
|
||||
result.error = { ClassifyHttpStatus(resp.statusCode), error, resp.statusCode };
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
error = errorPrefix + " network error: " + e.what();
|
||||
result.success = false;
|
||||
result.error = { AuthErrorCode::NetworkError, error, 0 };
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
HasJoinedResult HasJoined(const std::string& username,
|
||||
const std::string& serverId,
|
||||
std::string& error)
|
||||
{
|
||||
return HasJoinedImpl(
|
||||
"https://sessionserver.mojang.com/session/minecraft/hasJoined",
|
||||
username, serverId, "HasJoined", error);
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> CropSkinTo64x32(const std::vector<uint8_t>& pngData) {
|
||||
try {
|
||||
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
bool needUninit = SUCCEEDED(hr);
|
||||
|
||||
IWICImagingFactory* factory = nullptr;
|
||||
IWICStream* stream = nullptr;
|
||||
IWICBitmapDecoder* decoder = nullptr;
|
||||
IWICBitmapFrameDecode*frame = nullptr;
|
||||
IWICFormatConverter* converter = nullptr;
|
||||
IWICStream* outStream = nullptr;
|
||||
IStream* memStream = nullptr;
|
||||
IWICBitmapEncoder* encoder = nullptr;
|
||||
IWICBitmapFrameEncode*outFrame = nullptr;
|
||||
|
||||
auto cleanup = [&]() {
|
||||
if (outFrame) outFrame->Release();
|
||||
if (encoder) encoder->Release();
|
||||
if (outStream) outStream->Release();
|
||||
if (memStream) memStream->Release();
|
||||
if (converter) converter->Release();
|
||||
if (frame) frame->Release();
|
||||
if (decoder) decoder->Release();
|
||||
if (stream) stream->Release();
|
||||
if (factory) factory->Release();
|
||||
if (needUninit) CoUninitialize();
|
||||
};
|
||||
|
||||
hr = CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER,
|
||||
IID_PPV_ARGS(&factory));
|
||||
if (FAILED(hr)) { cleanup(); return pngData; }
|
||||
|
||||
#define WIC_CHECK(expr) do { hr = (expr); if (FAILED(hr)) { cleanup(); return pngData; } } while(0)
|
||||
|
||||
WIC_CHECK(factory->CreateStream(&stream));
|
||||
WIC_CHECK(stream->InitializeFromMemory((BYTE*)pngData.data(), (DWORD)pngData.size()));
|
||||
WIC_CHECK(factory->CreateDecoderFromStream(stream, nullptr, WICDecodeMetadataCacheOnDemand, &decoder));
|
||||
WIC_CHECK(decoder->GetFrame(0, &frame));
|
||||
|
||||
UINT width = 0, height = 0;
|
||||
WIC_CHECK(frame->GetSize(&width, &height));
|
||||
|
||||
if (width != 64 || height != 64) { cleanup(); return pngData; }
|
||||
|
||||
WIC_CHECK(factory->CreateFormatConverter(&converter));
|
||||
WIC_CHECK(converter->Initialize(frame, GUID_WICPixelFormat32bppBGRA,
|
||||
WICBitmapDitherTypeNone, nullptr, 0.0, WICBitmapPaletteTypeCustom));
|
||||
|
||||
const UINT cropW = 64, cropH = 32;
|
||||
const UINT stride = cropW * 4;
|
||||
std::vector<BYTE> pixels(stride * cropH);
|
||||
|
||||
WICRect cropRect = { 0, 0, (INT)cropW, (INT)cropH };
|
||||
WIC_CHECK(converter->CopyPixels(&cropRect, stride, (UINT)pixels.size(), pixels.data()));
|
||||
|
||||
WIC_CHECK(factory->CreateStream(&outStream));
|
||||
hr = CreateStreamOnHGlobal(nullptr, TRUE, &memStream);
|
||||
if (FAILED(hr)) { cleanup(); return pngData; }
|
||||
WIC_CHECK(outStream->InitializeFromIStream(memStream));
|
||||
|
||||
WIC_CHECK(factory->CreateEncoder(GUID_ContainerFormatPng, nullptr, &encoder));
|
||||
WIC_CHECK(encoder->Initialize(outStream, WICBitmapEncoderNoCache));
|
||||
|
||||
WIC_CHECK(encoder->CreateNewFrame(&outFrame, nullptr));
|
||||
WIC_CHECK(outFrame->Initialize(nullptr));
|
||||
WIC_CHECK(outFrame->SetSize(cropW, cropH));
|
||||
|
||||
WICPixelFormatGUID pixFmt = GUID_WICPixelFormat32bppBGRA;
|
||||
WIC_CHECK(outFrame->SetPixelFormat(&pixFmt));
|
||||
WIC_CHECK(outFrame->WritePixels(cropH, stride, (UINT)pixels.size(), pixels.data()));
|
||||
WIC_CHECK(outFrame->Commit());
|
||||
WIC_CHECK(encoder->Commit());
|
||||
|
||||
STATSTG stat = {};
|
||||
WIC_CHECK(memStream->Stat(&stat, STATFLAG_NONAME));
|
||||
ULONG pngSize = (ULONG)stat.cbSize.QuadPart;
|
||||
|
||||
#undef WIC_CHECK
|
||||
|
||||
std::vector<uint8_t> result(pngSize);
|
||||
LARGE_INTEGER zero = {};
|
||||
memStream->Seek(zero, STREAM_SEEK_SET, nullptr);
|
||||
memStream->Read(result.data(), pngSize, nullptr);
|
||||
|
||||
cleanup();
|
||||
return result;
|
||||
|
||||
} catch (...) {
|
||||
return pngData;
|
||||
}
|
||||
}
|
||||
|
||||
bool ValidateSkinPng(const uint8_t* data, size_t size) {
|
||||
static const uint8_t kPngMagic[8] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
|
||||
|
||||
if (data == nullptr || size < 24)
|
||||
return false;
|
||||
if (size > kMaxSkinBytes)
|
||||
return false;
|
||||
if (memcmp(data, kPngMagic, 8) != 0)
|
||||
return false;
|
||||
|
||||
uint32_t width = ((uint32_t)data[16] << 24) | ((uint32_t)data[17] << 16) |
|
||||
((uint32_t)data[18] << 8) | (uint32_t)data[19];
|
||||
uint32_t height = ((uint32_t)data[20] << 24) | ((uint32_t)data[21] << 16) |
|
||||
((uint32_t)data[22] << 8) | (uint32_t)data[23];
|
||||
|
||||
if (width != 64)
|
||||
return false;
|
||||
if (height != 32 && height != 64)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> FetchSkinPngImpl(const std::string& url, bool cropToLCE, std::string& error) {
|
||||
if (url.empty()) {
|
||||
error = "Empty skin URL";
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
auto resp = HttpGet(url);
|
||||
if (resp.statusCode == 200 && !resp.body.empty()) {
|
||||
auto rawPng = std::vector<uint8_t>(resp.body.begin(), resp.body.end());
|
||||
if (!ValidateSkinPng(rawPng.data(), rawPng.size())) {
|
||||
error = "FetchSkinPng: invalid PNG data (" + std::to_string(rawPng.size()) + " bytes)";
|
||||
return {};
|
||||
}
|
||||
return cropToLCE ? CropSkinTo64x32(rawPng) : rawPng;
|
||||
}
|
||||
error = "FetchSkinPng HTTP " + std::to_string(resp.statusCode);
|
||||
} catch (const std::exception& e) {
|
||||
error = std::string("FetchSkinPng exception: ") + e.what();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<uint8_t> FetchSkinPng(const std::string& url, std::string& error) {
|
||||
return FetchSkinPngImpl(url, /*cropToLCE=*/true, error);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> FetchSkinPngRaw(const std::string& url, std::string& error) {
|
||||
return FetchSkinPngImpl(url, /*cropToLCE=*/false, error);
|
||||
}
|
||||
|
||||
static std::string FetchProfileSkinUrlImpl(const std::string& baseUrl,
|
||||
const std::string& uuid,
|
||||
const std::string& errorPrefix,
|
||||
const std::string& noSkinMsg,
|
||||
std::string& error)
|
||||
{
|
||||
std::string undashed = UndashUuid(uuid);
|
||||
if (undashed.empty()) {
|
||||
error = "Empty UUID";
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string url = baseUrl + undashed;
|
||||
|
||||
try {
|
||||
auto resp = HttpGet(url);
|
||||
if (resp.statusCode != 200 || resp.body.empty()) {
|
||||
error = errorPrefix + " HTTP " + std::to_string(resp.statusCode);
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string skinUrl, capeUrl;
|
||||
ParseTextureProperties(resp.body, skinUrl, capeUrl);
|
||||
if (skinUrl.empty())
|
||||
error = noSkinMsg;
|
||||
return skinUrl;
|
||||
} catch (const std::exception& e) {
|
||||
error = errorPrefix + " exception: " + e.what();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
std::string FetchProfileSkinUrl(const std::string& uuid, std::string& error) {
|
||||
return FetchProfileSkinUrlImpl(
|
||||
"https://sessionserver.mojang.com/session/minecraft/profile/",
|
||||
uuid, "Profile", "No skin in profile", error);
|
||||
}
|
||||
|
||||
std::string MakeElybySkinKey(const std::string& uuid) {
|
||||
return "elyby_skin_" + UndashUuid(uuid) + ".png";
|
||||
}
|
||||
|
||||
bool ElybyJoinServer(const std::string& accessToken,
|
||||
const std::string& undashedUuid,
|
||||
const std::string& serverId,
|
||||
std::string& error)
|
||||
{
|
||||
return JoinServerImpl(
|
||||
"https://authserver.ely.by/session/join",
|
||||
accessToken, undashedUuid, serverId, "ElybyJoinServer", error);
|
||||
}
|
||||
|
||||
HasJoinedResult ElybyHasJoined(const std::string& username,
|
||||
const std::string& serverId,
|
||||
std::string& error)
|
||||
{
|
||||
return HasJoinedImpl(
|
||||
"https://authserver.ely.by/session/hasJoined",
|
||||
username, serverId, "ElybyHasJoined", error);
|
||||
}
|
||||
|
||||
std::string ElybyFetchProfileSkinUrl(const std::string& uuid, std::string& error) {
|
||||
return FetchProfileSkinUrlImpl(
|
||||
"https://authserver.ely.by/session/profile/",
|
||||
uuid, "ElybyProfile", "No skin in ely.by profile", error);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in a new issue