commit 0f76252c169c8679e920a065a8d35de611c6aef2 Author: Ayndpa Date: Mon Nov 17 20:41:03 2025 +0800 Add initial implementation of ConnectTool with Dear ImGui and Steam Networking - Created project files for ConnectTool including filters and user settings. - Added README.md with setup instructions and prerequisites for building the project. - Implemented imgui_hello.cpp as a simple Dear ImGui application using GLFW and OpenGL. - Developed online_game_tool.cpp for hosting and joining game rooms using Steam Networking. - Created p2p_chat.cpp for a peer-to-peer chat application utilizing Steam Networking. - Implemented steam_friends.cpp to display the user's Steam friends list. - Added TCP client and server classes for handling network communication. - Integrated TCP server and client functionality into online_game_tool for real-time data exchange. diff --git a/ConnectTool.vcxproj b/ConnectTool.vcxproj new file mode 100644 index 0000000..03fe1e1 --- /dev/null +++ b/ConnectTool.vcxproj @@ -0,0 +1,138 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + 18.0 + Win32Proj + {71a90be8-b7ae-4d02-a50d-93b6f4770ad8} + ConnectTool + 10.0 + + + + Application + true + v145 + Unicode + + + Application + false + v145 + true + Unicode + + + Application + true + v145 + Unicode + + + Application + false + v145 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + + + + + + + + diff --git a/ConnectTool.vcxproj.filters b/ConnectTool.vcxproj.filters new file mode 100644 index 0000000..afef69e --- /dev/null +++ b/ConnectTool.vcxproj.filters @@ -0,0 +1,17 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + \ No newline at end of file diff --git a/ConnectTool.vcxproj.user b/ConnectTool.vcxproj.user new file mode 100644 index 0000000..88a5509 --- /dev/null +++ b/ConnectTool.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b28074c --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# ConnectTool - Dear ImGui Hello World + +This repository includes a simple Dear ImGui "Hello World" example using GLFW + OpenGL3 for cross-platform compatibility. + +## Prerequisites + +- C++17 compatible compiler +- CMake 3.10 or higher +- OpenGL 3.0 or higher +- GLFW library + +## Getting Dear ImGui + +1. Download Dear ImGui from https://github.com/ocornut/imgui +2. Extract the contents to a folder named `imgui` in the project root (next to `CMakeLists.txt`) + +## Building + +1. Ensure you have GLFW installed. On Windows, you can use vcpkg: + ``` + vcpkg install glfw3 + ``` + On Linux/macOS, use your package manager (e.g., `sudo apt install libglfw3-dev` on Ubuntu). + +2. Create a build directory: + ``` + mkdir build + cd build + cmake .. + make + ``` + +3. Run the executable: + ``` + ./ConnectTool + ``` + +## Cross-Platform Notes + +- This setup uses GLFW for window management and OpenGL for rendering, ensuring compatibility across Windows, Linux, and macOS. +- If using Visual Studio on Windows, you can generate VS solution files with `cmake -G "Visual Studio 16 2019" ..` (adjust for your version). +- For macOS, ensure you have Xcode command line tools installed. diff --git a/imgui_hello.cpp b/imgui_hello.cpp new file mode 100644 index 0000000..d07d342 --- /dev/null +++ b/imgui_hello.cpp @@ -0,0 +1,72 @@ +#include +#include +#include +#include +#include + +int main() { + // Initialize GLFW + if (!glfwInit()) { + std::cerr << "Failed to initialize GLFW" << std::endl; + return -1; + } + + // Create window + GLFWwindow* window = glfwCreateWindow(1280, 720, "Dear ImGui Hello World", nullptr, nullptr); + if (!window) { + std::cerr << "Failed to create GLFW window" << std::endl; + glfwTerminate(); + return -1; + } + glfwMakeContextCurrent(window); + glfwSwapInterval(1); // Enable vsync + + // Initialize ImGui + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + (void)io; + ImGui::StyleColorsDark(); + + // Initialize ImGui backends + ImGui_ImplGlfw_InitForOpenGL(window, true); + ImGui_ImplOpenGL3_Init("#version 130"); + + // Main loop + while (!glfwWindowShouldClose(window)) { + // Poll events + glfwPollEvents(); + + // Start ImGui frame + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + // Create a simple window + ImGui::Begin("Hello, World!"); + ImGui::Text("Welcome to Dear ImGui!"); + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + ImGui::End(); + + // Rendering + ImGui::Render(); + int display_w, display_h; + glfwGetFramebufferSize(window, &display_w, &display_h); + glViewport(0, 0, display_w, display_h); + glClearColor(0.45f, 0.55f, 0.60f, 1.00f); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + // Swap buffers + glfwSwapBuffers(window); + } + + // Cleanup + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + glfwDestroyWindow(window); + glfwTerminate(); + + return 0; +} diff --git a/online_game_tool.cpp b/online_game_tool.cpp new file mode 100644 index 0000000..38b12f0 --- /dev/null +++ b/online_game_tool.cpp @@ -0,0 +1,351 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "tcp_server.h" +#include "tcp/tcp_client.h" + +using boost::asio::ip::tcp; + +// Callback class for Steam Friends +class SteamFriendsCallbacks { +public: + SteamFriendsCallbacks() {} + STEAM_CALLBACK(SteamFriendsCallbacks, OnGameRichPresenceJoinRequested, GameRichPresenceJoinRequested_t); +}; + +// Global variables for Steam Networking +HSteamNetConnection g_hConnection = k_HSteamNetConnection_Invalid; +bool g_isConnected = false; +HSteamListenSocket hListenSock = k_HSteamListenSocket_Invalid; +ISteamNetworkingSockets* m_pInterface = nullptr; +bool forwarding = false; +std::unique_ptr server; + +// New variables for multiple connections and TCP clients +std::vector connections; +std::map clientMap; +std::mutex clientMutex; +int localPort = 0; +bool g_isHost = false; +bool g_isClient = false; + +// Callback function for connection status changes +void OnSteamNetConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t *pInfo) +{ + std::lock_guard lock(clientMutex); + std::cout << "Connection status changed: " << pInfo->m_info.m_eState << std::endl; + if (pInfo->m_eOldState == k_ESteamNetworkingConnectionState_None && pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_Connecting) + { + // Incoming connection, accept it + SteamNetworkingSockets()->AcceptConnection(pInfo->m_hConn); + connections.push_back(pInfo->m_hConn); + g_hConnection = pInfo->m_hConn; // Keep for backward compatibility if needed + g_isConnected = true; + std::cout << "Accepted incoming connection" << std::endl; + // Removed: Create TCP Client here - now lazy connect on first message + } + else if (pInfo->m_eOldState == k_ESteamNetworkingConnectionState_Connecting && pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_Connected) + { + // Client connected successfully + g_isConnected = true; + std::cout << "Connected to host" << std::endl; + } + else if (pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_ClosedByPeer || pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_ProblemDetectedLocally) + { + // Connection closed + g_isConnected = false; + g_hConnection = k_HSteamNetConnection_Invalid; + // Remove from connections + auto it = connections.begin(); + while (it != connections.end()) { + if (*it == pInfo->m_hConn) { + it = connections.erase(it); + } else { + ++it; + } + } + // Cleanup TCP Client + if (clientMap.count(pInfo->m_hConn)) { + clientMap[pInfo->m_hConn]->disconnect(); + delete clientMap[pInfo->m_hConn]; + clientMap.erase(pInfo->m_hConn); + std::cout << "Cleaned up TCP Client for connection" << std::endl; + } + std::cout << "Connection closed" << std::endl; + } +} + +void SteamFriendsCallbacks::OnGameRichPresenceJoinRequested(GameRichPresenceJoinRequested_t *pCallback) { + CSteamID hostSteamID = pCallback->m_steamIDFriend; + if (!g_isHost && !g_isConnected) { + g_isClient = true; + SteamNetworkingIdentity identity; + identity.SetSteamID(hostSteamID); + g_hConnection = m_pInterface->ConnectP2P(identity, 0, 0, nullptr); + if (g_hConnection != k_HSteamNetConnection_Invalid) { + std::cout << "Joined game room via invite from " << hostSteamID.ConvertToUint64() << std::endl; + // Start TCP Server + server = std::make_unique(8888); + if (!server->start()) { + std::cerr << "Failed to start TCP server" << std::endl; + } + } + } +} + +int main() { + // Initialize Steam API + if (!SteamAPI_Init()) { + std::cerr << "Failed to initialize Steam API" << std::endl; + return 1; + } + + // Initialize Steam Friends callbacks + SteamFriendsCallbacks steamFriendsCallbacks; + + // Initialize Steam Networking Sockets + SteamNetworkingUtils()->InitRelayNetworkAccess(); + + // Set global callback for connection status changes + SteamNetworkingUtils()->SetGlobalCallback_SteamNetConnectionStatusChanged(OnSteamNetConnectionStatusChanged); + + m_pInterface = SteamNetworkingSockets(); + + // Initialize GLFW + if (!glfwInit()) { + std::cerr << "Failed to initialize GLFW" << std::endl; + SteamAPI_Shutdown(); + return -1; + } + + // Create window + GLFWwindow* window = glfwCreateWindow(1280, 720, "在线游戏工具", nullptr, nullptr); + if (!window) { + std::cerr << "Failed to create GLFW window" << std::endl; + glfwTerminate(); + SteamAPI_Shutdown(); + return -1; + } + glfwMakeContextCurrent(window); + glfwSwapInterval(1); // Enable vsync + + // Initialize ImGui + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + (void)io; + // Load Chinese font + io.Fonts->AddFontFromFileTTF("font.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesChineseSimplifiedCommon()); + ImGui::StyleColorsDark(); + + // Initialize ImGui backends + ImGui_ImplGlfw_InitForOpenGL(window, true); + ImGui_ImplOpenGL3_Init("#version 130"); + + // TCP Client for local port forwarding + // Removed: TCPClient* client = nullptr; + // Removed: bool isLocalConnected = false; + + // Steam Networking variables + bool isHost = false; + bool isClient = false; + char joinBuffer[256] = ""; + char filterBuffer[256] = ""; + // Removed: char portBuffer[256] = ""; + + // Get friends list + std::vector> friendsList; + 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}); + } + + // Main loop + while (!glfwWindowShouldClose(window)) { + // Poll events + glfwPollEvents(); + + // Run Steam callbacks + SteamAPI_RunCallbacks(); + + // Poll networking + m_pInterface->RunCallbacks(); + + // Receive messages from Steam and forward to TCP server + { + std::lock_guard lock(clientMutex); + for (auto conn : connections) { + ISteamNetworkingMessage* pIncomingMsg = nullptr; + int numMsgs = m_pInterface->ReceiveMessagesOnConnection(conn, &pIncomingMsg, 1); + if (numMsgs > 0 && pIncomingMsg) { + // std::cout << "Received " << pIncomingMsg->m_cbSize << " bytes" << std::endl; + if (server) { + server->sendToAll((const char*)pIncomingMsg->m_pData, pIncomingMsg->m_cbSize); + } + // Lazy connect: Create TCP Client on first message if not already connected + if (clientMap.find(conn) == clientMap.end() && g_isHost && localPort > 0) { + TCPClient* client = new TCPClient("localhost", localPort); + if (client->connect()) { + client->setReceiveCallback([conn](const char* data, size_t size) { + std::lock_guard lock(clientMutex); + m_pInterface->SendMessageToConnection(conn, data, size, k_nSteamNetworkingSend_Reliable, nullptr); + }); + clientMap[conn] = client; + std::cout << "Created TCP Client for connection on first message" << std::endl; + } else { + std::cerr << "Failed to connect TCP Client for connection" << std::endl; + delete client; + } + } + // Send to corresponding TCP client if exists (for host) + if (clientMap.count(conn)) { + clientMap[conn]->send((const char*)pIncomingMsg->m_pData, pIncomingMsg->m_cbSize); + } + pIncomingMsg->Release(); + } + } + } + + // Start ImGui frame + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + // Create a window for online game tool + ImGui::Begin("在线游戏工具"); + if (server) { + ImGui::Text("TCP服务器监听端口8888"); + ImGui::Text("已连接客户端: %d", server->getClientCount()); + } + ImGui::Separator(); + + if (!g_isHost && !g_isConnected) { + if (ImGui::Button("主持游戏房间")) { + // Create listen socket + hListenSock = m_pInterface->CreateListenSocketP2P(0, 0, nullptr); + if (hListenSock != k_HSteamListenSocket_Invalid) { + g_isHost = true; + // Set Rich Presence + std::string connectStr = std::to_string(SteamUser()->GetSteamID().ConvertToUint64()); + SteamFriends()->SetRichPresence("connect", connectStr.c_str()); + SteamFriends()->SetRichPresence("status", "主持游戏房间"); + std::cout << "Hosting game room. Connection string: " << connectStr << std::endl; + } + } + ImGui::InputText("主机Steam ID", joinBuffer, IM_ARRAYSIZE(joinBuffer)); + if (ImGui::Button("加入游戏房间")) { + uint64 hostID = std::stoull(joinBuffer); + CSteamID hostSteamID(hostID); + g_isClient = true; + // Connect to host + SteamNetworkingIdentity identity; + identity.SetSteamID(hostSteamID); + g_hConnection = m_pInterface->ConnectP2P(identity, 0, 0, nullptr); + if (g_hConnection != k_HSteamNetConnection_Invalid) { + // Connection initiated, wait for callback to confirm + std::cout << "Connecting to host..." << std::endl; + // Start TCP Server + server = std::make_unique(8888); + if (!server->start()) { + std::cerr << "Failed to start TCP server" << std::endl; + } + } + } + } + if (g_isHost) { + ImGui::Text("正在主持游戏房间。邀请朋友!"); + ImGui::Separator(); + ImGui::InputInt("本地端口", &localPort); + ImGui::Separator(); + ImGui::InputText("过滤朋友", filterBuffer, IM_ARRAYSIZE(filterBuffer)); + ImGui::Text("朋友:"); + for (const auto& friendPair : friendsList) { + std::string nameStr = friendPair.second; + std::string filterStr(filterBuffer); + // Convert to lowercase for case-insensitive search + std::transform(nameStr.begin(), nameStr.end(), nameStr.begin(), ::tolower); + std::transform(filterStr.begin(), filterStr.end(), filterStr.begin(), ::tolower); + if (filterStr.empty() || nameStr.find(filterStr) != std::string::npos) { + ImGui::PushID(friendPair.first.ConvertToUint64()); + if (ImGui::Button(("邀请 " + friendPair.second).c_str())) { + // Send invite via Steam + SteamFriends()->InviteUserToGame(friendPair.first, "加入我的游戏房间!"); + } + ImGui::PopID(); + } + } + } + + ImGui::End(); + + // Room status window - only show when hosting or joined + if (g_isHost || g_isClient) { + ImGui::Begin("房间状态"); + if (server) { + ImGui::Text("房间内玩家: %d", server->getClientCount() + 1); // +1 for host + } + { + std::lock_guard lock(clientMutex); + ImGui::Text("连接的好友: %d", (int)connections.size()); + ImGui::Text("活跃的TCP客户端: %d", (int)clientMap.size()); + } + ImGui::End(); + } + + // Rendering + ImGui::Render(); + int display_w, display_h; + glfwGetFramebufferSize(window, &display_w, &display_h); + glViewport(0, 0, display_w, display_h); + glClearColor(0.45f, 0.55f, 0.60f, 1.00f); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + // Swap buffers + glfwSwapBuffers(window); + } + + // Cleanup + if (g_hConnection != k_HSteamNetConnection_Invalid) { + m_pInterface->CloseConnection(g_hConnection, 0, nullptr, false); + } + if (hListenSock != k_HSteamListenSocket_Invalid) { + m_pInterface->CloseListenSocket(hListenSock); + } + // Cleanup TCP Clients + { + std::lock_guard lock(clientMutex); + for (auto& pair : clientMap) { + pair.second->disconnect(); + delete pair.second; + } + clientMap.clear(); + } + if (server) { + server->stop(); + } + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + glfwDestroyWindow(window); + glfwTerminate(); + SteamAPI_Shutdown(); + + return 0; +} \ No newline at end of file diff --git a/p2p_chat.cpp b/p2p_chat.cpp new file mode 100644 index 0000000..2f2f903 --- /dev/null +++ b/p2p_chat.cpp @@ -0,0 +1,240 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Global variables for callbacks +HSteamNetConnection g_hConnection = k_HSteamNetConnection_Invalid; +bool g_isConnected = false; + +// Callback function for connection status changes +void OnSteamNetConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t *pInfo) +{ + std::cout << "Connection status changed: " << pInfo->m_info.m_eState << std::endl; + if (pInfo->m_eOldState == k_ESteamNetworkingConnectionState_None && pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_Connecting) + { + // Incoming connection, accept it + SteamNetworkingSockets()->AcceptConnection(pInfo->m_hConn); + g_hConnection = pInfo->m_hConn; + g_isConnected = true; + std::cout << "Accepted incoming connection" << std::endl; + } + else if (pInfo->m_eOldState == k_ESteamNetworkingConnectionState_Connecting && pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_Connected) + { + // Client connected successfully + g_isConnected = true; + std::cout << "Connected to host" << std::endl; + } + else if (pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_ClosedByPeer || pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_ProblemDetectedLocally) + { + // Connection closed + g_isConnected = false; + g_hConnection = k_HSteamNetConnection_Invalid; + std::cout << "Connection closed" << std::endl; + } +} + +int main() { + // Initialize Steam API + if (!SteamAPI_Init()) { + std::cerr << "Failed to initialize Steam API" << std::endl; + return 1; + } + + // Initialize Steam Networking Sockets + SteamNetworkingUtils()->InitRelayNetworkAccess(); + + // Set global callback for connection status changes + SteamNetworkingUtils()->SetGlobalCallback_SteamNetConnectionStatusChanged(OnSteamNetConnectionStatusChanged); + + // Initialize GLFW + if (!glfwInit()) { + std::cerr << "Failed to initialize GLFW" << std::endl; + SteamAPI_Shutdown(); + return -1; + } + + // Create window + GLFWwindow* window = glfwCreateWindow(1280, 720, "Steam P2P Chat", nullptr, nullptr); + if (!window) { + std::cerr << "Failed to create GLFW window" << std::endl; + glfwTerminate(); + SteamAPI_Shutdown(); + return -1; + } + glfwMakeContextCurrent(window); + glfwSwapInterval(1); // Enable vsync + + // Initialize ImGui + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + (void)io; + // Load Chinese font if available + io.Fonts->AddFontFromFileTTF("font.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesChineseSimplifiedCommon()); + ImGui::StyleColorsDark(); + + // Initialize ImGui backends + ImGui_ImplGlfw_InitForOpenGL(window, true); + ImGui_ImplOpenGL3_Init("#version 130"); + + // Steam Networking variables + HSteamListenSocket hListenSock = k_HSteamListenSocket_Invalid; + ISteamNetworkingSockets* m_pInterface = SteamNetworkingSockets(); + + // Chat variables + std::vector messages; + char inputBuffer[256] = ""; + CSteamID selectedFriend; + bool isHost = false; + bool isClient = false; + char filterBuffer[256] = ""; + + // Get friends list + std::vector friendsList; + int friendCount = SteamFriends()->GetFriendCount(k_EFriendFlagAll); + for (int i = 0; i < friendCount; ++i) { + CSteamID friendID = SteamFriends()->GetFriendByIndex(i, k_EFriendFlagAll); + friendsList.push_back(friendID); + } + + // Main loop + while (!glfwWindowShouldClose(window)) { + // Poll events + glfwPollEvents(); + + // Run Steam callbacks + SteamAPI_RunCallbacks(); + + // Poll networking + m_pInterface->RunCallbacks(); + + // Receive messages + if (g_isConnected) { + ISteamNetworkingMessage* pIncomingMsg = nullptr; + int numMsgs = m_pInterface->ReceiveMessagesOnConnection(g_hConnection, &pIncomingMsg, 1); + if (numMsgs > 0 && pIncomingMsg) { + std::string msg((char*)pIncomingMsg->m_pData, pIncomingMsg->m_cbSize); + messages.push_back("Friend: " + msg); + pIncomingMsg->Release(); + } + } + + // Start ImGui frame + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + // Main menu + ImGui::Begin("Steam P2P Chat"); + if (!isHost && !g_isConnected) { + if (ImGui::Button("Host Chat Room")) { + // Create listen socket + hListenSock = m_pInterface->CreateListenSocketP2P(0, 0, nullptr); + if (hListenSock != k_HSteamListenSocket_Invalid) { + isHost = true; + // Set Rich Presence + std::string connectStr = std::to_string(SteamUser()->GetSteamID().ConvertToUint64()); + SteamFriends()->SetRichPresence("connect", connectStr.c_str()); + SteamFriends()->SetRichPresence("status", "Hosting Chat Room"); + std::cout << "Hosting chat room. Connect string: " << connectStr << std::endl; + } + } + static char joinBuffer[256] = ""; + ImGui::InputText("Host Steam ID", joinBuffer, IM_ARRAYSIZE(joinBuffer)); + if (ImGui::Button("Join Chat Room")) { + uint64 hostID = std::stoull(joinBuffer); + CSteamID hostSteamID(hostID); + isClient = true; + // Connect to host + SteamNetworkingIdentity identity; + identity.SetSteamID(hostSteamID); + g_hConnection = m_pInterface->ConnectP2P(identity, 0, 0, nullptr); + if (g_hConnection != k_HSteamNetConnection_Invalid) { + // Connection initiated, wait for callback to confirm + std::cout << "Connecting to host..." << std::endl; + } + } + } + if (isHost) { + ImGui::Text("Hosting chat room. Invite friends!"); + ImGui::Separator(); + ImGui::InputText("Filter Friends", filterBuffer, IM_ARRAYSIZE(filterBuffer)); + ImGui::Text("Friends:"); + for (size_t i = 0; i < friendsList.size(); ++i) { + const char* name = SteamFriends()->GetFriendPersonaName(friendsList[i]); + std::string nameStr(name); + std::string filterStr(filterBuffer); + // Convert to lowercase for case-insensitive search + std::transform(nameStr.begin(), nameStr.end(), nameStr.begin(), ::tolower); + std::transform(filterStr.begin(), filterStr.end(), filterStr.begin(), ::tolower); + if (filterStr.empty() || nameStr.find(filterStr) != std::string::npos) { + if (ImGui::Button((std::string("Invite ") + name).c_str())) { + SteamFriends()->InviteUserToGame(friendsList[i], ""); + } + } + } + } + ImGui::End(); + + // Chat window + if (g_isConnected) { + ImGui::Begin("Chat Room"); + ImGui::Text("Chatting"); + + // Display messages + ImGui::BeginChild("Messages", ImVec2(0, -ImGui::GetFrameHeightWithSpacing() - 30), true); + for (const auto& msg : messages) { + ImGui::TextWrapped("%s", msg.c_str()); + } + ImGui::EndChild(); + + // Input + if (ImGui::InputText("Message", inputBuffer, IM_ARRAYSIZE(inputBuffer), ImGuiInputTextFlags_EnterReturnsTrue)) { + if (strlen(inputBuffer) > 0) { + uint32 msgSize = static_cast(strlen(inputBuffer) + 1); + m_pInterface->SendMessageToConnection(g_hConnection, inputBuffer, msgSize, k_nSteamNetworkingSend_Reliable, nullptr); + messages.push_back("You: " + std::string(inputBuffer)); + memset(inputBuffer, 0, sizeof(inputBuffer)); + } + } + ImGui::End(); + } + + // Rendering + ImGui::Render(); + int display_w, display_h; + glfwGetFramebufferSize(window, &display_w, &display_h); + glViewport(0, 0, display_w, display_h); + glClearColor(0.45f, 0.55f, 0.60f, 1.00f); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + // Swap buffers + glfwSwapBuffers(window); + } + + // Cleanup + if (g_hConnection != k_HSteamNetConnection_Invalid) { + m_pInterface->CloseConnection(g_hConnection, 0, nullptr, false); + } + if (hListenSock != k_HSteamListenSocket_Invalid) { + m_pInterface->CloseListenSocket(hListenSock); + } + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + glfwDestroyWindow(window); + glfwTerminate(); + SteamAPI_Shutdown(); + + return 0; +} \ No newline at end of file diff --git a/steam_friends.cpp b/steam_friends.cpp new file mode 100644 index 0000000..85f03ab --- /dev/null +++ b/steam_friends.cpp @@ -0,0 +1,103 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +int main() { + // Initialize Steam API + if (!SteamAPI_Init()) { + std::cerr << "Failed to initialize Steam API" << std::endl; + return 1; + } + + // Initialize GLFW + if (!glfwInit()) { + std::cerr << "Failed to initialize GLFW" << std::endl; + SteamAPI_Shutdown(); + return -1; + } + + // Create window + GLFWwindow* window = glfwCreateWindow(1280, 720, "Steam Friends List", nullptr, nullptr); + if (!window) { + std::cerr << "Failed to create GLFW window" << std::endl; + glfwTerminate(); + SteamAPI_Shutdown(); + return -1; + } + glfwMakeContextCurrent(window); + glfwSwapInterval(1); // Enable vsync + + // Initialize ImGui + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + (void)io; + // Load Chinese font + io.Fonts->AddFontFromFileTTF("font.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesChineseSimplifiedCommon()); + ImGui::StyleColorsDark(); + + // Initialize ImGui backends + ImGui_ImplGlfw_InitForOpenGL(window, true); + ImGui_ImplOpenGL3_Init("#version 130"); + + // Get friends list + std::vector friendsList; + 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(std::string("Friend ") + std::to_string(i + 1) + ": " + name); + } + + // Main loop + while (!glfwWindowShouldClose(window)) { + // Poll events + glfwPollEvents(); + + // Start ImGui frame + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + // Create a window for friends list + ImGui::Begin("Steam Friends List"); + ImGui::Text("Number of friends: %d", friendCount); + ImGui::Separator(); + if (friendCount > 0) { + ImGui::Columns(4, nullptr, true); // 4 columns, with borders + for (const auto& friendName : friendsList) { + ImGui::Text("%s", friendName.c_str()); + ImGui::NextColumn(); + } + ImGui::Columns(1); // Reset to 1 column + } + ImGui::End(); + + // Rendering + ImGui::Render(); + int display_w, display_h; + glfwGetFramebufferSize(window, &display_w, &display_h); + glViewport(0, 0, display_w, display_h); + glClearColor(0.45f, 0.55f, 0.60f, 1.00f); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + // Swap buffers + glfwSwapBuffers(window); + } + + // Cleanup + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + glfwDestroyWindow(window); + glfwTerminate(); + SteamAPI_Shutdown(); + + return 0; +} \ No newline at end of file diff --git a/tcp/tcp_client.cpp b/tcp/tcp_client.cpp new file mode 100644 index 0000000..44a6c93 --- /dev/null +++ b/tcp/tcp_client.cpp @@ -0,0 +1,81 @@ +#include "tcp_client.h" +#include + +TCPClient::TCPClient(const std::string& host, int port) : host_(host), port_(port), connected_(false), socket_(std::make_shared(io_context_)), work_(boost::asio::make_work_guard(io_context_)), buffer_(1024) {} + +TCPClient::~TCPClient() { disconnect(); } + +bool TCPClient::connect() { + try { + tcp::resolver resolver(io_context_); + auto endpoints = resolver.resolve(host_, std::to_string(port_)); + boost::asio::connect(*socket_, endpoints); + connected_ = true; + clientThread_ = std::thread([this]() { + std::cout << "Client thread started" << std::endl; + io_context_.run(); + std::cout << "Client thread stopped" << std::endl; + }); + start_read(); + std::cout << "Connected to " << host_ << ":" << port_ << std::endl; + return true; + } catch (const std::exception& e) { + std::cerr << "Failed to connect: " << e.what() << std::endl; + return false; + } +} + +void TCPClient::disconnect() { + connected_ = false; + io_context_.stop(); + if (clientThread_.joinable()) { + clientThread_.join(); + } + if (socket_->is_open()) { + socket_->close(); + } +} + +void TCPClient::send(const std::string& message) { + send(message.c_str(), message.size()); +} + +void TCPClient::send(const char* data, size_t size) { + if (!connected_) return; + // std::cout << "Sending " << size << " bytes" << std::endl; + boost::asio::async_write(*socket_, boost::asio::buffer(data, size), [](const boost::system::error_code& error, std::size_t) { + if (error) { + std::cerr << "Send failed: " << error.message() << std::endl; + } + }); +} + +void TCPClient::setReceiveCallback(std::function callback) { + receiveCallback_ = callback; +} + +void TCPClient::setReceiveCallback(std::function callback) { + receiveCallbackBytes_ = callback; +} + +void TCPClient::start_read() { + socket_->async_read_some(boost::asio::buffer(buffer_), [this](const boost::system::error_code& error, std::size_t bytes_transferred) { + handle_read(error, bytes_transferred); + }); +} + +void TCPClient::handle_read(const boost::system::error_code& error, std::size_t bytes_transferred) { + if (!error) { + // std::cout << "Received " << bytes_transferred << " bytes" << std::endl; + if (receiveCallbackBytes_) { + receiveCallbackBytes_(buffer_.data(), bytes_transferred); + } else if (receiveCallback_) { + std::string message(buffer_.data(), bytes_transferred); + receiveCallback_(message); + } + start_read(); + } else { + std::cerr << "Read failed: " << error.message() << std::endl; + disconnect(); + } +} \ No newline at end of file diff --git a/tcp/tcp_client.h b/tcp/tcp_client.h new file mode 100644 index 0000000..25eafa0 --- /dev/null +++ b/tcp/tcp_client.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +using boost::asio::ip::tcp; + +// TCP Client class +class TCPClient { +public: + TCPClient(const std::string& host, int port); + ~TCPClient(); + + bool connect(); + void disconnect(); + void send(const std::string& message); + void send(const char* data, size_t size); + void setReceiveCallback(std::function callback); + void setReceiveCallback(std::function callback); + +private: + void start_read(); + void handle_read(const boost::system::error_code& error, std::size_t bytes_transferred); + + std::string host_; + int port_; + bool connected_; + boost::asio::io_context io_context_; + boost::asio::executor_work_guard work_; + std::shared_ptr socket_; + std::thread clientThread_; + std::mutex socketMutex_; + std::function receiveCallback_; + std::function receiveCallbackBytes_; + std::vector buffer_; +}; \ No newline at end of file diff --git a/tcp/tcp_server.cpp b/tcp/tcp_server.cpp new file mode 100644 index 0000000..e506d89 --- /dev/null +++ b/tcp/tcp_server.cpp @@ -0,0 +1,97 @@ +#include "tcp_server.h" +#include +#include + +TCPServer::TCPServer(int port) : port_(port), running_(false), acceptor_(io_context_), work_(boost::asio::make_work_guard(io_context_)) {} + +TCPServer::~TCPServer() { stop(); } + +bool TCPServer::start() { + try { + tcp::endpoint endpoint(tcp::v4(), port_); + acceptor_.open(endpoint.protocol()); + acceptor_.set_option(tcp::acceptor::reuse_address(true)); + acceptor_.bind(endpoint); + acceptor_.listen(); + + running_ = true; + serverThread_ = std::thread([this]() { + std::cout << "Server thread started" << std::endl; + io_context_.run(); + std::cout << "Server thread stopped" << std::endl; + }); + start_accept(); + std::cout << "TCP server started on port " << port_ << std::endl; + return true; + } catch (const std::exception& e) { + std::cerr << "Failed to start TCP server: " << e.what() << std::endl; + return false; + } +} + +void TCPServer::stop() { + running_ = false; + io_context_.stop(); + if (serverThread_.joinable()) { + serverThread_.join(); + } + acceptor_.close(); +} + +void TCPServer::sendToAll(const std::string& message, std::shared_ptr excludeSocket) { + sendToAll(message.c_str(), message.size(), excludeSocket); +} + +void TCPServer::sendToAll(const char* data, size_t size, std::shared_ptr excludeSocket) { + std::lock_guard lock(clientsMutex_); + for (auto& client : clients_) { + if (client != excludeSocket) { + boost::asio::async_write(*client, boost::asio::buffer(data, size), [](const boost::system::error_code&, std::size_t) {}); + } + } +} + +int TCPServer::getClientCount() { + std::lock_guard lock(clientsMutex_); + return clients_.size(); +} + +void TCPServer::start_accept() { + auto socket = std::make_shared(io_context_); + acceptor_.async_accept(*socket, [this, socket](const boost::system::error_code& error) { + if (!error) { + std::cout << "New client connected" << std::endl; + { + std::lock_guard lock(clientsMutex_); + clients_.push_back(socket); + } + start_read(socket); + } + if (running_) { + start_accept(); + } + }); +} + +void TCPServer::start_read(std::shared_ptr socket) { + auto buffer = std::make_shared>(1024); + socket->async_read_some(boost::asio::buffer(*buffer), [this, socket, buffer](const boost::system::error_code& error, std::size_t bytes_transferred) { + if (!error) { + // std::cout << "Received " << bytes_transferred << " bytes from client" << std::endl; + if (!forwarding) { + forwarding = true; + if (g_isConnected) { + m_pInterface->SendMessageToConnection(g_hConnection, buffer->data(), bytes_transferred, k_nSteamNetworkingSend_Reliable, nullptr); + } + forwarding = false; + } + sendToAll(buffer->data(), bytes_transferred, socket); + start_read(socket); + } else { + std::cout << "Client disconnected" << std::endl; + // Remove client + std::lock_guard lock(clientsMutex_); + clients_.erase(std::remove(clients_.begin(), clients_.end(), socket), clients_.end()); + } + }); +} \ No newline at end of file diff --git a/tcp/tcp_server.h b/tcp/tcp_server.h new file mode 100644 index 0000000..d9a0108 --- /dev/null +++ b/tcp/tcp_server.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using boost::asio::ip::tcp; + +// Extern declarations for global variables used in TCPServer +extern HSteamNetConnection g_hConnection; +extern bool g_isConnected; +extern ISteamNetworkingSockets* m_pInterface; +extern bool forwarding; + +// TCP Server class +class TCPServer { +public: + TCPServer(int port); + ~TCPServer(); + + bool start(); + void stop(); + void sendToAll(const std::string& message, std::shared_ptr excludeSocket = nullptr); + void sendToAll(const char* data, size_t size, std::shared_ptr excludeSocket = nullptr); + int getClientCount(); + +private: + void start_accept(); + void start_read(std::shared_ptr socket); + + int port_; + bool running_; + boost::asio::io_context io_context_; + boost::asio::executor_work_guard work_; + tcp::acceptor acceptor_; + std::vector> clients_; + std::mutex clientsMutex_; + std::thread serverThread_; +}; \ No newline at end of file