mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-02 06:40:42 -07:00
5bec3d5db7
## Link to GitHub Issue or related Pull Request, if one exists #0 ## Description of change Add `-icmphook` option under spicecfg Development/Network for emulating keepalive ping replies in user mode, so games do not need to open privileged raw ICMP sockets. This makes connecting to networks possible on macOS under Whisky, and on Windows with ICMP firewalled or spice running without Administrator privileges. ## Testing Tested DDR and SDVX on Windows 10 and macOS Tahoe. Network check status in test menu shows `CONNECTED` when -icmphook is enabled, and `NOT CONNECTED` when it is disabled. Actual network functionality works as expected (under previously impossible conditions).
277 lines
8.3 KiB
C++
277 lines
8.3 KiB
C++
#include <winsock2.h>
|
|
|
|
#include <windows.h>
|
|
#include <iphlpapi.h>
|
|
#include <stdlib.h>
|
|
#include <string>
|
|
#include <mutex>
|
|
|
|
#include "avs/core.h"
|
|
#include "avs/ea3.h"
|
|
#include "avs/game.h"
|
|
#include "util/logging.h"
|
|
#include "util/detour.h"
|
|
#include "util/fileutils.h"
|
|
#include "util/libutils.h"
|
|
#include "util/deferlog.h"
|
|
#include "hooks/icmphook_net.h"
|
|
|
|
// hooking related stuff
|
|
static decltype(GetAdaptersInfo) *GetAdaptersInfo_orig = nullptr;
|
|
static decltype(bind) *bind_orig = nullptr;
|
|
|
|
// settings
|
|
std::string NETWORK_ADDRESS = "10.9.0.0";
|
|
std::string NETWORK_SUBNET = "255.255.0.0";
|
|
static bool GetAdaptersInfo_log = true;
|
|
|
|
// network structs
|
|
static struct in_addr network;
|
|
static struct in_addr prefix;
|
|
static struct in_addr subnet;
|
|
|
|
static void defer_network_adapter_error() {
|
|
static std::once_flag printed;
|
|
std::call_once(printed, []() {
|
|
deferredlogs::defer_error_messages({
|
|
"network adapter issue detected!",
|
|
" ensure you have at least one network adapter with a valid IPv4 address",
|
|
" the IPv4 address can be external or internal, it just needs to be valid",
|
|
" the network adapter can be a wired or wireless connection",
|
|
" you still need to do this even if you are connecting to a local server!",
|
|
});
|
|
});
|
|
}
|
|
|
|
static ULONG WINAPI GetAdaptersInfo_hook(PIP_ADAPTER_INFO pAdapterInfo, PULONG pOutBufLen) {
|
|
|
|
// call orig
|
|
ULONG ret = GetAdaptersInfo_orig(pAdapterInfo, pOutBufLen);
|
|
if (ret != ERROR_SUCCESS) {
|
|
|
|
// workaround for QMA not having enough buffer space
|
|
if (pAdapterInfo != nullptr && avs::game::is_model({ "LMA", "MMA" })) {
|
|
|
|
// allocate the output buffer size
|
|
auto pAdapterInfo2 = (PIP_ADAPTER_INFO) malloc(*pOutBufLen);
|
|
|
|
// call ourself with an appropriate buffer size
|
|
ret = GetAdaptersInfo_hook(pAdapterInfo2, pOutBufLen);
|
|
if (ret != ERROR_SUCCESS) {
|
|
return ret;
|
|
}
|
|
|
|
// copy best interface
|
|
memcpy(pAdapterInfo, pAdapterInfo2, sizeof(*pAdapterInfo));
|
|
pAdapterInfo->Next = nullptr;
|
|
|
|
// free our allocated memory
|
|
free(pAdapterInfo2);
|
|
}
|
|
|
|
if (ret != ERROR_SUCCESS && ret != ERROR_BUFFER_OVERFLOW) {
|
|
defer_network_adapter_error();
|
|
log_warning(
|
|
"network",
|
|
"GetAdaptersInfo failed with {}; "
|
|
"check if you have at least one network adapter with a valid IPv4 address!",
|
|
ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// set the best network adapter
|
|
PIP_ADAPTER_INFO info = pAdapterInfo;
|
|
while (info != nullptr) {
|
|
|
|
// set subnet
|
|
struct in_addr info_subnet;
|
|
info_subnet.s_addr = inet_addr(info->IpAddressList.IpMask.String);
|
|
|
|
// set prefix
|
|
struct in_addr info_prefix;
|
|
info_prefix.s_addr = inet_addr(info->IpAddressList.IpAddress.String) & info_subnet.s_addr;
|
|
|
|
// check base IP and subnet
|
|
bool isCorrectBaseIp = prefix.s_addr == info_prefix.s_addr;
|
|
bool isCorrectSubnetMask = subnet.s_addr == info_subnet.s_addr;
|
|
|
|
// check if requirements are met
|
|
if (isCorrectBaseIp && isCorrectSubnetMask) {
|
|
|
|
// log adapter
|
|
if (GetAdaptersInfo_log)
|
|
log_info("network", "Using preferred network adapter: {}, {}",
|
|
info->AdapterName,
|
|
info->Description);
|
|
|
|
// set adapter information
|
|
memcpy(pAdapterInfo, info, sizeof(*info));
|
|
pAdapterInfo->Next = nullptr;
|
|
|
|
// we're done
|
|
GetAdaptersInfo_log = false;
|
|
return ret;
|
|
}
|
|
|
|
// iterate
|
|
info = info->Next;
|
|
}
|
|
|
|
// get IP forward table
|
|
PMIB_IPFORWARDTABLE pIpForwardTable = (MIB_IPFORWARDTABLE *) malloc(sizeof(MIB_IPFORWARDTABLE));
|
|
DWORD dwSize = 0;
|
|
if (GetIpForwardTable(pIpForwardTable, &dwSize, 1) == ERROR_INSUFFICIENT_BUFFER) {
|
|
free(pIpForwardTable);
|
|
pIpForwardTable = (MIB_IPFORWARDTABLE *) malloc(dwSize);
|
|
}
|
|
if (GetIpForwardTable(pIpForwardTable, &dwSize, 1) != NO_ERROR || pIpForwardTable->dwNumEntries == 0) {
|
|
defer_network_adapter_error();
|
|
if (GetAdaptersInfo_log) {
|
|
log_warning("network", "GetIpForwardTable failed");
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// determine best interface
|
|
DWORD best = pIpForwardTable->table[0].dwForwardIfIndex;
|
|
free(pIpForwardTable);
|
|
|
|
// find fallback adapter
|
|
info = pAdapterInfo;
|
|
while (info != nullptr) {
|
|
|
|
// check if this the adapter we search for
|
|
if (info->Index == best) {
|
|
|
|
// log information
|
|
if (GetAdaptersInfo_log)
|
|
log_info("network", "Using fallback network adapter: {}, {}",
|
|
info->AdapterName,
|
|
info->Description);
|
|
|
|
|
|
// set adapter information
|
|
memcpy(pAdapterInfo, info, sizeof(*info));
|
|
pAdapterInfo->Next = nullptr;
|
|
|
|
// exit the loop
|
|
break;
|
|
}
|
|
|
|
// iterate
|
|
info = info->Next;
|
|
}
|
|
|
|
if (pAdapterInfo->IpAddressList.IpAddress.String[0] == 0 ||
|
|
pAdapterInfo->IpAddressList.IpAddress.String[0] == '0') {
|
|
defer_network_adapter_error();
|
|
if (GetAdaptersInfo_log) {
|
|
log_warning(
|
|
"network",
|
|
"invalid IPv4 address for adapter {}, {} = {}; "
|
|
"ensure you have at least one network adapter with valid IPv4 address!",
|
|
pAdapterInfo->AdapterName,
|
|
pAdapterInfo->Description,
|
|
pAdapterInfo->IpAddressList.IpAddress.String);
|
|
}
|
|
}
|
|
|
|
// return original value
|
|
GetAdaptersInfo_log = false;
|
|
return ret;
|
|
}
|
|
|
|
static int WINAPI bind_hook(SOCKET s, const struct sockaddr *name, int namelen) {
|
|
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic push
|
|
#pragma ide diagnostic ignored "OCDFAInspection"
|
|
#endif
|
|
|
|
int icmp_bind_result = 0;
|
|
if (icmphook_try_bind(s, name, namelen, &icmp_bind_result)) {
|
|
return icmp_bind_result;
|
|
}
|
|
|
|
// cast to sockaddr_in
|
|
struct sockaddr_in *in_name = (struct sockaddr_in *) name;
|
|
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic pop
|
|
#endif
|
|
|
|
// override bind to allow all hosts
|
|
in_name->sin_addr.s_addr = inet_addr("0.0.0.0");
|
|
|
|
// call original
|
|
int ret = bind_orig(s, name, namelen);
|
|
if (ret != 0) {
|
|
log_warning("network", "bind failed: {}", WSAGetLastError());
|
|
}
|
|
|
|
// return result
|
|
return ret;
|
|
}
|
|
|
|
void networkhook_init() {
|
|
|
|
// announce init
|
|
log_info("network", "SpiceTools Network");
|
|
|
|
// set some same defaults
|
|
network.s_addr = inet_addr(NETWORK_ADDRESS.c_str());
|
|
subnet.s_addr = inet_addr(NETWORK_SUBNET.c_str());
|
|
prefix.s_addr = network.s_addr & subnet.s_addr;
|
|
|
|
// inet_ntoa(...) reuses the same char array so the results must be copied
|
|
char s_network[17]{}, s_subnet[17]{}, s_prefix[17]{};
|
|
strncpy(s_network, inet_ntoa(network), 16);
|
|
strncpy(s_subnet, inet_ntoa(subnet), 16);
|
|
strncpy(s_prefix, inet_ntoa(prefix), 16);
|
|
|
|
// log preferences
|
|
// log_info("network", "Network preferences: {}, {}, {}", s_network, s_subnet, s_prefix);
|
|
|
|
// GetAdaptersInfo hook
|
|
auto orig_addr = detour::iat_try(
|
|
"GetAdaptersInfo", GetAdaptersInfo_hook, nullptr);
|
|
if (!orig_addr) {
|
|
log_warning("network", "Could not hook GetAdaptersInfo");
|
|
} else if (GetAdaptersInfo_orig == nullptr) {
|
|
GetAdaptersInfo_orig = orig_addr;
|
|
}
|
|
|
|
/*
|
|
* Bind Hook
|
|
*/
|
|
bool bind_hook_enabled = true;
|
|
|
|
// disable hook for DDR A since the bind hook crashes there for some reason
|
|
if (fileutils::file_exists(MODULE_PATH / "gamemdx.dll")) {
|
|
bind_hook_enabled = false;
|
|
}
|
|
|
|
// hook bind
|
|
if (bind_hook_enabled) {
|
|
|
|
// hook by name
|
|
auto new_bind_orig = detour::iat_try("bind", bind_hook, nullptr);
|
|
if (bind_orig == nullptr) {
|
|
bind_orig = new_bind_orig;
|
|
}
|
|
|
|
// hook ESS by ordinal
|
|
HMODULE ess = libutils::try_module("ess.dll");
|
|
if (ess) {
|
|
auto new_bind_orig2 = detour::iat_try_ordinal("WS2_32.dll", 2, bind_hook, ess);
|
|
|
|
// try to get some valid pointer
|
|
if (bind_orig == nullptr && new_bind_orig2 != nullptr) {
|
|
bind_orig = new_bind_orig2;
|
|
}
|
|
}
|
|
}
|
|
}
|