mirror of
https://github.com/moonlight-stream/Internet-Hosting-Tool.git
synced 2025-07-01 07:15:32 +00:00
692 lines
43 KiB
C++
692 lines
43 KiB
C++
#define _CRT_SECURE_NO_WARNINGS
|
||
|
||
#define WIN32_LEAN_AND_MEAN
|
||
#include <Windows.h>
|
||
|
||
#include <WinSock2.h>
|
||
#include <WS2tcpip.h>
|
||
#include <iphlpapi.h>
|
||
|
||
#include <stdio.h>
|
||
#include <assert.h>
|
||
#include <stdlib.h>
|
||
|
||
#pragma comment(lib, "miniupnpc.lib")
|
||
#pragma comment(lib, "libnatpmp.lib")
|
||
#pragma comment(lib, "ws2_32.lib")
|
||
#pragma comment(lib, "iphlpapi.lib")
|
||
|
||
#define MINIUPNP_STATICLIB
|
||
#include <miniupnpc/miniupnpc.h>
|
||
#include <miniupnpc/upnpcommands.h>
|
||
#include <miniupnpc/upnperrors.h>
|
||
|
||
#define NATPMP_STATICLIB
|
||
#include <natpmp.h>
|
||
|
||
#define NL "\n"
|
||
#define STR(x) #x
|
||
|
||
#define SERVICE_NAME "MISS"
|
||
#define UPNP_SERVICE_NAME "Moonlight"
|
||
#define PORT_MAPPING_DURATION_SEC 3600
|
||
#define UPNP_DISCOVERY_DELAY_MS 5000
|
||
|
||
static struct port_entry {
|
||
int proto;
|
||
int port;
|
||
} k_Ports[] = {
|
||
{IPPROTO_TCP, 47984},
|
||
{IPPROTO_TCP, 47989},
|
||
{IPPROTO_TCP, 48010},
|
||
{IPPROTO_UDP, 47998},
|
||
{IPPROTO_UDP, 47999},
|
||
{IPPROTO_UDP, 48000},
|
||
{IPPROTO_UDP, 48002},
|
||
{IPPROTO_UDP, 48010}
|
||
};
|
||
|
||
void UPnPCreatePinholeForPort(struct UPNPUrls* urls, struct IGDdatas* data, int proto, const char* myAddr, int port)
|
||
{
|
||
char uniqueId[8];
|
||
char protoStr[3];
|
||
char portStr[6];
|
||
|
||
snprintf(portStr, sizeof(portStr), "%d", port);
|
||
snprintf(protoStr, sizeof(protoStr), "%d", proto);
|
||
|
||
printf("Creating UPnP IPv6 pinhole for %s %s -> %s...", protoStr, portStr, myAddr);
|
||
|
||
// Lease time is in seconds - 7200 = 2 hours
|
||
int err = UPNP_AddPinhole(urls->controlURL_6FC, data->IPv6FC.servicetype, "empty", portStr, myAddr, portStr, protoStr, "7200", uniqueId);
|
||
if (err == UPNPCOMMAND_SUCCESS) {
|
||
printf("OK" NL);
|
||
}
|
||
else {
|
||
printf("ERROR %d (%s)" NL, err, strupnperror(err));
|
||
}
|
||
}
|
||
|
||
bool UPnPMapPort(struct UPNPUrls* urls, struct IGDdatas* data, int proto, const char* myAddr, int port, bool enable)
|
||
{
|
||
char intClient[16];
|
||
char intPort[6];
|
||
char desc[80];
|
||
char enabled[4];
|
||
char leaseDuration[16];
|
||
const char* protoStr;
|
||
char portStr[6];
|
||
char myDesc[80];
|
||
char computerName[MAX_COMPUTERNAME_LENGTH + 1];
|
||
|
||
DWORD nameLen = sizeof(computerName);
|
||
if (!GetComputerNameA(computerName, &nameLen)) {
|
||
printf("GetComputerNameA() failed: %d", GetLastError());
|
||
snprintf(computerName, sizeof(computerName), "UNKNOWN");
|
||
}
|
||
snprintf(myDesc, sizeof(myDesc), "%s - %s", UPNP_SERVICE_NAME, computerName);
|
||
|
||
snprintf(portStr, sizeof(portStr), "%d", port);
|
||
switch (proto)
|
||
{
|
||
case IPPROTO_TCP:
|
||
protoStr = "TCP";
|
||
break;
|
||
case IPPROTO_UDP:
|
||
protoStr = "UDP";
|
||
break;
|
||
default:
|
||
assert(false);
|
||
return false;
|
||
}
|
||
|
||
printf("Checking for existing UPnP port mapping for %s %s -> %s...", protoStr, portStr, myAddr);
|
||
int err = UPNP_GetSpecificPortMappingEntry(
|
||
urls->controlURL, data->first.servicetype, portStr, protoStr, nullptr,
|
||
intClient, intPort, desc, enabled, leaseDuration);
|
||
if (err == 714) {
|
||
// NoSuchEntryInArray
|
||
printf("NOT FOUND" NL);
|
||
}
|
||
else if (err == UPNPCOMMAND_SUCCESS) {
|
||
if (!strcmp(intClient, myAddr) && !strcmp(desc, myDesc)) {
|
||
if (atoi(leaseDuration) == 0) {
|
||
printf("OK (Permanent)" NL);
|
||
}
|
||
else {
|
||
printf("OK (%s seconds remaining)" NL, leaseDuration);
|
||
}
|
||
|
||
if (!enable) {
|
||
// This is our entry. Go ahead and nuke it
|
||
printf("Deleting UPnP mapping for %s %s -> %s...", protoStr, portStr, myAddr);
|
||
err = UPNP_DeletePortMapping(urls->controlURL, data->first.servicetype, portStr, protoStr, nullptr);
|
||
if (err == UPNPCOMMAND_SUCCESS) {
|
||
printf("OK" NL);
|
||
}
|
||
else {
|
||
printf("ERROR %d" NL, err);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
}
|
||
else {
|
||
// UPnP IGDs won't let unauthenticated clients delete other conflicting port mappings
|
||
// for security reasons, so we have to give up in this case.
|
||
printf("CONFLICT: %s %s" NL, intClient, desc);
|
||
return false;
|
||
}
|
||
}
|
||
else {
|
||
printf("ERROR %d (%s)" NL, err, strupnperror(err));
|
||
}
|
||
|
||
// Bail if GameStream is disabled
|
||
if (!enable) {
|
||
return true;
|
||
}
|
||
|
||
// Create or update the expiration time of an existing mapping
|
||
printf("Updating UPnP port mapping for %s %s -> %s...", protoStr, portStr, myAddr);
|
||
err = UPNP_AddPortMapping(
|
||
urls->controlURL, data->first.servicetype, portStr,
|
||
portStr, myAddr, myDesc, protoStr, nullptr, STR(PORT_MAPPING_DURATION_SEC));
|
||
if (err == 725) { // OnlyPermanentLeasesSupported
|
||
err = UPNP_AddPortMapping(
|
||
urls->controlURL, data->first.servicetype, portStr,
|
||
portStr, myAddr, myDesc, protoStr, nullptr, "0");
|
||
printf("PERMANENT ");
|
||
}
|
||
if (err == UPNPCOMMAND_SUCCESS) {
|
||
printf("OK" NL);
|
||
return true;
|
||
}
|
||
else {
|
||
printf("ERROR %d (%s)" NL, err, strupnperror(err));
|
||
return false;
|
||
}
|
||
}
|
||
|
||
bool ResolveStableIP6Address(char* tmpAddr)
|
||
{
|
||
union {
|
||
IP_ADAPTER_ADDRESSES addresses;
|
||
char buffer[8192];
|
||
};
|
||
ULONG error;
|
||
ULONG length;
|
||
PIP_ADAPTER_ADDRESSES currentAdapter;
|
||
PIP_ADAPTER_UNICAST_ADDRESS currentAddress;
|
||
in6_addr targetAddress;
|
||
|
||
inet_pton(AF_INET6, tmpAddr, &targetAddress);
|
||
|
||
// Get a list of all interfaces with IPv6 addresses on the system
|
||
length = sizeof(buffer);
|
||
error = GetAdaptersAddresses(AF_INET6,
|
||
GAA_FLAG_SKIP_ANYCAST |
|
||
GAA_FLAG_SKIP_MULTICAST |
|
||
GAA_FLAG_SKIP_DNS_SERVER |
|
||
GAA_FLAG_SKIP_FRIENDLY_NAME,
|
||
NULL,
|
||
&addresses,
|
||
&length);
|
||
if (error != ERROR_SUCCESS) {
|
||
printf("GetAdaptersAddresses() failed: %d" NL, error);
|
||
return false;
|
||
}
|
||
|
||
currentAdapter = &addresses;
|
||
currentAddress = nullptr;
|
||
while (currentAdapter != nullptr) {
|
||
// First, search for the adapter
|
||
currentAddress = currentAdapter->FirstUnicastAddress;
|
||
while (currentAddress != nullptr) {
|
||
assert(currentAddress->Address.lpSockaddr->sa_family == AF_INET6);
|
||
|
||
PSOCKADDR_IN6 currentAddrV6 = (PSOCKADDR_IN6)currentAddress->Address.lpSockaddr;
|
||
|
||
if (RtlEqualMemory(¤tAddrV6->sin6_addr, &targetAddress, sizeof(targetAddress))) {
|
||
// Found interface with matching address
|
||
break;
|
||
}
|
||
|
||
currentAddress = currentAddress->Next;
|
||
}
|
||
|
||
if (currentAddress != nullptr) {
|
||
// Get out of the loop if we found the matching address
|
||
break;
|
||
}
|
||
|
||
currentAdapter = currentAdapter->Next;
|
||
}
|
||
|
||
if (currentAdapter == nullptr) {
|
||
printf("No adapter found with IPv6 address: %s" NL, tmpAddr);
|
||
return false;
|
||
}
|
||
|
||
// Now currentAdapter is the adapter we reached the IGD with. Find a suitable
|
||
// public address that we can use to create the pinhole.
|
||
currentAddress = currentAdapter->FirstUnicastAddress;
|
||
while (currentAddress != nullptr) {
|
||
assert(currentAddress->Address.lpSockaddr->sa_family == AF_INET6);
|
||
|
||
PSOCKADDR_IN6 currentAddrV6 = (PSOCKADDR_IN6)currentAddress->Address.lpSockaddr;
|
||
|
||
// Exclude temporary addresses and link-local addresses
|
||
if (currentAddress->SuffixOrigin != IpSuffixOriginRandom && currentAddrV6->sin6_scope_id == 0) {
|
||
break;
|
||
}
|
||
|
||
currentAddress = currentAddress->Next;
|
||
}
|
||
|
||
if (currentAddress == nullptr) {
|
||
printf("No suitable alternate address found for %s" NL, tmpAddr);
|
||
return false;
|
||
}
|
||
|
||
PSOCKADDR_IN6 currentAddrV6 = (PSOCKADDR_IN6)currentAddress->Address.lpSockaddr;
|
||
inet_ntop(AF_INET6, ¤tAddrV6->sin6_addr, tmpAddr, 128);
|
||
|
||
return true;
|
||
}
|
||
|
||
bool UPnPHandleDeviceList(struct UPNPDev* list, bool ipv6, bool enable)
|
||
{
|
||
struct UPNPUrls urls;
|
||
struct IGDdatas data;
|
||
char myAddr[128];
|
||
char wanAddr[128];
|
||
int pinholeAllowed = false;
|
||
bool success = true;
|
||
|
||
int ret = UPNP_GetValidIGD(list, &urls, &data, myAddr, sizeof(myAddr));
|
||
if (ret == 0) {
|
||
printf("No UPnP device found!" NL);
|
||
return false;
|
||
}
|
||
else if (ret == 1) {
|
||
printf("Found a connected UPnP IGD" NL);
|
||
}
|
||
else if (ret == 2) {
|
||
printf("Found a disconnected UPnP IGD (!)" NL);
|
||
|
||
// Even if we are able to add forwarding entries, go ahead and try NAT-PMP
|
||
success = false;
|
||
}
|
||
else if (ret == 3) {
|
||
printf("Found UPnP devices (assuming it's an IGD) (!)" NL);
|
||
|
||
// Even if we are able to add forwarding entries, go ahead and try NAT-PMP
|
||
success = false;
|
||
}
|
||
else {
|
||
printf("UPNP_GetValidIGD() failed: %d" NL, ret);
|
||
goto Exit;
|
||
}
|
||
|
||
if (ipv6) {
|
||
// Convert what is likely a IPv6 temporary address into
|
||
// the stable IPv6 address for the same interface.
|
||
if (ResolveStableIP6Address(myAddr)) {
|
||
printf("Stable global IPv6 address is: %s" NL, myAddr);
|
||
|
||
int firewallEnabled;
|
||
ret = UPNP_GetFirewallStatus(urls.controlURL_6FC, data.IPv6FC.servicetype, &firewallEnabled, &pinholeAllowed);
|
||
if (ret == UPNPCOMMAND_SUCCESS) {
|
||
printf("UPnP IPv6 firewall control available. Firewall is %s, pinhole is %s" NL,
|
||
firewallEnabled ? "enabled" : "disabled",
|
||
pinholeAllowed ? "allowed" : "disallowed");
|
||
}
|
||
else {
|
||
printf("UPnP IPv6 firewall control is unavailable with error %d (%s)" NL, ret, strupnperror(ret));
|
||
pinholeAllowed = false;
|
||
}
|
||
}
|
||
}
|
||
else {
|
||
ret = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, wanAddr);
|
||
if (ret == UPNPCOMMAND_SUCCESS) {
|
||
printf("UPnP IGD WAN address is: %s" NL, wanAddr);
|
||
}
|
||
}
|
||
|
||
for (int i = 0; i < ARRAYSIZE(k_Ports); i++) {
|
||
if (!ipv6) {
|
||
if (!UPnPMapPort(&urls, &data, k_Ports[i].proto, myAddr, k_Ports[i].port, enable)) {
|
||
success = false;
|
||
}
|
||
}
|
||
if (pinholeAllowed) {
|
||
UPnPCreatePinholeForPort(&urls, &data, k_Ports[i].proto, myAddr, k_Ports[i].port);
|
||
}
|
||
}
|
||
|
||
Exit:
|
||
FreeUPNPUrls(&urls);
|
||
return success;
|
||
}
|
||
|
||
bool NATPMPMapPort(natpmp_t* natpmp, int proto, int port, bool enable)
|
||
{
|
||
int natPmpProto;
|
||
|
||
switch (proto)
|
||
{
|
||
case IPPROTO_TCP:
|
||
natPmpProto = NATPMP_PROTOCOL_TCP;
|
||
break;
|
||
case IPPROTO_UDP:
|
||
natPmpProto = NATPMP_PROTOCOL_UDP;
|
||
break;
|
||
default:
|
||
assert(false);
|
||
return false;
|
||
}
|
||
|
||
printf("Updating NAT-PMP port mapping for %s %d...", proto == IPPROTO_TCP ? "TCP" : "UDP", port);
|
||
int err = sendnewportmappingrequest(natpmp, natPmpProto, port, port, enable ? PORT_MAPPING_DURATION_SEC : 0);
|
||
if (err < 0) {
|
||
printf("ERROR %d" NL, err);
|
||
return false;
|
||
}
|
||
|
||
natpmpresp_t response;
|
||
do
|
||
{
|
||
fd_set fds;
|
||
struct timeval timeout;
|
||
|
||
FD_ZERO(&fds);
|
||
FD_SET(natpmp->s, &fds);
|
||
|
||
err = getnatpmprequesttimeout(natpmp, &timeout);
|
||
if (err != 0) {
|
||
assert(err == 0);
|
||
printf("WAIT FAILED: %d" NL, err);
|
||
return false;
|
||
}
|
||
|
||
select(0, &fds, nullptr, nullptr, &timeout);
|
||
|
||
err = readnatpmpresponseorretry(natpmp, &response);
|
||
} while (err == NATPMP_TRYAGAIN);
|
||
|
||
if (err != 0) {
|
||
printf("FAILED %d" NL, err);
|
||
return false;
|
||
}
|
||
else if (response.pnu.newportmapping.lifetime == 0 && !enable) {
|
||
printf("DELETED" NL);
|
||
return true;
|
||
}
|
||
else if (response.pnu.newportmapping.mappedpublicport != port) {
|
||
printf("CONFLICT" NL);
|
||
// It couldn't assign us the external port we requested and gave us an alternate external port.
|
||
// We can't use this alternate mapping, so immediately release it.
|
||
sendnewportmappingrequest(natpmp, natPmpProto, port, response.pnu.newportmapping.mappedpublicport, 0);
|
||
return false;
|
||
}
|
||
else {
|
||
printf("OK (%d seconds remaining)" NL, response.pnu.newportmapping.lifetime);
|
||
return true;
|
||
}
|
||
}
|
||
|
||
bool IsGameStreamEnabled()
|
||
{
|
||
DWORD error;
|
||
DWORD enabled;
|
||
DWORD len;
|
||
HKEY key;
|
||
|
||
error = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "Software\\NVIDIA Corporation\\NvStream", 0, KEY_READ | KEY_WOW64_64KEY, &key);
|
||
if (error != ERROR_SUCCESS) {
|
||
printf("RegOpenKeyEx() failed: %d\n", error);
|
||
return false;
|
||
}
|
||
|
||
len = sizeof(enabled);
|
||
error = RegQueryValueExA(key, "EnableStreaming", nullptr, nullptr, (LPBYTE)&enabled, &len);
|
||
RegCloseKey(key);
|
||
if (error != ERROR_SUCCESS) {
|
||
printf("RegQueryValueExA() failed: %d" NL, error);
|
||
return false;
|
||
}
|
||
else if (!enabled) {
|
||
printf("GameStream is OFF!" NL);
|
||
return false;
|
||
}
|
||
else {
|
||
printf("GameStream is ON!" NL);
|
||
return true;
|
||
}
|
||
}
|
||
|
||
void UpdatePortMappings(bool enable)
|
||
{
|
||
natpmp_t natpmp;
|
||
bool tryNatPmp = true;
|
||
|
||
printf("Starting port mapping update..." NL);
|
||
|
||
int natPmpErr = initnatpmp(&natpmp, 0, 0);
|
||
if (natPmpErr != 0) {
|
||
printf("initnatpmp() failed: %d" NL, natPmpErr);
|
||
}
|
||
else {
|
||
natPmpErr = sendpublicaddressrequest(&natpmp);
|
||
if (natPmpErr < 0) {
|
||
printf("sendpublicaddressrequest() failed: %d" NL, natPmpErr);
|
||
closenatpmp(&natpmp);
|
||
}
|
||
}
|
||
|
||
fflush(stdout);
|
||
|
||
{
|
||
int upnpErr;
|
||
struct UPNPDev* ipv4Devs = upnpDiscoverAll(UPNP_DISCOVERY_DELAY_MS, nullptr, nullptr, UPNP_LOCAL_PORT_ANY, 0, 2, &upnpErr);
|
||
|
||
printf("UPnP IPv4 IGD discovery completed with error code: %d" NL, upnpErr);
|
||
|
||
// Use the delay of upnpDiscoverAll() to also allow the NAT-PMP endpoint time to respond
|
||
if (natPmpErr >= 0) {
|
||
natpmpresp_t response;
|
||
natPmpErr = readnatpmpresponseorretry(&natpmp, &response);
|
||
if (natPmpErr == 0) {
|
||
char addrStr[64];
|
||
inet_ntop(AF_INET, &response.pnu.publicaddress.addr, addrStr, sizeof(addrStr));
|
||
printf("NAT-PMP WAN address is: %s" NL, addrStr);
|
||
}
|
||
else {
|
||
printf("NAT-PMP public address request failed: %d" NL, natPmpErr);
|
||
closenatpmp(&natpmp);
|
||
}
|
||
}
|
||
|
||
// Don't try NAT-PMP if UPnP succeeds
|
||
if (UPnPHandleDeviceList(ipv4Devs, false, enable)) {
|
||
printf("UPnP IPv4 port mapping successful" NL);
|
||
if (enable) {
|
||
// We still want to try NAT-PMP if we're removing
|
||
// rules to ensure any NAT-PMP rules get cleaned up
|
||
tryNatPmp = false;
|
||
}
|
||
}
|
||
|
||
freeUPNPDevlist(ipv4Devs);
|
||
}
|
||
|
||
fflush(stdout);
|
||
|
||
{
|
||
int upnpErr;
|
||
struct UPNPDev* ipv6Devs = upnpDiscoverAll(UPNP_DISCOVERY_DELAY_MS, nullptr, nullptr, UPNP_LOCAL_PORT_ANY, 1, 2, &upnpErr);
|
||
|
||
printf("UPnP IPv6 IGD discovery completed with error code: %d" NL, upnpErr);
|
||
|
||
// Ignore whether IPv6 succeeded when decided to use NAT-PMP
|
||
UPnPHandleDeviceList(ipv6Devs, true, enable);
|
||
|
||
freeUPNPDevlist(ipv6Devs);
|
||
}
|
||
|
||
fflush(stdout);
|
||
|
||
if (natPmpErr == 0) {
|
||
if (tryNatPmp) {
|
||
bool success = true;
|
||
for (int i = 0; i < ARRAYSIZE(k_Ports); i++) {
|
||
if (!NATPMPMapPort(&natpmp, k_Ports[i].proto, k_Ports[i].port, enable)) {
|
||
success = false;
|
||
}
|
||
}
|
||
if (success) {
|
||
printf("NAT-PMP IPv4 port mapping successful" NL);
|
||
}
|
||
}
|
||
|
||
closenatpmp(&natpmp);
|
||
}
|
||
|
||
fflush(stdout);
|
||
}
|
||
|
||
void NETIOAPI_API_ IpInterfaceChangeNotificationCallback(PVOID context, PMIB_IPINTERFACE_ROW, MIB_NOTIFICATION_TYPE)
|
||
{
|
||
SetEvent((HANDLE)context);
|
||
}
|
||
|
||
void ResetLogFile()
|
||
{
|
||
char oldLogFilePath[MAX_PATH + 1];
|
||
char currentLogFilePath[MAX_PATH + 1];
|
||
|
||
ExpandEnvironmentStringsA("%ProgramData%\\MISS\\miss-old.log", oldLogFilePath, sizeof(oldLogFilePath));
|
||
ExpandEnvironmentStringsA("%ProgramData%\\MISS\\miss-current.log", currentLogFilePath, sizeof(currentLogFilePath));
|
||
|
||
// Delete the old log file
|
||
DeleteFileA(oldLogFilePath);
|
||
|
||
// Rotate the current to the old log file
|
||
MoveFileA(currentLogFilePath, oldLogFilePath);
|
||
|
||
// Redirect stdout to this new file
|
||
freopen(currentLogFilePath, "w", stdout);
|
||
}
|
||
|
||
DWORD WINAPI GameStreamStateChangeThread(PVOID Context)
|
||
{
|
||
HKEY key;
|
||
|
||
// We're watching this key that way we can still detect GameStream turning on
|
||
// if GFE wasn't even installed when our service started
|
||
DWORD err = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "Software\\NVIDIA Corporation", 0, KEY_READ | KEY_WOW64_64KEY, &key);
|
||
if (err != ERROR_SUCCESS) {
|
||
printf("RegOpenKeyExA() failed: %d" NL, err);
|
||
return err;
|
||
}
|
||
|
||
// Notify the main thread when the GameStream state changes
|
||
bool lastGameStreamState = IsGameStreamEnabled();
|
||
while ((err = RegNotifyChangeKeyValue(key, true, REG_NOTIFY_CHANGE_LAST_SET, nullptr, false)) == ERROR_SUCCESS) {
|
||
bool currentGameStreamState = IsGameStreamEnabled();
|
||
if (lastGameStreamState != currentGameStreamState) {
|
||
SetEvent((HANDLE)Context);
|
||
}
|
||
}
|
||
|
||
printf("RegNotifyChangeKeyValue() failed: %d" NL, err);
|
||
return err;
|
||
}
|
||
|
||
int Run()
|
||
{
|
||
HANDLE ifaceChangeEvent = CreateEvent(nullptr, true, false, nullptr);
|
||
HANDLE gsChangeEvent = CreateEvent(nullptr, true, false, nullptr);
|
||
HANDLE events[2] = { ifaceChangeEvent, gsChangeEvent };
|
||
|
||
ResetLogFile();
|
||
|
||
// Create the thread to watch for GameStream state changes
|
||
CreateThread(nullptr, 0, GameStreamStateChangeThread, gsChangeEvent, 0, nullptr);
|
||
|
||
// Watch for IP address and interface changes
|
||
HANDLE ifaceChangeHandle;
|
||
NotifyIpInterfaceChange(AF_UNSPEC, IpInterfaceChangeNotificationCallback, ifaceChangeEvent, false, &ifaceChangeHandle);
|
||
|
||
for (;;) {
|
||
ResetEvent(gsChangeEvent);
|
||
ResetEvent(ifaceChangeEvent);
|
||
UpdatePortMappings(IsGameStreamEnabled());
|
||
|
||
// Refresh when half the duration is expired or if an IP interface
|
||
// change event occurs.
|
||
printf("Going to sleep..." NL);
|
||
fflush(stdout);
|
||
|
||
ULONGLONG beforeSleepTime = GetTickCount64();
|
||
DWORD ret = WaitForMultipleObjects(ARRAYSIZE(events), events, false, (PORT_MAPPING_DURATION_SEC * 1000) / 2);
|
||
if (ret == WAIT_OBJECT_0) {
|
||
ResetLogFile();
|
||
|
||
printf("Woke up for interface change notification after %lld seconds" NL,
|
||
(GetTickCount64() - beforeSleepTime) / 1000);
|
||
|
||
// Wait a little bit for the interface to settle down (DHCP, RA, etc)
|
||
Sleep(10000);
|
||
}
|
||
else if (ret == WAIT_OBJECT_0 + 1) {
|
||
ResetLogFile();
|
||
|
||
printf("Woke up for GameStream state change notification after %lld seconds" NL,
|
||
(GetTickCount64() - beforeSleepTime) / 1000);
|
||
}
|
||
else {
|
||
ResetLogFile();
|
||
|
||
printf("Woke up for periodic refresh" NL);
|
||
}
|
||
}
|
||
}
|
||
|
||
static SERVICE_STATUS_HANDLE ServiceStatusHandle;
|
||
static SERVICE_STATUS ServiceStatus;
|
||
|
||
DWORD
|
||
WINAPI
|
||
HandlerEx(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext)
|
||
{
|
||
switch (dwControl)
|
||
{
|
||
case SERVICE_CONTROL_INTERROGATE:
|
||
return NO_ERROR;
|
||
|
||
case SERVICE_CONTROL_STOP:
|
||
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
|
||
SetServiceStatus(ServiceStatusHandle, &ServiceStatus);
|
||
return NO_ERROR;
|
||
|
||
default:
|
||
return NO_ERROR;
|
||
}
|
||
}
|
||
|
||
VOID
|
||
WINAPI
|
||
ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv)
|
||
{
|
||
int err;
|
||
|
||
ServiceStatusHandle = RegisterServiceCtrlHandlerEx(SERVICE_NAME, HandlerEx, NULL);
|
||
if (ServiceStatusHandle == NULL) {
|
||
fprintf(stderr, "RegisterServiceCtrlHandlerEx() failed: %d" NL, GetLastError());
|
||
return;
|
||
}
|
||
|
||
ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
|
||
ServiceStatus.dwServiceSpecificExitCode = 0;
|
||
ServiceStatus.dwWin32ExitCode = NO_ERROR;
|
||
ServiceStatus.dwWaitHint = 0;
|
||
ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
|
||
ServiceStatus.dwCheckPoint = 0;
|
||
|
||
// Tell SCM we're running
|
||
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
|
||
SetServiceStatus(ServiceStatusHandle, &ServiceStatus);
|
||
|
||
// Start the service
|
||
err = Run();
|
||
if (err != 0) {
|
||
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
|
||
ServiceStatus.dwWin32ExitCode = err;
|
||
SetServiceStatus(ServiceStatusHandle, &ServiceStatus);
|
||
return;
|
||
}
|
||
}
|
||
|
||
static const SERVICE_TABLE_ENTRY ServiceTable[] = {
|
||
{ (LPSTR)SERVICE_NAME, ServiceMain },
|
||
{ NULL, NULL }
|
||
};
|
||
|
||
int main(int argc, char* argv[])
|
||
{
|
||
WSADATA wsaData;
|
||
int err = WSAStartup(MAKEWORD(2, 2), &wsaData);
|
||
if (err != NO_ERROR) {
|
||
return err;
|
||
}
|
||
|
||
if (argc == 2 && !strcmp(argv[1], "exe")) {
|
||
Run();
|
||
return 0;
|
||
}
|
||
|
||
return StartServiceCtrlDispatcher(ServiceTable);
|
||
} |