diff --git a/Minecraft.Client/ClientConnection.cpp b/Minecraft.Client/ClientConnection.cpp index c092a3a0..0bccdbf6 100644 --- a/Minecraft.Client/ClientConnection.cpp +++ b/Minecraft.Client/ClientConnection.cpp @@ -57,6 +57,11 @@ #include "..\Minecraft.World\GenericStats.h" #endif +#ifdef _WINDOWS64 +#include "Xbox\Network\NetworkPlayerXbox.h" +#include "Common\Network\PlatformNetworkManagerStub.h" +#endif + ClientConnection::ClientConnection(Minecraft *minecraft, const wstring& ip, int port) { // 4J Stu - No longer used as we use the socket version below. @@ -758,6 +763,27 @@ void ClientConnection::handleAddPlayer(shared_ptr packet) player->displayName = player->name; #endif +#ifdef _WINDOWS64 + { + PlayerUID pktXuid = player->getXuid(); + const PlayerUID WIN64_XUID_BASE = (PlayerUID)0xe000d45248242f2e; + if (pktXuid >= WIN64_XUID_BASE && pktXuid < WIN64_XUID_BASE + MINECRAFT_NET_MAX_PLAYERS) + { + BYTE smallId = (BYTE)(pktXuid - WIN64_XUID_BASE); + INetworkPlayer *np = g_NetworkManager.GetPlayerBySmallId(smallId); + if (np != NULL) + { + NetworkPlayerXbox *npx = (NetworkPlayerXbox *)np; + IQNetPlayer *qp = npx->GetQNetPlayer(); + if (qp != NULL && qp->m_gamertag[0] == 0) + { + wcsncpy_s(qp->m_gamertag, 32, packet->name.c_str(), _TRUNCATE); + } + } + } + } +#endif + // printf("\t\t\t\t%d: Add player\n",packet->id,packet->yRot); int item = packet->carriedItem; @@ -893,6 +919,39 @@ void ClientConnection::handleMoveEntitySmall(shared_ptr p void ClientConnection::handleRemoveEntity(shared_ptr packet) { +#ifdef _WINDOWS64 + if (!g_NetworkManager.IsHost()) + { + for (int i = 0; i < packet->ids.length; i++) + { + shared_ptr entity = getEntity(packet->ids[i]); + if (entity != NULL && entity->GetType() == eTYPE_PLAYER) + { + shared_ptr player = dynamic_pointer_cast(entity); + if (player != NULL) + { + PlayerUID xuid = player->getXuid(); + INetworkPlayer *np = g_NetworkManager.GetPlayerByXuid(xuid); + if (np != NULL) + { + NetworkPlayerXbox *npx = (NetworkPlayerXbox *)np; + IQNetPlayer *qp = npx->GetQNetPlayer(); + if (qp != NULL) + { + extern CPlatformNetworkManagerStub *g_pPlatformNetworkManager; + g_pPlatformNetworkManager->NotifyPlayerLeaving(qp); + qp->m_smallId = 0; + qp->m_isRemote = false; + qp->m_isHostPlayer = false; + qp->m_gamertag[0] = 0; + qp->SetCustomDataValue(0); + } + } + } + } + } + } +#endif for (int i = 0; i < packet->ids.length; i++) { level->removeEntity(packet->ids[i]); diff --git a/Minecraft.Client/Common/Consoles_App.cpp b/Minecraft.Client/Common/Consoles_App.cpp index 56a1c0b6..6baa8e61 100644 --- a/Minecraft.Client/Common/Consoles_App.cpp +++ b/Minecraft.Client/Common/Consoles_App.cpp @@ -1220,12 +1220,17 @@ void CMinecraftApp::ActionGameSettings(int iPad,eGameSetting eVal) } break; case eGameSetting_Gamma: +#ifdef _WINDOWS64 + pMinecraft->options->set(Options::Option::GAMMA, ((float)GameSettingsA[iPad]->ucGamma) / 100.0f); +#else + if(iPad==ProfileManager.GetPrimaryPad()) { // ucGamma range is 0-100, UpdateGamma is 0 - 32768 float fVal=((float)GameSettingsA[iPad]->ucGamma)*327.68f; RenderManager.UpdateGamma((unsigned short)fVal); - } + } +#endif break; case eGameSetting_Difficulty: diff --git a/Minecraft.Client/Common/Network/GameNetworkManager.cpp b/Minecraft.Client/Common/Network/GameNetworkManager.cpp index 0cfcc93b..a6333597 100644 --- a/Minecraft.Client/Common/Network/GameNetworkManager.cpp +++ b/Minecraft.Client/Common/Network/GameNetworkManager.cpp @@ -40,10 +40,6 @@ #include "..\Minecraft.World\DurangoStats.h" #endif -#ifdef _WINDOWS64 -#include "..\..\Windows64\Network\P2PConnectionManagerWin.h" -#endif - // Global instance CGameNetworkManager g_NetworkManager; CPlatformNetworkManager *CGameNetworkManager::s_pPlatformNetworkManager; @@ -62,10 +58,6 @@ CGameNetworkManager::CGameNetworkManager() m_pUpsell = NULL; m_pInviteInfo = NULL; #endif - -#ifdef _WINDOWS64 - m_p2pManager = NULL; -#endif } void CGameNetworkManager::Initialise() @@ -91,9 +83,6 @@ void CGameNetworkManager::Terminate() { if( m_bInitialised ) { -#ifdef _WINDOWS64 - TeardownP2PMesh(); -#endif s_pPlatformNetworkManager->Terminate(); } } @@ -129,10 +118,6 @@ void CGameNetworkManager::DoWork() #endif s_pPlatformNetworkManager->DoWork(); -#ifdef _WINDOWS64 - TickP2P(); -#endif - #ifdef __ORBIS__ if (m_pUpsell != NULL && m_pUpsell->hasResponse()) { @@ -181,16 +166,14 @@ bool CGameNetworkManager::_RunNetworkGame(LPVOID lpParameter) return true; } } + else + { + // Client needs QNET_STATE_GAME_PLAY so that IsInGameplay() returns true + s_pPlatformNetworkManager->SetGamePlayState(); + } if( g_NetworkManager.IsLeavingGame() ) return false; -#ifdef _WINDOWS64 - if (!g_NetworkManager.IsLocalGame()) - { - g_NetworkManager.EstablishP2PMesh(); - } -#endif - app.SetGameStarted(true); // 4J-PB - if this is the trial game, start the trial timer @@ -1283,15 +1266,7 @@ bool CGameNetworkManager::SystemFlagGet(INetworkPlayer *pNetworkPlayer, int inde wstring CGameNetworkManager::GatherStats() { - wstring stats = s_pPlatformNetworkManager->GatherStats(); -#ifdef _WINDOWS64 - if (m_p2pManager != NULL) - { - stats += L"\n"; - stats += GatherP2PStats(); - } -#endif - return stats; + return s_pPlatformNetworkManager->GatherStats(); } void CGameNetworkManager::renderQueueMeter() @@ -1421,7 +1396,10 @@ void CGameNetworkManager::CreateSocket( INetworkPlayer *pNetworkPlayer, bool loc Minecraft *pMinecraft = Minecraft::GetInstance(); Socket *socket = NULL; - shared_ptr mpPlayer = pMinecraft->localplayers[pNetworkPlayer->GetUserIndex()]; + shared_ptr mpPlayer = nullptr; + int userIdx = pNetworkPlayer->GetUserIndex(); + if (userIdx >= 0 && userIdx < XUSER_MAX_COUNT) + mpPlayer = pMinecraft->localplayers[userIdx]; if( localPlayer && mpPlayer != NULL && mpPlayer->connection != NULL) { // If we already have a MultiplayerLocalPlayer here then we are doing a session type change @@ -1518,7 +1496,7 @@ void CGameNetworkManager::PlayerJoining( INetworkPlayer *pNetworkPlayer ) else { if( !pNetworkPlayer->IsHost() ) - { + { for(int idx = 0; idx < XUSER_MAX_COUNT; ++idx) { if(Minecraft::GetInstance()->localplayers[idx] != NULL) @@ -1529,29 +1507,10 @@ void CGameNetworkManager::PlayerJoining( INetworkPlayer *pNetworkPlayer ) } } #endif - -#ifdef _WINDOWS64 - if (m_p2pManager != NULL && !pNetworkPlayer->IsLocal()) - { - INetworkPlayer* localPlayer = GetLocalPlayerByUserIndex(0); - if (localPlayer != NULL) - { - m_p2pManager->EstablishDirectConnection(localPlayer, pNetworkPlayer); - } - } -#endif } void CGameNetworkManager::PlayerLeaving( INetworkPlayer *pNetworkPlayer ) { -#ifdef _WINDOWS64 - // Disconnect P2P with the leaving player - if (m_p2pManager != NULL && !pNetworkPlayer->IsLocal()) - { - m_p2pManager->DisconnectPeer(pNetworkPlayer); - } -#endif - if( pNetworkPlayer->IsLocal() ) { ProfileManager.SetCurrentGameActivity(pNetworkPlayer->GetUserIndex(),CONTEXT_PRESENCE_IDLE,false); @@ -2047,101 +2006,3 @@ void CGameNetworkManager::startAdhocMatching() } #endif - -#ifdef _WINDOWS64 - -///////////////////////////////////// P2P Networking ///////////////////////////////////// - -void CGameNetworkManager::InitializeP2PConnections() -{ - if (m_p2pManager != NULL) - return; // Already initialized - - CP2PConnectionManagerWin* winP2P = new CP2PConnectionManagerWin(); - if (winP2P->Initialize()) - { - m_p2pManager = winP2P; - - // Wire up the P2P manager to the socket layer and packet router - Socket::SetP2PManager(m_p2pManager); - g_PacketRouter.Initialize(m_p2pManager); - - app.DebugPrintf("P2P: Network manager initialized\n"); - } - else - { - delete winP2P; - app.DebugPrintf("P2P: Failed to initialize network manager\n"); - } -} - -void CGameNetworkManager::EstablishP2PMesh() -{ - if (m_p2pManager == NULL) - InitializeP2PConnections(); - - if (m_p2pManager == NULL) - return; - - // Discover our public endpoint - m_p2pManager->DiscoverPublicEndpoint(); - - INetworkPlayer* localPlayer = GetLocalPlayerByUserIndex(0); - if (localPlayer == NULL) - return; - - int playerCount = GetPlayerCount(); - for (int i = 0; i < playerCount; i++) - { - INetworkPlayer* player = GetPlayerByIndex(i); - if (player != NULL && !player->IsLocal() && player != localPlayer) - { - m_p2pManager->EstablishDirectConnection(localPlayer, player); - } - } - - app.DebugPrintf("P2P: Mesh establishment initiated for %d players\n", playerCount); -} - -void CGameNetworkManager::TeardownP2PMesh() -{ - if (m_p2pManager == NULL) - return; - - // Disconnect the P2P manager from socket layer and packet router - Socket::SetP2PManager(NULL); - g_PacketRouter.Shutdown(); - - // Shutdown and delete - m_p2pManager->Shutdown(); - delete m_p2pManager; - m_p2pManager = NULL; - - app.DebugPrintf("P2P: Mesh torn down\n"); -} - -void CGameNetworkManager::TickP2P() -{ - if (m_p2pManager != NULL) - { - m_p2pManager->Tick(); - } -} - -EP2PConnectionState CGameNetworkManager::GetP2PConnectionState(INetworkPlayer* player) -{ - if (m_p2pManager == NULL || player == NULL) - return P2P_STATE_DISCONNECTED; - - return m_p2pManager->GetConnectionState(player); -} - -wstring CGameNetworkManager::GatherP2PStats() -{ - if (m_p2pManager == NULL) - return L"P2P: Not active\n"; - - return m_p2pManager->GetDebugStats(); -} - -#endif // _WINDOWS64 diff --git a/Minecraft.Client/Common/Network/GameNetworkManager.h b/Minecraft.Client/Common/Network/GameNetworkManager.h index 945ae121..99076751 100644 --- a/Minecraft.Client/Common/Network/GameNetworkManager.h +++ b/Minecraft.Client/Common/Network/GameNetworkManager.h @@ -15,11 +15,6 @@ using namespace std; #endif #include "SessionInfo.h" -#ifdef _WINDOWS64 -#include "P2PConnectionManager.h" -#include "PacketRouter.h" -#endif - #ifdef __ORBIS__ #include "..\..\Orbis\Network\PsPlusUpsellWrapper_Orbis.h" #endif @@ -164,17 +159,6 @@ public: void renderQueueMeter(); wstring GatherRTTStats(); -#ifdef _WINDOWS64 - void InitializeP2PConnections(); - void EstablishP2PMesh(); - void TeardownP2PMesh(); - void TickP2P(); - IP2PConnectionManager* GetP2PManager() { return m_p2pManager; } - EP2PConnectionState GetP2PConnectionState(INetworkPlayer* player); - bool IsP2PActive() { return m_p2pManager != NULL; } - wstring GatherP2PStats(); -#endif - // GUI debug output // Used for debugging output @@ -217,21 +201,20 @@ private: C4JThread::Event* m_hServerReadyEvent; bool m_bInitialised; +public: + static CPlatformNetworkManager *s_pPlatformNetworkManager; + #ifdef _XBOX_ONE public: void SetFullSessionMessageOnNextSessionChange() { m_bFullSessionMessageOnNextSessionChange = true; } #endif private: float m_lastPlayerEventTimeStart; // For telemetry - static CPlatformNetworkManager *s_pPlatformNetworkManager; + bool m_bNetworkThreadRunning; int GetJoiningReadyPercentage(); bool m_bLastDisconnectWasLostRoomOnly; bool m_bFullSessionMessageOnNextSessionChange; - -#ifdef _WINDOWS64 - IP2PConnectionManager* m_p2pManager; -#endif #if defined __PS3__ || defined __PSVITA__ || defined __ORBIS__ bool m_bSignedOutofPSN; #endif diff --git a/Minecraft.Client/Common/Network/PacketRouter.cpp b/Minecraft.Client/Common/Network/PacketRouter.cpp deleted file mode 100644 index 9190a91e..00000000 --- a/Minecraft.Client/Common/Network/PacketRouter.cpp +++ /dev/null @@ -1,105 +0,0 @@ -#include "stdafx.h" -#include "PacketRouter.h" - -PacketRouter g_PacketRouter; - -PacketRouter::PacketRouter() -{ - m_p2pManager = NULL; - m_p2pEnabled = false; - m_p2pRoutedCount = 0; - m_hostRoutedCount = 0; -} - -void PacketRouter::Initialize(IP2PConnectionManager* p2pManager) -{ - m_p2pManager = p2pManager; - m_p2pEnabled = (p2pManager != NULL); -} - -void PacketRouter::Shutdown() -{ - m_p2pManager = NULL; - m_p2pEnabled = false; - ResetStats(); -} - -ERouteDecision PacketRouter::GetRouteDecision(shared_ptr packet, INetworkPlayer* targetPlayer) -{ - // If P2P is disabled or no manager, always use host - if (!m_p2pEnabled || m_p2pManager == NULL || targetPlayer == NULL) - return ROUTE_VIA_HOST; - - // Local players don't need P2P - if (targetPlayer->IsLocal()) - return ROUTE_VIA_HOST; - - int packetId = packet->getId(); - EPacketRoutingMode routingMode = Packet::GetRoutingMode(packetId); - - switch (routingMode) - { - case ROUTING_HOST_ONLY: - return ROUTE_VIA_HOST; - - case ROUTING_PREFER_DIRECT: - // Use P2P if we have a direct connection to this player - if (m_p2pManager->IsDirectConnectionAvailable(targetPlayer)) - return ROUTE_VIA_P2P; - return ROUTE_VIA_HOST; - - case ROUTING_DIRECT_ONLY: - if (m_p2pManager->IsDirectConnectionAvailable(targetPlayer)) - return ROUTE_VIA_P2P; - return ROUTE_VIA_HOST; // Fallback to host if no direct link - - case ROUTING_BROADCAST: - return ROUTE_VIA_HOST; // Broadcasts always go through host - - default: - return ROUTE_VIA_HOST; - } -} - -bool PacketRouter::RoutePacket(shared_ptr packet, INetworkPlayer* targetPlayer, - const void* serializedData, int dataSize) -{ - ERouteDecision decision = GetRouteDecision(packet, targetPlayer); - - if (decision == ROUTE_VIA_P2P && m_p2pManager != NULL) - { - EP2PChannel channel = GetChannelForPacket(packet->getId()); - m_p2pManager->SendDirect(targetPlayer, serializedData, dataSize, channel); - m_p2pRoutedCount++; - return true; - } - - m_hostRoutedCount++; - return false; // Caller should use existing host path -} - -EP2PChannel PacketRouter::GetChannelForPacket(int packetId) -{ - // Movement packets - if ((packetId >= 10 && packetId <= 13) || // MovePlayerPacket variants - (packetId >= 30 && packetId <= 34) || // MoveEntityPacket variants - (packetId >= 162 && packetId <= 165)) // MoveEntityPacketSmall variants - { - return P2P_CHANNEL_MOVEMENT; - } - - // Animation packets - if (packetId == 18 || packetId == 35) // AnimatePacket, RotateHeadPacket - { - return P2P_CHANNEL_ANIMATION; - } - - // Effects/sounds - if (packetId == 62) // LevelSoundPacket - { - return P2P_CHANNEL_EFFECTS; - } - - // Default - return P2P_CHANNEL_MOVEMENT; -} diff --git a/Minecraft.Client/Common/Network/PacketRouter.h b/Minecraft.Client/Common/Network/PacketRouter.h deleted file mode 100644 index 989aa997..00000000 --- a/Minecraft.Client/Common/Network/PacketRouter.h +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once -#include "..\..\..\Minecraft.World\Packet.h" -#include "P2PConnectionManager.h" -#include "NetworkPlayerInterface.h" - -// Packet routing decision -enum ERouteDecision -{ - ROUTE_VIA_HOST = 0, // Send through host TCP (existing path) - ROUTE_VIA_P2P, // Send directly via P2P UDP - ROUTE_VIA_BOTH // Send via both paths (redundancy for critical packets) -}; - -class PacketRouter -{ -public: - PacketRouter(); - - // Initialize with P2P manager reference - void Initialize(IP2PConnectionManager* p2pManager); - void Shutdown(); - - // Determine how to route a packet to a specific player - ERouteDecision GetRouteDecision(shared_ptr packet, INetworkPlayer* targetPlayer); - - // Route a serialized packet - returns true if sent via P2P - bool RoutePacket(shared_ptr packet, INetworkPlayer* targetPlayer, - const void* serializedData, int dataSize); - - // Enable/disable P2P routing (global toggle) - void SetP2PEnabled(bool enabled) { m_p2pEnabled = enabled; } - bool IsP2PEnabled() const { return m_p2pEnabled; } - - // Map packet IDs to P2P channels - static EP2PChannel GetChannelForPacket(int packetId); - - // Stats - int GetP2PRoutedCount() const { return m_p2pRoutedCount; } - int GetHostRoutedCount() const { return m_hostRoutedCount; } - void ResetStats() { m_p2pRoutedCount = 0; m_hostRoutedCount = 0; } - -private: - IP2PConnectionManager* m_p2pManager; - bool m_p2pEnabled; - int m_p2pRoutedCount; - int m_hostRoutedCount; -}; - -extern PacketRouter g_PacketRouter; diff --git a/Minecraft.Client/Common/Network/PlatformNetworkManagerInterface.h b/Minecraft.Client/Common/Network/PlatformNetworkManagerInterface.h index 901e59e7..4c57a56c 100644 --- a/Minecraft.Client/Common/Network/PlatformNetworkManagerInterface.h +++ b/Minecraft.Client/Common/Network/PlatformNetworkManagerInterface.h @@ -71,6 +71,7 @@ public: virtual void HostGame(int localUsersMask, bool bOnlineGame, bool bIsPrivate, unsigned char publicSlots = MINECRAFT_NET_MAX_PLAYERS, unsigned char privateSlots = 0) = 0; virtual int JoinGame(FriendSessionInfo *searchResult, int dwLocalUsersMask, int dwPrimaryUserIndex ) = 0; virtual void CancelJoinGame() {}; + virtual void SetGamePlayState() {}; virtual bool SetLocalGame(bool isLocal) = 0; virtual bool IsLocalGame() = 0; virtual void SetPrivateGame(bool isPrivate) = 0; diff --git a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp index 89ee03fa..cd0e67e2 100644 --- a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp +++ b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp @@ -1,8 +1,14 @@ -#include "stdafx.h" +#include "stdafx.h" #include "..\..\..\Minecraft.World\Socket.h" #include "..\..\..\Minecraft.World\StringHelpers.h" #include "PlatformNetworkManagerStub.h" #include "..\..\Xbox\Network\NetworkPlayerXbox.h" // TODO - stub version of this? +#ifdef _WINDOWS64 +#include "..\..\Windows64\Network\WinsockNetLayer.h" +#include "..\..\Minecraft.h" +#include "..\..\User.h" +#include "Extrax64Stubs.h" +#endif CPlatformNetworkManagerStub *g_pPlatformNetworkManager; @@ -72,7 +78,7 @@ void CPlatformNetworkManagerStub::NotifyPlayerJoined(IQNetPlayer *pQNetPlayer ) } } g_NetworkManager.PlayerJoining( networkPlayer ); - + if( createFakeSocket == true && !m_bHostChanged ) { g_NetworkManager.CreateSocket( networkPlayer, localPlayer ); @@ -92,7 +98,7 @@ void CPlatformNetworkManagerStub::NotifyPlayerJoined(IQNetPlayer *pQNetPlayer ) // g_NetworkManager.UpdateAndSetGameSessionData(); SystemFlagAddPlayer( networkPlayer ); } - + for( int idx = 0; idx < XUSER_MAX_COUNT; ++idx) { if(playerChangedCallback[idx] != NULL) @@ -114,10 +120,42 @@ void CPlatformNetworkManagerStub::NotifyPlayerJoined(IQNetPlayer *pQNetPlayer ) } } +void CPlatformNetworkManagerStub::NotifyPlayerLeaving(IQNetPlayer *pQNetPlayer) +{ + app.DebugPrintf("Player 0x%p \"%ls\" leaving.\n", pQNetPlayer, pQNetPlayer->GetGamertag()); + + INetworkPlayer *networkPlayer = getNetworkPlayer(pQNetPlayer); + if (networkPlayer == NULL) + return; + + Socket *socket = networkPlayer->GetSocket(); + if (socket != NULL) + { + if (m_pIQNet->IsHost()) + g_NetworkManager.CloseConnection(networkPlayer); + } + + if (m_pIQNet->IsHost()) + { + SystemFlagRemovePlayer(networkPlayer); + } + + g_NetworkManager.PlayerLeaving(networkPlayer); + + for (int idx = 0; idx < XUSER_MAX_COUNT; ++idx) + { + if (playerChangedCallback[idx] != NULL) + playerChangedCallback[idx](playerChangedCallbackParam[idx], networkPlayer, true); + } + + removeNetworkPlayer(pQNetPlayer); +} + bool CPlatformNetworkManagerStub::Initialise(CGameNetworkManager *pGameNetworkManager, int flagIndexSize) { m_pGameNetworkManager = pGameNetworkManager; m_flagIndexSize = flagIndexSize; + m_pIQNet = new IQNet(); g_pPlatformNetworkManager = this; for( int i = 0; i < XUSER_MAX_COUNT; i++ ) { @@ -148,19 +186,12 @@ bool CPlatformNetworkManagerStub::Initialise(CGameNetworkManager *pGameNetworkMa m_currentSearchResultsCount[i] = 0; } -#ifdef _WINDOWS64 - m_lanSessionManager.Initialize(); -#endif - // Success! return true; } void CPlatformNetworkManagerStub::Terminate() { -#ifdef _WINDOWS64 - m_lanSessionManager.Shutdown(); -#endif } int CPlatformNetworkManagerStub::GetJoiningReadyPercentage() @@ -182,8 +213,61 @@ bool CPlatformNetworkManagerStub::isSystemPrimaryPlayer(IQNetPlayer *pQNetPlayer void CPlatformNetworkManagerStub::DoWork() { #ifdef _WINDOWS64 - m_lanSessionManager.Tick(); - TickSearch(); + extern QNET_STATE _iQNetStubState; + if (_iQNetStubState == QNET_STATE_SESSION_STARTING && app.GetGameStarted()) + { + _iQNetStubState = QNET_STATE_GAME_PLAY; + if (m_pIQNet->IsHost()) + { + WinsockNetLayer::UpdateAdvertiseJoinable(true); + } + + + } + if (_iQNetStubState == QNET_STATE_IDLE) + TickSearch(); + if (_iQNetStubState == QNET_STATE_GAME_PLAY && m_pIQNet->IsHost()) + { + BYTE disconnectedSmallId; + while (WinsockNetLayer::PopDisconnectedSmallId(&disconnectedSmallId)) + { + IQNetPlayer *qnetPlayer = m_pIQNet->GetPlayerBySmallId(disconnectedSmallId); + if (qnetPlayer != NULL && qnetPlayer->m_smallId == disconnectedSmallId) + { + NotifyPlayerLeaving(qnetPlayer); + qnetPlayer->m_smallId = 0; + qnetPlayer->m_isRemote = false; + qnetPlayer->m_isHostPlayer = false; + qnetPlayer->m_gamertag[0] = 0; + qnetPlayer->SetCustomDataValue(0); + WinsockNetLayer::PushFreeSmallId(disconnectedSmallId); + if (IQNet::s_playerCount > 1) + IQNet::s_playerCount--; + } + } + + for (int i = 1; i < MINECRAFT_NET_MAX_PLAYERS; i++) + { + IQNetPlayer *qp = &IQNet::m_player[i]; + if (qp->GetCustomDataValue() == 0) + continue; + INetworkPlayer *np = (INetworkPlayer *)qp->GetCustomDataValue(); + Socket *sock = np->GetSocket(); + if (sock != NULL && sock->isClosing()) + { + WinsockNetLayer::CloseConnectionBySmallId((BYTE)i); + } + } + } + if (_iQNetStubState == QNET_STATE_GAME_PLAY && !m_pIQNet->IsHost()) + { + if (!WinsockNetLayer::IsConnected() && !g_NetworkManager.IsLeavingGame()) + { + if (app.GetDisconnectReason() == DisconnectPacket::eDisconnect_None) + app.SetDisconnectReason(DisconnectPacket::eDisconnect_Quitting); + app.SetAction(ProfileManager.GetPrimaryPad(), eAppAction_ExitWorld, (void *)TRUE); + } + } #endif } @@ -244,16 +328,31 @@ bool CPlatformNetworkManagerStub::LeaveGame(bool bMigrateHost) m_bLeavingGame = true; #ifdef _WINDOWS64 - m_lanSessionManager.StopBroadcasting(); + WinsockNetLayer::StopAdvertising(); #endif - // If we are the host wait for the game server to end if(m_pIQNet->IsHost() && g_NetworkManager.ServerStoppedValid()) { m_pIQNet->EndGame(); g_NetworkManager.ServerStoppedWait(); g_NetworkManager.ServerStoppedDestroy(); } + else + { + m_pIQNet->EndGame(); + } + + for (AUTO_VAR(it, currentNetworkPlayers.begin()); it != currentNetworkPlayers.end(); it++) + delete *it; + currentNetworkPlayers.clear(); + m_machineQNetPrimaryPlayers.clear(); + SystemFlagReset(); + +#ifdef _WINDOWS64 + WinsockNetLayer::Shutdown(); + WinsockNetLayer::Initialize(); +#endif + return true; } @@ -264,31 +363,38 @@ bool CPlatformNetworkManagerStub::_LeaveGame(bool bMigrateHost, bool bLeaveRoom) void CPlatformNetworkManagerStub::HostGame(int localUsersMask, bool bOnlineGame, bool bIsPrivate, unsigned char publicSlots /*= MINECRAFT_NET_MAX_PLAYERS*/, unsigned char privateSlots /*= 0*/) { -// #ifdef _XBOX - // 4J Stu - We probably did this earlier as well, but just to be sure! SetLocalGame( !bOnlineGame ); SetPrivateGame( bIsPrivate ); SystemFlagReset(); - // Make sure that the Primary Pad is in by default localUsersMask |= GetLocalPlayerMask( g_NetworkManager.GetPrimaryPad() ); m_bLeavingGame = false; m_pIQNet->HostGame(); +#ifdef _WINDOWS64 + IQNet::m_player[0].m_smallId = 0; + IQNet::m_player[0].m_isRemote = false; + IQNet::m_player[0].m_isHostPlayer = true; + IQNet::s_playerCount = 1; +#endif + _HostGame( localUsersMask, publicSlots, privateSlots ); -//#endif + +#ifdef _WINDOWS64 + int port = WIN64_NET_DEFAULT_PORT; + if (!WinsockNetLayer::IsActive()) + WinsockNetLayer::HostGame(port); + + const wchar_t *hostName = IQNet::m_player[0].m_gamertag; + unsigned int settings = app.GetGameHostOption(eGameHostOption_All); + WinsockNetLayer::StartAdvertising(port, hostName, settings, 0, 0, MINECRAFT_NET_VERSION); +#endif } void CPlatformNetworkManagerStub::_HostGame(int usersMask, unsigned char publicSlots /*= MINECRAFT_NET_MAX_PLAYERS*/, unsigned char privateSlots /*= 0*/) { -#ifdef _WINDOWS64 - // Start LAN broadcasting so other instances can discover this game - char* gamertag = ProfileManager.GetGamertag(0); - m_lanSessionManager.StartBroadcasting(m_hostGameSessionData, gamertag, "Minecraft World"); - app.DebugPrintf("LAN: Host game started, broadcasting session\n"); -#endif } bool CPlatformNetworkManagerStub::_StartGame() @@ -298,7 +404,52 @@ bool CPlatformNetworkManagerStub::_StartGame() int CPlatformNetworkManagerStub::JoinGame(FriendSessionInfo *searchResult, int localUsersMask, int primaryUserIndex) { +#ifdef _WINDOWS64 + if (searchResult == NULL) + return CGameNetworkManager::JOINGAME_FAIL_GENERAL; + + const char *hostIP = searchResult->data.hostIP; + int hostPort = searchResult->data.hostPort; + + if (hostPort <= 0 || hostIP[0] == 0) + return CGameNetworkManager::JOINGAME_FAIL_GENERAL; + + m_bLeavingGame = false; + IQNet::s_isHosting = false; + m_pIQNet->ClientJoinGame(); + + IQNet::m_player[0].m_smallId = 0; + IQNet::m_player[0].m_isRemote = true; + IQNet::m_player[0].m_isHostPlayer = true; + wcsncpy_s(IQNet::m_player[0].m_gamertag, 32, searchResult->data.hostName, _TRUNCATE); + + WinsockNetLayer::StopDiscovery(); + + if (!WinsockNetLayer::JoinGame(hostIP, hostPort)) + { + app.DebugPrintf("Win64 LAN: Failed to connect to %s:%d\n", hostIP, hostPort); + return CGameNetworkManager::JOINGAME_FAIL_GENERAL; + } + + BYTE localSmallId = WinsockNetLayer::GetLocalSmallId(); + + IQNet::m_player[localSmallId].m_smallId = localSmallId; + IQNet::m_player[localSmallId].m_isRemote = false; + IQNet::m_player[localSmallId].m_isHostPlayer = false; + + Minecraft *pMinecraft = Minecraft::GetInstance(); + wcscpy_s(IQNet::m_player[localSmallId].m_gamertag, 32, pMinecraft->user->name.c_str()); + IQNet::s_playerCount = localSmallId + 1; + + NotifyPlayerJoined(&IQNet::m_player[0]); + NotifyPlayerJoined(&IQNet::m_player[localSmallId]); + + m_pGameNetworkManager->StateChange_AnyToStarting(); + return CGameNetworkManager::JOINGAME_SUCCESS; +#else + return CGameNetworkManager::JOINGAME_SUCCESS; +#endif } bool CPlatformNetworkManagerStub::SetLocalGame(bool isLocal) @@ -331,54 +482,40 @@ void CPlatformNetworkManagerStub::UnRegisterPlayerChangedCallback(int iPad, void void CPlatformNetworkManagerStub::HandleSignInChange() { - return; + return; +} + +void CPlatformNetworkManagerStub::SetGamePlayState() +{ +#ifdef _WINDOWS64 + extern QNET_STATE _iQNetStubState; + _iQNetStubState = QNET_STATE_GAME_PLAY; +#endif } bool CPlatformNetworkManagerStub::_RunNetworkGame() { +#ifdef _WINDOWS64 + extern QNET_STATE _iQNetStubState; + _iQNetStubState = QNET_STATE_GAME_PLAY; + + for (DWORD i = 0; i < IQNet::s_playerCount; i++) + { + if (IQNet::m_player[i].m_isRemote) + { + INetworkPlayer *pNetworkPlayer = getNetworkPlayer(&IQNet::m_player[i]); + if (pNetworkPlayer != NULL && pNetworkPlayer->GetSocket() != NULL) + { + Socket::addIncomingSocket(pNetworkPlayer->GetSocket()); + } + } + } +#endif return true; } void CPlatformNetworkManagerStub::UpdateAndSetGameSessionData(INetworkPlayer *pNetworkPlayerLeaving /*= NULL*/) { -// DWORD playerCount = m_pIQNet->GetPlayerCount(); -// -// if( this->m_bLeavingGame ) -// return; -// -// if( GetHostPlayer() == NULL ) -// return; -// -// for(unsigned int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; ++i) -// { -// if( i < playerCount ) -// { -// INetworkPlayer *pNetworkPlayer = GetPlayerByIndex(i); -// -// // We can call this from NotifyPlayerLeaving but at that point the player is still considered in the session -// if( pNetworkPlayer != pNetworkPlayerLeaving ) -// { -// m_hostGameSessionData.players[i] = ((NetworkPlayerXbox *)pNetworkPlayer)->GetUID(); -// -// char *temp; -// temp = (char *)wstringtofilename( pNetworkPlayer->GetOnlineName() ); -// memcpy(m_hostGameSessionData.szPlayers[i],temp,XUSER_NAME_SIZE); -// } -// else -// { -// m_hostGameSessionData.players[i] = NULL; -// memset(m_hostGameSessionData.szPlayers[i],0,XUSER_NAME_SIZE); -// } -// } -// else -// { -// m_hostGameSessionData.players[i] = NULL; -// memset(m_hostGameSessionData.szPlayers[i],0,XUSER_NAME_SIZE); -// } -// } -// -// m_hostGameSessionData.hostPlayerUID = ((NetworkPlayerXbox *)GetHostPlayer())->GetQNetPlayer()->GetXuid(); -// m_hostGameSessionData.m_uiGameHostSettings = app.GetGameHostOption(eGameHostOption_All); } int CPlatformNetworkManagerStub::RemovePlayerOnSocketClosedThreadProc( void* lpParam ) @@ -525,28 +662,59 @@ wstring CPlatformNetworkManagerStub::GatherRTTStats() void CPlatformNetworkManagerStub::TickSearch() { #ifdef _WINDOWS64 - // Start listening if we're not already and not hosting - if (!m_lanSessionManager.IsListening() && !m_lanSessionManager.IsBroadcasting()) - { - m_lanSessionManager.StartListening(); - } + if (m_SessionsUpdatedCallback == NULL) + return; - // If new sessions were discovered, notify the UI - if (m_lanSessionManager.HasNewSessions()) - { - m_lanSessionManager.ClearNewSessionsFlag(); - m_bSearchResultsReady = true; + static DWORD lastSearchTime = 0; + DWORD now = GetTickCount(); + if (now - lastSearchTime < 2000) + return; + lastSearchTime = now; - if (m_SessionsUpdatedCallback != NULL) - { - m_SessionsUpdatedCallback(m_pSearchParam); - } - } + SearchForGames(); #endif } void CPlatformNetworkManagerStub::SearchForGames() { +#ifdef _WINDOWS64 + std::vector lanSessions = WinsockNetLayer::GetDiscoveredSessions(); + + for (size_t i = 0; i < friendsSessions[0].size(); i++) + delete friendsSessions[0][i]; + friendsSessions[0].clear(); + + for (size_t i = 0; i < lanSessions.size(); i++) + { + FriendSessionInfo *info = new FriendSessionInfo(); + size_t nameLen = wcslen(lanSessions[i].hostName); + info->displayLabel = new wchar_t[nameLen + 1]; + wcscpy_s(info->displayLabel, nameLen + 1, lanSessions[i].hostName); + info->displayLabelLength = (unsigned char)nameLen; + info->displayLabelViewableStartIndex = 0; + + info->data.netVersion = lanSessions[i].netVersion; + info->data.m_uiGameHostSettings = lanSessions[i].gameHostSettings; + info->data.texturePackParentId = lanSessions[i].texturePackParentId; + info->data.subTexturePackId = lanSessions[i].subTexturePackId; + info->data.isReadyToJoin = lanSessions[i].isJoinable; + info->data.isJoinable = lanSessions[i].isJoinable; + strncpy_s(info->data.hostIP, sizeof(info->data.hostIP), lanSessions[i].hostIP, _TRUNCATE); + info->data.hostPort = lanSessions[i].hostPort; + wcsncpy_s(info->data.hostName, XUSER_NAME_SIZE, lanSessions[i].hostName, _TRUNCATE); + info->data.playerCount = lanSessions[i].playerCount; + info->data.maxPlayers = lanSessions[i].maxPlayers; + + info->sessionId = (SessionID)((unsigned __int64)inet_addr(lanSessions[i].hostIP) | ((unsigned __int64)lanSessions[i].hostPort << 32)); + + friendsSessions[0].push_back(info); + } + + m_searchResultsCount[0] = (int)friendsSessions[0].size(); + + if (m_SessionsUpdatedCallback != NULL) + m_SessionsUpdatedCallback(m_pSearchParam); +#endif } int CPlatformNetworkManagerStub::SearchForGamesThreadProc( void* lpParameter ) @@ -562,12 +730,10 @@ void CPlatformNetworkManagerStub::SetSearchResultsReady(int resultCount) vector *CPlatformNetworkManagerStub::GetSessionList(int iPad, int localPlayers, bool partyOnly) { -#ifdef _WINDOWS64 - return m_lanSessionManager.GetSessionList(); -#else vector *filteredList = new vector(); + for (size_t i = 0; i < friendsSessions[0].size(); i++) + filteredList->push_back(friendsSessions[0][i]); return filteredList; -#endif } bool CPlatformNetworkManagerStub::GetGameSessionInfo(int iPad, SessionID sessionId, FriendSessionInfo *foundSessionInfo) @@ -596,15 +762,6 @@ void CPlatformNetworkManagerStub::ForceFriendsSessionRefresh() delete m_pSearchResults[i]; m_pSearchResults[i] = NULL; } - -#ifdef _WINDOWS64 - // Restart LAN listening to force a fresh discovery - if (m_lanSessionManager.IsListening()) - { - m_lanSessionManager.StopListening(); - m_lanSessionManager.StartListening(); - } -#endif } INetworkPlayer *CPlatformNetworkManagerStub::addNetworkPlayer(IQNetPlayer *pQNetPlayer) @@ -636,7 +793,7 @@ INetworkPlayer *CPlatformNetworkManagerStub::getNetworkPlayer(IQNetPlayer *pQNet INetworkPlayer *CPlatformNetworkManagerStub::GetLocalPlayerByUserIndex(int userIndex ) { - return getNetworkPlayer(m_pIQNet->GetLocalPlayerByUserIndex(userIndex)); + return getNetworkPlayer(m_pIQNet->GetLocalPlayerByUserIndex(userIndex)); } INetworkPlayer *CPlatformNetworkManagerStub::GetPlayerByIndex(int playerIndex) @@ -651,7 +808,21 @@ INetworkPlayer * CPlatformNetworkManagerStub::GetPlayerByXuid(PlayerUID xuid) INetworkPlayer * CPlatformNetworkManagerStub::GetPlayerBySmallId(unsigned char smallId) { - return getNetworkPlayer(m_pIQNet->GetPlayerBySmallId(smallId)); + IQNetPlayer *qnetPlayer = m_pIQNet->GetPlayerBySmallId(smallId); + if (qnetPlayer == NULL) + return NULL; + + INetworkPlayer *networkPlayer = getNetworkPlayer(qnetPlayer); +#ifdef _WINDOWS64 + if (networkPlayer == NULL && smallId != 0 && !m_pIQNet->IsHost()) + { + qnetPlayer->m_isRemote = true; + qnetPlayer->m_isHostPlayer = false; + NotifyPlayerJoined(qnetPlayer); + networkPlayer = getNetworkPlayer(qnetPlayer); + } +#endif + return networkPlayer; } INetworkPlayer *CPlatformNetworkManagerStub::GetHostPlayer() diff --git a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.h b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.h index c030ccdf..77246abe 100644 --- a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.h +++ b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.h @@ -6,10 +6,6 @@ using namespace std; #include "PlatformNetworkManagerInterface.h" #include "SessionInfo.h" -#ifdef _WINDOWS64 -#include "LANSessionManager.h" -#endif - class CPlatformNetworkManagerStub : public CPlatformNetworkManager { friend class CGameNetworkManager; @@ -59,15 +55,16 @@ public: virtual void HandleSignInChange(); virtual bool _RunNetworkGame(); + virtual void SetGamePlayState(); private: bool isSystemPrimaryPlayer(IQNetPlayer *pQNetPlayer); virtual bool _LeaveGame(bool bMigrateHost, bool bLeaveRoom); virtual void _HostGame(int dwUsersMask, unsigned char publicSlots = MINECRAFT_NET_MAX_PLAYERS, unsigned char privateSlots = 0); virtual bool _StartGame(); - +public: IQNet * m_pIQNet; // pointer to QNet interface - +private: HANDLE m_notificationListener; vector m_machineQNetPrimaryPlayers; // collection of players that we deem to be the main one for that system @@ -165,16 +162,11 @@ public: virtual void GetFullFriendSessionInfo( FriendSessionInfo *foundSession, void (* FriendSessionUpdatedFn)(bool success, void *pParam), void *pParam ); virtual void ForceFriendsSessionRefresh(); -private: +public: void NotifyPlayerJoined( IQNetPlayer *pQNetPlayer ); + void NotifyPlayerLeaving( IQNetPlayer *pQNetPlayer ); #ifndef _XBOX void FakeLocalPlayerJoined() { NotifyPlayerJoined(m_pIQNet->GetLocalPlayerByUserIndex(0)); } #endif - -#ifdef _WINDOWS64 - CLANSessionManager* GetLANSessionManager() { return &m_lanSessionManager; } -private: - CLANSessionManager m_lanSessionManager; -#endif }; diff --git a/Minecraft.Client/Common/Network/SessionInfo.h b/Minecraft.Client/Common/Network/SessionInfo.h index 31472a19..c4b6b9ef 100644 --- a/Minecraft.Client/Common/Network/SessionInfo.h +++ b/Minecraft.Client/Common/Network/SessionInfo.h @@ -63,12 +63,19 @@ typedef struct _GameSessionData #else typedef struct _GameSessionData { - unsigned short netVersion; // 2 bytes - unsigned int m_uiGameHostSettings; // 4 bytes - unsigned int texturePackParentId; // 4 bytes - unsigned char subTexturePackId; // 1 byte + unsigned short netVersion; + unsigned int m_uiGameHostSettings; + unsigned int texturePackParentId; + unsigned char subTexturePackId; - bool isReadyToJoin; // 1 byte + bool isReadyToJoin; + bool isJoinable; + + char hostIP[64]; + int hostPort; + wchar_t hostName[XUSER_NAME_SIZE]; + unsigned char playerCount; + unsigned char maxPlayers; _GameSessionData() { @@ -76,6 +83,13 @@ typedef struct _GameSessionData m_uiGameHostSettings = 0; texturePackParentId = 0; subTexturePackId = 0; + isReadyToJoin = false; + isJoinable = true; + memset(hostIP, 0, sizeof(hostIP)); + hostPort = 0; + memset(hostName, 0, sizeof(hostName)); + playerCount = 0; + maxPlayers = MINECRAFT_NET_MAX_PLAYERS; } } GameSessionData; #endif diff --git a/Minecraft.Client/Common/UI/UIController.cpp b/Minecraft.Client/Common/UI/UIController.cpp index a40edb6b..fa3377eb 100644 --- a/Minecraft.Client/Common/UI/UIController.cpp +++ b/Minecraft.Client/Common/UI/UIController.cpp @@ -691,7 +691,7 @@ void UIController::tickInput() #endif { #ifdef _WINDOWS64 - if (!g_KBMInput.IsMouseGrabbed()) + if (!g_KBMInput.IsMouseGrabbed() && g_KBMInput.IsKBMActive()) { UIScene *pScene = NULL; for (int grp = 0; grp < eUIGroup_COUNT && !pScene; ++grp) @@ -1018,7 +1018,7 @@ void UIController::handleKeyPress(unsigned int iPad, unsigned int key) released = InputManager.ButtonReleased(iPad,key); // Toggle #ifdef _WINDOWS64 - if (iPad == 0) + if (iPad == 0 && g_KBMInput.IsKBMActive()) { int vk = 0; switch (key) @@ -1029,10 +1029,10 @@ void UIController::handleKeyPress(unsigned int iPad, unsigned int key) case ACTION_MENU_DOWN: vk = VK_DOWN; break; case ACTION_MENU_LEFT: vk = VK_LEFT; break; case ACTION_MENU_RIGHT: vk = VK_RIGHT; break; - case ACTION_MENU_X: vk = 'E'; break; + case ACTION_MENU_X: vk = 'R'; break; case ACTION_MENU_Y: vk = VK_TAB; break; case ACTION_MENU_LEFT_SCROLL: vk = 'Q'; break; - case ACTION_MENU_RIGHT_SCROLL: vk = 'R'; break; + case ACTION_MENU_RIGHT_SCROLL: vk = 'E'; break; case ACTION_MENU_PAGEUP: vk = VK_PRIOR; break; case ACTION_MENU_PAGEDOWN: vk = VK_NEXT; break; } @@ -1043,7 +1043,20 @@ void UIController::handleKeyPress(unsigned int iPad, unsigned int key) if (!pressed && !released && g_KBMInput.IsKeyDown(vk)) { down = true; } } - if ((key == ACTION_MENU_OK || key == ACTION_MENU_A) && !g_KBMInput.IsMouseGrabbed()) + if ((key == ACTION_MENU_UP || key == ACTION_MENU_DOWN) && !pressed && !released && !down) + { + bool inCrafting = (m_groups[(EUIGroup)(iPad+1)]->FindScene(eUIScene_Crafting2x2Menu) != NULL) + || (m_groups[(EUIGroup)(iPad+1)]->FindScene(eUIScene_Crafting3x3Menu) != NULL); + if (inCrafting) + { + int wsKey = (key == ACTION_MENU_UP) ? 'W' : 'S'; + if (g_KBMInput.IsKeyPressed(wsKey)) { pressed = true; down = true; } + if (g_KBMInput.IsKeyReleased(wsKey)) { released = true; down = false; } + if (!pressed && !released && g_KBMInput.IsKeyDown(wsKey)) { down = true; } + } + } + + if ((key == ACTION_MENU_OK || key == ACTION_MENU_A) && !g_KBMInput.IsMouseGrabbed() && g_KBMInput.IsKBMActive()) { if (g_KBMInput.IsMouseButtonPressed(KeyboardMouseInput::MOUSE_LEFT)) { pressed = true; down = true; } if (g_KBMInput.IsMouseButtonReleased(KeyboardMouseInput::MOUSE_LEFT)) { released = true; down = false; } @@ -1477,7 +1490,7 @@ GDrawTexture * RADLINK UIController::TextureSubstitutionCreateCallback ( void * // 4J Stu - All our flash controls that allow replacing textures use a special 64x64 symbol // Force this size here so that our images don't get scaled wildly - #if (defined __ORBIS__ || defined _DURANGO ) + #if (defined __ORBIS__ || defined _DURANGO || defined _WINDOWS64 ) *width = 96; *height = 96; #else diff --git a/Minecraft.Client/Common/UI/UIScene.cpp b/Minecraft.Client/Common/UI/UIScene.cpp index e71fd22c..e7b58f3f 100644 --- a/Minecraft.Client/Common/UI/UIScene.cpp +++ b/Minecraft.Client/Common/UI/UIScene.cpp @@ -1164,7 +1164,7 @@ void UIScene::externalCallback(IggyExternalFunctionCallUTF16 * call) void UIScene::registerSubstitutionTexture(const wstring &textureName, PBYTE pbData, DWORD dwLength, bool deleteData) { - m_registeredTextures[textureName] = deleteData;; + m_registeredTextures[textureName] = deleteData; ui.registerSubstitutionTexture(textureName, pbData, dwLength); } diff --git a/Minecraft.Client/Common/UI/UIScene_Credits.cpp b/Minecraft.Client/Common/UI/UIScene_Credits.cpp index 11558598..b5d5fa67 100644 --- a/Minecraft.Client/Common/UI/UIScene_Credits.cpp +++ b/Minecraft.Client/Common/UI/UIScene_Credits.cpp @@ -454,6 +454,11 @@ SCreditTextItemDef UIScene_Credits::gs_aCreditDefs[MAX_CREDIT_STRINGS] = { L"Victoria Bruder (CompuCom Systems Inc)", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING,eSmallText }, #elif defined(_WIN64) + { L"", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING,eSmallText }, // extra blank line + { L"Code Used", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING,eExtraLargeText }, + { L"", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING,eSmallText }, // extra blank line + { L"LCEMP Multiplayer", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING,eLargeText }, + { L"notpies", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING,eSmallText }, #elif defined(__PSVITA__) // font credits { L"", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING,eSmallText }, // extra blank line diff --git a/Minecraft.Client/Common/UI/UIScene_Credits.h b/Minecraft.Client/Common/UI/UIScene_Credits.h index 4116628d..1a302744 100644 --- a/Minecraft.Client/Common/UI/UIScene_Credits.h +++ b/Minecraft.Client/Common/UI/UIScene_Credits.h @@ -6,6 +6,7 @@ #define PSVITA_CREDITS_COUNT 77 #define PS4_CREDITS_COUNT 75 #define XBOXONE_CREDITS_COUNT (75+318) +#define WIN64_EXTRA_CREDITS_COUNT 5 #define MILES_AND_IGGY_CREDITS_COUNT 8 #define DYNAMODE_FONT_CREDITS_COUNT 2 #define PS3_DOLBY_CREDIT 4 @@ -15,8 +16,10 @@ #define MAX_CREDIT_STRINGS (PS3_CREDITS_COUNT + MILES_AND_IGGY_CREDITS_COUNT + DYNAMODE_FONT_CREDITS_COUNT + PS3_DOLBY_CREDIT) #elif defined(__ORBIS__) #define MAX_CREDIT_STRINGS (PS4_CREDITS_COUNT + MILES_AND_IGGY_CREDITS_COUNT + DYNAMODE_FONT_CREDITS_COUNT) -#elif defined(_DURANGO) || defined _WIN64 +#elif defined(_DURANGO) #define MAX_CREDIT_STRINGS (XBOXONE_CREDITS_COUNT + MILES_AND_IGGY_CREDITS_COUNT) +#elif defined _WIN64 +#define MAX_CREDIT_STRINGS (XBOXONE_CREDITS_COUNT + WIN64_EXTRA_CREDITS_COUNT + MILES_AND_IGGY_CREDITS_COUNT) #elif defined(__PSVITA__) #define MAX_CREDIT_STRINGS (PSVITA_CREDITS_COUNT + MILES_AND_IGGY_CREDITS_COUNT + DYNAMODE_FONT_CREDITS_COUNT) #endif diff --git a/Minecraft.Client/Common/UI/UIScene_DLCOffersMenu.cpp b/Minecraft.Client/Common/UI/UIScene_DLCOffersMenu.cpp index 33be0fdf..c5731705 100644 --- a/Minecraft.Client/Common/UI/UIScene_DLCOffersMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_DLCOffersMenu.cpp @@ -15,6 +15,8 @@ // Windows64 DLC store stuff blahhhh - whisper #ifdef _WINDOWS64 #include "Windows64_DLCOffers.h" +#include "..\..\User.h" +#include "Windows64_Minecraft.h" #include static std::unordered_map s_offerIndexToListPos; #endif @@ -166,10 +168,12 @@ void UIScene_DLCOffersMenu::handleInput(int iPad, int key, bool repeat, bool pre int iIndex = getControlChildFocus(); if (iIndex >= 0 && iIndex < Windows64_DLCOffers::Get().GetOfferCount()) { + Minecraft *pMinecraft = Minecraft::GetInstance(); + Windows64_DLCOffers::Get().InstallOffer(iIndex, [](const wchar_t* id, bool ok, void*) { wprintf(L"[DLC] Install %ls: %ls\n", id, ok ? L"OK" : L"FAILED"); - }, nullptr); + }, nullptr, pMinecraft->user->name.c_str()); } } #endif @@ -249,9 +253,9 @@ void UIScene_DLCOffersMenu::handlePress(F64 controlId, F64 childId) iIndex < Windows64_DLCOffers::Get().GetOfferCount()) { Windows64_DLCOffers::Get().InstallOffer(iIndex, - [](const wchar_t* id, bool ok, void*) { - wprintf(L"[DLC] Install %ls: %ls\n", id, ok ? L"OK" : L"FAILED"); - }, nullptr); + [](const wchar_t* id, bool ok, void*) { + wprintf(L"[DLC] Install %ls: %ls\n", id, ok ? L"OK" : L"FAILED"); + }, nullptr, Minecraft::GetInstance()->user->name.c_str()); } #else diff --git a/Minecraft.Client/Common/UI/UIScene_LoadMenu.cpp b/Minecraft.Client/Common/UI/UIScene_LoadMenu.cpp index e2cbc2aa..ff40d1cc 100644 --- a/Minecraft.Client/Common/UI/UIScene_LoadMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_LoadMenu.cpp @@ -191,6 +191,8 @@ UIScene_LoadMenu::UIScene_LoadMenu(int iPad, void *initData, UILayer *parentLaye else { + + #if defined(__PS3__) || defined(__ORBIS__)|| defined(_DURANGO) || defined (__PSVITA__) // convert to utf16 uint16_t u16Message[MAX_SAVEFILENAME_LENGTH]; diff --git a/Minecraft.Client/Common/UI/UIScene_LoadOrJoinMenu.cpp b/Minecraft.Client/Common/UI/UIScene_LoadOrJoinMenu.cpp index c5c034f1..ef5a339b 100644 --- a/Minecraft.Client/Common/UI/UIScene_LoadOrJoinMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_LoadOrJoinMenu.cpp @@ -2,11 +2,6 @@ #include "UI.h" #include "UIScene_LoadOrJoinMenu.h" -#ifdef _WINDOWS64 -#include "..\Network\P2PConnectionManager.h" -#include "..\Network\GameNetworkManager.h" -#endif - #include "..\..\..\Minecraft.World\StringHelpers.h" #include "..\..\..\Minecraft.World\net.minecraft.world.item.h" #include "..\..\..\Minecraft.World\net.minecraft.world.level.h" @@ -288,13 +283,8 @@ UIScene_LoadOrJoinMenu::~UIScene_LoadOrJoinMenu() g_NetworkManager.SetSessionsUpdatedCallback( NULL, NULL ); app.SetLiveLinkRequired( false ); - if(m_currentSessions) - { - for(AUTO_VAR(it, m_currentSessions->begin()); it < m_currentSessions->end(); ++it) - { - delete (*it); - } - } + delete m_currentSessions; + m_currentSessions = NULL; #if TO_BE_IMPLEMENTED // Reset the background downloading, in case we changed it by attempting to download a texture pack @@ -1739,24 +1729,6 @@ void UIScene_LoadOrJoinMenu::UpdateGamesList() } } -#ifdef _WINDOWS64 - if (g_NetworkManager.IsP2PActive() && g_NetworkManager.IsInGameplay()) - { - int directPeers = g_NetworkManager.GetP2PManager()->GetDirectPeerCount(); - int totalOnline = g_NetworkManager.GetOnlinePlayerCount(); - if (directPeers > 0 && totalOnline > 1) - { - wchar_t labelWithP2P[256]; - swprintf_s(labelWithP2P, 256, L"%s [P2P:%d/%d]", sessionInfo->displayLabel, directPeers, totalOnline - 1); - m_buttonListGames.addItem(labelWithP2P, textureName); - } - else - { - m_buttonListGames.addItem(sessionInfo->displayLabel, textureName); - } - } - else -#endif m_buttonListGames.addItem( sessionInfo->displayLabel, textureName ); if(memcmp( &selectedSessionId, &sessionInfo->sessionId, sizeof(SessionID) ) == 0) diff --git a/Minecraft.Client/Common/UI/UIScene_LoadOrJoinMenu.h b/Minecraft.Client/Common/UI/UIScene_LoadOrJoinMenu.h index 589d9b3a..01d94b05 100644 --- a/Minecraft.Client/Common/UI/UIScene_LoadOrJoinMenu.h +++ b/Minecraft.Client/Common/UI/UIScene_LoadOrJoinMenu.h @@ -105,7 +105,6 @@ private: bool m_bSaveTransferCancelled; #endif bool m_bUpdateSaveSize; - wstring m_directConnectAddress; public: UIScene_LoadOrJoinMenu(int iPad, void *initData, UILayer *parentLayer); @@ -154,7 +153,6 @@ public: static int DeleteSaveDataReturned(LPVOID lpParam,bool bRes); static int RenameSaveDataReturned(LPVOID lpParam,bool bRes); static int KeyboardCompleteWorldNameCallback(LPVOID lpParam,bool bRes); - static int KeyboardCompleteDirectConnectCallback(LPVOID lpParam,bool bRes); protected: void handlePress(F64 controlId, F64 childId); void LoadLevelGen(LevelGenerationOptions *levelGen); diff --git a/Minecraft.Client/Common/UI/UIScene_MainMenu.cpp b/Minecraft.Client/Common/UI/UIScene_MainMenu.cpp index 525ae802..0876586e 100644 --- a/Minecraft.Client/Common/UI/UIScene_MainMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_MainMenu.cpp @@ -9,6 +9,7 @@ #ifdef _WINDOWS64 #include "AchievementScreen.h" #include "StatsCounter.h" +#include "Windows64_Minecraft.h" #endif #ifdef __ORBIS__ #include @@ -48,7 +49,7 @@ UIScene_MainMenu::UIScene_MainMenu(int iPad, void *initData, UILayer *parentLaye if(ProfileManager.IsFullVersion()) { m_bTrialVersion=false; - m_buttons[(int)eControl_UnlockOrDLC].init(app.GetString(IDS_DOWNLOADABLECONTENT),eControl_UnlockOrDLC); + m_buttons[(int)eControl_UnlockOrDLC].init(L"LCE Workshop",eControl_UnlockOrDLC); } else { @@ -115,6 +116,8 @@ UIScene_MainMenu::UIScene_MainMenu(int iPad, void *initData, UILayer *parentLaye // Fix for #45154 - Frontend: DLC: Content can only be downloaded from the frontend if you have not joined/exited multiplayer XBackgroundDownloadSetMode(XBACKGROUND_DOWNLOAD_MODE_ALWAYS_ALLOW); #endif + + Minecraft::GetInstance()->user->name = convStringToWstring( ProfileManager.GetGamertag(ProfileManager.GetPrimaryPad())); } UIScene_MainMenu::~UIScene_MainMenu() @@ -179,7 +182,7 @@ void UIScene_MainMenu::handleGainFocus(bool navBack) if(navBack && ProfileManager.IsFullVersion()) { // Replace the Unlock Full Game with Downloadable Content - m_buttons[(int)eControl_UnlockOrDLC].setLabel(app.GetString(IDS_DOWNLOADABLECONTENT)); + m_buttons[(int)eControl_UnlockOrDLC].setLabel(L"LCE Workshop"); } #if TO_BE_IMPLEMENTED @@ -368,6 +371,12 @@ void UIScene_MainMenu::handlePress(F64 controlId, F64 childId) break; #endif +#ifdef _WINDOWS64 + case eControl_Exit: + PostMessage(GetMinecraftWindowHWND(), WM_CLOSE, 0, 0); + break; +#endif + default: __debugbreak(); } diff --git a/Minecraft.Client/EnderDragonRenderer.cpp b/Minecraft.Client/EnderDragonRenderer.cpp index 8c531d4e..65998db0 100644 --- a/Minecraft.Client/EnderDragonRenderer.cpp +++ b/Minecraft.Client/EnderDragonRenderer.cpp @@ -75,12 +75,7 @@ void EnderDragonRenderer::renderModel(shared_ptr _mob, float wp, float w glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glColor4f(1, 0, 0, 0.5f); -#ifdef __PSVITA__ - // AP - not sure that the usecompiled flag is supposed to be false. This makes it really slow on vita. Making it true still seems to look the same model->render(mob, wp, ws, bob, headRotMinusBodyRot, headRotx, scale, true); -#else - model->render(mob, wp, ws, bob, headRotMinusBodyRot, headRotx, scale, false); -#endif glEnable(GL_TEXTURE_2D); glDisable(GL_BLEND); glDepthFunc(GL_LEQUAL); diff --git a/Minecraft.Client/Extrax64Stubs.cpp b/Minecraft.Client/Extrax64Stubs.cpp index 1ca86337..d757377b 100644 --- a/Minecraft.Client/Extrax64Stubs.cpp +++ b/Minecraft.Client/Extrax64Stubs.cpp @@ -1,4 +1,5 @@ #include "stdafx.h" +#include "Extrax64Stubs.h" #ifndef __PS3__ //#include #endif // __PS3__ @@ -40,6 +41,7 @@ #ifdef _WINDOWS64 #include "discord_game_sdk.h" #pragma comment(lib, "discord_game_sdk.dll.lib") +#include "Windows64\Network\WinsockNetLayer.h" #endif #ifndef E_FAIL @@ -72,6 +74,90 @@ VOID ATG::XMLParser::RegisterSAXCallbackInterface( ISAXCallback *pISAXCallback ) static char s_discordUsername[32] = ""; static ULONGLONG s_discordXuid = 0; static struct IDiscordCore *discordCore = NULL; +static char s_DiscordJoinSecret[64] = ""; +static IDiscordActivityEvents s_ActivityEvents; + + +void TickDiscord() +{ + if(discordCore != NULL) + discordCore->run_callbacks(discordCore); +} + +static char s_pendingJoinIP[64] = ""; +static int s_pendingJoinPort = 0; +static bool s_hasPendingJoin = false; + + + +static void OnActivityJoin(void* event_data, const char* join_secret) +{ + printf("Discord OnActivityJoin: secret=%s\n", join_secret); + + char secret[64]; + strncpy_s(secret, sizeof(secret), join_secret, _TRUNCATE); + char* colon = strchr(secret, ':'); + if (colon != NULL) + { + *colon = '\0'; + strncpy_s(s_pendingJoinIP, sizeof(s_pendingJoinIP), secret, _TRUNCATE); + s_pendingJoinPort = atoi(colon + 1); + + FriendSessionInfo info; +memset(&info, 0, sizeof(info)); +strncpy_s(info.data.hostIP, sizeof(info.data.hostIP), s_pendingJoinIP, _TRUNCATE); +info.data.hostPort = s_pendingJoinPort; +info.data.isJoinable = true; +info.data.isReadyToJoin = true; + +// Allocate displayLabel on heap so the destructor can safely free it +static const wchar_t* label = L"Discord Invite"; +size_t labelLen = wcslen(label); +info.displayLabel = new wchar_t[labelLen + 1]; +wcscpy_s(info.displayLabel, labelLen + 1, label); +info.displayLabelLength = (unsigned char)labelLen; + + + + g_NetworkManager.s_pPlatformNetworkManager->JoinGame(&info, 1,0); + } +} + +static void OnActivityInvite(void* event_data, enum EDiscordActivityActionType type, struct DiscordUser* user, struct DiscordActivity* activity) +{ + printf("Discord OnActivityInvite from user: %s\n", user->username); + + struct IDiscordActivityManager* am = discordCore->get_activity_manager(discordCore); + if (am != NULL) + { + static int s_dummy = 0; + am->accept_invite(am, user->id, &s_dummy, + [](void* data, enum EDiscordResult result) + { + printf("accept_invite result: %d\n", result); + }); + } +} + +static void OnActivityJoinRequest(void* event_data, struct DiscordUser* user) +{ + printf("Discord join request from: %s\n", user->username); + + struct IDiscordActivityManager* am = discordCore->get_activity_manager(discordCore); + if(am != NULL) + { + static int s_dummy = 0; + } +} + +void Discord_SetJoinSecret(const char* hostIp, WORD port) +{ + printf("Discord_SetJoinSecret called!\n"); + sprintf_s(s_DiscordJoinSecret, sizeof(s_DiscordJoinSecret), "%s:%u", hostIp, (unsigned)port); + + ProfileManager.SetCurrentGameActivity(0,4,false); +} + static void InitDiscordCore() { @@ -80,19 +166,31 @@ static void InitDiscordCore() static IDiscordCoreEvents coreEvents; memset(&coreEvents, 0, sizeof(coreEvents)); + memset(&s_ActivityEvents, 0, sizeof(s_ActivityEvents)); + s_ActivityEvents.on_activity_join = OnActivityJoin; + s_ActivityEvents.on_activity_invite = OnActivityInvite; + s_ActivityEvents.on_activity_join_request = OnActivityJoinRequest; + + struct DiscordCreateParams params; DiscordCreateParamsSetDefault(¶ms); params.client_id = 1477855587978182828LL; params.flags = DiscordCreateFlags_NoRequireDiscord; params.events = &coreEvents; params.event_data = NULL; + params.activity_events = &s_ActivityEvents; enum EDiscordResult result = DiscordCreate(DISCORD_VERSION, ¶ms, &discordCore); if(result != DiscordResult_Ok) { printf("DiscordCreate failed: %d\n", result); discordCore = NULL; + return; } + + struct IDiscordActivityManager* am = discordCore->get_activity_manager(discordCore); + if (am != NULL) + am->register_command(am, "Minecraft.exe"); } static ULONGLONG DiscordIdToXuid(DiscordSnowflake discordId) @@ -108,9 +206,6 @@ static void InitDiscordIdentity() for(int i = 0; i < 100; ++i) { - discordCore->run_callbacks(discordCore); - Sleep(10); - struct IDiscordUserManager *userManager = discordCore->get_user_manager(discordCore); if(userManager != NULL) { @@ -256,7 +351,7 @@ void PIXSetMarkerDeprecated(int a, char *b, ...) {} bool IsEqualXUID(PlayerUID a, PlayerUID b) { -#if defined(__PS3__) || defined(__ORBIS__) || defined (__PSVITA__) || defined(_DURANGO) +#if defined(__PS3__) || defined(__ORBIS__) || defined (__PSVITA__) || defined(_DURANGO) || defined(_WINDOWS64) return (a == b); #else return false; @@ -280,33 +375,6 @@ namespace char s_stubGamertagA[XUSER_MAX_COUNT][32]; wchar_t s_stubGamertagW[XUSER_MAX_COUNT][32]; - DWORD StubSupportedLocalUserMask() - { - DWORD mask = 0; - for(unsigned int i = 0; i < XUSER_MAX_COUNT && i < 32; ++i) - { - mask |= (1u << i); - } - return mask; - } - - bool StubSupportsLocalUser(DWORD userIndex) - { - return userIndex < XUSER_MAX_COUNT; - } - - DWORD StubClampLocalUserMask(DWORD usersMask) - { - DWORD clampedMask = usersMask & StubSupportedLocalUserMask(); - if(clampedMask == 0) - { - clampedMask = (1u << 0); - } - return clampedMask; - } - - - void EnsureStubIdentity() { if(s_stubIdentityInitialised) @@ -347,11 +415,7 @@ namespace } else { -#ifdef _WINDOWS64 sprintf_s(s_stubGamertagA[i], sizeof(s_stubGamertagA[i]), "Player%u-%u", pid % 10000, i); -#else - snprintf(s_stubGamertagA[i], sizeof(s_stubGamertagA[i]), "Player%u-%u", pid % 10000, i); -#endif } } @@ -365,70 +429,36 @@ namespace } } -extern bool _bQNetStubIsHost; -BYTE IQNetPlayer::GetSmallId() -{ - int idx = (int)(this - &IQNet::m_player[0]); - if(idx < 0 || idx >= (int)XUSER_MAX_COUNT) - { - return 0; - } - if(!_bQNetStubIsHost) - { - // When acting as a client, reserve small id 0 for the remote host. - return (BYTE)(idx + 1); - } - return (BYTE)idx; -} +QNET_STATE _iQNetStubState = QNET_STATE_IDLE; + +BYTE IQNetPlayer::GetSmallId() { return m_smallId; } void IQNetPlayer::SendData(IQNetPlayer *player, const void *pvData, DWORD dwDataSize, DWORD dwFlags) { - app.DebugPrintf("Sending from 0x%x to 0x%x %d bytes\n",this,player,dwDataSize); -} -bool IQNetPlayer::IsSameSystem(IQNetPlayer *player) -{ - if(player == NULL) + if (WinsockNetLayer::IsActive()) { - return false; + WinsockNetLayer::SendToSmallId(player->m_smallId, pvData, dwDataSize); } - - ULONG_PTR ptr = (ULONG_PTR)player; - ULONG_PTR begin = (ULONG_PTR)&IQNet::m_player[0]; - ULONG_PTR end = (ULONG_PTR)&IQNet::m_player[XUSER_MAX_COUNT]; - return (ptr >= begin && ptr < end); } +bool IQNetPlayer::IsSameSystem(IQNetPlayer *player) { return (this == player) || (!m_isRemote && !player->m_isRemote); } DWORD IQNetPlayer::GetSendQueueSize( IQNetPlayer *player, DWORD dwFlags ) { return 0; } DWORD IQNetPlayer::GetCurrentRtt() { return 0; } -extern bool _bQNetStubIsHost; -bool IQNetPlayer::IsHost() { return _bQNetStubIsHost && this == &IQNet::m_player[0]; } +bool IQNetPlayer::IsHost() { return m_isHostPlayer; } bool IQNetPlayer::IsGuest() { return false; } -bool IQNetPlayer::IsLocal() { return true; } +bool IQNetPlayer::IsLocal() { return !m_isRemote; } PlayerUID IQNetPlayer::GetXuid() { - EnsureStubIdentity(); - int idx = (int)(this - &IQNet::m_player[0]); - if(idx < 0 || idx >= (int)XUSER_MAX_COUNT) + // For the local player (smallId 0 on host, or the assigned smallId on client), + // try Discord XUID first for richer identity + if (!m_isRemote) { - idx = 0; + EnsureStubIdentity(); + if (s_discordXuid != 0) + return s_discordXuid | 0x100000000ULL; } -#if defined(__PS3__) || defined(__ORBIS__) || defined(__PSVITA__) - PlayerUID uid; - uid.setUserID(0x70000000u | (unsigned int)(idx & 0xFF)); - return uid; -#else - return (s_stubXuidBase + (ULONGLONG)(idx & 0x0F)) | 0x100000000ULL; -#endif + return (PlayerUID)(0xe000d45248242f2e + m_smallId); } -LPCWSTR IQNetPlayer::GetGamertag() -{ - EnsureStubIdentity(); - int idx = (int)(this - &IQNet::m_player[0]); - if(idx < 0 || idx >= (int)XUSER_MAX_COUNT) - { - idx = 0; - } - return s_stubGamertagW[idx]; -} -int IQNetPlayer::GetSessionIndex() { return GetSmallId(); } +LPCWSTR IQNetPlayer::GetGamertag() { return m_gamertag; } +int IQNetPlayer::GetSessionIndex() { return m_smallId; } bool IQNetPlayer::IsTalking() { return false; } bool IQNetPlayer::IsMutedByLocalUser(DWORD dwUserIndex) { return false; } bool IQNetPlayer::HasVoice() { return false; } @@ -441,156 +471,118 @@ ULONG_PTR IQNetPlayer::GetCustomDataValue() { return m_customData; } -IQNetPlayer IQNet::m_player[4]; +IQNetPlayer IQNet::m_player[MINECRAFT_NET_MAX_PLAYERS]; +DWORD IQNet::s_playerCount = 1; +bool IQNet::s_isHosting = true; -bool _bQNetStubGameRunning = false; -bool _bQNetStubIsHost = true; -DWORD _dwQNetStubLocalUsersMask = (1u << 0); - -HRESULT IQNet::AddLocalPlayerByUserIndex(DWORD dwUserIndex) +void Win64_SetupRemoteQNetPlayer(IQNetPlayer *player, BYTE smallId, bool isHost, bool isLocal) { - if(!StubSupportsLocalUser(dwUserIndex)) - { - return E_FAIL; - } - - _dwQNetStubLocalUsersMask |= (1u << dwUserIndex); - return S_OK; + player->m_smallId = smallId; + player->m_isRemote = !isLocal; + player->m_isHostPlayer = isHost; + swprintf_s(player->m_gamertag, 32, L"Player%d", smallId); + if (smallId >= IQNet::s_playerCount) + IQNet::s_playerCount = smallId + 1; } + +static bool Win64_IsActivePlayer(IQNetPlayer *p, DWORD index) +{ + if (index == 0) return true; + return (p->GetCustomDataValue() != 0); +} + +HRESULT IQNet::AddLocalPlayerByUserIndex(DWORD dwUserIndex){ return S_OK; } IQNetPlayer *IQNet::GetHostPlayer() { return &m_player[0]; } IQNetPlayer *IQNet::GetLocalPlayerByUserIndex(DWORD dwUserIndex) { - if(!StubSupportsLocalUser(dwUserIndex)) + if (s_isHosting) { + if (dwUserIndex < MINECRAFT_NET_MAX_PLAYERS && + !m_player[dwUserIndex].m_isRemote && + Win64_IsActivePlayer(&m_player[dwUserIndex], dwUserIndex)) + return &m_player[dwUserIndex]; return NULL; } - - return (_dwQNetStubLocalUsersMask & (1u << dwUserIndex)) ? &m_player[dwUserIndex] : NULL; + if (dwUserIndex != 0) + return NULL; + for (DWORD i = 0; i < s_playerCount; i++) + { + if (!m_player[i].m_isRemote && Win64_IsActivePlayer(&m_player[i], i)) + return &m_player[i]; + } + return NULL; } IQNetPlayer *IQNet::GetPlayerByIndex(DWORD dwPlayerIndex) { - if(!_bQNetStubGameRunning) + DWORD found = 0; + for (DWORD i = 0; i < s_playerCount; i++) { - return NULL; - } - - DWORD currentIndex = 0; - for(DWORD localUser = 0; localUser < XUSER_MAX_COUNT; ++localUser) - { - if((_dwQNetStubLocalUsersMask & (1u << localUser)) == 0) + if (Win64_IsActivePlayer(&m_player[i], i)) { - continue; + if (found == dwPlayerIndex) return &m_player[i]; + found++; } - - if(currentIndex == dwPlayerIndex) - { - return &m_player[localUser]; - } - - ++currentIndex; } - - return NULL; + return &m_player[0]; } IQNetPlayer *IQNet::GetPlayerBySmallId(BYTE SmallId) { - if(!_bQNetStubGameRunning) - { + if (SmallId >= MINECRAFT_NET_MAX_PLAYERS) return NULL; - } - for(DWORD localUser = 0; localUser < XUSER_MAX_COUNT; ++localUser) - { - if((_dwQNetStubLocalUsersMask & (1u << localUser)) == 0) - { - continue; - } - - if(m_player[localUser].GetSmallId() == SmallId) - { - return &m_player[localUser]; - } - } - - return NULL; + m_player[SmallId].m_smallId = SmallId; + if (SmallId >= s_playerCount) + s_playerCount = SmallId + 1; + return &m_player[SmallId]; } IQNetPlayer *IQNet::GetPlayerByXuid(PlayerUID xuid) { - if(!_bQNetStubGameRunning) + for (DWORD i = 0; i < MINECRAFT_NET_MAX_PLAYERS; i++) { - return NULL; + if (Win64_IsActivePlayer(&m_player[i], i) && m_player[i].GetXuid() == xuid) return &m_player[i]; } - - for(DWORD localUser = 0; localUser < XUSER_MAX_COUNT; ++localUser) - { - if((_dwQNetStubLocalUsersMask & (1u << localUser)) == 0) - { - continue; - } - - IQNetPlayer *localPlayer = &m_player[localUser]; - PlayerUID localXuidOnline = localPlayer->GetXuid(); -#if defined(__PS3__) || defined(__ORBIS__) || defined(__PSVITA__) || defined(_DURANGO) - if(xuid == localXuidOnline) - { - return localPlayer; - } -#else - PlayerUID localXuidOffline = localXuidOnline & ~0x100000000ULL; - if(xuid == localXuidOnline || xuid == localXuidOffline) - { - return localPlayer; - } -#endif - } - return NULL; } DWORD IQNet::GetPlayerCount() { - if(!_bQNetStubGameRunning) - { - return 0; - } - DWORD count = 0; - for(DWORD localUser = 0; localUser < XUSER_MAX_COUNT; ++localUser) + for (DWORD i = 0; i < s_playerCount; i++) { - if(_dwQNetStubLocalUsersMask & (1u << localUser)) - { - ++count; - } + if (Win64_IsActivePlayer(&m_player[i], i)) count++; } - return count; } -QNET_STATE IQNet::GetState() { return _bQNetStubGameRunning ? QNET_STATE_GAME_PLAY : QNET_STATE_IDLE; } -bool IQNet::IsHost() { return _bQNetStubIsHost; } -HRESULT IQNet::JoinGameFromInviteInfo(DWORD dwUserIndex, DWORD dwUserMask, const INVITE_INFO *pInviteInfo) +QNET_STATE IQNet::GetState() { return _iQNetStubState; } +bool IQNet::IsHost() { return s_isHosting; } +HRESULT IQNet::JoinGameFromInviteInfo(DWORD dwUserIndex, DWORD dwUserMask, const INVITE_INFO *pInviteInfo) { return S_OK; } +void IQNet::HostGame() { _iQNetStubState = QNET_STATE_SESSION_STARTING; s_isHosting = true; } +void IQNet::ClientJoinGame() { - UNREFERENCED_PARAMETER(dwUserIndex); - UNREFERENCED_PARAMETER(pInviteInfo); - _bQNetStubIsHost = false; - _bQNetStubGameRunning = true; - _dwQNetStubLocalUsersMask = StubClampLocalUserMask(dwUserMask); - if(StubSupportsLocalUser(dwUserIndex)) + _iQNetStubState = QNET_STATE_SESSION_STARTING; + s_isHosting = false; + + for (int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; i++) { - _dwQNetStubLocalUsersMask |= (1u << dwUserIndex); + m_player[i].m_smallId = (BYTE)i; + m_player[i].m_isRemote = true; + m_player[i].m_isHostPlayer = false; + m_player[i].m_gamertag[0] = 0; + m_player[i].SetCustomDataValue(0); } - _dwQNetStubLocalUsersMask = StubClampLocalUserMask(_dwQNetStubLocalUsersMask); - return S_OK; -} -void IQNet::HostGame() -{ - _bQNetStubIsHost = true; - _bQNetStubGameRunning = true; - _dwQNetStubLocalUsersMask = (1u << 0); } void IQNet::EndGame() { - _bQNetStubGameRunning = false; - _bQNetStubIsHost = true; - _dwQNetStubLocalUsersMask = (1u << 0); + _iQNetStubState = QNET_STATE_IDLE; + s_isHosting = false; + s_playerCount = 1; + for (int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; i++) + { + m_player[i].m_smallId = (BYTE)i; + m_player[i].m_isRemote = false; + m_player[i].m_isHostPlayer = false; + m_player[i].m_gamertag[0] = 0; + m_player[i].SetCustomDataValue(0); + } } DWORD MinecraftDynamicConfigurations::GetTrialTime() { return DYNAMIC_CONFIG_DEFAULT_TRIAL_TIME; } @@ -766,7 +758,6 @@ DWORD XEnableGuestSignin(BOOL fEnable) { return 0; } #ifdef _WINDOWS64 static void *profileData[4]; static bool s_bProfileIsFullVersion; -static int s_iPrimaryPad = 0; void C_4JProfile::Initialise( DWORD dwTitleID, DWORD dwOfferID, unsigned short usProfileVersion, @@ -836,21 +827,8 @@ void C_4JProfile::SetTrialTextStringTable(CXuiStringTable *pStringTable,int i void C_4JProfile::SetTrialAwardText(eAwardType AwardType,int iTitle,int iText) {} int C_4JProfile::GetLockedProfile() { return 0; } void C_4JProfile::SetLockedProfile(int iProf) {} -bool C_4JProfile::IsSignedIn(int iQuadrant) -{ - if(iQuadrant < 0 || iQuadrant >= (int)XUSER_MAX_COUNT) - { - return false; - } - - if(iQuadrant == 0) - { - return true; - } - - return InputManager.IsPadConnected(iQuadrant); -} -bool C_4JProfile::IsSignedInLive(int iProf) { return IsSignedIn(iProf); } +bool C_4JProfile::IsSignedIn(int iQuadrant) { return ( iQuadrant == 0); } +bool C_4JProfile::IsSignedInLive(int iProf) { return true; } bool C_4JProfile::IsGuest(int iQuadrant) { return false; } UINT C_4JProfile::RequestSignInUI(bool bFromInvite,bool bLocalGame,bool bNoGuestsAllowed,bool bMultiplayerSignIn,bool bAddUser, int( *Func)(LPVOID,const bool, const int iPad),LPVOID lpParam,int iQuadrant) { return 0; } UINT C_4JProfile::DisplayOfflineProfile(int( *Func)(LPVOID,const bool, const int iPad),LPVOID lpParam,int iQuadrant) { return 0; } @@ -859,18 +837,23 @@ void C_4JProfile::SetPrimaryPlayerChanged(bool bVal) {} bool C_4JProfile::QuerySigninStatus(void) { return true; } void C_4JProfile::GetXUID(int iPad, PlayerUID *pXuid,bool bOnlineXuid) { - EnsureStubIdentity(); if(pXuid == NULL) { return; } - - ULONGLONG value = s_stubXuidBase + (ULONGLONG)(iPad & 0x0F); - if(bOnlineXuid) +#ifdef _WINDOWS64 + if (iPad != 0) { - value |= 0x100000000ULL; + *pXuid = INVALID_XUID; + return; } - *pXuid = value; + if (IQNet::s_isHosting) + *pXuid = 0xe000d45248242f2e; + else + *pXuid = 0xe000d45248242f2e + WinsockNetLayer::GetLocalSmallId(); +#else + *pXuid = 0xe000d45248242f2e + iPad; +#endif } BOOL C_4JProfile::AreXUIDSEqual(PlayerUID xuid1,PlayerUID xuid2) { return xuid1 == xuid2; } BOOL C_4JProfile::XUIDIsGuest(PlayerUID xuid) { return false; } @@ -891,45 +874,17 @@ void C_4JProfile::AllowedPlayerCreatedContent(int iPad, bool thisQuadrantOnly BOOL C_4JProfile::CanViewPlayerCreatedContent(int iPad, bool thisQuadrantOnly, PPlayerUID pXuids, DWORD dwXuidCount ) { return true; } bool C_4JProfile::GetProfileAvatar(int iPad,int( *Func)(LPVOID lpParam,PBYTE pbThumbnail,DWORD dwThumbnailBytes), LPVOID lpParam) { return false; } void C_4JProfile::CancelProfileAvatarRequest() {} -int C_4JProfile::GetPrimaryPad() { return s_iPrimaryPad; } -void C_4JProfile::SetPrimaryPad(int iPad) -{ - if(iPad >= 0 && iPad < (int)XUSER_MAX_COUNT && IsSignedIn(iPad)) - { - s_iPrimaryPad = iPad; - } - else - { - s_iPrimaryPad = 0; - } -} +int C_4JProfile::GetPrimaryPad() { return 0; } +void C_4JProfile::SetPrimaryPad(int iPad) {} #ifdef _DURANGO char fakeGamerTag[32] = "PlayerName"; void SetFakeGamertag(char *name){ strcpy_s(fakeGamerTag, name); } char* C_4JProfile::GetGamertag(int iPad){ return fakeGamerTag; } #else -char* C_4JProfile::GetGamertag(int iPad) -{ - EnsureStubIdentity(); - if(iPad < 0 || iPad >= (int)XUSER_MAX_COUNT) - { - iPad = 0; - } - return s_stubGamertagA[iPad]; -} -wstring C_4JProfile::GetDisplayName(int iPad) -{ - EnsureStubIdentity(); - if(iPad < 0 || iPad >= (int)XUSER_MAX_COUNT) - { - iPad = 0; - } - wchar_t displayName[32]; - swprintf(displayName, 32, L"%S", s_stubGamertagA[iPad]); - return displayName; -} +char* C_4JProfile::GetGamertag(int iPad){ extern char g_Win64Username[17]; return g_Win64Username; } +wstring C_4JProfile::GetDisplayName(int iPad){ extern wchar_t g_Win64UsernameW[17]; return g_Win64UsernameW; } #endif -bool C_4JProfile::IsFullVersion() { return true; } +bool C_4JProfile::IsFullVersion() { return s_bProfileIsFullVersion; } void C_4JProfile::SetSignInChangeCallback(void ( *Func)(LPVOID, bool, unsigned int),LPVOID lpParam) {} void C_4JProfile::SetNotificationsCallback(void ( *Func)(LPVOID, DWORD, unsigned int),LPVOID lpParam) {} bool C_4JProfile::RegionIsNorthAmerica(void) { return false; } @@ -950,10 +905,6 @@ int C_4JProfile::SetOldProfileVersionCallback(int( *Func)(LPVOID,unsigned ch // To store the dashboard preferences for controller flipped, etc. C_4JProfile::PROFILESETTINGS ProfileSettingsA[XUSER_MAX_COUNT]; -#define MAX_AWARDS 32 - -static bool s_awardsUnlocked[XUSER_MAX_COUNT][MAX_AWARDS] = {}; - C_4JProfile::PROFILESETTINGS * C_4JProfile::GetDashboardProfileSettings(int iPad) { return &ProfileSettingsA[iPad]; } void C_4JProfile::WriteToProfile(int iQuadrant, bool bGameDefinedDataChanged, bool bOverride5MinuteLimitOnProfileWrites) {} void C_4JProfile::ForceQueuedProfileWrites(int iPad) {} @@ -979,24 +930,9 @@ void C_4JProfile::RegisterAward(int iAwardNumber,int iGamerconfigID, eAwardTy CXuiStringTable*pStringTable, int iTitleStr, int iTextStr, int iAcceptStr, char *pszThemeName, unsigned int ulThemeSize) {} int C_4JProfile::GetAwardId(int iAwardNumber) { return 0; } eAwardType C_4JProfile::GetAwardType(int iAwardNumber) { return eAwardType_Achievement; } -bool C_4JProfile::CanBeAwarded(int iQuadrant, int iAwardNumber) -{ - if (iQuadrant < 0 || iQuadrant >= XUSER_MAX_COUNT) return false; - if (iAwardNumber < 0 || iAwardNumber >= MAX_AWARDS) return false; - return !s_awardsUnlocked[iQuadrant][iAwardNumber]; -} -void C_4JProfile::Award(int iQuadrant, int iAwardNumber, bool bForce) -{ - if (iQuadrant < 0 || iQuadrant >= XUSER_MAX_COUNT) return; - if (iAwardNumber < 0 || iAwardNumber >= MAX_AWARDS) return; - s_awardsUnlocked[iQuadrant][iAwardNumber] = true; -} -bool C_4JProfile::IsAwardsFlagSet(int iQuadrant, int iAward) -{ - if (iQuadrant < 0 || iQuadrant >= XUSER_MAX_COUNT) return false; - if (iAward < 0 || iAward >= MAX_AWARDS) return false; - return s_awardsUnlocked[iQuadrant][iAward]; -} +bool C_4JProfile::CanBeAwarded(int iQuadrant, int iAwardNumber) { return false; } +void C_4JProfile::Award(int iQuadrant, int iAwardNumber, bool bForce) {} +bool C_4JProfile::IsAwardsFlagSet(int iQuadrant, int iAward) { return false; } void C_4JProfile::RichPresenceInit(int iPresenceCount, int iContextCount) {} void C_4JProfile::RegisterRichPresenceContext(int iGameConfigContextID) {} void C_4JProfile::SetRichPresenceContextValue(int iPad,int iContextID, int iVal) {} @@ -1030,6 +966,17 @@ void C_4JProfile::SetCurrentGameActivity(int iPad,int iNewPresence, bool bSet case 4: // In-game Online strncpy_s(activity.details, sizeof(activity.details), "Playing Minecraft", _TRUNCATE); strncpy_s(activity.state, sizeof(activity.state), "Multiplayer", _TRUNCATE); + + activity.instance = true; + + sprintf_s(activity.party.id, sizeof(activity.party.id), "lce-whisper-session%lu", GetCurrentProcessId()); + activity.party.size.current_size = (int32_t)g_NetworkManager.GetPlayerCount(); + activity.party.size.max_size = (int32_t)MINECRAFT_NET_MAX_PLAYERS; + + if (g_NetworkManager.IsHost() && s_DiscordJoinSecret[0] != '\0') + { + strncpy_s(activity.secrets.join, sizeof(activity.secrets.join), s_DiscordJoinSecret, _TRUNCATE); + } break; case 5: // In-game Singleplayer strncpy_s(activity.details, sizeof(activity.details), "Playing Minecraft", _TRUNCATE); @@ -1052,12 +999,6 @@ void C_4JProfile::SetCurrentGameActivity(int iPad,int iNewPresence, bool bSet [](void *data, enum EDiscordResult result) { printf("update_activity result: %d\n", result); }); - - for(int i = 0; i < 50; ++i) - { - discordCore->run_callbacks(discordCore); - Sleep(10); - } #endif } void C_4JProfile::DisplayFullVersionPurchase(bool bRequired, int iQuadrant, int iUpsellParam) {} diff --git a/Minecraft.Client/Extrax64Stubs.h b/Minecraft.Client/Extrax64Stubs.h new file mode 100644 index 00000000..6f510893 --- /dev/null +++ b/Minecraft.Client/Extrax64Stubs.h @@ -0,0 +1,3 @@ +void Discord_SetJoinSecret(const char* hostIp, WORD port); +bool Discord_HasPendingJoin(char* outIP, int* outPort); +void TickDiscord(); \ No newline at end of file diff --git a/Minecraft.Client/GameRenderer.cpp b/Minecraft.Client/GameRenderer.cpp index 82302b51..fe5b74b1 100644 --- a/Minecraft.Client/GameRenderer.cpp +++ b/Minecraft.Client/GameRenderer.cpp @@ -928,7 +928,7 @@ void GameRenderer::updateLightTexture(float a) } } - float brightness = 0.0f; // 4J - TODO - was mc->options->gamma; + float brightness = mc->options->gamma; // 4J - TODO - was mc->options->gamma; if (_r > 1) _r = 1; if (_g > 1) _g = 1; if (_b > 1) _b = 1; diff --git a/Minecraft.Client/Input.cpp b/Minecraft.Client/Input.cpp index 6772b3c1..b44e81b1 100644 --- a/Minecraft.Client/Input.cpp +++ b/Minecraft.Client/Input.cpp @@ -46,7 +46,7 @@ void Input::tick(LocalPlayer *player) float kbXA = 0.0f; float kbYA = 0.0f; #ifdef _WINDOWS64 - if (iPad == 0 && g_KBMInput.IsMouseGrabbed()) + if (iPad == 0 && g_KBMInput.IsMouseGrabbed() && g_KBMInput.IsKBMActive()) { if( pMinecraft->localgameModes[iPad]->isInputAllowed(MINECRAFT_ACTION_LEFT) || pMinecraft->localgameModes[iPad]->isInputAllowed(MINECRAFT_ACTION_RIGHT) ) kbXA = g_KBMInput.GetMoveX(); @@ -94,7 +94,7 @@ void Input::tick(LocalPlayer *player) } #ifdef _WINDOWS64 - if (iPad == 0 && g_KBMInput.IsMouseGrabbed()) + if (iPad == 0 && g_KBMInput.IsMouseGrabbed() && g_KBMInput.IsKBMActive()) { // Left Shift = sneak (hold to crouch) if (pMinecraft->localgameModes[iPad]->isInputAllowed(MINECRAFT_ACTION_SNEAK_TOGGLE)) @@ -166,7 +166,7 @@ void Input::tick(LocalPlayer *player) float turnY = ty * abs(ty) * turnSpeed; #ifdef _WINDOWS64 - if (iPad == 0 && g_KBMInput.IsMouseGrabbed()) + if (iPad == 0 && g_KBMInput.IsMouseGrabbed() && g_KBMInput.IsKBMActive()) { float mouseSensitivity = ((float)app.GetGameSettings(iPad,eGameSetting_Sensitivity_InGame)) / 100.0f; float mouseLookScale = 5.0f; @@ -190,7 +190,7 @@ void Input::tick(LocalPlayer *player) unsigned int jump = InputManager.GetValue(iPad, MINECRAFT_ACTION_JUMP); bool kbJump = false; #ifdef _WINDOWS64 - kbJump = (iPad == 0) && g_KBMInput.IsMouseGrabbed() && g_KBMInput.IsKeyDown(KeyboardMouseInput::KEY_JUMP); + kbJump = (iPad == 0) && g_KBMInput.IsMouseGrabbed() && g_KBMInput.IsKBMActive() && g_KBMInput.IsKeyDown(KeyboardMouseInput::KEY_JUMP); #endif if( (jump > 0 || kbJump) && pMinecraft->localgameModes[iPad]->isInputAllowed(MINECRAFT_ACTION_JUMP) ) jumping = true; diff --git a/Minecraft.Client/KeyboardMouseInput.cpp b/Minecraft.Client/KeyboardMouseInput.cpp index 40dbd229..07433fc7 100644 --- a/Minecraft.Client/KeyboardMouseInput.cpp +++ b/Minecraft.Client/KeyboardMouseInput.cpp @@ -35,6 +35,8 @@ void KeyboardMouseInput::Init() m_mouseGrabbed = false; m_cursorHiddenForUI = false; m_windowFocused = true; + m_kbmActive = true; + m_screenWantsCursorHidden = false; m_hasInput = false; RAWINPUTDEVICE rid; diff --git a/Minecraft.Client/KeyboardMouseInput.h b/Minecraft.Client/KeyboardMouseInput.h index 5a18d943..043acf15 100644 --- a/Minecraft.Client/KeyboardMouseInput.h +++ b/Minecraft.Client/KeyboardMouseInput.h @@ -64,6 +64,12 @@ public: void SetWindowFocused(bool focused); bool IsWindowFocused() const { return m_windowFocused; } + void SetKBMActive(bool active) { m_kbmActive = active; } + bool IsKBMActive() const { return m_kbmActive; } + + void SetScreenCursorHidden(bool hidden) { m_screenWantsCursorHidden = hidden; } + bool IsScreenCursorHidden() const { return m_screenWantsCursorHidden; } + bool HasAnyInput() const { return m_hasInput; } float GetMoveX() const; @@ -106,6 +112,9 @@ private: bool m_windowFocused; + bool m_kbmActive; + bool m_screenWantsCursorHidden; + bool m_hasInput; }; diff --git a/Minecraft.Client/LevelRenderer.cpp b/Minecraft.Client/LevelRenderer.cpp index 98360078..7160ba53 100644 --- a/Minecraft.Client/LevelRenderer.cpp +++ b/Minecraft.Client/LevelRenderer.cpp @@ -1941,7 +1941,12 @@ bool LevelRenderer::updateDirtyChunks() { if( (!onlyRebuild) || globalChunkFlags[ pClipChunk->globalIdx ] & CHUNK_FLAG_COMPILED || +#ifdef _WINDOWS64 + ( distSq < 96 * 96 ) ) // Always rebuild really near things or else building (say) at tower up into empty blocks when we are low on memory will not create render data +#else ( distSq < 20 * 20 ) ) // Always rebuild really near things or else building (say) at tower up into empty blocks when we are low on memory will not create render data +#endif + { considered++; // Is this chunk nearer than our nearest? diff --git a/Minecraft.Client/LevelRenderer.h b/Minecraft.Client/LevelRenderer.h index 41eb3593..d54812cf 100644 --- a/Minecraft.Client/LevelRenderer.h +++ b/Minecraft.Client/LevelRenderer.h @@ -49,6 +49,8 @@ public: static const int MAX_COMMANDBUFFER_ALLOCATIONS = 448 * 1024 * 1024; // 4J - added - hard limit is 512 so giving a lot of headroom here for fragmentation (have seen 16MB lost to fragmentation in multiplayer crash dump before) #elif defined __PS3__ static const int MAX_COMMANDBUFFER_ALLOCATIONS = 110 * 1024 * 1024; // 4J - added +#elif defined _WINDOWS64 + static const int MAX_COMMANDBUFFER_ALLOCATIONS = 2047 * 1024 * 1024; #else static const int MAX_COMMANDBUFFER_ALLOCATIONS = 55 * 1024 * 1024; // 4J - added #endif diff --git a/Minecraft.Client/LocalPlayer.cpp b/Minecraft.Client/LocalPlayer.cpp index 0964b3c4..c0cc8ccd 100644 --- a/Minecraft.Client/LocalPlayer.cpp +++ b/Minecraft.Client/LocalPlayer.cpp @@ -331,6 +331,12 @@ void LocalPlayer::aiStep() } } if (isSneaking()) sprintTriggerTime = 0; + + if (input->sprinting && !isSprinting() && enoughFoodToSprint && !isUsingItem() && !hasEffect(MobEffect::blindness) && input->ya >= runTreshold) + { + setSprinting(true); + } + // 4J-PB - try not stopping sprint on collision //if (isSprinting() && (input->ya < runTreshold || horizontalCollision || !enoughFoodToSprint)) if (isSprinting() && (input->ya < runTreshold || !enoughFoodToSprint)) @@ -480,7 +486,10 @@ void LocalPlayer::aiStep() // Check if the player is idle and the rich presence needs updated if( !m_bIsIdle && InputManager.GetIdleSeconds( m_iPad ) > PLAYER_IDLE_TIME ) { - ProfileManager.SetCurrentGameActivity(m_iPad,CONTEXT_PRESENCE_IDLE,false); + #ifdef _WINDOWS64 + if (!g_NetworkManager.s_pPlatformNetworkManager->IsInSession()) +#endif + ProfileManager.SetCurrentGameActivity(m_iPad, CONTEXT_PRESENCE_IDLE, false); m_bIsIdle = true; } else if ( m_bIsIdle && InputManager.GetIdleSeconds( m_iPad ) < PLAYER_IDLE_TIME ) diff --git a/Minecraft.Client/Minecraft.Client.vcxproj b/Minecraft.Client/Minecraft.Client.vcxproj index 60e1ab1e..0f9fcc42 100644 --- a/Minecraft.Client/Minecraft.Client.vcxproj +++ b/Minecraft.Client/Minecraft.Client.vcxproj @@ -1297,7 +1297,7 @@ if not exist "$(TargetDir)\savedata" mkdir "$(TargetDir)\savedata" d3d11.lib;..\Minecraft.World\x64_Debug\Minecraft.World.lib;%(AdditionalDependencies);XInput9_1_0.lib;..\Minecraft.Client\Windows64\Miles\Lib\mss64.lib;discord_game_sdk.dll.lib NotSet false - $(ProjectDir)\discord;%(AdditionalLibraryDirectories) + %(AdditionalLibraryDirectories) $(ProjectDir)xbox\xex-dev.xml @@ -1403,7 +1403,7 @@ xcopy /q /y /i /s /e $(ProjectDir)Durango\CU $(LayoutDir)Image\Loose\CU - Use + NotUsing Level3 ProgramDatabase Full @@ -1411,9 +1411,9 @@ xcopy /q /y /i /s /e $(ProjectDir)Durango\CU $(LayoutDir)Image\Loose\CUfalse $(OutDir)$(ProjectName).pch MultiThreaded - _LARGE_WORLDS;_DEBUG_MENUS_ENABLED;_CRT_NON_CONFORMING_SWPRINTFS;_CRT_SECURE_NO_WARNINGS;_WINDOWS64;%(PreprocessorDefinitions) + _LARGE_WORLDS;_DEBUG_MENUS_ENABLED;_CRT_NON_CONFORMING_SWPRINTFS;_CRT_SECURE_NO_WARNINGS;_WINDOWS64;MINIUPNP_STATICLIB;%(PreprocessorDefinitions) Disabled - Windows64\Iggy\include;$(ProjectDir);$(ProjectDir)\discord;%(AdditionalIncludeDirectories) + Windows64\Iggy\include;$(ProjectDir);$(ProjectDir)\discord;$(ProjectDir)miniupnpc\;%(AdditionalIncludeDirectories) true true Default @@ -1423,10 +1423,10 @@ xcopy /q /y /i /s /e $(ProjectDir)Durango\CU $(LayoutDir)Image\Loose\CU true $(OutDir)$(ProjectName).pdb - d3d11.lib;..\Minecraft.World\x64_Release\Minecraft.World.lib;XInput9_1_0.lib;Windows64\Iggy\lib\iggy_w64.lib;discord_game_sdk.dll.lib;%(AdditionalDependencies) + d3d11.lib;..\Minecraft.World\x64_Release\Minecraft.World.lib;XInput9_1_0.lib;Windows64\Iggy\lib\iggy_w64.lib;discord_game_sdk.dll.lib;IPHlpApi.lib;%(AdditionalDependencies) NotSet false - $(ProjectDir)\discord;%(AdditionalLibraryDirectories) + $(ProjectDir)\discord;C:\Program Files (x86)\Windows Kits\8.0\Lib\win8\um\x64;%(AdditionalLibraryDirectories) $(ProjectDir)xbox\xex-dev.xml @@ -2938,6 +2938,8 @@ xcopy /q /y /i /s /e $(ProjectDir)Durango\CU $(LayoutDir)Image\Loose\CUtrue true + + true @@ -4363,8 +4365,10 @@ xcopy /q /y /i /s /e $(ProjectDir)Durango\CU $(LayoutDir)Image\Loose\CU + + @@ -16609,37 +16613,6 @@ xcopy /q /y /i /s /e $(ProjectDir)Durango\CU $(LayoutDir)Image\Loose\CUtrue true - - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - false - false - false - false - false - false - false - true - true - true - true - true - true - true - true true @@ -16710,9 +16683,11 @@ xcopy /q /y /i /s /e $(ProjectDir)Durango\CU $(LayoutDir)Image\Loose\CUtrue + + true true @@ -21561,11 +21536,24 @@ xcopy /q /y /i /s /e $(ProjectDir)Durango\CU $(LayoutDir)Image\Loose\CU - + + + + + + + + + + + + + + true true diff --git a/Minecraft.Client/Minecraft.Client.vcxproj.filters b/Minecraft.Client/Minecraft.Client.vcxproj.filters index 0751d835..fb8f26e4 100644 --- a/Minecraft.Client/Minecraft.Client.vcxproj.filters +++ b/Minecraft.Client/Minecraft.Client.vcxproj.filters @@ -881,6 +881,8 @@ PSVita\Iggy\gdraw + + @@ -2338,9 +2340,6 @@ Windows64 - - Windows64 - Durango\Source Files @@ -2971,6 +2970,9 @@ Common\Source Files\Network + + Common\Source Files\Network + PS3\PS3Extras @@ -3658,6 +3660,15 @@ Header Files + + Header Files + + + Header Files + + + Header Files + @@ -5085,15 +5096,15 @@ Common\Source Files\Network - - Common\Source Files\Network - Common\Source Files\Network Common\Source Files\Network + + Common\Source Files\Network + PS3\PS3Extras @@ -5688,6 +5699,45 @@ Source Files + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + diff --git a/Minecraft.Client/Minecraft.cpp b/Minecraft.Client/Minecraft.cpp index e14e14c3..84a933b3 100644 --- a/Minecraft.Client/Minecraft.cpp +++ b/Minecraft.Client/Minecraft.cpp @@ -36,6 +36,7 @@ #include "Camera.h" #ifdef _WINDOWS64 #include "KeyboardMouseInput.h" +#include "KeyMapping.h" #endif #include "..\Minecraft.World\MobEffect.h" @@ -1470,7 +1471,7 @@ void Minecraft::run_middle() { if(InputManager.ButtonDown(i, MINECRAFT_ACTION_SNEAK_TOGGLE)) localplayers[i]->ullButtonsPressed|=1LL<ullButtonsPressed|=1LL<ullButtonsPressed|=1LL<ullButtonsPressed|=1LL<ullButtonsPressed|=1LL< 0) - localplayers[i]->ullButtonsPressed|=1LL<ullButtonsPressed|=1LL< 0) wheel = -1; @@ -3325,7 +3320,7 @@ void Minecraft::tick(bool bFirst, bool bUpdateTextures) } #ifdef _WINDOWS64 - bool actionHeld = InputManager.ButtonDown(iPad, MINECRAFT_ACTION_ACTION) || (iPad == 0 && g_KBMInput.IsMouseButtonDown(KeyboardMouseInput::MOUSE_LEFT)); + bool actionHeld = InputManager.ButtonDown(iPad, MINECRAFT_ACTION_ACTION) || (iPad == 0 && g_KBMInput.IsKBMActive() && g_KBMInput.IsMouseButtonDown(KeyboardMouseInput::MOUSE_LEFT)); #else bool actionHeld = InputManager.ButtonDown(iPad, MINECRAFT_ACTION_ACTION); #endif @@ -3356,7 +3351,7 @@ void Minecraft::tick(bool bFirst, bool bUpdateTextures) } */ #ifdef _WINDOWS64 - bool useHeld = InputManager.ButtonDown(iPad, MINECRAFT_ACTION_USE) || (iPad == 0 && g_KBMInput.IsMouseButtonDown(KeyboardMouseInput::MOUSE_RIGHT)); + bool useHeld = InputManager.ButtonDown(iPad, MINECRAFT_ACTION_USE) || (iPad == 0 && g_KBMInput.IsKBMActive() && g_KBMInput.IsMouseButtonDown(KeyboardMouseInput::MOUSE_RIGHT)); #else bool useHeld = InputManager.ButtonDown(iPad, MINECRAFT_ACTION_USE); #endif @@ -3594,6 +3589,12 @@ void Minecraft::tick(bool bFirst, bool bUpdateTextures) // } // #endif + + if(Keyboard::isKeyDown(options->keyChat->key)) + { + setScreen(new ChatScreen()); + } + #if 0 // 4J - TODO - some replacement for input handling... if (screen == NULL || screen.passEvents) diff --git a/Minecraft.Client/Windows64/GameHDD/20260303223738/saveData.ms b/Minecraft.Client/Windows64/GameHDD/20260303223738/saveData.ms new file mode 100644 index 00000000..ed367122 Binary files /dev/null and b/Minecraft.Client/Windows64/GameHDD/20260303223738/saveData.ms differ diff --git a/Minecraft.Client/Windows64/GameHDD/20260303224052/saveData.ms b/Minecraft.Client/Windows64/GameHDD/20260303224052/saveData.ms new file mode 100644 index 00000000..aa00c691 Binary files /dev/null and b/Minecraft.Client/Windows64/GameHDD/20260303224052/saveData.ms differ diff --git a/Minecraft.Client/Windows64/GameHDD/20260303225915/saveData.ms b/Minecraft.Client/Windows64/GameHDD/20260303225915/saveData.ms new file mode 100644 index 00000000..03d975a6 Binary files /dev/null and b/Minecraft.Client/Windows64/GameHDD/20260303225915/saveData.ms differ diff --git a/Minecraft.Client/Windows64/GameHDD/20260303230038/saveData.ms b/Minecraft.Client/Windows64/GameHDD/20260303230038/saveData.ms new file mode 100644 index 00000000..39b2f400 Binary files /dev/null and b/Minecraft.Client/Windows64/GameHDD/20260303230038/saveData.ms differ diff --git a/Minecraft.Client/Windows64/GameHDD/20260303231510/saveData.ms b/Minecraft.Client/Windows64/GameHDD/20260303231510/saveData.ms new file mode 100644 index 00000000..d6d6ef92 Binary files /dev/null and b/Minecraft.Client/Windows64/GameHDD/20260303231510/saveData.ms differ diff --git a/Minecraft.Client/Windows64/GameHDD/20260304000018/saveData.ms b/Minecraft.Client/Windows64/GameHDD/20260304000018/saveData.ms new file mode 100644 index 00000000..9c291cbc Binary files /dev/null and b/Minecraft.Client/Windows64/GameHDD/20260304000018/saveData.ms differ diff --git a/Minecraft.Client/Windows64/GameHDD/20260304034103/saveData.ms b/Minecraft.Client/Windows64/GameHDD/20260304034103/saveData.ms new file mode 100644 index 00000000..f9b1e929 Binary files /dev/null and b/Minecraft.Client/Windows64/GameHDD/20260304034103/saveData.ms differ diff --git a/Minecraft.Client/Windows64/GameHDD/20260304091252/saveData.ms b/Minecraft.Client/Windows64/GameHDD/20260304091252/saveData.ms new file mode 100644 index 00000000..909daeb6 Binary files /dev/null and b/Minecraft.Client/Windows64/GameHDD/20260304091252/saveData.ms differ diff --git a/Minecraft.Client/Windows64/GameHDD/20260304093601/saveData.ms b/Minecraft.Client/Windows64/GameHDD/20260304093601/saveData.ms new file mode 100644 index 00000000..372c5432 Binary files /dev/null and b/Minecraft.Client/Windows64/GameHDD/20260304093601/saveData.ms differ diff --git a/Minecraft.Client/Windows64/GameHDD/20260304095754/saveData.ms b/Minecraft.Client/Windows64/GameHDD/20260304095754/saveData.ms new file mode 100644 index 00000000..c26fa9cb Binary files /dev/null and b/Minecraft.Client/Windows64/GameHDD/20260304095754/saveData.ms differ diff --git a/Minecraft.Client/Windows64/GameHDD/20260304100244/saveData.ms b/Minecraft.Client/Windows64/GameHDD/20260304100244/saveData.ms new file mode 100644 index 00000000..0057a319 Binary files /dev/null and b/Minecraft.Client/Windows64/GameHDD/20260304100244/saveData.ms differ diff --git a/Minecraft.Client/Windows64/GameHDD/20260304101040/saveData.ms b/Minecraft.Client/Windows64/GameHDD/20260304101040/saveData.ms new file mode 100644 index 00000000..329b46e8 Binary files /dev/null and b/Minecraft.Client/Windows64/GameHDD/20260304101040/saveData.ms differ diff --git a/Minecraft.Client/Windows64/GameHDD/20260304103510/saveData.ms b/Minecraft.Client/Windows64/GameHDD/20260304103510/saveData.ms new file mode 100644 index 00000000..f132bfc0 Binary files /dev/null and b/Minecraft.Client/Windows64/GameHDD/20260304103510/saveData.ms differ diff --git a/Minecraft.Client/Windows64/GameHDD/20260304103542/saveData.ms b/Minecraft.Client/Windows64/GameHDD/20260304103542/saveData.ms new file mode 100644 index 00000000..a013b9cf Binary files /dev/null and b/Minecraft.Client/Windows64/GameHDD/20260304103542/saveData.ms differ diff --git a/Minecraft.Client/Windows64/Network/GameNetworkManager.cpp b/Minecraft.Client/Windows64/Network/GameNetworkManager.cpp index ba11c003..19befc99 100644 --- a/Minecraft.Client/Windows64/Network/GameNetworkManager.cpp +++ b/Minecraft.Client/Windows64/Network/GameNetworkManager.cpp @@ -161,12 +161,17 @@ bool CGameNetworkManager::_RunNetworkGame(LPVOID lpParameter) success = s_pPlatformNetworkManager->_RunNetworkGame(); if(!success) - { + { app.SetAction(ProfileManager.GetPrimaryPad(),eAppAction_ExitWorld,(void *)TRUE); return true; } } - + else + { + // Client needs QNET_STATE_GAME_PLAY so that IsInGameplay() returns true + s_pPlatformNetworkManager->SetGamePlayState(); + } + if( g_NetworkManager.IsLeavingGame() ) return false; app.SetGameStarted(true); @@ -1391,7 +1396,10 @@ void CGameNetworkManager::CreateSocket( INetworkPlayer *pNetworkPlayer, bool loc Minecraft *pMinecraft = Minecraft::GetInstance(); Socket *socket = NULL; - shared_ptr mpPlayer = pMinecraft->localplayers[pNetworkPlayer->GetUserIndex()]; + shared_ptr mpPlayer = nullptr; + int userIdx = pNetworkPlayer->GetUserIndex(); + if (userIdx >= 0 && userIdx < XUSER_MAX_COUNT) + mpPlayer = pMinecraft->localplayers[userIdx]; if( localPlayer && mpPlayer != NULL && mpPlayer->connection != NULL) { // If we already have a MultiplayerLocalPlayer here then we are doing a session type change diff --git a/Minecraft.Client/Windows64/Network/PlatformNetworkManagerInterface.h b/Minecraft.Client/Windows64/Network/PlatformNetworkManagerInterface.h index 901e59e7..4c57a56c 100644 --- a/Minecraft.Client/Windows64/Network/PlatformNetworkManagerInterface.h +++ b/Minecraft.Client/Windows64/Network/PlatformNetworkManagerInterface.h @@ -71,6 +71,7 @@ public: virtual void HostGame(int localUsersMask, bool bOnlineGame, bool bIsPrivate, unsigned char publicSlots = MINECRAFT_NET_MAX_PLAYERS, unsigned char privateSlots = 0) = 0; virtual int JoinGame(FriendSessionInfo *searchResult, int dwLocalUsersMask, int dwPrimaryUserIndex ) = 0; virtual void CancelJoinGame() {}; + virtual void SetGamePlayState() {}; virtual bool SetLocalGame(bool isLocal) = 0; virtual bool IsLocalGame() = 0; virtual void SetPrivateGame(bool isPrivate) = 0; diff --git a/Minecraft.Client/Windows64/Network/PlatformNetworkManagerStub.cpp b/Minecraft.Client/Windows64/Network/PlatformNetworkManagerStub.cpp index b6b0fe12..abb019c3 100644 --- a/Minecraft.Client/Windows64/Network/PlatformNetworkManagerStub.cpp +++ b/Minecraft.Client/Windows64/Network/PlatformNetworkManagerStub.cpp @@ -313,6 +313,12 @@ void CPlatformNetworkManagerStub::HandleSignInChange() return; } +void CPlatformNetworkManagerStub::SetGamePlayState() +{ + extern QNET_STATE _iQNetStubState; + _iQNetStubState = QNET_STATE_GAME_PLAY; +} + bool CPlatformNetworkManagerStub::_RunNetworkGame() { return true; diff --git a/Minecraft.Client/Windows64/Network/PlatformNetworkManagerStub.h b/Minecraft.Client/Windows64/Network/PlatformNetworkManagerStub.h index f997dece..e4d25136 100644 --- a/Minecraft.Client/Windows64/Network/PlatformNetworkManagerStub.h +++ b/Minecraft.Client/Windows64/Network/PlatformNetworkManagerStub.h @@ -55,6 +55,7 @@ public: virtual void HandleSignInChange(); virtual bool _RunNetworkGame(); + virtual void SetGamePlayState(); private: bool isSystemPrimaryPlayer(IQNetPlayer *pQNetPlayer); diff --git a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp new file mode 100644 index 00000000..e9df4471 --- /dev/null +++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp @@ -0,0 +1,1025 @@ +// Code implemented by LCEMP, credit if used on other repos + +#include "stdafx.h" +#ifdef _WINDOWS64 +#include "stubs.h" +#include "WinsockNetLayer.h" +#include "..\..\Common\Network\PlatformNetworkManagerStub.h" +#include "..\..\..\Minecraft.World\Socket.h" +#include "Extrax64Stubs.h" +#include "miniupnpc/miniupnpc.h" +#include "miniupnpc/upnpcommands.h" + + +SOCKET WinsockNetLayer::s_listenSocket = INVALID_SOCKET; +SOCKET WinsockNetLayer::s_hostConnectionSocket = INVALID_SOCKET; +HANDLE WinsockNetLayer::s_acceptThread = NULL; +HANDLE WinsockNetLayer::s_clientRecvThread = NULL; + +bool WinsockNetLayer::s_isHost = false; +bool WinsockNetLayer::s_connected = false; +bool WinsockNetLayer::s_active = false; +bool WinsockNetLayer::s_initialized = false; + +BYTE WinsockNetLayer::s_localSmallId = 0; +BYTE WinsockNetLayer::s_hostSmallId = 0; +BYTE WinsockNetLayer::s_nextSmallId = 1; + +CRITICAL_SECTION WinsockNetLayer::s_sendLock; +CRITICAL_SECTION WinsockNetLayer::s_connectionsLock; + +std::vector WinsockNetLayer::s_connections; + +SOCKET WinsockNetLayer::s_advertiseSock = INVALID_SOCKET; +HANDLE WinsockNetLayer::s_advertiseThread = NULL; +volatile bool WinsockNetLayer::s_advertising = false; +Win64LANBroadcast WinsockNetLayer::s_advertiseData = {}; +CRITICAL_SECTION WinsockNetLayer::s_advertiseLock; +int WinsockNetLayer::s_hostGamePort = WIN64_NET_DEFAULT_PORT; + +SOCKET WinsockNetLayer::s_discoverySock = INVALID_SOCKET; +HANDLE WinsockNetLayer::s_discoveryThread = NULL; +volatile bool WinsockNetLayer::s_discovering = false; +CRITICAL_SECTION WinsockNetLayer::s_discoveryLock; +std::vector WinsockNetLayer::s_discoveredSessions; + +CRITICAL_SECTION WinsockNetLayer::s_disconnectLock; +std::vector WinsockNetLayer::s_disconnectedSmallIds; + +CRITICAL_SECTION WinsockNetLayer::s_freeSmallIdLock; +std::vector WinsockNetLayer::s_freeSmallIds; + +bool g_Win64MultiplayerHost = false; +bool g_Win64MultiplayerJoin = false; +int g_Win64MultiplayerPort = WIN64_NET_DEFAULT_PORT; +char g_Win64MultiplayerIP[256] = "127.0.0.1"; + +bool WinsockNetLayer::s_upnpMapped = false; +char WinsockNetLayer::s_externalIP[64] = ""; + +bool WinsockNetLayer::Initialize() +{ + if (s_initialized) return true; + + WSADATA wsaData; + int result = WSAStartup(MAKEWORD(2, 2), &wsaData); + if (result != 0) + { + app.DebugPrintf("WSAStartup failed: %d\n", result); + return false; + } + + InitializeCriticalSection(&s_sendLock); + InitializeCriticalSection(&s_connectionsLock); + InitializeCriticalSection(&s_advertiseLock); + InitializeCriticalSection(&s_discoveryLock); + InitializeCriticalSection(&s_disconnectLock); + InitializeCriticalSection(&s_freeSmallIdLock); + + s_initialized = true; + + StartDiscovery(); + + return true; +} + +void WinsockNetLayer::CleanupUPnP(int port) +{ + if (!s_upnpMapped) return; + + struct UPNPDev* devlist = NULL; + int error = 0; + devlist = upnpDiscover(2000, NULL, NULL, UPNP_LOCAL_PORT_ANY, 0, 2, &error); + if (devlist == NULL) return; + + struct UPNPUrls urls; + struct IGDdatas data; + char lanAddr[64] = ""; + char wanAddr[64] = ""; + int status = UPNP_GetValidIGD(devlist, &urls, &data, lanAddr, sizeof(lanAddr), wanAddr, sizeof(wanAddr)); + freeUPNPDevlist(devlist); + if (status != 1) return; + + char portStr[16]; + sprintf_s(portStr, "%d", port); + UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, portStr, "TCP", NULL); + FreeUPNPUrls(&urls); + + app.DebugPrintf("UPnP: Port %d unmapped\n", port); + s_upnpMapped = false; +} + +void WinsockNetLayer::Shutdown() +{ + CleanupUPnP(s_hostGamePort); + StopAdvertising(); + StopDiscovery(); + + s_active = false; + s_connected = false; + + if (s_listenSocket != INVALID_SOCKET) + { + closesocket(s_listenSocket); + s_listenSocket = INVALID_SOCKET; + } + + if (s_hostConnectionSocket != INVALID_SOCKET) + { + closesocket(s_hostConnectionSocket); + s_hostConnectionSocket = INVALID_SOCKET; + } + + EnterCriticalSection(&s_connectionsLock); + for (size_t i = 0; i < s_connections.size(); i++) + { + s_connections[i].active = false; + if (s_connections[i].tcpSocket != INVALID_SOCKET) + { + closesocket(s_connections[i].tcpSocket); + } + } + s_connections.clear(); + LeaveCriticalSection(&s_connectionsLock); + + if (s_acceptThread != NULL) + { + WaitForSingleObject(s_acceptThread, 2000); + CloseHandle(s_acceptThread); + s_acceptThread = NULL; + } + + if (s_clientRecvThread != NULL) + { + WaitForSingleObject(s_clientRecvThread, 2000); + CloseHandle(s_clientRecvThread); + s_clientRecvThread = NULL; + } + + if (s_initialized) + { + DeleteCriticalSection(&s_sendLock); + DeleteCriticalSection(&s_connectionsLock); + DeleteCriticalSection(&s_advertiseLock); + DeleteCriticalSection(&s_discoveryLock); + DeleteCriticalSection(&s_disconnectLock); + s_disconnectedSmallIds.clear(); + DeleteCriticalSection(&s_freeSmallIdLock); + s_freeSmallIds.clear(); + WSACleanup(); + s_initialized = false; + } +} + +static bool TryUPnP(int port, char* externalIPOut) +{ + struct UPNPDev* devlist = NULL; + char lanAddr[64] = ""; + int error = 0; + + devlist = upnpDiscover(2000, NULL, NULL, UPNP_LOCAL_PORT_ANY, 0, 2, &error); + if (devlist == NULL) + { + app.DebugPrintf("UPnP: No devices found (error %d)\n", error); + return false; + } + + struct UPNPUrls urls; + struct IGDdatas data; + char wanAddr[64] = ""; + int status = UPNP_GetValidIGD(devlist, &urls, &data, lanAddr, sizeof(lanAddr), wanAddr, sizeof(wanAddr)); + freeUPNPDevlist(devlist); + + if (status != UPNP_CONNECTED_IGD) + { + app.DebugPrintf("UPnP: No valid IGD found (status %d)\n", status); + return false; + } + + strncpy_s(externalIPOut, 64, wanAddr, _TRUNCATE); + app.DebugPrintf("UPnP: External IP: %s, LAN IP: %s\n", externalIPOut, lanAddr); + app.DebugPrintf("UPnP: External IP: %s, LAN IP: %s\n", externalIPOut, lanAddr); + + // Map the port + char portStr[16]; + sprintf_s(portStr, "%d", port); + + int r = UPNP_AddPortMapping( + urls.controlURL, data.first.servicetype, + portStr, portStr, lanAddr, + "Minecraft LAN", "TCP", NULL, "86400" + ); + + FreeUPNPUrls(&urls); + + if (r != UPNPCOMMAND_SUCCESS) + { + app.DebugPrintf("UPnP: AddPortMapping failed (error %d)\n", r); + return false; + } + + app.DebugPrintf("UPnP: Port %d mapped successfully\n", port); + return true; +} + + +bool WinsockNetLayer::HostGame(int port) +{ + if (!s_initialized && !Initialize()) return false; + + s_isHost = true; + s_localSmallId = 0; + s_hostSmallId = 0; + s_nextSmallId = 1; + s_hostGamePort = port; + + EnterCriticalSection(&s_freeSmallIdLock); + s_freeSmallIds.clear(); + LeaveCriticalSection(&s_freeSmallIdLock); + + struct addrinfo hints = {}; + struct addrinfo *result = NULL; + + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_flags = AI_PASSIVE; + + char portStr[16]; + sprintf_s(portStr, "%d", port); + + int iResult = getaddrinfo(NULL, portStr, &hints, &result); + if (iResult != 0) + { + app.DebugPrintf("getaddrinfo failed: %d\n", iResult); + return false; + } + + s_listenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol); + if (s_listenSocket == INVALID_SOCKET) + { + app.DebugPrintf("socket() failed: %d\n", WSAGetLastError()); + freeaddrinfo(result); + return false; + } + + int opt = 1; + setsockopt(s_listenSocket, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)); + + iResult = ::bind(s_listenSocket, result->ai_addr, (int)result->ai_addrlen); + freeaddrinfo(result); + if (iResult == SOCKET_ERROR) + { + app.DebugPrintf("bind() failed: %d\n", WSAGetLastError()); + closesocket(s_listenSocket); + s_listenSocket = INVALID_SOCKET; + return false; + } + + iResult = listen(s_listenSocket, SOMAXCONN); + if (iResult == SOCKET_ERROR) + { + app.DebugPrintf("listen() failed: %d\n", WSAGetLastError()); + closesocket(s_listenSocket); + s_listenSocket = INVALID_SOCKET; + return false; + } + + s_active = true; + s_connected = true; + + s_acceptThread = CreateThread(NULL, 0, AcceptThreadProc, NULL, 0, NULL); + + app.DebugPrintf("Win64 LAN: Hosting on port %d\n", port); + + char joinIp[64] = ""; + s_upnpMapped = TryUPnP(port, s_externalIP); + if (s_upnpMapped && s_externalIP[0] != '\0') + { + // UPnP worked, use external IP for Discord invite + strncpy_s(joinIp, sizeof(joinIp), s_externalIP, _TRUNCATE); + printf("Win64 LAN: Using external IP for invite: %s\n", joinIp); + } + else + { + // Fall back to LAN IP + printf("Win64 LAN: UPnP failed, falling back to LAN IP\n"); + char hostname[256]; + if (gethostname(hostname, sizeof(hostname)) == 0) + { + struct addrinfo hints2 = {}; + struct addrinfo* res = NULL; + hints2.ai_family = AF_INET; + hints2.ai_socktype = SOCK_STREAM; + if (getaddrinfo(hostname, NULL, &hints2, &res) == 0 && res != NULL) + { + inet_ntop(AF_INET, &((struct sockaddr_in*)res->ai_addr)->sin_addr, joinIp, sizeof(joinIp)); + freeaddrinfo(res); + } + } + } + Discord_SetJoinSecret(joinIp, port); + + return true; +} + +bool WinsockNetLayer::JoinGame(const char *ip, int port) +{ + if (!s_initialized && !Initialize()) return false; + + s_isHost = false; + s_hostSmallId = 0; + s_connected = false; + s_active = false; + + if (s_hostConnectionSocket != INVALID_SOCKET) + { + closesocket(s_hostConnectionSocket); + s_hostConnectionSocket = INVALID_SOCKET; + } + + struct addrinfo hints = {}; + struct addrinfo *result = NULL; + + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + char portStr[16]; + sprintf_s(portStr, "%d", port); + + int iResult = getaddrinfo(ip, portStr, &hints, &result); + if (iResult != 0) + { + app.DebugPrintf("getaddrinfo failed for %s:%d - %d\n", ip, port, iResult); + return false; + } + + bool connected = false; + BYTE assignedSmallId = 0; + const int maxAttempts = 12; + + for (int attempt = 0; attempt < maxAttempts; ++attempt) + { + s_hostConnectionSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol); + if (s_hostConnectionSocket == INVALID_SOCKET) + { + app.DebugPrintf("socket() failed: %d\n", WSAGetLastError()); + break; + } + + int noDelay = 1; + setsockopt(s_hostConnectionSocket, IPPROTO_TCP, TCP_NODELAY, (const char *)&noDelay, sizeof(noDelay)); + + iResult = connect(s_hostConnectionSocket, result->ai_addr, (int)result->ai_addrlen); + if (iResult == SOCKET_ERROR) + { + int err = WSAGetLastError(); + app.DebugPrintf("connect() to %s:%d failed (attempt %d/%d): %d\n", ip, port, attempt + 1, maxAttempts, err); + closesocket(s_hostConnectionSocket); + s_hostConnectionSocket = INVALID_SOCKET; + Sleep(200); + continue; + } + + BYTE assignBuf[1]; + int bytesRecv = recv(s_hostConnectionSocket, (char *)assignBuf, 1, 0); + if (bytesRecv != 1) + { + app.DebugPrintf("Failed to receive small ID assignment from host (attempt %d/%d)\n", attempt + 1, maxAttempts); + closesocket(s_hostConnectionSocket); + s_hostConnectionSocket = INVALID_SOCKET; + Sleep(200); + continue; + } + + assignedSmallId = assignBuf[0]; + connected = true; + break; + } + freeaddrinfo(result); + + if (!connected) + { + return false; + } + s_localSmallId = assignedSmallId; + + app.DebugPrintf("Win64 LAN: Connected to %s:%d, assigned smallId=%d\n", ip, port, s_localSmallId); + + s_active = true; + s_connected = true; + + s_clientRecvThread = CreateThread(NULL, 0, ClientRecvThreadProc, NULL, 0, NULL); + + return true; +} + +bool WinsockNetLayer::SendOnSocket(SOCKET sock, const void *data, int dataSize) +{ + if (sock == INVALID_SOCKET || dataSize <= 0) return false; + + EnterCriticalSection(&s_sendLock); + + BYTE header[4]; + header[0] = (BYTE)((dataSize >> 24) & 0xFF); + header[1] = (BYTE)((dataSize >> 16) & 0xFF); + header[2] = (BYTE)((dataSize >> 8) & 0xFF); + header[3] = (BYTE)(dataSize & 0xFF); + + int totalSent = 0; + int toSend = 4; + while (totalSent < toSend) + { + int sent = send(sock, (const char *)header + totalSent, toSend - totalSent, 0); + if (sent == SOCKET_ERROR || sent == 0) + { + LeaveCriticalSection(&s_sendLock); + return false; + } + totalSent += sent; + } + + totalSent = 0; + while (totalSent < dataSize) + { + int sent = send(sock, (const char *)data + totalSent, dataSize - totalSent, 0); + if (sent == SOCKET_ERROR || sent == 0) + { + LeaveCriticalSection(&s_sendLock); + return false; + } + totalSent += sent; + } + + LeaveCriticalSection(&s_sendLock); + return true; +} + +bool WinsockNetLayer::SendToSmallId(BYTE targetSmallId, const void *data, int dataSize) +{ + if (!s_active) return false; + + if (s_isHost) + { + SOCKET sock = GetSocketForSmallId(targetSmallId); + if (sock == INVALID_SOCKET) return false; + return SendOnSocket(sock, data, dataSize); + } + else + { + return SendOnSocket(s_hostConnectionSocket, data, dataSize); + } +} + +SOCKET WinsockNetLayer::GetSocketForSmallId(BYTE smallId) +{ + EnterCriticalSection(&s_connectionsLock); + for (size_t i = 0; i < s_connections.size(); i++) + { + if (s_connections[i].smallId == smallId && s_connections[i].active) + { + SOCKET sock = s_connections[i].tcpSocket; + LeaveCriticalSection(&s_connectionsLock); + return sock; + } + } + LeaveCriticalSection(&s_connectionsLock); + return INVALID_SOCKET; +} + +static bool RecvExact(SOCKET sock, BYTE *buf, int len) +{ + int totalRecv = 0; + while (totalRecv < len) + { + int r = recv(sock, (char *)buf + totalRecv, len - totalRecv, 0); + if (r <= 0) return false; + totalRecv += r; + } + return true; +} + +void WinsockNetLayer::HandleDataReceived(BYTE fromSmallId, BYTE toSmallId, unsigned char *data, unsigned int dataSize) +{ + INetworkPlayer *pPlayerFrom = g_NetworkManager.GetPlayerBySmallId(fromSmallId); + INetworkPlayer *pPlayerTo = g_NetworkManager.GetPlayerBySmallId(toSmallId); + + if (pPlayerFrom == NULL || pPlayerTo == NULL) return; + + if (s_isHost) + { + ::Socket *pSocket = pPlayerFrom->GetSocket(); + if (pSocket != NULL) + pSocket->pushDataToQueue(data, dataSize, false); + } + else + { + ::Socket *pSocket = pPlayerTo->GetSocket(); + if (pSocket != NULL) + pSocket->pushDataToQueue(data, dataSize, true); + } +} + +DWORD WINAPI WinsockNetLayer::AcceptThreadProc(LPVOID param) +{ + while (s_active) + { + SOCKET clientSocket = accept(s_listenSocket, NULL, NULL); + if (clientSocket == INVALID_SOCKET) + { + if (s_active) + app.DebugPrintf("accept() failed: %d\n", WSAGetLastError()); + break; + } + + int noDelay = 1; + setsockopt(clientSocket, IPPROTO_TCP, TCP_NODELAY, (const char *)&noDelay, sizeof(noDelay)); + + extern QNET_STATE _iQNetStubState; + if (_iQNetStubState != QNET_STATE_GAME_PLAY) + { + app.DebugPrintf("Win64 LAN: Rejecting connection, game not ready\n"); + closesocket(clientSocket); + continue; + } + + BYTE assignedSmallId; + EnterCriticalSection(&s_freeSmallIdLock); + if (!s_freeSmallIds.empty()) + { + assignedSmallId = s_freeSmallIds.back(); + s_freeSmallIds.pop_back(); + } + else if (s_nextSmallId < MINECRAFT_NET_MAX_PLAYERS) + { + assignedSmallId = s_nextSmallId++; + } + else + { + LeaveCriticalSection(&s_freeSmallIdLock); + app.DebugPrintf("Win64 LAN: Server full, rejecting connection\n"); + closesocket(clientSocket); + continue; + } + LeaveCriticalSection(&s_freeSmallIdLock); + + BYTE assignBuf[1] = { assignedSmallId }; + int sent = send(clientSocket, (const char *)assignBuf, 1, 0); + if (sent != 1) + { + app.DebugPrintf("Failed to send small ID to client\n"); + closesocket(clientSocket); + continue; + } + + Win64RemoteConnection conn; + conn.tcpSocket = clientSocket; + conn.smallId = assignedSmallId; + conn.active = true; + conn.recvThread = NULL; + + EnterCriticalSection(&s_connectionsLock); + s_connections.push_back(conn); + int connIdx = (int)s_connections.size() - 1; + LeaveCriticalSection(&s_connectionsLock); + + app.DebugPrintf("Win64 LAN: Client connected, assigned smallId=%d\n", assignedSmallId); + + IQNetPlayer *qnetPlayer = &IQNet::m_player[assignedSmallId]; + + extern void Win64_SetupRemoteQNetPlayer(IQNetPlayer *player, BYTE smallId, bool isHost, bool isLocal); + Win64_SetupRemoteQNetPlayer(qnetPlayer, assignedSmallId, false, false); + + extern CPlatformNetworkManagerStub *g_pPlatformNetworkManager; + g_pPlatformNetworkManager->NotifyPlayerJoined(qnetPlayer); + //ProfileManager.SetCurrentGameActivity(0, 4, false); // make sure it gets reset properly + + DWORD *threadParam = new DWORD; + *threadParam = connIdx; + HANDLE hThread = CreateThread(NULL, 0, RecvThreadProc, threadParam, 0, NULL); + + EnterCriticalSection(&s_connectionsLock); + if (connIdx < (int)s_connections.size()) + s_connections[connIdx].recvThread = hThread; + LeaveCriticalSection(&s_connectionsLock); + } + return 0; +} + +DWORD WINAPI WinsockNetLayer::RecvThreadProc(LPVOID param) +{ + DWORD connIdx = *(DWORD *)param; + delete (DWORD *)param; + + EnterCriticalSection(&s_connectionsLock); + if (connIdx >= (DWORD)s_connections.size()) + { + LeaveCriticalSection(&s_connectionsLock); + return 0; + } + SOCKET sock = s_connections[connIdx].tcpSocket; + BYTE clientSmallId = s_connections[connIdx].smallId; + LeaveCriticalSection(&s_connectionsLock); + + std::vector recvBuf; + recvBuf.resize(WIN64_NET_RECV_BUFFER_SIZE); + + while (s_active) + { + BYTE header[4]; + if (!RecvExact(sock, header, 4)) + { + app.DebugPrintf("Win64 LAN: Client smallId=%d disconnected (header)\n", clientSmallId); + break; + } + + int packetSize = + ((uint32_t)header[0] << 24) | + ((uint32_t)header[1] << 16) | + ((uint32_t)header[2] << 8) | + ((uint32_t)header[3]); + + if (packetSize <= 0 || packetSize > WIN64_NET_MAX_PACKET_SIZE) + { + app.DebugPrintf("Win64 LAN: Invalid packet size %d from client smallId=%d (max=%d)\n", + packetSize, + clientSmallId, + (int)WIN64_NET_MAX_PACKET_SIZE); + break; + } + + if ((int)recvBuf.size() < packetSize) + { + recvBuf.resize(packetSize); + app.DebugPrintf("Win64 LAN: Resized host recv buffer to %d bytes for client smallId=%d\n", packetSize, clientSmallId); + } + + if (!RecvExact(sock, &recvBuf[0], packetSize)) + { + app.DebugPrintf("Win64 LAN: Client smallId=%d disconnected (body)\n", clientSmallId); + break; + } + + HandleDataReceived(clientSmallId, s_hostSmallId, &recvBuf[0], packetSize); + } + + EnterCriticalSection(&s_connectionsLock); + for (size_t i = 0; i < s_connections.size(); i++) + { + if (s_connections[i].smallId == clientSmallId) + { + s_connections[i].active = false; + if (s_connections[i].tcpSocket != INVALID_SOCKET) + { + closesocket(s_connections[i].tcpSocket); + s_connections[i].tcpSocket = INVALID_SOCKET; + } + break; + } + } + LeaveCriticalSection(&s_connectionsLock); + + EnterCriticalSection(&s_disconnectLock); + s_disconnectedSmallIds.push_back(clientSmallId); + LeaveCriticalSection(&s_disconnectLock); + + return 0; +} + +bool WinsockNetLayer::PopDisconnectedSmallId(BYTE *outSmallId) +{ + bool found = false; + EnterCriticalSection(&s_disconnectLock); + if (!s_disconnectedSmallIds.empty()) + { + *outSmallId = s_disconnectedSmallIds.back(); + s_disconnectedSmallIds.pop_back(); + found = true; + } + LeaveCriticalSection(&s_disconnectLock); + return found; +} + +void WinsockNetLayer::PushFreeSmallId(BYTE smallId) +{ + EnterCriticalSection(&s_freeSmallIdLock); + s_freeSmallIds.push_back(smallId); + LeaveCriticalSection(&s_freeSmallIdLock); +} + +void WinsockNetLayer::CloseConnectionBySmallId(BYTE smallId) +{ + EnterCriticalSection(&s_connectionsLock); + for (size_t i = 0; i < s_connections.size(); i++) + { + if (s_connections[i].smallId == smallId && s_connections[i].active && s_connections[i].tcpSocket != INVALID_SOCKET) + { + closesocket(s_connections[i].tcpSocket); + s_connections[i].tcpSocket = INVALID_SOCKET; + app.DebugPrintf("Win64 LAN: Force-closed TCP connection for smallId=%d\n", smallId); + break; + } + } + LeaveCriticalSection(&s_connectionsLock); +} + +DWORD WINAPI WinsockNetLayer::ClientRecvThreadProc(LPVOID param) +{ + std::vector recvBuf; + recvBuf.resize(WIN64_NET_RECV_BUFFER_SIZE); + + while (s_active && s_hostConnectionSocket != INVALID_SOCKET) + { + BYTE header[4]; + if (!RecvExact(s_hostConnectionSocket, header, 4)) + { + app.DebugPrintf("Win64 LAN: Disconnected from host (header)\n"); + break; + } + + int packetSize = (header[0] << 24) | (header[1] << 16) | (header[2] << 8) | header[3]; + + if (packetSize <= 0 || packetSize > WIN64_NET_MAX_PACKET_SIZE) + { + app.DebugPrintf("Win64 LAN: Invalid packet size %d from host (max=%d)\n", + packetSize, + (int)WIN64_NET_MAX_PACKET_SIZE); + break; + } + + if ((int)recvBuf.size() < packetSize) + { + recvBuf.resize(packetSize); + app.DebugPrintf("Win64 LAN: Resized client recv buffer to %d bytes\n", packetSize); + } + + if (!RecvExact(s_hostConnectionSocket, &recvBuf[0], packetSize)) + { + app.DebugPrintf("Win64 LAN: Disconnected from host (body)\n"); + break; + } + + HandleDataReceived(s_hostSmallId, s_localSmallId, &recvBuf[0], packetSize); + } + + s_connected = false; + return 0; +} + +bool WinsockNetLayer::StartAdvertising(int gamePort, const wchar_t *hostName, unsigned int gameSettings, unsigned int texPackId, unsigned char subTexId, unsigned short netVer) +{ + if (s_advertising) return true; + if (!s_initialized) return false; + + EnterCriticalSection(&s_advertiseLock); + memset(&s_advertiseData, 0, sizeof(s_advertiseData)); + s_advertiseData.magic = WIN64_LAN_BROADCAST_MAGIC; + s_advertiseData.netVersion = netVer; + s_advertiseData.gamePort = (WORD)gamePort; + wcsncpy_s(s_advertiseData.hostName, 32, hostName, _TRUNCATE); + s_advertiseData.playerCount = 1; + s_advertiseData.maxPlayers = MINECRAFT_NET_MAX_PLAYERS; + s_advertiseData.gameHostSettings = gameSettings; + s_advertiseData.texturePackParentId = texPackId; + s_advertiseData.subTexturePackId = subTexId; + s_advertiseData.isJoinable = 0; + s_hostGamePort = gamePort; + LeaveCriticalSection(&s_advertiseLock); + + s_advertiseSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (s_advertiseSock == INVALID_SOCKET) + { + app.DebugPrintf("Win64 LAN: Failed to create advertise socket: %d\n", WSAGetLastError()); + return false; + } + + BOOL broadcast = TRUE; + setsockopt(s_advertiseSock, SOL_SOCKET, SO_BROADCAST, (const char *)&broadcast, sizeof(broadcast)); + + s_advertising = true; + s_advertiseThread = CreateThread(NULL, 0, AdvertiseThreadProc, NULL, 0, NULL); + + app.DebugPrintf("Win64 LAN: Started advertising on UDP port %d\n", WIN64_LAN_DISCOVERY_PORT); + return true; +} + +void WinsockNetLayer::StopAdvertising() +{ + s_advertising = false; + + if (s_advertiseSock != INVALID_SOCKET) + { + closesocket(s_advertiseSock); + s_advertiseSock = INVALID_SOCKET; + } + + if (s_advertiseThread != NULL) + { + WaitForSingleObject(s_advertiseThread, 2000); + CloseHandle(s_advertiseThread); + s_advertiseThread = NULL; + } +} + +void WinsockNetLayer::UpdateAdvertisePlayerCount(BYTE count) +{ + EnterCriticalSection(&s_advertiseLock); + s_advertiseData.playerCount = count; + LeaveCriticalSection(&s_advertiseLock); +} + +void WinsockNetLayer::UpdateAdvertiseJoinable(bool joinable) +{ + EnterCriticalSection(&s_advertiseLock); + s_advertiseData.isJoinable = joinable ? 1 : 0; + LeaveCriticalSection(&s_advertiseLock); +} + +DWORD WINAPI WinsockNetLayer::AdvertiseThreadProc(LPVOID param) +{ + struct sockaddr_in broadcastAddr; + memset(&broadcastAddr, 0, sizeof(broadcastAddr)); + broadcastAddr.sin_family = AF_INET; + broadcastAddr.sin_port = htons(WIN64_LAN_DISCOVERY_PORT); + broadcastAddr.sin_addr.s_addr = INADDR_BROADCAST; + + while (s_advertising) + { + EnterCriticalSection(&s_advertiseLock); + Win64LANBroadcast data = s_advertiseData; + LeaveCriticalSection(&s_advertiseLock); + + int sent = sendto(s_advertiseSock, (const char *)&data, sizeof(data), 0, + (struct sockaddr *)&broadcastAddr, sizeof(broadcastAddr)); + + if (sent == SOCKET_ERROR && s_advertising) + { + app.DebugPrintf("Win64 LAN: Broadcast sendto failed: %d\n", WSAGetLastError()); + } + + Sleep(1000); + } + + return 0; +} + +bool WinsockNetLayer::StartDiscovery() +{ + if (s_discovering) return true; + if (!s_initialized) return false; + + s_discoverySock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (s_discoverySock == INVALID_SOCKET) + { + app.DebugPrintf("Win64 LAN: Failed to create discovery socket: %d\n", WSAGetLastError()); + return false; + } + + BOOL reuseAddr = TRUE; + setsockopt(s_discoverySock, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuseAddr, sizeof(reuseAddr)); + + struct sockaddr_in bindAddr; + memset(&bindAddr, 0, sizeof(bindAddr)); + bindAddr.sin_family = AF_INET; + bindAddr.sin_port = htons(WIN64_LAN_DISCOVERY_PORT); + bindAddr.sin_addr.s_addr = INADDR_ANY; + + if (::bind(s_discoverySock, (struct sockaddr *)&bindAddr, sizeof(bindAddr)) == SOCKET_ERROR) + { + app.DebugPrintf("Win64 LAN: Discovery bind failed: %d\n", WSAGetLastError()); + closesocket(s_discoverySock); + s_discoverySock = INVALID_SOCKET; + return false; + } + + DWORD timeout = 500; + setsockopt(s_discoverySock, SOL_SOCKET, SO_RCVTIMEO, (const char *)&timeout, sizeof(timeout)); + + s_discovering = true; + s_discoveryThread = CreateThread(NULL, 0, DiscoveryThreadProc, NULL, 0, NULL); + + app.DebugPrintf("Win64 LAN: Listening for LAN games on UDP port %d\n", WIN64_LAN_DISCOVERY_PORT); + return true; +} + +void WinsockNetLayer::StopDiscovery() +{ + s_discovering = false; + + if (s_discoverySock != INVALID_SOCKET) + { + closesocket(s_discoverySock); + s_discoverySock = INVALID_SOCKET; + } + + if (s_discoveryThread != NULL) + { + WaitForSingleObject(s_discoveryThread, 2000); + CloseHandle(s_discoveryThread); + s_discoveryThread = NULL; + } + + EnterCriticalSection(&s_discoveryLock); + s_discoveredSessions.clear(); + LeaveCriticalSection(&s_discoveryLock); +} + +std::vector WinsockNetLayer::GetDiscoveredSessions() +{ + std::vector result; + EnterCriticalSection(&s_discoveryLock); + result = s_discoveredSessions; + LeaveCriticalSection(&s_discoveryLock); + return result; +} + +DWORD WINAPI WinsockNetLayer::DiscoveryThreadProc(LPVOID param) +{ + char recvBuf[512]; + + while (s_discovering) + { + struct sockaddr_in senderAddr; + int senderLen = sizeof(senderAddr); + + int recvLen = recvfrom(s_discoverySock, recvBuf, sizeof(recvBuf), 0, + (struct sockaddr *)&senderAddr, &senderLen); + + if (recvLen == SOCKET_ERROR) + { + continue; + } + + if (recvLen < (int)sizeof(Win64LANBroadcast)) + continue; + + Win64LANBroadcast *broadcast = (Win64LANBroadcast *)recvBuf; + if (broadcast->magic != WIN64_LAN_BROADCAST_MAGIC) + continue; + + char senderIP[64]; + inet_ntop(AF_INET, &senderAddr.sin_addr, senderIP, sizeof(senderIP)); + + DWORD now = GetTickCount(); + + EnterCriticalSection(&s_discoveryLock); + + bool found = false; + for (size_t i = 0; i < s_discoveredSessions.size(); i++) + { + if (strcmp(s_discoveredSessions[i].hostIP, senderIP) == 0 && + s_discoveredSessions[i].hostPort == (int)broadcast->gamePort) + { + s_discoveredSessions[i].netVersion = broadcast->netVersion; + wcsncpy_s(s_discoveredSessions[i].hostName, 32, broadcast->hostName, _TRUNCATE); + s_discoveredSessions[i].playerCount = broadcast->playerCount; + s_discoveredSessions[i].maxPlayers = broadcast->maxPlayers; + s_discoveredSessions[i].gameHostSettings = broadcast->gameHostSettings; + s_discoveredSessions[i].texturePackParentId = broadcast->texturePackParentId; + s_discoveredSessions[i].subTexturePackId = broadcast->subTexturePackId; + s_discoveredSessions[i].isJoinable = (broadcast->isJoinable != 0); + s_discoveredSessions[i].lastSeenTick = now; + found = true; + break; + } + } + + if (!found) + { + Win64LANSession session; + memset(&session, 0, sizeof(session)); + strncpy_s(session.hostIP, sizeof(session.hostIP), senderIP, _TRUNCATE); + session.hostPort = (int)broadcast->gamePort; + session.netVersion = broadcast->netVersion; + wcsncpy_s(session.hostName, 32, broadcast->hostName, _TRUNCATE); + session.playerCount = broadcast->playerCount; + session.maxPlayers = broadcast->maxPlayers; + session.gameHostSettings = broadcast->gameHostSettings; + session.texturePackParentId = broadcast->texturePackParentId; + session.subTexturePackId = broadcast->subTexturePackId; + session.isJoinable = (broadcast->isJoinable != 0); + session.lastSeenTick = now; + s_discoveredSessions.push_back(session); + + app.DebugPrintf("Win64 LAN: Discovered game \"%ls\" at %s:%d\n", + session.hostName, session.hostIP, session.hostPort); + } + + for (size_t i = s_discoveredSessions.size(); i > 0; i--) + { + if (now - s_discoveredSessions[i - 1].lastSeenTick > 5000) + { + app.DebugPrintf("Win64 LAN: Session \"%ls\" at %s timed out\n", + s_discoveredSessions[i - 1].hostName, s_discoveredSessions[i - 1].hostIP); + s_discoveredSessions.erase(s_discoveredSessions.begin() + (i - 1)); + } + } + + LeaveCriticalSection(&s_discoveryLock); + } + + return 0; +} + +#endif diff --git a/Minecraft.Client/Windows64/Network/WinsockNetLayer.h b/Minecraft.Client/Windows64/Network/WinsockNetLayer.h new file mode 100644 index 00000000..1b349237 --- /dev/null +++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.h @@ -0,0 +1,153 @@ +#pragma once + +#ifdef _WINDOWS64 + +#include +#include +#include +#include "..\..\Common\Network\NetworkPlayerInterface.h" + +#pragma comment(lib, "Ws2_32.lib") + +#define WIN64_NET_DEFAULT_PORT 25565 +#define WIN64_NET_MAX_CLIENTS 7 +#define WIN64_NET_RECV_BUFFER_SIZE 65536 +#define WIN64_NET_MAX_PACKET_SIZE (4 * 1024 * 1024) +#define WIN64_LAN_DISCOVERY_PORT 25566 +#define WIN64_LAN_BROADCAST_MAGIC 0x4D434C4E + +class Socket; + +#pragma pack(push, 1) +struct Win64LANBroadcast +{ + DWORD magic; + WORD netVersion; + WORD gamePort; + wchar_t hostName[32]; + BYTE playerCount; + BYTE maxPlayers; + DWORD gameHostSettings; + DWORD texturePackParentId; + BYTE subTexturePackId; + BYTE isJoinable; +}; +#pragma pack(pop) + +struct Win64LANSession +{ + char hostIP[64]; + int hostPort; + wchar_t hostName[32]; + unsigned short netVersion; + unsigned char playerCount; + unsigned char maxPlayers; + unsigned int gameHostSettings; + unsigned int texturePackParentId; + unsigned char subTexturePackId; + bool isJoinable; + DWORD lastSeenTick; +}; + +struct Win64RemoteConnection +{ + SOCKET tcpSocket; + BYTE smallId; + HANDLE recvThread; + volatile bool active; +}; + +class WinsockNetLayer +{ +public: + static bool s_upnpMapped; +static char s_externalIP[64]; +static void CleanupUPnP(int port); + + static bool Initialize(); + static void Shutdown(); + + static bool HostGame(int port); + static bool JoinGame(const char *ip, int port); + + static bool SendToSmallId(BYTE targetSmallId, const void *data, int dataSize); + static bool SendOnSocket(SOCKET sock, const void *data, int dataSize); + + static bool IsHosting() { return s_isHost; } + static bool IsConnected() { return s_connected; } + static bool IsActive() { return s_active; } + + static BYTE GetLocalSmallId() { return s_localSmallId; } + static BYTE GetHostSmallId() { return s_hostSmallId; } + + static SOCKET GetSocketForSmallId(BYTE smallId); + + static void HandleDataReceived(BYTE fromSmallId, BYTE toSmallId, unsigned char *data, unsigned int dataSize); + + static bool PopDisconnectedSmallId(BYTE *outSmallId); + static void PushFreeSmallId(BYTE smallId); + static void CloseConnectionBySmallId(BYTE smallId); + + static bool StartAdvertising(int gamePort, const wchar_t *hostName, unsigned int gameSettings, unsigned int texPackId, unsigned char subTexId, unsigned short netVer); + static void StopAdvertising(); + static void UpdateAdvertisePlayerCount(BYTE count); + static void UpdateAdvertiseJoinable(bool joinable); + + static bool StartDiscovery(); + static void StopDiscovery(); + static std::vector GetDiscoveredSessions(); + + static int GetHostPort() { return s_hostGamePort; } + +private: + static DWORD WINAPI AcceptThreadProc(LPVOID param); + static DWORD WINAPI RecvThreadProc(LPVOID param); + static DWORD WINAPI ClientRecvThreadProc(LPVOID param); + static DWORD WINAPI AdvertiseThreadProc(LPVOID param); + static DWORD WINAPI DiscoveryThreadProc(LPVOID param); + + static SOCKET s_listenSocket; + static SOCKET s_hostConnectionSocket; + static HANDLE s_acceptThread; + static HANDLE s_clientRecvThread; + + static bool s_isHost; + static bool s_connected; + static bool s_active; + static bool s_initialized; + + static BYTE s_localSmallId; + static BYTE s_hostSmallId; + static BYTE s_nextSmallId; + + static CRITICAL_SECTION s_sendLock; + static CRITICAL_SECTION s_connectionsLock; + + static std::vector s_connections; + + static SOCKET s_advertiseSock; + static HANDLE s_advertiseThread; + static volatile bool s_advertising; + static Win64LANBroadcast s_advertiseData; + static CRITICAL_SECTION s_advertiseLock; + static int s_hostGamePort; + + static SOCKET s_discoverySock; + static HANDLE s_discoveryThread; + static volatile bool s_discovering; + static CRITICAL_SECTION s_discoveryLock; + static std::vector s_discoveredSessions; + + static CRITICAL_SECTION s_disconnectLock; + static std::vector s_disconnectedSmallIds; + + static CRITICAL_SECTION s_freeSmallIdLock; + static std::vector s_freeSmallIds; +}; + +extern bool g_Win64MultiplayerHost; +extern bool g_Win64MultiplayerJoin; +extern int g_Win64MultiplayerPort; +extern char g_Win64MultiplayerIP[256]; + +#endif diff --git a/Minecraft.Client/Windows64/Windows64_App.cpp b/Minecraft.Client/Windows64/Windows64_App.cpp index ef9f6cf6..133049d5 100644 --- a/Minecraft.Client/Windows64/Windows64_App.cpp +++ b/Minecraft.Client/Windows64/Windows64_App.cpp @@ -26,6 +26,8 @@ void CConsoleMinecraftApp::StoreLaunchData() } void CConsoleMinecraftApp::ExitGame() { + extern HWND g_hWnd; + PostMessage(g_hWnd, WM_CLOSE, 0, 0); } void CConsoleMinecraftApp::FatalLoadError() { @@ -55,7 +57,8 @@ void CConsoleMinecraftApp::TemporaryCreateGameStart() Minecraft *pMinecraft=Minecraft::GetInstance(); app.ReleaseSaveThumbnail(); ProfileManager.SetLockedProfile(0); - pMinecraft->user->name = L"Windows"; + extern wchar_t g_Win64UsernameW[17]; + pMinecraft->user->name = g_Win64UsernameW; app.ApplyGameSettingsChanged(0); ////////////////////////////////////////////////////////////////////////////////////////////// From CScene_MultiGameJoinLoad::OnInit diff --git a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp index 2d201600..5647f071 100644 --- a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp +++ b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp @@ -37,9 +37,13 @@ #include "Resource.h" #include "..\..\Minecraft.World\compression.h" #include "..\..\Minecraft.World\OldChunkStorage.h" +#include "Network\WinsockNetLayer.h" +#include "Extrax64Stubs.h" #include "Xbox/resource.h" +#include "Windows64_Minecraft.h" + HINSTANCE hMyInst; LRESULT CALLBACK DlgProc(HWND hWndDlg, UINT Msg, WPARAM wParam, LPARAM lParam); char chGlobalText[256]; @@ -80,6 +84,9 @@ BOOL g_bWidescreen = TRUE; int g_iScreenWidth = 1920; int g_iScreenHeight = 1080; +char g_Win64Username[17] = {0}; +wchar_t g_Win64UsernameW[17] = {0}; + void DefineActions(void) { // The app needs to define the actions required, and the possible mappings for these @@ -289,6 +296,11 @@ void MemSect(int sect) HINSTANCE g_hInst = NULL; HWND g_hWnd = NULL; +HWND GetMinecraftWindowHWND() +{ + return g_hWnd; +} + static bool g_isFullscreen = false; static RECT g_windowedRect = {}; static LONG g_windowedStyle = 0; @@ -335,6 +347,52 @@ D3D_FEATURE_LEVEL g_featureLevel = D3D_FEATURE_LEVEL_11_0; ID3D11Device* g_pd3dDevice = NULL; ID3D11DeviceContext* g_pImmediateContext = NULL; IDXGISwapChain* g_pSwapChain = NULL; + +static WORD g_originalGammaRamp[3][256]; +static bool g_gammaRampSaved = false; + +void Windows64_UpdateGamma(unsigned short usGamma) +{ + if (!g_hWnd) return; + + HDC hdc = GetDC(g_hWnd); + if (!hdc) return; + + if (!g_gammaRampSaved) + { + GetDeviceGammaRamp(hdc, g_originalGammaRamp); + g_gammaRampSaved = true; + } + + float gamma = (float)usGamma / 32768.0f; + if (gamma < 0.01f) gamma = 0.01f; + if (gamma > 1.0f) gamma = 1.0f; + + float invGamma = 1.0f / (0.5f + gamma * 0.5f); + + WORD ramp[3][256]; + for (int i = 0; i < 256; i++) + { + float normalized = (float)i / 255.0f; + float corrected = powf(normalized, invGamma); + WORD val = (WORD)(corrected * 65535.0f + 0.5f); + ramp[0][i] = val; + ramp[1][i] = val; + ramp[2][i] = val; + } + + SetDeviceGammaRamp(hdc, ramp); + ReleaseDC(g_hWnd, hdc); +} + +void Windows64_RestoreGamma() +{ + if (!g_gammaRampSaved || !g_hWnd) return; + HDC hdc = GetDC(g_hWnd); + if (!hdc) return; + SetDeviceGammaRamp(hdc, g_originalGammaRamp); + ReleaseDC(g_hWnd, hdc); +} ID3D11RenderTargetView* g_pRenderTargetView = NULL; ID3D11DepthStencilView* g_pDepthStencilView = NULL; ID3D11Texture2D* g_pDepthStencilBuffer = NULL; @@ -822,6 +880,9 @@ void Render() //-------------------------------------------------------------------------------------- void CleanupDevice() { + extern void Windows64_RestoreGamma(); + Windows64_RestoreGamma(); + if( g_pImmediateContext ) g_pImmediateContext->ClearState(); if( g_pRenderTargetView ) g_pRenderTargetView->Release(); @@ -870,8 +931,32 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, //g_iScreenWidth = 960; //g_iScreenHeight = 544; } + + char cmdLineA[1024]; + strncpy_s(cmdLineA, sizeof(cmdLineA), lpCmdLine, _TRUNCATE); + + char *nameArg = strstr(cmdLineA, "-name "); + if (nameArg) + { + nameArg += 6; + while (*nameArg == ' ') nameArg++; + char nameBuf[17]; + int n = 0; + while (nameArg[n] && nameArg[n] != ' ' && n < 16) { nameBuf[n] = nameArg[n]; n++; } + nameBuf[n] = 0; + strncpy_s(g_Win64Username, 17, nameBuf, _TRUNCATE); + } } + if (g_Win64Username[0] == 0) + { + DWORD sz = 17; + if (!GetUserNameA(g_Win64Username, &sz)) + strncpy_s(g_Win64Username, 17, "Player", _TRUNCATE); + g_Win64Username[16] = 0; + } + + MultiByteToWideChar(CP_ACP, 0, g_Win64Username, -1, g_Win64UsernameW, 17); // Initialize global strings MyRegisterClass(hInstance); @@ -1021,6 +1106,22 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, ProfileManager.SetNotificationsCallback(&CConsoleMinecraftApp::NotificationsCallback,(LPVOID)&app); #endif + + // Ensure the GameHDD save directory exists at runtime (the 4J_Storage lib expects it) + { + wchar_t exePath[MAX_PATH]; + if (GetModuleFileNameW(NULL, exePath, MAX_PATH)) + { + wstring exeDir(exePath); + size_t lastSlash = exeDir.find_last_of(L"\\/"); + if (lastSlash != wstring::npos) + exeDir = exeDir.substr(0, lastSlash); + wstring gameHDDPath = exeDir + L"\\Windows64\\GameHDD"; + CreateDirectoryW((exeDir + L"\\Windows64").c_str(), NULL); + CreateDirectoryW(gameHDDPath.c_str(), NULL); + } + } + // Set a callback for the default player options to be set - when there is no profile data for the player ProfileManager.SetDefaultOptionsCallback(&CConsoleMinecraftApp::DefaultOptionsCallback,(LPVOID)&app); #if 0 @@ -1036,7 +1137,17 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, // ProfileManager for XN_LIVE_INVITE_ACCEPTED for QNet. g_NetworkManager.Initialise(); + for (int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; i++) + { + IQNet::m_player[i].m_smallId = (BYTE)i; + IQNet::m_player[i].m_isRemote = false; + IQNet::m_player[i].m_isHostPlayer = (i == 0); + swprintf_s(IQNet::m_player[i].m_gamertag, 32, L"Player%d", i); + } + extern wchar_t g_Win64UsernameW[17]; + wcscpy_s(IQNet::m_player[0].m_gamertag, 32, g_Win64UsernameW); + WinsockNetLayer::Initialize(); // 4J-PB moved further down //app.InitGameSettings(); @@ -1135,6 +1246,7 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, // Intro loop ? while(app.IntroRunning()) { + ProfileManager.Tick(); // Tick XUI app.RunFrame(); @@ -1156,14 +1268,30 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, MSG msg = {0}; while( WM_QUIT != msg.message ) { - g_KBMInput.Tick(); - - if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) + while( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) { + if( msg.message == WM_QUIT ) break; TranslateMessage( &msg ); DispatchMessage( &msg ); - continue; } + if( msg.message == WM_QUIT ) break; + + g_KBMInput.Tick(); + + + +#ifdef _DEBUG + for( int vk = 0; vk < 256; vk++ ) + { + if( g_KBMInput.IsKeyPressed(vk) ) + { + char dbgBuf[64]; + sprintf_s(dbgBuf, "INPUT: Key pressed vk=0x%02X\n", vk); + OutputDebugStringA(dbgBuf); + } + } +#endif + RenderManager.StartFrame(); #if 0 if(pMinecraft->soundEngine->isStreamingWavebankReady() && @@ -1186,6 +1314,26 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, PIXBeginNamedEvent(0,"Input manager tick"); InputManager.Tick(); PIXEndNamedEvent(); + + if (InputManager.IsPadConnected(0)) + { + bool controllerActive = InputManager.ButtonPressed(0) || + InputManager.GetJoypadStick_LX(0,false) != 0.0f || InputManager.GetJoypadStick_LY(0,false) != 0.0f || + InputManager.GetJoypadStick_RX(0,false) != 0.0f || InputManager.GetJoypadStick_RY(0,false) != 0.0f || + InputManager.GetJoypadLTrigger(0,false) != 0 || InputManager.GetJoypadRTrigger(0,false) != 0; + + if (controllerActive && g_KBMInput.IsKBMActive()) + { + g_KBMInput.SetKBMActive(false); + g_KBMInput.SetMouseGrabbed(false); + g_KBMInput.SetCursorHiddenForUI(true); + } + else if (!g_KBMInput.IsKBMActive() && g_KBMInput.HasAnyInput()) + { + g_KBMInput.SetCursorHiddenForUI(false); + g_KBMInput.SetKBMActive(true); + } + } PIXBeginNamedEvent(0,"Profile manager tick"); // ProfileManager.Tick(); PIXEndNamedEvent(); @@ -1209,9 +1357,11 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, PIXEndNamedEvent(); PIXBeginNamedEvent(0,"Network manager do work #1"); - // g_NetworkManager.DoWork(); + g_NetworkManager.DoWork(); PIXEndNamedEvent(); + TickDiscord(); + // LeaderboardManager::Instance()->Tick(); // Render game graphics. if(app.GetGameStarted()) diff --git a/Minecraft.Client/Windows64_DLCOffers.cpp b/Minecraft.Client/Windows64_DLCOffers.cpp index 386fbefa..93477e8b 100644 --- a/Minecraft.Client/Windows64_DLCOffers.cpp +++ b/Minecraft.Client/Windows64_DLCOffers.cpp @@ -4,456 +4,482 @@ #include #pragma comment(lib, "winhttp.lib") #include +#include "../Windows64_Minecraft.h" +#include "../User.h" static bool ParseUrl(const wchar_t* url, - std::wstring& host, INTERNET_PORT& port, - std::wstring& path) + std::wstring& host, INTERNET_PORT& port, + std::wstring& path) { - URL_COMPONENTS uc = {}; - uc.dwStructSize = sizeof(uc); - uc.dwHostNameLength = (DWORD)-1; - uc.dwUrlPathLength = (DWORD)-1; - uc.dwExtraInfoLength = (DWORD)-1; - if (!WinHttpCrackUrl(url, 0, 0, &uc)) return false; - host.assign(uc.lpszHostName, uc.dwHostNameLength); - path.assign(uc.lpszUrlPath, uc.dwUrlPathLength); - if (uc.lpszExtraInfo && uc.dwExtraInfoLength) - path.append(uc.lpszExtraInfo, uc.dwExtraInfoLength); - port = uc.nPort; - return true; + URL_COMPONENTS uc = {}; + uc.dwStructSize = sizeof(uc); + uc.dwHostNameLength = (DWORD)-1; + uc.dwUrlPathLength = (DWORD)-1; + uc.dwExtraInfoLength = (DWORD)-1; + if (!WinHttpCrackUrl(url, 0, 0, &uc)) return false; + host.assign(uc.lpszHostName, uc.dwHostNameLength); + path.assign(uc.lpszUrlPath, uc.dwUrlPathLength); + if (uc.lpszExtraInfo && uc.dwExtraInfoLength) + path.append(uc.lpszExtraInfo, uc.dwExtraInfoLength); + port = uc.nPort; + return true; } bool Windows64_DLCOffers::FetchBytesFromUrl(const wchar_t* url, - PBYTE* ppData, DWORD* pdwSize) + PBYTE* ppData, DWORD* pdwSize) { - *ppData = nullptr; - *pdwSize = 0; + *ppData = nullptr; + *pdwSize = 0; - printf("[DLC] FetchBytesFromUrl: '%ls'\n", url); + printf("[DLC] FetchBytesFromUrl: '%ls'\n", url); - std::wstring host, path; - INTERNET_PORT port = 80; - if (!ParseUrl(url, host, port, path)) - { - printf("[DLC] FetchBytesFromUrl: ParseUrl FAILED (err=%u)\n", GetLastError()); - return false; - } + std::wstring host, path; + INTERNET_PORT port = 80; + if (!ParseUrl(url, host, port, path)) + { + printf("[DLC] FetchBytesFromUrl: ParseUrl FAILED (err=%u)\n", GetLastError()); + return false; + } - printf("[DLC] FetchBytesFromUrl: host='%ls' port=%u path='%ls'\n", - host.c_str(), (unsigned)port, path.c_str()); + printf("[DLC] FetchBytesFromUrl: host='%ls' port=%u path='%ls'\n", + host.c_str(), (unsigned)port, path.c_str()); - HINTERNET hSession = WinHttpOpen(L"W64DLC/1.0", - WINHTTP_ACCESS_TYPE_NO_PROXY, - WINHTTP_NO_PROXY_NAME, - WINHTTP_NO_PROXY_BYPASS, 0); - if (!hSession) - { - printf("[DLC] FetchBytesFromUrl: WinHttpOpen FAILED (err=%u)\n", GetLastError()); - return false; - } + HINTERNET hSession = WinHttpOpen(L"W64DLC/1.0", + WINHTTP_ACCESS_TYPE_NO_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, 0); + if (!hSession) + { + printf("[DLC] FetchBytesFromUrl: WinHttpOpen FAILED (err=%u)\n", GetLastError()); + return false; + } - HINTERNET hConn = WinHttpConnect(hSession, host.c_str(), port, 0); - if (!hConn) - { - printf("[DLC] FetchBytesFromUrl: WinHttpConnect FAILED (err=%u)\n", GetLastError()); - WinHttpCloseHandle(hSession); - return false; - } + HINTERNET hConn = WinHttpConnect(hSession, host.c_str(), port, 0); + if (!hConn) + { + printf("[DLC] FetchBytesFromUrl: WinHttpConnect FAILED (err=%u)\n", GetLastError()); + WinHttpCloseHandle(hSession); + return false; + } - HINTERNET hReq = WinHttpOpenRequest(hConn, L"GET", path.c_str(), - nullptr, WINHTTP_NO_REFERER, - WINHTTP_DEFAULT_ACCEPT_TYPES, 0); - if (!hReq) - { - printf("[DLC] FetchBytesFromUrl: WinHttpOpenRequest FAILED (err=%u)\n", GetLastError()); - WinHttpCloseHandle(hConn); - WinHttpCloseHandle(hSession); - return false; - } + HINTERNET hReq = WinHttpOpenRequest(hConn, L"GET", path.c_str(), + nullptr, WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, 0); + if (!hReq) + { + printf("[DLC] FetchBytesFromUrl: WinHttpOpenRequest FAILED (err=%u)\n", GetLastError()); + WinHttpCloseHandle(hConn); + WinHttpCloseHandle(hSession); + return false; + } - if (!WinHttpSendRequest(hReq, WINHTTP_NO_ADDITIONAL_HEADERS, 0, - WINHTTP_NO_REQUEST_DATA, 0, 0, 0)) - { - printf("[DLC] FetchBytesFromUrl: WinHttpSendRequest FAILED (err=%u)\n", GetLastError()); - WinHttpCloseHandle(hReq); - WinHttpCloseHandle(hConn); - WinHttpCloseHandle(hSession); - return false; - } + if (!WinHttpSendRequest(hReq, WINHTTP_NO_ADDITIONAL_HEADERS, 0, + WINHTTP_NO_REQUEST_DATA, 0, 0, 0)) + { + printf("[DLC] FetchBytesFromUrl: WinHttpSendRequest FAILED (err=%u)\n", GetLastError()); + WinHttpCloseHandle(hReq); + WinHttpCloseHandle(hConn); + WinHttpCloseHandle(hSession); + return false; + } - if (!WinHttpReceiveResponse(hReq, nullptr)) - { - printf("[DLC] FetchBytesFromUrl: WinHttpReceiveResponse FAILED (err=%u)\n", GetLastError()); - WinHttpCloseHandle(hReq); - WinHttpCloseHandle(hConn); - WinHttpCloseHandle(hSession); - return false; - } + if (!WinHttpReceiveResponse(hReq, nullptr)) + { + printf("[DLC] FetchBytesFromUrl: WinHttpReceiveResponse FAILED (err=%u)\n", GetLastError()); + WinHttpCloseHandle(hReq); + WinHttpCloseHandle(hConn); + WinHttpCloseHandle(hSession); + return false; + } - DWORD dwStatusCode = 0; - DWORD dwStatusSize = sizeof(dwStatusCode); - WinHttpQueryHeaders(hReq, - WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, - WINHTTP_HEADER_NAME_BY_INDEX, - &dwStatusCode, &dwStatusSize, - WINHTTP_NO_HEADER_INDEX); - printf("[DLC] FetchBytesFromUrl: HTTP status = %u\n", dwStatusCode); + DWORD dwStatusCode = 0; + DWORD dwStatusSize = sizeof(dwStatusCode); + WinHttpQueryHeaders(hReq, + WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + WINHTTP_HEADER_NAME_BY_INDEX, + &dwStatusCode, &dwStatusSize, + WINHTTP_NO_HEADER_INDEX); + printf("[DLC] FetchBytesFromUrl: HTTP status = %u\n", dwStatusCode); - if (dwStatusCode != 200) - { - printf("[DLC] FetchBytesFromUrl: non-200 response, aborting\n"); - WinHttpCloseHandle(hReq); - WinHttpCloseHandle(hConn); - WinHttpCloseHandle(hSession); - return false; - } + if (dwStatusCode != 200) + { + printf("[DLC] FetchBytesFromUrl: non-200 response, aborting\n"); + WinHttpCloseHandle(hReq); + WinHttpCloseHandle(hConn); + WinHttpCloseHandle(hSession); + return false; + } - std::vector buf; - DWORD dwRead = 0; - BYTE chunk[8192]; - while (WinHttpReadData(hReq, chunk, sizeof(chunk), &dwRead) && dwRead) - buf.insert(buf.end(), chunk, chunk + dwRead); + std::vector buf; + DWORD dwRead = 0; + BYTE chunk[8192]; + while (WinHttpReadData(hReq, chunk, sizeof(chunk), &dwRead) && dwRead) + buf.insert(buf.end(), chunk, chunk + dwRead); - printf("[DLC] FetchBytesFromUrl: read %zu bytes\n", buf.size()); + printf("[DLC] FetchBytesFromUrl: read %zu bytes\n", buf.size()); - bool bOk = false; - if (!buf.empty()) - { - *ppData = new BYTE[buf.size()]; - *pdwSize = (DWORD)buf.size(); - memcpy(*ppData, buf.data(), buf.size()); - bOk = true; - } - else - { - printf("[DLC] FetchBytesFromUrl: response body was empty\n"); - } + bool bOk = false; + if (!buf.empty()) + { + *ppData = new BYTE[buf.size()]; + *pdwSize = (DWORD)buf.size(); + memcpy(*ppData, buf.data(), buf.size()); + bOk = true; + } + else + { + printf("[DLC] FetchBytesFromUrl: response body was empty\n"); + } - WinHttpCloseHandle(hReq); - WinHttpCloseHandle(hConn); - WinHttpCloseHandle(hSession); - return bOk; + WinHttpCloseHandle(hReq); + WinHttpCloseHandle(hConn); + WinHttpCloseHandle(hSession); + return bOk; } static bool WinHttpGetUrl(const wchar_t* fullUrl, PBYTE* ppData, DWORD* pdwSize) { - *ppData = nullptr; - *pdwSize = 0; + *ppData = nullptr; + *pdwSize = 0; - URL_COMPONENTS uc; - ZeroMemory(&uc, sizeof(uc)); - uc.dwStructSize = sizeof(uc); + URL_COMPONENTS uc; + ZeroMemory(&uc, sizeof(uc)); + uc.dwStructSize = sizeof(uc); - wchar_t szHostName[256] = {}; - wchar_t szUrlPath[1024] = {}; - uc.lpszHostName = szHostName; - uc.dwHostNameLength = _countof(szHostName); - uc.lpszUrlPath = szUrlPath; - uc.dwUrlPathLength = _countof(szUrlPath); + wchar_t szHostName[256] = {}; + wchar_t szUrlPath[1024] = {}; + uc.lpszHostName = szHostName; + uc.dwHostNameLength = _countof(szHostName); + uc.lpszUrlPath = szUrlPath; + uc.dwUrlPathLength = _countof(szUrlPath); - if (!WinHttpCrackUrl(fullUrl, 0, 0, &uc)) - { - printf("[DLC] WinHttpGetUrl: WinHttpCrackUrl failed for '%ls': %u\n", fullUrl, GetLastError()); - return false; - } + if (!WinHttpCrackUrl(fullUrl, 0, 0, &uc)) + { + printf("[DLC] WinHttpGetUrl: WinHttpCrackUrl failed for '%ls': %u\n", fullUrl, GetLastError()); + return false; + } - HINTERNET hSession = WinHttpOpen(L"MC/1.0", WINHTTP_ACCESS_TYPE_NO_PROXY, NULL, NULL, 0); - if (!hSession) return false; + HINTERNET hSession = WinHttpOpen(L"MC/1.0", WINHTTP_ACCESS_TYPE_NO_PROXY, NULL, NULL, 0); + if (!hSession) return false; - HINTERNET hConnect = WinHttpConnect(hSession, szHostName, uc.nPort, 0); - if (!hConnect) { WinHttpCloseHandle(hSession); return false; } + HINTERNET hConnect = WinHttpConnect(hSession, szHostName, uc.nPort, 0); + if (!hConnect) { WinHttpCloseHandle(hSession); return false; } - HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", szUrlPath, - NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0); - if (!hRequest) - { - WinHttpCloseHandle(hConnect); - WinHttpCloseHandle(hSession); - return false; - } + HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", szUrlPath, + NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0); + if (!hRequest) + { + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return false; + } - if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, - WINHTTP_NO_REQUEST_DATA, 0, 0, 0) || - !WinHttpReceiveResponse(hRequest, NULL)) - { - WinHttpCloseHandle(hRequest); - WinHttpCloseHandle(hConnect); - WinHttpCloseHandle(hSession); - return false; - } + if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, + WINHTTP_NO_REQUEST_DATA, 0, 0, 0) || + !WinHttpReceiveResponse(hRequest, NULL)) + { + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return false; + } - std::string body; - DWORD dwSize = 0; - do { - DWORD dwDownloaded = 0; - if (!WinHttpQueryDataAvailable(hRequest, &dwSize) || dwSize == 0) break; - char* buf = new char[dwSize]; - if (WinHttpReadData(hRequest, buf, dwSize, &dwDownloaded)) - body.append(buf, dwDownloaded); - delete[] buf; - } while (dwSize > 0); + std::string body; + DWORD dwSize = 0; + do { + DWORD dwDownloaded = 0; + if (!WinHttpQueryDataAvailable(hRequest, &dwSize) || dwSize == 0) break; + char* buf = new char[dwSize]; + if (WinHttpReadData(hRequest, buf, dwSize, &dwDownloaded)) + body.append(buf, dwDownloaded); + delete[] buf; + } while (dwSize > 0); - WinHttpCloseHandle(hRequest); - WinHttpCloseHandle(hConnect); - WinHttpCloseHandle(hSession); + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); - if (body.empty()) return false; + if (body.empty()) return false; - *pdwSize = (DWORD)body.size(); - *ppData = new BYTE[*pdwSize]; - memcpy(*ppData, body.data(), *pdwSize); - return true; + *pdwSize = (DWORD)body.size(); + *ppData = new BYTE[*pdwSize]; + memcpy(*ppData, body.data(), *pdwSize); + return true; } bool Windows64_DLCOffers::FetchImageFromUrl(const wchar_t* url, PBYTE* ppData, DWORD* pdwSize) { - return WinHttpGetUrl(url, ppData, pdwSize); + return WinHttpGetUrl(url, ppData, pdwSize); } void Windows64_DLCOffers::InstallOffer(int iIndex, - W64_INSTALL_CALLBACK pfnCallback, - void* pUserData) + W64_INSTALL_CALLBACK pfnCallback, + void* pUserData, wstring username) { - if (iIndex < 0 || iIndex >= (int)m_offers.size()) return; + if (iIndex < 0 || iIndex >= (int)m_offers.size()) return; - InstallCtx* ctx = new InstallCtx(); - ctx->offer = m_offers[iIndex]; - ctx->offerIndex = iIndex; - ctx->pfnCallback = pfnCallback; - ctx->pUserData = pUserData; + InstallCtx* ctx = new InstallCtx(); + ctx->offer = m_offers[iIndex]; + ctx->offerIndex = iIndex; + ctx->pfnCallback = pfnCallback; + ctx->pUserData = pUserData; + ctx->username = username; - HANDLE hThread = CreateThread(nullptr, 0, InstallThreadProc, ctx, 0, nullptr); - if (hThread) CloseHandle(hThread); - else delete ctx; + HANDLE hThread = CreateThread(nullptr, 0, InstallThreadProc, ctx, 0, nullptr); + if (hThread) CloseHandle(hThread); + else delete ctx; } DWORD WINAPI Windows64_DLCOffers::InstallThreadProc(LPVOID lpParam) { - InstallCtx* ctx = reinterpret_cast(lpParam); - bool bSuccess = false; + InstallCtx* ctx = reinterpret_cast(lpParam); + bool bSuccess = false; - // \Windows64Media\DLC\\folder.pck - wchar_t wszExeDir[MAX_PATH] = {}; - GetModuleFileNameW(nullptr, wszExeDir, MAX_PATH); - wchar_t* pLastSlash = wcsrchr(wszExeDir, L'\\'); - if (pLastSlash) *(pLastSlash + 1) = L'\0'; + // \Windows64Media\DLC\\folder.pck + wchar_t wszExeDir[MAX_PATH] = {}; + GetModuleFileNameW(nullptr, wszExeDir, MAX_PATH); + wchar_t* pLastSlash = wcsrchr(wszExeDir, L'\\'); + if (pLastSlash) *(pLastSlash + 1) = L'\0'; - wchar_t wszInstallDir[MAX_PATH]; - _snwprintf_s(wszInstallDir, _countof(wszInstallDir), _TRUNCATE, - L"%lsWindows64Media\\DLC\\%ls", - wszExeDir, ctx->offer.wszProductID); + wchar_t wszInstallDir[MAX_PATH]; + _snwprintf_s(wszInstallDir, _countof(wszInstallDir), _TRUNCATE, + L"%lsWindows64Media\\DLC\\%ls", + wszExeDir, ctx->offer.wszProductID); - CreateDirectoryW(wszInstallDir, nullptr); + CreateDirectoryW(wszInstallDir, nullptr); - wchar_t wszFilePath[MAX_PATH]; - _snwprintf_s(wszFilePath, _countof(wszFilePath), _TRUNCATE, - L"%ls\\folder.pck", wszInstallDir); + wchar_t wszFilePath[MAX_PATH]; + _snwprintf_s(wszFilePath, _countof(wszFilePath), _TRUNCATE, + L"%ls\\folder.pck", wszInstallDir); - wchar_t wszDownloadUrl[512]; - _snwprintf_s(wszDownloadUrl, _countof(wszDownloadUrl), _TRUNCATE, - L"http://127.0.0.1:3000/download/%ls", ctx->offer.wszProductID); + wchar_t wszDownloadUrl[512]; + _snwprintf_s(wszDownloadUrl, _countof(wszDownloadUrl), _TRUNCATE, + L"http://127.0.0.1:3000/download/%ls", ctx->offer.wszProductID); - PBYTE pData = nullptr; - DWORD dwBytes = 0; + PBYTE pData = nullptr; + DWORD dwBytes = 0; - if (FetchBytesFromUrl(wszDownloadUrl, &pData, &dwBytes) && pData) - { - HANDLE hFile = CreateFileW(wszFilePath, GENERIC_WRITE, 0, nullptr, - CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); - if (hFile != INVALID_HANDLE_VALUE) - { - DWORD dwWritten = 0; - WriteFile(hFile, pData, dwBytes, &dwWritten, nullptr); - CloseHandle(hFile); - bSuccess = (dwWritten == dwBytes); - } - delete[] pData; - } + if (FetchBytesFromUrl(wszDownloadUrl, &pData, &dwBytes) && pData) + { + HANDLE hFile = CreateFileW(wszFilePath, GENERIC_WRITE, 0, nullptr, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hFile != INVALID_HANDLE_VALUE) + { + DWORD dwWritten = 0; + WriteFile(hFile, pData, dwBytes, &dwWritten, nullptr); + CloseHandle(hFile); + bSuccess = (dwWritten == dwBytes); + } + delete[] pData; + } - if (bSuccess) - { - // Mark the offer as owned. - Windows64_DLCOffers::Get().SetPurchased(ctx->offerIndex); + if (bSuccess) + { + Windows64_DLCOffers::Get().SetPurchased(ctx->offerIndex); + Windows64_DLCOffers::Get().SetPendingLoadPath(ctx->offerIndex, wszFilePath); + InterlockedExchange(&Windows64_DLCOffers::Get().m_iLastInstalled, (LONG)ctx->offerIndex); - // Must be set BEFORE m_iLastInstalled is updated so the game thread always - Windows64_DLCOffers::Get().SetPendingLoadPath(ctx->offerIndex, wszFilePath); - InterlockedExchange(&Windows64_DLCOffers::Get().m_iLastInstalled, - (LONG)ctx->offerIndex); + // notify server so purchase persists across sessions + wchar_t wszPath[256]; + _snwprintf_s(wszPath, _countof(wszPath), _TRUNCATE, + L"/purchase/%ls?user=%ls", + ctx->offer.wszProductID, ctx->username.c_str()); - printf("[DLC] Installed '%ls' -> %ls\n", - ctx->offer.wszProductID, wszFilePath); - } - else - { - printf("[DLC] Install FAILED for '%ls'\n", ctx->offer.wszProductID); - } + HINTERNET hSession = WinHttpOpen(L"W64DLC/1.0", WINHTTP_ACCESS_TYPE_NO_PROXY, nullptr, nullptr, 0); + if (hSession) + { + HINTERNET hConn = WinHttpConnect(hSession, L"127.0.0.1", 3000, 0); + if (hConn) + { + HINTERNET hReq = WinHttpOpenRequest(hConn, L"POST", wszPath, nullptr, + WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, 0); + if (hReq) + { + WinHttpSendRequest(hReq, WINHTTP_NO_ADDITIONAL_HEADERS, 0, + WINHTTP_NO_REQUEST_DATA, 0, 0, 0); + WinHttpReceiveResponse(hReq, nullptr); + WinHttpCloseHandle(hReq); + } + WinHttpCloseHandle(hConn); + } + WinHttpCloseHandle(hSession); + } + } + else + { + printf("[DLC] Install FAILED for '%ls'\n", ctx->offer.wszProductID); + } - if (ctx->pfnCallback) - ctx->pfnCallback(ctx->offer.wszProductID, bSuccess, ctx->pUserData); + if (ctx->pfnCallback) + ctx->pfnCallback(ctx->offer.wszProductID, bSuccess, ctx->pUserData); - delete ctx; - return 0; + delete ctx; + return 0; } void Windows64_DLCOffers::FetchFromServer() { - printf("[DLC] FetchFromServer called\n"); - m_offers.clear(); - m_bReady = false; + printf("[DLC] FetchFromServer called\n"); + m_offers.clear(); + m_bReady = false; - HINTERNET hSession = WinHttpOpen(L"MC/1.0", WINHTTP_ACCESS_TYPE_NO_PROXY, NULL, NULL, 0); - if (!hSession) return; + HINTERNET hSession = WinHttpOpen(L"MC/1.0", WINHTTP_ACCESS_TYPE_NO_PROXY, NULL, NULL, 0); + if (!hSession) return; - HINTERNET hConnect = WinHttpConnect(hSession, L"localhost", 3000, 0); - if (!hConnect) { WinHttpCloseHandle(hSession); return; } + HINTERNET hConnect = WinHttpConnect(hSession, L"localhost", 3000, 0); + if (!hConnect) { WinHttpCloseHandle(hSession); return; } - HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", L"/dlc", - NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0); - if (!hRequest) - { - WinHttpCloseHandle(hConnect); - WinHttpCloseHandle(hSession); - return; - } + wchar_t wszDlcPath[128]; + _snwprintf_s(wszDlcPath, _countof(wszDlcPath), _TRUNCATE, L"/dlc?user=%ls", Minecraft::GetInstance()->user->name.c_str()); - if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, - WINHTTP_NO_REQUEST_DATA, 0, 0, 0) || - !WinHttpReceiveResponse(hRequest, NULL)) - { - WinHttpCloseHandle(hRequest); - WinHttpCloseHandle(hConnect); - WinHttpCloseHandle(hSession); - return; - } +HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", wszDlcPath, + NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0); + if (!hRequest) + { + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return; + } - std::string body; - DWORD dwSize = 0; - do { - DWORD dwDownloaded = 0; - if (!WinHttpQueryDataAvailable(hRequest, &dwSize) || dwSize == 0) break; - char* buf = new char[dwSize + 1]; - ZeroMemory(buf, dwSize + 1); - if (WinHttpReadData(hRequest, buf, dwSize, &dwDownloaded)) - body.append(buf, dwDownloaded); - delete[] buf; - } while (dwSize > 0); + if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, + WINHTTP_NO_REQUEST_DATA, 0, 0, 0) || + !WinHttpReceiveResponse(hRequest, NULL)) + { + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return; + } - WinHttpCloseHandle(hRequest); - WinHttpCloseHandle(hConnect); - WinHttpCloseHandle(hSession); + std::string body; + DWORD dwSize = 0; + do { + DWORD dwDownloaded = 0; + if (!WinHttpQueryDataAvailable(hRequest, &dwSize) || dwSize == 0) break; + char* buf = new char[dwSize + 1]; + ZeroMemory(buf, dwSize + 1); + if (WinHttpReadData(hRequest, buf, dwSize, &dwDownloaded)) + body.append(buf, dwDownloaded); + delete[] buf; + } while (dwSize > 0); - if (body.empty()) { printf("[DLC] Empty /dlc response\n"); return; } + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); - printf("[DLC] /dlc response (%d bytes): %s\n", (int)body.size(), body.c_str()); + if (body.empty()) { printf("[DLC] Empty /dlc response\n"); return; } - auto toWide = [](const std::string& s, wchar_t* dst, int maxChars) { - MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, dst, maxChars); - }; + printf("[DLC] /dlc response (%d bytes): %s\n", (int)body.size(), body.c_str()); - size_t pos = 0; - while ((pos = body.find('{', pos)) != std::string::npos) - { - W64_OFFER_INFO offer; - ZeroMemory(&offer, sizeof(offer)); + auto toWide = [](const std::string& s, wchar_t* dst, int maxChars) { + MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, dst, maxChars); + }; - auto readField = [&](const char* key) -> std::string { - std::string search = std::string("\"") + key + "\""; - size_t k = body.find(search, pos); - if (k == std::string::npos) return ""; - size_t colon = body.find(':', k); - if (colon == std::string::npos) return ""; - size_t q1 = body.find('"', colon); - if (q1 == std::string::npos) return ""; - size_t q2 = body.find('"', q1 + 1); - if (q2 == std::string::npos) return ""; - return body.substr(q1 + 1, q2 - q1 - 1); - }; + size_t pos = 0; + while ((pos = body.find('{', pos)) != std::string::npos) + { + W64_OFFER_INFO offer; + ZeroMemory(&offer, sizeof(offer)); - auto readInt = [&](const char* key) -> int { - std::string search = std::string("\"") + key + "\""; - size_t k = body.find(search, pos); - if (k == std::string::npos) return 0; - size_t colon = body.find(':', k); - if (colon == std::string::npos) return 0; - size_t n = colon + 1; - while (n < body.size() && (body[n] == ' ' || body[n] == '\t')) n++; - return atoi(&body[n]); - }; + auto readField = [&](const char* key) -> std::string { + std::string search = std::string("\"") + key + "\""; + size_t k = body.find(search, pos); + if (k == std::string::npos) return ""; + size_t colon = body.find(':', k); + if (colon == std::string::npos) return ""; + size_t q1 = body.find('"', colon); + if (q1 == std::string::npos) return ""; + size_t q2 = body.find('"', q1 + 1); + if (q2 == std::string::npos) return ""; + return body.substr(q1 + 1, q2 - q1 - 1); + }; - toWide(readField("id"), offer.wszProductID, 64); - toWide(readField("name"), offer.wszOfferName, 128); - toWide(readField("description"), offer.wszSellText, 512); - toWide(readField("price"), offer.wszCurrencyPrice, 32); - toWide(readField("type"), offer.wszType, 32); - toWide(readField("bannerUrl"), offer.wszBannerUrl, 256); - offer.fUserHasPurchased = readInt("purchased"); - offer.pbBannerData = nullptr; - offer.dwBannerBytes = 0; + auto readInt = [&](const char* key) -> int { + std::string search = std::string("\"") + key + "\""; + size_t k = body.find(search, pos); + if (k == std::string::npos) return 0; + size_t colon = body.find(':', k); + if (colon == std::string::npos) return 0; + size_t n = colon + 1; + while (n < body.size() && (body[n] == ' ' || body[n] == '\t')) n++; + return atoi(&body[n]); + }; - if (offer.wszProductID[0] != L'\0') - { - printf("[DLC] Parsed: id='%ls' type='%ls' bannerUrl='%ls'\n", - offer.wszProductID, offer.wszType, offer.wszBannerUrl); - m_offers.push_back(offer); - } + toWide(readField("id"), offer.wszProductID, 64); + toWide(readField("name"), offer.wszOfferName, 128); + toWide(readField("description"), offer.wszSellText, 512); + toWide(readField("price"), offer.wszCurrencyPrice, 32); + toWide(readField("type"), offer.wszType, 32); + toWide(readField("bannerUrl"), offer.wszBannerUrl, 256); + offer.fUserHasPurchased = readInt("purchased"); + offer.pbBannerData = nullptr; + offer.dwBannerBytes = 0; - pos = body.find('}', pos); - if (pos == std::string::npos) break; - pos++; - } + if (offer.wszProductID[0] != L'\0') + { + printf("[DLC] Parsed: id='%ls' type='%ls' bannerUrl='%ls'\n", + offer.wszProductID, offer.wszType, offer.wszBannerUrl); + m_offers.push_back(offer); + } - printf("[DLC] FetchFromServer complete - %d offers\n", (int)m_offers.size()); - m_bReady = true; + pos = body.find('}', pos); + if (pos == std::string::npos) break; + pos++; + } + + printf("[DLC] FetchFromServer complete - %d offers\n", (int)m_offers.size()); + m_bReady = true; } void Windows64_DLCOffers::FetchBanners() { - for (int i = 0; i < (int)m_offers.size(); i++) - { - W64_OFFER_INFO& offer = m_offers[i]; - if (offer.wszBannerUrl[0] == L'\0') continue; - if (offer.pbBannerData != nullptr) continue; + for (int i = 0; i < (int)m_offers.size(); i++) + { + W64_OFFER_INFO& offer = m_offers[i]; + if (offer.wszBannerUrl[0] == L'\0') continue; + if (offer.pbBannerData != nullptr) continue; - PBYTE pbData = nullptr; - DWORD dwBytes = 0; + PBYTE pbData = nullptr; + DWORD dwBytes = 0; - if (FetchImageFromUrl(offer.wszBannerUrl, &pbData, &dwBytes)) - { - offer.pbBannerData = pbData; - offer.dwBannerBytes = dwBytes; - printf("[DLC] Banner fetched for '%ls' - %u bytes\n", - offer.wszProductID, dwBytes); - } - else - { - printf("[DLC] Banner fetch FAILED for '%ls' url='%ls'\n", - offer.wszProductID, offer.wszBannerUrl); - } - } + if (FetchImageFromUrl(offer.wszBannerUrl, &pbData, &dwBytes)) + { + offer.pbBannerData = pbData; + offer.dwBannerBytes = dwBytes; + printf("[DLC] Banner fetched for '%ls' - %u bytes\n", + offer.wszProductID, dwBytes); + } + else + { + printf("[DLC] Banner fetch FAILED for '%ls' url='%ls'\n", + offer.wszProductID, offer.wszBannerUrl); + } + } - // Register all offers with the app now that banners are populated. - for (int i = 0; i < (int)m_offers.size(); i++) - { - const W64_OFFER_INFO& offer = m_offers[i]; + // Register all offers with the app now that banners are populated. + for (int i = 0; i < (int)m_offers.size(); i++) + { + const W64_OFFER_INFO& offer = m_offers[i]; - eDLCContentType eType = e_DLC_SkinPack; - if (wcscmp(offer.wszType, L"MashUp") == 0) eType = e_DLC_MashupPacks; - else if (wcscmp(offer.wszType, L"TexturePack") == 0) eType = e_DLC_TexturePacks; - else if (wcscmp(offer.wszType, L"SkinPack") == 0) eType = e_DLC_SkinPack; + eDLCContentType eType = e_DLC_SkinPack; + if (wcscmp(offer.wszType, L"MashUp") == 0) eType = e_DLC_MashupPacks; + else if (wcscmp(offer.wszType, L"TexturePack") == 0) eType = e_DLC_TexturePacks; + else if (wcscmp(offer.wszType, L"SkinPack") == 0) eType = e_DLC_SkinPack; - printf("[DLC] Registering W64 DLC: '%ls' type=%d\n", - offer.wszProductID, (int)eType); + printf("[DLC] Registering W64 DLC: '%ls' type=%d\n", + offer.wszProductID, (int)eType); - app.RegisterW64DLC( - eType, - offer.wszProductID, // key AND banner texture name - offer.wszProductID, // banner texture name (used as lookup key in UpdateDisplay) - 0, // iConfig - (unsigned int)i, // uiSortIndex - offer.pbBannerData, - offer.dwBannerBytes - ); - } + app.RegisterW64DLC( + eType, + offer.wszProductID, // key AND banner texture name + offer.wszProductID, // banner texture name (used as lookup key in UpdateDisplay) + 0, // iConfig + (unsigned int)i, // uiSortIndex + offer.pbBannerData, + offer.dwBannerBytes + ); + } } #endif \ No newline at end of file diff --git a/Minecraft.Client/Windows64_DLCOffers.h b/Minecraft.Client/Windows64_DLCOffers.h index aa45bc06..0f0464cb 100644 --- a/Minecraft.Client/Windows64_DLCOffers.h +++ b/Minecraft.Client/Windows64_DLCOffers.h @@ -67,7 +67,7 @@ public: void InstallOffer(int iIndex, W64_INSTALL_CALLBACK pfnCallback = nullptr, - void* pUserData = nullptr); + void* pUserData = nullptr, wstring username = L""); static bool FetchImageFromUrl(const wchar_t* url, PBYTE* ppData, DWORD* pdwSize); static bool FetchBytesFromUrl(const wchar_t* url, PBYTE* ppData, DWORD* pdwSize); @@ -95,6 +95,7 @@ private: int offerIndex; W64_INSTALL_CALLBACK pfnCallback; void* pUserData; + wstring username; }; static DWORD WINAPI InstallThreadProc(LPVOID lpParam); diff --git a/Minecraft.Client/Windows64_Minecraft.h b/Minecraft.Client/Windows64_Minecraft.h new file mode 100644 index 00000000..7ceb9434 --- /dev/null +++ b/Minecraft.Client/Windows64_Minecraft.h @@ -0,0 +1,3 @@ +#include + +HWND GetMinecraftWindowHWND(); \ No newline at end of file diff --git a/Minecraft.Client/miniupnpc/CMakeLists.txt b/Minecraft.Client/miniupnpc/CMakeLists.txt new file mode 100644 index 00000000..ac9c0ba6 --- /dev/null +++ b/Minecraft.Client/miniupnpc/CMakeLists.txt @@ -0,0 +1,318 @@ +cmake_minimum_required(VERSION 3.14 FATAL_ERROR) + +project (miniupnpc + VERSION 2.3.3 + DESCRIPTION "UPnP IGD client lightweight library" + HOMEPAGE_URL https://miniupnp.tuxfamily.org/ + LANGUAGES C) + +set (MINIUPNPC_API_VERSION 21) + +option (UPNPC_BUILD_STATIC "Build static library" TRUE) +option (UPNPC_BUILD_SHARED "Build shared library" TRUE) +option (UPNPC_BUILD_TESTS "Build test executables" TRUE) +option (UPNPC_BUILD_SAMPLE "Build sample executables" TRUE) +option (NO_GETADDRINFO "Define NO_GETADDRINFO" FALSE) +option (UPNPC_NO_INSTALL "Disable installation" FALSE) + +if (NOT UPNPC_BUILD_STATIC AND NOT UPNPC_BUILD_SHARED) + message (FATAL "Both shared and static libraries are disabled!") +endif () + +include(GNUInstallDirs) + +# Interface library for compile definitions, flags and option +add_library(miniupnpc-private INTERFACE) + +if (NO_GETADDRINFO) + target_compile_definitions(miniupnpc-private INTERFACE NO_GETADDRINFO) +endif () + +if (NOT WIN32) + target_compile_definitions(miniupnpc-private INTERFACE + MINIUPNPC_SET_SOCKET_TIMEOUT + MINIUPNPC_GET_SRC_ADDR + _BSD_SOURCE _DEFAULT_SOURCE) + if (NOT APPLE AND NOT CMAKE_SYSTEM_NAME MATCHES ".*BSD" AND NOT CMAKE_SYSTEM_NAME STREQUAL "SunOS") + # add_definitions (-D_POSIX_C_SOURCE=200112L) + target_compile_definitions(miniupnpc-private INTERFACE _XOPEN_SOURCE=600) + endif () + if (CMAKE_SYSTEM_NAME STREQUAL "NetBSD") + target_compile_definitions(miniupnpc-private INTERFACE _NETBSD_SOURCE) + endif () +else () + set (MINIUPNPC_TARGET_WINDOWS_VERSION "0x0501" CACHE STRING "Minimum target Windows version as hex string") # XP or higher for getnameinfo and friends + if (MINIUPNPC_TARGET_WINDOWS_VERSION) + target_compile_definitions(miniupnpc-private INTERFACE _WIN32_WINNT=${MINIUPNPC_TARGET_WINDOWS_VERSION}) + endif () +endif () + +if (APPLE) + target_compile_definitions(miniupnpc-private INTERFACE _DARWIN_C_SOURCE) +endif () + +# Set compiler specific build flags +if (CMAKE_COMPILER_IS_GNUCC AND NOT CMAKE_SYSTEM_NAME STREQUAL "AmigaOS") + set(CMAKE_POSITION_INDEPENDENT_CODE ON) + target_compile_options(miniupnpc-private INTERFACE -Wall) +endif () + +# Suppress noise warnings +if (MSVC) + target_compile_definitions(miniupnpc-private INTERFACE _CRT_SECURE_NO_WARNINGS _WINSOCK_DEPRECATED_NO_WARNINGS) +endif() + +configure_file (${CMAKE_CURRENT_SOURCE_DIR}/miniupnpcstrings.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/miniupnpcstrings.h) +target_include_directories(miniupnpc-private INTERFACE $) + +set (MINIUPNPC_SOURCES + src/igd_desc_parse.c + src/miniupnpc.c + src/minixml.c + src/minisoap.c + src/minissdpc.c + src/miniwget.c + src/upnpcommands.c + src/upnpdev.c + src/upnpreplyparse.c + src/upnperrors.c + src/connecthostport.c + src/portlistingparse.c + src/receivedata.c + src/addr_is_reserved.c + ${CMAKE_CURRENT_BINARY_DIR}/miniupnpcstrings.h +) + +if (WIN32) + target_link_libraries(miniupnpc-private INTERFACE ws2_32 iphlpapi) +elseif (CMAKE_SYSTEM_NAME STREQUAL "SunOS") + target_link_libraries(miniupnpc-private INTERFACE socket nsl resolv) + find_library (SOCKET_LIBRARY NAMES socket) + find_library (NSL_LIBRARY NAMES nsl) + find_library (RESOLV_LIBRARY NAMES resolv) + set (LDLIBS ${SOCKET_LIBRARY} ${NSL_LIBRARY} ${RESOLV_LIBRARY} ${LDLIBS}) +elseif (HAIKU) + target_link_libraries(miniupnpc-private INTERFACE network) + find_library (SOCKET_LIBRARY NAMES network) + find_library (NSL_LIBRARY NAMES network) + find_library (RESOLV_LIBRARY NAMES network) + set (LDLIBS ${SOCKET_LIBRARY} ${NSL_LIBRARY} ${RESOLV_LIBRARY} ${LDLIBS}) +endif () + + +if (UPNPC_BUILD_STATIC) + add_library (libminiupnpc-static STATIC ${MINIUPNPC_SOURCES}) + target_include_directories (libminiupnpc-static PUBLIC + $ + $) + if (NOT UPNPC_BUILD_SHARED) + add_library (miniupnpc::miniupnpc ALIAS libminiupnpc-static) + endif() + set_target_properties (libminiupnpc-static PROPERTIES EXPORT_NAME miniupnpc) + if (WIN32 AND NOT MINGW) + set_target_properties (libminiupnpc-static PROPERTIES OUTPUT_NAME "libminiupnpc") + else() + set_target_properties (libminiupnpc-static PROPERTIES OUTPUT_NAME "miniupnpc") + endif() + target_link_libraries (libminiupnpc-static PRIVATE miniupnpc-private) + target_include_directories(libminiupnpc-static INTERFACE $) + target_compile_definitions(libminiupnpc-static PUBLIC MINIUPNP_STATICLIB) + + if (NOT UPNPC_NO_INSTALL) + install (TARGETS miniupnpc-private EXPORT miniupnpc-private) + + install (EXPORT miniupnpc-private + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/miniupnpc" + NAMESPACE miniupnpc::) + + install (TARGETS libminiupnpc-static + EXPORT libminiupnpc-static + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + + install (EXPORT libminiupnpc-static + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/miniupnpc" + NAMESPACE miniupnpc::) + endif() + + if (UPNPC_BUILD_SAMPLE) + add_executable (upnpc-static src/upnpc.c) + target_link_libraries (upnpc-static PRIVATE libminiupnpc-static) + target_include_directories(upnpc-static PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + + add_executable (upnp-listdevices-static src/listdevices.c) + target_link_libraries (upnp-listdevices-static PRIVATE libminiupnpc-static) + target_include_directories(upnp-listdevices-static PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + + if (NOT UPNPC_NO_INSTALL) + install (TARGETS upnpc-static upnp-listdevices-static + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + endif() + endif () +endif () + +if (UPNPC_BUILD_SHARED) + add_library (libminiupnpc-shared SHARED ${MINIUPNPC_SOURCES}) + target_include_directories (libminiupnpc-shared PUBLIC + $ + $) + add_library (miniupnpc::miniupnpc ALIAS libminiupnpc-shared) + set_target_properties (libminiupnpc-shared PROPERTIES EXPORT_NAME miniupnpc) + set_target_properties (libminiupnpc-shared PROPERTIES OUTPUT_NAME "miniupnpc") + set_target_properties (libminiupnpc-shared PROPERTIES VERSION ${PROJECT_VERSION}) + set_target_properties (libminiupnpc-shared PROPERTIES SOVERSION ${MINIUPNPC_API_VERSION}) + target_link_libraries (libminiupnpc-shared PRIVATE miniupnpc-private) + target_compile_definitions(libminiupnpc-shared PRIVATE MINIUPNP_EXPORTS) + + target_include_directories(libminiupnpc-shared INTERFACE $) + if (WIN32) + target_link_libraries(libminiupnpc-shared INTERFACE ws2_32 iphlpapi) + endif() + + if (NOT UPNPC_NO_INSTALL) + install (TARGETS libminiupnpc-shared + EXPORT libminiupnpc-shared + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + + install (EXPORT libminiupnpc-shared + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/miniupnpc" + NAMESPACE miniupnpc::) + endif() + + if (UPNPC_BUILD_SAMPLE) + add_executable (upnpc-shared src/upnpc.c) + target_link_libraries (upnpc-shared PRIVATE libminiupnpc-shared) + target_include_directories(upnpc-shared PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + + add_executable (upnp-listdevices-shared src/listdevices.c) + target_link_libraries (upnp-listdevices-shared PRIVATE libminiupnpc-shared) + target_include_directories(upnp-listdevices-shared PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + + if (NOT UPNPC_NO_INSTALL) + install (TARGETS upnpc-shared upnp-listdevices-shared + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + endif() + endif () +endif () + +if (UPNPC_BUILD_TESTS) + add_library(miniupnpc-tests INTERFACE) + target_link_libraries(miniupnpc-tests INTERFACE miniupnpc-private) + target_compile_definitions(miniupnpc-tests INTERFACE MINIUPNP_STATICLIB) + + add_executable (testminixml src/testminixml.c src/minixml.c src/igd_desc_parse.c) + target_include_directories (testminixml PRIVATE + $) + target_link_libraries (testminixml PRIVATE miniupnpc-tests) + + add_executable (minixmlvalid src/minixmlvalid.c src/minixml.c) + target_link_libraries (minixmlvalid PRIVATE miniupnpc-tests) + + add_executable (testupnpreplyparse src/testupnpreplyparse.c + src/minixml.c src/upnpreplyparse.c) + target_include_directories (testupnpreplyparse PRIVATE + $) + target_link_libraries (testupnpreplyparse PRIVATE miniupnpc-tests) + + add_executable (testigddescparse src/testigddescparse.c + src/igd_desc_parse.c src/minixml.c src/miniupnpc.c src/miniwget.c src/minissdpc.c + src/upnpcommands.c src/upnpreplyparse.c src/minisoap.c src/connecthostport.c + src/portlistingparse.c src/receivedata.c src/addr_is_reserved.c + ) + target_include_directories (testigddescparse PRIVATE + $) + target_link_libraries (testigddescparse PRIVATE miniupnpc-tests) + + add_executable (testminiwget src/testminiwget.c + src/miniwget.c src/miniupnpc.c src/minisoap.c src/upnpcommands.c src/minissdpc.c + src/upnpreplyparse.c src/minixml.c src/igd_desc_parse.c src/connecthostport.c + src/portlistingparse.c src/receivedata.c src/addr_is_reserved.c + ) + target_include_directories (testminiwget PRIVATE + $) + target_link_libraries (testminiwget PRIVATE miniupnpc-tests) + + add_executable (testaddr_is_reserved src/testaddr_is_reserved.c + src/addr_is_reserved.c + ) + target_link_libraries (testaddr_is_reserved PRIVATE miniupnpc-tests) + + add_executable (testportlistingparse src/testportlistingparse.c + src/minixml.c src/portlistingparse.c) + target_include_directories (testportlistingparse PRIVATE + $) + target_link_libraries (testportlistingparse PRIVATE miniupnpc-tests) + + if (NOT WIN32) + add_executable (minihttptestserver src/minihttptestserver.c) + endif() + +# set (UPNPC_INSTALL_TARGETS ${UPNPC_INSTALL_TARGETS} testminixml minixmlvalid testupnpreplyparse testigddescparse testminiwget) + include(CTest) + add_test(NAME validateminixml + COMMAND minixmlvalid) + add_test(NAME validateminiwget + COMMAND ${CMAKE_SOURCE_DIR}/testminiwget.sh) + if (NOT WIN32) + set_property(TEST validateminiwget + PROPERTY ENVIRONMENT + TESTSERVER=${CMAKE_BINARY_DIR}/minihttptestserver + TESTMINIWGET=${CMAKE_BINARY_DIR}/testminiwget) + endif() + add_test(NAME validateupnpreplyparse + COMMAND ${CMAKE_SOURCE_DIR}/testupnpreplyparse.sh + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + set_property(TEST validateupnpreplyparse + PROPERTY ENVIRONMENT + TESTUPNPREPLYPARSE=${CMAKE_BINARY_DIR}/testupnpreplyparse) + add_test(NAME validateportlistingparse + COMMAND testportlistingparse) + add_test(NAME validateigddescparse1 + COMMAND testigddescparse new_LiveBox_desc.xml new_LiveBox_desc.values + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/testdesc) + add_test(NAME validateigddescparse2 + COMMAND testigddescparse linksys_WAG200G_desc.xml linksys_WAG200G_desc.values + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/testdesc) + add_test(NAME validateaddr_is_reserved + COMMAND testaddr_is_reserved) + +endif () + +configure_file(miniupnpc.pc.in miniupnpc.pc @ONLY) + +if (NOT UPNPC_NO_INSTALL) + install (FILES + include/miniupnpc.h + include/miniwget.h + include/upnpcommands.h + include/igd_desc_parse.h + include/upnpreplyparse.h + include/upnperrors.h + include/upnpdev.h + include/miniupnpctypes.h + include/portlistingparse.h + include/miniupnpc_declspec.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/miniupnpc + ) + + install(FILES miniupnpc-config.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/miniupnpc + ) + + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/miniupnpc.pc + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig + ) + + install(FILES man3/miniupnpc.3 + DESTINATION ${CMAKE_INSTALL_MANDIR}/man3 + ) + + install(FILES external-ip.sh + TYPE BIN + ) +endif() + +# vim: ts=2:sw=2:expandtab diff --git a/Minecraft.Client/miniupnpc/Changelog.txt b/Minecraft.Client/miniupnpc/Changelog.txt new file mode 100644 index 00000000..ca440e1a --- /dev/null +++ b/Minecraft.Client/miniupnpc/Changelog.txt @@ -0,0 +1,862 @@ +$Id: Changelog.txt,v 1.280 2025/05/25 21:56:47 nanard Exp $ +miniUPnP client Changelog. + +VERSION 2.3.3 : released 2025/05/27 + +2025/05/25: + define WIN32_LEAN_AND_MEAN + +2025/03/29: + fix a bug in ssdpDiscoverDevices() that has been there since 2010/04/05 + and the use of getaddrinfo() + more argument checks in UPNP_AddAnyPortMapping() and + UPNP_GetGenericPortMappingEntryExt() + +2025/03/18: + Add UPNP_GetSpecificPortMappingEntryExt() and + UPNP_GetGenericPortMappingEntryExt() functions to support retrieving + arbitrary long descriptions. + Increments API_VERSION to 21 + +VERSION 2.3.2 : released 2025/03/05 + +2025/02/24: + remove STRTOUI from public headers + remove parseURL() from public headers + remove struct NameValue from upnpreplyparse.h and use flexible array member + Increments API_VERSION to 20 + +VERSION 2.3.1 : released 2025/02/23 + +2025/02/23: + allocate argument arrays on stack instead of heap in upnpcommand.c + +2025/02/09: + doxygen documentation of the public headers + +VERSION 2.3.0 : released 2025/01/11 + +2025/01/10: + Change simpleUPnPcommand() prototype to remove unused first argument + Increments API_VERSION to 19 + +2024/07/27: + Add #define for UPNP_GetValidIGD() return values + +VERSION 2.2.8 : released 2024/06/09 + +2024/05/16: + IPv6: try site-local before link-local + +2024/05/08: + upnpc.c: Add -f option to upnpc program (delete multiple port redirections) + UPNP_GetValidIGD(): distinguish between not connected and connected to a + "private" network (with a reserved IP address). + Increments API_VERSION to 18 + +VERSION 2.2.7 : released 2024/03/20 + +2024/01/15: + listdevices.c: exit with status code 1 if no UPNP device is found + +2024/01/07: + upnpc.c: reformat Usage + +VERSION 2.2.6 : released 2024/01/04 + +2024/01/04: + includes charset="utf-8" in Content-Type + fix memory allocation error in minissdpc.c + +2023/06/15: + Make User-Agent compliant. + listdevices => upnp-listdevices + +VERSION 2.2.5 : released 2023/06/12 + +2023/06/05: + GetListOfPortMappings NewStartPort 0 => 1 + +2023/05/30: + CheckPinholeWorking is optional + add 60x errors from UPnP Device Architecture + +2023/01/04: + cmake: install binaries, man pages and external-ip.sh + +VERSION 2.2.4 : released 2022/10/21 + +2022/02/20: + upnpc: use of @ to replace local lan address + +2021/11/09: + python module : Allow to specify the root description url + +VERSION 2.2.3 : released 2021/09/28 + +2021/08/13: + Change directory structure : include/ and src/ directories. + +VERSION 2.2.2 : released 2021/03/03 + +2021/01/15: + miniupnpcmodule.c: throw an exception in UPnP_discover() + +2020/12/30: + Fix usage of IP_MULTICAST_IF with struct ip_mreqn + +VERSION 2.2.1 : released 2020/12/20 + +2020/11/30: + Add miniupnpc.rc for .dll description + +VERSION 2.2.0 : released 2020/11/09 + +2020/09/24: + Check properly for reserved IP addresses + +2020/09/23: + prevent infinite loop in upnpDiscover() + +2020/02/16: + Add Haiku support + +2019/10/22: + testminiwget.sh can use either "ip addr" or "ifconfig -a + +2019/10/13: + fix UPNP_GetValidIGD() when several devices are found + which are reachable from != local address + +2019/08/24: + Allow Remote Host on upnpc command line + fix error 708 description in strupnperror() + +2019/04/05: + Fix memory leak in upnpreplyparse.c with NewPortListing element + +2019/03/10: + connecthostport.c: Code simplification, error trace fix + +2019/01/23: + set timeout for select() in connecthostport() + +2018/10/31: + miniupnpcmodule.c: check return of WSAStartup() + +2018/07/14: + Fix and improve MSVC project : + Add Dll configurations + improve genminiupnpcstrings.vbs + +2018/06/18: + Fixes for windows 64-bits. + +VERSION 2.1 : released 2018/05/07 + +2018/05/07: + CMake Modernize and cleanup CMakeLists.txt + Update MS Visual Studio projects + +2018/04/30: + listdevices: show devices sorted by XML desc URL + +2018/04/26: + Small fix in miniupnpcmodule.c (python module) + Support cross compiling in Makefile.mingw + +2018/04/06: + Use SOCKET type instead of int (for Win64 compilation) + Increments API_VERSION to 17 + +2018/02/22: + Disable usage of MiniSSDPd when using -m option + +2017/12/11: + Fix buffer over run in minixml.c + Fix uninitialized variable access in upnpreplyparse.c + +2017/05/05: + Fix CVE-2017-8798 Thanks to tin/Team OSTStrom + +2016/11/11: + check strlen before memcmp in XML parsing portlistingparse.c + fix build under SOLARIS and CYGWIN + +2016/10/11: + Add python 3 compatibility to IGD test + +VERSION 2.0 : released 2016/04/19 + +2016/01/24: + change miniwget to return HTTP status code + increments API_VERSION to 16 + +2016/01/22: + Improve UPNPIGD_IsConnected() to check if WAN address is not private. + parse HTTP response status line in miniwget.c + +2015/10/26: + snprintf() overflow check. check overflow in simpleUPnPcommand2() + +2015/10/25: + fix compilation with old macs + fix compilation with mingw32 (for Appveyor) + fix python module for python <= 2.3 + +2015/10/08: + Change sameport to localport + see https://github.com/miniupnp/miniupnp/pull/120 + increments API_VERSION to 15 + +2015/09/15: + Fix buffer overflow in igd_desc_parse.c/IGDstartelt() + Discovered by Aleksandar Nikolic of Cisco Talos + +2015/08/28: + move ssdpDiscoverDevices() to minissdpc.c + +2015/08/27: + avoid unix socket leak in getDevicesFromMiniSSDPD() + +2015/08/16: + Also accept "Up" as ConnectionStatus value + +2015/07/23: + split getDevicesFromMiniSSDPD + add ttl argument to upnpDiscover() functions + increments API_VERSION to 14 + +2015/07/22: + Read USN from SSDP messages. + +2015/07/15: + Check malloc/calloc + +2015/06/16: + update getDevicesFromMiniSSDPD() to process longer minissdpd + responses + +2015/05/22: + add searchalltypes param to upnpDiscoverDevices() + increments API_VERSION to 13 + +2015/04/30: + upnpc: output version on the terminal + +2015/04/27: + _BSD_SOURCE is deprecated in favor of _DEFAULT_SOURCE + fix CMakeLists.txt COMPILE_DEFINITIONS + fix getDevicesFromMiniSSDPD() not setting scope_id + improve -r command of upnpc command line tool + +2014/11/17: + search all : + upnpDiscoverDevices() / upnpDiscoverAll() functions + listdevices executable + increment API_VERSION to 12 + validate igd_desc_parse + +2014/11/13: + increment API_VERSION to 11 + +2014/11/05: + simplified function GetUPNPUrls() + +2014/09/11: + use remoteHost arg of DeletePortMapping + +2014/09/06: + Fix python3 build + +2014/07/01: + Fix parsing of IGD2 root descriptions + +2014/06/10: + rename LIBSPEC to MINIUPNP_LIBSPEC + +2014/05/15: + Add support for IGD2 AddAnyPortMapping and DeletePortMappingRange + +2014/02/05: + handle EINPROGRESS after connect() + +2014/02/03: + minixml now handle XML comments + +VERSION 1.9 : released 2014/01/31 + +2014/01/31: + added argument remoteHost to UPNP_GetSpecificPortMappingEntry() + increment API_VERSION to 10 + +2013/12/09: + --help and -h arguments in upnpc.c + +2013/10/07: + fixed potential buffer overrun in miniwget.c + Modified UPNP_GetValidIGD() to check for ExternalIpAddress + +2013/08/01: + define MAXHOSTNAMELEN if not already done + +2013/06/06: + update upnpreplyparse to allow larger values (128 chars instead of 64) + +2013/05/14: + Update upnpreplyparse to take into account "empty" elements + validate upnpreplyparse.c code with "make check" + +2013/05/03: + Fix Solaris build thanks to Maciej MaÅ‚ecki + +2013/04/27: + Fix testminiwget.sh for BSD + +2013/03/23: + Fixed Makefile for *BSD + +2013/03/11: + Update Makefile to use JNAerator version 0.11 + +2013/02/11: + Fix testminiwget.sh for use with dash + Use $(DESTDIR) in Makefile + +VERSION 1.8 : released 2013/02/06 + +2012/10/16: + fix testminiwget with no IPv6 support + +2012/09/27: + Rename all include guards to not clash with C99 + (7.1.3 Reserved identifiers). + +2012/08/30: + Added -e option to upnpc program (set description for port mappings) + +2012/08/29: + Python 3 support (thanks to Christopher Foo) + +2012/08/11: + Fix a memory link in UPNP_GetValidIGD() + Try to handle scope id in link local IPv6 URL under MS Windows + +2012/07/20: + Disable HAS_IP_MREQN on DragonFly BSD + +2012/06/28: + GetUPNPUrls() now inserts scope into link-local IPv6 addresses + +2012/06/23: + More error return checks in upnpc.c + #define MINIUPNPC_GET_SRC_ADDR enables receivedata() to get scope_id + parseURL() now parses IPv6 addresses scope + new parameter for miniwget() : IPv6 address scope + increment API_VERSION to 9 + +2012/06/20: + fixed CMakeLists.txt + +2012/05/29 + Improvements in testminiwget.sh + +VERSION 1.7 : released 2012/05/24 + +2012/05/01: + Cleanup settings of CFLAGS in Makefile + Fix signed/unsigned integer comparaisons + +2012/04/20: + Allow to specify protocol with TCP or UDP for -A option + +2012/04/09: + Only try to fetch XML description once in UPNP_GetValidIGD() + Added -ansi flag to compilation, and fixed C++ comments to ANSI C comments. + +2012/04/05: + minor improvements to minihttptestserver.c + +2012/03/15: + upnperrors.c returns valid error string for unrecognized error codes + +2012/03/08: + make minihttptestserver listen on loopback interface instead of 0.0.0.0 + +2012/01/25: + Maven installation thanks to Alexey Kuznetsov + +2012/01/21: + Replace WIN32 macro by _WIN32 + +2012/01/19: + Fixes in java wrappers thanks to Alexey Kuznetsov : + https://github.com/axet/miniupnp/tree/fix-javatest/miniupnpc + Make and install .deb packages (python) thanks to Alexey Kuznetsov : + https://github.com/axet/miniupnp/tree/feature-debbuild/miniupnpc + +2012/01/07: + The multicast interface can now be specified by name with IPv4. + +2012/01/02: + Install man page + +2011/11/25: + added header to Port Mappings list in upnpc.c + +2011/10/09: + Makefile : make clean now removes jnaerator generated files. + MINIUPNPC_VERSION in miniupnpc.h (updated by make) + +2011/09/12: + added rootdescURL to UPNPUrls structure. + +VERSION 1.6 : released 2011/07/25 + +2011/07/25: + Update doc for version 1.6 release + +2011/06/18: + Fix for windows in miniwget.c + +2011/06/04: + display remote host in port mapping listing + +2011/06/03: + Fix in make install : there were missing headers + +2011/05/26: + Fix the socket leak in miniwget thanks to Richard Marsh. + Permit to add leaseduration in -a command. Display lease duration. + +2011/05/15: + Try both LinkLocal and SiteLocal multicast address for SSDP in IPv6 + +2011/05/09: + add a test in testminiwget.sh. + more error checking in miniwget.c + +2011/05/06: + Adding some tool to test and validate miniwget.c + simplified and debugged miniwget.c + +2011/04/11: + moving ReceiveData() to a receivedata.c file. + parsing presentation url + adding IGD v2 WANIPv6FirewallControl commands + +2011/04/10: + update of miniupnpcmodule.c + comments in miniwget.c, update in testminiwget + Adding errors codes from IGD v2 + new functions in upnpc.c for IGD v2 + +2011/04/09: + Support for litteral ip v6 address in miniwget + +2011/04/08: + Adding support for urn:schemas-upnp-org:service:WANIPv6FirewallControl:1 + Updating APIVERSION + Supporting IPV6 in upnpDiscover() + Adding a -6 option to upnpc command line tool + +2011/03/18: + miniwget/parseURL() : return an error when url param is null. + fixing GetListOfPortMappings() + +2011/03/14: + upnpDiscover() now reporting an error code. + improvements in comments. + +2011/03/11: + adding miniupnpcstrings.h.cmake and CMakeLists.txt files. + +2011/02/15: + Implementation of GetListOfPortMappings() + +2011/02/07: + updates to minixml to support character data starting with spaces + minixml now support CDATA + upnpreplyparse treats specificaly + change in simpleUPnPcommand to return the buffer (simplification) + +2011/02/06: + Added leaseDuration argument to AddPortMapping() + Starting to implement GetListOfPortMappings() + +2011/01/11: + updating wingenminiupnpcstrings.c + +2011/01/04: + improving updateminiupnpcstrings.sh + +VERSION 1.5 : released 2011/01/01 + +2010/12/21: + use NO_GETADDRINFO macro to disable the use of getaddrinfo/freeaddrinfo + +2010/12/11: + Improvements on getHTTPResponse() code. + +2010/12/09: + new code for miniwget that handle Chunked transfer encoding + using getHTTPResponse() in SOAP call code + Adding MANIFEST.in for 'python setup.py bdist_rpm' + +2010/11/25: + changes to minissdpc.c to compile under Win32. + see http://miniupnp.tuxfamily.org/forum/viewtopic.php?t=729 + +2010/09/17: + Various improvement to Makefile from MichaÅ‚ Górny + +2010/08/05: + Adding the script "external-ip.sh" from Reuben Hawkins + +2010/06/09: + update to python module to match modification made on 2010/04/05 + update to Java test code to match modification made on 2010/04/05 + all UPNP_* function now return an error if the SOAP request failed + at HTTP level. + +2010/04/17: + Using GetBestRoute() under win32 in order to find the + right interface to use. + +2010/04/12: + Retrying with HTTP/1.1 if HTTP/1.0 failed. see + http://miniupnp.tuxfamily.org/forum/viewtopic.php?p=1703 + +2010/04/07: + avoid returning duplicates in upnpDiscover() + +2010/04/05: + Create a connecthostport.h/.c with connecthostport() function + and use it in miniwget and miniupnpc. + Use getnameinfo() instead of inet_ntop or inet_ntoa + Work to make miniupnpc IPV6 compatible... + Add java test code. + Big changes in order to support device having both WANIPConnection + and WANPPPConnection. + +2010/04/04: + Use getaddrinfo() instead of gethostbyname() in miniwget. + +2010/01/06: + #define _DARWIN_C_SOURCE for Mac OS X + +2009/12/19: + Improve MinGW32 build + +2009/12/11: + adding a MSVC9 project to build the static library and executable + +2009/12/10: + Fixing some compilation stuff for Windows/MinGW + +2009/12/07: + adaptations in Makefile and updateminiupnpcstring.sh for AmigaOS + some fixes for Windows when using virtual ethernet adapters (it is the + case with VMWare installed). + +2009/12/04: + some fixes for AmigaOS compilation + Changed HTTP version to HTTP/1.0 for Soap too (to prevent chunked + transfer encoding) + +2009/12/03: + updating printIDG and testigddescparse.c for debug. + modifications to compile under AmigaOS + adding a testminiwget program + Changed miniwget to advertise itself as HTTP/1.0 to prevent chunked + transfer encoding + +2009/11/26: + fixing updateminiupnpcstrings.sh to take into account + which command that does not return an error code. + +VERSION 1.4 : released 2009/10/30 + +2009/10/16: + using Py_BEGIN_ALLOW_THREADS and Py_END_ALLOW_THREADS in python module. + +2009/10/10: + Some fixes for compilation under Solaris + compilation fixes : http://miniupnp.tuxfamily.org/forum/viewtopic.php?p=1464 + +2009/09/21: + fixing the code to ignore EINTR during connect() calls. + +2009/08/07: + Set socket timeout for connect() + Some cleanup in miniwget.c + +2009/08/04: + remove multiple redirections with -d in upnpc.c + Print textual error code in upnpc.c + Ignore EINTR during the connect() and poll() calls. + +2009/07/29: + fix in updateminiupnpcstrings.sh if OS name contains "/" + Sending a correct value for MX: field in SSDP request + +2009/07/20: + Change the Makefile to compile under Mac OS X + Fixed a stackoverflow in getDevicesFromMiniSSDPD() + +2009/07/09: + Compile under Haiku + generate miniupnpcstrings.h.in from miniupnpcstrings.h + +2009/06/04: + patching to compile under CygWin and cross compile for minGW + +VERSION 1.3 : + +2009/04/17: + updating python module + Use strtoull() when using C99 + +2009/02/28: + Fixed miniwget.c for compiling under sun + +2008/12/18: + cleanup in Makefile (thanks to Paul de Weerd) + minissdpc.c : win32 compatibility + miniupnpc.c : changed xmlns prefix from 'm' to 'u' + Removed NDEBUG (using DEBUG) + +2008/10/14: + Added the ExternalHost argument to DeletePortMapping() + +2008/10/11: + Added the ExternalHost argument to AddPortMapping() + Put a correct User-Agent: header in HTTP requests. + +VERSION 1.2 : + +2008/10/07: + Update docs + +2008/09/25: + Integrated sameport patch from Dario Meloni : Added a "sameport" + argument to upnpDiscover(). + +2008/07/18: + small modif to make Clang happy :) + +2008/07/17: + #define SOAPPREFIX "s" in miniupnpc.c in order to remove SOAP-ENV... + +2008/07/14: + include declspec.h in installation (to /usr/include/miniupnpc) + +VERSION 1.1 : + +2008/07/04: + standard options for install/ln instead of gnu-specific stuff. + +2008/07/03: + now builds a .dll and .lib with win32. (mingw32) + +2008/04/28: + make install now install the binary of the upnpc tool + +2008/04/27: + added testupnpigd.py + added error strings for miniupnpc "internal" errors + improved python module error/exception reporting. + +2008/04/23: + Completely rewrite igd_desc_parse.c in order to be compatible with + Linksys WAG200G + Added testigddescparse + updated python module + +VERSION 1.0 : + +2008/02/21: + put some #ifdef DEBUG around DisplayNameValueList() + +2008/02/18: + Improved error reporting in upnpcommands.c + UPNP_GetStatusInfo() returns LastConnectionError + +2008/02/16: + better error handling in minisoap.c + improving display of "valid IGD found" in upnpc.c + +2008/02/03: + Fixing UPNP_GetValidIGD() + improved make install :) + +2007/12/22: + Adding upnperrors.c/h to provide a strupnperror() function + used to translate UPnP error codes to string. + +2007/12/19: + Fixing getDevicesFromMiniSSDPD() + improved error reporting of UPnP functions + +2007/12/18: + It is now possible to specify a different location for MiniSSDPd socket. + working with MiniSSDPd is now more efficient. + python module improved. + +2007/12/16: + improving error reporting + +2007/12/13: + Try to improve compatibility by using HTTP/1.0 instead of 1.1 and + XML a bit different for SOAP. + +2007/11/25: + fixed select() call for linux + +2007/11/15: + Added -fPIC to CFLAG for better shared library code. + +2007/11/02: + Fixed a potential socket leak in miniwget2() + +2007/10/16: + added a parameter to upnpDiscover() in order to allow the use of another + interface than the default multicast interface. + +2007/10/12: + Fixed the creation of symbolic link in Makefile + +2007/10/08: + Added man page + +2007/10/02: + fixed memory bug in GetUPNPUrls() + +2007/10/01: + fixes in the Makefile + Added UPNP_GetIGDFromUrl() and adapted the sample program accordingly. + Added SONAME in the shared library to please debian :) + fixed MS Windows compilation (minissdpd is not available under MS Windows). + +2007/09/25: + small change to Makefile to be able to install in a different location + (default is /usr) + +2007/09/24: + now compiling both shared and static library + +2007/09/19: + Cosmetic changes on upnpc.c + +2007/09/02: + adapting to new miniSSDPd (release version ?) + +2007/08/31: + Usage of miniSSDPd to skip discovery process. + +2007/08/27: + fixed python module to allow compilation with Python older than Python 2.4 + +2007/06/12: + Added a python module. + +2007/05/19: + Fixed compilation under MinGW + +2007/05/15: + fixed a memory leak in AddPortMapping() + Added testupnpreplyparse executable to check the parsing of + upnp soap messages + minixml now ignore namespace prefixes. + +2007/04/26: + upnpc now displays external ip address with -s or -l + +2007/04/11: + changed MINIUPNPC_URL_MAXSIZE to 128 to accommodate the "BT Voyager 210" + +2007/03/19: + cleanup in miniwget.c + +2007/03/01: + Small typo fix... + +2007/01/30: + Now parsing the HTTP header from SOAP responses in order to + get content-length value. + +2007/01/29: + Fixed the Soap Query to speedup the HTTP request. + added some Win32 DLL stuff... + +2007/01/27: + Fixed some WIN32 compatibility issues + +2006/12/14: + Added UPNPIGD_IsConnected() function in miniupnp.c/.h + Added UPNP_GetValidIGD() in miniupnp.c/.h + cleaned upnpc.c main(). now using UPNP_GetValidIGD() + +2006/12/07: + Version 1.0-RC1 released + +2006/12/03: + Minor changes to compile under SunOS/Solaris + +2006/11/30: + made a minixml parser validator program + updated minixml to handle attributes correctly + +2006/11/22: + Added a -r option to the upnpc sample thanks to Alexander Hubmann. + +2006/11/19: + Cleanup code to make it more ANSI C compliant + +2006/11/10: + detect and display local lan address. + +2006/11/04: + Packets and Bytes Sent/Received are now unsigned int. + +2006/11/01: + Bug fix thanks to Giuseppe D'Angelo + +2006/10/31: + C++ compatibility for .h files. + Added a way to get ip Address on the LAN used to reach the IGD. + +2006/10/25: + Added M-SEARCH to the services in the discovery process. + +2006/10/22: + updated the Makefile to use makedepend, added a "make install" + update Makefile + +2006/10/20: + fixing the description url parsing thanks to patch sent by + Wayne Dawe. + Fixed/translated some comments. + Implemented a better discover process, first looking + for IGD then for root devices (as some devices only reply to + M-SEARCH for root devices). + +2006/09/02: + added freeUPNPDevlist() function. + +2006/08/04: + More command line arguments checking + +2006/08/01: + Added the .bat file to compile under Win32 with minGW32 + +2006/07/31: + Fixed the rootdesc parser (igd_desc_parse.c) + +2006/07/20: + parseMSEARCHReply() is now returning the ST: line as well + starting changes to detect several UPnP devices on the network + +2006/07/19: + using GetCommonLinkProperties to get down/upload bitrate + diff --git a/Minecraft.Client/miniupnpc/DESCRIPTION b/Minecraft.Client/miniupnpc/DESCRIPTION new file mode 100644 index 00000000..1451da7c --- /dev/null +++ b/Minecraft.Client/miniupnpc/DESCRIPTION @@ -0,0 +1,6 @@ +MiniUPnPc is a library enabling applications to access the services provided +by an UPnP "Internet Gateway Device" present on the network. In UPnP +terminology, MiniUPnPc is a UPnP Control Point. + +MiniUPnPc is designed to have low footprint and the libc as unique +dependency. diff --git a/Minecraft.Client/miniupnpc/Doxyfile b/Minecraft.Client/miniupnpc/Doxyfile new file mode 100644 index 00000000..b32ecf19 --- /dev/null +++ b/Minecraft.Client/miniupnpc/Doxyfile @@ -0,0 +1,2736 @@ +# Doxyfile 1.9.6 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "MiniUPnPc" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = 2.3.3 + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "UPnP Internet Gateway Device Control Point" + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = ./doxygen + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:^^" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. + +CASE_SENSE_NAMES = SYSTEM + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete +# function parameter documentation. If set to NO, doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about +# undocumented enumeration values. If set to NO, doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = include/ + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding +# "INPUT_ENCODING" for further information on supported encodings. + +INPUT_FILE_ENCODING = + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, +# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C +# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, +# *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.l \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# ANamespace::AClass, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generate light mode output, DARK always +# generate dark mode output, AUTO_LIGHT automatically set the mode according to +# the user preference, use light mode if no preference is set (the default), +# AUTO_DARK automatically set the mode according to the user preference, use +# dark mode if no preference is set and TOGGLE allow to user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a color-wheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use gray-scales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /