添加CMakeLists.txt,修复macOS26编译运行openGL版本不支持错误

This commit is contained in:
liu5580
2025-11-27 15:14:18 +08:00
parent 6897466539
commit 3430871fbf
4 changed files with 479 additions and 464 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

3
.gitmodules vendored Normal file
View File

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

46
CMakeLists.txt Normal file
View File

@@ -0,0 +1,46 @@
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}/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"
)
# Create executable
add_executable(ConnectTool ${SOURCES})
# Link libraries
target_link_libraries(ConnectTool
glfw
OpenGL::GL
Boost::headers
${CMAKE_SOURCE_DIR}/steamworks/redistributable_bin/osx/libsteam_api.dylib
)
# Copy libsteam_api.dylib to output directory for runtime
add_custom_command(TARGET ConnectTool POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/steamworks/redistributable_bin/osx/libsteam_api.dylib
$<TARGET_FILE_DIR:ConnectTool>/libsteam_api.dylib
)

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 <GLFW/glfw3.h>
#include <algorithm>
#include <boost/asio.hpp>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <imgui.h> #include <imgui.h>
#include <imgui_impl_glfw.h> #include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h> #include <imgui_impl_opengl3.h>
#include <iostream> #include <iostream>
#include <vector> #include <map>
#include <memory>
#include <mutex>
#include <string> #include <string>
#include <thread> #include <thread>
#include <mutex> #include <vector>
#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"
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h> #include <windows.h>
#define GLFW_EXPOSE_NATIVE_WIN32 #define GLFW_EXPOSE_NATIVE_WIN32
#include <GLFW/glfw3native.h> #include <GLFW/glfw3native.h>
#else #else
#include <signal.h>
#include <sys/file.h> #include <sys/file.h>
#include <unistd.h> #include <unistd.h>
#include <signal.h>
#endif #endif
using boost::asio::ip::tcp; using boost::asio::ip::tcp;
@@ -41,71 +41,64 @@ std::unique_ptr<TCPServer> server;
// Windows implementation using mutex and shared memory // Windows implementation using mutex and shared memory
HANDLE g_hMutex = nullptr; HANDLE g_hMutex = nullptr;
HANDLE g_hMapFile = nullptr; HANDLE g_hMapFile = nullptr;
HWND* g_pSharedHwnd = nullptr; HWND *g_pSharedHwnd = nullptr;
bool checkSingleInstance() bool checkSingleInstance() {
{ g_hMutex = CreateMutexW(nullptr, FALSE,
g_hMutex = CreateMutexW(nullptr, FALSE, L"Global\\OnlineGameTool_SingleInstance_Mutex"); L"Global\\OnlineGameTool_SingleInstance_Mutex");
if (GetLastError() == ERROR_ALREADY_EXISTS) if (GetLastError() == ERROR_ALREADY_EXISTS) {
{ // Another instance exists, try to find and activate it
// Another instance exists, try to find and activate it g_hMapFile = OpenFileMappingW(FILE_MAP_READ, FALSE,
g_hMapFile = OpenFileMappingW(FILE_MAP_READ, FALSE, L"Global\\OnlineGameTool_HWND_Share"); L"Global\\OnlineGameTool_HWND_Share");
if (g_hMapFile != nullptr) if (g_hMapFile != nullptr) {
{ HWND *pHwnd =
HWND* pHwnd = (HWND*)MapViewOfFile(g_hMapFile, FILE_MAP_READ, 0, 0, sizeof(HWND)); (HWND *)MapViewOfFile(g_hMapFile, FILE_MAP_READ, 0, 0, sizeof(HWND));
if (pHwnd != nullptr && *pHwnd != nullptr && IsWindow(*pHwnd)) if (pHwnd != nullptr && *pHwnd != nullptr && IsWindow(*pHwnd)) {
{ // Restore and bring to front
// Restore and bring to front if (IsIconic(*pHwnd)) {
if (IsIconic(*pHwnd)) ShowWindow(*pHwnd, SW_RESTORE);
{
ShowWindow(*pHwnd, SW_RESTORE);
}
SetForegroundWindow(*pHwnd);
UnmapViewOfFile(pHwnd);
}
CloseHandle(g_hMapFile);
} }
if (g_hMutex) SetForegroundWindow(*pHwnd);
{ UnmapViewOfFile(pHwnd);
CloseHandle(g_hMutex); }
} CloseHandle(g_hMapFile);
return false;
} }
if (g_hMutex) {
CloseHandle(g_hMutex);
}
return false;
}
// Create shared memory for HWND // Create shared memory for HWND
g_hMapFile = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, sizeof(HWND), L"Global\\OnlineGameTool_HWND_Share"); g_hMapFile =
if (g_hMapFile != nullptr) CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0,
{ sizeof(HWND), L"Global\\OnlineGameTool_HWND_Share");
g_pSharedHwnd = (HWND*)MapViewOfFile(g_hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(HWND)); if (g_hMapFile != nullptr) {
} g_pSharedHwnd = (HWND *)MapViewOfFile(g_hMapFile, FILE_MAP_ALL_ACCESS, 0, 0,
return true; sizeof(HWND));
}
return true;
} }
void storeWindowHandle(GLFWwindow* window) void storeWindowHandle(GLFWwindow *window) {
{ if (g_pSharedHwnd != nullptr) {
if (g_pSharedHwnd != nullptr) *g_pSharedHwnd = glfwGetWin32Window(window);
{ }
*g_pSharedHwnd = glfwGetWin32Window(window);
}
} }
void cleanupSingleInstance() void cleanupSingleInstance() {
{ if (g_pSharedHwnd != nullptr) {
if (g_pSharedHwnd != nullptr) UnmapViewOfFile(g_pSharedHwnd);
{ g_pSharedHwnd = nullptr;
UnmapViewOfFile(g_pSharedHwnd); }
g_pSharedHwnd = nullptr; if (g_hMapFile != nullptr) {
} CloseHandle(g_hMapFile);
if (g_hMapFile != nullptr) g_hMapFile = nullptr;
{ }
CloseHandle(g_hMapFile); if (g_hMutex != nullptr) {
g_hMapFile = nullptr; CloseHandle(g_hMutex);
} g_hMutex = nullptr;
if (g_hMutex != nullptr) }
{
CloseHandle(g_hMutex);
g_hMutex = nullptr;
}
} }
#else #else
@@ -113,414 +106,387 @@ void cleanupSingleInstance()
int g_lockfd = -1; int g_lockfd = -1;
std::string g_lockFilePath; std::string g_lockFilePath;
void signalHandler(int signum) void signalHandler(int signum) {
{ // Signal received to bring window to front
// Signal received to bring window to front std::cout << "Received signal to activate window" << std::endl;
std::cout << "Received signal to activate window" << std::endl;
} }
bool checkSingleInstance() bool checkSingleInstance() {
{ std::string tempDir;
std::string tempDir;
#ifdef __APPLE__ #ifdef __APPLE__
const char* tmpdir = getenv("TMPDIR"); const char *tmpdir = getenv("TMPDIR");
tempDir = tmpdir ? tmpdir : "/tmp"; tempDir = tmpdir ? tmpdir : "/tmp";
#else #else
tempDir = "/tmp"; tempDir = "/tmp";
#endif #endif
g_lockFilePath = tempDir + "/OnlineGameTool.lock"; g_lockFilePath = tempDir + "/OnlineGameTool.lock";
g_lockfd = open(g_lockFilePath.c_str(), O_CREAT | O_RDWR, 0666); g_lockfd = open(g_lockFilePath.c_str(), O_CREAT | O_RDWR, 0666);
if (g_lockfd < 0) if (g_lockfd < 0) {
{ std::cerr << "Failed to open lock file" << std::endl;
std::cerr << "Failed to open lock file" << std::endl; return false;
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 // Write our PID to the lock file
if (flock(g_lockfd, LOCK_EX | LOCK_NB) != 0) ftruncate(g_lockfd, 0);
{ pid_t myPid = getpid();
// Lock failed, another instance is running std::string pidStr = std::to_string(myPid);
// Read PID and send signal write(g_lockfd, pidStr.c_str(), pidStr.length());
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 // Set up signal handler
ftruncate(g_lockfd, 0); signal(SIGUSR1, signalHandler);
pid_t myPid = getpid();
std::string pidStr = std::to_string(myPid);
write(g_lockfd, pidStr.c_str(), pidStr.length());
// Set up signal handler return true;
signal(SIGUSR1, signalHandler);
return true;
} }
void storeWindowHandle(GLFWwindow* window) void storeWindowHandle(GLFWwindow *window) {
{ // GLFW doesn't provide a standard way to bring window to front on Unix
// GLFW doesn't provide a standard way to bring window to front on Unix // but we can request attention
// but we can request attention glfwRequestWindowAttention(window);
glfwRequestWindowAttention(window);
} }
void cleanupSingleInstance() void cleanupSingleInstance() {
{ if (g_lockfd >= 0) {
if (g_lockfd >= 0) flock(g_lockfd, LOCK_UN);
{ close(g_lockfd);
flock(g_lockfd, LOCK_UN); g_lockfd = -1;
close(g_lockfd); unlink(g_lockFilePath.c_str());
g_lockfd = -1; }
unlink(g_lockFilePath.c_str());
}
} }
#endif #endif
int main() int main() {
{ // Check for single instance
// Check for single instance if (!checkSingleInstance()) {
if (!checkSingleInstance()) std::cout << "另一个实例已在运行,正在激活该窗口..." << std::endl;
{
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();
return 0; 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;
} }