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
5bc5e029fd
|
|
@ -5,6 +5,68 @@ set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
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)
|
if(NOT WIN32)
|
||||||
message(FATAL_ERROR "This CMake build currently supports Windows only.")
|
message(FATAL_ERROR "This CMake build currently supports Windows only.")
|
||||||
endif()
|
endif()
|
||||||
|
|
@ -13,6 +75,8 @@ if(NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||||
message(FATAL_ERROR "Use a 64-bit generator/toolchain (x64).")
|
message(FATAL_ERROR "Use a 64-bit generator/toolchain (x64).")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
configure_msvc_system_includes()
|
||||||
|
|
||||||
set(CMAKE_CONFIGURATION_TYPES
|
set(CMAKE_CONFIGURATION_TYPES
|
||||||
"Debug"
|
"Debug"
|
||||||
"Release"
|
"Release"
|
||||||
|
|
@ -77,6 +141,7 @@ list(APPEND MINECRAFT_SHARED_DEFINES ${PLATFORM_DEFINES})
|
||||||
# Sources
|
# Sources
|
||||||
# ---
|
# ---
|
||||||
add_subdirectory(Minecraft.World)
|
add_subdirectory(Minecraft.World)
|
||||||
|
add_subdirectory(newauth)
|
||||||
add_subdirectory(Minecraft.Client)
|
add_subdirectory(Minecraft.Client)
|
||||||
if(PLATFORM_NAME STREQUAL "Windows64") # Server is only supported on Windows for now
|
if(PLATFORM_NAME STREQUAL "Windows64") # Server is only supported on Windows for now
|
||||||
add_subdirectory(Minecraft.Server)
|
add_subdirectory(Minecraft.Server)
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ set_target_properties(Minecraft.Client PROPERTIES
|
||||||
|
|
||||||
target_link_libraries(Minecraft.Client PRIVATE
|
target_link_libraries(Minecraft.Client PRIVATE
|
||||||
Minecraft.World
|
Minecraft.World
|
||||||
|
newauth
|
||||||
d3d11
|
d3d11
|
||||||
d3dcompiler
|
d3dcompiler
|
||||||
XInput9_1_0
|
XInput9_1_0
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,12 @@
|
||||||
#ifdef _WINDOWS64
|
#ifdef _WINDOWS64
|
||||||
#include "Xbox\Network\NetworkPlayerXbox.h"
|
#include "Xbox\Network\NetworkPlayerXbox.h"
|
||||||
#include "Common\Network\PlatformNetworkManagerStub.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
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -368,6 +374,14 @@ void ClientConnection::handleLogin(shared_ptr<LoginPacket> packet)
|
||||||
minecraft->player->setCustomSkin( app.GetPlayerSkinId(m_userIndex) );
|
minecraft->player->setCustomSkin( app.GetPlayerSkinId(m_userIndex) );
|
||||||
minecraft->player->setCustomCape( app.GetPlayerCapeId(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());
|
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)
|
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);
|
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
|
#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;
|
IQNetPlayer* matchedQNetPlayer = nullptr;
|
||||||
PlayerUID pktXuid = player->getXuid();
|
PlayerUID pktXuid = player->getXuid();
|
||||||
const PlayerUID WIN64_XUID_BASE = (PlayerUID)0xe000d45248242f2e;
|
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.
|
// Current Win64 path: identify QNet player by name and attach packet XUID.
|
||||||
if (matchedQNetPlayer == nullptr)
|
if (matchedQNetPlayer == nullptr)
|
||||||
{
|
{
|
||||||
|
|
@ -1087,6 +1159,8 @@ void ClientConnection::handleRemoveEntity(shared_ptr<RemoveEntitiesPacket> packe
|
||||||
{
|
{
|
||||||
for (int i = 0; i < packet->ids.length; i++)
|
for (int i = 0; i < packet->ids.length; i++)
|
||||||
{
|
{
|
||||||
|
m_pendingRemotePlayerSmallIds.erase(packet->ids[i]);
|
||||||
|
|
||||||
shared_ptr<Entity> entity = getEntity(packet->ids[i]);
|
shared_ptr<Entity> entity = getEntity(packet->ids[i]);
|
||||||
if (entity != nullptr && entity->GetType() == eTYPE_PLAYER)
|
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
|
// 4J - Check that we can play with all the players already in the game who have Friends-Only UGC set
|
||||||
BOOL canPlay = TRUE;
|
BOOL canPlay = TRUE;
|
||||||
BOOL canPlayLocal = 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 isAtLeastOneFriend = g_NetworkManager.IsHost();
|
||||||
BOOL isFriendsWithHost = TRUE;
|
BOOL isFriendsWithHost = TRUE;
|
||||||
|
#endif
|
||||||
BOOL cantPlayContentRestricted = FALSE;
|
BOOL cantPlayContentRestricted = FALSE;
|
||||||
|
|
||||||
if(!g_NetworkManager.IsHost())
|
if(!g_NetworkManager.IsHost())
|
||||||
|
|
@ -2443,8 +2524,14 @@ void ClientConnection::handlePreLogin(shared_ptr<PreLoginPacket> packet)
|
||||||
}
|
}
|
||||||
BOOL allAllowed, friendsAllowed;
|
BOOL allAllowed, friendsAllowed;
|
||||||
ProfileManager.AllowedPlayerCreatedContent(m_userIndex,true,&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),
|
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)));
|
packet->m_ugcPlayersVersion, app.GetPlayerSkinId(m_userIndex), app.GetPlayerCapeId(m_userIndex), ProfileManager.IsGuest(m_userIndex)));
|
||||||
|
#endif
|
||||||
|
|
||||||
if(!g_NetworkManager.IsHost() )
|
if(!g_NetworkManager.IsHost() )
|
||||||
{
|
{
|
||||||
|
|
@ -3556,9 +3643,31 @@ bool ClientConnection::isServerPacketListener()
|
||||||
|
|
||||||
void ClientConnection::handlePlayerInfo(shared_ptr<PlayerInfoPacket> packet)
|
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())
|
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
|
// 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);
|
shared_ptr<Entity> entity = getEntity(packet->m_entityId);
|
||||||
if(entity != nullptr && entity->instanceof(eTYPE_PLAYER))
|
if(entity != nullptr && entity->instanceof(eTYPE_PLAYER))
|
||||||
|
|
@ -4129,3 +4238,301 @@ ClientConnection::DeferredEntityLinkPacket::DeferredEntityLinkPacket(shared_ptr<
|
||||||
m_recievedTick = GetTickCount();
|
m_recievedTick = GetTickCount();
|
||||||
m_packet = packet;
|
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
|
#pragma once
|
||||||
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include "..\Minecraft.World\net.minecraft.network.h"
|
#include "..\Minecraft.World\net.minecraft.network.h"
|
||||||
class Minecraft;
|
class Minecraft;
|
||||||
|
|
@ -6,6 +7,8 @@ class MultiPlayerLevel;
|
||||||
class SavedDataStorage;
|
class SavedDataStorage;
|
||||||
class Socket;
|
class Socket;
|
||||||
class MultiplayerLocalPlayer;
|
class MultiplayerLocalPlayer;
|
||||||
|
class AuthSchemePacket;
|
||||||
|
class AuthResultPacket;
|
||||||
|
|
||||||
class ClientConnection : public PacketListener
|
class ClientConnection : public PacketListener
|
||||||
{
|
{
|
||||||
|
|
@ -14,6 +17,7 @@ private:
|
||||||
{
|
{
|
||||||
eCCPreLoginSent = 0,
|
eCCPreLoginSent = 0,
|
||||||
eCCPreLoginReceived,
|
eCCPreLoginReceived,
|
||||||
|
eCCAuthWaiting,
|
||||||
eCCLoginSent,
|
eCCLoginSent,
|
||||||
eCCLoginReceived,
|
eCCLoginReceived,
|
||||||
eCCConnected
|
eCCConnected
|
||||||
|
|
@ -48,6 +52,7 @@ private:
|
||||||
|
|
||||||
std::unordered_set<int> m_trackedEntityIds;
|
std::unordered_set<int> m_trackedEntityIds;
|
||||||
std::unordered_set<int64_t> m_visibleChunks;
|
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); }
|
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 shouldProcessForEntity(int entityId) const;
|
||||||
bool shouldProcessForPosition(int blockX, int blockZ) const;
|
bool shouldProcessForPosition(int blockX, int blockZ) const;
|
||||||
bool anyOtherConnectionHasChunk(int x, int z) const;
|
bool anyOtherConnectionHasChunk(int x, int z) const;
|
||||||
|
#ifdef _WINDOWS64
|
||||||
|
bool ensureRemoteNetworkPlayer(BYTE smallId, const std::wstring *playerName, PlayerUID xuid);
|
||||||
|
#endif
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool isTrackingEntity(int entityId) const { return m_trackedEntityIds.count(entityId) > 0; }
|
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 handleParticleEvent(shared_ptr<LevelParticlesPacket> packet);
|
||||||
virtual void handleUpdateAttributes(shared_ptr<UpdateAttributesPacket> 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:
|
private:
|
||||||
// 4J: Entity link packet deferred
|
// 4J: Entity link packet deferred
|
||||||
class DeferredEntityLinkPacket
|
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 )
|
void CPlatformNetworkManagerStub::NotifyPlayerJoined(IQNetPlayer *pQNetPlayer )
|
||||||
{
|
{
|
||||||
const char * pszDescription;
|
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
|
// 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
|
// 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 );
|
m_machineQNetPrimaryPlayers.push_back( pQNetPlayer );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
g_NetworkManager.PlayerJoining( networkPlayer );
|
if (exposePlayer)
|
||||||
|
{
|
||||||
|
g_NetworkManager.PlayerJoining( networkPlayer );
|
||||||
|
}
|
||||||
|
|
||||||
if( createFakeSocket == true && !m_bHostChanged )
|
if( createFakeSocket == true && !m_bHostChanged )
|
||||||
{
|
{
|
||||||
|
|
@ -101,10 +105,13 @@ void CPlatformNetworkManagerStub::NotifyPlayerJoined(IQNetPlayer *pQNetPlayer )
|
||||||
SystemFlagAddPlayer( networkPlayer );
|
SystemFlagAddPlayer( networkPlayer );
|
||||||
}
|
}
|
||||||
|
|
||||||
for( int idx = 0; idx < XUSER_MAX_COUNT; ++idx)
|
if (exposePlayer)
|
||||||
{
|
{
|
||||||
if(playerChangedCallback[idx] != nullptr)
|
for( int idx = 0; idx < XUSER_MAX_COUNT; ++idx)
|
||||||
playerChangedCallback[idx]( playerChangedCallbackParam[idx], networkPlayer, false );
|
{
|
||||||
|
if(playerChangedCallback[idx] != nullptr)
|
||||||
|
playerChangedCallback[idx]( playerChangedCallbackParam[idx], networkPlayer, false );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(m_pIQNet->GetState() == QNET_STATE_GAME_PLAY)
|
if(m_pIQNet->GetState() == QNET_STATE_GAME_PLAY)
|
||||||
|
|
@ -142,12 +149,18 @@ void CPlatformNetworkManagerStub::NotifyPlayerLeaving(IQNetPlayer* pQNetPlayer)
|
||||||
SystemFlagRemovePlayer(networkPlayer);
|
SystemFlagRemovePlayer(networkPlayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_NetworkManager.PlayerLeaving(networkPlayer);
|
if (Win64_ShouldExposeSessionPlayer(pQNetPlayer))
|
||||||
|
|
||||||
for (int idx = 0; idx < XUSER_MAX_COUNT; ++idx)
|
|
||||||
{
|
{
|
||||||
if (playerChangedCallback[idx] != nullptr)
|
g_NetworkManager.PlayerLeaving(networkPlayer);
|
||||||
playerChangedCallback[idx](playerChangedCallbackParam[idx], networkPlayer, true);
|
}
|
||||||
|
|
||||||
|
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);
|
removeNetworkPlayer(pQNetPlayer);
|
||||||
|
|
@ -544,6 +557,7 @@ int CPlatformNetworkManagerStub::JoinGame(FriendSessionInfo* searchResult, int l
|
||||||
m_bLeaveGameOnTick = false;
|
m_bLeaveGameOnTick = false;
|
||||||
IQNet::s_isHosting = false;
|
IQNet::s_isHosting = false;
|
||||||
m_pIQNet->ClientJoinGame();
|
m_pIQNet->ClientJoinGame();
|
||||||
|
Win64_SetJoinedDedicatedServer(searchResult->data.isDedicatedServer);
|
||||||
|
|
||||||
IQNet::m_player[0].m_smallId = 0;
|
IQNet::m_player[0].m_smallId = 0;
|
||||||
IQNet::m_player[0].m_isRemote = true;
|
IQNet::m_player[0].m_isRemote = true;
|
||||||
|
|
@ -865,6 +879,7 @@ void CPlatformNetworkManagerStub::SearchForGames()
|
||||||
info->data.subTexturePackId = lanSessions[i].subTexturePackId;
|
info->data.subTexturePackId = lanSessions[i].subTexturePackId;
|
||||||
info->data.isReadyToJoin = lanSessions[i].isJoinable;
|
info->data.isReadyToJoin = lanSessions[i].isJoinable;
|
||||||
info->data.isJoinable = 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);
|
strncpy_s(info->data.hostIP, sizeof(info->data.hostIP), lanSessions[i].hostIP, _TRUNCATE);
|
||||||
info->data.hostPort = lanSessions[i].hostPort;
|
info->data.hostPort = lanSessions[i].hostPort;
|
||||||
wcsncpy_s(info->data.hostName, XUSER_NAME_SIZE, lanSessions[i].hostName, _TRUNCATE);
|
wcsncpy_s(info->data.hostName, XUSER_NAME_SIZE, lanSessions[i].hostName, _TRUNCATE);
|
||||||
|
|
@ -916,6 +931,7 @@ void CPlatformNetworkManagerStub::SearchForGames()
|
||||||
info->displayLabelViewableStartIndex = 0;
|
info->displayLabelViewableStartIndex = 0;
|
||||||
info->data.isReadyToJoin = true;
|
info->data.isReadyToJoin = true;
|
||||||
info->data.isJoinable = true;
|
info->data.isJoinable = true;
|
||||||
|
info->data.isDedicatedServer = true;
|
||||||
strncpy_s(info->data.hostIP, sizeof(info->data.hostIP), ipBuf, _TRUNCATE);
|
strncpy_s(info->data.hostIP, sizeof(info->data.hostIP), ipBuf, _TRUNCATE);
|
||||||
info->data.hostPort = port;
|
info->data.hostPort = port;
|
||||||
info->sessionId = static_cast<uint64_t>(inet_addr(ipBuf)) | static_cast<uint64_t>(port) << 32;
|
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 isReadyToJoin;
|
||||||
bool isJoinable;
|
bool isJoinable;
|
||||||
|
bool isDedicatedServer;
|
||||||
|
|
||||||
char hostIP[64];
|
char hostIP[64];
|
||||||
int hostPort;
|
int hostPort;
|
||||||
|
|
@ -85,6 +86,7 @@ typedef struct _GameSessionData
|
||||||
subTexturePackId = 0;
|
subTexturePackId = 0;
|
||||||
isReadyToJoin = false;
|
isReadyToJoin = false;
|
||||||
isJoinable = true;
|
isJoinable = true;
|
||||||
|
isDedicatedServer = false;
|
||||||
memset(hostIP, 0, sizeof(hostIP));
|
memset(hostIP, 0, sizeof(hostIP));
|
||||||
hostPort = 0;
|
hostPort = 0;
|
||||||
memset(hostName, 0, sizeof(hostName));
|
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_Intro.h"
|
||||||
#include "UIScene_SaveMessage.h"
|
#include "UIScene_SaveMessage.h"
|
||||||
#include "UIScene_MainMenu.h"
|
#include "UIScene_MainMenu.h"
|
||||||
|
#ifndef MINECRAFT_SERVER_BUILD
|
||||||
|
#include "UIScene_MSAuth.h"
|
||||||
|
#endif
|
||||||
#include "UIScene_LoadMenu.h"
|
#include "UIScene_LoadMenu.h"
|
||||||
#include "UIScene_JoinMenu.h"
|
#include "UIScene_JoinMenu.h"
|
||||||
#include "UIScene_LoadOrJoinMenu.h"
|
#include "UIScene_LoadOrJoinMenu.h"
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,7 @@ enum EUIScene
|
||||||
|
|
||||||
#ifndef _XBOX
|
#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
|
// 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_Panorama,
|
||||||
eUIComponent_Logo,
|
eUIComponent_Logo,
|
||||||
eUIComponent_DebugUIConsole,
|
eUIComponent_DebugUIConsole,
|
||||||
|
|
|
||||||
|
|
@ -367,6 +367,11 @@ bool UILayer::NavigateToScene(int iPad, EUIScene scene, void *initData)
|
||||||
case eUIScene_MainMenu:
|
case eUIScene_MainMenu:
|
||||||
newScene = new UIScene_MainMenu(iPad, initData, this);
|
newScene = new UIScene_MainMenu(iPad, initData, this);
|
||||||
break;
|
break;
|
||||||
|
case eUIScene_MSAuth:
|
||||||
|
#ifndef MINECRAFT_SERVER_BUILD
|
||||||
|
newScene = new UIScene_MSAuth(iPad, initData, this);
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
case eUIScene_LoadOrJoinMenu:
|
case eUIScene_LoadOrJoinMenu:
|
||||||
newScene = new UIScene_LoadOrJoinMenu(iPad, initData, this);
|
newScene = new UIScene_LoadOrJoinMenu(iPad, initData, this);
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -1000,7 +1000,7 @@ void UIScene::gainFocus()
|
||||||
updateTooltips();
|
updateTooltips();
|
||||||
updateComponents();
|
updateComponents();
|
||||||
|
|
||||||
if(!m_bFocussedOnce)
|
if(swf && !m_bFocussedOnce)
|
||||||
{
|
{
|
||||||
IggyDataValue result;
|
IggyDataValue result;
|
||||||
IggyDataValue value[1];
|
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;
|
m_bIgnorePress=false;
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef _WINDOWS64
|
||||||
|
m_bPendingPlayAfterAuth = false;
|
||||||
|
#endif
|
||||||
m_buttons[static_cast<int>(eControl_PlayGame)].init(IDS_PLAY_GAME,eControl_PlayGame);
|
m_buttons[static_cast<int>(eControl_PlayGame)].init(IDS_PLAY_GAME,eControl_PlayGame);
|
||||||
|
|
||||||
#ifdef _XBOX_ONE
|
#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_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_Achievements)].init( (UIString)IDS_ACHIEVEMENTS,eControl_Achievements);
|
||||||
m_buttons[static_cast<int>(eControl_HelpAndOptions)].init(IDS_HELP_AND_OPTIONS,eControl_HelpAndOptions);
|
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())
|
if(ProfileManager.IsFullVersion())
|
||||||
{
|
{
|
||||||
m_bTrialVersion=false;
|
m_bTrialVersion=false;
|
||||||
|
|
@ -53,6 +60,7 @@ UIScene_MainMenu::UIScene_MainMenu(int iPad, void *initData, UILayer *parentLaye
|
||||||
m_bTrialVersion=true;
|
m_bTrialVersion=true;
|
||||||
m_buttons[static_cast<int>(eControl_UnlockOrDLC)].init(IDS_UNLOCK_FULL_GAME,eControl_UnlockOrDLC);
|
m_buttons[static_cast<int>(eControl_UnlockOrDLC)].init(IDS_UNLOCK_FULL_GAME,eControl_UnlockOrDLC);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef _DURANGO
|
#ifndef _DURANGO
|
||||||
m_buttons[static_cast<int>(eControl_Exit)].init(app.GetString(IDS_EXIT_GAME),eControl_Exit);
|
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);
|
ui.ShowPlayerDisplayname(false);
|
||||||
m_bIgnorePress=false;
|
m_bIgnorePress=false;
|
||||||
|
|
||||||
|
#ifdef _WINDOWS64
|
||||||
|
if (navBack && m_bPendingPlayAfterAuth)
|
||||||
|
{
|
||||||
|
m_bPendingPlayAfterAuth = false;
|
||||||
|
proceedToScene(ProfileManager.GetPrimaryPad(), eUIScene_LoadOrJoinMenu);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (eNavigateWhenReady >= 0)
|
if (eNavigateWhenReady >= 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
@ -181,8 +198,10 @@ void UIScene_MainMenu::handleGainFocus(bool navBack)
|
||||||
|
|
||||||
if(navBack && ProfileManager.IsFullVersion())
|
if(navBack && ProfileManager.IsFullVersion())
|
||||||
{
|
{
|
||||||
|
#ifndef _WINDOWS64
|
||||||
// Replace the Unlock Full Game with Downloadable Content
|
// Replace the Unlock Full Game with Downloadable Content
|
||||||
m_buttons[static_cast<int>(eControl_UnlockOrDLC)].setLabel(IDS_DOWNLOADABLECONTENT);
|
m_buttons[static_cast<int>(eControl_UnlockOrDLC)].setLabel(IDS_DOWNLOADABLECONTENT);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if TO_BE_IMPLEMENTED
|
#if TO_BE_IMPLEMENTED
|
||||||
|
|
@ -356,8 +375,12 @@ void UIScene_MainMenu::handlePress(F64 controlId, F64 childId)
|
||||||
//CD - Added for audio
|
//CD - Added for audio
|
||||||
ui.PlayUISFX(eSFX_Press);
|
ui.PlayUISFX(eSFX_Press);
|
||||||
|
|
||||||
|
#ifdef _WINDOWS64
|
||||||
|
ui.NavigateToScene(primaryPad, eUIScene_MSAuth);
|
||||||
|
#else
|
||||||
m_eAction=eAction_RunUnlockOrDLC;
|
m_eAction=eAction_RunUnlockOrDLC;
|
||||||
signInReturnedFunc = &UIScene_MainMenu::UnlockFullGame_SignInReturned;
|
signInReturnedFunc = &UIScene_MainMenu::UnlockFullGame_SignInReturned;
|
||||||
|
#endif
|
||||||
break;
|
break;
|
||||||
case eControl_Exit:
|
case eControl_Exit:
|
||||||
//CD - Added for audio
|
//CD - Added for audio
|
||||||
|
|
@ -1419,7 +1442,22 @@ void UIScene_MainMenu::RunPlayGame(int iPad)
|
||||||
#ifdef _XBOX_ONE
|
#ifdef _XBOX_ONE
|
||||||
ui.ShowPlayerDisplayname(true);
|
ui.ShowPlayerDisplayname(true);
|
||||||
#endif
|
#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);
|
proceedToScene(ProfileManager.GetPrimaryPad(), eUIScene_LoadOrJoinMenu);
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -2129,7 +2167,9 @@ void UIScene_MainMenu::LoadTrial(void)
|
||||||
|
|
||||||
void UIScene_MainMenu::handleUnlockFullVersion()
|
void UIScene_MainMenu::handleUnlockFullVersion()
|
||||||
{
|
{
|
||||||
|
#ifndef _WINDOWS64
|
||||||
m_buttons[static_cast<int>(eControl_UnlockOrDLC)].setLabel(IDS_DOWNLOADABLECONTENT,true);
|
m_buttons[static_cast<int>(eControl_UnlockOrDLC)].setLabel(IDS_DOWNLOADABLECONTENT,true);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "UIScene.h"
|
#include "UIScene.h"
|
||||||
|
#ifdef _WINDOWS64
|
||||||
|
#include "../../../newauth/include/newauthManager.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
class UIScene_MainMenu : public UIScene
|
class UIScene_MainMenu : public UIScene
|
||||||
{
|
{
|
||||||
|
|
@ -56,6 +59,9 @@ private:
|
||||||
#ifdef _XBOX_ONE
|
#ifdef _XBOX_ONE
|
||||||
bool m_bWaitingForDLCInfo;
|
bool m_bWaitingForDLCInfo;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef _WINDOWS64
|
||||||
|
bool m_bPendingPlayAfterAuth;
|
||||||
|
#endif
|
||||||
|
|
||||||
float m_fScreenWidth,m_fScreenHeight;
|
float m_fScreenWidth,m_fScreenHeight;
|
||||||
float m_fRawWidth,m_fRawHeight;
|
float m_fRawWidth,m_fRawHeight;
|
||||||
|
|
|
||||||
|
|
@ -238,9 +238,32 @@ ULONG_PTR IQNetPlayer::GetCustomDataValue() {
|
||||||
IQNetPlayer IQNet::m_player[MINECRAFT_NET_MAX_PLAYERS];
|
IQNetPlayer IQNet::m_player[MINECRAFT_NET_MAX_PLAYERS];
|
||||||
DWORD IQNet::s_playerCount = 1;
|
DWORD IQNet::s_playerCount = 1;
|
||||||
bool IQNet::s_isHosting = true;
|
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;
|
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)
|
void Win64_SetupRemoteQNetPlayer(IQNetPlayer * player, BYTE smallId, bool isHost, bool isLocal)
|
||||||
{
|
{
|
||||||
player->m_smallId = smallId;
|
player->m_smallId = smallId;
|
||||||
|
|
@ -317,7 +340,7 @@ IQNetPlayer* IQNet::GetPlayerByIndex(DWORD dwPlayerIndex)
|
||||||
DWORD found = 0;
|
DWORD found = 0;
|
||||||
for (DWORD i = 0; i < s_playerCount; i++)
|
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];
|
if (found == dwPlayerIndex) return &m_player[i];
|
||||||
found++;
|
found++;
|
||||||
|
|
@ -351,7 +374,7 @@ DWORD IQNet::GetPlayerCount()
|
||||||
DWORD count = 0;
|
DWORD count = 0;
|
||||||
for (DWORD i = 0; i < s_playerCount; i++)
|
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;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
@ -362,13 +385,15 @@ void IQNet::HostGame()
|
||||||
{
|
{
|
||||||
_iQNetStubState = QNET_STATE_SESSION_STARTING;
|
_iQNetStubState = QNET_STATE_SESSION_STARTING;
|
||||||
s_isHosting = true;
|
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();
|
m_player[0].m_resolvedXuid = Win64Xuid::GetLegacyEmbeddedHostXuid();
|
||||||
}
|
}
|
||||||
void IQNet::ClientJoinGame()
|
void IQNet::ClientJoinGame()
|
||||||
{
|
{
|
||||||
_iQNetStubState = QNET_STATE_SESSION_STARTING;
|
_iQNetStubState = QNET_STATE_SESSION_STARTING;
|
||||||
s_isHosting = false;
|
s_isHosting = false;
|
||||||
|
s_joinedDedicatedServer = false;
|
||||||
|
|
||||||
for (int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; i++)
|
for (int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; i++)
|
||||||
{
|
{
|
||||||
|
|
@ -384,6 +409,7 @@ void IQNet::EndGame()
|
||||||
{
|
{
|
||||||
_iQNetStubState = QNET_STATE_IDLE;
|
_iQNetStubState = QNET_STATE_IDLE;
|
||||||
s_isHosting = false;
|
s_isHosting = false;
|
||||||
|
s_joinedDedicatedServer = false;
|
||||||
s_playerCount = 1;
|
s_playerCount = 1;
|
||||||
for (int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; i++)
|
for (int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; i++)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,15 @@ public:
|
||||||
void setEnforceUnicodeSheet(bool enforceUnicodeSheet);
|
void setEnforceUnicodeSheet(bool enforceUnicodeSheet);
|
||||||
void setBidirectional(bool bidirectional);
|
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
|
// 4J-PB - check for invalid player name - Japanese local name
|
||||||
bool AllCharactersValid(const wstring &str);
|
bool AllCharactersValid(const wstring &str);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@
|
||||||
#include "..\Minecraft.World\net.minecraft.world.damagesource.h"
|
#include "..\Minecraft.World\net.minecraft.world.damagesource.h"
|
||||||
#ifdef _WINDOWS64
|
#ifdef _WINDOWS64
|
||||||
#include "Windows64\Network\WinsockNetLayer.h"
|
#include "Windows64\Network\WinsockNetLayer.h"
|
||||||
|
#include "..\newauth\include\newauthManager.h"
|
||||||
#endif
|
#endif
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#ifdef SPLIT_SAVES
|
#ifdef SPLIT_SAVES
|
||||||
|
|
@ -644,10 +645,33 @@ bool MinecraftServer::initServer(int64_t seed, NetworkGameInitData *initData, DW
|
||||||
|
|
||||||
// 4J - Unused
|
// 4J - Unused
|
||||||
//localIp = settings->getString(L"server-ip", L"");
|
//localIp = settings->getString(L"server-ip", L"");
|
||||||
//onlineMode = settings->getBoolean(L"online-mode", true);
|
|
||||||
//motd = settings->getString(L"motd", L"A Minecraft Server");
|
//motd = settings->getString(L"motd", L"A Minecraft Server");
|
||||||
//motd.replace('<27>', '$');
|
//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));
|
setAnimals(GetDedicatedServerBool(settings, L"spawn-animals", true));
|
||||||
setNpcsEnabled(GetDedicatedServerBool(settings, L"spawn-npcs", true));
|
setNpcsEnabled(GetDedicatedServerBool(settings, L"spawn-npcs", true));
|
||||||
setPvpAllowed(app.GetGameHostOption( eGameHostOption_PvP )>0?true:false);
|
setPvpAllowed(app.GetGameHostOption( eGameHostOption_PvP )>0?true:false);
|
||||||
|
|
@ -680,11 +704,11 @@ bool MinecraftServer::initServer(int64_t seed, NetworkGameInitData *initData, DW
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!onlineMode) {
|
if (authProvider == "offline") {
|
||||||
logger.warning("**** SERVER IS RUNNING IN OFFLINE/INSECURE MODE!");
|
logger.warning("**** SERVER IS RUNNING IN OFFLINE/INSECURE MODE!");
|
||||||
logger.warning("The server will make no attempt to authenticate usernames. Beware.");
|
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("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
|
#endif
|
||||||
setPlayers(new PlayerList(this));
|
setPlayers(new PlayerList(this));
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ private:
|
||||||
vector<ConsoleInput *> consoleInput; // 4J - was synchronizedList - TODO - investigate
|
vector<ConsoleInput *> consoleInput; // 4J - was synchronizedList - TODO - investigate
|
||||||
CRITICAL_SECTION m_consoleInputCS;
|
CRITICAL_SECTION m_consoleInputCS;
|
||||||
public:
|
public:
|
||||||
bool onlineMode;
|
std::string authProvider; // mojang, elyby, or offline. not recommended.
|
||||||
bool animals;
|
bool animals;
|
||||||
bool npcs;
|
bool npcs;
|
||||||
bool pvp;
|
bool pvp;
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,15 @@
|
||||||
#include "..\Minecraft.World\net.minecraft.world.level.storage.h"
|
#include "..\Minecraft.World\net.minecraft.world.level.storage.h"
|
||||||
#include "..\Minecraft.World\net.minecraft.world.item.h"
|
#include "..\Minecraft.World\net.minecraft.world.item.h"
|
||||||
#include "..\Minecraft.World\SharedConstants.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 "Settings.h"
|
||||||
|
#include <thread>
|
||||||
|
#ifdef _WINDOWS64
|
||||||
|
#include "..\newauth\include\newauth.h"
|
||||||
|
#endif
|
||||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||||
#include "..\Minecraft.Server\ServerLogManager.h"
|
#include "..\Minecraft.Server\ServerLogManager.h"
|
||||||
#include "..\Minecraft.Server\Access\Access.h"
|
#include "..\Minecraft.Server\Access\Access.h"
|
||||||
|
|
@ -63,6 +71,8 @@ PendingConnection::PendingConnection(MinecraftServer *server, Socket *socket, co
|
||||||
|
|
||||||
PendingConnection::~PendingConnection()
|
PendingConnection::~PendingConnection()
|
||||||
{
|
{
|
||||||
|
if (m_authVerifyResult)
|
||||||
|
m_authVerifyResult->cancelled.store(true, std::memory_order_release);
|
||||||
delete connection;
|
delete connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,6 +83,71 @@ void PendingConnection::tick()
|
||||||
this->handleAcceptedLogin(acceptedLogin);
|
this->handleAcceptedLogin(acceptedLogin);
|
||||||
acceptedLogin = nullptr;
|
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)
|
if (_tick++ == MAX_TICKS_BEFORE_LOGIN)
|
||||||
{
|
{
|
||||||
disconnect(DisconnectPacket::eDisconnect_LoginTooLong);
|
disconnect(DisconnectPacket::eDisconnect_LoginTooLong);
|
||||||
|
|
@ -150,8 +225,8 @@ void PendingConnection::sendPreLoginResponse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
if (false)// server->onlineMode) // 4J - removed
|
if (false) // 4J - removed
|
||||||
{
|
{
|
||||||
loginKey = L"TOIMPLEMENT"; // 4J - todo Long.toHexString(random.nextLong());
|
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) ) );
|
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);
|
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));
|
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)
|
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");
|
// printf("Server: handleLogin\n");
|
||||||
//name = packet->userName;
|
//name = packet->userName;
|
||||||
if (packet->clientVersion != SharedConstants::NETWORK_PROTOCOL_VERSION)
|
if (packet->clientVersion != SharedConstants::NETWORK_PROTOCOL_VERSION)
|
||||||
|
|
@ -183,11 +507,8 @@ void PendingConnection::handleLogin(shared_ptr<LoginPacket> packet)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (true)// 4J removed !server->onlineMode)
|
|
||||||
bool sentDisconnect = false;
|
bool sentDisconnect = false;
|
||||||
|
|
||||||
// Use the same Xuid choice as handleAcceptedLogin (offline first, online fallback).
|
|
||||||
//
|
|
||||||
PlayerUID loginXuid = packet->m_offlineXuid;
|
PlayerUID loginXuid = packet->m_offlineXuid;
|
||||||
if (loginXuid == INVALID_XUID) loginXuid = packet->m_onlineXuid;
|
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)
|
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||||
ServerRuntime::ServerLogManager::OnAcceptedPlayerLogin(GetPendingConnectionSmallId(connection), name);
|
ServerRuntime::ServerLogManager::OnAcceptedPlayerLogin(GetPendingConnectionSmallId(connection), name);
|
||||||
#endif
|
#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);
|
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
|
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
|
#pragma once
|
||||||
|
#include <atomic>
|
||||||
|
#include <mutex>
|
||||||
|
#include <memory>
|
||||||
#include "..\Minecraft.World\PacketListener.h"
|
#include "..\Minecraft.World\PacketListener.h"
|
||||||
class MinecraftServer;
|
class MinecraftServer;
|
||||||
class Socket;
|
class Socket;
|
||||||
class LoginPacket;
|
class LoginPacket;
|
||||||
|
class AuthSchemePacket;
|
||||||
|
class AuthResponsePacket;
|
||||||
class Connection;
|
class Connection;
|
||||||
class Random;
|
class Random;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
class PendingConnection : public PacketListener
|
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:
|
private:
|
||||||
static const int FAKE_LAG = 0;
|
static const int FAKE_LAG = 0;
|
||||||
static const int MAX_TICKS_BEFORE_LOGIN = 20 * 30;
|
static const int MAX_TICKS_BEFORE_LOGIN = 20 * 30;
|
||||||
|
|
@ -27,6 +56,14 @@ private:
|
||||||
shared_ptr<LoginPacket> acceptedLogin;
|
shared_ptr<LoginPacket> acceptedLogin;
|
||||||
wstring loginKey;
|
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:
|
public:
|
||||||
PendingConnection(MinecraftServer *server, Socket *socket, const wstring& id);
|
PendingConnection(MinecraftServer *server, Socket *socket, const wstring& id);
|
||||||
~PendingConnection();
|
~PendingConnection();
|
||||||
|
|
@ -35,6 +72,7 @@ public:
|
||||||
virtual void handlePreLogin(shared_ptr<PreLoginPacket> packet);
|
virtual void handlePreLogin(shared_ptr<PreLoginPacket> packet);
|
||||||
virtual void handleLogin(shared_ptr<LoginPacket> packet);
|
virtual void handleLogin(shared_ptr<LoginPacket> packet);
|
||||||
virtual void handleAcceptedLogin(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 onDisconnect(DisconnectPacket::eDisconnectReason reason, void *reasonObjects);
|
||||||
virtual void handleGetInfo(shared_ptr<GetInfoPacket> packet);
|
virtual void handleGetInfo(shared_ptr<GetInfoPacket> packet);
|
||||||
virtual void handleKeepAlive(shared_ptr<KeepAlivePacket> packet);
|
virtual void handleKeepAlive(shared_ptr<KeepAlivePacket> packet);
|
||||||
|
|
@ -46,4 +84,5 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void sendPreLoginResponse();
|
void sendPreLoginResponse();
|
||||||
|
void sendAuthScheme();
|
||||||
};
|
};
|
||||||
|
|
@ -290,6 +290,9 @@ public:
|
||||||
public:
|
public:
|
||||||
void clearLastBoundId();
|
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:
|
private:
|
||||||
int loadTexture(TEXTURE_NAME texId, const wstring& resourceName);
|
int loadTexture(TEXTURE_NAME texId, const wstring& resourceName);
|
||||||
public:
|
public:
|
||||||
|
|
|
||||||
|
|
@ -1317,6 +1317,7 @@ bool WinsockNetLayer::StartAdvertising(int gamePort, const wchar_t* hostName, un
|
||||||
s_advertiseData.texturePackParentId = texPackId;
|
s_advertiseData.texturePackParentId = texPackId;
|
||||||
s_advertiseData.subTexturePackId = subTexId;
|
s_advertiseData.subTexturePackId = subTexId;
|
||||||
s_advertiseData.isJoinable = 0;
|
s_advertiseData.isJoinable = 0;
|
||||||
|
s_advertiseData.isDedicatedServer = g_Win64DedicatedServer ? 1 : 0;
|
||||||
s_hostGamePort = gamePort;
|
s_hostGamePort = gamePort;
|
||||||
LeaveCriticalSection(&s_advertiseLock);
|
LeaveCriticalSection(&s_advertiseLock);
|
||||||
|
|
||||||
|
|
@ -1519,6 +1520,7 @@ DWORD WINAPI WinsockNetLayer::DiscoveryThreadProc(LPVOID param)
|
||||||
s_discoveredSessions[i].texturePackParentId = broadcast->texturePackParentId;
|
s_discoveredSessions[i].texturePackParentId = broadcast->texturePackParentId;
|
||||||
s_discoveredSessions[i].subTexturePackId = broadcast->subTexturePackId;
|
s_discoveredSessions[i].subTexturePackId = broadcast->subTexturePackId;
|
||||||
s_discoveredSessions[i].isJoinable = (broadcast->isJoinable != 0);
|
s_discoveredSessions[i].isJoinable = (broadcast->isJoinable != 0);
|
||||||
|
s_discoveredSessions[i].isDedicatedServer = (broadcast->isDedicatedServer != 0);
|
||||||
s_discoveredSessions[i].lastSeenTick = now;
|
s_discoveredSessions[i].lastSeenTick = now;
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
|
|
@ -1539,6 +1541,7 @@ DWORD WINAPI WinsockNetLayer::DiscoveryThreadProc(LPVOID param)
|
||||||
session.texturePackParentId = broadcast->texturePackParentId;
|
session.texturePackParentId = broadcast->texturePackParentId;
|
||||||
session.subTexturePackId = broadcast->subTexturePackId;
|
session.subTexturePackId = broadcast->subTexturePackId;
|
||||||
session.isJoinable = (broadcast->isJoinable != 0);
|
session.isJoinable = (broadcast->isJoinable != 0);
|
||||||
|
session.isDedicatedServer = (broadcast->isDedicatedServer != 0);
|
||||||
session.lastSeenTick = now;
|
session.lastSeenTick = now;
|
||||||
s_discoveredSessions.push_back(session);
|
s_discoveredSessions.push_back(session);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ struct Win64LANBroadcast
|
||||||
DWORD texturePackParentId;
|
DWORD texturePackParentId;
|
||||||
BYTE subTexturePackId;
|
BYTE subTexturePackId;
|
||||||
BYTE isJoinable;
|
BYTE isJoinable;
|
||||||
|
BYTE isDedicatedServer;
|
||||||
};
|
};
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
|
@ -51,6 +52,7 @@ struct Win64LANSession
|
||||||
unsigned int texturePackParentId;
|
unsigned int texturePackParentId;
|
||||||
unsigned char subTexturePackId;
|
unsigned char subTexturePackId;
|
||||||
bool isJoinable;
|
bool isJoinable;
|
||||||
|
bool isDedicatedServer;
|
||||||
DWORD lastSeenTick;
|
DWORD lastSeenTick;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@
|
||||||
#include "Network\WinsockNetLayer.h"
|
#include "Network\WinsockNetLayer.h"
|
||||||
#include "Windows64_Xuid.h"
|
#include "Windows64_Xuid.h"
|
||||||
#include "Common/UI/UI.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.
|
// 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.
|
// C4JRender (RenderManager) is a stateless wrapper — all D3D state lives in InternalRenderManager.
|
||||||
|
|
@ -1276,6 +1277,8 @@ static Minecraft* InitialiseMinecraftRuntime()
|
||||||
|
|
||||||
ProfileManager.SetDebugFullOverride(true);
|
ProfileManager.SetDebugFullOverride(true);
|
||||||
|
|
||||||
|
newauthManager::Get().TryRestoreActiveJavaAccount();
|
||||||
|
|
||||||
Tesselator::CreateNewThreadStorage(1024 * 1024);
|
Tesselator::CreateNewThreadStorage(1024 * 1024);
|
||||||
AABB::CreateNewThreadStorage();
|
AABB::CreateNewThreadStorage();
|
||||||
Vec3::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/UIGroup.h"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UILayer.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UILayer.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UILayer.h"
|
"${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.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UIScene.h"
|
"${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UIScene.h"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UITTFFont.cpp"
|
"${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_LoadOrJoinMenu.h"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UIScene_MainMenu.cpp"
|
"${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_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.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UIScene_NewUpdateMessage.h"
|
"${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UIScene_NewUpdateMessage.h"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/UI/UIScene_SaveMessage.cpp"
|
"${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
|
target_link_libraries(Minecraft.Server PRIVATE
|
||||||
Minecraft.World
|
Minecraft.World
|
||||||
|
newauth
|
||||||
d3d11
|
d3d11
|
||||||
d3dcompiler
|
d3dcompiler
|
||||||
XInput9_1_0
|
XInput9_1_0
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,8 @@ static const ServerPropertyDefault kServerPropertyDefaults[] =
|
||||||
{ "spawn-monsters", "true" },
|
{ "spawn-monsters", "true" },
|
||||||
{ "spawn-npcs", "true" },
|
{ "spawn-npcs", "true" },
|
||||||
{ "tnt", "true" },
|
{ "tnt", "true" },
|
||||||
{ "trust-players", "true" }
|
{ "trust-players", "true" },
|
||||||
|
{ "auth-provider", "mojang" }
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::string BoolToString(bool value)
|
static std::string BoolToString(bool value)
|
||||||
|
|
@ -783,6 +784,11 @@ ServerPropertiesConfig LoadServerPropertiesConfig()
|
||||||
merged[it->first] = it->second;
|
merged[it->first] = it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (merged.erase("online-mode") > 0)
|
||||||
|
{
|
||||||
|
shouldWrite = true;
|
||||||
|
}
|
||||||
|
|
||||||
std::string worldName = TrimAscii(merged["level-name"]);
|
std::string worldName = TrimAscii(merged["level-name"]);
|
||||||
if (worldName.empty())
|
if (worldName.empty())
|
||||||
{
|
{
|
||||||
|
|
@ -904,6 +910,8 @@ bool SaveServerPropertiesConfig(const ServerPropertiesConfig &config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
merged.erase("online-mode");
|
||||||
|
|
||||||
std::string worldName = TrimAscii(WideToUtf8(config.worldName));
|
std::string worldName = TrimAscii(WideToUtf8(config.worldName));
|
||||||
if (worldName.empty())
|
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
|
#ifdef _XBOX_ONE
|
||||||
eDisconnect_ExitedGame,
|
eDisconnect_ExitedGame,
|
||||||
#endif
|
#endif
|
||||||
|
eDisconnect_AuthFailed,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 4J Stu - The reason was a string, but we need to send a non-locale specific reason
|
// 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(166, true, true, false, false, typeid(XZPacket), XZPacket::create);
|
||||||
map(167, false, true, false, false, typeid(GameCommandPacket), GameCommandPacket::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(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(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);
|
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);
|
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 AdditionalModelPartsPacket;
|
||||||
class XZPacket;
|
class XZPacket;
|
||||||
class GameCommandPacket;
|
class GameCommandPacket;
|
||||||
|
class AuthSchemePacket;
|
||||||
|
class AuthResponsePacket;
|
||||||
|
class AuthResultPacket;
|
||||||
|
|
||||||
class PacketListener
|
class PacketListener
|
||||||
{
|
{
|
||||||
|
|
@ -227,4 +230,7 @@ public:
|
||||||
virtual void handleKickPlayer(shared_ptr<KickPlayerPacket> packet);
|
virtual void handleKickPlayer(shared_ptr<KickPlayerPacket> packet);
|
||||||
virtual void handleXZ(shared_ptr<XZPacket> packet);
|
virtual void handleXZ(shared_ptr<XZPacket> packet);
|
||||||
virtual void handleGameCommand(shared_ptr<GameCommandPacket> 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}/Direction.h"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/Facing.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/Facing.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/Facing.h"
|
"${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.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/Pos.h"
|
"${CMAKE_CURRENT_SOURCE_DIR}/Pos.h"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/SharedConstants.cpp"
|
"${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}/AddPlayerPacket.h"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/AnimatePacket.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/AnimatePacket.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/AnimatePacket.h"
|
"${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.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/AwardStatPacket.h"
|
"${CMAKE_CURRENT_SOURCE_DIR}/AwardStatPacket.h"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/BlockRegionUpdatePacket.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/BlockRegionUpdatePacket.cpp"
|
||||||
|
|
|
||||||
|
|
@ -108,4 +108,7 @@
|
||||||
#include "KickPlayerPacket.h"
|
#include "KickPlayerPacket.h"
|
||||||
#include "XZPacket.h"
|
#include "XZPacket.h"
|
||||||
#include "GameCommandPacket.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 XMemSet128(void *a, int t, size_t s);
|
||||||
void *XPhysicalAlloc(SIZE_T a, ULONG_PTR b, ULONG_PTR c, DWORD d);
|
void *XPhysicalAlloc(SIZE_T a, ULONG_PTR b, ULONG_PTR c, DWORD d);
|
||||||
void XPhysicalFree(void *a);
|
void XPhysicalFree(void *a);
|
||||||
|
class IQNetPlayer;
|
||||||
|
void Win64_SetJoinedDedicatedServer(bool isDedicated);
|
||||||
|
bool Win64_IsJoinedDedicatedServer();
|
||||||
|
bool Win64_ShouldExposeSessionPlayer(IQNetPlayer *player);
|
||||||
|
|
||||||
class DLCManager;
|
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