From b9025b50bdbb281fd433b04fac5239dbe9394f65 Mon Sep 17 00:00:00 2001 From: ANR2ME Date: Thu, 19 Mar 2020 15:21:00 +0700 Subject: [PATCH] Added a UPnP option to use original port for external port instead of using the shifted port by port offset, to be compatible with real PSP or other PSP emulators --- Core/Config.cpp | 3 ++- Core/Config.h | 1 + Core/HLE/proAdhoc.cpp | 39 ++++++++++++++++++++++++++++--------- Core/HLE/proAdhoc.h | 6 ++++++ Core/HLE/proAdhocServer.cpp | 4 ++-- Core/HLE/sceNet.cpp | 1 + Core/HLE/sceNetAdhoc.cpp | 27 ++++++++++++++++--------- Core/Util/PortManager.cpp | 29 +++++++++++++-------------- Core/Util/PortManager.h | 12 ++++++------ UI/GameSettingsScreen.cpp | 9 ++++++--- 10 files changed, 86 insertions(+), 45 deletions(-) diff --git a/Core/Config.cpp b/Core/Config.cpp index a61ea02740..3bc22a4a20 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -957,8 +957,9 @@ static ConfigSetting networkSettings[] = { ConfigSetting("EnableAdhocServer", &g_Config.bEnableAdhocServer, false, true, true), ConfigSetting("proAdhocServer", &g_Config.proAdhocServer, "myneighborsushicat.com", true, true), ConfigSetting("PortOffset", &g_Config.iPortOffset, 0, true, true), - ConfigSetting("EnableUPnP", &g_Config.bEnableUPnP, false, true, true), ConfigSetting("MinTimeout", &g_Config.iMinTimeout, 1, true, true), + ConfigSetting("EnableUPnP", &g_Config.bEnableUPnP, false, true, true), + ConfigSetting("UPnPUseOriginalPort", &g_Config.bUPnPUseOriginalPort, true, true, true), ConfigSetting("EnableNetworkChat", &g_Config.bEnableNetworkChat, false, true, true), ConfigSetting("ChatButtonPosition",&g_Config.iChatButtonPosition,BOTTOM_LEFT,true,true), diff --git a/Core/Config.h b/Core/Config.h index 8ef4696b4f..5bd145c601 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -412,6 +412,7 @@ public: bool bEnableWlan; bool bEnableAdhocServer; bool bEnableUPnP; + bool bUPnPUseOriginalPort; int iPortOffset; int iMinTimeout; int iWlanAdhocChannel; diff --git a/Core/HLE/proAdhoc.cpp b/Core/HLE/proAdhoc.cpp index 418612ee56..1bbbf61714 100644 --- a/Core/HLE/proAdhoc.cpp +++ b/Core/HLE/proAdhoc.cpp @@ -73,6 +73,7 @@ std::string message = ""; bool chatScreenVisible = false; bool updateChatScreen = false; int newChat = 0; +bool isOriPort = false; bool isLocalServer = false; sockaddr LocalhostIP; sockaddr LocalIP; @@ -82,11 +83,7 @@ bool isLocalMAC(const SceNetEtherAddr * addr) { SceNetEtherAddr saddr; getLocalMac(&saddr); - // Compare MAC Addresses - int match = memcmp((const void *)addr, (const void *)&saddr, ETHER_ADDR_LEN); - - // Return Result - return (match == 0); + return (memcmp((const void*)addr, (const void*)&saddr, ETHER_ADDR_LEN) == 0); } bool isPDPPortInUse(uint16_t port) { @@ -1604,6 +1601,33 @@ uint32_t getLocalIp(int sock) { return localAddr.sin_addr.s_addr; } +static std::vector> InitPrivateIPRanges() { + struct sockaddr_in saNet, saMask; + std::vector> ip_ranges; + + if (1 == inet_pton(AF_INET, "192.168.0.0", &(saNet.sin_addr)) && 1 == inet_pton(AF_INET, "255.255.0.0", &(saMask.sin_addr))) + ip_ranges.push_back({saNet.sin_addr.s_addr, saMask.sin_addr.s_addr}); + if (1 == inet_pton(AF_INET, "172.16.0.0", &(saNet.sin_addr)) && 1 == inet_pton(AF_INET, "255.240.0.0", &(saMask.sin_addr))) + ip_ranges.push_back({ saNet.sin_addr.s_addr, saMask.sin_addr.s_addr }); + if (1 == inet_pton(AF_INET, "10.0.0.0", &(saNet.sin_addr)) && 1 == inet_pton(AF_INET, "255.0.0.0", &(saMask.sin_addr))) + ip_ranges.push_back({ saNet.sin_addr.s_addr, saMask.sin_addr.s_addr }); + if (1 == inet_pton(AF_INET, "127.0.0.0", &(saNet.sin_addr)) && 1 == inet_pton(AF_INET, "255.0.0.0", &(saMask.sin_addr))) + ip_ranges.push_back({ saNet.sin_addr.s_addr, saMask.sin_addr.s_addr }); + if (1 == inet_pton(AF_INET, "169.254.0.0", &(saNet.sin_addr)) && 1 == inet_pton(AF_INET, "255.255.0.0", &(saMask.sin_addr))) + ip_ranges.push_back({ saNet.sin_addr.s_addr, saMask.sin_addr.s_addr }); + + return ip_ranges; +} + +bool isPrivateIP(uint32_t ip) { + static const std::vector> ip_ranges = InitPrivateIPRanges(); + for (auto ipRange : ip_ranges) { + if ((ip & ipRange.second) == (ipRange.first & ipRange.second)) // We can just use ipRange.first directly if it's already correctly formatted + return true; + } + return false; +} + void getLocalMac(SceNetEtherAddr * addr){ // Read MAC Address from config uint8_t mac[ETHER_ADDR_LEN] = {0}; @@ -1859,10 +1883,7 @@ int initNetwork(SceNetAdhocctlAdhocId *adhoc_id){ } bool isBroadcastMAC(const SceNetEtherAddr * addr) { - // Broadcast MAC - if (memcmp(addr->data, "\xFF\xFF\xFF\xFF\xFF\xFF", ETHER_ADDR_LEN) == 0) return true; - // Normal MAC - return false; + return (memcmp(addr->data, "\xFF\xFF\xFF\xFF\xFF\xFF", ETHER_ADDR_LEN) == 0); } bool resolveIP(uint32_t ip, SceNetEtherAddr * mac) { diff --git a/Core/HLE/proAdhoc.h b/Core/HLE/proAdhoc.h index 29ce58f2d1..c1d5fe8b25 100644 --- a/Core/HLE/proAdhoc.h +++ b/Core/HLE/proAdhoc.h @@ -844,6 +844,7 @@ extern SceNetAdhocPtpStat * ptp[255]; extern uint16_t portOffset; extern uint32_t minSocketTimeoutUS; +extern bool isOriPort; extern bool isLocalServer; extern sockaddr LocalhostIP; // Used to differentiate localhost IP on multiple-instance extern sockaddr LocalIP; // IP of Network Adapter used to connect to Adhoc Server (LAN/WAN) @@ -1159,6 +1160,11 @@ int getActivePeerCount(const bool excludeTimedout = true); int getLocalIp(sockaddr_in * SocketAddress); uint32_t getLocalIp(int sock); +/* + * Check if an IP (big-endian/network order) is Private or Public IP + */ +bool isPrivateIP(uint32_t ip); + /* * Get UDP Socket Max Message Size */ diff --git a/Core/HLE/proAdhocServer.cpp b/Core/HLE/proAdhocServer.cpp index c8ef8ea79c..7d663ba1d4 100644 --- a/Core/HLE/proAdhocServer.cpp +++ b/Core/HLE/proAdhocServer.cpp @@ -1685,13 +1685,13 @@ int proAdhocServerThread(int port) // (int argc, char * argv[]) INFO_LOG(SCENET, "AdhocServer: Listening for Connections on TCP Port %u", port); //SERVER_PORT // Port forward - g_PortManager.Add(port, IP_PROTOCOL_TCP); + g_PortManager.Add(IP_PROTOCOL_TCP, port); // Enter Server Loop result = server_loop(server); // Remove Port mapping - g_PortManager.Remove(port, IP_PROTOCOL_TCP); + g_PortManager.Remove(IP_PROTOCOL_TCP, port); // Notify User INFO_LOG(SCENET, "AdhocServer: Shutdown complete"); diff --git a/Core/HLE/sceNet.cpp b/Core/HLE/sceNet.cpp index 208cbfebd3..7a04f827cc 100644 --- a/Core/HLE/sceNet.cpp +++ b/Core/HLE/sceNet.cpp @@ -118,6 +118,7 @@ static void __ResetInitNetLib() { void __NetInit() { // Windows: Assuming WSAStartup already called beforehand portOffset = g_Config.iPortOffset; + isOriPort = g_Config.bEnableUPnP && g_Config.bUPnPUseOriginalPort; minSocketTimeoutUS = g_Config.iMinTimeout * 1000UL; InitLocalhostIP(); diff --git a/Core/HLE/sceNetAdhoc.cpp b/Core/HLE/sceNetAdhoc.cpp index 301c41d5c1..28dca3b32b 100644 --- a/Core/HLE/sceNetAdhoc.cpp +++ b/Core/HLE/sceNetAdhoc.cpp @@ -377,7 +377,7 @@ static int sceNetAdhocPdpCreate(const char *mac, int port, int bufferSize, u32 u // Fill in Data internal->id = usocket; internal->laddr = *saddr; - internal->lport = port; //getLocalPort(usocket) - portOffset; //should use the port given to the socket (in case it's UNUSED_PORT port) isn't? + internal->lport = port; //getLocalPort(usocket) - portOffset; internal->rcv_sb_cc = bufferSize; // Link Socket to Translator ID @@ -385,7 +385,7 @@ static int sceNetAdhocPdpCreate(const char *mac, int port, int bufferSize, u32 u // Forward Port on Router //sceNetPortOpen("UDP", port); - g_PortManager.Add(port + portOffset, IP_PROTOCOL_UDP); + g_PortManager.Add(IP_PROTOCOL_UDP, isOriPort ? port : port + portOffset, port + portOffset); // Success return i + 1; @@ -519,6 +519,9 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int // Get Peer IP if (resolveMAC((SceNetEtherAddr *)daddr, (uint32_t *)&target.sin_addr.s_addr)) { + // Some games (ie. PSP2) might try to talk to it's self, not sure if they talked through WAN or LAN when using public Adhoc Server tho + target.sin_port = htons(dport + ((isOriPort && !isPrivateIP(target.sin_addr.s_addr)) ? 0 : portOffset)); + // Acquire Network Lock //_acquireNetworkLock(); @@ -588,7 +591,7 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int sockaddr_in target; target.sin_family = AF_INET; target.sin_addr.s_addr = peer->ip_addr; - target.sin_port = htons(dport + portOffset); + target.sin_port = htons(dport + ((isOriPort && !isPrivateIP(peer->ip_addr)) ? 0 : portOffset)); int sent = sendto(socket->id, (const char *)data, len, 0, (sockaddr *)&target, sizeof(target)); int error = errno; @@ -980,7 +983,7 @@ static int sceNetAdhocPdpDelete(int id, int unknown) { // Remove Port Forward from Router //sceNetPortClose("UDP", sock->lport); - //g_PortManager.Remove(sock->lport + portOffset, IP_PROTOCOL_UDP); // Let's not remove mapping in real-time as it could cause lags/disconnection when joining a room with slow routers + //g_PortManager.Remove(IP_PROTOCOL_UDP, isOriPort ? sock->lport : sock->lport + portOffset); // Let's not remove mapping in real-time as it could cause lags/disconnection when joining a room with slow routers // Free Memory free(sock); @@ -1870,6 +1873,7 @@ static int sceNetAdhocPtpOpen(const char *srcmac, int sport, const char *dstmac, } SceNetEtherAddr* saddr = (SceNetEtherAddr*)srcmac; SceNetEtherAddr* daddr = (SceNetEtherAddr*)dstmac; + bool isClient = false; // Library is initialized if (netAdhocInited) { // Some games (ie. DBZ Shin Budokai 2) might be getting the saddr/srcmac content from SaveState and causing problems if current MAC is different :( So we try to fix it here @@ -1880,6 +1884,7 @@ static int sceNetAdhocPtpOpen(const char *srcmac, int sport, const char *dstmac, if (saddr != NULL && isLocalMAC(saddr) && daddr != NULL && !isBroadcastMAC(daddr)) { // Random Port required if (sport == 0) { + isClient = true; // Find unused Port // while (sport == 0 || _IsPTPPortInUse(sport)) { // // Generate Port Number @@ -1963,7 +1968,8 @@ static int sceNetAdhocPtpOpen(const char *srcmac, int sport, const char *dstmac, // Add Port Forward to Router. We may not even need to forward this local port, since PtpOpen usually have port 0 (any port) as source port and followed by PtpConnect (which mean acting as Client), right? //sceNetPortOpen("TCP", sport); - //g_PortManager.Add(sport + portOffset, IP_PROTOCOL_TCP); + if (!isClient) + g_PortManager.Add(IP_PROTOCOL_TCP, isOriPort ? sport : sport + portOffset, sport + portOffset); // Return PTP Socket Pointer return i + 1; @@ -2153,7 +2159,7 @@ static int sceNetAdhocPtpAccept(int id, u32 peerMacAddrPtr, u32 peerPortPtr, int // Add Port Forward to Router. Or may be doesn't need to be forwarded since local port already accessible from outside if others were able to connect & get accepted at this point, right? //sceNetPortOpen("TCP", internal->lport); - //g_PortManager.Add(internal->lport + portOffset, IP_PROTOCOL_TCP); + //g_PortManager.Add(IP_PROTOCOL_TCP, internal->lport + portOffset); INFO_LOG(SCENET, "sceNetAdhocPtpAccept[%i->%i:%u]: Established (%s:%u)", id, i+1, internal->lport, inet_ntoa(peeraddr.sin_addr), internal->pport); @@ -2234,6 +2240,9 @@ static int sceNetAdhocPtpConnect(int id, int timeout, int flag) { // Grab Peer IP if (resolveMAC(&socket->paddr, (uint32_t *)&sin.sin_addr.s_addr)) { + // Some games (ie. PSP2) might try to talk to it's self, not sure if they talked through WAN or LAN when using public Adhoc Server tho + sin.sin_port = htons(socket->pport + ((isOriPort && !isPrivateIP(sin.sin_addr.s_addr)) ? 0 : portOffset)); + // Grab Nonblocking Flag uint32_t nbio = getBlockingFlag(socket->id); @@ -2245,7 +2254,7 @@ static int sceNetAdhocPtpConnect(int id, int timeout, int flag) { }*/ // Connect Socket to Peer (Nonblocking) - // Fixme: The First Non-blocking POSIX connect will always returns EAGAIN/EWOULDBLOCK because it returns without waiting for ACK, But GvG Next Plus is treating non-blocking connect just like blocking connect, May be on a real PSP the first non-blocking sceNetAdhocPtpConnect can be successfull? + // NOTE: Based on what i read at stackoverflow, The First Non-blocking POSIX connect will always returns EAGAIN/EWOULDBLOCK because it returns without waiting for ACK/handshake, But GvG Next Plus is treating non-blocking PtpConnect just like blocking connect, May be on a real PSP the first non-blocking sceNetAdhocPtpConnect can be successfull? int connectresult = connect(socket->id, (sockaddr *)&sin, sizeof(sin)); // Grab Error Code @@ -2352,7 +2361,7 @@ static int sceNetAdhocPtpClose(int id, int unknown) { // Remove Port Forward from Router //sceNetPortClose("TCP", socket->lport); - //g_PortManager.Remove(socket->lport + portOffset, IP_PROTOCOL_TCP); // Let's not remove mapping in real-time as it could cause lags/disconnection when joining a room with slow routers + //g_PortManager.Remove(IP_PROTOCOL_TCP, isOriPort ? socket->lport : socket->lport + portOffset); // Let's not remove mapping in real-time as it could cause lags/disconnection when joining a room with slow routers // Free Memory free(socket); @@ -2488,7 +2497,7 @@ static int sceNetAdhocPtpListen(const char *srcmac, int sport, int bufsize, int // Add Port Forward to Router //sceNetPortOpen("TCP", sport); - g_PortManager.Add(sport + portOffset, IP_PROTOCOL_TCP); + g_PortManager.Add(IP_PROTOCOL_TCP, isOriPort ? sport : sport + portOffset, sport + portOffset); // Return PTP Socket Pointer return i + 1; diff --git a/Core/Util/PortManager.cpp b/Core/Util/PortManager.cpp index 166944e75b..8e25cdca37 100644 --- a/Core/Util/PortManager.cpp +++ b/Core/Util/PortManager.cpp @@ -30,8 +30,8 @@ #include #include #include -#include "Core/HLE/proAdhoc.h" // This import is only used to get product id that was used to connect to adhoc server #include "Core/Util/PortManager.h" +#include #include "i18n/i18n.h" @@ -77,7 +77,7 @@ bool PortManager::Init(const unsigned int timeout) { char* descXML; int descXMLsize = 0; int descXMLstatus = 0; - int localport = m_LocalPort; // UPNP_LOCAL_PORT_ANY (0), or UPNP_LOCAL_PORT_SAME (1) as an alias for 1900 for backwards compatability? + int localport = m_LocalPort; // UPNP_LOCAL_PORT_ANY (0), or UPNP_LOCAL_PORT_SAME (1) as an alias for 1900 (for backwards compatability?) int ipv6 = 0; // 0 = IPv4, 1 = IPv6 unsigned char ttl = 2; // defaulting to 2 int error = 0; @@ -151,13 +151,8 @@ bool PortManager::Init(const unsigned int timeout) { INFO_LOG(SCENET, "PortManager - Connection Type: %s", connectionType); } - // Using Game ID & Player Name as default description for mapping (prioritizing the ID sent by the game to Adhoc server) - char productid[10] = { 0 }; - memcpy(productid, product_code.data, sizeof(product_code.data)); - std::string gameID = std::string(productid); - if (productid[0] == '\0') { - gameID = g_paramSFO.GetDiscID(); - } + // Using Game ID & Player Name as default description for mapping + std::string gameID = g_paramSFO.GetDiscID(); m_defaultDesc = "PPSSPP:" + gameID + ":" + g_Config.sNickName; freeUPNPDevlist(devlist); @@ -179,17 +174,21 @@ int PortManager::GetInitState() { return m_InitState; } -bool PortManager::Add(unsigned short port, const char* protocol) { +bool PortManager::Add(const char* protocol, unsigned short port, unsigned short intport) { char port_str[16]; + char intport_str[16]; int r; - INFO_LOG(SCENET, "PortManager::Add(%d, %s)", port, protocol); + if (intport == 0) + intport = port; + INFO_LOG(SCENET, "PortManager::Add(%s, %d, %d)", protocol, port, intport); if (urls == NULL || urls->controlURL == NULL || urls->controlURL[0] == '\0') { if (g_Config.bEnableUPnP) WARN_LOG(SCENET, "PortManager::Add - the init was not done !"); return false; } sprintf(port_str, "%d", port); + sprintf(intport_str, "%d", intport); // Only add new port map if it's not previously created by PPSSPP for current IP auto el_it = std::find_if(m_portList.begin(), m_portList.end(), [port_str, protocol](const std::pair &el) { return el.first == port_str && el.second == protocol; }); @@ -201,11 +200,11 @@ bool PortManager::Add(unsigned short port, const char* protocol) { r = UPNP_DeletePortMapping(urls->controlURL, datas->first.servicetype, port_str, protocol, NULL); } r = UPNP_AddPortMapping(urls->controlURL, datas->first.servicetype, - port_str, port_str, m_lanip.c_str(), m_defaultDesc.c_str(), protocol, NULL, m_leaseDuration.c_str()); + port_str, intport_str, m_lanip.c_str(), m_defaultDesc.c_str(), protocol, NULL, m_leaseDuration.c_str()); if (r == 725 && m_leaseDuration != "0") { m_leaseDuration = "0"; r = UPNP_AddPortMapping(urls->controlURL, datas->first.servicetype, - port_str, port_str, m_lanip.c_str(), m_defaultDesc.c_str(), protocol, NULL, m_leaseDuration.c_str()); + port_str, intport_str, m_lanip.c_str(), m_defaultDesc.c_str(), protocol, NULL, m_leaseDuration.c_str()); } if (r != 0) { @@ -224,10 +223,10 @@ bool PortManager::Add(unsigned short port, const char* protocol) { return true; } -bool PortManager::Remove(unsigned short port, const char* protocol) { +bool PortManager::Remove(const char* protocol, unsigned short port) { char port_str[16]; - INFO_LOG(SCENET, "PortManager::Remove(%d, %s)", port, protocol); + INFO_LOG(SCENET, "PortManager::Remove(%s, %d)", protocol, port); if (urls == NULL || urls->controlURL == NULL || urls->controlURL[0] == '\0') { if (g_Config.bEnableUPnP) WARN_LOG(SCENET, "PortManager::Remove - the init was not done !"); diff --git a/Core/Util/PortManager.h b/Core/Util/PortManager.h index 9a4cdedf05..85a285a5eb 100644 --- a/Core/Util/PortManager.h +++ b/Core/Util/PortManager.h @@ -69,19 +69,19 @@ public: // Get UPnP Initialization status int GetInitState(); - // Add a port & protocol (TCP, UDP or vendor-defined) to map for forwarding - bool Add(unsigned short port, const char* protocol); + // Add a port & protocol (TCP, UDP or vendor-defined) to map for forwarding (intport = 0 : same as [external] port) + bool Add(const char* protocol, unsigned short port, unsigned short intport = 0); - // Remove a port mapping - bool Remove(unsigned short port, const char* protocol); + // Remove a port mapping (external port) + bool Remove(const char* protocol, unsigned short port); - // Removes any lingering mapped ports from previous crashes + // Removes any lingering mapped ports created by PPSSPP from previous crashes bool Clear(); // Restore ports mapped by others that were taken by PPSSPP, better used after Clear() bool Restore(); - // Get port list mapped by PPSSPP for current LAN IP & others + // Get port lists mapped by PPSSPP for current LAN IP & other's applications bool RefreshPortList(); protected: diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 4090861c53..b26cb38035 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -690,7 +690,6 @@ void GameSettingsScreen::CreateViews() { networkingSettings->Add(new ChoiceWithValueDisplay(&g_Config.proAdhocServer, n->T("Change proAdhocServer Address"), (const char *)nullptr))->OnClick.Handle(this, &GameSettingsScreen::OnChangeproAdhocServerAddress); networkingSettings->Add(new CheckBox(&g_Config.bEnableAdhocServer, n->T("Enable built-in PRO Adhoc Server", "Enable built-in PRO Adhoc Server"))); - networkingSettings->Add(new CheckBox(&g_Config.bEnableUPnP, n->T("Enable UPnP", "Enable UPnP (need a few seconds to detect)"))); networkingSettings->Add(new ChoiceWithValueDisplay(&g_Config.sMACAddress, n->T("Change Mac Address"), (const char *)nullptr))->OnClick.Handle(this, &GameSettingsScreen::OnChangeMacAddress); static const char* wlanChannels[] = { "Auto", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11" }; auto wlanChannelChoice = networkingSettings->Add(new PopupMultiChoice(&g_Config.iWlanAdhocChannel, gr->T("WLAN Channel"), wlanChannels, 0, ARRAY_SIZE(wlanChannels), gr->GetName(), screenManager())); @@ -698,8 +697,12 @@ void GameSettingsScreen::CreateViews() { wlanChannelChoice->HideChoice(i+2); wlanChannelChoice->HideChoice(i+7); } - networkingSettings->Add(new PopupSliderChoice(&g_Config.iPortOffset, 0, 60000, n->T("Port offset", "Port offset(0 = PSP compatibility)"), 100, screenManager())); - networkingSettings->Add(new PopupSliderChoice(&g_Config.iMinTimeout, 1, 15000, n->T("Minimum Timeout", "Minimum Timeout (override low latency communication in milliseconds)"), 100, screenManager())); + networkingSettings->Add(new PopupSliderChoice(&g_Config.iPortOffset, 0, 60000, n->T("Port offset", "Port offset (0 = PSP compatibility)"), 100, screenManager())); + networkingSettings->Add(new PopupSliderChoice(&g_Config.iMinTimeout, 1, 15000, n->T("Minimum Timeout", "Minimum Timeout (override low latency in ms)"), 100, screenManager())); + + networkingSettings->Add(new ItemHeader(n->T("UPnP"))); + networkingSettings->Add(new CheckBox(&g_Config.bEnableUPnP, n->T("Enable UPnP", "Enable UPnP (need a few seconds to detect)"))); + networkingSettings->Add(new CheckBox(&g_Config.bUPnPUseOriginalPort, n->T("UPnP use original port", "UPnP use original port (PSP compatibility)")))->SetEnabledPtr(&g_Config.bEnableUPnP); networkingSettings->Add(new ItemHeader(n->T("Chat"))); networkingSettings->Add(new CheckBox(&g_Config.bEnableNetworkChat, n->T("Enable network chat", "Enable network chat")));