mirror of
https://forge.banditvault.co.uk/racoon/MinecraftConsoles.git
synced 2026-07-19 14:18:07 +00:00
Add TCP-over-WebSocket relay client for Minecraft
- Implemented Win64LceLiveRelay.h and Win64LceLiveSignaling.cpp to facilitate TCP-over-WebSocket communication for Minecraft, allowing game traffic to route through the LCELive relay server when direct TCP is blocked. - Introduced signaling mechanisms for host and joiner connections, including session management and candidate exchange. - Added logging functionality in Windows_Log.cpp and Windows_Log.h for better debugging and session tracking. - Created build-release.bat script for streamlined build and deployment process, including exclusion of unnecessary files.
This commit is contained in:
724
Minecraft.Client/Windows64/Windows64_LceLiveSignaling.cpp
Normal file
724
Minecraft.Client/Windows64/Windows64_LceLiveSignaling.cpp
Normal file
@@ -0,0 +1,724 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <winhttp.h>
|
||||
#include <objbase.h> // CoCreateGuid
|
||||
|
||||
#include "Windows64_LceLiveSignaling.h"
|
||||
#include "Windows64_LceLive.h"
|
||||
#include "Windows64_LceLiveP2P.h"
|
||||
#include "Windows64_Log.h"
|
||||
#include "../../../Minecraft.Server/vendor/nlohmann/json.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#pragma comment(lib, "Winhttp.lib")
|
||||
|
||||
// ============================================================================
|
||||
// Internal implementation
|
||||
// ============================================================================
|
||||
|
||||
namespace
|
||||
{
|
||||
using Json = nlohmann::json;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
std::wstring Utf8ToWideLocal(const std::string& s)
|
||||
{
|
||||
if (s.empty()) return L"";
|
||||
const int n = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, nullptr, 0);
|
||||
if (n <= 0) return L"";
|
||||
std::wstring out(static_cast<size_t>(n), L'\0');
|
||||
MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, &out[0], n);
|
||||
if (!out.empty() && out.back() == L'\0') out.pop_back();
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string WideToUtf8Local(const std::wstring& s)
|
||||
{
|
||||
if (s.empty()) return "";
|
||||
const int n = WideCharToMultiByte(CP_UTF8, 0, s.c_str(), -1, nullptr, 0, nullptr, nullptr);
|
||||
if (n <= 0) return "";
|
||||
std::string out(static_cast<size_t>(n), '\0');
|
||||
WideCharToMultiByte(CP_UTF8, 0, s.c_str(), -1, &out[0], n, nullptr, nullptr);
|
||||
if (!out.empty() && out.back() == '\0') out.pop_back();
|
||||
return out;
|
||||
}
|
||||
|
||||
// Generate a lowercase hyphenated UUID string (e.g. "550e8400-e29b-41d4-a716-446655440000").
|
||||
std::string GenerateUuid()
|
||||
{
|
||||
GUID guid = {};
|
||||
CoCreateGuid(&guid);
|
||||
|
||||
char buf[40] = {};
|
||||
snprintf(buf, sizeof(buf),
|
||||
"%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
|
||||
guid.Data1, guid.Data2, guid.Data3,
|
||||
guid.Data4[0], guid.Data4[1],
|
||||
guid.Data4[2], guid.Data4[3], guid.Data4[4],
|
||||
guid.Data4[5], guid.Data4[6], guid.Data4[7]);
|
||||
return buf;
|
||||
}
|
||||
|
||||
// Read the API base URL the same way Windows64_LceLive.cpp does:
|
||||
// LCELIVE_API_BASE_URL env var → lcelive.properties → localhost:5187.
|
||||
std::string GetBaseUrl()
|
||||
{
|
||||
char envValue[512] = {};
|
||||
const DWORD len = GetEnvironmentVariableA("LCELIVE_API_BASE_URL", envValue,
|
||||
static_cast<DWORD>(sizeof(envValue)));
|
||||
if (len > 0 && len < sizeof(envValue))
|
||||
return std::string(envValue);
|
||||
|
||||
// Try lcelive.properties next to the .exe
|
||||
char exePath[MAX_PATH] = {};
|
||||
GetModuleFileNameA(nullptr, exePath, MAX_PATH);
|
||||
std::string props(exePath);
|
||||
const size_t lastSlash = props.find_last_of("\\/");
|
||||
if (lastSlash != std::string::npos)
|
||||
props = props.substr(0, lastSlash + 1);
|
||||
props += "lcelive.properties";
|
||||
|
||||
FILE* f = nullptr;
|
||||
if (fopen_s(&f, props.c_str(), "rb") == 0 && f != nullptr)
|
||||
{
|
||||
char line[512] = {};
|
||||
while (fgets(line, sizeof(line), f) != nullptr)
|
||||
{
|
||||
std::string s(line);
|
||||
while (!s.empty() && (s.back() == '\n' || s.back() == '\r')) s.pop_back();
|
||||
if (s.substr(0, 12) == "api_base_url")
|
||||
{
|
||||
const size_t eq = s.find('=');
|
||||
if (eq != std::string::npos) { fclose(f); return s.substr(eq + 1); }
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
return "http://localhost:5187";
|
||||
}
|
||||
|
||||
// URL-percent-encode a string (for safe use in query parameters).
|
||||
std::string UrlEncode(const std::string& s)
|
||||
{
|
||||
std::string out;
|
||||
out.reserve(s.size() * 3);
|
||||
for (unsigned char c : s)
|
||||
{
|
||||
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~')
|
||||
out += static_cast<char>(c);
|
||||
else
|
||||
{
|
||||
char hex[4] = {};
|
||||
snprintf(hex, sizeof(hex), "%%%02X", c);
|
||||
out += hex;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Build the JSON candidate message we send over the signaling channel.
|
||||
std::string BuildCandidateJson(const std::string& ip, int port,
|
||||
Win64LceLiveP2P::EConnMethod method)
|
||||
{
|
||||
const char* methodStr =
|
||||
(method == Win64LceLiveP2P::EConnMethod::UPnP) ? "upnp" : "stun";
|
||||
|
||||
Json j;
|
||||
j["type"] = "candidate";
|
||||
j["ip"] = ip;
|
||||
j["port"] = port;
|
||||
j["method"] = methodStr;
|
||||
return j.dump();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Worker context — passed to the background thread
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
struct WorkerContext
|
||||
{
|
||||
bool isHost;
|
||||
std::string sessionId;
|
||||
std::string ourIp;
|
||||
int ourPort;
|
||||
Win64LceLiveP2P::EConnMethod ourMethod;
|
||||
std::string accessToken;
|
||||
std::string baseUrl;
|
||||
|
||||
// Written by worker to signal results back to main thread.
|
||||
volatile bool workerDone;
|
||||
bool wsConnected; // WebSocket opened successfully
|
||||
bool peerReceived; // Peer candidate decoded
|
||||
std::string peerIp;
|
||||
int peerPort;
|
||||
bool peerNeedsHolePunch;
|
||||
std::string errorMessage;
|
||||
|
||||
// Handle the main thread can close to unblock a stuck WinHttpWebSocketReceive.
|
||||
volatile HINTERNET wsHandle;
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Runtime state
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
struct SignalingState
|
||||
{
|
||||
bool initialized;
|
||||
CRITICAL_SECTION lock;
|
||||
|
||||
Win64LceLiveSignaling::ESignalingState state;
|
||||
std::string sessionId;
|
||||
std::string peerIp;
|
||||
int peerPort;
|
||||
bool peerNeedsHolePunch;
|
||||
std::string lastError;
|
||||
|
||||
// Set by PrepareJoin(); cleared when JoinerConnect() fires.
|
||||
std::string pendingJoinerSessionId;
|
||||
|
||||
HANDLE workerThread;
|
||||
WorkerContext* workerCtx;
|
||||
};
|
||||
|
||||
static SignalingState g_sig = {};
|
||||
static INIT_ONCE g_initOnce = INIT_ONCE_STATIC_INIT;
|
||||
|
||||
BOOL CALLBACK InitSignalingState(PINIT_ONCE, PVOID, PVOID*)
|
||||
{
|
||||
InitializeCriticalSection(&g_sig.lock);
|
||||
g_sig.state = Win64LceLiveSignaling::ESignalingState::Idle;
|
||||
g_sig.initialized = true;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void EnsureInitialized()
|
||||
{
|
||||
InitOnceExecuteOnce(&g_initOnce, &InitSignalingState, nullptr, nullptr);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// WebSocket worker thread
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
DWORD WINAPI SignalingWorkerProc(LPVOID param)
|
||||
{
|
||||
WorkerContext* ctx = static_cast<WorkerContext*>(param);
|
||||
|
||||
// ---- Parse the base URL ----
|
||||
const std::wstring baseUrlW = Utf8ToWideLocal(ctx->baseUrl);
|
||||
|
||||
std::vector<wchar_t> hostBuf(256, 0);
|
||||
std::vector<wchar_t> pathBuf(2048, 0);
|
||||
|
||||
URL_COMPONENTSW components = {};
|
||||
components.dwStructSize = sizeof(components);
|
||||
components.lpszHostName = hostBuf.data();
|
||||
components.dwHostNameLength = static_cast<DWORD>(hostBuf.size());
|
||||
components.lpszUrlPath = pathBuf.data();
|
||||
components.dwUrlPathLength = static_cast<DWORD>(pathBuf.size());
|
||||
|
||||
if (!WinHttpCrackUrl(baseUrlW.c_str(), static_cast<DWORD>(baseUrlW.size()), 0, &components))
|
||||
{
|
||||
ctx->errorMessage = "Signaling: WinHttpCrackUrl failed for base URL";
|
||||
ctx->workerDone = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const bool secure = (components.nScheme == INTERNET_SCHEME_HTTPS);
|
||||
const std::wstring hostW(components.lpszHostName, components.dwHostNameLength);
|
||||
|
||||
// Build the WebSocket path including query params.
|
||||
// Auth goes in the Authorization header (server prefers that over ?token=).
|
||||
const std::wstring basePath = components.lpszUrlPath
|
||||
? std::wstring(components.lpszUrlPath, components.dwUrlPathLength)
|
||||
: L"";
|
||||
|
||||
const std::wstring wsPath = basePath + L"/api/signaling/ws"
|
||||
+ L"?sessionId=" + Utf8ToWideLocal(ctx->sessionId)
|
||||
+ L"&role=" + Utf8ToWideLocal(ctx->isHost ? "host" : "joiner");
|
||||
|
||||
LCELOG("SIG", "connecting to %s%ls (role=%s sessionId=%s)",
|
||||
secure ? "wss://" : "ws://",
|
||||
(hostW + wsPath).c_str(),
|
||||
ctx->isHost ? "host" : "joiner",
|
||||
ctx->sessionId.c_str());
|
||||
|
||||
// ---- Open WinHTTP session ----
|
||||
HINTERNET hSession = WinHttpOpen(
|
||||
L"MCLCE-LceLive/1.0",
|
||||
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
|
||||
WINHTTP_NO_PROXY_NAME,
|
||||
WINHTTP_NO_PROXY_BYPASS,
|
||||
0);
|
||||
|
||||
if (hSession == nullptr)
|
||||
{
|
||||
ctx->errorMessage = "Signaling: WinHttpOpen failed";
|
||||
ctx->workerDone = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
WinHttpSetTimeouts(hSession, 10000, 10000, 30000, 30000);
|
||||
|
||||
HINTERNET hConnect = WinHttpConnect(hSession, hostW.c_str(), components.nPort, 0);
|
||||
if (hConnect == nullptr)
|
||||
{
|
||||
WinHttpCloseHandle(hSession);
|
||||
ctx->errorMessage = "Signaling: WinHttpConnect failed";
|
||||
ctx->workerDone = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- Open GET request (will be upgraded to WebSocket) ----
|
||||
const DWORD flags = secure ? WINHTTP_FLAG_SECURE : 0;
|
||||
HINTERNET hRequest = WinHttpOpenRequest(
|
||||
hConnect,
|
||||
L"GET",
|
||||
wsPath.c_str(),
|
||||
nullptr,
|
||||
WINHTTP_NO_REFERER,
|
||||
WINHTTP_DEFAULT_ACCEPT_TYPES,
|
||||
flags);
|
||||
|
||||
if (hRequest == nullptr)
|
||||
{
|
||||
WinHttpCloseHandle(hConnect);
|
||||
WinHttpCloseHandle(hSession);
|
||||
ctx->errorMessage = "Signaling: WinHttpOpenRequest failed";
|
||||
ctx->workerDone = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Mark this request for WebSocket upgrade BEFORE sending.
|
||||
WinHttpSetOption(hRequest, WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET, nullptr, 0);
|
||||
|
||||
// Add Authorization header.
|
||||
if (!ctx->accessToken.empty())
|
||||
{
|
||||
const std::wstring authHeader =
|
||||
L"Authorization: Bearer " + Utf8ToWideLocal(ctx->accessToken);
|
||||
WinHttpAddRequestHeaders(hRequest,
|
||||
authHeader.c_str(),
|
||||
static_cast<DWORD>(authHeader.size()),
|
||||
WINHTTP_ADDREQ_FLAG_ADD);
|
||||
}
|
||||
|
||||
// Send the upgrade request.
|
||||
if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
|
||||
WINHTTP_NO_REQUEST_DATA, 0, 0, 0))
|
||||
{
|
||||
WinHttpCloseHandle(hRequest);
|
||||
WinHttpCloseHandle(hConnect);
|
||||
WinHttpCloseHandle(hSession);
|
||||
ctx->errorMessage = "Signaling: WinHttpSendRequest failed (WSA "
|
||||
+ std::to_string(GetLastError()) + ")";
|
||||
ctx->workerDone = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!WinHttpReceiveResponse(hRequest, nullptr))
|
||||
{
|
||||
WinHttpCloseHandle(hRequest);
|
||||
WinHttpCloseHandle(hConnect);
|
||||
WinHttpCloseHandle(hSession);
|
||||
ctx->errorMessage = "Signaling: WinHttpReceiveResponse failed — server may be down";
|
||||
ctx->workerDone = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- Complete WebSocket upgrade ----
|
||||
HINTERNET hWs = WinHttpWebSocketCompleteUpgrade(hRequest, 0);
|
||||
WinHttpCloseHandle(hRequest);
|
||||
|
||||
if (hWs == nullptr)
|
||||
{
|
||||
WinHttpCloseHandle(hConnect);
|
||||
WinHttpCloseHandle(hSession);
|
||||
ctx->errorMessage = "Signaling: WebSocket upgrade failed — server returned non-101";
|
||||
ctx->workerDone = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Make the handle visible to the main thread so Close() can abort the receive.
|
||||
ctx->wsHandle = hWs;
|
||||
ctx->wsConnected = true;
|
||||
|
||||
LCELOG("SIG", "WebSocket connected (session %s)", ctx->sessionId.c_str());
|
||||
|
||||
// ---- Send our P2P candidate ----
|
||||
const std::string candidateJson =
|
||||
BuildCandidateJson(ctx->ourIp, ctx->ourPort, ctx->ourMethod);
|
||||
|
||||
DWORD sendResult = WinHttpWebSocketSend(
|
||||
hWs,
|
||||
WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE,
|
||||
const_cast<PVOID>(reinterpret_cast<const void*>(candidateJson.c_str())),
|
||||
static_cast<DWORD>(candidateJson.size()));
|
||||
|
||||
if (sendResult != ERROR_SUCCESS)
|
||||
{
|
||||
WinHttpWebSocketClose(hWs, WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS, nullptr, 0);
|
||||
WinHttpCloseHandle(hWs);
|
||||
WinHttpCloseHandle(hConnect);
|
||||
WinHttpCloseHandle(hSession);
|
||||
ctx->wsHandle = nullptr;
|
||||
ctx->errorMessage = "Signaling: failed to send candidate";
|
||||
ctx->workerDone = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
LCELOG("SIG", "candidate sent %s", candidateJson.c_str());
|
||||
|
||||
// ---- Receive loop: wait for peer's candidate ----
|
||||
// We also pass through joiner_connected notifications (host only) so the
|
||||
// log shows when the joiner arrives.
|
||||
std::vector<BYTE> recvBuf(8192);
|
||||
bool done = false;
|
||||
|
||||
while (!done)
|
||||
{
|
||||
DWORD bytesRead = 0;
|
||||
WINHTTP_WEB_SOCKET_BUFFER_TYPE bufType = WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE;
|
||||
|
||||
const DWORD recvErr = WinHttpWebSocketReceive(
|
||||
hWs,
|
||||
recvBuf.data(),
|
||||
static_cast<DWORD>(recvBuf.size() - 1),
|
||||
&bytesRead,
|
||||
&bufType);
|
||||
|
||||
if (recvErr != ERROR_SUCCESS)
|
||||
{
|
||||
// Closed from main thread (Close() called) or network error.
|
||||
LCELOG("SIG", "receive ended (%lu)", recvErr);
|
||||
break;
|
||||
}
|
||||
|
||||
if (bufType != WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE &&
|
||||
bufType != WINHTTP_WEB_SOCKET_UTF8_FRAGMENT_BUFFER_TYPE)
|
||||
continue; // binary or close frame — ignore
|
||||
|
||||
recvBuf[bytesRead] = 0;
|
||||
const std::string msg(reinterpret_cast<char*>(recvBuf.data()), bytesRead);
|
||||
|
||||
LCELOG("SIG", "recv %s", msg.c_str());
|
||||
|
||||
try
|
||||
{
|
||||
const Json j = Json::parse(msg);
|
||||
const std::string type = j.value("type", "");
|
||||
|
||||
if (type == "joiner_connected")
|
||||
{
|
||||
// Host-only: the joiner has arrived on the signaling channel.
|
||||
// Their candidate will follow shortly.
|
||||
LCELOG("SIG", "joiner connected on session %s", ctx->sessionId.c_str());
|
||||
}
|
||||
else if (type == "candidate")
|
||||
{
|
||||
const std::string peerIp = j.value("ip", "");
|
||||
const int peerPort = j.value("port", 0);
|
||||
const std::string methodStr = j.value("method", "stun");
|
||||
const bool holePunch = (methodStr == "stun");
|
||||
|
||||
if (!peerIp.empty() && peerPort > 0)
|
||||
{
|
||||
ctx->peerIp = peerIp;
|
||||
ctx->peerPort = peerPort;
|
||||
ctx->peerNeedsHolePunch = holePunch;
|
||||
ctx->peerReceived = true;
|
||||
|
||||
LCELOG("SIG", "peer endpoint %s:%d (method=%s)",
|
||||
peerIp.c_str(), peerPort, methodStr.c_str());
|
||||
|
||||
done = true; // Both candidates exchanged — we're done here.
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...) {} // Malformed JSON — skip
|
||||
}
|
||||
|
||||
// ---- Clean up ----
|
||||
WinHttpWebSocketClose(hWs, WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS, nullptr, 0);
|
||||
WinHttpCloseHandle(hWs);
|
||||
WinHttpCloseHandle(hConnect);
|
||||
WinHttpCloseHandle(hSession);
|
||||
ctx->wsHandle = nullptr;
|
||||
ctx->workerDone = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Common start helper (host & joiner share 90% of setup)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
bool StartWorker(bool isHost,
|
||||
const std::string& sessionId,
|
||||
const std::string& externalIp, int externalPort,
|
||||
Win64LceLiveP2P::EConnMethod method)
|
||||
{
|
||||
EnsureInitialized();
|
||||
|
||||
EnterCriticalSection(&g_sig.lock);
|
||||
if (g_sig.state != Win64LceLiveSignaling::ESignalingState::Idle)
|
||||
{
|
||||
LeaveCriticalSection(&g_sig.lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
WorkerContext* ctx = new WorkerContext();
|
||||
ctx->isHost = isHost;
|
||||
ctx->sessionId = sessionId;
|
||||
ctx->ourIp = externalIp;
|
||||
ctx->ourPort = externalPort;
|
||||
ctx->ourMethod = method;
|
||||
ctx->accessToken = Win64LceLive::GetAccessToken();
|
||||
ctx->baseUrl = GetBaseUrl();
|
||||
ctx->workerDone = false;
|
||||
ctx->wsConnected = false;
|
||||
ctx->peerReceived = false;
|
||||
ctx->peerPort = 0;
|
||||
ctx->peerNeedsHolePunch = false;
|
||||
ctx->wsHandle = nullptr;
|
||||
|
||||
g_sig.state = Win64LceLiveSignaling::ESignalingState::Connecting;
|
||||
g_sig.sessionId = sessionId;
|
||||
g_sig.peerPort = 0;
|
||||
g_sig.lastError.clear();
|
||||
g_sig.peerIp.clear();
|
||||
g_sig.workerCtx = ctx;
|
||||
|
||||
g_sig.workerThread = CreateThread(nullptr, 0, &SignalingWorkerProc, ctx, 0, nullptr);
|
||||
if (g_sig.workerThread == nullptr)
|
||||
{
|
||||
g_sig.state = Win64LceLiveSignaling::ESignalingState::Failed;
|
||||
g_sig.lastError = "Signaling: failed to create worker thread";
|
||||
delete ctx;
|
||||
g_sig.workerCtx = nullptr;
|
||||
LeaveCriticalSection(&g_sig.lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
LeaveCriticalSection(&g_sig.lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// ============================================================================
|
||||
// Public API
|
||||
// ============================================================================
|
||||
|
||||
namespace Win64LceLiveSignaling
|
||||
{
|
||||
bool HostConnect(const std::string& externalIp, int externalPort,
|
||||
Win64LceLiveP2P::EConnMethod method)
|
||||
{
|
||||
const std::string sessionId = GenerateUuid();
|
||||
LCELOG("SIG", "hosting session %s endpoint %s:%d",
|
||||
sessionId.c_str(), externalIp.c_str(), externalPort);
|
||||
return StartWorker(true, sessionId, externalIp, externalPort, method);
|
||||
}
|
||||
|
||||
bool JoinerConnect(const std::string& sessionId,
|
||||
const std::string& externalIp, int externalPort,
|
||||
Win64LceLiveP2P::EConnMethod method)
|
||||
{
|
||||
// Clear the pending ID — we're acting on it now.
|
||||
EnterCriticalSection(&g_sig.lock);
|
||||
g_sig.pendingJoinerSessionId.clear();
|
||||
LeaveCriticalSection(&g_sig.lock);
|
||||
|
||||
LCELOG("SIG", "joining session %s our endpoint %s:%d",
|
||||
sessionId.c_str(), externalIp.c_str(), externalPort);
|
||||
return StartWorker(false, sessionId, externalIp, externalPort, method);
|
||||
}
|
||||
|
||||
void PrepareJoin(const std::string& sessionId)
|
||||
{
|
||||
EnsureInitialized();
|
||||
EnterCriticalSection(&g_sig.lock);
|
||||
g_sig.pendingJoinerSessionId = sessionId;
|
||||
LeaveCriticalSection(&g_sig.lock);
|
||||
LCELOG("SIG", "joiner session ID stored (%s) — waiting for P2P", sessionId.c_str());
|
||||
}
|
||||
|
||||
std::string GetPendingJoinerSessionId()
|
||||
{
|
||||
EnsureInitialized();
|
||||
EnterCriticalSection(&g_sig.lock);
|
||||
const std::string id = g_sig.pendingJoinerSessionId;
|
||||
LeaveCriticalSection(&g_sig.lock);
|
||||
return id;
|
||||
}
|
||||
|
||||
void Tick()
|
||||
{
|
||||
EnsureInitialized();
|
||||
|
||||
EnterCriticalSection(&g_sig.lock);
|
||||
|
||||
if (g_sig.workerCtx == nullptr ||
|
||||
(g_sig.state != ESignalingState::Connecting &&
|
||||
g_sig.state != ESignalingState::Connected))
|
||||
{
|
||||
LeaveCriticalSection(&g_sig.lock);
|
||||
return;
|
||||
}
|
||||
|
||||
WorkerContext* ctx = g_sig.workerCtx;
|
||||
|
||||
// Promote Connecting → Connected once WebSocket is open.
|
||||
if (g_sig.state == ESignalingState::Connecting && ctx->wsConnected)
|
||||
g_sig.state = ESignalingState::Connected;
|
||||
|
||||
// Integrate completed peer exchange.
|
||||
if (ctx->workerDone)
|
||||
{
|
||||
HANDLE t = g_sig.workerThread;
|
||||
g_sig.workerThread = nullptr;
|
||||
LeaveCriticalSection(&g_sig.lock);
|
||||
|
||||
if (t != nullptr)
|
||||
{
|
||||
WaitForSingleObject(t, INFINITE);
|
||||
CloseHandle(t);
|
||||
}
|
||||
|
||||
EnterCriticalSection(&g_sig.lock);
|
||||
if (ctx->peerReceived)
|
||||
{
|
||||
g_sig.state = ESignalingState::PeerKnown;
|
||||
g_sig.peerIp = ctx->peerIp;
|
||||
g_sig.peerPort = ctx->peerPort;
|
||||
g_sig.peerNeedsHolePunch = ctx->peerNeedsHolePunch;
|
||||
LCELOG("SIG", "peer known %s:%d (holePunch=%d)",
|
||||
ctx->peerIp.c_str(), ctx->peerPort, ctx->peerNeedsHolePunch ? 1 : 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_sig.state = ESignalingState::Failed;
|
||||
g_sig.lastError = ctx->errorMessage.empty()
|
||||
? "Signaling: connection closed before peer candidate received"
|
||||
: ctx->errorMessage;
|
||||
LCELOG("SIG", "failed — %s", g_sig.lastError.c_str());
|
||||
}
|
||||
|
||||
delete ctx;
|
||||
g_sig.workerCtx = nullptr;
|
||||
}
|
||||
|
||||
LeaveCriticalSection(&g_sig.lock);
|
||||
}
|
||||
|
||||
SignalingSnapshot GetSnapshot()
|
||||
{
|
||||
EnsureInitialized();
|
||||
Tick();
|
||||
|
||||
SignalingSnapshot snap = {};
|
||||
|
||||
EnterCriticalSection(&g_sig.lock);
|
||||
snap.state = g_sig.state;
|
||||
snap.sessionId = g_sig.sessionId;
|
||||
snap.peerIp = g_sig.peerIp;
|
||||
snap.peerPort = g_sig.peerPort;
|
||||
snap.peerNeedsHolePunch = g_sig.peerNeedsHolePunch;
|
||||
|
||||
switch (g_sig.state)
|
||||
{
|
||||
case ESignalingState::Idle:
|
||||
snap.statusMessage = L"Signaling: idle.";
|
||||
break;
|
||||
case ESignalingState::Connecting:
|
||||
snap.statusMessage = L"Signaling: connecting to relay...";
|
||||
break;
|
||||
case ESignalingState::Connected:
|
||||
snap.statusMessage = L"Signaling: waiting for peer...";
|
||||
break;
|
||||
case ESignalingState::PeerKnown:
|
||||
{
|
||||
wchar_t buf[256] = {};
|
||||
swprintf_s(buf, L"Signaling: peer at %hs:%d (%s)",
|
||||
g_sig.peerIp.c_str(), g_sig.peerPort,
|
||||
g_sig.peerNeedsHolePunch ? L"hole punch" : L"direct");
|
||||
snap.statusMessage = buf;
|
||||
break;
|
||||
}
|
||||
case ESignalingState::Failed:
|
||||
snap.statusMessage = L"Signaling: failed.";
|
||||
snap.errorMessage = std::wstring(g_sig.lastError.begin(), g_sig.lastError.end());
|
||||
break;
|
||||
case ESignalingState::Closed:
|
||||
snap.statusMessage = L"Signaling: closed.";
|
||||
break;
|
||||
}
|
||||
|
||||
LeaveCriticalSection(&g_sig.lock);
|
||||
return snap;
|
||||
}
|
||||
|
||||
void Close()
|
||||
{
|
||||
EnsureInitialized();
|
||||
|
||||
// Unblock any in-progress WinHttpWebSocketReceive by closing the handle.
|
||||
HINTERNET wsToClose = nullptr;
|
||||
EnterCriticalSection(&g_sig.lock);
|
||||
if (g_sig.workerCtx != nullptr)
|
||||
wsToClose = g_sig.workerCtx->wsHandle;
|
||||
LeaveCriticalSection(&g_sig.lock);
|
||||
|
||||
if (wsToClose != nullptr)
|
||||
{
|
||||
// Sending a Close frame unblocks the receive on the worker thread.
|
||||
WinHttpWebSocketClose(wsToClose,
|
||||
WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS, nullptr, 0);
|
||||
}
|
||||
|
||||
// Now wait for the worker to finish.
|
||||
HANDLE t = nullptr;
|
||||
EnterCriticalSection(&g_sig.lock);
|
||||
t = g_sig.workerThread;
|
||||
LeaveCriticalSection(&g_sig.lock);
|
||||
|
||||
if (t != nullptr)
|
||||
{
|
||||
WaitForSingleObject(t, 5000);
|
||||
CloseHandle(t);
|
||||
}
|
||||
|
||||
EnterCriticalSection(&g_sig.lock);
|
||||
if (g_sig.workerCtx != nullptr)
|
||||
{
|
||||
delete g_sig.workerCtx;
|
||||
g_sig.workerCtx = nullptr;
|
||||
}
|
||||
g_sig.workerThread = nullptr;
|
||||
g_sig.state = ESignalingState::Closed;
|
||||
g_sig.sessionId.clear();
|
||||
g_sig.peerIp.clear();
|
||||
g_sig.peerPort = 0;
|
||||
g_sig.peerNeedsHolePunch = false;
|
||||
g_sig.lastError.clear();
|
||||
g_sig.pendingJoinerSessionId.clear();
|
||||
LeaveCriticalSection(&g_sig.lock);
|
||||
|
||||
LCELOG("SIG", "closed");
|
||||
}
|
||||
|
||||
} // namespace Win64LceLiveSignaling
|
||||
|
||||
#endif // _WINDOWS64
|
||||
Reference in New Issue
Block a user