Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cdbaa0aff3 | ||
|
|
36ea86faaa | ||
|
|
8590a0813b | ||
|
|
51ae1c8770 | ||
|
|
f1ca6a71f0 | ||
|
|
ac92212464 | ||
|
|
dbf43ac7a1 | ||
|
|
893aa76c9c | ||
|
|
21d8c71a2c | ||
|
|
41ef072c9b | ||
|
|
53246bd4c5 | ||
|
|
ac850e79d8 | ||
|
|
944c8993e8 | ||
|
|
b6508d9024 |
+1
-1
Submodule GS-IPv6-Forwarder updated: d2b927c7b1...6fc14fa95e
@@ -70,6 +70,7 @@
|
||||
ErrorControl="ignore"
|
||||
Interactive="no">
|
||||
<ServiceConfig DelayedAutoStart="no" OnInstall="yes" OnReinstall="yes" ServiceSid="unrestricted"/>
|
||||
<util:ServiceConfig FirstFailureActionType="restart" SecondFailureActionType="restart" ThirdFailureActionType="restart" ResetPeriodInDays="1" RestartServiceDelayInSeconds="10"/>
|
||||
</ServiceInstall>
|
||||
<ServiceControl Id="StartService" Start="install" Stop="both" Remove="uninstall" Name="MISS" Wait="yes" />
|
||||
</Component>
|
||||
@@ -80,6 +81,43 @@
|
||||
Name="Moonlight Internet Streaming Tester"/>
|
||||
</File>
|
||||
</Component>
|
||||
<Component Id="GameStreamFirewallRules" Guid="{16CA2511-B533-46F8-8882-F8AEEFDFD7DF}" KeyPath="yes">
|
||||
<fire:FirewallException Id="HttpsFwException"
|
||||
Scope="any"
|
||||
Port="47984"
|
||||
Protocol="tcp"
|
||||
Name="Moonlight - HTTPS"/>
|
||||
<fire:FirewallException Id="HttpFwException"
|
||||
Scope="any"
|
||||
Port="47989"
|
||||
Protocol="tcp"
|
||||
Name="Moonlight - HTTP"/>
|
||||
<fire:FirewallException Id="RtspFwException"
|
||||
Scope="any"
|
||||
Port="48010"
|
||||
Protocol="tcp"
|
||||
Name="Moonlight - RTSP"/>
|
||||
<fire:FirewallException Id="VideoFwException"
|
||||
Scope="any"
|
||||
Port="47998"
|
||||
Protocol="udp"
|
||||
Name="Moonlight - Video"/>
|
||||
<fire:FirewallException Id="ControlFwException"
|
||||
Scope="any"
|
||||
Port="47999"
|
||||
Protocol="udp"
|
||||
Name="Moonlight - Control"/>
|
||||
<fire:FirewallException Id="AudioFwException"
|
||||
Scope="any"
|
||||
Port="48000"
|
||||
Protocol="udp"
|
||||
Name="Moonlight - Audio"/>
|
||||
<fire:FirewallException Id="RtspuFwException"
|
||||
Scope="any"
|
||||
Port="48010"
|
||||
Protocol="udp"
|
||||
Name="Moonlight - RTSPU"/>
|
||||
</Component>
|
||||
<Component Id="Shortcuts" Guid="*">
|
||||
<Shortcut Id="StartMenuShortcut"
|
||||
Name="Moonlight Internet Streaming Tester"
|
||||
|
||||
+30
-23
@@ -116,7 +116,7 @@ bool UPnPMapPort(struct UPNPUrls* urls, struct IGDdatas* data, int proto, const
|
||||
// Some routers change the description, so we can't check that here
|
||||
if (!strcmp(intClient, myAddr)) {
|
||||
if (atoi(leaseDuration) == 0) {
|
||||
printf("OK (Static)" NL);
|
||||
printf("OK (Static, Internal port: %s)" NL, intPort);
|
||||
|
||||
// If we have an existing permanent mapping, we can just leave it alone.
|
||||
if (enable) {
|
||||
@@ -124,7 +124,7 @@ bool UPnPMapPort(struct UPNPUrls* urls, struct IGDdatas* data, int proto, const
|
||||
}
|
||||
}
|
||||
else {
|
||||
printf("OK (%s seconds remaining)" NL, leaseDuration);
|
||||
printf("OK (%s seconds remaining, Internal port: %s)" NL, leaseDuration, intPort);
|
||||
}
|
||||
|
||||
// If we're just validating, we found an entry, so we're done.
|
||||
@@ -206,7 +206,7 @@ bool UPnPMapPort(struct UPNPUrls* urls, struct IGDdatas* data, int proto, const
|
||||
else if (indefinite) {
|
||||
printf("STATIC ");
|
||||
}
|
||||
if (err == 718 && proto == IPPROTO_UDP) { // ConflictInMappingEntry
|
||||
if (err == 718 && proto == IPPROTO_UDP && port >= 47000) { // ConflictInMappingEntry
|
||||
// Some UPnP implementations incorrectly deduplicate on the internal port instead
|
||||
// of the external port, in violation of the UPnP IGD specification. Since GFE creates
|
||||
// mappings on the same internal port as us, those routers break our mappings. To
|
||||
@@ -415,7 +415,7 @@ bool NATPMPMapPort(natpmp_t* natpmp, int proto, int port, bool enable, bool inde
|
||||
lifetime = 604800; // 1 week
|
||||
}
|
||||
else {
|
||||
lifetime = 3600;
|
||||
lifetime = PORT_MAPPING_DURATION_SEC;
|
||||
}
|
||||
|
||||
printf("Updating NAT-PMP port mapping for %s %d...", proto == IPPROTO_TCP ? "TCP" : "UDP", port);
|
||||
@@ -805,25 +805,28 @@ void NETIOAPI_API_ IpInterfaceChangeNotificationCallback(PVOID context, PMIB_IPI
|
||||
SetEvent((HANDLE)context);
|
||||
}
|
||||
|
||||
void ResetLogFile()
|
||||
void ResetLogFile(bool standaloneExe)
|
||||
{
|
||||
char oldLogFilePath[MAX_PATH + 1];
|
||||
char currentLogFilePath[MAX_PATH + 1];
|
||||
char timeString[MAX_PATH + 1] = {};
|
||||
SYSTEMTIME time;
|
||||
|
||||
ExpandEnvironmentStringsA("%ProgramData%\\MISS\\miss-old.log", oldLogFilePath, sizeof(oldLogFilePath));
|
||||
ExpandEnvironmentStringsA("%ProgramData%\\MISS\\miss-current.log", currentLogFilePath, sizeof(currentLogFilePath));
|
||||
if (!standaloneExe) {
|
||||
char oldLogFilePath[MAX_PATH + 1];
|
||||
char currentLogFilePath[MAX_PATH + 1];
|
||||
|
||||
// Close the existing stdout handle. This is important because otherwise
|
||||
// it may still be open as stdout when we try to MoveFileEx below.
|
||||
fclose(stdout);
|
||||
ExpandEnvironmentStringsA("%ProgramData%\\MISS\\miss-old.log", oldLogFilePath, sizeof(oldLogFilePath));
|
||||
ExpandEnvironmentStringsA("%ProgramData%\\MISS\\miss-current.log", currentLogFilePath, sizeof(currentLogFilePath));
|
||||
|
||||
// Rotate the current to the old log file
|
||||
MoveFileExA(currentLogFilePath, oldLogFilePath, MOVEFILE_REPLACE_EXISTING);
|
||||
// Close the existing stdout handle. This is important because otherwise
|
||||
// it may still be open as stdout when we try to MoveFileEx below.
|
||||
fclose(stdout);
|
||||
|
||||
// Redirect stdout to this new file
|
||||
freopen(currentLogFilePath, "w", stdout);
|
||||
// Rotate the current to the old log file
|
||||
MoveFileExA(currentLogFilePath, oldLogFilePath, MOVEFILE_REPLACE_EXISTING);
|
||||
|
||||
// Redirect stdout to this new file
|
||||
freopen(currentLogFilePath, "w", stdout);
|
||||
}
|
||||
|
||||
// Print a log header
|
||||
printf("Moonlight Internet Streaming Service v" VER_VERSION_STR NL);
|
||||
@@ -860,13 +863,17 @@ DWORD WINAPI GameStreamStateChangeThread(PVOID Context)
|
||||
return err;
|
||||
}
|
||||
|
||||
int Run()
|
||||
int Run(bool standaloneExe)
|
||||
{
|
||||
HANDLE ifaceChangeEvent = CreateEvent(nullptr, true, false, nullptr);
|
||||
HANDLE gsChangeEvent = CreateEvent(nullptr, true, false, nullptr);
|
||||
HANDLE events[2] = { ifaceChangeEvent, gsChangeEvent };
|
||||
|
||||
ResetLogFile();
|
||||
ResetLogFile(standaloneExe);
|
||||
|
||||
// Bump the process priority class to above normal. The UDP relay threads will
|
||||
// further raise their own thread priorities to avoid preemption by other activity.
|
||||
SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS);
|
||||
|
||||
// Create the UDP alternate port relays
|
||||
for (int i = 0; i < ARRAYSIZE(k_Ports); i++) {
|
||||
@@ -895,7 +902,7 @@ int Run()
|
||||
ULONGLONG beforeSleepTime = GetTickCount64();
|
||||
DWORD ret = WaitForMultipleObjects(ARRAYSIZE(events), events, false, POLLING_DELAY_SEC * 1000);
|
||||
if (ret == WAIT_OBJECT_0) {
|
||||
ResetLogFile();
|
||||
ResetLogFile(standaloneExe);
|
||||
|
||||
printf("Woke up for interface change notification after %lld seconds" NL,
|
||||
(GetTickCount64() - beforeSleepTime) / 1000);
|
||||
@@ -904,13 +911,13 @@ int Run()
|
||||
Sleep(10000);
|
||||
}
|
||||
else if (ret == WAIT_OBJECT_0 + 1) {
|
||||
ResetLogFile();
|
||||
ResetLogFile(standaloneExe);
|
||||
|
||||
printf("Woke up for GameStream state change notification after %lld seconds" NL,
|
||||
(GetTickCount64() - beforeSleepTime) / 1000);
|
||||
}
|
||||
else {
|
||||
ResetLogFile();
|
||||
ResetLogFile(standaloneExe);
|
||||
|
||||
printf("Woke up for periodic refresh" NL);
|
||||
}
|
||||
@@ -971,7 +978,7 @@ ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv)
|
||||
SetServiceStatus(ServiceStatusHandle, &ServiceStatus);
|
||||
|
||||
// Start the service
|
||||
err = Run();
|
||||
err = Run(false);
|
||||
if (err != 0) {
|
||||
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
|
||||
ServiceStatus.dwWin32ExitCode = err;
|
||||
@@ -994,7 +1001,7 @@ int main(int argc, char* argv[])
|
||||
}
|
||||
|
||||
if (argc == 2 && !strcmp(argv[1], "exe")) {
|
||||
Run();
|
||||
Run(true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
+20
-22
@@ -19,34 +19,28 @@ WINAPI
|
||||
UdpRelayThreadProc(LPVOID Context)
|
||||
{
|
||||
PUDP_TUPLE tuple = (PUDP_TUPLE)Context;
|
||||
fd_set fds;
|
||||
int err;
|
||||
USHORT nboPort = htons(tuple->port);
|
||||
SOCKADDR_IN lastRemoteAddr;
|
||||
|
||||
// Ensure the relay threads aren't preempted by games or other CPU intensive activity
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
|
||||
|
||||
RtlZeroMemory(&lastRemoteAddr, sizeof(lastRemoteAddr));
|
||||
|
||||
for (;;) {
|
||||
char buffer[4096];
|
||||
SOCKADDR_IN sourceAddr;
|
||||
int sourceAddrLen;
|
||||
|
||||
FD_ZERO(&fds);
|
||||
|
||||
FD_SET(tuple->socket, &fds);
|
||||
|
||||
err = select(0, &fds, NULL, NULL, NULL);
|
||||
if (err <= 0) {
|
||||
break;
|
||||
}
|
||||
int recvLen;
|
||||
|
||||
sourceAddrLen = sizeof(sourceAddr);
|
||||
err = recvfrom(tuple->socket, buffer, sizeof(buffer), 0, (PSOCKADDR)&sourceAddr, &sourceAddrLen);
|
||||
if (err == SOCKET_ERROR) {
|
||||
recvLen = recvfrom(tuple->socket, buffer, sizeof(buffer), 0, (PSOCKADDR)&sourceAddr, &sourceAddrLen);
|
||||
if (recvLen == SOCKET_ERROR) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SOCKADDR_IN destinationAddr;
|
||||
if (RtlEqualMemory(&sourceAddr.sin_addr, &in4addr_loopback, sizeof(sourceAddr.sin_addr))) {
|
||||
if (RtlEqualMemory(&sourceAddr.sin_addr, &in4addr_loopback, sizeof(sourceAddr.sin_addr)) && sourceAddr.sin_port == nboPort) {
|
||||
// Traffic incoming from loopback interface - send it to the last remote address
|
||||
destinationAddr = lastRemoteAddr;
|
||||
}
|
||||
@@ -57,10 +51,10 @@ UdpRelayThreadProc(LPVOID Context)
|
||||
// Send it to the normal port via the loopback adapter
|
||||
destinationAddr = sourceAddr;
|
||||
destinationAddr.sin_addr = in4addr_loopback;
|
||||
destinationAddr.sin_port = htons(tuple->port);
|
||||
destinationAddr.sin_port = nboPort;
|
||||
}
|
||||
|
||||
sendto(tuple->socket, buffer, err, 0, (PSOCKADDR)&destinationAddr, sizeof(destinationAddr));
|
||||
sendto(tuple->socket, buffer, recvLen, 0, (PSOCKADDR)&destinationAddr, sizeof(destinationAddr));
|
||||
}
|
||||
|
||||
closesocket(tuple->socket);
|
||||
@@ -74,11 +68,13 @@ int StartUdpRelay(unsigned short Port)
|
||||
SOCKADDR_IN addr;
|
||||
HANDLE thread;
|
||||
PUDP_TUPLE tuple;
|
||||
int error;
|
||||
|
||||
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (sock == INVALID_SOCKET) {
|
||||
printf("socket() failed: %d\n", WSAGetLastError());
|
||||
return WSAGetLastError();
|
||||
error = WSAGetLastError();
|
||||
printf("socket() failed: %d\n", error);
|
||||
return error;
|
||||
}
|
||||
|
||||
// Bind to the alternate port
|
||||
@@ -86,9 +82,10 @@ int StartUdpRelay(unsigned short Port)
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(Port + RELAY_PORT_OFFSET);
|
||||
if (bind(sock, (PSOCKADDR)&addr, sizeof(addr)) == SOCKET_ERROR) {
|
||||
printf("bind() failed: %d\n", WSAGetLastError());
|
||||
error = WSAGetLastError();
|
||||
printf("bind() failed: %d\n", error);
|
||||
closesocket(sock);
|
||||
return WSAGetLastError();
|
||||
return error;
|
||||
}
|
||||
|
||||
tuple = (PUDP_TUPLE)malloc(sizeof(*tuple));
|
||||
@@ -101,9 +98,10 @@ int StartUdpRelay(unsigned short Port)
|
||||
|
||||
thread = CreateThread(NULL, 0, UdpRelayThreadProc, tuple, 0, NULL);
|
||||
if (thread == NULL) {
|
||||
printf("CreateThread() failed: %d\n", GetLastError());
|
||||
error = GetLastError();
|
||||
printf("CreateThread() failed: %d\n", error);
|
||||
closesocket(sock);
|
||||
return GetLastError();
|
||||
return error;
|
||||
}
|
||||
|
||||
CloseHandle(thread);
|
||||
|
||||
+4
-5
@@ -1002,7 +1002,7 @@ UPnPPortStatus UPnPCheckPort(struct UPNPUrls* urls, struct IGDdatas* data, int p
|
||||
}
|
||||
else if (err == UPNPCOMMAND_SUCCESS) {
|
||||
if (!strcmp(myAddr, intClient)) {
|
||||
fprintf(LOG_OUT, "OK\n");
|
||||
fprintf(LOG_OUT, "OK (Internal port: %s)\n", intPort);
|
||||
return OK;
|
||||
}
|
||||
else {
|
||||
@@ -1490,10 +1490,9 @@ int main(int argc, char* argv[])
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Tested against a working server and it failed
|
||||
break;
|
||||
}
|
||||
|
||||
// Tested against a IPv6 working server
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+31
-23
@@ -45,15 +45,20 @@ bool getExternalAddressPortIP4(unsigned short localPort, PSOCKADDR_IN wanAddr)
|
||||
int i;
|
||||
int bytesRead;
|
||||
int err;
|
||||
bool ret;
|
||||
PSTUN_ATTRIBUTE_HEADER attribute;
|
||||
PSTUN_MAPPED_IPV4_ADDRESS_ATTRIBUTE ipv4Attrib;
|
||||
struct addrinfo* result;
|
||||
struct addrinfo hints;
|
||||
struct sockaddr_in bindAddr;
|
||||
union {
|
||||
STUN_MESSAGE hdr;
|
||||
char buf[1024];
|
||||
} resp;
|
||||
|
||||
sock = INVALID_SOCKET;
|
||||
ret = false;
|
||||
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_flags = AI_ADDRCONFIG;
|
||||
@@ -68,18 +73,15 @@ bool getExternalAddressPortIP4(unsigned short localPort, PSOCKADDR_IN wanAddr)
|
||||
sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol);
|
||||
if (sock == INVALID_SOCKET) {
|
||||
fprintf(LOG_OUT, "socket() failed: %d\n", WSAGetLastError());
|
||||
freeaddrinfo(result);
|
||||
return false;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
struct sockaddr_in bindAddr = {};
|
||||
bindAddr = {};
|
||||
bindAddr.sin_family = hints.ai_family;
|
||||
bindAddr.sin_port = htons(localPort);
|
||||
if (bind(sock, (struct sockaddr*)&bindAddr, sizeof(bindAddr)) == SOCKET_ERROR) {
|
||||
fprintf(LOG_OUT, "bind() failed: %d\n", WSAGetLastError());
|
||||
closesocket(sock);
|
||||
freeaddrinfo(result);
|
||||
return false;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
reqMsg.messageType = htons(STUN_MESSAGE_BINDING_REQUEST);
|
||||
@@ -113,9 +115,7 @@ bool getExternalAddressPortIP4(unsigned short localPort, PSOCKADDR_IN wanAddr)
|
||||
}
|
||||
else if (selectRes == SOCKET_ERROR) {
|
||||
fprintf(LOG_OUT, "select() failed: %d\n", WSAGetLastError());
|
||||
closesocket(sock);
|
||||
freeaddrinfo(result);
|
||||
return false;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
// Error handling is below
|
||||
@@ -123,32 +123,29 @@ bool getExternalAddressPortIP4(unsigned short localPort, PSOCKADDR_IN wanAddr)
|
||||
break;
|
||||
}
|
||||
|
||||
freeaddrinfo(result);
|
||||
closesocket(sock);
|
||||
|
||||
if (bytesRead == 0) {
|
||||
fprintf(LOG_OUT, "No response from STUN server\n");
|
||||
return false;
|
||||
goto Exit;
|
||||
}
|
||||
else if (bytesRead == SOCKET_ERROR) {
|
||||
fprintf(LOG_OUT, "Failed to read STUN binding response: %d\n", WSAGetLastError());
|
||||
return false;
|
||||
goto Exit;
|
||||
}
|
||||
else if (bytesRead < sizeof(resp.hdr)) {
|
||||
fprintf(LOG_OUT, "STUN message truncated: %d\n", bytesRead);
|
||||
return false;
|
||||
goto Exit;
|
||||
}
|
||||
else if (htonl(resp.hdr.magicCookie) != STUN_MESSAGE_COOKIE) {
|
||||
fprintf(LOG_OUT, "Bad STUN cookie value: %x\n", htonl(resp.hdr.magicCookie));
|
||||
return false;
|
||||
goto Exit;
|
||||
}
|
||||
else if (memcmp(reqMsg.transactionId, resp.hdr.transactionId, sizeof(reqMsg.transactionId))) {
|
||||
fprintf(LOG_OUT, "STUN transaction ID mismatch\n");
|
||||
return false;
|
||||
goto Exit;
|
||||
}
|
||||
else if (htons(resp.hdr.messageType) != STUN_MESSAGE_BINDING_SUCCESS) {
|
||||
fprintf(LOG_OUT, "STUN message type mismatch: %x\n", htons(resp.hdr.messageType));
|
||||
return false;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
attribute = (PSTUN_ATTRIBUTE_HEADER)(&resp.hdr + 1);
|
||||
@@ -156,7 +153,7 @@ bool getExternalAddressPortIP4(unsigned short localPort, PSOCKADDR_IN wanAddr)
|
||||
while (bytesRead > sizeof(*attribute)) {
|
||||
if (bytesRead < sizeof(*attribute) + htons(attribute->length)) {
|
||||
fprintf(LOG_OUT, "STUN attribute out of bounds: %d\n", htons(attribute->length));
|
||||
return false;
|
||||
goto Exit;
|
||||
}
|
||||
// Mask off the comprehension bit
|
||||
else if ((htons(attribute->type) & 0x7FFF) != STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS) {
|
||||
@@ -169,11 +166,11 @@ bool getExternalAddressPortIP4(unsigned short localPort, PSOCKADDR_IN wanAddr)
|
||||
ipv4Attrib = (PSTUN_MAPPED_IPV4_ADDRESS_ATTRIBUTE)attribute;
|
||||
if (htons(ipv4Attrib->hdr.length) != 8) {
|
||||
fprintf(LOG_OUT, "STUN address length mismatch: %d\n", htons(ipv4Attrib->hdr.length));
|
||||
return false;
|
||||
goto Exit;
|
||||
}
|
||||
else if (ipv4Attrib->addressFamily != 1) {
|
||||
fprintf(LOG_OUT, "STUN address family mismatch: %x\n", ipv4Attrib->addressFamily);
|
||||
return false;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
*wanAddr = {};
|
||||
@@ -183,9 +180,20 @@ bool getExternalAddressPortIP4(unsigned short localPort, PSOCKADDR_IN wanAddr)
|
||||
wanAddr->sin_port = ipv4Attrib->port ^ (short)resp.hdr.magicCookie;
|
||||
wanAddr->sin_addr.S_un.S_addr = ipv4Attrib->address ^ resp.hdr.magicCookie;
|
||||
|
||||
return true;
|
||||
ret = true;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
fprintf(LOG_OUT, "No XOR mapped address found in STUN response!\n");
|
||||
return false;
|
||||
|
||||
Exit:
|
||||
if (sock != INVALID_SOCKET) {
|
||||
closesocket(sock);
|
||||
}
|
||||
|
||||
if (result != NULL) {
|
||||
freeaddrinfo(result);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#define VER_VERSION 5,2,0,0
|
||||
#define VER_VERSION_STR "5.2.0.0"
|
||||
#define VER_VERSION 5,3,1,0
|
||||
#define VER_VERSION_STR "5.3.1.0"
|
||||
|
||||
#define VER_COMPANYNAME_STR "Moonlight Game Streaming Project"
|
||||
#define VER_PRODUCTNAME_STR "Moonlight Internet Hosting Tool"
|
||||
|
||||
Reference in New Issue
Block a user