network: NIC spoof matching tunnel (#912)

## Link to GitHub Issue or related Pull Request, if one exists
#911

## Description of change
Add tunnelhost/tunnelclient modes to -nicspoof so two cabs can do
LAN-style BPL matching over a real UDP path (including Wine), while
keeping the fake overlay NIC. Offline spoof remains available as before.

## Testing
DDR
This commit is contained in:
drmext
2026-09-11 07:37:45 +00:00
committed by GitHub
parent 531e7a54b3
commit 5e4afecd90
10 changed files with 2447 additions and 73 deletions
+48 -9
View File
@@ -4,6 +4,7 @@
#include <vector>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <shlwapi.h>
#include <windows.h>
@@ -258,7 +259,7 @@ int main_implementation(int argc, char *argv[]) {
bool load_stubs = false;
bool netfix_disable = false;
bool icmphook_enable = false;
bool nicspoof_enable = false;
NicSpoofConfig nicspoof_cfg;
bool lang_disable = false;
std::string process_priority_str = "high";
bool cardio_enabled = false;
@@ -801,8 +802,43 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::EnableICMPHook].value_bool()) {
icmphook_enable = true;
}
if (options[launcher::Options::EnableNICSpoof].value_bool()) {
nicspoof_enable = true;
if (options[launcher::Options::EnableNICSpoof].is_active()) {
const auto &mode = options[launcher::Options::EnableNICSpoof].value_text();
if (mode == "offline" || mode == "/ENABLED") {
nicspoof_cfg.mode = NicSpoofMode::Offline;
} else if (mode == "tunnelhost") {
nicspoof_cfg.mode = NicSpoofMode::TunnelHost;
} else if (mode == "tunnelclient") {
nicspoof_cfg.mode = NicSpoofMode::TunnelClient;
} else {
log_warning("launcher", "unknown -nicspoof value '{}', ignoring", mode);
}
}
if (nicspoof_cfg.mode != NicSpoofMode::Off) {
if (options[launcher::Options::NICSpoofIP].is_active()) {
unsigned a = 0, b = 0, c = 0, d = 0;
char trail = 0;
const auto &ip = options[launcher::Options::NICSpoofIP].value_text();
if (sscanf(ip.c_str(), "%u.%u.%u.%u%c", &a, &b, &c, &d, &trail) == 4 &&
a <= 255 && b <= 255 && c <= 255 && d <= 255) {
nicspoof_cfg.local_ip = (a << 24) | (b << 16) | (c << 8) | d;
} else {
log_warning("launcher", "invalid -nicspoofip '{}'", ip);
}
}
if (options[launcher::Options::NICSpoofHostRealIP].is_active()) {
nicspoof_cfg.hub_host =
options[launcher::Options::NICSpoofHostRealIP].value_text();
}
if (options[launcher::Options::NICSpoofPort].is_active()) {
const uint32_t p =
options[launcher::Options::NICSpoofPort].value_uint32();
if (p == 0 || p > 65535) {
log_warning("launcher", "invalid -nicspoofport {}", p);
} else {
nicspoof_cfg.tunnel_port = static_cast<uint16_t>(p);
}
}
}
if (options[launcher::Options::DisableACPHook].value_bool()) {
lang_disable = true;
@@ -2558,14 +2594,17 @@ int main_implementation(int argc, char *argv[]) {
avs::core::load_dll();
avs::ea3::load_dll();
// ICMP emulation (opt-in; before games open raw ICMP sockets)
if (icmphook_enable) {
icmphook_net_init();
// NIC spoof / matching tunnel first so divert owns overlapping ws2_32
// MinHook slots (bind/sendto/recvfrom/...). ICMP then installs only the
// non-overlapping socket-creation hooks and is reached via icmphook_try_*.
if (nicspoof_cfg.mode != NicSpoofMode::Off) {
nicspoof_configure(nicspoof_cfg);
nicspoof_init();
}
// NIC spoof (opt-in; fake Ethernet with no real NIC)
if (nicspoof_enable) {
nicspoof_init();
// ICMP emulation (opt-in; after tunnel so hooks do not collide)
if (icmphook_enable) {
icmphook_net_init();
}
// net fix