diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ea8df1c..3cf0c4a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -158,6 +158,10 @@ set(WOWEE_HEADERS include/network/socket.hpp include/network/packet.hpp include/network/tcp_socket.hpp + include/network/world_socket.hpp + include/network/net_platform.hpp + + include/platform/process.hpp include/auth/auth_handler.hpp include/auth/auth_opcodes.hpp @@ -240,6 +244,15 @@ target_link_libraries(wowee PRIVATE Threads::Threads ) +# Platform-specific libraries +if(WIN32) + target_link_libraries(wowee PRIVATE ws2_32) + # SDL2main provides WinMain entry point on Windows + if(TARGET SDL2::SDL2main) + target_link_libraries(wowee PRIVATE SDL2::SDL2main) + endif() +endif() + # Link StormLib if found if(STORMLIB_LIBRARY AND STORMLIB_INCLUDE_DIR) target_link_libraries(wowee PRIVATE ${STORMLIB_LIBRARY}) diff --git a/include/audio/activity_sound_manager.hpp b/include/audio/activity_sound_manager.hpp index 1a4b253c..0c70ddc1 100644 --- a/include/audio/activity_sound_manager.hpp +++ b/include/audio/activity_sound_manager.hpp @@ -1,6 +1,7 @@ #pragma once #include "audio/footstep_manager.hpp" +#include "platform/process.hpp" #include #include #include @@ -51,10 +52,10 @@ private: bool swimmingActive = false; bool swimMoving = false; - pid_t swimLoopPid = -1; - pid_t oneShotPid = -1; - std::string loopTempPath = "/tmp/wowee_swim_loop.wav"; - std::string oneShotTempPath = "/tmp/wowee_activity.wav"; + ProcessHandle swimLoopPid = INVALID_PROCESS; + ProcessHandle oneShotPid = INVALID_PROCESS; + std::string loopTempPath = platform::getTempFilePath("wowee_swim_loop.wav"); + std::string oneShotTempPath = platform::getTempFilePath("wowee_activity.wav"); std::mt19937 rng; std::chrono::steady_clock::time_point lastJumpAt{}; diff --git a/include/audio/footstep_manager.hpp b/include/audio/footstep_manager.hpp index 6efa3d9c..e051b201 100644 --- a/include/audio/footstep_manager.hpp +++ b/include/audio/footstep_manager.hpp @@ -1,10 +1,10 @@ #pragma once +#include "platform/process.hpp" #include #include #include #include -#include #include namespace wowee { @@ -56,8 +56,8 @@ private: SurfaceSamples surfaces[7]; size_t sampleCount = 0; - std::string tempFilePath = "/tmp/wowee_footstep.wav"; - pid_t playerPid = -1; + std::string tempFilePath = platform::getTempFilePath("wowee_footstep.wav"); + ProcessHandle playerPid = INVALID_PROCESS; std::chrono::steady_clock::time_point lastPlayTime = std::chrono::steady_clock::time_point{}; std::mt19937 rng; diff --git a/include/audio/music_manager.hpp b/include/audio/music_manager.hpp index d8ec8b08..981a0673 100644 --- a/include/audio/music_manager.hpp +++ b/include/audio/music_manager.hpp @@ -1,5 +1,6 @@ #pragma once +#include "platform/process.hpp" #include #include @@ -31,7 +32,7 @@ private: pipeline::AssetManager* assetManager = nullptr; std::string currentTrack; std::string tempFilePath; - pid_t playerPid = -1; + ProcessHandle playerPid = INVALID_PROCESS; bool playing = false; // Crossfade state diff --git a/include/network/net_platform.hpp b/include/network/net_platform.hpp new file mode 100644 index 00000000..849c69a6 --- /dev/null +++ b/include/network/net_platform.hpp @@ -0,0 +1,122 @@ +#pragma once + +// Cross-platform socket abstractions for Windows (Winsock2) and POSIX. + +#ifdef _WIN32 + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #include + #include + #pragma comment(lib, "ws2_32.lib") + + using socket_t = SOCKET; + using ssize_t = int; // recv/send return int on Windows + + inline constexpr socket_t INVALID_SOCK = INVALID_SOCKET; + +#else + #include + #include + #include + #include + #include + #include + #include + + using socket_t = int; + + inline constexpr socket_t INVALID_SOCK = -1; + +#endif + +#include + +namespace wowee { +namespace net { + +// ---- Winsock lifecycle (no-op on Linux) ---- + +#ifdef _WIN32 +struct WinsockInit { + WinsockInit() { + WSADATA wsa; + WSAStartup(MAKEWORD(2, 2), &wsa); + } + ~WinsockInit() { WSACleanup(); } +}; +// Call once at program start (e.g. as a static in Application). +inline void ensureInit() { + static WinsockInit instance; +} +#else +inline void ensureInit() {} +#endif + +// ---- Portable helpers ---- + +inline void closeSocket(socket_t s) { +#ifdef _WIN32 + closesocket(s); +#else + close(s); +#endif +} + +inline bool setNonBlocking(socket_t s) { +#ifdef _WIN32 + u_long mode = 1; + return ioctlsocket(s, FIONBIO, &mode) == 0; +#else + int flags = fcntl(s, F_GETFL, 0); + return fcntl(s, F_SETFL, flags | O_NONBLOCK) != -1; +#endif +} + +inline int lastError() { +#ifdef _WIN32 + return WSAGetLastError(); +#else + return errno; +#endif +} + +inline bool isWouldBlock(int err) { +#ifdef _WIN32 + return err == WSAEWOULDBLOCK; +#else + return err == EAGAIN || err == EWOULDBLOCK; +#endif +} + +inline bool isInProgress(int err) { +#ifdef _WIN32 + return err == WSAEWOULDBLOCK || err == WSAEALREADY; +#else + return err == EINPROGRESS; +#endif +} + +inline const char* errorString(int err) { +#ifdef _WIN32 + // Simple thread-local buffer for FormatMessage + thread_local char buf[256]; + FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, err, 0, buf, sizeof(buf), nullptr); + return buf; +#else + return strerror(err); +#endif +} + +// Portable send — Windows recv/send take char*, not void*. +inline ssize_t portableSend(socket_t s, const uint8_t* data, size_t len) { + return ::send(s, reinterpret_cast(data), static_cast(len), 0); +} + +inline ssize_t portableRecv(socket_t s, uint8_t* buf, size_t len) { + return ::recv(s, reinterpret_cast(buf), static_cast(len), 0); +} + +} // namespace net +} // namespace wowee diff --git a/include/network/tcp_socket.hpp b/include/network/tcp_socket.hpp index 2b812295..5a7c36e0 100644 --- a/include/network/tcp_socket.hpp +++ b/include/network/tcp_socket.hpp @@ -1,7 +1,7 @@ #pragma once #include "network/socket.hpp" -#include +#include "network/net_platform.hpp" namespace wowee { namespace network { @@ -22,7 +22,7 @@ private: void tryParsePackets(); size_t getExpectedPacketSize(uint8_t opcode); - int sockfd = -1; + socket_t sockfd = INVALID_SOCK; bool connected = false; std::vector receiveBuffer; }; diff --git a/include/network/world_socket.hpp b/include/network/world_socket.hpp index 6e5b80c9..c256fbe5 100644 --- a/include/network/world_socket.hpp +++ b/include/network/world_socket.hpp @@ -2,6 +2,7 @@ #include "network/socket.hpp" #include "network/packet.hpp" +#include "network/net_platform.hpp" #include "auth/rc4.hpp" #include #include @@ -73,7 +74,7 @@ private: */ void tryParsePackets(); - int sockfd = -1; + socket_t sockfd = INVALID_SOCK; bool connected = false; bool encryptionEnabled = false; diff --git a/include/platform/process.hpp b/include/platform/process.hpp new file mode 100644 index 00000000..c9871d49 --- /dev/null +++ b/include/platform/process.hpp @@ -0,0 +1,157 @@ +#pragma once + +// Cross-platform subprocess helpers for spawning ffplay (audio playback). +// Linux: fork/exec/kill/waitpid. Windows: CreateProcess/TerminateProcess. + +#include +#include + +#ifdef _WIN32 + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #include + + using ProcessHandle = HANDLE; + inline constexpr ProcessHandle INVALID_PROCESS = INVALID_HANDLE_VALUE; + +#else + #include + #include + #include + #include + + using ProcessHandle = pid_t; + inline constexpr ProcessHandle INVALID_PROCESS = -1; + +#endif + +#include + +namespace wowee { +namespace platform { + +// Return a platform-appropriate temp file path for the given filename. +inline std::string getTempFilePath(const std::string& filename) { + auto tmp = std::filesystem::temp_directory_path() / filename; + return tmp.string(); +} + +// Spawn ffplay with the given arguments. Returns process handle. +// args should be the full argument list (e.g. {"-nodisp", "-autoexit", ...}). +// The executable "ffplay" is resolved from PATH. +inline ProcessHandle spawnProcess(const std::vector& args) { +#ifdef _WIN32 + // Build command line string + std::string cmdline = "ffplay"; + for (const auto& arg : args) { + cmdline += " "; + // Quote arguments that contain spaces + if (arg.find(' ') != std::string::npos) { + cmdline += "\"" + arg + "\""; + } else { + cmdline += arg; + } + } + + STARTUPINFOA si{}; + si.cb = sizeof(si); + // Hide the subprocess window and suppress stdout/stderr + si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; + si.wShowWindow = SW_HIDE; + si.hStdInput = INVALID_HANDLE_VALUE; + si.hStdOutput = INVALID_HANDLE_VALUE; + si.hStdError = INVALID_HANDLE_VALUE; + + PROCESS_INFORMATION pi{}; + + // CreateProcessA needs a mutable char buffer for lpCommandLine + std::vector cmdBuf(cmdline.begin(), cmdline.end()); + cmdBuf.push_back('\0'); + + BOOL ok = CreateProcessA( + nullptr, // lpApplicationName — resolve from PATH + cmdBuf.data(), // lpCommandLine + nullptr, nullptr, // process/thread security + FALSE, // inherit handles + CREATE_NO_WINDOW, // creation flags + nullptr, nullptr, // environment, working dir + &si, &pi + ); + + if (!ok) { + return INVALID_PROCESS; + } + + // We don't need the thread handle + CloseHandle(pi.hThread); + return pi.hProcess; + +#else + pid_t pid = fork(); + if (pid == 0) { + // Child process + setpgid(0, 0); + freopen("/dev/null", "w", stdout); + freopen("/dev/null", "w", stderr); + + // Build argv for exec + std::vector argv; + argv.push_back("ffplay"); + for (const auto& arg : args) { + argv.push_back(arg.c_str()); + } + argv.push_back(nullptr); + + execvp("ffplay", const_cast(argv.data())); + _exit(1); // exec failed + } + return (pid > 0) ? pid : INVALID_PROCESS; +#endif +} + +// Kill a subprocess (and its children on Linux). +inline void killProcess(ProcessHandle& handle) { + if (handle == INVALID_PROCESS) return; + +#ifdef _WIN32 + TerminateProcess(handle, 0); + WaitForSingleObject(handle, 2000); + CloseHandle(handle); +#else + kill(-handle, SIGTERM); // kill process group + kill(handle, SIGTERM); + int status = 0; + waitpid(handle, &status, 0); +#endif + + handle = INVALID_PROCESS; +} + +// Check if a process has exited. If so, clean up and set handle to INVALID_PROCESS. +// Returns true if the process is still running. +inline bool isProcessRunning(ProcessHandle& handle) { + if (handle == INVALID_PROCESS) return false; + +#ifdef _WIN32 + DWORD result = WaitForSingleObject(handle, 0); + if (result == WAIT_OBJECT_0) { + // Process has exited + CloseHandle(handle); + handle = INVALID_PROCESS; + return false; + } + return true; +#else + int status = 0; + pid_t result = waitpid(handle, &status, WNOHANG); + if (result == handle) { + handle = INVALID_PROCESS; + return false; + } + return true; +#endif +} + +} // namespace platform +} // namespace wowee diff --git a/src/audio/activity_sound_manager.cpp b/src/audio/activity_sound_manager.cpp index 120ea5b7..3aaa4d60 100644 --- a/src/audio/activity_sound_manager.cpp +++ b/src/audio/activity_sound_manager.cpp @@ -1,13 +1,11 @@ #include "audio/activity_sound_manager.hpp" #include "pipeline/asset_manager.hpp" #include "core/logger.hpp" +#include "platform/process.hpp" #include -#include #include #include #include -#include -#include namespace wowee { namespace audio { @@ -193,7 +191,7 @@ void ActivitySoundManager::rebuildHardLandClipsForProfile(const std::string& rac bool ActivitySoundManager::playOneShot(const std::vector& clips, float volume, float pitchLo, float pitchHi) { if (clips.empty()) return false; reapProcesses(); - if (oneShotPid > 0) return false; + if (oneShotPid != INVALID_PROCESS) return false; std::uniform_int_distribution clipDist(0, clips.size() - 1); const Sample& sample = clips[clipDist(rng)]; @@ -209,24 +207,16 @@ bool ActivitySoundManager::playOneShot(const std::vector& clips, float v std::string filter = "asetrate=44100*" + std::to_string(pitch) + ",aresample=44100,volume=" + std::to_string(volume); - pid_t pid = fork(); - if (pid == 0) { - setpgid(0, 0); - FILE* outFile = freopen("/dev/null", "w", stdout); - FILE* errFile = freopen("/dev/null", "w", stderr); - (void)outFile; (void)errFile; - execlp("ffplay", "ffplay", "-nodisp", "-autoexit", "-loglevel", "quiet", - "-af", filter.c_str(), oneShotTempPath.c_str(), nullptr); - _exit(1); - } else if (pid > 0) { - oneShotPid = pid; - return true; - } - return false; + oneShotPid = platform::spawnProcess({ + "-nodisp", "-autoexit", "-loglevel", "quiet", + "-af", filter, oneShotTempPath + }); + + return oneShotPid != INVALID_PROCESS; } void ActivitySoundManager::startSwimLoop() { - if (swimLoopPid > 0 || swimLoopClips.empty()) return; + if (swimLoopPid != INVALID_PROCESS || swimLoopClips.empty()) return; std::uniform_int_distribution clipDist(0, swimLoopClips.size() - 1); const Sample& sample = swimLoopClips[clipDist(rng)]; @@ -238,50 +228,26 @@ void ActivitySoundManager::startSwimLoop() { float volume = swimMoving ? 0.85f : 0.65f; std::string filter = "volume=" + std::to_string(volume); - pid_t pid = fork(); - if (pid == 0) { - setpgid(0, 0); - FILE* outFile = freopen("/dev/null", "w", stdout); - FILE* errFile = freopen("/dev/null", "w", stderr); - (void)outFile; (void)errFile; - execlp("ffplay", "ffplay", "-nodisp", "-autoexit", "-loop", "0", "-loglevel", "quiet", - "-af", filter.c_str(), loopTempPath.c_str(), nullptr); - _exit(1); - } else if (pid > 0) { - swimLoopPid = pid; - } + swimLoopPid = platform::spawnProcess({ + "-nodisp", "-autoexit", "-loop", "0", "-loglevel", "quiet", + "-af", filter, loopTempPath + }); } void ActivitySoundManager::stopSwimLoop() { - if (swimLoopPid > 0) { - kill(-swimLoopPid, SIGTERM); - kill(swimLoopPid, SIGTERM); - int status = 0; - waitpid(swimLoopPid, &status, 0); - swimLoopPid = -1; - } + platform::killProcess(swimLoopPid); } void ActivitySoundManager::stopOneShot() { - if (oneShotPid > 0) { - kill(-oneShotPid, SIGTERM); - kill(oneShotPid, SIGTERM); - int status = 0; - waitpid(oneShotPid, &status, 0); - oneShotPid = -1; - } + platform::killProcess(oneShotPid); } void ActivitySoundManager::reapProcesses() { - if (oneShotPid > 0) { - int status = 0; - pid_t result = waitpid(oneShotPid, &status, WNOHANG); - if (result == oneShotPid) oneShotPid = -1; + if (oneShotPid != INVALID_PROCESS) { + platform::isProcessRunning(oneShotPid); } - if (swimLoopPid > 0) { - int status = 0; - pid_t result = waitpid(swimLoopPid, &status, WNOHANG); - if (result == swimLoopPid) swimLoopPid = -1; + if (swimLoopPid != INVALID_PROCESS) { + platform::isProcessRunning(swimLoopPid); } } diff --git a/src/audio/footstep_manager.cpp b/src/audio/footstep_manager.cpp index f856f194..0849f4ce 100644 --- a/src/audio/footstep_manager.cpp +++ b/src/audio/footstep_manager.cpp @@ -1,13 +1,11 @@ #include "audio/footstep_manager.hpp" #include "pipeline/asset_manager.hpp" #include "core/logger.hpp" +#include "platform/process.hpp" #include -#include #include #include #include -#include -#include namespace wowee { namespace audio { @@ -114,24 +112,14 @@ void FootstepManager::preloadSurface(FootstepSurface surface, const std::vector< } void FootstepManager::stopCurrentProcess() { - if (playerPid > 0) { - kill(-playerPid, SIGTERM); - kill(playerPid, SIGTERM); - int status = 0; - waitpid(playerPid, &status, 0); - playerPid = -1; - } + platform::killProcess(playerPid); } void FootstepManager::reapFinishedProcess() { - if (playerPid <= 0) { + if (playerPid == INVALID_PROCESS) { return; } - int status = 0; - pid_t result = waitpid(playerPid, &status, WNOHANG); - if (result == playerPid) { - playerPid = -1; - } + platform::isProcessRunning(playerPid); } bool FootstepManager::playRandomStep(FootstepSurface surface, bool sprinting) { @@ -153,7 +141,7 @@ bool FootstepManager::playRandomStep(FootstepSurface surface, bool sprinting) { } // Keep one active step at a time to avoid ffplay process buildup. - if (playerPid > 0) { + if (playerPid != INVALID_PROCESS) { return false; } @@ -178,18 +166,12 @@ bool FootstepManager::playRandomStep(FootstepSurface surface, bool sprinting) { std::string filter = "asetrate=44100*" + std::to_string(pitch) + ",aresample=44100,volume=" + std::to_string(volume); - pid_t pid = fork(); - if (pid == 0) { - setpgid(0, 0); - FILE* outFile = freopen("/dev/null", "w", stdout); - FILE* errFile = freopen("/dev/null", "w", stderr); - (void)outFile; - (void)errFile; - execlp("ffplay", "ffplay", "-nodisp", "-autoexit", "-loglevel", "quiet", - "-af", filter.c_str(), tempFilePath.c_str(), nullptr); - _exit(1); - } else if (pid > 0) { - playerPid = pid; + playerPid = platform::spawnProcess({ + "-nodisp", "-autoexit", "-loglevel", "quiet", + "-af", filter, tempFilePath + }); + + if (playerPid != INVALID_PROCESS) { lastPlayTime = now; return true; } diff --git a/src/audio/music_manager.cpp b/src/audio/music_manager.cpp index 4ec06b6d..f84aeacd 100644 --- a/src/audio/music_manager.cpp +++ b/src/audio/music_manager.cpp @@ -1,17 +1,14 @@ #include "audio/music_manager.hpp" #include "pipeline/asset_manager.hpp" #include "core/logger.hpp" +#include "platform/process.hpp" #include -#include -#include -#include -#include namespace wowee { namespace audio { MusicManager::MusicManager() { - tempFilePath = "/tmp/wowee_music.mp3"; + tempFilePath = platform::getTempFilePath("wowee_music.mp3"); } MusicManager::~MusicManager() { @@ -54,29 +51,24 @@ void MusicManager::playMusic(const std::string& mpqPath, bool loop) { out.close(); // Play with ffplay in background - pid_t pid = fork(); - if (pid == 0) { - // Child process — create new process group so we can kill all children - setpgid(0, 0); - // Redirect output to /dev/null - freopen("/dev/null", "w", stdout); - freopen("/dev/null", "w", stderr); + std::vector args; + args.push_back("-nodisp"); + args.push_back("-autoexit"); + if (loop) { + args.push_back("-loop"); + args.push_back("0"); + } + args.push_back("-volume"); + args.push_back("30"); + args.push_back(tempFilePath); - if (loop) { - execlp("ffplay", "ffplay", "-nodisp", "-autoexit", "-loop", "0", - "-volume", "30", tempFilePath.c_str(), nullptr); - } else { - execlp("ffplay", "ffplay", "-nodisp", "-autoexit", - "-volume", "30", tempFilePath.c_str(), nullptr); - } - _exit(1); // exec failed - } else if (pid > 0) { - playerPid = pid; + playerPid = platform::spawnProcess(args); + if (playerPid != INVALID_PROCESS) { playing = true; currentTrack = mpqPath; LOG_INFO("Music: Playing ", mpqPath); } else { - LOG_ERROR("Music: fork() failed"); + LOG_ERROR("Music: Failed to spawn ffplay process"); } } @@ -104,12 +96,8 @@ void MusicManager::crossfadeTo(const std::string& mpqPath, float fadeMs) { void MusicManager::update(float deltaTime) { // Check if player process is still running - if (playerPid > 0) { - int status; - pid_t result = waitpid(playerPid, &status, WNOHANG); - if (result == playerPid) { - // Process ended - playerPid = -1; + if (playerPid != INVALID_PROCESS) { + if (!platform::isProcessRunning(playerPid)) { playing = false; } } @@ -127,13 +115,8 @@ void MusicManager::update(float deltaTime) { } void MusicManager::stopCurrentProcess() { - if (playerPid > 0) { - // Kill the entire process group (ffplay may spawn children) - kill(-playerPid, SIGTERM); - kill(playerPid, SIGTERM); - int status; - waitpid(playerPid, &status, 0); - playerPid = -1; + if (playerPid != INVALID_PROCESS) { + platform::killProcess(playerPid); playing = false; } } diff --git a/src/network/tcp_socket.cpp b/src/network/tcp_socket.cpp index 81203238..93fb212b 100644 --- a/src/network/tcp_socket.cpp +++ b/src/network/tcp_socket.cpp @@ -1,18 +1,14 @@ #include "network/tcp_socket.hpp" #include "network/packet.hpp" +#include "network/net_platform.hpp" #include "core/logger.hpp" -#include -#include -#include -#include -#include -#include -#include namespace wowee { namespace network { -TCPSocket::TCPSocket() = default; +TCPSocket::TCPSocket() { + net::ensureInit(); +} TCPSocket::~TCPSocket() { disconnect(); @@ -23,21 +19,20 @@ bool TCPSocket::connect(const std::string& host, uint16_t port) { // Create socket sockfd = socket(AF_INET, SOCK_STREAM, 0); - if (sockfd < 0) { + if (sockfd == INVALID_SOCK) { LOG_ERROR("Failed to create socket"); return false; } // Set non-blocking - int flags = fcntl(sockfd, F_GETFL, 0); - fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); + net::setNonBlocking(sockfd); // Resolve host struct hostent* server = gethostbyname(host.c_str()); if (server == nullptr) { LOG_ERROR("Failed to resolve host: ", host); - close(sockfd); - sockfd = -1; + net::closeSocket(sockfd); + sockfd = INVALID_SOCK; return false; } @@ -49,11 +44,14 @@ bool TCPSocket::connect(const std::string& host, uint16_t port) { serverAddr.sin_port = htons(port); int result = ::connect(sockfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)); - if (result < 0 && errno != EINPROGRESS) { - LOG_ERROR("Failed to connect: ", strerror(errno)); - close(sockfd); - sockfd = -1; - return false; + if (result < 0) { + int err = net::lastError(); + if (!net::isInProgress(err)) { + LOG_ERROR("Failed to connect: ", net::errorString(err)); + net::closeSocket(sockfd); + sockfd = INVALID_SOCK; + return false; + } } connected = true; @@ -62,9 +60,9 @@ bool TCPSocket::connect(const std::string& host, uint16_t port) { } void TCPSocket::disconnect() { - if (sockfd >= 0) { - close(sockfd); - sockfd = -1; + if (sockfd != INVALID_SOCK) { + net::closeSocket(sockfd); + sockfd = INVALID_SOCK; } connected = false; receiveBuffer.clear(); @@ -87,9 +85,9 @@ void TCPSocket::send(const Packet& packet) { " size=", sendData.size(), " bytes"); // Send complete packet - ssize_t sent = ::send(sockfd, sendData.data(), sendData.size(), 0); + ssize_t sent = net::portableSend(sockfd, sendData.data(), sendData.size()); if (sent < 0) { - LOG_ERROR("Send failed: ", strerror(errno)); + LOG_ERROR("Send failed: ", net::errorString(net::lastError())); } else if (static_cast(sent) != sendData.size()) { LOG_WARNING("Partial send: ", sent, " of ", sendData.size(), " bytes"); } @@ -100,7 +98,7 @@ void TCPSocket::update() { // Receive data into buffer uint8_t buffer[4096]; - ssize_t received = recv(sockfd, buffer, sizeof(buffer), 0); + ssize_t received = net::portableRecv(sockfd, buffer, sizeof(buffer)); if (received > 0) { LOG_DEBUG("Received ", received, " bytes from server"); @@ -113,9 +111,12 @@ void TCPSocket::update() { LOG_INFO("Connection closed by server"); disconnect(); } - else if (errno != EAGAIN && errno != EWOULDBLOCK) { - LOG_ERROR("Receive failed: ", strerror(errno)); - disconnect(); + else { + int err = net::lastError(); + if (!net::isWouldBlock(err)) { + LOG_ERROR("Receive failed: ", net::errorString(err)); + disconnect(); + } } } diff --git a/src/network/world_socket.cpp b/src/network/world_socket.cpp index 1c3fbdbc..f6528681 100644 --- a/src/network/world_socket.cpp +++ b/src/network/world_socket.cpp @@ -1,14 +1,8 @@ #include "network/world_socket.hpp" #include "network/packet.hpp" +#include "network/net_platform.hpp" #include "auth/crypto.hpp" #include "core/logger.hpp" -#include -#include -#include -#include -#include -#include -#include namespace wowee { namespace network { @@ -24,7 +18,9 @@ static const uint8_t DECRYPT_KEY[] = { 0x12, 0xDD, 0xC0, 0x93, 0x42, 0x91, 0x53, 0x57 }; -WorldSocket::WorldSocket() = default; +WorldSocket::WorldSocket() { + net::ensureInit(); +} WorldSocket::~WorldSocket() { disconnect(); @@ -35,21 +31,20 @@ bool WorldSocket::connect(const std::string& host, uint16_t port) { // Create socket sockfd = socket(AF_INET, SOCK_STREAM, 0); - if (sockfd < 0) { + if (sockfd == INVALID_SOCK) { LOG_ERROR("Failed to create socket"); return false; } // Set non-blocking - int flags = fcntl(sockfd, F_GETFL, 0); - fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); + net::setNonBlocking(sockfd); // Resolve host struct hostent* server = gethostbyname(host.c_str()); if (server == nullptr) { LOG_ERROR("Failed to resolve host: ", host); - close(sockfd); - sockfd = -1; + net::closeSocket(sockfd); + sockfd = INVALID_SOCK; return false; } @@ -61,11 +56,14 @@ bool WorldSocket::connect(const std::string& host, uint16_t port) { serverAddr.sin_port = htons(port); int result = ::connect(sockfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)); - if (result < 0 && errno != EINPROGRESS) { - LOG_ERROR("Failed to connect: ", strerror(errno)); - close(sockfd); - sockfd = -1; - return false; + if (result < 0) { + int err = net::lastError(); + if (!net::isInProgress(err)) { + LOG_ERROR("Failed to connect: ", net::errorString(err)); + net::closeSocket(sockfd); + sockfd = INVALID_SOCK; + return false; + } } connected = true; @@ -74,9 +72,9 @@ bool WorldSocket::connect(const std::string& host, uint16_t port) { } void WorldSocket::disconnect() { - if (sockfd >= 0) { - close(sockfd); - sockfd = -1; + if (sockfd != INVALID_SOCK) { + net::closeSocket(sockfd); + sockfd = INVALID_SOCK; } connected = false; encryptionEnabled = false; @@ -122,9 +120,9 @@ void WorldSocket::send(const Packet& packet) { " size=", size, " bytes (", sendData.size(), " total)"); // Send complete packet - ssize_t sent = ::send(sockfd, sendData.data(), sendData.size(), 0); + ssize_t sent = net::portableSend(sockfd, sendData.data(), sendData.size()); if (sent < 0) { - LOG_ERROR("Send failed: ", strerror(errno)); + LOG_ERROR("Send failed: ", net::errorString(net::lastError())); } else if (static_cast(sent) != sendData.size()) { LOG_WARNING("Partial send: ", sent, " of ", sendData.size(), " bytes"); } @@ -135,7 +133,7 @@ void WorldSocket::update() { // Receive data into buffer uint8_t buffer[4096]; - ssize_t received = recv(sockfd, buffer, sizeof(buffer), 0); + ssize_t received = net::portableRecv(sockfd, buffer, sizeof(buffer)); if (received > 0) { LOG_DEBUG("Received ", received, " bytes from world server"); @@ -148,9 +146,12 @@ void WorldSocket::update() { LOG_INFO("World server connection closed"); disconnect(); } - else if (errno != EAGAIN && errno != EWOULDBLOCK) { - LOG_ERROR("Receive failed: ", strerror(errno)); - disconnect(); + else { + int err = net::lastError(); + if (!net::isWouldBlock(err)) { + LOG_ERROR("Receive failed: ", net::errorString(err)); + disconnect(); + } } } diff --git a/src/rendering/character_renderer.cpp b/src/rendering/character_renderer.cpp index d95df084..5174082f 100644 --- a/src/rendering/character_renderer.cpp +++ b/src/rendering/character_renderer.cpp @@ -27,6 +27,7 @@ #include #include #include +#include namespace wowee { namespace rendering { @@ -317,7 +318,7 @@ GLuint CharacterRenderer::compositeTextures(const std::vector& laye // Debug: save overlay to disk { - std::string fname = "/tmp/overlay_debug_" + std::to_string(layer) + ".rgba"; + std::string fname = (std::filesystem::temp_directory_path() / ("overlay_debug_" + std::to_string(layer) + ".rgba")).string(); FILE* f = fopen(fname.c_str(), "wb"); if (f) { fwrite(&overlay.width, 4, 1, f); @@ -394,14 +395,15 @@ GLuint CharacterRenderer::compositeTextures(const std::vector& laye // Debug: save composite as raw RGBA file { - FILE* f = fopen("/tmp/composite_debug.rgba", "wb"); + std::string dbgPath = (std::filesystem::temp_directory_path() / "composite_debug.rgba").string(); + FILE* f = fopen(dbgPath.c_str(), "wb"); if (f) { // Write width, height as 4 bytes each, then pixel data fwrite(&width, 4, 1, f); fwrite(&height, 4, 1, f); fwrite(composite.data(), 1, composite.size(), f); fclose(f); - core::Logger::getInstance().info("DEBUG: saved composite to /tmp/composite_debug.rgba"); + core::Logger::getInstance().info("DEBUG: saved composite to ", dbgPath); } }