#include "steam_networking_manager.h" #include #include SteamNetworkingManager *SteamNetworkingManager::instance = nullptr; // Static callback function void SteamNetworkingManager::OnSteamNetConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t *pInfo) { if (instance) { instance->handleConnectionStatusChanged(pInfo); } } SteamFriendsCallbacks::SteamFriendsCallbacks(SteamNetworkingManager *manager) : manager_(manager) { std::cout << "SteamFriendsCallbacks constructor called" << std::endl; } void SteamFriendsCallbacks::OnGameRichPresenceJoinRequested(GameRichPresenceJoinRequested_t *pCallback) { std::cout << "GameRichPresenceJoinRequested received" << std::endl; if (manager_) { const char *connectStr = pCallback->m_rgchConnect; std::cout << "Connect string: '" << (connectStr ? connectStr : "null") << "'" << std::endl; if (connectStr && connectStr[0] != '\0') { try { uint64 id = std::stoull(connectStr); std::string str = connectStr; std::cout << "Parsed ID: " << id << std::endl; if (str.find("7656119") == 0) { // It's a Steam ID, join host directly std::cout << "Parsed Steam ID: " << id << ", joining host" << std::endl; if (!manager_->isHost() && !manager_->isConnected()) { manager_->joinHost(id); // Start TCP Server if dependencies are set if (manager_->server_ && !(*manager_->server_)) { *manager_->server_ = std::make_unique(8888, manager_); if (!(*manager_->server_)->start()) { std::cerr << "Failed to start TCP server" << std::endl; } } } else { std::cout << "Already host or connected, ignoring join request" << std::endl; } } else { // Assume it's a lobby ID CSteamID lobbySteamID(id); std::cout << "Parsed lobby ID: " << id << std::endl; if (!manager_->isHost() && !manager_->isConnected()) { std::cout << "Joining lobby from invite: " << id << std::endl; manager_->joinLobby(lobbySteamID); } else { std::cout << "Already host or connected, ignoring invite" << std::endl; } } } catch (const std::exception &e) { std::cerr << "Failed to parse connect string: " << connectStr << " error: " << e.what() << std::endl; } } else { std::cerr << "Empty connect string in join request" << std::endl; } } else { std::cout << "Manager is null" << std::endl; } } void SteamFriendsCallbacks::OnGameLobbyJoinRequested(GameLobbyJoinRequested_t *pCallback) { std::cout << "GameLobbyJoinRequested received" << std::endl; if (manager_) { CSteamID lobbyID = pCallback->m_steamIDLobby; std::cout << "Lobby ID: " << lobbyID.ConvertToUint64() << std::endl; if (!manager_->isHost() && !manager_->isConnected()) { std::cout << "Joining lobby from request: " << lobbyID.ConvertToUint64() << std::endl; manager_->joinLobby(lobbyID); } else { std::cout << "Already host or connected, ignoring lobby join request" << std::endl; } } else { std::cout << "Manager is null" << std::endl; } } SteamNetworkingManager::SteamNetworkingManager() : m_pInterface(nullptr), hListenSock(k_HSteamListenSocket_Invalid), g_isHost(false), g_isClient(false), g_isConnected(false), g_hConnection(k_HSteamNetConnection_Invalid), io_context_(nullptr), clientMap_(nullptr), clientMutex_(nullptr), server_(nullptr), localPort_(nullptr), messageHandler_(nullptr), steamFriendsCallbacks(nullptr), steamMatchmakingCallbacks(nullptr), currentLobby(k_steamIDNil) { std::cout << "Initialized SteamNetworkingManager" << std::endl; } SteamNetworkingManager::~SteamNetworkingManager() { stopMessageHandler(); delete messageHandler_; delete steamFriendsCallbacks; delete steamMatchmakingCallbacks; shutdown(); } bool SteamNetworkingManager::initialize() { instance = this; if (!SteamAPI_Init()) { std::cerr << "Failed to initialize Steam API" << std::endl; return false; } // 【新增】开启详细日志 SteamNetworkingUtils()->SetDebugOutputFunction(k_ESteamNetworkingSocketsDebugOutputType_Msg, [](ESteamNetworkingSocketsDebugOutputType nType, const char *pszMsg) { std::cout << "[SteamNet] " << pszMsg << std::endl; }); int32 logLevel = k_ESteamNetworkingSocketsDebugOutputType_Verbose; SteamNetworkingUtils()->SetConfigValue( k_ESteamNetworkingConfig_LogLevel_P2PRendezvous, k_ESteamNetworkingConfig_Global, 0, k_ESteamNetworkingConfig_Int32, &logLevel); // 1. 允许 P2P (ICE) 直连 // 默认情况下 Steam 可能会保守地只允许 LAN,这里设置为 "All" 允许公网 P2P int32 nIceEnable = k_nSteamNetworkingConfig_P2P_Transport_ICE_Enable_Public | k_nSteamNetworkingConfig_P2P_Transport_ICE_Enable_Private; SteamNetworkingUtils()->SetConfigValue( k_ESteamNetworkingConfig_P2P_Transport_ICE_Enable, k_ESteamNetworkingConfig_Global, // <--- 关键:作用域选 Global 0, // Global 时此参数填 0 k_ESteamNetworkingConfig_Int32, &nIceEnable); // 2. (可选) 极度排斥中继 // 如果你铁了心不想走中继,可以给中继路径增加巨大的虚拟延迟惩罚 // 这样只有在直连完全打不通(比如防火墙太严格)时,Steam 才会无奈选择中继 int32 nSdrPenalty = 10000; // 10000ms 惩罚 SteamNetworkingUtils()->SetConfigValue( k_ESteamNetworkingConfig_P2P_Transport_SDR_Penalty, k_ESteamNetworkingConfig_Global, 0, k_ESteamNetworkingConfig_Int32, &nSdrPenalty); // Allow connections from IPs without authentication int32 allowWithoutAuth = 2; SteamNetworkingUtils()->SetConfigValue( k_ESteamNetworkingConfig_IP_AllowWithoutAuth, k_ESteamNetworkingConfig_Global, 0, k_ESteamNetworkingConfig_Int32, &allowWithoutAuth); // Manually set STUN server list std::string stunServers = "stun.l.google.com:19302,stun1.l.google.com:19302,stun2.l.google.com:19302,stun3.l.google.com:19302,stun4.l.google.com:19302"; SteamNetworkingUtils()->SetConfigValue( k_ESteamNetworkingConfig_P2P_STUN_ServerList, k_ESteamNetworkingConfig_Global, 0, k_ESteamNetworkingConfig_String, stunServers.c_str()); // 打印当前配置的 TURN 和 STUN 服务器列表 SteamNetworkingUtils()->GetConfigValueInfo(k_ESteamNetworkingConfig_P2P_TURN_ServerList, nullptr, nullptr); char turnServers[4096] = {}; ESteamNetworkingConfigDataType turnType; size_t turnServersSize = sizeof(turnServers); ESteamNetworkingGetConfigValueResult turnResult = SteamNetworkingUtils()->GetConfigValue( k_ESteamNetworkingConfig_P2P_TURN_ServerList, k_ESteamNetworkingConfig_Global, 0, &turnType, turnServers, &turnServersSize); std::cout << "[SteamNet] TURN servers: " << turnServers << std::endl; char stunServersBuffer[4096] = {}; ESteamNetworkingConfigDataType stunType; size_t stunServersSize = sizeof(stunServersBuffer); ESteamNetworkingGetConfigValueResult stunResult = SteamNetworkingUtils()->GetConfigValue( k_ESteamNetworkingConfig_P2P_STUN_ServerList, k_ESteamNetworkingConfig_Global, 0, &stunType, stunServersBuffer, &stunServersSize); std::cout << "[SteamNet] STUN servers: " << stunServersBuffer << std::endl; // Create callbacks after Steam API init steamFriendsCallbacks = new SteamFriendsCallbacks(this); steamMatchmakingCallbacks = new SteamMatchmakingCallbacks(this); SteamNetworkingUtils()->InitRelayNetworkAccess(); SteamNetworkingUtils()->SetGlobalCallback_SteamNetConnectionStatusChanged(OnSteamNetConnectionStatusChanged); m_pInterface = SteamNetworkingSockets(); // Clear Rich Presence on startup SteamFriends()->ClearRichPresence(); std::cout << "Cleared Rich Presence on startup" << std::endl; // Check if callbacks are registered std::cout << "Steam API initialized" << std::endl; // Get friends list int friendCount = SteamFriends()->GetFriendCount(k_EFriendFlagAll); for (int i = 0; i < friendCount; ++i) { CSteamID friendID = SteamFriends()->GetFriendByIndex(i, k_EFriendFlagAll); const char *name = SteamFriends()->GetFriendPersonaName(friendID); friendsList.push_back({friendID, name}); } return true; } void SteamNetworkingManager::shutdown() { leaveLobby(); if (g_hConnection != k_HSteamNetConnection_Invalid) { m_pInterface->CloseConnection(g_hConnection, 0, nullptr, false); } if (hListenSock != k_HSteamListenSocket_Invalid) { m_pInterface->CloseListenSocket(hListenSock); } // Clear Rich Presence on shutdown SteamFriends()->ClearRichPresence(); SteamAPI_Shutdown(); } bool SteamNetworkingManager::createLobby() { SteamAPICall_t hSteamAPICall = SteamMatchmaking()->CreateLobby(k_ELobbyTypePublic, 4); if (hSteamAPICall == k_uAPICallInvalid) { std::cerr << "Failed to create lobby" << std::endl; return false; } // Call result will be handled by callback return true; } void SteamNetworkingManager::leaveLobby() { if (currentLobby != k_steamIDNil) { SteamMatchmaking()->LeaveLobby(currentLobby); currentLobby = k_steamIDNil; } } bool SteamNetworkingManager::searchLobbies() { lobbies.clear(); SteamAPICall_t hSteamAPICall = SteamMatchmaking()->RequestLobbyList(); if (hSteamAPICall == k_uAPICallInvalid) { std::cerr << "Failed to request lobby list" << std::endl; return false; } // Results will be handled by callback return true; } bool SteamNetworkingManager::joinLobby(CSteamID lobbyID) { if (SteamMatchmaking()->JoinLobby(lobbyID) != k_EResultOK) { std::cerr << "Failed to join lobby" << std::endl; return false; } // Connection will be handled by callback return true; } bool SteamNetworkingManager::startHosting() { if (!createLobby()) { return false; } hListenSock = m_pInterface->CreateListenSocketP2P(0, 0, nullptr); if (hListenSock != k_HSteamListenSocket_Invalid) { g_isHost = true; std::cout << "Created listen socket for hosting game room" << std::endl; // Rich Presence is set in OnLobbyCreated callback return true; } else { std::cerr << "Failed to create listen socket for hosting" << std::endl; leaveLobby(); return false; } } void SteamNetworkingManager::stopHosting() { if (hListenSock != k_HSteamListenSocket_Invalid) { m_pInterface->CloseListenSocket(hListenSock); hListenSock = k_HSteamListenSocket_Invalid; } leaveLobby(); g_isHost = false; } bool SteamNetworkingManager::joinHost(uint64 hostID) { CSteamID hostSteamID(hostID); g_isClient = true; g_hostSteamID = hostSteamID; SteamNetworkingIdentity identity; identity.SetSteamID(hostSteamID); g_hConnection = m_pInterface->ConnectP2P(identity, 0, 0, nullptr); if (g_hConnection != k_HSteamNetConnection_Invalid) { std::cout << "Attempting to connect to host " << hostSteamID.ConvertToUint64() << " with virtual port " << 0 << std::endl; return true; } else { std::cerr << "Failed to initiate connection" << std::endl; return false; } } void SteamNetworkingManager::setMessageHandlerDependencies(boost::asio::io_context &io_context, std::map> &clientMap, std::mutex &clientMutex, std::unique_ptr &server, int &localPort) { io_context_ = &io_context; clientMap_ = &clientMap; clientMutex_ = &clientMutex; server_ = &server; localPort_ = &localPort; messageHandler_ = new SteamMessageHandler(io_context, m_pInterface, connections, clientMap, clientMutex, connectionsMutex, server, g_isHost, localPort); } void SteamNetworkingManager::startMessageHandler() { if (messageHandler_) { messageHandler_->start(); } } void SteamNetworkingManager::stopMessageHandler() { if (messageHandler_) { messageHandler_->stop(); } } void SteamNetworkingManager::update() { std::lock_guard lock(connectionsMutex); for (auto &pair : userMap) { HSteamNetConnection conn = pair.first; UserInfo &userInfo = pair.second; SteamNetConnectionInfo_t info; SteamNetConnectionRealTimeStatus_t status; if (m_pInterface->GetConnectionInfo(conn, &info) && m_pInterface->GetConnectionRealTimeStatus(conn, &status, 0, nullptr)) { userInfo.ping = status.m_nPing; userInfo.isRelay = (info.m_idPOPRelay != 0); } } } void SteamNetworkingManager::handleConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t *pInfo) { std::lock_guard lock(connectionsMutex); std::cout << "Connection status changed: " << pInfo->m_info.m_eState << " for connection " << pInfo->m_hConn << std::endl; if (pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_ProblemDetectedLocally) { std::cout << "Connection failed: " << pInfo->m_info.m_szEndDebug << std::endl; } if (pInfo->m_eOldState == k_ESteamNetworkingConnectionState_None && pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_Connecting) { m_pInterface->AcceptConnection(pInfo->m_hConn); connections.push_back(pInfo->m_hConn); g_hConnection = pInfo->m_hConn; g_isConnected = true; std::cout << "Accepted incoming connection from " << pInfo->m_info.m_identityRemote.GetSteamID().ConvertToUint64() << std::endl; // Add user info SteamNetConnectionInfo_t info; SteamNetConnectionRealTimeStatus_t status; if (m_pInterface->GetConnectionInfo(pInfo->m_hConn, &info) && m_pInterface->GetConnectionRealTimeStatus(pInfo->m_hConn, &status, 0, nullptr)) { UserInfo userInfo; userInfo.steamID = pInfo->m_info.m_identityRemote.GetSteamID(); userInfo.name = SteamFriends()->GetFriendPersonaName(userInfo.steamID); userInfo.ping = status.m_nPing; userInfo.isRelay = (info.m_idPOPRelay != 0); userMap[pInfo->m_hConn] = userInfo; std::cout << "Incoming connection details: ping=" << status.m_nPing << "ms, relay=" << (info.m_idPOPRelay != 0 ? "yes" : "no") << std::endl; } } else if (pInfo->m_eOldState == k_ESteamNetworkingConnectionState_Connecting && pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_Connected) { g_isConnected = true; std::cout << "Connected to host" << std::endl; // Add user info SteamNetConnectionInfo_t info; SteamNetConnectionRealTimeStatus_t status; if (m_pInterface->GetConnectionInfo(pInfo->m_hConn, &info) && m_pInterface->GetConnectionRealTimeStatus(pInfo->m_hConn, &status, 0, nullptr)) { UserInfo userInfo; userInfo.steamID = pInfo->m_info.m_identityRemote.GetSteamID(); userInfo.name = SteamFriends()->GetFriendPersonaName(userInfo.steamID); userInfo.ping = status.m_nPing; userInfo.isRelay = (info.m_idPOPRelay != 0); userMap[pInfo->m_hConn] = userInfo; std::cout << "Outgoing connection details: ping=" << status.m_nPing << "ms, relay=" << (info.m_idPOPRelay != 0 ? "yes" : "no") << std::endl; } } else if (pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_ClosedByPeer || pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_ProblemDetectedLocally) { g_isConnected = false; g_hConnection = k_HSteamNetConnection_Invalid; // Remove from connections auto it = std::find(connections.begin(), connections.end(), pInfo->m_hConn); if (it != connections.end()) { connections.erase(it); } userMap.erase(pInfo->m_hConn); std::cout << "Connection closed" << std::endl; } } SteamMatchmakingCallbacks::SteamMatchmakingCallbacks(SteamNetworkingManager *manager) : manager_(manager) {} void SteamMatchmakingCallbacks::OnLobbyCreated(LobbyCreated_t *pCallback) { if (pCallback->m_eResult == k_EResultOK) { manager_->currentLobby = pCallback->m_ulSteamIDLobby; std::cout << "Lobby created: " << manager_->currentLobby.ConvertToUint64() << std::endl; // Set Rich Presence with lobby ID std::string lobbyStr = std::to_string(manager_->currentLobby.ConvertToUint64()); SteamFriends()->SetRichPresence("connect", lobbyStr.c_str()); SteamFriends()->SetRichPresence("status", "主持游戏房间"); SteamFriends()->SetRichPresence("steam_display", "#StatusWithConnectFormat"); std::cout << "Set Rich Presence connect to: " << lobbyStr << std::endl; } else { std::cerr << "Failed to create lobby" << std::endl; } } void SteamMatchmakingCallbacks::OnLobbyListReceived(LobbyMatchList_t *pCallback) { manager_->lobbies.clear(); for (uint32 i = 0; i < pCallback->m_nLobbiesMatching; ++i) { CSteamID lobbyID = SteamMatchmaking()->GetLobbyByIndex(i); manager_->lobbies.push_back(lobbyID); } std::cout << "Received " << pCallback->m_nLobbiesMatching << " lobbies" << std::endl; } void SteamMatchmakingCallbacks::OnLobbyEntered(LobbyEnter_t *pCallback) { if (pCallback->m_EChatRoomEnterResponse == k_EChatRoomEnterResponseSuccess) { manager_->currentLobby = pCallback->m_ulSteamIDLobby; std::cout << "Entered lobby: " << pCallback->m_ulSteamIDLobby << std::endl; // Only join host if not the host if (!manager_->isHost()) { CSteamID hostID = SteamMatchmaking()->GetLobbyOwner(pCallback->m_ulSteamIDLobby); if (manager_->joinHost(hostID.ConvertToUint64())) { // Start TCP Server if dependencies are set if (manager_->server_ && !(*manager_->server_)) { *manager_->server_ = std::make_unique(8888, manager_); if (!(*manager_->server_)->start()) { std::cerr << "Failed to start TCP server" << std::endl; } } } } } else { std::cerr << "Failed to enter lobby" << std::endl; } }