diff --git a/.github/workflows/build-cs2remoteconsole-client-windows.yaml b/.github/workflows/build-cs2remoteconsole-client-windows.yaml deleted file mode 100644 index 3416aac..0000000 --- a/.github/workflows/build-cs2remoteconsole-client-windows.yaml +++ /dev/null @@ -1,113 +0,0 @@ -name: Build libvconsole and CS2RemoteConsole-client - -on: - push: - branches: [ master ] - paths: - - 'libvconsole/**' - - 'CS2RemoteConsole-client/**' - pull_request: - branches: [ master ] - paths: - - 'libvconsole/**' - - 'CS2RemoteConsole-client/**' - workflow_dispatch: - -jobs: - build-libvconsole: - runs-on: windows-latest - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Add MSBuild to PATH - uses: microsoft/setup-msbuild@v1.1 - - - name: Set up Visual Studio shell - uses: egor-tensin/vs-shell@v2 - with: - arch: x64 - - - name: Build libvconsole - run: | - cd libvconsole - msbuild libvconsole.vcxproj /p:Configuration=Release /p:Platform=x64 - - - name: Upload libvconsole artifact - uses: actions/upload-artifact@v2 - with: - name: libvconsole-build - path: | - libvconsole/x64/Release/libvconsole.lib - libvconsole/src/messages.h - libvconsole/src/vconsole.h - - build-cs2remoteconsole-client: - needs: build-libvconsole - runs-on: windows-latest - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Add MSBuild to PATH - uses: microsoft/setup-msbuild@v1.1 - - - name: Set up Visual Studio shell - uses: egor-tensin/vs-shell@v2 - with: - arch: x64 - - - name: Download libvconsole artifact - uses: actions/download-artifact@v2 - with: - name: libvconsole-build - path: libvconsole-build - - - name: Display structure of downloaded files - run: | - echo "Content of libvconsole-build:" - dir /s libvconsole-build - shell: cmd - - - name: Prepare libvconsole for CS2RemoteConsole-client - run: | - if not exist "CS2RemoteConsole-client\lib" mkdir "CS2RemoteConsole-client\lib" - if not exist "CS2RemoteConsole-client\include" mkdir "CS2RemoteConsole-client\include" - copy "libvconsole-build\x64\Release\libvconsole.lib" "CS2RemoteConsole-client\lib\" - copy "libvconsole-build\src\messages.h" "CS2RemoteConsole-client\include\" - copy "libvconsole-build\src\vconsole.h" "CS2RemoteConsole-client\include\" - shell: cmd - - - name: Verify copied files - run: | - echo "Verifying lib file:" - dir "CS2RemoteConsole-client\lib" - echo "Verifying header files:" - dir "CS2RemoteConsole-client\include" - shell: cmd - - - name: Build CS2RemoteConsole-client - run: | - cd CS2RemoteConsole-client - msbuild CS2RemoteConsole-client.vcxproj /p:Configuration=Release /p:Platform=x64 - - - name: Prepare artifact directory - run: | - mkdir artifact - copy "CS2RemoteConsole-client\x64\Release\CS2RemoteConsole-client.exe" "artifact\" - copy "CS2RemoteConsole-client\config.ini" "artifact\" - shell: cmd - - - name: Verify artifact contents - run: | - echo "Verifying artifact contents:" - dir artifact - shell: cmd - - - name: Upload CS2RemoteConsole-client artifact - uses: actions/upload-artifact@v2 - with: - name: CS2RemoteConsole-client-build - path: artifact \ No newline at end of file diff --git a/.github/workflows/build-cs2remoteconsole-server-linux.yaml b/.github/workflows/build-cs2remoteconsole-server-linux.yaml deleted file mode 100644 index 648a33e..0000000 --- a/.github/workflows/build-cs2remoteconsole-server-linux.yaml +++ /dev/null @@ -1,47 +0,0 @@ -name: Build CS2RemoteConsole-server (Linux) - -on: - push: - branches: [ master ] - paths: - - 'CS2RemoteConsole-server/**' - pull_request: - branches: [ master ] - paths: - - 'CS2RemoteConsole-server/**' - workflow_dispatch: - -jobs: - build-cs2remoteconsole-server-linux: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Set up GCC - uses: egor-tensin/setup-gcc@v1 - with: - version: latest - platform: x64 - - - name: Build CS2RemoteConsole-server - run: | - cd CS2RemoteConsole-server - make - - - name: Prepare artifact directory - run: | - mkdir -p artifact - cp CS2RemoteConsole-server/CS2RemoteConsole-server artifact/ - - - name: Verify artifact contents - run: | - echo "Verifying artifact contents:" - ls -l artifact - - - name: Upload CS2RemoteConsole-server artifact - uses: actions/upload-artifact@v2 - with: - name: CS2RemoteConsole-server-linux-build - path: artifact \ No newline at end of file diff --git a/.github/workflows/build-cs2remoteconsole-server-windows.yaml b/.github/workflows/build-cs2remoteconsole-server-windows.yaml deleted file mode 100644 index c5a0fde..0000000 --- a/.github/workflows/build-cs2remoteconsole-server-windows.yaml +++ /dev/null @@ -1,51 +0,0 @@ -name: Build CS2RemoteConsole-server - -on: - push: - branches: [ master ] - paths: - - 'CS2RemoteConsole-server/**' - pull_request: - branches: [ master ] - paths: - - 'CS2RemoteConsole-server/**' - workflow_dispatch: - -jobs: - build-cs2remoteconsole-server: - runs-on: windows-latest - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Add MSBuild to PATH - uses: microsoft/setup-msbuild@v1.1 - - - name: Set up Visual Studio shell - uses: egor-tensin/vs-shell@v2 - with: - arch: x64 - - - name: Build CS2RemoteConsole-server - run: | - cd CS2RemoteConsole-server - msbuild CS2RemoteConsole-server.vcxproj /p:Configuration=Release /p:Platform=x64 - - - name: Prepare artifact directory - run: | - mkdir artifact - copy "CS2RemoteConsole-server\x64\Release\CS2RemoteConsole-server.exe" "artifact\" - shell: cmd - - - name: Verify artifact contents - run: | - echo "Verifying artifact contents:" - dir artifact - shell: cmd - - - name: Upload CS2RemoteConsole-server artifact - uses: actions/upload-artifact@v2 - with: - name: CS2RemoteConsole-server-build - path: artifact \ No newline at end of file diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..e0baee7 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,137 @@ +name: Build CS2RemoteConsole + +on: + push: + branches: [ master ] + paths: + - 'libvconsole/**' + - 'CS2RemoteConsole-client/**' + - 'CS2RemoteConsole-server/**' + - 'common/**' + - 'CMakeLists.txt' + pull_request: + branches: [ master ] + paths: + - 'libvconsole/**' + - 'CS2RemoteConsole-client/**' + - 'CS2RemoteConsole-server/**' + - 'common/**' + - 'CMakeLists.txt' + workflow_dispatch: + +jobs: + build: + strategy: + matrix: + include: + - os: ubuntu-latest + os_name: linux + - os: windows-latest + os_name: windows + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + # Linux setup + - name: Install dependencies (Linux) + if: matrix.os == 'ubuntu-latest' + run: | + sudo apt-get update + sudo apt-get install -y libncurses-dev + + # Windows setup + - name: Add MSBuild to PATH (Windows) + if: matrix.os == 'windows-latest' + uses: microsoft/setup-msbuild@v2 + + - name: Set up Visual Studio shell (Windows) + if: matrix.os == 'windows-latest' + uses: egor-tensin/vs-shell@v2 + with: + arch: x64 + + # Build - Linux (CMake) + - name: Configure CMake (Linux) + if: matrix.os == 'ubuntu-latest' + run: | + mkdir build + cd build + cmake .. -DCMAKE_BUILD_TYPE=Release + + - name: Build (Linux) + if: matrix.os == 'ubuntu-latest' + run: | + cd build + make -j$(nproc) + + # Build - Windows (MSBuild) + - name: Build libvconsole (Windows) + if: matrix.os == 'windows-latest' + run: | + cd libvconsole + msbuild libvconsole.vcxproj /p:Configuration=Release /p:Platform=x64 + + - name: Prepare libvconsole for client (Windows) + if: matrix.os == 'windows-latest' + run: | + if not exist "CS2RemoteConsole-client\lib" mkdir "CS2RemoteConsole-client\lib" + copy "libvconsole\x64\Release\libvconsole.lib" "CS2RemoteConsole-client\lib\" + shell: cmd + + - name: Build CS2RemoteConsole-client (Windows) + if: matrix.os == 'windows-latest' + run: | + cd CS2RemoteConsole-client + msbuild CS2RemoteConsole-client.vcxproj /p:Configuration=Release /p:Platform=x64 + + - name: Build CS2RemoteConsole-server (Windows) + if: matrix.os == 'windows-latest' + run: | + cd CS2RemoteConsole-server + msbuild CS2RemoteConsole-server.vcxproj /p:Configuration=Release /p:Platform=x64 + + # Prepare artifacts - Linux + - name: Prepare client artifact (Linux) + if: matrix.os == 'ubuntu-latest' + run: | + mkdir -p artifact-client + cp build/bin/CS2RemoteConsole-client artifact-client/ + cp CS2RemoteConsole-client/config.ini artifact-client/ + + - name: Prepare server artifact (Linux) + if: matrix.os == 'ubuntu-latest' + run: | + mkdir -p artifact-server + cp build/bin/CS2RemoteConsole-server artifact-server/ + + # Prepare artifacts - Windows + - name: Prepare client artifact (Windows) + if: matrix.os == 'windows-latest' + run: | + mkdir artifact-client + copy "CS2RemoteConsole-client\x64\Release\CS2RemoteConsole-client.exe" "artifact-client\" + copy "CS2RemoteConsole-client\config.ini" "artifact-client\" + shell: cmd + + - name: Prepare server artifact (Windows) + if: matrix.os == 'windows-latest' + run: | + mkdir artifact-server + copy "CS2RemoteConsole-server\x64\Release\CS2RemoteConsole-server.exe" "artifact-server\" + shell: cmd + + # Upload artifacts + - name: Upload client artifact + uses: actions/upload-artifact@v4 + with: + name: CS2RemoteConsole-client-${{ matrix.os_name }} + path: artifact-client + + - name: Upload server artifact + uses: actions/upload-artifact@v4 + with: + name: CS2RemoteConsole-server-${{ matrix.os_name }} + path: artifact-server diff --git a/.gitignore b/.gitignore index a1ef686..54ba57b 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ x86/ [Aa][Rr][Mm]/ [Aa][Rr][Mm]64/ bld/ +build/ [Bb]in/ [Oo]bj/ [Ll]og/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9d7bd80 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.14) +project(CS2RemoteConsole VERSION 1.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Build options +option(BUILD_CLIENT "Build the CS2RemoteConsole client" ON) +option(BUILD_SERVER "Build the CS2RemoteConsole server" ON) + +# Add subdirectories +add_subdirectory(libvconsole) + +if(BUILD_CLIENT) + add_subdirectory(CS2RemoteConsole-client) +endif() + +if(BUILD_SERVER) + add_subdirectory(CS2RemoteConsole-server) +endif() diff --git a/CS2RemoteConsole-client/CMakeLists.txt b/CS2RemoteConsole-client/CMakeLists.txt new file mode 100644 index 0000000..ece2348 --- /dev/null +++ b/CS2RemoteConsole-client/CMakeLists.txt @@ -0,0 +1,77 @@ +cmake_minimum_required(VERSION 3.14) +project(CS2RemoteConsole-client VERSION 1.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Source files +set(SOURCES + src/main.cpp + src/config.cpp + src/utils.cpp + src/tui/tui.cpp + src/connection/connection_cs2console.cpp + src/connection/connection_remoteserver.cpp +) + +set(HEADERS + src/config.h + src/constants.h + src/singletons.h + src/utils.h + src/tui/tui.h + src/connection/connection_cs2console.h + src/connection/connection_remoteserver.h +) + +# Create executable +add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS}) + +# Common include directories (no curses here - platform-specific below) +target_include_directories(${PROJECT_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}/../common + ${CMAKE_CURRENT_SOURCE_DIR}/../libvconsole/src +) + +# Link libvconsole +target_link_libraries(${PROJECT_NAME} PRIVATE vconsole) + +# Platform-specific settings +if(WIN32) + # Windows: Use bundled PDCurses and spdlog + target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include) + target_link_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/lib) + target_link_libraries(${PROJECT_NAME} PRIVATE pdcurses ws2_32) +else() + # Linux/macOS: Use system ncurses + find_package(Curses REQUIRED) + target_include_directories(${PROJECT_NAME} PRIVATE ${CURSES_INCLUDE_DIR}) + target_link_libraries(${PROJECT_NAME} PRIVATE ${CURSES_LIBRARIES}) + + # spdlog: Try system package first, fallback to bundled (excluding bundled curses.h) + find_package(spdlog QUIET) + if(spdlog_FOUND) + target_link_libraries(${PROJECT_NAME} PRIVATE spdlog::spdlog) + else() + # Use bundled spdlog headers (header-only mode) + target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include) + message(STATUS "System spdlog not found, using bundled headers.") + endif() + + # Threads + find_package(Threads REQUIRED) + target_link_libraries(${PROJECT_NAME} PRIVATE Threads::Threads) +endif() + +# Copy config.ini to build directory +add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_SOURCE_DIR}/config.ini + $ +) + +# Output directory +set_target_properties(${PROJECT_NAME} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin +) diff --git a/CS2RemoteConsole-client/CS2RemoteConsole-client.vcxproj b/CS2RemoteConsole-client/CS2RemoteConsole-client.vcxproj index b89c32b..3922c55 100644 --- a/CS2RemoteConsole-client/CS2RemoteConsole-client.vcxproj +++ b/CS2RemoteConsole-client/CS2RemoteConsole-client.vcxproj @@ -48,14 +48,14 @@ true - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(SolutionDir)/libvconsole/src/ + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)..\libvconsole\src\ false - .\include + .\include;$(ProjectDir)..\common;$(ProjectDir)..\libvconsole\src NotUsing Level3 Disabled @@ -66,14 +66,14 @@ ./lib; - $(SolutionDir)/x64/Debug/libvconsole.lib + .\lib\libvconsole.lib;.\lib\pdcurses.lib Console true - .\include + .\include;$(ProjectDir)..\common;$(ProjectDir)..\libvconsole\src NotUsing Level3 MaxSpeed diff --git a/CS2RemoteConsole-client/config.ini b/CS2RemoteConsole-client/config.ini index baee727..a186964 100644 --- a/CS2RemoteConsole-client/config.ini +++ b/CS2RemoteConsole-client/config.ini @@ -4,6 +4,7 @@ cs2_console_port=29000 cs2_console_reconnect_delay=5000 # Remote Server Connection (Optional) +remote_server_enabled=1 remote_server_ip=127.0.0.1 remote_server_port=42069 remote_server_reconnect_delay=5000 diff --git a/CS2RemoteConsole-client/include/curses.h b/CS2RemoteConsole-client/include/pdcurses/curses.h similarity index 100% rename from CS2RemoteConsole-client/include/curses.h rename to CS2RemoteConsole-client/include/pdcurses/curses.h diff --git a/CS2RemoteConsole-client/include/panel.h b/CS2RemoteConsole-client/include/pdcurses/panel.h similarity index 100% rename from CS2RemoteConsole-client/include/panel.h rename to CS2RemoteConsole-client/include/pdcurses/panel.h diff --git a/CS2RemoteConsole-client/src/connection/connection_cs2console.cpp b/CS2RemoteConsole-client/src/connection/connection_cs2console.cpp index 38067b1..88b2425 100644 --- a/CS2RemoteConsole-client/src/connection/connection_cs2console.cpp +++ b/CS2RemoteConsole-client/src/connection/connection_cs2console.cpp @@ -1,11 +1,9 @@ -#include "connection_cs2console.h" +#include "connection_cs2console.h" #include "spdlog/spdlog.h" #include "../config.h" #include #include -#pragma once - std::atomic listeningCS2(false); std::atomic cs2ConsoleConnected(false); std::atomic running(true); @@ -60,7 +58,10 @@ void cs2ConsoleConnectorLoop() else { spdlog::error("[CS2ConsoleConnection] Failed to connect to CS2 console. Retrying in {} seconds...", reconnect_delay / 1000); - std::this_thread::sleep_for(std::chrono::milliseconds(reconnect_delay)); + for (int i = 0; i < reconnect_delay / 100 && running; ++i) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } continue; } } @@ -147,6 +148,9 @@ void cleanupCS2Console() listeningCS2 = false; cs2ConsoleConnected = false; + // Disconnect first to interrupt any blocking connect() calls + vconsole.disconnect(); + if (cs2ListenerThread.joinable()) { cs2ListenerThread.join(); @@ -155,8 +159,6 @@ void cleanupCS2Console() { cs2ConnectorThread.join(); } - - vconsole.disconnect(); } void initializeCS2Connection() diff --git a/CS2RemoteConsole-client/src/connection/connection_cs2console.h b/CS2RemoteConsole-client/src/connection/connection_cs2console.h index 8d07ab2..7c1be4b 100644 --- a/CS2RemoteConsole-client/src/connection/connection_cs2console.h +++ b/CS2RemoteConsole-client/src/connection/connection_cs2console.h @@ -1,4 +1,4 @@ -#ifndef CONNECTION_CS2CONSOLE_H +#ifndef CONNECTION_CS2CONSOLE_H #define CONNECTION_CS2CONSOLE_H #include @@ -7,7 +7,9 @@ #include "vconsole.h" #include "../singletons.h" +#ifdef _WIN32 #pragma comment(lib, "libvconsole.lib") +#endif #pragma once diff --git a/CS2RemoteConsole-client/src/connection/connection_remoteserver.cpp b/CS2RemoteConsole-client/src/connection/connection_remoteserver.cpp index 4d9f6ad..8dba21d 100644 --- a/CS2RemoteConsole-client/src/connection/connection_remoteserver.cpp +++ b/CS2RemoteConsole-client/src/connection/connection_remoteserver.cpp @@ -1,4 +1,4 @@ -#include "connection_remoteserver.h" +#include "connection_remoteserver.h" #include #include #include @@ -19,7 +19,7 @@ bool connectToRemoteServer() remoteServerSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (remoteServerSock == INVALID_SOCKET) { - spdlog::error("[RemoteServerConnection] Failed to create socket for remote server: {}", WSAGetLastError()); + spdlog::error("[RemoteServerConnection] Failed to create socket for remote server: {}", SOCKET_ERROR_CODE); return false; } @@ -30,8 +30,8 @@ bool connectToRemoteServer() if (connect(remoteServerSock, (sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) { - spdlog::error("[RemoteServerConnection] Connection to remote server failed: {}", WSAGetLastError()); - closesocket(remoteServerSock); + spdlog::error("[RemoteServerConnection] Connection to remote server failed: {}", SOCKET_ERROR_CODE); + CLOSE_SOCKET(remoteServerSock); remoteServerSock = INVALID_SOCKET; return false; } @@ -51,7 +51,7 @@ bool sendMessageToRemoteServer(const std::string& message) int sendResult = send(remoteServerSock, message.c_str(), static_cast(message.length()), 0); if (sendResult == SOCKET_ERROR) { - spdlog::error("[RemoteServerConnection] Failed to send message to remote server: {}", WSAGetLastError()); + spdlog::error("[RemoteServerConnection] Failed to send message to remote server: {}", SOCKET_ERROR_CODE); return false; } @@ -62,7 +62,7 @@ void remoteServerConnectorLoop() { const int reconnect_delay = Config::getInstance().getInt("remote_server_reconnect_delay", 5000); - while (true) + while (running) { if (!remoteServerConnected) { @@ -93,12 +93,18 @@ void remoteServerConnectorLoop() else { spdlog::error("[RemoteServerConnection] Failed to connect to remote server. Retrying in {} seconds...", reconnect_delay / 1000); - std::this_thread::sleep_for(std::chrono::milliseconds(reconnect_delay)); + for (int i = 0; i < reconnect_delay / 100 && running; ++i) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } } } else { - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + for (int i = 0; i < 10 && running; ++i) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } } } } @@ -107,10 +113,9 @@ void listenForRemoteServerData() { char buffer[1024]; int bytesReceived; - u_long mode = 1; // Set non-blocking mode - ioctlsocket(remoteServerSock, FIONBIO, &mode); + setSocketNonBlocking(remoteServerSock); - while (listeningRemoteServer) + while (listeningRemoteServer && running) { bytesReceived = recv(remoteServerSock, buffer, sizeof(buffer) - 1, 0); if (bytesReceived > 0) @@ -124,9 +129,9 @@ void listenForRemoteServerData() spdlog::warn("[RemoteServerConnection] Connection closed by remote server"); break; } - else if (WSAGetLastError() != WSAEWOULDBLOCK) + else if (SOCKET_ERROR_CODE != WOULD_BLOCK_ERROR) { - spdlog::error("[RemoteServerConnection] recv failed from remote server: Error code {} ", WSAGetLastError()); + spdlog::error("[RemoteServerConnection] recv failed from remote server: Error code {} ", SOCKET_ERROR_CODE); break; } std::this_thread::sleep_for(std::chrono::milliseconds(100)); @@ -135,7 +140,7 @@ void listenForRemoteServerData() spdlog::info("[RemoteServerConnection] Remote server listener thread stopping..."); remoteServerConnected = false; listeningRemoteServer = false; - closesocket(remoteServerSock); + CLOSE_SOCKET(remoteServerSock); remoteServerSock = INVALID_SOCKET; } @@ -143,17 +148,20 @@ void cleanupRemoteServer() { listeningRemoteServer = false; remoteServerConnected = false; + + // Close socket first to interrupt any blocking connect() calls + if (remoteServerSock != INVALID_SOCKET) + { + CLOSE_SOCKET(remoteServerSock); + remoteServerSock = INVALID_SOCKET; + } + if (remoteServerListenerThread.joinable()) { remoteServerListenerThread.join(); } if (remoteServerConnectorThread.joinable()) { - remoteServerConnectorThread.detach(); - } - if (remoteServerSock != INVALID_SOCKET) - { - closesocket(remoteServerSock); - remoteServerSock = INVALID_SOCKET; + remoteServerConnectorThread.join(); } } diff --git a/CS2RemoteConsole-client/src/connection/connection_remoteserver.h b/CS2RemoteConsole-client/src/connection/connection_remoteserver.h index a25c1ba..e1242fa 100644 --- a/CS2RemoteConsole-client/src/connection/connection_remoteserver.h +++ b/CS2RemoteConsole-client/src/connection/connection_remoteserver.h @@ -1,8 +1,7 @@ -#ifndef CONNECTION_REMOTESERVER_H +#ifndef CONNECTION_REMOTESERVER_H #define CONNECTION_REMOTESERVER_H -#include -#include +#include "../../../common/platform.h" #include #include #include "connection_cs2console.h" @@ -10,8 +9,6 @@ #pragma once -#pragma comment(lib, "ws2_32.lib") - extern SOCKET remoteServerSock; extern std::atomic listeningRemoteServer; extern std::atomic remoteServerConnected; diff --git a/CS2RemoteConsole-client/src/constants.h b/CS2RemoteConsole-client/src/constants.h index 2cd14ee..a02e188 100644 --- a/CS2RemoteConsole-client/src/constants.h +++ b/CS2RemoteConsole-client/src/constants.h @@ -1,4 +1,4 @@ -#ifndef CONSTANTS_H +#ifndef CONSTANTS_H #define CONSTANTS_H #include diff --git a/CS2RemoteConsole-client/src/main.cpp b/CS2RemoteConsole-client/src/main.cpp index 197802e..ebb3666 100644 --- a/CS2RemoteConsole-client/src/main.cpp +++ b/CS2RemoteConsole-client/src/main.cpp @@ -14,15 +14,16 @@ #include "connection/connection_cs2console.h" #include "connection/connection_remoteserver.h" #include "tui/tui.h" -#include +#include "../../common/platform.h" std::atomic applicationRunning(true); -TUI tui; +TUI tui(applicationRunning); void signalHandler(int signum) { spdlog::info("[Main] Interrupt signal {} received.", signum); applicationRunning = false; + running = false; // Stop connection loops } void gracefulShutdown() @@ -31,12 +32,10 @@ void gracefulShutdown() applicationRunning = false; - tui.shutdown(); - cleanupCS2Console(); cleanupRemoteServer(); - WSACleanup(); + platformSocketCleanup(); std::cout << "CS2RemoteConsole shutdown complete. Bye-bye!..." << std::endl; } @@ -76,7 +75,7 @@ int main() auto applicationLogger = std::make_shared("CS2RemoteConsole-Client", begin(applicationSinks), end(applicationSinks)); spdlog::set_default_logger(applicationLogger); - if (!setupConfig() || !setupApplicationWinsock()) + if (!setupConfig() || !setupApplicationSockets()) { return 1; } @@ -113,7 +112,11 @@ int main() spdlog::info("[Main] Starting {}", application_name); cs2ConnectorThread = std::thread(cs2ConsoleConnectorLoop); - remoteServerConnectorThread = std::thread(remoteServerConnectorLoop); + + if (Config::getInstance().getInt("remote_server_enabled", 0) == 1) + { + remoteServerConnectorThread = std::thread(remoteServerConnectorLoop); + } tui.run(); @@ -124,7 +127,7 @@ int main() spdlog::error("[Main] Unhandled exception: {}", e.what()); cleanupCS2Console(); cleanupRemoteServer(); - WSACleanup(); + platformSocketCleanup(); return 1; } catch (...) @@ -132,7 +135,7 @@ int main() spdlog::error("[Main] Unhandled unknown exception."); cleanupCS2Console(); cleanupRemoteServer(); - WSACleanup(); + platformSocketCleanup(); return 1; } return 0; diff --git a/CS2RemoteConsole-client/src/tui/tui.cpp b/CS2RemoteConsole-client/src/tui/tui.cpp index 82b13be..5b53a9b 100644 --- a/CS2RemoteConsole-client/src/tui/tui.cpp +++ b/CS2RemoteConsole-client/src/tui/tui.cpp @@ -1,19 +1,24 @@ -#include "tui.h" +#include "tui.h" #include #include #include #include #include +// PDCurses uses nc_getmouse, ncurses uses getmouse +#ifndef _WIN32 +#define nc_getmouse getmouse +#endif + template T clamp(const T& value, const T& low, const T& high) { return std::max(low, std::min(value, high)); } -TUI::TUI() +TUI::TUI(std::atomic& runningFlag) : m_consoleWindow(nullptr), m_inputWindow(nullptr), - m_running(true), m_needsResize(false), m_lastWidth(0), m_lastHeight(0), + m_running(runningFlag), m_needsResize(false), m_lastWidth(0), m_lastHeight(0), m_scrollPosition(0), m_consoleDirty(false) { } @@ -80,11 +85,11 @@ void TUI::setCommandCallback(std::function callback) } void TUI::addConsoleMessage(int channelId, const std::string& message, uint32_t msgColor) //no background color required, this is only for PRNT as of now, which doesn't deliver background colors anyways -{ +{ std::lock_guard lock(m_consoleMutex); ConsoleMessage cMessage; cMessage.channelId = channelId; - cMessage.color = _byteswap_ulong(msgColor); //byteswapping because apparently message colors have their bytes swapped relative to channel colors...Valve... + cMessage.color = byteSwap32(msgColor); //byteswapping because apparently message colors have their bytes swapped relative to channel colors...Valve... cMessage.message = message; cMessage.timestamp = std::chrono::system_clock::now(); diff --git a/CS2RemoteConsole-client/src/tui/tui.h b/CS2RemoteConsole-client/src/tui/tui.h index e53474b..c23fedb 100644 --- a/CS2RemoteConsole-client/src/tui/tui.h +++ b/CS2RemoteConsole-client/src/tui/tui.h @@ -1,10 +1,12 @@ -#ifndef TUI_H +#ifndef TUI_H #define TUI_H +#ifdef _WIN32 #pragma comment(lib, "pdcurses.lib") #pragma comment(lib, "user32.lib") #pragma comment(lib, "winmm.lib") #pragma comment(lib, "advapi32.lib") +#endif #include #include @@ -15,9 +17,15 @@ #include #include -#include +#ifdef _WIN32 +#include +#else +#include +#endif #include +#include "../../../common/platform.h" + const int APPLICATION_SPECIAL_CHANNEL_ID = -1337; struct ConsoleChannel @@ -38,7 +46,7 @@ struct ConsoleMessage class TUI { public: - TUI(); + explicit TUI(std::atomic& runningFlag); ~TUI(); void init(); @@ -70,7 +78,7 @@ class TUI std::function m_commandCallback; std::mutex m_consoleMutex; std::mutex m_channelsMutex; - std::atomic m_running; + std::atomic& m_running; // Reference to external running flag std::atomic m_needsResize; int m_lastWidth; int m_lastHeight; diff --git a/CS2RemoteConsole-client/src/utils.cpp b/CS2RemoteConsole-client/src/utils.cpp index 9800cd7..1110b31 100644 --- a/CS2RemoteConsole-client/src/utils.cpp +++ b/CS2RemoteConsole-client/src/utils.cpp @@ -1,4 +1,4 @@ -#include +#include #include "config.h" #include "utils.h" #include "connection/connection_cs2console.h" @@ -7,19 +7,22 @@ std::string getCurrentDirectory() { char buffer[FILENAME_MAX]; +#ifdef _WIN32 if (_getcwd(buffer, FILENAME_MAX) != nullptr) +#else + if (getcwd(buffer, FILENAME_MAX) != nullptr) +#endif { return std::string(buffer); } return ""; } -bool setupApplicationWinsock() +bool setupApplicationSockets() { - WSADATA wsaData; - if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) + if (!platformSocketInit()) { - std::cerr << "[Main] WSAStartup failed" << '\n'; + std::cerr << "[Main] Socket initialization failed" << '\n'; return false; } return true; diff --git a/CS2RemoteConsole-client/src/utils.h b/CS2RemoteConsole-client/src/utils.h index 7da6dfb..a6a45c8 100644 --- a/CS2RemoteConsole-client/src/utils.h +++ b/CS2RemoteConsole-client/src/utils.h @@ -1,12 +1,19 @@ -#ifndef UTILS_H +#ifndef UTILS_H #define UTILS_H #include #include +#include "../../common/platform.h" + +#ifdef _WIN32 #include +#else +#include +#endif #pragma once std::string getCurrentDirectory(); -bool setupApplicationWinsock(); +bool setupApplicationSockets(); + #endif // UTILS_H diff --git a/CS2RemoteConsole-server/CMakeLists.txt b/CS2RemoteConsole-server/CMakeLists.txt new file mode 100644 index 0000000..ae58ae5 --- /dev/null +++ b/CS2RemoteConsole-server/CMakeLists.txt @@ -0,0 +1,41 @@ +cmake_minimum_required(VERSION 3.14) +project(CS2RemoteConsole-server VERSION 1.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Source files +set(SOURCES + src/main.cpp + src/server.cpp + src/client_handler.cpp + src/utils.cpp +) + +set(HEADERS + src/server.h + src/client_handler.h + src/utils.h +) + +# Create executable +add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS}) + +# Include directories +target_include_directories(${PROJECT_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src +) + +# Platform-specific settings +if(WIN32) + target_link_libraries(${PROJECT_NAME} PRIVATE ws2_32) +endif() + +# Threads +find_package(Threads REQUIRED) +target_link_libraries(${PROJECT_NAME} PRIVATE Threads::Threads) + +# Output directory +set_target_properties(${PROJECT_NAME} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin +) diff --git a/CS2RemoteConsole-server/src/main.cpp b/CS2RemoteConsole-server/src/main.cpp index 98871b5..71c4d51 100644 --- a/CS2RemoteConsole-server/src/main.cpp +++ b/CS2RemoteConsole-server/src/main.cpp @@ -4,6 +4,12 @@ #include #include #include +#include +#ifdef _WIN32 +#include +#else +#include +#endif std::atomic applicationRunning(true); @@ -11,6 +17,11 @@ void signalHandler(int signum) { std::cout << "\nInterrupt signal (" << signum << ") received. Initiating shutdown..." << std::endl; applicationRunning = false; +#ifdef _WIN32 + _close(_fileno(stdin)); // Force getline to return +#else + close(STDIN_FILENO); // Force getline to return +#endif } int main(int argc, char* argv[]) diff --git a/CS2RemoteConsole-server/src/server.cpp b/CS2RemoteConsole-server/src/server.cpp index 470220c..bdce177 100644 --- a/CS2RemoteConsole-server/src/server.cpp +++ b/CS2RemoteConsole-server/src/server.cpp @@ -87,6 +87,10 @@ void Server::run() userInputHandler(); std::cout << "Shutting down server..." << std::endl; + + // Close sockets first to interrupt any blocking accept() + cleanupSockets(); + if (m_acceptThread.joinable()) { m_acceptThread.join(); @@ -210,6 +214,12 @@ void Server::userInputHandler() std::cout << ANSI_COLOR_COMMON << "Enter command to send to clients (or '/quit' to exit): " << ANSI_COLOR_RESET; std::getline(std::cin, input); + if (!std::cin.good() || !m_running) + { + m_running = false; + break; + } + if (input == "/quit") { m_running = false; diff --git a/common/platform.h b/common/platform.h new file mode 100644 index 0000000..27c50c3 --- /dev/null +++ b/common/platform.h @@ -0,0 +1,73 @@ +#ifndef PLATFORM_H +#define PLATFORM_H + +#ifdef _WIN32 + #define WIN32_LEAN_AND_MEAN + #include + #include + #include + #pragma comment(lib, "ws2_32.lib") + + #define SOCKET_ERROR_CODE WSAGetLastError() + #define WOULD_BLOCK_ERROR WSAEWOULDBLOCK + #define CLOSE_SOCKET closesocket + #define SHUT_RDWR SD_BOTH + + inline bool platformSocketInit() { + WSADATA wsaData; + return WSAStartup(MAKEWORD(2, 2), &wsaData) == 0; + } + + inline void platformSocketCleanup() { + WSACleanup(); + } + + inline void setSocketNonBlocking(SOCKET sock) { + u_long mode = 1; + ioctlsocket(sock, FIONBIO, &mode); + } + +#else + #include + #include + #include + #include + #include + #include + #include + + typedef int SOCKET; + #define INVALID_SOCKET -1 + #define SOCKET_ERROR -1 + #define SOCKET_ERROR_CODE errno + #define WOULD_BLOCK_ERROR EWOULDBLOCK + #define CLOSE_SOCKET close + #define closesocket close + + inline bool platformSocketInit() { + return true; // No initialization needed on POSIX + } + + inline void platformSocketCleanup() { + // No cleanup needed on POSIX + } + + inline void setSocketNonBlocking(SOCKET sock) { + int flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, flags | O_NONBLOCK); + } + +#endif + +// Common utilities +#include + +inline uint32_t byteSwap32(uint32_t value) { +#ifdef _WIN32 + return _byteswap_ulong(value); +#else + return __builtin_bswap32(value); +#endif +} + +#endif // PLATFORM_H diff --git a/libvconsole/CMakeLists.txt b/libvconsole/CMakeLists.txt new file mode 100644 index 0000000..2ba0199 --- /dev/null +++ b/libvconsole/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.14) +project(libvconsole VERSION 1.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Library sources +set(SOURCES + src/vconsole.cpp +) + +set(HEADERS + src/vconsole.h + src/messages.h +) + +# Create library +add_library(vconsole STATIC ${SOURCES} ${HEADERS}) + +# Include directories +target_include_directories(vconsole PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}/../common +) + +# Platform-specific settings +if(WIN32) + target_link_libraries(vconsole PRIVATE ws2_32) +endif() + +# Export library +set_target_properties(vconsole PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib +) diff --git a/libvconsole/libvconsole.vcxproj b/libvconsole/libvconsole.vcxproj index 92ba78f..e45573e 100644 --- a/libvconsole/libvconsole.vcxproj +++ b/libvconsole/libvconsole.vcxproj @@ -61,6 +61,7 @@ _DEBUG;_LIB;%(PreprocessorDefinitions) true pch.h + $(ProjectDir)..\common;%(AdditionalIncludeDirectories) Windows @@ -78,6 +79,7 @@ NDEBUG;_LIB;%(PreprocessorDefinitions) true pch.h + $(ProjectDir)..\common;%(AdditionalIncludeDirectories) Windows diff --git a/libvconsole/src/vconsole.cpp b/libvconsole/src/vconsole.cpp index 21a9562..34d7b92 100644 --- a/libvconsole/src/vconsole.cpp +++ b/libvconsole/src/vconsole.cpp @@ -1,10 +1,10 @@ -#include "vconsole.h" +#include "vconsole.h" #include #include VConsole::VConsole() : clientSocket(INVALID_SOCKET) { - setupWinsock(); + platformSocketInit(); } VConsole::~VConsole() @@ -39,6 +39,7 @@ void VConsole::disconnect() { if (clientSocket != INVALID_SOCKET) { + shutdown(clientSocket, SHUT_RDWR); // Force pending recv/send to return closesocket(clientSocket); clientSocket = INVALID_SOCKET; } @@ -61,7 +62,7 @@ int VConsole::readChunk(std::vector& outputBuf) int n = recv(clientSocket, reinterpret_cast(&header), sizeof(header), 0); if (n < static_cast(sizeof(VConChunk))) { - if (WSAGetLastError() == WSAEWOULDBLOCK) + if (SOCKET_ERROR_CODE == WOULD_BLOCK_ERROR) { return 0; } @@ -78,7 +79,7 @@ int VConsole::readChunk(std::vector& outputBuf) int p = recv(clientSocket, outputBuf.data() + sizeof(VConChunk), header.length - sizeof(VConChunk), 0); if (p < static_cast(header.length - sizeof(VConChunk))) { - if (WSAGetLastError() == WSAEWOULDBLOCK) + if (SOCKET_ERROR_CODE == WOULD_BLOCK_ERROR) { return 0; } @@ -176,15 +177,7 @@ void VConsole::setOnCHANReceived(std::function callback) onCHANReceived = callback; } -bool setupWinsock() -{ - WSADATA wsaData; - if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) - { - return false; - } - return true; -} +// Platform socket initialization is now handled by platformSocketInit() in platform.h std::vector createCommandPayload(const std::string& command) { diff --git a/libvconsole/src/vconsole.h b/libvconsole/src/vconsole.h index a49bee7..e4dad07 100644 --- a/libvconsole/src/vconsole.h +++ b/libvconsole/src/vconsole.h @@ -7,15 +7,7 @@ #include #include #include "messages.h" - -#ifdef _WIN32 -#include -#include -#pragma comment(lib, "ws2_32.lib") -#else -#include -#include -#endif +#include "../../common/platform.h" #pragma once @@ -68,7 +60,7 @@ class VConsole // Perform a non-blocking check on the socket char buf; int result = recv(clientSocket, &buf, 1, MSG_PEEK); - if (result == 0 || (result == SOCKET_ERROR && WSAGetLastError() != WSAEWOULDBLOCK)) + if (result == 0 || (result == SOCKET_ERROR && SOCKET_ERROR_CODE != WOULD_BLOCK_ERROR)) return false; return true; @@ -97,7 +89,6 @@ class VConsole CFGV parseCFGV(const std::vector& chunkBuf); }; -bool setupWinsock(); std::vector createCommandPayload(const std::string& command); #endif // VCONSOLE_H