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.
This commit is contained in:
Ayndpa
2025-11-17 20:41:03 +08:00
commit 0f76252c16
12 changed files with 1230 additions and 0 deletions

138
ConnectTool.vcxproj Normal file
View File

@@ -0,0 +1,138 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>18.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{71a90be8-b7ae-4d02-a50d-93b6f4770ad8}</ProjectGuid>
<RootNamespace>ConnectTool</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v145</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v145</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v145</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v145</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared" >
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup></ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="源文件">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="头文件">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="资源文件">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
</Project>

4
ConnectTool.vcxproj.user Normal file
View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup />
</Project>

42
README.md Normal file
View File

@@ -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.

72
imgui_hello.cpp Normal file
View File

@@ -0,0 +1,72 @@
#include <GLFW/glfw3.h>
#include <imgui.h>
#include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h>
#include <iostream>
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;
}

351
online_game_tool.cpp Normal file
View File

@@ -0,0 +1,351 @@
#include <GLFW/glfw3.h>
#include <imgui.h>
#include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h>
#include <iostream>
#include <vector>
#include <string>
#include <thread>
#include <mutex>
#include <map>
#include <algorithm>
#include <steam_api.h>
#include <isteamnetworkingsockets.h>
#include <isteamnetworkingutils.h>
#include <steamnetworkingtypes.h>
#include <boost/asio.hpp>
#include <memory>
#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<TCPServer> server;
// New variables for multiple connections and TCP clients
std::vector<HSteamNetConnection> connections;
std::map<HSteamNetConnection, TCPClient*> 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<std::mutex> 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<TCPServer>(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<std::pair<CSteamID, std::string>> 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<std::mutex> 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<std::mutex> 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<TCPServer>(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<std::mutex> 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<std::mutex> 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;
}

240
p2p_chat.cpp Normal file
View File

@@ -0,0 +1,240 @@
#include <GLFW/glfw3.h>
#include <imgui.h>
#include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h>
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <steam_api.h>
#include <isteamnetworkingsockets.h>
#include <isteamnetworkingutils.h>
#include <steamnetworkingtypes.h>
// 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<std::string> messages;
char inputBuffer[256] = "";
CSteamID selectedFriend;
bool isHost = false;
bool isClient = false;
char filterBuffer[256] = "";
// Get friends list
std::vector<CSteamID> 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<uint32>(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;
}

103
steam_friends.cpp Normal file
View File

@@ -0,0 +1,103 @@
#include <GLFW/glfw3.h>
#include <imgui.h>
#include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h>
#include <iostream>
#include <vector>
#include <string>
#include <steam_api.h>
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<std::string> 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;
}

81
tcp/tcp_client.cpp Normal file
View File

@@ -0,0 +1,81 @@
#include "tcp_client.h"
#include <iostream>
TCPClient::TCPClient(const std::string& host, int port) : host_(host), port_(port), connected_(false), socket_(std::make_shared<tcp::socket>(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<void(const std::string&)> callback) {
receiveCallback_ = callback;
}
void TCPClient::setReceiveCallback(std::function<void(const char*, size_t)> 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();
}
}

40
tcp/tcp_client.h Normal file
View File

@@ -0,0 +1,40 @@
#pragma once
#include <boost/asio.hpp>
#include <memory>
#include <string>
#include <thread>
#include <mutex>
#include <functional>
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<void(const std::string&)> callback);
void setReceiveCallback(std::function<void(const char*, size_t)> 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<boost::asio::io_context::executor_type> work_;
std::shared_ptr<tcp::socket> socket_;
std::thread clientThread_;
std::mutex socketMutex_;
std::function<void(const std::string&)> receiveCallback_;
std::function<void(const char*, size_t)> receiveCallbackBytes_;
std::vector<char> buffer_;
};

97
tcp/tcp_server.cpp Normal file
View File

@@ -0,0 +1,97 @@
#include "tcp_server.h"
#include <iostream>
#include <algorithm>
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<tcp::socket> excludeSocket) {
sendToAll(message.c_str(), message.size(), excludeSocket);
}
void TCPServer::sendToAll(const char* data, size_t size, std::shared_ptr<tcp::socket> excludeSocket) {
std::lock_guard<std::mutex> 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<std::mutex> lock(clientsMutex_);
return clients_.size();
}
void TCPServer::start_accept() {
auto socket = std::make_shared<tcp::socket>(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<std::mutex> lock(clientsMutex_);
clients_.push_back(socket);
}
start_read(socket);
}
if (running_) {
start_accept();
}
});
}
void TCPServer::start_read(std::shared_ptr<tcp::socket> socket) {
auto buffer = std::make_shared<std::vector<char>>(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<std::mutex> lock(clientsMutex_);
clients_.erase(std::remove(clients_.begin(), clients_.end(), socket), clients_.end());
}
});
}

45
tcp/tcp_server.h Normal file
View File

@@ -0,0 +1,45 @@
#pragma once
#include <boost/asio.hpp>
#include <memory>
#include <vector>
#include <string>
#include <thread>
#include <mutex>
#include <isteamnetworkingsockets.h>
#include <isteamnetworkingutils.h>
#include <steamnetworkingtypes.h>
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<tcp::socket> excludeSocket = nullptr);
void sendToAll(const char* data, size_t size, std::shared_ptr<tcp::socket> excludeSocket = nullptr);
int getClientCount();
private:
void start_accept();
void start_read(std::shared_ptr<tcp::socket> socket);
int port_;
bool running_;
boost::asio::io_context io_context_;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> work_;
tcp::acceptor acceptor_;
std::vector<std::shared_ptr<tcp::socket>> clients_;
std::mutex clientsMutex_;
std::thread serverThread_;
};