Compare commits

9 Commits

Author SHA1 Message Date
Toukaiteio
62a1e1b965 chore: 更新.gitignore和CMakeLists.txt 2025-11-28 13:10:58 +08:00
Daiyosei
140d5bf348 Add MIT License to the project
1
2025-11-28 12:25:21 +08:00
Toukaiteio
9fc0a73c7c build(CMake): 添加nanoid支持并改进Steam API链接逻辑
添加nanoid_cpp头文件路径到CMake配置
重构Steam API链接逻辑以支持多平台
添加steam_id.txt生成命令
调整编码添加了BOM头
2025-11-28 09:22:59 +08:00
Toukaiteio
91e4198f8a add .gitignore 2025-11-28 08:33:24 +08:00
YoungUsing
cf04905577 Delete .DS_Store 2025-11-27 22:42:42 +08:00
Ji Fuyao
c0bc7720c9 Merge pull request #4 from liu5580/main
添加CMakeLists.txt,修复macOS26编译运行openGL版本不支持错误
2025-11-27 15:16:16 +08:00
liu5580
3430871fbf 添加CMakeLists.txt,修复macOS26编译运行openGL版本不支持错误 2025-11-27 15:14:18 +08:00
Ji Fuyao
6897466539 Update README with QQ discussion group details
Add QQ discussion group information to README
2025-11-24 10:18:01 +08:00
Ji Fuyao
9691dc36fd Add QQ discussion group information to README 2025-11-24 10:17:49 +08:00
8 changed files with 608 additions and 465 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
build/
.DS_Store
.claude/
steamworks/

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "imgui"]
path = imgui
url = https://github.com/ocornut/imgui.git

145
CMakeLists.txt Normal file
View File

@@ -0,0 +1,145 @@
cmake_minimum_required(VERSION 3.10)
project(ConnectTool)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Find packages
find_package(OpenGL REQUIRED)
find_package(glfw3 REQUIRED)
find_package(Boost REQUIRED)
# Include directories
include_directories(${CMAKE_SOURCE_DIR})
include_directories(${CMAKE_SOURCE_DIR}/imgui)
include_directories(${CMAKE_SOURCE_DIR}/imgui/backends)
include_directories(${CMAKE_SOURCE_DIR}/nanoid_cpp/inc)
include_directories(${CMAKE_SOURCE_DIR}/steamworks/public)
include_directories(${CMAKE_SOURCE_DIR}/steamworks/public/steam)
include_directories(${CMAKE_SOURCE_DIR}/net)
# Source files
file(GLOB SOURCES
"online_game_tool.cpp"
"imgui/*.cpp"
"imgui/backends/imgui_impl_glfw.cpp"
"imgui/backends/imgui_impl_opengl3.cpp"
"net/*.cpp"
"steam/*.cpp"
"nanoid_cpp/src/nanoid/*.cpp"
)
# Create executable
add_executable(ConnectTool ${SOURCES})
# Determine Steamworks directory location with fallback
set(STEAMWORKS_DIR "${CMAKE_SOURCE_DIR}/steamworks")
set(STEAMWORKS_SDK_DIR "${CMAKE_SOURCE_DIR}/sdk")
# Function to find Steam API library with fallback
function(find_steam_api OUTPUT_VAR PLATFORM_PATH)
set(STEAM_API_PATH "${STEAMWORKS_DIR}/redistributable_bin/${PLATFORM_PATH}")
set(STEAM_API_SDK_PATH "${STEAMWORKS_SDK_DIR}/redistributable_bin/${PLATFORM_PATH}")
if(EXISTS "${STEAM_API_PATH}")
set(${OUTPUT_VAR} "${STEAM_API_PATH}" PARENT_SCOPE)
elseif(EXISTS "${STEAM_API_SDK_PATH}")
set(${OUTPUT_VAR} "${STEAM_API_SDK_PATH}" PARENT_SCOPE)
message(STATUS "Using Steam API from sdk directory: ${STEAM_API_SDK_PATH}")
else()
set(${OUTPUT_VAR} "${STEAM_API_PATH}" PARENT_SCOPE)
message(WARNING "Steam API library not found at: ${STEAM_API_PATH} or ${STEAM_API_SDK_PATH}")
endif()
endfunction()
# Platform-specific Steam API linking
if(WIN32)
# Windows 64-bit
find_steam_api(STEAM_API_LIB "win64/steam_api64.dll")
target_link_libraries(ConnectTool
glfw
OpenGL::GL
Boost::headers
${STEAM_API_LIB}
)
# Copy steam_api64.dll to output directory for runtime
add_custom_command(TARGET ConnectTool POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${STEAM_API_LIB}
$<TARGET_FILE_DIR:ConnectTool>/steam_api64.dll
)
elseif(UNIX AND NOT APPLE)
# Linux platforms
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64" OR CMAKE_SYSTEM_PROCESSOR MATCHES "amd64")
# x86_64 Linux
find_steam_api(STEAM_API_LIB "linux64/libsteam_api.so")
target_link_libraries(ConnectTool
glfw
OpenGL::GL
Boost::headers
${STEAM_API_LIB}
)
add_custom_command(TARGET ConnectTool POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${STEAM_API_LIB}
$<TARGET_FILE_DIR:ConnectTool>/libsteam_api.so
)
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "i386" OR CMAKE_SYSTEM_PROCESSOR MATCHES "i686")
# x86 32-bit Linux
find_steam_api(STEAM_API_LIB "linux32/libsteam_api.so")
target_link_libraries(ConnectTool
glfw
OpenGL::GL
Boost::headers
${STEAM_API_LIB}
)
add_custom_command(TARGET ConnectTool POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${STEAM_API_LIB}
$<TARGET_FILE_DIR:ConnectTool>/libsteam_api.so
)
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64" OR CMAKE_SYSTEM_PROCESSOR MATCHES "arm64")
# ARM64 Linux
find_steam_api(STEAM_API_LIB "linuxarm64/libsteam_api.so")
target_link_libraries(ConnectTool
glfw
OpenGL::GL
Boost::headers
${STEAM_API_LIB}
)
add_custom_command(TARGET ConnectTool POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${STEAM_API_LIB}
$<TARGET_FILE_DIR:ConnectTool>/libsteam_api.so
)
else()
message(WARNING "Unsupported Linux architecture: ${CMAKE_SYSTEM_PROCESSOR}")
endif()
elseif(APPLE)
# macOS
find_steam_api(STEAM_API_LIB "osx/libsteam_api.dylib")
target_link_libraries(ConnectTool
glfw
OpenGL::GL
Boost::headers
${STEAM_API_LIB}
)
# Copy libsteam_api.dylib to output directory for runtime
add_custom_command(TARGET ConnectTool POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${STEAM_API_LIB}
$<TARGET_FILE_DIR:ConnectTool>/libsteam_api.dylib
)
else()
message(WARNING "Unsupported platform")
endif()
# Create steam_id.txt file with content 480
add_custom_command(TARGET ConnectTool POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "480" > $<TARGET_FILE_DIR:ConnectTool>/steam_appid.txt
)

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Ji Fuyao
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,5 +1,7 @@
# ConnectTool - 在线游戏联机工具
QQ讨论群616325806
基于 Steam 网络的在线游戏联机工具,支持创建和加入游戏房间,提供 P2P 网络连接和 TCP 端口转发功能。使用 Dear ImGui 构建图形界面。
## 功能特性

1
imgui Submodule

Submodule imgui added at 620a33dd85

1
nanoid_cpp Submodule

Submodule nanoid_cpp added at 9105e0f388

View File

@@ -1,32 +1,32 @@
#include "steam/steam_networking_manager.h"
#include "steam/steam_room_manager.h"
#include "steam/steam_utils.h"
#include "tcp_server.h"
#include <GLFW/glfw3.h>
#include <algorithm>
#include <boost/asio.hpp>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <imgui.h>
#include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h>
#include <iostream>
#include <vector>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <mutex>
#include <map>
#include <algorithm>
#include <cstring>
#include <boost/asio.hpp>
#include <memory>
#include <fstream>
#include <filesystem>
#include "tcp_server.h"
#include "steam/steam_networking_manager.h"
#include "steam/steam_room_manager.h"
#include "steam/steam_utils.h"
#include <vector>
#ifdef _WIN32
#include <windows.h>
#define GLFW_EXPOSE_NATIVE_WIN32
#include <GLFW/glfw3native.h>
#else
#include <signal.h>
#include <sys/file.h>
#include <unistd.h>
#include <signal.h>
#endif
using boost::asio::ip::tcp;
@@ -41,71 +41,64 @@ std::unique_ptr<TCPServer> server;
// Windows implementation using mutex and shared memory
HANDLE g_hMutex = nullptr;
HANDLE g_hMapFile = nullptr;
HWND* g_pSharedHwnd = nullptr;
HWND *g_pSharedHwnd = nullptr;
bool checkSingleInstance()
{
g_hMutex = CreateMutexW(nullptr, FALSE, L"Global\\OnlineGameTool_SingleInstance_Mutex");
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
// Another instance exists, try to find and activate it
g_hMapFile = OpenFileMappingW(FILE_MAP_READ, FALSE, L"Global\\OnlineGameTool_HWND_Share");
if (g_hMapFile != nullptr)
{
HWND* pHwnd = (HWND*)MapViewOfFile(g_hMapFile, FILE_MAP_READ, 0, 0, sizeof(HWND));
if (pHwnd != nullptr && *pHwnd != nullptr && IsWindow(*pHwnd))
{
// Restore and bring to front
if (IsIconic(*pHwnd))
{
ShowWindow(*pHwnd, SW_RESTORE);
}
SetForegroundWindow(*pHwnd);
UnmapViewOfFile(pHwnd);
}
CloseHandle(g_hMapFile);
bool checkSingleInstance() {
g_hMutex = CreateMutexW(nullptr, FALSE,
L"Global\\OnlineGameTool_SingleInstance_Mutex");
if (GetLastError() == ERROR_ALREADY_EXISTS) {
// Another instance exists, try to find and activate it
g_hMapFile = OpenFileMappingW(FILE_MAP_READ, FALSE,
L"Global\\OnlineGameTool_HWND_Share");
if (g_hMapFile != nullptr) {
HWND *pHwnd =
(HWND *)MapViewOfFile(g_hMapFile, FILE_MAP_READ, 0, 0, sizeof(HWND));
if (pHwnd != nullptr && *pHwnd != nullptr && IsWindow(*pHwnd)) {
// Restore and bring to front
if (IsIconic(*pHwnd)) {
ShowWindow(*pHwnd, SW_RESTORE);
}
if (g_hMutex)
{
CloseHandle(g_hMutex);
}
return false;
SetForegroundWindow(*pHwnd);
UnmapViewOfFile(pHwnd);
}
CloseHandle(g_hMapFile);
}
if (g_hMutex) {
CloseHandle(g_hMutex);
}
return false;
}
// Create shared memory for HWND
g_hMapFile = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, sizeof(HWND), L"Global\\OnlineGameTool_HWND_Share");
if (g_hMapFile != nullptr)
{
g_pSharedHwnd = (HWND*)MapViewOfFile(g_hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(HWND));
}
return true;
// Create shared memory for HWND
g_hMapFile =
CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0,
sizeof(HWND), L"Global\\OnlineGameTool_HWND_Share");
if (g_hMapFile != nullptr) {
g_pSharedHwnd = (HWND *)MapViewOfFile(g_hMapFile, FILE_MAP_ALL_ACCESS, 0, 0,
sizeof(HWND));
}
return true;
}
void storeWindowHandle(GLFWwindow* window)
{
if (g_pSharedHwnd != nullptr)
{
*g_pSharedHwnd = glfwGetWin32Window(window);
}
void storeWindowHandle(GLFWwindow *window) {
if (g_pSharedHwnd != nullptr) {
*g_pSharedHwnd = glfwGetWin32Window(window);
}
}
void cleanupSingleInstance()
{
if (g_pSharedHwnd != nullptr)
{
UnmapViewOfFile(g_pSharedHwnd);
g_pSharedHwnd = nullptr;
}
if (g_hMapFile != nullptr)
{
CloseHandle(g_hMapFile);
g_hMapFile = nullptr;
}
if (g_hMutex != nullptr)
{
CloseHandle(g_hMutex);
g_hMutex = nullptr;
}
void cleanupSingleInstance() {
if (g_pSharedHwnd != nullptr) {
UnmapViewOfFile(g_pSharedHwnd);
g_pSharedHwnd = nullptr;
}
if (g_hMapFile != nullptr) {
CloseHandle(g_hMapFile);
g_hMapFile = nullptr;
}
if (g_hMutex != nullptr) {
CloseHandle(g_hMutex);
g_hMutex = nullptr;
}
}
#else
@@ -113,414 +106,387 @@ void cleanupSingleInstance()
int g_lockfd = -1;
std::string g_lockFilePath;
void signalHandler(int signum)
{
// Signal received to bring window to front
std::cout << "Received signal to activate window" << std::endl;
void signalHandler(int signum) {
// Signal received to bring window to front
std::cout << "Received signal to activate window" << std::endl;
}
bool checkSingleInstance()
{
std::string tempDir;
bool checkSingleInstance() {
std::string tempDir;
#ifdef __APPLE__
const char* tmpdir = getenv("TMPDIR");
tempDir = tmpdir ? tmpdir : "/tmp";
const char *tmpdir = getenv("TMPDIR");
tempDir = tmpdir ? tmpdir : "/tmp";
#else
tempDir = "/tmp";
tempDir = "/tmp";
#endif
g_lockFilePath = tempDir + "/OnlineGameTool.lock";
g_lockFilePath = tempDir + "/OnlineGameTool.lock";
g_lockfd = open(g_lockFilePath.c_str(), O_CREAT | O_RDWR, 0666);
if (g_lockfd < 0)
{
std::cerr << "Failed to open lock file" << std::endl;
return false;
g_lockfd = open(g_lockFilePath.c_str(), O_CREAT | O_RDWR, 0666);
if (g_lockfd < 0) {
std::cerr << "Failed to open lock file" << std::endl;
return false;
}
// Try to acquire exclusive lock
if (flock(g_lockfd, LOCK_EX | LOCK_NB) != 0) {
// Lock failed, another instance is running
// Read PID and send signal
char pidBuf[32];
ssize_t bytesRead = read(g_lockfd, pidBuf, sizeof(pidBuf) - 1);
if (bytesRead > 0) {
pidBuf[bytesRead] = '\0';
pid_t existingPid = atoi(pidBuf);
if (existingPid > 0) {
// Send SIGUSR1 to existing instance
kill(existingPid, SIGUSR1);
}
}
close(g_lockfd);
g_lockfd = -1;
return false;
}
// Try to acquire exclusive lock
if (flock(g_lockfd, LOCK_EX | LOCK_NB) != 0)
{
// Lock failed, another instance is running
// Read PID and send signal
char pidBuf[32];
ssize_t bytesRead = read(g_lockfd, pidBuf, sizeof(pidBuf) - 1);
if (bytesRead > 0)
{
pidBuf[bytesRead] = '\0';
pid_t existingPid = atoi(pidBuf);
if (existingPid > 0)
{
// Send SIGUSR1 to existing instance
kill(existingPid, SIGUSR1);
}
}
close(g_lockfd);
g_lockfd = -1;
return false;
}
// Write our PID to the lock file
ftruncate(g_lockfd, 0);
pid_t myPid = getpid();
std::string pidStr = std::to_string(myPid);
write(g_lockfd, pidStr.c_str(), pidStr.length());
// Write our PID to the lock file
ftruncate(g_lockfd, 0);
pid_t myPid = getpid();
std::string pidStr = std::to_string(myPid);
write(g_lockfd, pidStr.c_str(), pidStr.length());
// Set up signal handler
signal(SIGUSR1, signalHandler);
// Set up signal handler
signal(SIGUSR1, signalHandler);
return true;
return true;
}
void storeWindowHandle(GLFWwindow* window)
{
// GLFW doesn't provide a standard way to bring window to front on Unix
// but we can request attention
glfwRequestWindowAttention(window);
void storeWindowHandle(GLFWwindow *window) {
// GLFW doesn't provide a standard way to bring window to front on Unix
// but we can request attention
glfwRequestWindowAttention(window);
}
void cleanupSingleInstance()
{
if (g_lockfd >= 0)
{
flock(g_lockfd, LOCK_UN);
close(g_lockfd);
g_lockfd = -1;
unlink(g_lockFilePath.c_str());
}
void cleanupSingleInstance() {
if (g_lockfd >= 0) {
flock(g_lockfd, LOCK_UN);
close(g_lockfd);
g_lockfd = -1;
unlink(g_lockFilePath.c_str());
}
}
#endif
int main()
{
// Check for single instance
if (!checkSingleInstance())
{
std::cout << "另一个实例已在运行,正在激活该窗口..." << std::endl;
return 0;
}
// Initialize Steam API first
if (!SteamAPI_Init())
{
std::cerr << "Failed to initialize Steam API" << std::endl;
return 1;
}
boost::asio::io_context io_context;
auto work_guard = boost::asio::make_work_guard(io_context);
std::thread io_thread([&io_context]()
{ io_context.run(); });
// Initialize Steam Networking Manager
SteamNetworkingManager steamManager;
if (!steamManager.initialize())
{
std::cerr << "Failed to initialize Steam Networking Manager" << std::endl;
SteamAPI_Shutdown();
return 1;
}
// Initialize Steam Room Manager
SteamRoomManager roomManager(&steamManager);
// Initialize GLFW
if (!glfwInit())
{
std::cerr << "Failed to initialize GLFW" << std::endl;
steamManager.shutdown();
return -1;
}
// Create window
GLFWwindow *window = glfwCreateWindow(1280, 720, "在线游戏工具 - 1.0.0", nullptr, nullptr);
if (!window)
{
std::cerr << "Failed to create GLFW window" << std::endl;
glfwTerminate();
cleanupSingleInstance();
SteamAPI_Shutdown();
return -1;
}
glfwMakeContextCurrent(window);
glfwSwapInterval(1); // Enable vsync
// Store window handle for single instance activation
storeWindowHandle(window);
// 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");
// Set message handler dependencies
steamManager.setMessageHandlerDependencies(io_context, server, localPort);
steamManager.startMessageHandler();
// Steam Networking variables
bool isHost = false;
bool isClient = false;
char joinBuffer[256] = "";
char filterBuffer[256] = "";
// Lambda to get connection info for a member
auto getMemberConnectionInfo = [&](const CSteamID &memberID, const CSteamID &hostSteamID) -> std::pair<int, std::string>
{
int ping = 0;
std::string relayInfo = "-";
if (steamManager.isHost())
{
// Find connection for this member
std::lock_guard<std::mutex> lockConn(connectionsMutex);
for (const auto &conn : steamManager.getConnections())
{
SteamNetConnectionInfo_t info;
if (steamManager.getInterface()->GetConnectionInfo(conn, &info))
{
if (info.m_identityRemote.GetSteamID() == memberID)
{
ping = steamManager.getConnectionPing(conn);
relayInfo = steamManager.getConnectionRelayInfo(conn);
break;
}
}
}
}
else
{
// Client only shows ping to host, not to other clients
if (memberID == hostSteamID)
{
ping = steamManager.getHostPing();
if (steamManager.getConnection() != k_HSteamNetConnection_Invalid)
{
relayInfo = steamManager.getConnectionRelayInfo(steamManager.getConnection());
}
}
}
return {ping, relayInfo};
};
// Lambda to render invite friends UI
auto renderInviteFriends = [&]()
{
ImGui::InputText("过滤朋友", filterBuffer, IM_ARRAYSIZE(filterBuffer));
ImGui::Text("朋友:");
for (const auto &friendPair : SteamUtils::getFriendsList())
{
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 to lobby
if (SteamMatchmaking())
{
SteamMatchmaking()->InviteUserToLobby(roomManager.getCurrentLobby(), friendPair.first);
std::cout << "Sent lobby invite to " << friendPair.second << std::endl;
}
else
{
std::cerr << "SteamMatchmaking() is null! Cannot send invite." << std::endl;
}
}
ImGui::PopID();
}
}
};
// Frame rate limiting
const double targetFrameTimeForeground = 1.0 / 60.0; // 60 FPS when focused
const double targetFrameTimeBackground = 1.0; // 1 FPS when in background
double lastFrameTime = glfwGetTime();
// Main loop
while (!glfwWindowShouldClose(window))
{
// Frame rate control based on window focus
bool isFocused = glfwGetWindowAttrib(window, GLFW_FOCUSED);
double targetFrameTime = isFocused ? targetFrameTimeForeground : targetFrameTimeBackground;
double currentTime = glfwGetTime();
double deltaTime = currentTime - lastFrameTime;
if (deltaTime < targetFrameTime)
{
std::this_thread::sleep_for(std::chrono::duration<double>(targetFrameTime - deltaTime));
}
lastFrameTime = glfwGetTime();
// Poll events
glfwPollEvents();
SteamAPI_RunCallbacks();
// Update Steam networking info
steamManager.update();
// 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 (!steamManager.isHost() && !steamManager.isConnected())
{
if (ImGui::Button("主持游戏房间"))
{
roomManager.startHosting();
}
ImGui::InputText("房间ID", joinBuffer, IM_ARRAYSIZE(joinBuffer));
if (ImGui::Button("加入游戏房间"))
{
uint64 hostID = std::stoull(joinBuffer);
if (steamManager.joinHost(hostID))
{
// Start TCP Server
server = std::make_unique<TCPServer>(8888, &steamManager);
if (!server->start())
{
std::cerr << "Failed to start TCP server" << std::endl;
}
}
}
}
if (steamManager.isHost() || steamManager.isConnected())
{
ImGui::Text(steamManager.isHost() ? "正在主持游戏房间。邀请朋友!" : "已连接到游戏房间。邀请朋友!");
ImGui::Separator();
if (ImGui::Button("断开连接"))
{
roomManager.leaveLobby();
steamManager.disconnect();
if (server)
{
server->stop();
server.reset();
}
}
if (steamManager.isHost())
{
ImGui::InputInt("本地端口", &localPort);
}
ImGui::Separator();
renderInviteFriends();
}
ImGui::End();
// Room status window - only show when hosting or connected
if ((steamManager.isHost() || steamManager.isConnected()) && roomManager.getCurrentLobby().IsValid())
{
ImGui::Begin("房间状态");
ImGui::Text("用户列表:");
if (ImGui::BeginTable("UserTable", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg))
{
ImGui::TableSetupColumn("名称");
ImGui::TableSetupColumn("延迟 (ms)");
ImGui::TableSetupColumn("连接类型");
ImGui::TableHeadersRow();
{
std::vector<CSteamID> members = roomManager.getLobbyMembers();
CSteamID mySteamID = SteamUser()->GetSteamID();
CSteamID hostSteamID = steamManager.getHostSteamID();
for (const auto &memberID : members)
{
ImGui::TableNextRow();
ImGui::TableNextColumn();
const char *name = SteamFriends()->GetFriendPersonaName(memberID);
ImGui::Text("%s", name);
ImGui::TableNextColumn();
if (memberID == mySteamID)
{
ImGui::Text("-");
ImGui::TableNextColumn();
ImGui::Text("-");
}
else
{
auto [ping, relayInfo] = getMemberConnectionInfo(memberID, hostSteamID);
if (relayInfo != "-")
{
ImGui::Text("%d", ping);
}
else
{
ImGui::Text("-");
}
ImGui::TableNextColumn();
ImGui::Text("%s", relayInfo.c_str());
}
}
}
ImGui::EndTable();
}
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);
}
// Stop message handler
steamManager.stopMessageHandler();
// Cleanup
if (server)
{
server->stop();
}
// Stop io_context and join thread
work_guard.reset();
io_context.stop();
if (io_thread.joinable())
{
io_thread.join();
}
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
glfwDestroyWindow(window);
glfwTerminate();
steamManager.shutdown();
// Cleanup single instance resources
cleanupSingleInstance();
int main() {
// Check for single instance
if (!checkSingleInstance()) {
std::cout << "另一个实例已在运行,正在激活该窗口..." << std::endl;
return 0;
}
// Initialize Steam API first
if (!SteamAPI_Init()) {
std::cerr << "Failed to initialize Steam API" << std::endl;
return 1;
}
boost::asio::io_context io_context;
auto work_guard = boost::asio::make_work_guard(io_context);
std::thread io_thread([&io_context]() { io_context.run(); });
// Initialize Steam Networking Manager
SteamNetworkingManager steamManager;
if (!steamManager.initialize()) {
std::cerr << "Failed to initialize Steam Networking Manager" << std::endl;
SteamAPI_Shutdown();
return 1;
}
// Initialize Steam Room Manager
SteamRoomManager roomManager(&steamManager);
// Initialize GLFW
if (!glfwInit()) {
std::cerr << "Failed to initialize GLFW" << std::endl;
steamManager.shutdown();
return -1;
}
#ifdef __APPLE__
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac
#endif
// Create window
GLFWwindow *window =
glfwCreateWindow(1280, 720, "在线游戏工具 - 1.0.0", nullptr, nullptr);
if (!window) {
std::cerr << "Failed to create GLFW window" << std::endl;
glfwTerminate();
cleanupSingleInstance();
SteamAPI_Shutdown();
return -1;
}
glfwMakeContextCurrent(window);
glfwSwapInterval(1); // Enable vsync
// Store window handle for single instance activation
storeWindowHandle(window);
// 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);
const char *glsl_version = "#version 130";
#ifdef __APPLE__
glsl_version = "#version 150";
#endif
ImGui_ImplOpenGL3_Init(glsl_version);
// Set message handler dependencies
steamManager.setMessageHandlerDependencies(io_context, server, localPort);
steamManager.startMessageHandler();
// Steam Networking variables
bool isHost = false;
bool isClient = false;
char joinBuffer[256] = "";
char filterBuffer[256] = "";
// Lambda to get connection info for a member
auto getMemberConnectionInfo =
[&](const CSteamID &memberID,
const CSteamID &hostSteamID) -> std::pair<int, std::string> {
int ping = 0;
std::string relayInfo = "-";
if (steamManager.isHost()) {
// Find connection for this member
std::lock_guard<std::mutex> lockConn(connectionsMutex);
for (const auto &conn : steamManager.getConnections()) {
SteamNetConnectionInfo_t info;
if (steamManager.getInterface()->GetConnectionInfo(conn, &info)) {
if (info.m_identityRemote.GetSteamID() == memberID) {
ping = steamManager.getConnectionPing(conn);
relayInfo = steamManager.getConnectionRelayInfo(conn);
break;
}
}
}
} else {
// Client only shows ping to host, not to other clients
if (memberID == hostSteamID) {
ping = steamManager.getHostPing();
if (steamManager.getConnection() != k_HSteamNetConnection_Invalid) {
relayInfo =
steamManager.getConnectionRelayInfo(steamManager.getConnection());
}
}
}
return {ping, relayInfo};
};
// Lambda to render invite friends UI
auto renderInviteFriends = [&]() {
ImGui::InputText("过滤朋友", filterBuffer, IM_ARRAYSIZE(filterBuffer));
ImGui::Text("朋友:");
for (const auto &friendPair : SteamUtils::getFriendsList()) {
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 to lobby
if (SteamMatchmaking()) {
SteamMatchmaking()->InviteUserToLobby(roomManager.getCurrentLobby(),
friendPair.first);
std::cout << "Sent lobby invite to " << friendPair.second
<< std::endl;
} else {
std::cerr << "SteamMatchmaking() is null! Cannot send invite."
<< std::endl;
}
}
ImGui::PopID();
}
}
};
// Frame rate limiting
const double targetFrameTimeForeground = 1.0 / 60.0; // 60 FPS when focused
const double targetFrameTimeBackground = 1.0; // 1 FPS when in background
double lastFrameTime = glfwGetTime();
// Main loop
while (!glfwWindowShouldClose(window)) {
// Frame rate control based on window focus
bool isFocused = glfwGetWindowAttrib(window, GLFW_FOCUSED);
double targetFrameTime =
isFocused ? targetFrameTimeForeground : targetFrameTimeBackground;
double currentTime = glfwGetTime();
double deltaTime = currentTime - lastFrameTime;
if (deltaTime < targetFrameTime) {
std::this_thread::sleep_for(
std::chrono::duration<double>(targetFrameTime - deltaTime));
}
lastFrameTime = glfwGetTime();
// Poll events
glfwPollEvents();
SteamAPI_RunCallbacks();
// Update Steam networking info
steamManager.update();
// 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 (!steamManager.isHost() && !steamManager.isConnected()) {
if (ImGui::Button("主持游戏房间")) {
roomManager.startHosting();
}
ImGui::InputText("房间ID", joinBuffer, IM_ARRAYSIZE(joinBuffer));
if (ImGui::Button("加入游戏房间")) {
uint64 hostID = std::stoull(joinBuffer);
if (steamManager.joinHost(hostID)) {
// Start TCP Server
server = std::make_unique<TCPServer>(8888, &steamManager);
if (!server->start()) {
std::cerr << "Failed to start TCP server" << std::endl;
}
}
}
}
if (steamManager.isHost() || steamManager.isConnected()) {
ImGui::Text(steamManager.isHost() ? "正在主持游戏房间。邀请朋友!"
: "已连接到游戏房间。邀请朋友!");
ImGui::Separator();
if (ImGui::Button("断开连接")) {
roomManager.leaveLobby();
steamManager.disconnect();
if (server) {
server->stop();
server.reset();
}
}
if (steamManager.isHost()) {
ImGui::InputInt("本地端口", &localPort);
}
ImGui::Separator();
renderInviteFriends();
}
ImGui::End();
// Room status window - only show when hosting or connected
if ((steamManager.isHost() || steamManager.isConnected()) &&
roomManager.getCurrentLobby().IsValid()) {
ImGui::Begin("房间状态");
ImGui::Text("用户列表:");
if (ImGui::BeginTable("UserTable", 3,
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
ImGui::TableSetupColumn("名称");
ImGui::TableSetupColumn("延迟 (ms)");
ImGui::TableSetupColumn("连接类型");
ImGui::TableHeadersRow();
{
std::vector<CSteamID> members = roomManager.getLobbyMembers();
CSteamID mySteamID = SteamUser()->GetSteamID();
CSteamID hostSteamID = steamManager.getHostSteamID();
for (const auto &memberID : members) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
const char *name = SteamFriends()->GetFriendPersonaName(memberID);
ImGui::Text("%s", name);
ImGui::TableNextColumn();
if (memberID == mySteamID) {
ImGui::Text("-");
ImGui::TableNextColumn();
ImGui::Text("-");
} else {
auto [ping, relayInfo] =
getMemberConnectionInfo(memberID, hostSteamID);
if (relayInfo != "-") {
ImGui::Text("%d", ping);
} else {
ImGui::Text("-");
}
ImGui::TableNextColumn();
ImGui::Text("%s", relayInfo.c_str());
}
}
}
ImGui::EndTable();
}
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);
}
// Stop message handler
steamManager.stopMessageHandler();
// Cleanup
if (server) {
server->stop();
}
// Stop io_context and join thread
work_guard.reset();
io_context.stop();
if (io_thread.joinable()) {
io_thread.join();
}
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
glfwDestroyWindow(window);
glfwTerminate();
steamManager.shutdown();
// Cleanup single instance resources
cleanupSingleInstance();
return 0;
}