1
0
mirror of https://github.com/RPCS3/rpcs3.git synced 2024-11-22 02:32:36 +01:00

rpcn: signaling handler improvements & upnp

Simplify signaling by making Matching2 a layer over normal signaling.
Implements UPNP port forwarding
Implement sceNpMatching2AbortRequest
Fix reported bw in sceNpUtil
Hack for Fat Princess binding udp on 3658
Reenable CB for sceNpBasicAddPlayersHistoryAsync
Misc fixes
This commit is contained in:
RipleyTom 2023-01-12 04:05:05 +01:00 committed by Megamouse
parent 364c33060b
commit 6186ac0245
45 changed files with 1290 additions and 747 deletions

3
.gitignore vendored
View File

@ -122,6 +122,9 @@ yaml-cpp.pc
/3rdparty/libusb_cmake/config.h
/3rdparty/libusb_cmake/libusb-1.0.pc
# miniupnp
/3rdparty/miniupnp/x64/*
# ssl certificate
cacert.pem

3
.gitmodules vendored
View File

@ -80,3 +80,6 @@
path = 3rdparty/libsdl-org/SDL
url = https://github.com/libsdl-org/SDL.git
ignore = dirty
[submodule "3rdparty/miniupnp/miniupnp"]
path = 3rdparty/miniupnp/miniupnp
url = https://github.com/miniupnp/miniupnp.git

View File

@ -358,6 +358,9 @@ if(USE_SDL)
endif()
endif()
# MINIUPNP
add_subdirectory(miniupnp EXCLUDE_FROM_ALL)
# add nice ALIAS targets for ease of use
if(USE_SYSTEM_LIBUSB)
add_library(3rdparty::libusb ALIAS usb-1.0-shared)
@ -385,3 +388,4 @@ add_library(3rdparty::wolfssl ALIAS wolfssl)
add_library(3rdparty::libcurl ALIAS libcurl)
add_library(3rdparty::soundtouch ALIAS soundtouch)
add_library(3rdparty::sdl2 ALIAS ${SDL2_TARGET})
add_library(3rdparty::miniupnpc ALIAS libminiupnpc-static)

8
3rdparty/miniupnp/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,8 @@
option (UPNPC_BUILD_STATIC "Build static library" TRUE)
option (UPNPC_BUILD_SHARED "Build shared library" FALSE)
option (UPNPC_BUILD_TESTS "Build test executables" FALSE)
option (UPNPC_BUILD_SAMPLE "Build sample executables" FALSE)
option (NO_GETADDRINFO "Define NO_GETADDRINFO" FALSE)
option (UPNPC_NO_INSTALL "Disable installation" TRUE)
add_subdirectory(miniupnp/miniupnpc EXCLUDE_FROM_ALL)

1
3rdparty/miniupnp/miniupnp vendored Submodule

@ -0,0 +1 @@
Subproject commit f4a739d73083bee207af30b8aa3e668383ee070e

View File

@ -0,0 +1,138 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{5228f863-e0dd-4de7-aa7b-5c52b14cd4d0}</ProjectGuid>
<RootNamespace>miniupnpcstatic</RootNamespace>
</PropertyGroup>
<Import Project="$(SolutionDir)\buildfiles\msvc\common_default.props" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="$(SolutionDir)\buildfiles\msvc\common_default_macros.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)lib\$(Configuration)-$(Platform)\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)lib\$(Configuration)-$(Platform)\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_WINSOCK_DEPRECATED_NO_WARNINGS; _CRT_SECURE_NO_WARNINGS;MINIUPNP_STATICLIB;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>generated;miniupnp\miniupnpc;miniupnp\miniupnpc\include</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
</Link>
<PreBuildEvent>
<Command>cd $(ProjectDir)miniupnp\miniupnpc\msvc
cscript genminiupnpcstrings.vbs</Command>
</PreBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_WINSOCK_DEPRECATED_NO_WARNINGS; _CRT_SECURE_NO_WARNINGS;MINIUPNP_STATICLIB;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>generated;miniupnp\miniupnpc;miniupnp\miniupnpc\include</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>
</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
<PreBuildEvent>
<Command>cd $(ProjectDir)miniupnp\miniupnpc\msvc
cscript genminiupnpcstrings.vbs</Command>
</PreBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="miniupnp\miniupnpc\src\addr_is_reserved.c" />
<ClCompile Include="miniupnp\miniupnpc\src\connecthostport.c" />
<ClCompile Include="miniupnp\miniupnpc\src\igd_desc_parse.c" />
<ClCompile Include="miniupnp\miniupnpc\src\minisoap.c" />
<ClCompile Include="miniupnp\miniupnpc\src\minissdpc.c" />
<ClCompile Include="miniupnp\miniupnpc\src\miniupnpc.c" />
<ClCompile Include="miniupnp\miniupnpc\src\miniwget.c" />
<ClCompile Include="miniupnp\miniupnpc\src\minixml.c" />
<ClCompile Include="miniupnp\miniupnpc\src\portlistingparse.c" />
<ClCompile Include="miniupnp\miniupnpc\src\receivedata.c" />
<ClCompile Include="miniupnp\miniupnpc\src\upnpcommands.c" />
<ClCompile Include="miniupnp\miniupnpc\src\upnpdev.c" />
<ClCompile Include="miniupnp\miniupnpc\src\upnperrors.c" />
<ClCompile Include="miniupnp\miniupnpc\src\upnpreplyparse.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="miniupnp\miniupnpc\src\addr_is_reserved.h" />
<ClInclude Include="miniupnp\miniupnpc\src\connecthostport.h" />
<ClInclude Include="miniupnp\miniupnpc\include\igd_desc_parse.h" />
<ClInclude Include="miniupnp\miniupnpc\src\minisoap.h" />
<ClInclude Include="miniupnp\miniupnpc\src\minissdpc.h" />
<ClInclude Include="miniupnp\miniupnpc\include\miniupnpc.h" />
<ClInclude Include="miniupnp\miniupnpc\miniupnpcstrings.h" />
<ClInclude Include="miniupnp\miniupnpc\include\miniupnpctypes.h" />
<ClInclude Include="miniupnp\miniupnpc\include\miniupnpc_declspec.h" />
<ClInclude Include="miniupnp\miniupnpc\include\miniwget.h" />
<ClInclude Include="miniupnp\miniupnpc\src\miniwget_private.h" />
<ClInclude Include="miniupnp\miniupnpc\src\minixml.h" />
<ClInclude Include="miniupnp\miniupnpc\include\portlistingparse.h" />
<ClInclude Include="miniupnp\miniupnpc\src\receivedata.h" />
<ClInclude Include="miniupnp\miniupnpc\include\upnpcommands.h" />
<ClInclude Include="miniupnp\miniupnpc\include\upnpdev.h" />
<ClInclude Include="miniupnp\miniupnpc\include\upnperrors.h" />
<ClInclude Include="miniupnp\miniupnpc\include\upnpreplyparse.h" />
<ClInclude Include="miniupnp\miniupnpc\src\win32_snprintf.h" />
<ClInclude Include="miniupnp\miniupnpc\rc_version.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -74,7 +74,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libcurl", "3rdparty\curl\li
{73973223-5EE8-41CA-8E88-1D60E89A237B} = {73973223-5EE8-41CA-8E88-1D60E89A237B}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SPIRV", "3rdparty\SPIRV\spirv.vcxproj", "{4CBD3DDD-5555-49A4-A44D-DD3D8CB516A1}"
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "spirv", "3rdparty\SPIRV\spirv.vcxproj", "{4CBD3DDD-5555-49A4-A44D-DD3D8CB516A1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3rdParty", "3rdParty", "{6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8}"
EndProject
@ -93,6 +93,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "pine", "pine", "{A55DA1B5-C
3rdparty\pine\pine_server.h = 3rdparty\pine\pine_server.h
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "miniupnpc_static", "3rdparty\miniupnp\miniupnpc_static.vcxproj", "{5228F863-E0DD-4DE7-AA7B-5C52B14CD4D0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@ -187,6 +189,10 @@ Global
{8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA}.Debug|x64.Build.0 = Debug|x64
{8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA}.Release|x64.ActiveCfg = Release|x64
{8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA}.Release|x64.Build.0 = Release|x64
{5228F863-E0DD-4DE7-AA7B-5C52B14CD4D0}.Debug|x64.ActiveCfg = Debug|x64
{5228F863-E0DD-4DE7-AA7B-5C52B14CD4D0}.Debug|x64.Build.0 = Debug|x64
{5228F863-E0DD-4DE7-AA7B-5C52B14CD4D0}.Release|x64.ActiveCfg = Release|x64
{5228F863-E0DD-4DE7-AA7B-5C52B14CD4D0}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -216,6 +222,7 @@ Global
{508C291A-3D18-49F5-B25D-F7C8DB92CB21} = {6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8}
{8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA} = {6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8}
{A55DA1B5-CC17-4525-BE7F-1659CE17BB56} = {6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8}
{5228F863-E0DD-4DE7-AA7B-5C52B14CD4D0} = {6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {06CC7920-E085-4B81-9582-8DE8AAD42510}

View File

@ -159,6 +159,10 @@ target_link_libraries(rpcs3_emu
PUBLIC
3rdparty::soundtouch)
target_link_libraries(rpcs3_emu
PUBLIC
3rdparty::miniupnpc)
# Cell
target_sources(rpcs3_emu PRIVATE
Cell/MFC.cpp
@ -411,6 +415,8 @@ target_sources(rpcs3_emu PRIVATE
NP/np_structs_extra.cpp
NP/rpcn_client.cpp
NP/rpcn_config.cpp
NP/upnp_config.cpp
NP/upnp_handler.cpp
)
# Memory

View File

@ -221,16 +221,15 @@ error_code cellNetCtlGetInfo(s32 code, vm::ptr<CellNetCtlInfo> info)
switch (code)
{
case CELL_NET_CTL_INFO_DEVICE: info->device = CELL_NET_CTL_DEVICE_WIRED; break;
// case CELL_NET_CTL_INFO_ETHER_ADDR: std::memset(info->ether_addr.data, 0xFF, sizeof(info->ether_addr.data)); break;
case CELL_NET_CTL_INFO_MTU: info->mtu = 1500; break;
case CELL_NET_CTL_INFO_LINK: info->link = CELL_NET_CTL_LINK_CONNECTED; break;
case CELL_NET_CTL_INFO_LINK_TYPE: info->link_type = CELL_NET_CTL_LINK_TYPE_10BASE_FULL; break;
case CELL_NET_CTL_INFO_LINK_TYPE: info->link_type = CELL_NET_CTL_LINK_TYPE_100BASE_FULL; break;
case CELL_NET_CTL_INFO_IP_CONFIG: info->ip_config = CELL_NET_CTL_IP_STATIC; break;
case CELL_NET_CTL_INFO_DEFAULT_ROUTE: strcpy_trunc(info->default_route, "192.168.1.1"); break;
case CELL_NET_CTL_INFO_PRIMARY_DNS: strcpy_trunc(info->primary_dns, np::ip_to_string(nph.get_dns_ip())); break;
case CELL_NET_CTL_INFO_SECONDARY_DNS: strcpy_trunc(info->secondary_dns, np::ip_to_string(nph.get_dns_ip())); break;
case CELL_NET_CTL_INFO_IP_ADDRESS: strcpy_trunc(info->ip_address, np::ip_to_string(nph.get_public_ip_addr())); break;
case CELL_NET_CTL_INFO_NETMASK: strcpy_trunc(info->netmask, "255.255.255.255"); break;
case CELL_NET_CTL_INFO_IP_ADDRESS: strcpy_trunc(info->ip_address, np::ip_to_string(nph.get_local_ip_addr())); break; // verified on HW
case CELL_NET_CTL_INFO_NETMASK: strcpy_trunc(info->netmask, "255.255.255.0"); break;
case CELL_NET_CTL_INFO_HTTP_PROXY_CONFIG: info->http_proxy_config = 0; break;
case CELL_NET_CTL_INFO_DHCP_HOSTNAME: strcpy_trunc(info->dhcp_hostname, nph.get_hostname()); break;
default: cellNetCtl.error("Unsupported request: %s", InfoCodeToName(code)); break;
@ -348,7 +347,7 @@ error_code cellNetCtlGetNatInfo(vm::ptr<CellNetCtlNatInfo> natInfo)
natInfo->nat_type = CELL_NET_CTL_NATINFO_NAT_TYPE_2;
natInfo->stun_status = CELL_NET_CTL_NATINFO_STUN_OK;
natInfo->upnp_status = CELL_NET_CTL_NATINFO_UPNP_NO;
natInfo->upnp_status = nph.get_upnp_status();
return CELL_OK;
}

View File

@ -18,6 +18,7 @@
#include "Emu/Cell/lv2/sys_fs.h"
#include "Emu/NP/np_handler.h"
#include "Emu/NP/np_contexts.h"
#include "Emu/NP/np_helpers.h"
#include "Emu/system_config.h"
LOG_CHANNEL(sceNp);
@ -5237,11 +5238,11 @@ error_code sceNpSignalingActivateConnection(u32 ctx_id, vm::ptr<SceNpId> npId, v
return SCE_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
if (strncmp(nph.get_npid().handle.data, npId->handle.data, 16) == 0)
if (np::is_same_npid(nph.get_npid(), *npId))
return SCE_NP_SIGNALING_ERROR_OWN_NP_ID;
auto& sigh = g_fxo->get<named_thread<signaling_handler>>();
*conn_id = sigh.init_sig_infos(npId.get_ptr());
*conn_id = sigh.init_sig1(*npId);
return CELL_OK;
}
@ -5298,19 +5299,15 @@ error_code sceNpSignalingGetConnectionStatus(u32 ctx_id, u32 conn_id, vm::ptr<s3
const auto si = sigh.get_sig_infos(conn_id);
if (si.connStatus == SCE_NP_SIGNALING_CONN_STATUS_ACTIVE && si.ext_status == ext_sign_peer)
{
*conn_status = SCE_NP_SIGNALING_CONN_STATUS_INACTIVE;
}
else
{
*conn_status = si.connStatus;
}
if (!si)
return SCE_NP_SIGNALING_ERROR_CONN_NOT_FOUND;
*conn_status = si->conn_status;
if (peer_addr)
(*peer_addr).np_s_addr = si.addr; // infos.addr is already BE
(*peer_addr).np_s_addr = si->addr; // infos.addr is already BE
if (peer_port)
*peer_port = si.port;
*peer_port = si->port;
return CELL_OK;
}
@ -5334,33 +5331,36 @@ error_code sceNpSignalingGetConnectionInfo(u32 ctx_id, u32 conn_id, s32 code, vm
auto& sigh = g_fxo->get<named_thread<signaling_handler>>();
const auto si = sigh.get_sig_infos(conn_id);
if (!si)
return SCE_NP_SIGNALING_ERROR_CONN_NOT_FOUND;
switch (code)
{
case SCE_NP_SIGNALING_CONN_INFO_RTT:
{
info->rtt = si.rtt;
info->rtt = si->rtt;
break;
}
case SCE_NP_SIGNALING_CONN_INFO_BANDWIDTH:
{
info->bandwidth = 10'000'000; // 10 MBPS HACK
info->bandwidth = 100'000'000; // 100 MBPS HACK
break;
}
case SCE_NP_SIGNALING_CONN_INFO_PEER_NPID:
{
info->npId = si.npid;
info->npId = si->npid;
break;
}
case SCE_NP_SIGNALING_CONN_INFO_PEER_ADDRESS:
{
info->address.port = std::bit_cast<u16, be_t<u16>>(si.port);
info->address.addr.np_s_addr = si.addr;
info->address.port = std::bit_cast<u16, be_t<u16>>(si->port);
info->address.addr.np_s_addr = si->addr;
break;
}
case SCE_NP_SIGNALING_CONN_INFO_MAPPED_ADDRESS:
{
info->address.port = std::bit_cast<u16, be_t<u16>>(si.mapped_port);
info->address.addr.np_s_addr = si.mapped_addr;
info->address.port = std::bit_cast<u16, be_t<u16>>(si->mapped_port);
info->address.addr.np_s_addr = si->mapped_addr;
break;
}
case SCE_NP_SIGNALING_CONN_INFO_PACKET_LOSS:
@ -5393,8 +5393,13 @@ error_code sceNpSignalingGetConnectionFromNpId(u32 ctx_id, vm::ptr<SceNpId> npId
return SCE_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
if (np::is_same_npid(*npId, nph.get_npid()))
{
return SCE_NP_SIGNALING_ERROR_OWN_NP_ID;
}
auto& sigh = g_fxo->get<named_thread<signaling_handler>>();
const auto found_conn_id = sigh.get_conn_id_from_npid(npId.get_ptr());
const auto found_conn_id = sigh.get_conn_id_from_npid(*npId);
if (!found_conn_id)
{
@ -5455,9 +5460,9 @@ error_code sceNpSignalingGetLocalNetInfo(u32 ctx_id, vm::ptr<SceNpSignalingNetIn
info->mapped_addr = nph.get_public_ip_addr();
// Pure speculation below
info->nat_status = 0;
info->upnp_status = 0;
info->npport_status = 0;
info->nat_status = SCE_NP_SIGNALING_NETINFO_NAT_STATUS_TYPE2;
info->upnp_status = nph.get_upnp_status();
info->npport_status = SCE_NP_SIGNALING_NETINFO_NPPORT_STATUS_OPEN;
info->npport = SCE_NP_PORT;
return CELL_OK;
@ -5537,25 +5542,8 @@ error_code sceNpUtilCmpNpId(vm::ptr<SceNpId> id1, vm::ptr<SceNpId> id2)
return SCE_NP_UTIL_ERROR_INVALID_ARGUMENT;
}
// Unknown what this constant means
// if (id1->reserved[0] != 1 || id2->reserved[0] != 1)
// {
// return SCE_NP_UTIL_ERROR_INVALID_NP_ID;
// }
if (strncmp(id1->handle.data, id2->handle.data, 16))// || id1->unk1[0] != id2->unk1[0])
{
if (!np::is_same_npid(*id1, *id2))
return not_an_error(SCE_NP_UTIL_ERROR_NOT_MATCH);
}
// if (id1->unk1[1] != id2->unk1[1])
// {
// // If either is zero they match
// if (id1->opt[4] && id2->opt[4])
// {
// return SCE_NP_UTIL_ERROR_NOT_MATCH;
// }
// }
return CELL_OK;
}

View File

@ -6,6 +6,7 @@
#include "sceNp2.h"
#include "Emu/NP/np_handler.h"
#include "Emu/NP/np_contexts.h"
#include "Emu/NP/np_helpers.h"
#include "cellSysutil.h"
LOG_CHANNEL(sceNp2);
@ -444,20 +445,35 @@ error_code sceNpMatching2SignalingGetConnectionStatus(
return SCE_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
auto [res, npid] = nph.local_get_npid(roomId, memberId);
if (res)
return res;
if (np::is_same_npid(nph.get_npid(), *npid))
return SCE_NP_SIGNALING_ERROR_OWN_NP_ID;
auto& sigh = g_fxo->get<named_thread<signaling_handler>>();
const auto si = sigh.get_sig2_infos(roomId, memberId);
auto conn_id = sigh.get_conn_id_from_npid(*npid);
if (!conn_id)
return SCE_NP_SIGNALING_ERROR_CONN_NOT_FOUND;
*connStatus = si.connStatus;
const auto si = sigh.get_sig_infos(*conn_id);
if (!si)
return SCE_NP_SIGNALING_ERROR_CONN_NOT_FOUND;
*connStatus = si->conn_status;
if (peerAddr)
{
(*peerAddr).np_s_addr = si.addr; // infos.addr is already BE
(*peerAddr).np_s_addr = si->addr; // infos.addr is already BE
}
if (peerPort)
{
*peerPort = si.port;
*peerPort = si->port;
}
return CELL_OK;
@ -616,42 +632,58 @@ error_code sceNpMatching2SignalingGetConnectionInfo(
return SCE_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
auto [res, npid] = nph.local_get_npid(roomId, memberId);
if (res)
return res;
if (np::is_same_npid(nph.get_npid(), *npid))
return SCE_NP_SIGNALING_ERROR_OWN_NP_ID;
auto& sigh = g_fxo->get<named_thread<signaling_handler>>();
const auto si = sigh.get_sig2_infos(roomId, memberId);
auto conn_id = sigh.get_conn_id_from_npid(*npid);
if (!conn_id)
return SCE_NP_SIGNALING_ERROR_CONN_NOT_FOUND;
const auto si = sigh.get_sig_infos(*conn_id);
if (!si)
return SCE_NP_SIGNALING_ERROR_CONN_NOT_FOUND;
switch (code)
{
case SCE_NP_SIGNALING_CONN_INFO_RTT:
{
connInfo->rtt = si.rtt;
connInfo->rtt = si->rtt;
sceNp2.warning("Returning a RTT of %d microseconds", connInfo->rtt);
break;
}
case SCE_NP_SIGNALING_CONN_INFO_BANDWIDTH:
{
connInfo->bandwidth = 10'000'000; // 10 MBPS HACK
connInfo->bandwidth = 100'000'000; // 100 MBPS HACK
break;
}
case SCE_NP_SIGNALING_CONN_INFO_PEER_NPID:
{
connInfo->npId = si.npid;
connInfo->npId = si->npid;
break;
}
case SCE_NP_SIGNALING_CONN_INFO_PEER_ADDRESS:
{
connInfo->address.port = std::bit_cast<u16, be_t<u16>>(si.port);
connInfo->address.addr.np_s_addr = si.addr;
connInfo->address.port = std::bit_cast<u16, be_t<u16>>(si->port);
connInfo->address.addr.np_s_addr = si->addr;
break;
}
case SCE_NP_SIGNALING_CONN_INFO_MAPPED_ADDRESS:
{
connInfo->address.port = std::bit_cast<u16, be_t<u16>>(si.mapped_port);
connInfo->address.addr.np_s_addr = si.mapped_addr;
connInfo->address.port = std::bit_cast<u16, be_t<u16>>(si->mapped_port);
connInfo->address.addr.np_s_addr = si->mapped_addr;
break;
}
case SCE_NP_SIGNALING_CONN_INFO_PACKET_LOSS:
{
connInfo->packet_loss = 1; // HACK
connInfo->packet_loss = 0; // HACK
break;
}
default:
@ -709,7 +741,7 @@ error_code sceNpMatching2GetRoomMemberDataExternalList(SceNpMatching2ContextId c
error_code sceNpMatching2AbortRequest(SceNpMatching2ContextId ctxId, SceNpMatching2RequestId reqId)
{
sceNp2.todo("sceNpMatching2AbortRequest(ctxId=%d, reqId=%d)", ctxId, reqId);
sceNp2.warning("sceNpMatching2AbortRequest(ctxId=%d, reqId=%d)", ctxId, reqId);
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
@ -728,6 +760,9 @@ error_code sceNpMatching2AbortRequest(SceNpMatching2ContextId ctxId, SceNpMatchi
return SCE_NP_MATCHING2_ERROR_CONTEXT_NOT_FOUND;
}
if (!nph.abort_request(reqId))
return SCE_NP_MATCHING2_ERROR_REQUEST_NOT_FOUND;
return CELL_OK;
}
@ -1591,7 +1626,7 @@ error_code sceNpMatching2SignalingCancelPeerNetInfo(SceNpMatching2ContextId ctxI
error_code sceNpMatching2SignalingGetLocalNetInfo(vm::ptr<SceNpMatching2SignalingNetInfo> netinfo)
{
sceNp2.todo("sceNpMatching2SignalingGetLocalNetInfo(netinfo=*0x%x)", netinfo);
sceNp2.warning("sceNpMatching2SignalingGetLocalNetInfo(netinfo=*0x%x)", netinfo);
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
@ -1600,12 +1635,17 @@ error_code sceNpMatching2SignalingGetLocalNetInfo(vm::ptr<SceNpMatching2Signalin
return SCE_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
if (!netinfo)
if (!netinfo || netinfo->size != sizeof(SceNpMatching2SignalingNetInfo))
{
// TODO: check netinfo->size
return SCE_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
netinfo->localAddr = nph.get_local_ip_addr();
netinfo->mappedAddr = nph.get_public_ip_addr();
// Pure speculation below
netinfo->natStatus = SCE_NP_SIGNALING_NETINFO_NAT_STATUS_TYPE2;
return CELL_OK;
}
@ -1639,9 +1679,8 @@ error_code sceNpMatching2SignalingGetPeerNetInfoResult(SceNpMatching2ContextId c
return SCE_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
if (!netinfo)
if (!netinfo || netinfo->size != sizeof(SceNpMatching2SignalingNetInfo))
{
// TODO: check netinfo->size
return SCE_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}

View File

@ -1555,9 +1555,9 @@ struct SceNpMatching2CbQueueInfo
u8 reserved[12];
};
union SceNpMatching2SignalingNetInfo // TODO check values
struct SceNpMatching2SignalingNetInfo
{
be_t<u64> size;
be_t<u32> size;
be_t<u32> localAddr;
be_t<u32> mappedAddr;
be_t<u32> natStatus;

View File

@ -39,8 +39,8 @@ struct bandwidth_test
}
}
test_result.upload_bps = 16000.0;
test_result.download_bps = 16000.0;
test_result.upload_bps = 100'000'000.0;
test_result.download_bps = 100'000'000.0;
test_result.result = CELL_OK;
status = SCE_NP_UTIL_BANDWIDTH_TEST_STATUS_FINISHED;
finished = true;

View File

@ -1769,7 +1769,7 @@ error_code sys_net_abort(ppu_thread& ppu, s32 type, u64 arg, s32 flags)
{
ppu.state += cpu_flag::wait;
sys_net.todo("sys_net_abort(type=%d, arg=0x%x, flags=0x%x)", type, arg, flags);
sys_net.warning("sys_net_abort(type=%d, arg=0x%x, flags=0x%x)", type, arg, flags);
enum abort_type : s32
{

View File

@ -50,6 +50,13 @@ lv2_socket_native::~lv2_socket_native()
::close(socket);
#endif
}
if (bound_port)
{
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
nph.upnp_remove_port_mapping(bound_port, type == SYS_NET_SOCK_STREAM ? "TCP" : "UDP");
bound_port = 0;
}
}
s32 lv2_socket_native::create_socket()
@ -143,10 +150,36 @@ s32 lv2_socket_native::bind(const sys_net_sockaddr& addr)
native_addr.sin_addr.s_addr = saddr;
::socklen_t native_addr_len = sizeof(native_addr);
// Note that this is a hack(TODO)
// ATM we don't support binding 3658 udp because we use it for the p2ps main socket
// Only Fat Princess is known to do this to my knowledge
if (psa_in->sin_port == 3658 && type == SYS_NET_SOCK_DGRAM)
{
native_addr.sin_port = std::bit_cast<u16, be_t<u16>>(3659);
}
sys_net.warning("[Native] Trying to bind %s:%d", native_addr.sin_addr, std::bit_cast<be_t<u16>, u16>(native_addr.sin_port));
if (::bind(socket, reinterpret_cast<struct sockaddr*>(&native_addr), native_addr_len) == 0)
{
// Only UPNP port forward binds to 0.0.0.0
if (saddr == 0)
{
if (native_addr.sin_port == 0)
{
sockaddr_in client_addr;
socklen_t client_addr_size = sizeof(client_addr);
ensure(::getsockname(socket, reinterpret_cast<struct sockaddr*>(&client_addr), &client_addr_size) == 0);
bound_port = std::bit_cast<u16, be_t<u16>>(client_addr.sin_port);
}
else
{
bound_port = std::bit_cast<u16, be_t<u16>>(native_addr.sin_port);
}
nph.upnp_add_port_mapping(bound_port, type == SYS_NET_SOCK_STREAM ? "TCP" : "UDP");
}
last_bound_addr = addr;
return CELL_OK;
}
@ -1011,6 +1044,13 @@ void lv2_socket_native::close()
auto& dnshook = g_fxo->get<np::dnshook>();
dnshook.remove_dns_spy(lv2_id);
if (bound_port)
{
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
nph.upnp_remove_port_mapping(bound_port, type == SYS_NET_SOCK_STREAM ? "TCP" : "UDP");
bound_port = 0;
}
}
s32 lv2_socket_native::shutdown(s32 how)

View File

@ -68,4 +68,5 @@ private:
s32 so_reuseaddr = 0;
s32 so_reuseport = 0;
#endif
u16 bound_port = 0;
};

View File

@ -83,16 +83,12 @@ void need_network()
initialize_tcp_timeout_monitor();
}
network_thread::network_thread() noexcept
void network_thread::bind_sce_np_port()
{
if (g_cfg.net.psn_status == np_psn_status::psn_rpcn)
std::lock_guard list_lock(list_p2p_ports_mutex);
list_p2p_ports.emplace(std::piecewise_construct, std::forward_as_tuple(SCE_NP_PORT), std::forward_as_tuple(SCE_NP_PORT));
}
network_thread::~network_thread()
{
}
void network_thread::operator()()
{
std::vector<std::shared_ptr<lv2_socket>> socklist;

View File

@ -17,10 +17,7 @@ struct network_thread
static constexpr auto thread_name = "Network Thread";
network_thread() noexcept;
~network_thread();
void bind_sce_np_port();
void operator()();
};

View File

@ -10,6 +10,7 @@
#include "Emu/NP/signaling_handler.h"
#include "sys_net_helpers.h"
#include "Emu/NP/vport0.h"
#include "Emu/NP/np_handler.h"
LOG_CHANNEL(sys_net);
@ -69,6 +70,9 @@ nt_p2p_port::nt_p2p_port(u16 port)
if (ret_bind == -1)
fmt::throw_exception("Failed to bind DGRAM socket to %d for P2P: %s!", port, get_last_error(true));
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
nph.upnp_add_port_mapping(port, "UDP");
sys_net.notice("P2P port %d was bound!", port);
}
@ -82,6 +86,9 @@ nt_p2p_port::~nt_p2p_port()
::close(p2p_socket);
#endif
}
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
nph.upnp_remove_port_mapping(port, "UDP");
}
void nt_p2p_port::dump_packet(p2ps_encapsulated_tcp* tcph)

View File

@ -3,6 +3,9 @@
#include "Emu/NP/np_allocator.h"
#include "Emu/NP/np_cache.h"
#include "Emu/NP/np_helpers.h"
LOG_CHANNEL(np_cache);
namespace np
{
@ -80,20 +83,32 @@ namespace np
rooms[sce_roomdata->roomId].update(sce_roomdata);
}
void cache_manager::add_member(SceNpMatching2RoomId room_id, const SceNpMatching2RoomMemberDataInternal* sce_roommemberdata)
bool cache_manager::add_member(SceNpMatching2RoomId room_id, const SceNpMatching2RoomMemberDataInternal* sce_roommemberdata)
{
std::lock_guard lock(mutex);
ensure(rooms.contains(room_id), "cache_manager::add_member: Room not cached!");
rooms[room_id].members.insert_or_assign(sce_roommemberdata->memberId, member_cache(sce_roommemberdata));
if (!rooms.contains(room_id))
{
np_cache.error("np_cache::add_member cache miss: room_id(%d)", room_id);
return false;
}
void cache_manager::del_member(SceNpMatching2RoomId room_id, SceNpMatching2RoomMemberId member_id)
rooms[room_id].members.insert_or_assign(sce_roommemberdata->memberId, member_cache(sce_roommemberdata));
return true;
}
bool cache_manager::del_member(SceNpMatching2RoomId room_id, SceNpMatching2RoomMemberId member_id)
{
std::lock_guard lock(mutex);
ensure(rooms.contains(room_id), "cache_manager::del_member: Room not cached!");
if (!rooms.contains(room_id))
{
np_cache.error("np_cache::del_member cache miss: room_id(%d)/member_id(%d)", room_id, member_id);
return false;
}
rooms.erase(member_id);
return true;
}
void cache_manager::update_password(SceNpMatching2RoomId room_id, const std::optional<SceNpMatching2SessionPassword>& password)
@ -121,7 +136,7 @@ namespace np
SceNpMatching2RoomJoinedSlotMask join_mask = 0;
for (const auto& member : room.members)
{
join_mask |= (1 << (member.first - 1));
join_mask |= (1 << ((member.first >> 4) - 1));
}
slots.joinedSlotMask = join_mask;
slots.passwordSlotMask = room.mask_password;
@ -307,14 +322,45 @@ namespace np
return needed_data_size;
}
SceNpId cache_manager::get_npid(u64 room_id, u16 member_id)
std::pair<error_code, std::optional<SceNpId>> cache_manager::get_npid(u64 room_id, u16 member_id)
{
std::lock_guard lock(mutex);
ensure(rooms.contains(room_id), "cache_manager::get_npid: Room not cached!");
ensure(::at32(rooms, room_id).members.contains(member_id), "cache_manager::get_npid: Member not cached!");
if (!rooms.contains(room_id))
{
np_cache.error("np_cache::get_npid cache miss room_id: room_id(%d)/member_id(%d)", room_id, member_id);
return {SCE_NP_MATCHING2_ERROR_INVALID_ROOM_ID, std::nullopt};
}
return ::at32(::at32(rooms, room_id).members, member_id).userInfo.npId;
if (!::at32(rooms, room_id).members.contains(member_id))
{
np_cache.error("np_cache::get_npid cache miss member_id: room_id(%d)/member_id(%d)", room_id, member_id);
return {SCE_NP_MATCHING2_ERROR_INVALID_MEMBER_ID, std::nullopt};
}
return {CELL_OK, ::at32(::at32(rooms, room_id).members, member_id).userInfo.npId};
}
std::optional<u16> cache_manager::get_memberid(u64 room_id, const SceNpId& npid)
{
std::lock_guard lock(mutex);
if (!rooms.contains(room_id))
{
np_cache.error("np_cache::get_memberid cache miss room_id: room_id(%d)/npid(%s)", room_id, static_cast<const char*>(npid.handle.data));
return std::nullopt;
}
const auto& members = ::at32(rooms, room_id).members;
for (const auto& [id, member_cache] : members)
{
if (np::is_same_npid(member_cache.userInfo.npId, npid))
return id;
}
np_cache.error("np_cache::get_memberid cache miss member_id: room_id(%d)/npid(%s)", room_id, static_cast<const char*>(npid.handle.data));
return std::nullopt;
}
} // namespace np

View File

@ -67,15 +67,16 @@ namespace np
cache_manager() = default;
void insert_room(const SceNpMatching2RoomDataInternal* sce_roomdata);
void add_member(SceNpMatching2RoomId room_id, const SceNpMatching2RoomMemberDataInternal* sce_roommemberdata);
void del_member(SceNpMatching2RoomId room_id, SceNpMatching2RoomMemberId member_id);
bool add_member(SceNpMatching2RoomId room_id, const SceNpMatching2RoomMemberDataInternal* sce_roommemberdata);
bool del_member(SceNpMatching2RoomId room_id, SceNpMatching2RoomMemberId member_id);
void update_password(SceNpMatching2RoomId room_id, const std::optional<SceNpMatching2SessionPassword>& password);
std::pair<error_code, std::optional<SceNpMatching2RoomSlotInfo>> get_slots(SceNpMatching2RoomId room_id);
std::pair<error_code, std::vector<SceNpMatching2RoomMemberId>> get_memberids(u64 room_id, s32 sort_method);
std::pair<error_code, std::optional<SceNpMatching2SessionPassword>> get_password(SceNpMatching2RoomId room_id);
error_code get_member_and_attrs(SceNpMatching2RoomId room_id, SceNpMatching2RoomMemberId member_id, const std::vector<SceNpMatching2AttributeId>& binattrs_list, SceNpMatching2RoomMemberDataInternal* ptr_member, u32 addr_data, u32 size_data);
SceNpId get_npid(u64 room_id, u16 member_id);
std::pair<error_code, std::optional<SceNpId>> get_npid(u64 room_id, u16 member_id);
std::optional<u16> get_memberid(u64 room_id, const SceNpId& npid);
private:
shared_mutex mutex;

View File

@ -6,7 +6,6 @@
#include "Emu/Cell/Modules/sceNp2.h"
#include "Emu/Cell/Modules/cellNetCtl.h"
#include "Utilities/StrUtil.h"
#include "Emu/Cell/Modules/cellSysutil.h"
#include "Emu/IdManager.h"
#include "Emu/NP/np_structs_extra.h"
#include "Emu/System.h"
@ -14,6 +13,8 @@
#include "Emu/NP/np_contexts.h"
#include "Emu/NP/np_helpers.h"
#include "Emu/RSX/Overlays/overlay_message.h"
#include "Emu/Cell/lv2/sys_net/network_context.h"
#include "Emu/Cell/lv2/sys_net/sys_net_helpers.h"
#ifdef _WIN32
#include <winsock2.h>
@ -342,21 +343,18 @@ namespace np
{
g_fxo->need<named_thread<signaling_handler>>();
std::lock_guard lock(mutex_rpcn);
rpcn = rpcn::rpcn_client::get_instance();
is_connected = (g_cfg.net.net_active == np_internet_status::enabled);
is_psn_active = (g_cfg.net.psn_status >= np_psn_status::psn_fake);
is_psn_active = (g_cfg.net.psn_status >= np_psn_status::psn_fake) && is_connected;
if (get_net_status() == CELL_NET_CTL_STATE_IPObtained)
{
discover_ip_address();
if (!discover_ether_address())
if (!discover_ether_address() || !discover_ip_address())
{
nph_log.error("Failed to discover ethernet address!");
nph_log.error("Failed to discover ethernet or ip address!");
is_connected = false;
is_psn_active = false;
return;
}
// Convert dns address
@ -383,6 +381,19 @@ namespace np
{
bind_ip = conv.s_addr;
}
if (g_cfg.net.upnp_enabled)
upnp.upnp_enable();
}
if (is_psn_active && g_cfg.net.psn_status == np_psn_status::psn_rpcn)
{
g_fxo->need<network_context>();
auto& nc = g_fxo->get<network_context>();
nc.bind_sce_np_port();
std::lock_guard lock(mutex_rpcn);
rpcn = rpcn::rpcn_client::get_instance();
}
}
@ -449,46 +460,45 @@ namespace np
ar(m_pool, m_size, m_allocs, m_avail);
}
void np_handler::discover_ip_address()
bool np_handler::discover_ip_address()
{
hostname.clear();
hostname.resize(1024);
auto sockfd = socket(AF_INET, SOCK_DGRAM, 0);
const auto use_default_ip_addr = [this](const std::string_view error_msg)
auto close_socket = [&]()
{
nph_log.error("discover_ip_address: %s", error_msg);
nph_log.error("discover_ip_address: Defaulting to 127.0.0.1!");
local_ip_addr = 0x0100007f;
public_ip_addr = local_ip_addr;
#ifdef _WIN32
closesocket(sockfd);
#else
close(sockfd);
#endif
};
if (gethostname(hostname.data(), hostname.size()) == -1)
::sockaddr_in addr;
std::memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = 53;
addr.sin_addr.s_addr = 0x08080808;
if (connect(sockfd, reinterpret_cast<const sockaddr *>(&addr), sizeof(addr)) != 0)
{
use_default_ip_addr("gethostname failed!");
return;
// If connect fails a route to the internet is not available
nph_log.error("connect to discover local ip failed: %d", get_native_error());
close_socket();
return false; // offline
}
// nph_log.notice("discover_ip_address: Hostname was determined to be %s", hostname.c_str());
hostent* host = gethostbyname(hostname.data());
if (!host)
sockaddr_in client_addr;
socklen_t client_addr_size = sizeof(client_addr);
if (getsockname(sockfd, reinterpret_cast<struct sockaddr*>(&client_addr), &client_addr_size) != 0)
{
use_default_ip_addr("gethostbyname failed!");
return;
rpcn_log.error("getsockname to discover local ip failed: %d", get_native_error());
close_socket();
return true; // still assume online
}
if (host->h_addrtype != AF_INET)
{
use_default_ip_addr("Could only find IPv6 addresses for current host!");
return;
}
local_ip_addr = read_from_ptr<u32>(host->h_addr_list[0]);
// Set public address to local discovered address for now, may be updated later from RPCN socket
public_ip_addr = local_ip_addr;
// nph_log.notice("discover_ip_address: IP was determined to be %s", ip_to_string(local_ip_addr));
local_ip_addr = client_addr.sin_addr.s_addr;
nph_log.trace("discover_ip_address: IP was determined to be %s", ip_to_string(local_ip_addr));
close_socket();
return true;
}
bool np_handler::discover_ether_address()
@ -612,6 +622,14 @@ namespace np
return is_psn_active ? SCE_NP_MANAGER_STATUS_ONLINE : SCE_NP_MANAGER_STATUS_OFFLINE;
}
s32 np_handler::get_upnp_status() const
{
if (upnp.is_active())
return SCE_NP_SIGNALING_NETINFO_UPNP_STATUS_VALID;
return SCE_NP_SIGNALING_NETINFO_UPNP_STATUS_INVALID;
}
const SceNpId& np_handler::get_npid() const
{
return npid;
@ -649,7 +667,7 @@ namespace np
std::string s_npid = g_cfg_rpcn.get_npid();
ensure(!s_npid.empty()); // It should have been generated before this
string_to_npid(s_npid, &npid);
string_to_npid(s_npid, npid);
auto& sigh = g_fxo->get<named_thread<signaling_handler>>();
sigh.set_self_sig_info(npid);
}
@ -660,8 +678,8 @@ namespace np
break;
case np_psn_status::psn_fake:
{
string_to_online_name("RPCS3's user", &online_name);
string_to_avatar_url("https://rpcs3.net/cdn/netplay/DefaultAvatar.png", &avatar_url);
string_to_online_name("RPCS3's user", online_name);
string_to_avatar_url("https://rpcs3.net/cdn/netplay/DefaultAvatar.png", avatar_url);
break;
}
case np_psn_status::psn_rpcn:
@ -692,8 +710,8 @@ namespace np
rsx::overlays::queue_message(localized_string_id::RPCN_SUCCESS_LOGGED_ON);
string_to_online_name(rpcn->get_online_name(), &online_name);
string_to_avatar_url(rpcn->get_avatar_url(), &avatar_url);
string_to_online_name(rpcn->get_online_name(), online_name);
string_to_avatar_url(rpcn->get_avatar_url(), avatar_url);
public_ip_addr = rpcn->get_addr_sig();
local_ip_addr = std::bit_cast<u32, be_t<u32>>(rpcn->get_addr_local());
@ -930,7 +948,7 @@ namespace np
return false;
}
u32 np_handler::generate_callback_info(SceNpMatching2ContextId ctx_id, vm::cptr<SceNpMatching2RequestOptParam> optParam)
u32 np_handler::generate_callback_info(SceNpMatching2ContextId ctx_id, vm::cptr<SceNpMatching2RequestOptParam> optParam, SceNpMatching2Event event_type)
{
callback_info ret;
@ -942,6 +960,7 @@ namespace np
ret.ctx_id = ctx_id;
ret.cb_arg = (optParam && optParam->cbFuncArg) ? optParam->cbFuncArg : ctx->default_match2_optparam.cbFuncArg;
ret.cb = (optParam && optParam->cbFunc) ? optParam->cbFunc : ctx->default_match2_optparam.cbFunc;
ret.event_type = event_type;
nph_log.warning("Callback used is 0x%x", ret.cb);
@ -953,16 +972,31 @@ namespace np
return req_id;
}
np_handler::callback_info np_handler::take_pending_request(u32 req_id)
std::optional<np_handler::callback_info> np_handler::take_pending_request(u32 req_id)
{
std::lock_guard lock(mutex_pending_requests);
if (!pending_requests.contains(req_id))
return std::nullopt;
const auto cb_info = std::move(::at32(pending_requests, req_id));
pending_requests.erase(req_id);
return cb_info;
}
bool np_handler::abort_request(u32 req_id)
{
auto cb_info_opt = take_pending_request(req_id);
if (!cb_info_opt)
return false;
cb_info_opt->queue_callback(req_id, 0, SCE_NP_MATCHING2_ERROR_ABORTED, 0);
return true;
}
event_data& np_handler::allocate_req_result(u32 event_key, u32 max_size, u32 initial_size)
{
std::lock_guard lock(mutex_match2_req_results);
@ -974,14 +1008,14 @@ namespace np
{
const u32 req_id = get_req_id(0);
// if (basic_handler)
// {
// sysutil_register_cb([basic_handler = this->basic_handler, req_id, basic_handler_arg = this->basic_handler_arg](ppu_thread& cb_ppu) -> s32
// {
// basic_handler(cb_ppu, SCE_NP_BASIC_EVENT_ADD_PLAYERS_HISTORY_RESULT, 0, req_id, basic_handler_arg);
// return 0;
// });
// }
if (basic_handler.handler_func)
{
sysutil_register_cb([req_id, cb = basic_handler.handler_func, cb_arg = basic_handler.handler_arg](ppu_thread& cb_ppu) -> s32
{
cb(cb_ppu, SCE_NP_BASIC_EVENT_ADD_PLAYERS_HISTORY_RESULT, 0, req_id, cb_arg);
return 0;
});
}
return req_id;
}
@ -1006,11 +1040,16 @@ namespace np
}
SceNpId npid_friend;
string_to_npid(str_friend.value(), &npid_friend);
string_to_npid(str_friend.value(), npid_friend);
return {CELL_OK, npid_friend};
}
std::pair<error_code, std::optional<SceNpId>> np_handler::local_get_npid(u64 room_id, u16 member_id)
{
return np_cache.get_npid(room_id, member_id);
}
std::pair<error_code, std::optional<SceNpMatching2SessionPassword>> np_handler::local_get_room_password(SceNpMatching2RoomId room_id)
{
return np_cache.get_password(room_id);
@ -1030,4 +1069,14 @@ namespace np
{
return np_cache.get_member_and_attrs(room_id, member_id, binattrs_list, ptr_member, addr_data, size_data);
}
void np_handler::upnp_add_port_mapping(u16 internal_port, std::string_view protocol)
{
upnp.add_port_redir(np::ip_to_string(get_local_ip_addr()), internal_port, protocol);
}
void np_handler::upnp_remove_port_mapping(u16 internal_port, std::string_view protocol)
{
upnp.remove_port_redir(internal_port, protocol);
}
} // namespace np

View File

@ -7,6 +7,7 @@
#include "Emu/Memory/vm_ptr.h"
#include "Emu/Cell/Modules/sceNp.h"
#include "Emu/Cell/Modules/sceNp2.h"
#include "Emu/Cell/Modules/cellSysutil.h"
#include "Emu/NP/rpcn_client.h"
#include "Emu/NP/generated/np2_structs_generated.h"
@ -15,6 +16,7 @@
#include "Emu/NP/np_cache.h"
#include "Emu/NP/np_event_data.h"
#include "Emu/NP/np_contexts.h"
#include "Emu/NP/upnp_handler.h"
namespace np
{
@ -83,6 +85,7 @@ namespace np
s32 get_psn_status() const;
s32 get_net_status() const;
s32 get_upnp_status() const;
const SceNpId& get_npid() const;
const SceNpOnlineId& get_online_id() const;
@ -165,6 +168,7 @@ namespace np
void get_score_friend(std::shared_ptr<score_transaction_ctx>& trans_ctx, SceNpScoreBoardId boardId, bool include_self, vm::ptr<SceNpScoreRankData> rankArray, u32 rankArraySize, vm::ptr<SceNpScoreComment> commentArray, u32 commentArraySize, vm::ptr<void> infoArray, u32 infoArraySize, u32 arrayNum, vm::ptr<CellRtcTick> lastSortDate, vm::ptr<SceNpScoreRankNumber> totalRecord, bool async);
// Local functions
std::pair<error_code, std::optional<SceNpId>> local_get_npid(u64 room_id, u16 member_id);
std::pair<error_code, std::optional<SceNpMatching2RoomSlotInfo>> local_get_room_slots(SceNpMatching2RoomId room_id);
std::pair<error_code, std::optional<SceNpMatching2SessionPassword>> local_get_room_password(SceNpMatching2RoomId room_id);
std::pair<error_code, std::vector<SceNpMatching2RoomMemberId>> local_get_room_memberids(SceNpMatching2RoomId room_id, s32 sort_method);
@ -179,10 +183,15 @@ namespace np
void req_ticket(u32 version, const SceNpId* npid, const char* service_id, const u8* cookie, u32 cookie_size, const char* entitlement_id, u32 consumed_count);
const ticket& get_ticket() const;
u32 add_players_to_history(vm::cptr<SceNpId> npids, u32 count);
bool abort_request(u32 req_id);
// For signaling
void req_sign_infos(const std::string& npid, u32 conn_id);
// For UPNP
void upnp_add_port_mapping(u16 internal_port, std::string_view protocol);
void upnp_remove_port_mapping(u16 internal_port, std::string_view protocol);
// For custom menu
struct custom_menu_action
{
@ -205,7 +214,7 @@ namespace np
private:
// Various generic helpers
void discover_ip_address();
bool discover_ip_address();
bool discover_ether_address();
bool error_and_disconnect(const std::string& error_msg);
@ -267,9 +276,23 @@ namespace np
SceNpMatching2ContextId ctx_id;
vm::ptr<SceNpMatching2RequestCallback> cb;
vm::ptr<void> cb_arg;
SceNpMatching2Event event_type;
void queue_callback(u32 req_id, u32 event_key, s32 error_code, u32 data_size) const
{
if (cb)
{
sysutil_register_cb([=, *this](ppu_thread& cb_ppu) -> s32
{
cb(cb_ppu, ctx_id, req_id, event_type, event_key, error_code, data_size, cb_arg);
return 0;
});
}
}
};
u32 generate_callback_info(SceNpMatching2ContextId ctx_id, vm::cptr<SceNpMatching2RequestOptParam> optParam);
callback_info take_pending_request(u32 req_id);
u32 generate_callback_info(SceNpMatching2ContextId ctx_id, vm::cptr<SceNpMatching2RequestOptParam> optParam, SceNpMatching2Event event_type);
std::optional<callback_info> take_pending_request(u32 req_id);
shared_mutex mutex_pending_requests;
std::unordered_map<u32, callback_info> pending_requests;
@ -327,5 +350,8 @@ namespace np
// RPCN
shared_mutex mutex_rpcn;
std::shared_ptr<rpcn::rpcn_client> rpcn;
// UPNP
upnp_handler upnp;
};
} // namespace np

View File

@ -22,25 +22,65 @@ namespace np
return fmt::format("%02X:%02X:%02X:%02X:%02X:%02X", ether[0], ether[1], ether[2], ether[3], ether[4], ether[5]);
}
void string_to_npid(std::string_view str, SceNpId* npid)
void string_to_npid(std::string_view str, SceNpId& npid)
{
memset(npid, 0, sizeof(SceNpId));
strcpy_trunc(npid->handle.data, str);
memset(&npid, 0, sizeof(npid));
strcpy_trunc(npid.handle.data, str);
// npid->reserved[0] = 1;
}
void string_to_online_name(std::string_view str, SceNpOnlineName* online_name)
void string_to_online_name(std::string_view str, SceNpOnlineName& online_name)
{
strcpy_trunc(online_name->data, str);
memset(&online_name, 0, sizeof(online_name));
strcpy_trunc(online_name.data, str);
}
void string_to_avatar_url(std::string_view str, SceNpAvatarUrl* avatar_url)
void string_to_avatar_url(std::string_view str, SceNpAvatarUrl& avatar_url)
{
strcpy_trunc(avatar_url->data, str);
memset(&avatar_url, 0, sizeof(avatar_url));
strcpy_trunc(avatar_url.data, str);
}
void string_to_communication_id(std::string_view str, SceNpCommunicationId* comm_id)
void string_to_communication_id(std::string_view str, SceNpCommunicationId& comm_id)
{
strcpy_trunc(comm_id->data, str);
memset(&comm_id, 0, sizeof(comm_id));
strcpy_trunc(comm_id.data, str);
}
bool is_valid_npid(const SceNpId& npid)
{
if (!std::all_of(npid.handle.data, npid.handle.data + 16, [](char c) { return std::isalnum(c) || c == '-' || c == '_' || c == 0; } )
|| npid.handle.data[16] != 0
|| !std::all_of(npid.handle.dummy, npid.handle.dummy + 3, [](char val) { return val == 0; }) )
{
return false;
}
return true;
}
bool is_same_npid(const SceNpId& npid_1, const SceNpId& npid_2)
{
// Unknown what this constant means
// if (id1->reserved[0] != 1 || id2->reserved[0] != 1)
// {
// return SCE_NP_UTIL_ERROR_INVALID_NP_ID;
// }
if (strncmp(npid_1.handle.data, npid_2.handle.data, 16) == 0) // || id1->unk1[0] != id2->unk1[0])
{
return true;
}
// if (id1->unk1[1] != id2->unk1[1])
// {
// // If either is zero they match
// if (id1->opt[4] && id2->opt[4])
// {
// return SCE_NP_UTIL_ERROR_NOT_MATCH;
// }
// }
return false;
}
}

View File

@ -1,3 +1,5 @@
#pragma once
#include "util/types.hpp"
#include "Emu/Cell/Modules/sceNp.h"
#include "Emu/Cell/Modules/sceNp2.h"
@ -7,8 +9,11 @@ namespace np
std::string ip_to_string(u32 addr);
std::string ether_to_string(std::array<u8, 6>& ether);
void string_to_npid(std::string_view str, SceNpId* npid);
void string_to_online_name(std::string_view str, SceNpOnlineName* online_name);
void string_to_avatar_url(std::string_view str, SceNpAvatarUrl* avatar_url);
void string_to_communication_id(std::string_view str, SceNpCommunicationId* comm_id);
void string_to_npid(std::string_view str, SceNpId& npid);
void string_to_online_name(std::string_view str, SceNpOnlineName& online_name);
void string_to_avatar_url(std::string_view str, SceNpAvatarUrl& avatar_url);
void string_to_communication_id(std::string_view str, SceNpCommunicationId& comm_id);
bool is_valid_npid(const SceNpId& npid);
bool is_same_npid(const SceNpId& npid_1, const SceNpId& npid_2);
}

View File

@ -29,7 +29,8 @@ namespace np
RoomMemberUpdateInfo_to_SceNpMatching2RoomMemberUpdateInfo(edata, update_info, notif_data);
np_memory.shrink_allocation(edata.addr(), edata.size());
np_cache.add_member(room_id, notif_data->roomMemberDataInternal.get_ptr());
if (!np_cache.add_member(room_id, notif_data->roomMemberDataInternal.get_ptr()))
return;
rpcn_log.notice("Received notification that user %s(%d) joined the room(%d)", notif_data->roomMemberDataInternal->userInfo.npId.handle.data, notif_data->roomMemberDataInternal->memberId, room_id);
extra_nps::print_room_member_data_internal(notif_data->roomMemberDataInternal.get_ptr());
@ -60,7 +61,8 @@ namespace np
RoomMemberUpdateInfo_to_SceNpMatching2RoomMemberUpdateInfo(edata, update_info, notif_data);
np_memory.shrink_allocation(edata.addr(), edata.size());
np_cache.del_member(room_id, notif_data->roomMemberDataInternal->memberId);
if (!np_cache.del_member(room_id, notif_data->roomMemberDataInternal->memberId))
return;
rpcn_log.notice("Received notification that user %s(%d) left the room(%d)", notif_data->roomMemberDataInternal->userInfo.npId.handle.data, notif_data->roomMemberDataInternal->memberId, room_id);
extra_nps::print_room_member_data_internal(notif_data->roomMemberDataInternal.get_ptr());
@ -154,7 +156,8 @@ namespace np
RoomMemberDataInternalUpdateInfo_to_SceNpMatching2RoomMemberDataInternalUpdateInfo(edata, update_info, notif_data);
np_memory.shrink_allocation(edata.addr(), edata.size());
np_cache.add_member(room_id, notif_data->newRoomMemberDataInternal.get_ptr());
if (!np_cache.add_member(room_id, notif_data->newRoomMemberDataInternal.get_ptr()))
return;
rpcn_log.notice("Received notification that user's %s(%d) room (%d) data was updated", notif_data->newRoomMemberDataInternal->userInfo.npId.handle.data, notif_data->newRoomMemberDataInternal->memberId, room_id);
extra_nps::print_room_member_data_internal(notif_data->newRoomMemberDataInternal.get_ptr());
@ -200,22 +203,27 @@ namespace np
void np_handler::notif_p2p_connect(std::vector<u8>& data)
{
if (data.size() != 16)
vec_stream noti(data);
const u64 room_id = noti.get<u64>();
const u16 member_id = noti.get<u16>();
const u16 port_p2p = noti.get<u16>();
const u32 addr_p2p = noti.get<u32>();
if (noti.is_error())
{
rpcn_log.error("Notification data for SignalP2PConnect != 14");
rpcn_log.error("Received faulty SignalP2PConnect notification");
return;
}
const u64 room_id = read_from_ptr<le_t<u64>>(data);
const u16 member_id = read_from_ptr<le_t<u16>>(data, 8);
const u16 port_p2p = read_from_ptr<be_t<u16>>(data, 10);
const u32 addr_p2p = read_from_ptr<le_t<u32>>(data, 12);
auto [res, npid] = np_cache.get_npid(room_id, member_id);
if (!npid)
return;
rpcn_log.notice("Received notification to connect to member(%d) of room(%d): %s:%d", member_id, room_id, ip_to_string(addr_p2p), port_p2p);
rpcn_log.notice("Received notification to connect to member(%d=%s) of room(%d): %s:%d", member_id, reinterpret_cast<const char*>((*npid).handle.data), room_id, ip_to_string(addr_p2p), port_p2p);
// Attempt Signaling
auto& sigh = g_fxo->get<named_thread<signaling_handler>>();
sigh.set_sig2_infos(room_id, member_id, SCE_NP_SIGNALING_CONN_STATUS_PENDING, addr_p2p, port_p2p, np_cache.get_npid(room_id, member_id));
sigh.start_sig2(room_id, member_id);
const u32 conn_id = sigh.init_sig2(*npid, room_id, member_id);
sigh.start_sig(conn_id, addr_p2p, port_p2p);
}
} // namespace np

View File

@ -35,7 +35,7 @@ namespace np
u32 np_handler::get_server_status(SceNpMatching2ContextId ctx_id, vm::cptr<SceNpMatching2RequestOptParam> optParam, u16 server_id)
{
// TODO: actually implement interaction with server for this?
u32 req_id = generate_callback_info(ctx_id, optParam);
u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_GetServerInfo);
u32 event_key = get_event_key();
auto& edata = allocate_req_result(event_key, SCE_NP_MATCHING2_EVENT_DATA_MAX_SIZE_GetServerInfo, sizeof(SceNpMatching2GetServerInfoResponse));
@ -44,61 +44,38 @@ namespace np
serv_info->server.status = SCE_NP_MATCHING2_SERVER_STATUS_AVAILABLE;
np_memory.shrink_allocation(edata.addr(), edata.size());
const auto cb_info = take_pending_request(req_id);
if (cb_info.cb)
{
sysutil_register_cb([=, size = edata.size()](ppu_thread& cb_ppu) -> s32
{
cb_info.cb(cb_ppu, cb_info.ctx_id, req_id, SCE_NP_MATCHING2_REQUEST_EVENT_GetServerInfo, event_key, 0, size, cb_info.cb_arg);
return 0;
});
}
const auto cb_info_opt = take_pending_request(req_id);
ensure(cb_info_opt);
cb_info_opt->queue_callback(req_id, event_key, 0, edata.size());
return req_id;
}
u32 np_handler::create_server_context(SceNpMatching2ContextId ctx_id, vm::cptr<SceNpMatching2RequestOptParam> optParam, u16 /*server_id*/)
{
u32 req_id = generate_callback_info(ctx_id, optParam);
u32 event_key = get_event_key();
u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_CreateServerContext);
const auto cb_info = take_pending_request(req_id);
if (cb_info.cb)
{
sysutil_register_cb([=](ppu_thread& cb_ppu) -> s32
{
cb_info.cb(cb_ppu, cb_info.ctx_id, req_id, SCE_NP_MATCHING2_REQUEST_EVENT_CreateServerContext, event_key, 0, 0, cb_info.cb_arg);
return 0;
});
}
const auto cb_info_opt = take_pending_request(req_id);
ensure(cb_info_opt);
cb_info_opt->queue_callback(req_id, 0, 0, 0);
return req_id;
}
u32 np_handler::delete_server_context(SceNpMatching2ContextId ctx_id, vm::cptr<SceNpMatching2RequestOptParam> optParam, u16 /*server_id*/)
{
u32 req_id = generate_callback_info(ctx_id, optParam);
u32 event_key = get_event_key();
u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_DeleteServerContext);
const auto cb_info = take_pending_request(req_id);
if (cb_info.cb)
{
sysutil_register_cb([=](ppu_thread& cb_ppu) -> s32
{
cb_info.cb(cb_ppu, cb_info.ctx_id, req_id, SCE_NP_MATCHING2_REQUEST_EVENT_DeleteServerContext, event_key, 0, 0, cb_info.cb_arg);
return 0;
});
}
const auto cb_info_opt = take_pending_request(req_id);
ensure(cb_info_opt);
cb_info_opt->queue_callback(req_id, 0, 0, 0);
return req_id;
}
u32 np_handler::get_world_list(SceNpMatching2ContextId ctx_id, vm::cptr<SceNpMatching2RequestOptParam> optParam, u16 server_id)
{
u32 req_id = generate_callback_info(ctx_id, optParam);
u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_GetWorldInfoList);
if (!rpcn->get_world_list(req_id, get_match2_context(ctx_id)->communicationId, server_id))
{
@ -111,7 +88,10 @@ namespace np
bool np_handler::reply_get_world_list(u32 req_id, std::vector<u8>& reply_data)
{
const auto cb_info = take_pending_request(req_id);
auto cb_info_opt = take_pending_request(req_id);
if (!cb_info_opt)
return true;
vec_stream reply(reply_data, 1);
@ -139,31 +119,18 @@ namespace np
for (usz i = 0; i < world_list.size(); i++)
{
worlds[i].worldId = world_list[i];
worlds[i].numOfLobby = 1; // TODO
worlds[i].maxNumOfTotalLobbyMember = 10000;
worlds[i].curNumOfTotalLobbyMember = 1;
worlds[i].curNumOfRoom = 1;
worlds[i].curNumOfTotalRoomMember = 1;
}
}
np_memory.shrink_allocation(edata.addr(), edata.size());
if (cb_info.cb)
{
sysutil_register_cb([=, size = edata.size()](ppu_thread& cb_ppu) -> s32
{
cb_info.cb(cb_ppu, cb_info.ctx_id, req_id, SCE_NP_MATCHING2_REQUEST_EVENT_GetWorldInfoList, event_key, 0, size, cb_info.cb_arg);
return 0;
});
}
cb_info_opt->queue_callback(req_id, event_key, 0, edata.size());
return true;
}
u32 np_handler::create_join_room(SceNpMatching2ContextId ctx_id, vm::cptr<SceNpMatching2RequestOptParam> optParam, const SceNpMatching2CreateJoinRoomRequest* req)
{
u32 req_id = generate_callback_info(ctx_id, optParam);
u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_CreateJoinRoom);
extra_nps::print_createjoinroom(req);
@ -181,7 +148,10 @@ namespace np
bool np_handler::reply_create_join_room(u32 req_id, std::vector<u8>& reply_data)
{
const auto cb_info = take_pending_request(req_id);
auto cb_info_opt = take_pending_request(req_id);
if (!cb_info_opt)
return true;
vec_stream reply(reply_data, 1);
auto* resp = reply.get_flatbuffer<RoomDataInternal>();
@ -200,29 +170,16 @@ namespace np
np_cache.insert_room(room_info);
np_cache.update_password(room_resp->roomDataInternal->roomId, cached_cj_password);
// Establish Matching2 self signaling info
auto& sigh = g_fxo->get<named_thread<signaling_handler>>();
sigh.set_self_sig2_info(room_info->roomId, 1);
sigh.set_sig2_infos(room_info->roomId, 1, SCE_NP_SIGNALING_CONN_STATUS_ACTIVE, rpcn->get_addr_sig(), rpcn->get_port_sig(), get_npid(), true);
// TODO? Should this send a message to Signaling CB? Is this even necessary?
extra_nps::print_create_room_resp(room_resp);
if (cb_info.cb)
{
sysutil_register_cb([=, size = edata.size()](ppu_thread& cb_ppu) -> s32
{
cb_info.cb(cb_ppu, cb_info.ctx_id, req_id, SCE_NP_MATCHING2_REQUEST_EVENT_CreateJoinRoom, event_key, 0, size, cb_info.cb_arg);
return 0;
});
}
cb_info_opt->queue_callback(req_id, event_key, 0, edata.size());
return true;
}
u32 np_handler::join_room(SceNpMatching2ContextId ctx_id, vm::cptr<SceNpMatching2RequestOptParam> optParam, const SceNpMatching2JoinRoomRequest* req)
{
u32 req_id = generate_callback_info(ctx_id, optParam);
u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_JoinRoom);
extra_nps::print_joinroom(req);
@ -237,6 +194,11 @@ namespace np
bool np_handler::reply_join_room(u32 req_id, std::vector<u8>& reply_data)
{
auto cb_info_opt = take_pending_request(req_id);
if (!cb_info_opt)
return true;
s32 error_code = 0;
if (rpcn::is_error(static_cast<rpcn::ErrorType>(reply_data[0])))
@ -250,19 +212,9 @@ namespace np
}
}
const auto cb_info = take_pending_request(req_id);
if (error_code != 0)
{
if (cb_info.cb)
{
sysutil_register_cb([=](ppu_thread& cb_ppu) -> s32
{
cb_info.cb(cb_ppu, cb_info.ctx_id, req_id, SCE_NP_MATCHING2_REQUEST_EVENT_JoinRoom, 0, error_code, 0, cb_info.cb_arg);
return 0;
});
}
cb_info_opt->queue_callback(req_id, 0, error_code, 0);
return true;
}
@ -278,34 +230,21 @@ namespace np
auto& edata = allocate_req_result(event_key, SCE_NP_MATCHING2_EVENT_DATA_MAX_SIZE_JoinRoom, sizeof(SceNpMatching2JoinRoomResponse));
auto* room_resp = reinterpret_cast<SceNpMatching2JoinRoomResponse*>(edata.data());
auto* room_info = edata.allocate<SceNpMatching2RoomDataInternal>(sizeof(SceNpMatching2RoomDataInternal), room_resp->roomDataInternal);
u16 member_id = RoomDataInternal_to_SceNpMatching2RoomDataInternal(edata, resp, room_info, npid);
RoomDataInternal_to_SceNpMatching2RoomDataInternal(edata, resp, room_info, npid);
np_memory.shrink_allocation(edata.addr(), edata.size());
np_cache.insert_room(room_info);
extra_nps::print_room_data_internal(room_info);
// Establish Matching2 self signaling info
auto& sigh = g_fxo->get<named_thread<signaling_handler>>();
sigh.set_self_sig2_info(room_info->roomId, member_id);
sigh.set_sig2_infos(room_info->roomId, member_id, SCE_NP_SIGNALING_CONN_STATUS_ACTIVE, rpcn->get_addr_sig(), rpcn->get_port_sig(), get_npid(), true);
// TODO? Should this send a message to Signaling CB? Is this even necessary?
if (cb_info.cb)
{
sysutil_register_cb([=, size = edata.size()](ppu_thread& cb_ppu) -> s32
{
cb_info.cb(cb_ppu, cb_info.ctx_id, req_id, SCE_NP_MATCHING2_REQUEST_EVENT_JoinRoom, event_key, 0, size, cb_info.cb_arg);
return 0;
});
}
cb_info_opt->queue_callback(req_id, event_key, 0, edata.size());
return true;
}
u32 np_handler::leave_room(SceNpMatching2ContextId ctx_id, vm::cptr<SceNpMatching2RequestOptParam> optParam, const SceNpMatching2LeaveRoomRequest* req)
{
u32 req_id = generate_callback_info(ctx_id, optParam);
u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_LeaveRoom);
if (!rpcn->leave_room(req_id, get_match2_context(ctx_id)->communicationId, req))
{
@ -318,34 +257,27 @@ namespace np
bool np_handler::reply_leave_room(u32 req_id, std::vector<u8>& reply_data)
{
const auto cb_info = take_pending_request(req_id);
auto cb_info_opt = take_pending_request(req_id);
if (!cb_info_opt)
return true;
vec_stream reply(reply_data, 1);
u64 room_id = reply.get<u64>();
if (reply.is_error())
return error_and_disconnect("Malformed reply to LeaveRoom command");
u32 event_key = get_event_key(); // Unsure if necessary if there is no data
// Disconnect all users from that room
auto& sigh = g_fxo->get<named_thread<signaling_handler>>();
sigh.disconnect_sig2_users(room_id);
if (cb_info.cb)
{
sysutil_register_cb([=](ppu_thread& cb_ppu) -> s32
{
cb_info.cb(cb_ppu, cb_info.ctx_id, req_id, SCE_NP_MATCHING2_REQUEST_EVENT_LeaveRoom, event_key, 0, 0, cb_info.cb_arg);
return 0;
});
}
cb_info_opt->queue_callback(req_id, 0, 0, 0);
return true;
}
u32 np_handler::search_room(SceNpMatching2ContextId ctx_id, vm::cptr<SceNpMatching2RequestOptParam> optParam, const SceNpMatching2SearchRoomRequest* req)
{
u32 req_id = generate_callback_info(ctx_id, optParam);
u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SearchRoom);
extra_nps::print_search_room(req);
@ -360,7 +292,10 @@ namespace np
bool np_handler::reply_search_room(u32 req_id, std::vector<u8>& reply_data)
{
const auto cb_info = take_pending_request(req_id);
auto cb_info_opt = take_pending_request(req_id);
if (!cb_info_opt)
return true;
vec_stream reply(reply_data, 1);
auto* resp = reply.get_flatbuffer<SearchRoomResponse>();
@ -376,22 +311,14 @@ namespace np
np_memory.shrink_allocation(edata.addr(), edata.size());
extra_nps::print_search_room_resp(search_resp);
if (cb_info.cb)
{
sysutil_register_cb([=, size = edata.size()](ppu_thread& cb_ppu) -> s32
{
cb_info.cb(cb_ppu, cb_info.ctx_id, req_id, SCE_NP_MATCHING2_REQUEST_EVENT_SearchRoom, event_key, 0, size, cb_info.cb_arg);
return 0;
});
}
cb_info_opt->queue_callback(req_id, event_key, 0, edata.size());
return true;
}
u32 np_handler::get_roomdata_external_list(SceNpMatching2ContextId ctx_id, vm::cptr<SceNpMatching2RequestOptParam> optParam, const SceNpMatching2GetRoomDataExternalListRequest* req)
{
u32 req_id = generate_callback_info(ctx_id, optParam);
u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_GetRoomDataExternalList);
extra_nps::print_get_roomdata_external_list_req(req);
@ -406,7 +333,10 @@ namespace np
bool np_handler::reply_get_roomdata_external_list(u32 req_id, std::vector<u8>& reply_data)
{
const auto cb_info = take_pending_request(req_id);
auto cb_info_opt = take_pending_request(req_id);
if (!cb_info_opt)
return true;
vec_stream reply(reply_data, 1);
auto* resp = reply.get_flatbuffer<GetRoomDataExternalListResponse>();
@ -423,21 +353,14 @@ namespace np
extra_nps::print_get_roomdata_external_list_resp(sce_get_room_ext_resp);
if (cb_info.cb)
{
sysutil_register_cb([=, size = edata.size()](ppu_thread& cb_ppu) -> s32
{
cb_info.cb(cb_ppu, cb_info.ctx_id, req_id, SCE_NP_MATCHING2_REQUEST_EVENT_GetRoomDataExternalList, event_key, 0, size, cb_info.cb_arg);
return 0;
});
}
cb_info_opt->queue_callback(req_id, event_key, 0, edata.size());
return true;
}
u32 np_handler::set_roomdata_external(SceNpMatching2ContextId ctx_id, vm::cptr<SceNpMatching2RequestOptParam> optParam, const SceNpMatching2SetRoomDataExternalRequest* req)
{
u32 req_id = generate_callback_info(ctx_id, optParam);
u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SetRoomDataExternal);
extra_nps::print_set_roomdata_ext_req(req);
@ -452,25 +375,19 @@ namespace np
bool np_handler::reply_set_roomdata_external(u32 req_id, std::vector<u8>& /*reply_data*/)
{
const auto cb_info = take_pending_request(req_id);
auto cb_info_opt = take_pending_request(req_id);
u32 event_key = get_event_key(); // Unsure if necessary if there is no data
if (!cb_info_opt)
return true;
if (cb_info.cb)
{
sysutil_register_cb([=](ppu_thread& cb_ppu) -> s32
{
cb_info.cb(cb_ppu, cb_info.ctx_id, req_id, SCE_NP_MATCHING2_REQUEST_EVENT_SetRoomDataExternal, event_key, 0, 0, cb_info.cb_arg);
return 0;
});
}
cb_info_opt->queue_callback(req_id, 0, 0, 0);
return true;
}
u32 np_handler::get_roomdata_internal(SceNpMatching2ContextId ctx_id, vm::cptr<SceNpMatching2RequestOptParam> optParam, const SceNpMatching2GetRoomDataInternalRequest* req)
{
u32 req_id = generate_callback_info(ctx_id, optParam);
u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_GetRoomDataInternal);
if (!rpcn->get_roomdata_internal(req_id, get_match2_context(ctx_id)->communicationId, req))
{
@ -483,7 +400,10 @@ namespace np
bool np_handler::reply_get_roomdata_internal(u32 req_id, std::vector<u8>& reply_data)
{
const auto cb_info = take_pending_request(req_id);
auto cb_info_opt = take_pending_request(req_id);
if (!cb_info_opt)
return true;
vec_stream reply(reply_data, 1);
@ -504,21 +424,14 @@ namespace np
extra_nps::print_room_data_internal(room_info);
if (cb_info.cb)
{
sysutil_register_cb([=, size = edata.size()](ppu_thread& cb_ppu) -> s32
{
cb_info.cb(cb_ppu, cb_info.ctx_id, req_id, SCE_NP_MATCHING2_REQUEST_EVENT_GetRoomDataInternal, event_key, 0, size, cb_info.cb_arg);
return 0;
});
}
cb_info_opt->queue_callback(req_id, event_key, 0, edata.size());
return true;
}
u32 np_handler::set_roomdata_internal(SceNpMatching2ContextId ctx_id, vm::cptr<SceNpMatching2RequestOptParam> optParam, const SceNpMatching2SetRoomDataInternalRequest* req)
{
u32 req_id = generate_callback_info(ctx_id, optParam);
u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SetRoomDataInternal);
// TODO: extra_nps::print_set_roomdata_req(req);
@ -535,25 +448,19 @@ namespace np
bool np_handler::reply_set_roomdata_internal(u32 req_id, std::vector<u8>& /*reply_data*/)
{
const auto cb_info = take_pending_request(req_id);
auto cb_info_opt = take_pending_request(req_id);
u32 event_key = get_event_key(); // Unsure if necessary if there is no data
if (!cb_info_opt)
return true;
if (cb_info.cb)
{
sysutil_register_cb([=](ppu_thread& cb_ppu) -> s32
{
cb_info.cb(cb_ppu, cb_info.ctx_id, req_id, SCE_NP_MATCHING2_REQUEST_EVENT_SetRoomDataInternal, event_key, 0, 0, cb_info.cb_arg);
return 0;
});
}
cb_info_opt->queue_callback(req_id, 0, 0, 0);
return true;
}
u32 np_handler::set_roommemberdata_internal(SceNpMatching2ContextId ctx_id, vm::cptr<SceNpMatching2RequestOptParam> optParam, const SceNpMatching2SetRoomMemberDataInternalRequest* req)
{
u32 req_id = generate_callback_info(ctx_id, optParam);
u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SetRoomMemberDataInternal);
extra_nps::print_set_roommemberdata_int_req(req);
@ -568,25 +475,19 @@ namespace np
bool np_handler::reply_set_roommemberdata_internal(u32 req_id, std::vector<u8>& /*reply_data*/)
{
const auto cb_info = take_pending_request(req_id);
auto cb_info_opt = take_pending_request(req_id);
u32 event_key = get_event_key(); // Unsure if necessary if there is no data
if (!cb_info_opt)
return true;
if (cb_info.cb)
{
sysutil_register_cb([=](ppu_thread& cb_ppu) -> s32
{
cb_info.cb(cb_ppu, cb_info.ctx_id, req_id, SCE_NP_MATCHING2_REQUEST_EVENT_SetRoomMemberDataInternal, event_key, 0, 0, cb_info.cb_arg);
return 0;
});
}
cb_info_opt->queue_callback(req_id, 0, 0, 0);
return true;
}
u32 np_handler::get_ping_info(SceNpMatching2ContextId ctx_id, vm::cptr<SceNpMatching2RequestOptParam> optParam, const SceNpMatching2SignalingGetPingInfoRequest* req)
{
u32 req_id = generate_callback_info(ctx_id, optParam);
u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SignalingGetPingInfo);
if (!rpcn->ping_room_owner(req_id, get_match2_context(ctx_id)->communicationId, req->roomId))
{
@ -599,7 +500,10 @@ namespace np
bool np_handler::reply_get_ping_info(u32 req_id, std::vector<u8>& reply_data)
{
const auto cb_info = take_pending_request(req_id);
auto cb_info_opt = take_pending_request(req_id);
if (!cb_info_opt)
return true;
vec_stream reply(reply_data, 1);
@ -614,22 +518,14 @@ namespace np
auto* final_ping_resp = reinterpret_cast<SceNpMatching2SignalingGetPingInfoResponse*>(edata.data());
GetPingInfoResponse_to_SceNpMatching2SignalingGetPingInfoResponse(resp, final_ping_resp);
np_memory.shrink_allocation(edata.addr(), edata.size());
if (cb_info.cb)
{
sysutil_register_cb([=, size = edata.size()](ppu_thread& cb_ppu) -> s32
{
cb_info.cb(cb_ppu, cb_info.ctx_id, req_id, SCE_NP_MATCHING2_REQUEST_EVENT_SignalingGetPingInfo, event_key, 0, size, cb_info.cb_arg);
return 0;
});
}
cb_info_opt->queue_callback(req_id, event_key, 0, edata.size());
return true;
}
u32 np_handler::send_room_message(SceNpMatching2ContextId ctx_id, vm::cptr<SceNpMatching2RequestOptParam> optParam, const SceNpMatching2SendRoomMessageRequest* req)
{
u32 req_id = generate_callback_info(ctx_id, optParam);
u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SendRoomMessage);
if (!rpcn->send_room_message(req_id, get_match2_context(ctx_id)->communicationId, req))
{
@ -642,16 +538,12 @@ namespace np
bool np_handler::reply_send_room_message(u32 req_id, std::vector<u8>& /*reply_data*/)
{
const auto cb_info = take_pending_request(req_id);
auto cb_info_opt = take_pending_request(req_id);
if (cb_info.cb)
{
sysutil_register_cb([=](ppu_thread& cb_ppu) -> s32
{
cb_info.cb(cb_ppu, cb_info.ctx_id, req_id, SCE_NP_MATCHING2_REQUEST_EVENT_SendRoomMessage, 0, 0, 0, cb_info.cb_arg);
return 0;
});
}
if (!cb_info_opt)
return true;
cb_info_opt->queue_callback(req_id, 0, 0, 0);
return true;
}
@ -1112,8 +1004,8 @@ namespace np
cur_rank = &rankPlayerArray[i].rankData;
}
string_to_npid(fb_rankdata->npId()->string_view(), &cur_rank->npId);
string_to_online_name(fb_rankdata->onlineName()->string_view(), &cur_rank->onlineName);
string_to_npid(fb_rankdata->npId()->string_view(), cur_rank->npId);
string_to_online_name(fb_rankdata->onlineName()->string_view(), cur_rank->onlineName);
cur_rank->pcId = fb_rankdata->pcId();
cur_rank->serialRank = fb_rankdata->rank();

View File

@ -18,6 +18,7 @@ namespace extra_nps
void print_sigoptparam(const SceNpMatching2SignalingOptParam* opt)
{
sceNp2.warning("SceNpMatching2SignalingOptParam:");
sceNp2.warning("type: %d", opt->type);
sceNp2.warning("flag: %d", opt->flag);
sceNp2.warning("hubMemberId: %d", opt->hubMemberId);
@ -227,7 +228,10 @@ namespace extra_nps
sceNp2.warning("curMemberNum: %d", room->curMemberNum);
sceNp2.warning("SceNpMatching2RoomPasswordSlotMask: 0x%x", room->passwordSlotMask);
sceNp2.warning("owner: *0x%x", room->owner);
if (room->owner)
print_userinfo2(room->owner.get_ptr());
sceNp2.warning("roomGroup: *0x%x", room->roomGroup);
// TODO: print roomGroup
sceNp2.warning("roomGroupNum: %d", room->roomGroupNum);

View File

@ -71,7 +71,7 @@ namespace rpcn
return get_localized_string(rpcn_state_to_localized_string_id(state));
}
constexpr u32 RPCN_PROTOCOL_VERSION = 18;
constexpr u32 RPCN_PROTOCOL_VERSION = 19;
constexpr usz RPCN_HEADER_SIZE = 15;
constexpr usz COMMUNICATION_ID_SIZE = 9;

View File

@ -1,6 +1,6 @@
#include "stdafx.h"
#include "rpcn_config.h"
#include "Emu/System.h"
#include "Utilities/File.h"
cfg_rpcn g_cfg_rpcn;

View File

@ -5,6 +5,7 @@
#include "Emu/Cell/Modules/cellSysutil.h"
#include "np_handler.h"
#include "Emu/NP/vport0.h"
#include "Emu/NP/np_helpers.h"
#ifdef _WIN32
#include <winsock2.h>
@ -102,17 +103,15 @@ void signaling_handler::signal_ext_sig_callback(u32 conn_id, int event) const
void signaling_handler::signal_sig2_callback(u64 room_id, u16 member_id, SceNpMatching2Event event) const
{
// Signal the callback
if (sig2_cb)
if (room_id && sig2_cb)
{
sysutil_register_cb([sig2_cb = this->sig2_cb, sig2_cb_ctx = this->sig2_cb_ctx, room_id, member_id, event, sig2_cb_arg = this->sig2_cb_arg](ppu_thread& cb_ppu) -> s32
{
sig2_cb(cb_ppu, sig2_cb_ctx, room_id, member_id, event, 0, sig2_cb_arg);
return 0;
});
}
sign_log.notice("Called sig2 CB: 0x%x (room_id: %d, member_id: %d)", event, room_id, member_id);
}
}
///////////////////////////////////
@ -164,9 +163,21 @@ bool signaling_handler::validate_signaling_packet(const signaling_packet* sp)
return false;
}
if (sp->version != 1u && sp->version != 2u)
if (sp->version != SIGNALING_VERSION)
{
sign_log.error("Invalid version in signaling packet");
sign_log.error("Invalid version in signaling packet: %d, expected: %d", sp->version, SIGNALING_VERSION);
if (sp->version > SIGNALING_VERSION)
sign_log.error("You are most likely using an outdated version of RPCS3");
else
sign_log.error("The other user is most likely using an outdated version of RPCS3");
return false;
}
if (!np::is_valid_npid(sp->npid))
{
sign_log.error("Invalid npid in signaling packet");
return false;
}
@ -187,57 +198,36 @@ void signaling_handler::process_incoming_messages()
auto op_addr = msg.src_addr;
auto op_port = msg.src_port;
auto* sp = reinterpret_cast<const signaling_packet*>(msg.data.data());
const auto* sp = reinterpret_cast<const signaling_packet*>(msg.data.data());
if (!validate_signaling_packet(sp))
continue;
if (sign_log.trace)
{
in_addr addr;
in_addr addr{};
addr.s_addr = op_addr;
if (sp->version == 1u)
{
char npid_buf[17]{};
memcpy(npid_buf, sp->V1.npid.handle.data, 16);
std::string npid(npid_buf);
char ip_str[16];
inet_ntop(AF_INET, &addr, ip_str, sizeof(ip_str));
std::string_view npid(sp->npid.handle.data);
sign_log.trace("sig1 %s from %s:%d(%s)", sp->command, ip_str, op_port, npid);
}
else
{
char inet_addr[16];
const char* inet_addr_string = inet_ntop(AF_INET, &addr, inet_addr, sizeof(inet_addr));
sign_log.trace("sig2 %s from %s:%d(%d:%d)", sp->command, inet_addr_string, op_port, sp->V2.room_id, sp->V2.member_id);
}
sign_log.trace("SP %s from %s:%d(npid: %s)", sp->command, ip_str, op_port, npid);
}
bool reply = false, schedule_repeat = false;
auto& sent_packet = sp->version == 1u ? sig1_packet : sig2_packet;
auto& sent_packet = sig_packet;
// Get signaling info for user to know if we should even bother looking further
auto si = get_signaling_ptr(sp);
if (!si && sp->version == 1u && sp->command == signal_connect)
if (!si && sp->command == signal_connect)
{
// Connection can be remotely established and not mutual
const u32 conn_id = create_sig_infos(&sp->V1.npid);
si = ::at32(sig1_peers, conn_id);
// Activate the connection without triggering the main CB
si->connStatus = SCE_NP_SIGNALING_CONN_STATUS_ACTIVE;
si->addr = op_addr;
si->port = op_port;
si->ext_status = ext_sign_peer;
// Notify extended callback that peer activated
signal_ext_sig_callback(conn_id, SCE_NP_SIGNALING_EVENT_EXT_PEER_ACTIVATED);
const u32 conn_id = get_always_conn_id(sp->npid);
si = ::at32(sig_peers, conn_id);
}
if (!si || (si->connStatus == SCE_NP_SIGNALING_CONN_STATUS_INACTIVE && sp->command != signal_finished))
if (!si && sp->command != signal_finished)
{
// User is unknown to us or the connection is inactive
// Ignore packet unless it's a finished packet in case the finished_ack wasn't received by opponent
@ -300,7 +290,7 @@ void signaling_handler::process_incoming_messages()
sent_packet.command = signal_connect_ack;
sent_packet.timestamp_sender = sp->timestamp_sender;
sent_packet.timestamp_receiver = now.time_since_epoch().count();
// connection is established
update_si_addr(si, op_addr, op_port);
break;
case signal_connect_ack:
update_rtt(sp->timestamp_sender);
@ -310,7 +300,6 @@ void signaling_handler::process_incoming_messages()
sent_packet.command = signal_confirm;
sent_packet.timestamp_receiver = now.time_since_epoch().count();
retire_packet(si, signal_connect);
// connection is active
update_si_addr(si, op_addr, op_port);
update_si_mapped_addr(si, sp->sent_addr, sp->sent_port);
update_si_status(si, SCE_NP_SIGNALING_CONN_STATUS_ACTIVE);
@ -321,21 +310,20 @@ void signaling_handler::process_incoming_messages()
schedule_repeat = false;
setup_ping();
retire_packet(si, signal_connect_ack);
// connection is active
update_si_addr(si, op_addr, op_port);
update_si_mapped_addr(si, sp->sent_addr, sp->sent_port);
update_si_status(si, SCE_NP_SIGNALING_CONN_STATUS_ACTIVE, true);
update_ext_si_status(si, true);
break;
case signal_finished:
reply = true;
schedule_repeat = false;
sent_packet.command = signal_finished_ack;
// terminate connection
update_si_status(si, SCE_NP_SIGNALING_CONN_STATUS_INACTIVE);
update_ext_si_status(si, false);
break;
case signal_finished_ack:
reply = false;
schedule_repeat = false;
update_si_status(si, SCE_NP_SIGNALING_CONN_STATUS_INACTIVE);
retire_packet(si, signal_finished);
break;
default: sign_log.error("Invalid signaling command received"); continue;
@ -369,37 +357,39 @@ void signaling_handler::operator()()
for (auto it = qpackets.begin(); it != qpackets.end();)
{
if (it->first > now)
auto& [timestamp, sig] = *it;
if (timestamp > now)
break;
if (it->second.sig_info->time_last_msg_recvd < now - 60s)
if (sig.sig_info->time_last_msg_recvd < now - 60s)
{
// We had no connection to opponent for 60 seconds, consider the connection dead
sign_log.trace("Timeout disconnection");
update_si_status(it->second.sig_info, SCE_NP_SIGNALING_CONN_STATUS_INACTIVE);
break; // qpackets will be emptied of all packets from this user so we're requeuing
sign_log.notice("Timeout disconnection");
update_si_status(sig.sig_info, SCE_NP_SIGNALING_CONN_STATUS_INACTIVE);
break; // qpackets will be emptied of all packets for this user so we're requeuing
}
// Update the timestamp if necessary
switch (it->second.packet.command)
switch (sig.packet.command)
{
case signal_connect:
case signal_ping:
it->second.packet.timestamp_sender = now.time_since_epoch().count();
sig.packet.timestamp_sender = now.time_since_epoch().count();
break;
case signal_connect_ack:
it->second.packet.timestamp_receiver = now.time_since_epoch().count();
sig.packet.timestamp_receiver = now.time_since_epoch().count();
break;
default:
break;
}
// Resend the packet
send_signaling_packet(it->second.packet, it->second.sig_info->addr, it->second.sig_info->port);
send_signaling_packet(sig.packet, sig.sig_info->addr, sig.sig_info->port);
// Reschedule another packet
SignalingCommand cmd = it->second.packet.command;
auto& si = it->second.sig_info;
SignalingCommand cmd = sig.packet.command;
auto& si = sig.sig_info;
std::chrono::milliseconds delay(500);
switch (cmd)
@ -441,6 +431,8 @@ void signaling_handler::update_si_addr(std::shared_ptr<signaling_info>& si, u32
ensure(si);
if (si->addr != new_addr || si->port != new_port)
{
if (sign_log.trace)
{
in_addr addr_old, addr_new;
addr_old.s_addr = si->addr;
@ -452,6 +444,8 @@ void signaling_handler::update_si_addr(std::shared_ptr<signaling_info>& si, u32
inet_ntop(AF_INET, &addr_new, ip_str_new, sizeof(ip_str_new));
sign_log.trace("Updated Address from %s:%d to %s:%d", ip_str_old, si->port, ip_str_new, new_port);
}
si->addr = new_addr;
si->port = new_port;
}
@ -461,7 +455,9 @@ void signaling_handler::update_si_mapped_addr(std::shared_ptr<signaling_info>& s
{
ensure(si);
if (si->addr != new_addr || si->port != new_port)
if (si->mapped_addr != new_addr || si->mapped_port != new_port)
{
if (sign_log.trace)
{
in_addr addr_old, addr_new;
addr_old.s_addr = si->mapped_addr;
@ -473,59 +469,60 @@ void signaling_handler::update_si_mapped_addr(std::shared_ptr<signaling_info>& s
inet_ntop(AF_INET, &addr_new, ip_str_new, sizeof(ip_str_new));
sign_log.trace("Updated Mapped Address from %s:%d to %s:%d", ip_str_old, si->mapped_port, ip_str_new, new_port);
}
si->mapped_addr = new_addr;
si->mapped_port = new_port;
}
}
void signaling_handler::update_si_status(std::shared_ptr<signaling_info>& si, s32 new_status, bool confirm_packet)
void signaling_handler::update_si_status(std::shared_ptr<signaling_info>& si, s32 new_status)
{
if (!si)
return;
if (si->connStatus == SCE_NP_SIGNALING_CONN_STATUS_PENDING && new_status == SCE_NP_SIGNALING_CONN_STATUS_ACTIVE)
if (si->conn_status == SCE_NP_SIGNALING_CONN_STATUS_PENDING && new_status == SCE_NP_SIGNALING_CONN_STATUS_ACTIVE)
{
si->connStatus = SCE_NP_SIGNALING_CONN_STATUS_ACTIVE;
ensure(si->version == 1u || si->version == 2u);
if (si->version == 1u)
si->conn_status = SCE_NP_SIGNALING_CONN_STATUS_ACTIVE;
signal_sig_callback(si->conn_id, SCE_NP_SIGNALING_EVENT_ESTABLISHED);
else
signal_sig2_callback(si->room_id, si->member_id, SCE_NP_MATCHING2_SIGNALING_EVENT_Established);
return;
}
if ((si->connStatus == SCE_NP_SIGNALING_CONN_STATUS_PENDING || si->connStatus == SCE_NP_SIGNALING_CONN_STATUS_ACTIVE) && new_status == SCE_NP_SIGNALING_CONN_STATUS_INACTIVE)
{
si->connStatus = SCE_NP_SIGNALING_CONN_STATUS_INACTIVE;
ensure(si->version == 1u || si->version == 2u);
if (si->version == 1u)
signal_sig_callback(si->conn_id, SCE_NP_SIGNALING_EVENT_DEAD);
else
signal_sig2_callback(si->room_id, si->member_id, SCE_NP_MATCHING2_SIGNALING_EVENT_Dead);
retire_all_packets(si);
return;
}
if (confirm_packet && si->version == 1u && si->ext_status == ext_sign_none)
{
si->ext_status = ext_sign_mutual;
if (si->op_activated)
signal_ext_sig_callback(si->conn_id, SCE_NP_SIGNALING_EVENT_EXT_MUTUAL_ACTIVATED);
}
else if ((si->conn_status == SCE_NP_SIGNALING_CONN_STATUS_PENDING || si->conn_status == SCE_NP_SIGNALING_CONN_STATUS_ACTIVE) && new_status == SCE_NP_SIGNALING_CONN_STATUS_INACTIVE)
{
si->conn_status = SCE_NP_SIGNALING_CONN_STATUS_INACTIVE;
signal_sig_callback(si->conn_id, SCE_NP_SIGNALING_EVENT_DEAD);
signal_sig2_callback(si->room_id, si->member_id, SCE_NP_MATCHING2_SIGNALING_EVENT_Dead);
retire_all_packets(si);
}
}
void signaling_handler::update_ext_si_status(std::shared_ptr<signaling_info>& si, bool op_activated)
{
if (op_activated && !si->op_activated)
{
si->op_activated = true;
if (si->conn_status != SCE_NP_SIGNALING_CONN_STATUS_ACTIVE)
signal_ext_sig_callback(si->conn_id, SCE_NP_SIGNALING_EVENT_EXT_PEER_ACTIVATED);
else
signal_ext_sig_callback(si->conn_id, SCE_NP_SIGNALING_EVENT_EXT_MUTUAL_ACTIVATED);
}
else if (!op_activated && si->op_activated)
{
si->op_activated = false;
signal_ext_sig_callback(si->conn_id, SCE_NP_SIGNALING_EVENT_EXT_PEER_DEACTIVATED);
}
}
void signaling_handler::set_self_sig_info(SceNpId& npid)
{
std::lock_guard lock(data_mutex);
sig1_packet.V1.npid = npid;
}
void signaling_handler::set_self_sig2_info(u64 room_id, u16 member_id)
{
std::lock_guard lock(data_mutex);
sig2_packet.V2.room_id = room_id;
sig2_packet.V2.member_id = member_id;
sig_packet.npid = npid;
}
void signaling_handler::send_signaling_packet(signaling_packet& sp, u32 addr, u16 port) const
@ -564,49 +561,35 @@ void signaling_handler::queue_signaling_packet(signaling_packet& sp, std::shared
std::shared_ptr<signaling_info> signaling_handler::get_signaling_ptr(const signaling_packet* sp)
{
// V1
if (sp->version == 1u)
{
u32 conn_id;
char npid_buf[17]{};
memcpy(npid_buf, sp->V1.npid.handle.data, 16);
memcpy(npid_buf, sp->npid.handle.data, 16);
std::string npid(npid_buf);
if (!npid_to_conn_id.contains(npid))
return nullptr;
const u32 conn_id = ::at32(npid_to_conn_id, npid);
if (!sig1_peers.contains(conn_id))
conn_id = ::at32(npid_to_conn_id, npid);
if (!sig_peers.contains(conn_id))
{
sign_log.error("Discrepancy in signaling 1 data");
sign_log.error("Discrepancy in signaling data");
return nullptr;
}
return ::at32(sig1_peers, conn_id);
}
// V2
auto room_id = sp->V2.room_id;
auto member_id = sp->V2.member_id;
if (!sig2_peers.contains(room_id) || !::at32(sig2_peers, room_id).contains(member_id))
return nullptr;
return ::at32(::at32(sig2_peers, room_id), member_id);
return ::at32(sig_peers, conn_id);
}
void signaling_handler::start_sig(u32 conn_id, u32 addr, u16 port)
{
std::lock_guard lock(data_mutex);
start_sig_nl(conn_id, addr, port);
}
void signaling_handler::start_sig_nl(u32 conn_id, u32 addr, u16 port)
{
auto& sent_packet = sig1_packet;
auto& sent_packet = sig_packet;
sent_packet.command = signal_connect;
sent_packet.timestamp_sender = steady_clock::now().time_since_epoch().count();
ensure(sig1_peers.contains(conn_id));
std::shared_ptr<signaling_info> si = ::at32(sig1_peers, conn_id);
ensure(sig_peers.contains(conn_id));
std::shared_ptr<signaling_info> si = ::at32(sig_peers, conn_id);
si->addr = addr;
si->port = port;
@ -616,133 +599,113 @@ void signaling_handler::start_sig_nl(u32 conn_id, u32 addr, u16 port)
wake_up();
}
void signaling_handler::stop_sig_nl(u32 conn_id)
{
if (!sig_peers.contains(conn_id))
return;
auto& sent_packet = sig_packet;
sent_packet.command = signal_finished;
std::shared_ptr<signaling_info> si = ::at32(sig_peers, conn_id);
send_signaling_packet(sent_packet, si->addr, si->port);
queue_signaling_packet(sent_packet, std::move(si), steady_clock::now() + REPEAT_FINISHED_DELAY);
}
void signaling_handler::stop_sig(u32 conn_id)
{
std::lock_guard lock(data_mutex);
if (!sig1_peers.contains(conn_id))
return;
auto& sent_packet = sig1_packet;
sent_packet.command = signal_finished;
std::shared_ptr<signaling_info> si = ::at32(sig1_peers, conn_id);
send_signaling_packet(sent_packet, si->addr, si->port);
queue_signaling_packet(sent_packet, si, steady_clock::now() + REPEAT_FINISHED_DELAY);
}
void signaling_handler::start_sig2(u64 room_id, u16 member_id)
{
std::lock_guard lock(data_mutex);
auto& sent_packet = sig2_packet;
sent_packet.command = signal_connect;
sent_packet.timestamp_sender = steady_clock::now().time_since_epoch().count();
ensure(sig2_peers.contains(room_id));
const auto& sp = ::at32(sig2_peers, room_id);
ensure(sp.contains(member_id));
std::shared_ptr<signaling_info> si = ::at32(sp, member_id);
send_signaling_packet(sent_packet, si->addr, si->port);
queue_signaling_packet(sent_packet, si, steady_clock::now() + REPEAT_CONNECT_DELAY);
wake_up();
stop_sig_nl(conn_id);
}
void signaling_handler::disconnect_sig2_users(u64 room_id)
{
std::lock_guard lock(data_mutex);
if (!sig2_peers.contains(room_id))
return;
auto& sent_packet = sig2_packet;
sent_packet.command = signal_finished;
for (const auto& member : ::at32(sig2_peers, room_id))
for (auto& [conn_id, si] : sig_peers)
{
auto& si = member.second;
if (si->connStatus != SCE_NP_SIGNALING_CONN_STATUS_INACTIVE && !si->self)
if (si->room_id == room_id)
{
send_signaling_packet(sent_packet, si->addr, si->port);
queue_signaling_packet(sent_packet, si, steady_clock::now() + REPEAT_FINISHED_DELAY);
stop_sig_nl(conn_id);
}
}
}
u32 signaling_handler::create_sig_infos(const SceNpId* npid)
u32 signaling_handler::get_always_conn_id(const SceNpId& npid)
{
ensure(npid->handle.data[16] == 0);
std::string npid_str(reinterpret_cast<const char*>(npid->handle.data));
std::string npid_str(reinterpret_cast<const char*>(npid.handle.data));
if (npid_to_conn_id.contains(npid_str))
{
return ::at32(npid_to_conn_id, npid_str);
}
const u32 conn_id = cur_conn_id++;
npid_to_conn_id.emplace(npid_str, conn_id);
sig1_peers.emplace(conn_id, std::make_shared<signaling_info>());
::at32(sig1_peers, conn_id)->version = 1;
::at32(sig1_peers, conn_id)->conn_id = conn_id;
::at32(sig1_peers, conn_id)->npid = *npid;
npid_to_conn_id.emplace(std::move(npid_str), conn_id);
sig_peers.emplace(conn_id, std::make_shared<signaling_info>());
auto& si = ::at32(sig_peers, conn_id);
si->conn_id = conn_id;
si->npid = npid;
return conn_id;
}
u32 signaling_handler::init_sig_infos(const SceNpId* npid)
u32 signaling_handler::init_sig1(const SceNpId& npid)
{
std::lock_guard lock(data_mutex);
const u32 conn_id = create_sig_infos(npid);
const u32 conn_id = get_always_conn_id(npid);
if (sig1_peers[conn_id]->connStatus == SCE_NP_SIGNALING_CONN_STATUS_INACTIVE)
if (sig_peers[conn_id]->conn_status == SCE_NP_SIGNALING_CONN_STATUS_INACTIVE)
{
sign_log.trace("Creating new sig1 connection and requesting infos from RPCN");
sig1_peers[conn_id]->connStatus = SCE_NP_SIGNALING_CONN_STATUS_PENDING;
sig_peers[conn_id]->conn_status = SCE_NP_SIGNALING_CONN_STATUS_PENDING;
// Request peer infos from RPCN
std::string npid_str(reinterpret_cast<const char*>(npid->handle.data));
std::string npid_str(reinterpret_cast<const char*>(npid.handle.data));
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
nph.req_sign_infos(npid_str, conn_id);
}
else
{
// Connection already exists(from passive connection)
if (sig1_peers[conn_id]->connStatus == SCE_NP_SIGNALING_CONN_STATUS_ACTIVE && sig1_peers[conn_id]->ext_status == ext_sign_peer)
{
sign_log.trace("Activating already peer activated connection");
sig1_peers[conn_id]->ext_status = ext_sign_mutual;
start_sig_nl(conn_id, sig1_peers[conn_id]->addr, sig1_peers[conn_id]->port);
signal_sig_callback(conn_id, SCE_NP_SIGNALING_EVENT_ESTABLISHED);
signal_ext_sig_callback(conn_id, SCE_NP_SIGNALING_EVENT_EXT_MUTUAL_ACTIVATED);
}
}
return conn_id;
}
signaling_info signaling_handler::get_sig_infos(u32 conn_id)
u32 signaling_handler::init_sig2(const SceNpId& npid, u64 room_id, u16 member_id)
{
std::lock_guard lock(data_mutex);
return *sig1_peers[conn_id];
u32 conn_id = get_always_conn_id(npid);
auto& si = ::at32(sig_peers, conn_id);
si->room_id = room_id;
si->member_id = member_id;
si->conn_status = SCE_NP_SIGNALING_CONN_STATUS_PENDING;
return conn_id;
}
std::optional<u32> signaling_handler::get_conn_id_from_npid(const SceNpId* npid)
std::optional<u32> signaling_handler::get_conn_id_from_npid(const SceNpId& npid)
{
std::lock_guard lock(data_mutex);
// Diff behaviour here depending on SDK version, 420+ always succeeds
return create_sig_infos(npid);
std::string npid_str(reinterpret_cast<const char*>(npid.handle.data));
if (npid_to_conn_id.contains(npid_str))
return ::at32(npid_to_conn_id, npid_str);
return std::nullopt;
}
std::optional<signaling_info> signaling_handler::get_sig_infos(u32 conn_id)
{
std::lock_guard lock(data_mutex);
if (sig_peers.contains(conn_id))
return *::at32(sig_peers, conn_id);
return std::nullopt;
}
std::optional<u32> signaling_handler::get_conn_id_from_addr(u32 addr, u16 port)
{
std::lock_guard lock(data_mutex);
for (const auto& [conn_id, conn_info] : sig1_peers)
for (const auto& [conn_id, conn_info] : sig_peers)
{
if (conn_info && std::bit_cast<u32, be_t<u32>>(conn_info->addr) == addr && conn_info->port == port)
{
@ -752,36 +715,3 @@ std::optional<u32> signaling_handler::get_conn_id_from_addr(u32 addr, u16 port)
return std::nullopt;
}
void signaling_handler::set_sig2_infos(u64 room_id, u16 member_id, s32 status, u32 addr, u16 port, const SceNpId& npid, bool self)
{
std::lock_guard lock(data_mutex);
if (!sig2_peers[room_id][member_id])
sig2_peers[room_id][member_id] = std::make_shared<signaling_info>();
auto& peer = sig2_peers[room_id][member_id];
peer->connStatus = status;
peer->addr = addr;
peer->port = port;
peer->self = self;
peer->version = 2;
peer->room_id = room_id;
peer->member_id = member_id;
peer->npid = npid;
}
signaling_info signaling_handler::get_sig2_infos(u64 room_id, u16 member_id)
{
std::lock_guard lock(data_mutex);
if (!sig2_peers[room_id][member_id])
{
sig2_peers[room_id][member_id] = std::make_shared<signaling_info>();
auto& peer = sig2_peers[room_id][member_id];
peer->room_id = room_id;
peer->member_id = member_id;
peer->version = 2;
}
return *sig2_peers[room_id][member_id];
}

View File

@ -9,16 +9,9 @@
#include <chrono>
#include <optional>
enum ext_signaling_status : u8
{
ext_sign_none = 0,
ext_sign_peer = 1,
ext_sign_mutual = 2,
};
struct signaling_info
{
s32 connStatus = SCE_NP_SIGNALING_CONN_STATUS_INACTIVE;
s32 conn_status = SCE_NP_SIGNALING_CONN_STATUS_INACTIVE;
u32 addr = 0;
u16 port = 0;
@ -29,12 +22,11 @@ struct signaling_info
// For handler
steady_clock::time_point time_last_msg_recvd = steady_clock::now();
bool self = false;
u32 version = 0;
SceNpId npid{};
// Signaling
u32 conn_id = 0;
ext_signaling_status ext_status = ext_sign_none;
bool op_activated = false;
// Matching2
u64 room_id = 0;
@ -68,16 +60,13 @@ public:
signaling_handler& operator=(thread_state);
void set_self_sig_info(SceNpId& npid);
void set_self_sig2_info(u64 room_id, u16 member_id);
u32 init_sig_infos(const SceNpId* npid);
signaling_info get_sig_infos(u32 conn_id);
std::optional<u32> get_conn_id_from_npid(const SceNpId* npid);
u32 init_sig1(const SceNpId& npid);
u32 init_sig2(const SceNpId& npid, u64 room_id, u16 member_id);
std::optional<signaling_info> get_sig_infos(u32 conn_id);
std::optional<u32> get_conn_id_from_npid(const SceNpId& npid);
std::optional<u32> get_conn_id_from_addr(u32 addr, u16 port);
void set_sig2_infos(u64 room_id, u16 member_id, s32 status, u32 addr, u16 port, const SceNpId& npid, bool self = false);
signaling_info get_sig2_infos(u64 room_id, u16 member_id);
void set_sig_cb(u32 sig_cb_ctx, vm::ptr<SceNpSignalingHandler> sig_cb, vm::ptr<void> sig_cb_arg);
void set_ext_sig_cb(u32 sig_ext_cb_ctx, vm::ptr<SceNpSignalingHandler> sig_ext_cb, vm::ptr<void> sig_ext_cb_arg);
void set_sig2_cb(u16 sig2_cb_ctx, vm::ptr<SceNpMatching2SignalingCallback> sig2_cb, vm::ptr<void> sig2_cb_arg);
@ -95,28 +84,18 @@ private:
static constexpr auto REPEAT_PING_DELAY = std::chrono::milliseconds(500);
static constexpr auto REPEAT_FINISHED_DELAY = std::chrono::milliseconds(500);
static constexpr be_t<u32> SIGNALING_SIGNATURE = (static_cast<u32>('S') << 24 | static_cast<u32>('I') << 16 | static_cast<u32>('G') << 8 | static_cast<u32>('N'));
static constexpr le_t<u32> SIGNALING_VERSION = 3;
struct signaling_packet
{
be_t<u32> signature = SIGNALING_SIGNATURE;
le_t<u32> version;
le_t<u32> version = SIGNALING_VERSION;
le_t<u64> timestamp_sender;
le_t<u64> timestamp_receiver;
le_t<SignalingCommand> command;
le_t<u32> sent_addr;
le_t<u16> sent_port;
union
{
struct
{
SceNpId npid;
} V1;
struct
{
le_t<u64> room_id;
le_t<u16> member_id;
} V2;
};
};
struct queued_packet
@ -137,33 +116,32 @@ private:
vm::ptr<SceNpMatching2SignalingCallback> sig2_cb{};
vm::ptr<void> sig2_cb_arg{};
u32 create_sig_infos(const SceNpId* npid);
u32 get_always_conn_id(const SceNpId& npid);
static void update_si_addr(std::shared_ptr<signaling_info>& si, u32 new_addr, u16 new_port);
static void update_si_mapped_addr(std::shared_ptr<signaling_info>& si, u32 new_addr, u16 new_port);
void update_si_status(std::shared_ptr<signaling_info>& si, s32 new_status, bool confirm_packet = false);
static void update_room_info(std::shared_ptr<signaling_info>& si, u64 room_id, u16 member_id);
void update_si_status(std::shared_ptr<signaling_info>& si, s32 new_status);
void update_ext_si_status(std::shared_ptr<signaling_info>& si, bool op_activated);
void signal_sig_callback(u32 conn_id, int event);
void signal_ext_sig_callback(u32 conn_id, int event) const;
void signal_sig2_callback(u64 room_id, u16 member_id, SceNpMatching2Event event) const;
void start_sig_nl(u32 conn_id, u32 addr, u16 port);
static bool validate_signaling_packet(const signaling_packet* sp);
void reschedule_packet(std::shared_ptr<signaling_info>& si, SignalingCommand cmd, steady_clock::time_point new_timepoint);
void retire_packet(std::shared_ptr<signaling_info>& si, SignalingCommand cmd);
void retire_all_packets(std::shared_ptr<signaling_info>& si);
void stop_sig_nl(u32 conn_id);
std::mutex data_mutex;
std::condition_variable wakey;
signaling_packet sig1_packet{.version = 1u};
signaling_packet sig2_packet{.version = 2u};
signaling_packet sig_packet{};
std::map<steady_clock::time_point, queued_packet> qpackets; // (wakeup time, packet)
u32 cur_conn_id = 1;
std::unordered_map<std::string, u32> npid_to_conn_id; // (npid, conn_id)
std::unordered_map<u32, std::shared_ptr<signaling_info>> sig1_peers; // (conn_id, sig_info)
std::unordered_map<u64, std::unordered_map<u16, std::shared_ptr<signaling_info>>> sig2_peers; // (room (member_id, sig_info))
std::unordered_map<u32, std::shared_ptr<signaling_info>> sig_peers; // (conn_id, sig_info)
void process_incoming_messages();
std::shared_ptr<signaling_info> get_signaling_ptr(const signaling_packet* sp);

View File

@ -0,0 +1,59 @@
#include "stdafx.h"
#include "upnp_config.h"
#include "Utilities/File.h"
LOG_CHANNEL(upnp_cfg_log, "UPNP_CFG");
void cfg_upnp::load()
{
const std::string path = cfg_upnp::get_path();
fs::file cfg_file(path, fs::read);
if (cfg_file)
{
upnp_cfg_log.notice("Loading UPNP config. Path: %s", path);
from_string(cfg_file.to_string());
}
else
{
upnp_cfg_log.notice("UPNP config missing. Using default settings. Path: %s", path);
from_default();
}
}
void cfg_upnp::save() const
{
#ifdef _WIN32
const std::string path_to_cfg = fs::get_config_dir() + "config/";
if (!fs::create_path(path_to_cfg))
{
upnp_cfg_log.error("Could not create path: %s", path_to_cfg);
}
#endif
fs::pending_file cfg_file(cfg_upnp::get_path());
if (!cfg_file.file || (cfg_file.file.write(to_string()), !cfg_file.commit()))
{
upnp_cfg_log.error("Could not save config: %s (error=%s)", cfg_upnp::get_path(), fs::g_tls_error);
}
}
std::string cfg_upnp::get_device_url() const
{
return device_url.to_string();
}
void cfg_upnp::set_device_url(std::string_view url)
{
device_url.from_string(url);
}
std::string cfg_upnp::get_path()
{
#ifdef _WIN32
return fs::get_config_dir() + "config/upnp.yml";
#else
return fs::get_config_dir() + "upnp.yml";
#endif
}

View File

@ -0,0 +1,18 @@
#pragma once
#include "Utilities/Config.h"
struct cfg_upnp : cfg::node
{
cfg::string device_url{this, "DeviceUrl", ""};
void load();
void save() const;
std::string get_device_url() const;
void set_device_url(std::string_view url);
private:
static std::string get_path();
};

View File

@ -0,0 +1,186 @@
#include "stdafx.h"
#include "upnp_handler.h"
#include "util/logs.hpp"
#include <miniwget.h>
#include <upnpcommands.h>
LOG_CHANNEL(upnp_log, "UPNP");
upnp_handler::~upnp_handler()
{
std::lock_guard lock(m_mutex);
for (const auto& [protocol, prot_bindings] : m_bindings)
{
for (const auto& [internal_port, external_port] : prot_bindings)
{
remove_port_redir_external(external_port, protocol);
}
}
m_active = false;
}
void upnp_handler::upnp_enable()
{
std::lock_guard lock(m_mutex);
m_cfg.load();
auto check_igd = [&](const char* url) -> bool
{
int desc_xml_size = 0;
int status_code = 0;
m_igd_data = {};
m_igd_urls = {};
char* desc_xml = static_cast<char*>(miniwget(url, &desc_xml_size, 1, &status_code));
if (!desc_xml)
return false;
parserootdesc(desc_xml, desc_xml_size, &m_igd_data);
free(desc_xml);
desc_xml = nullptr;
GetUPNPUrls(&m_igd_urls, &m_igd_data, url, 1);
return true;
};
std::string dev_url = m_cfg.get_device_url();
if (!dev_url.empty())
{
if (check_igd(dev_url.c_str()))
{
upnp_log.notice("Saved UPNP(%s) enabled", dev_url);
m_active = true;
return;
}
upnp_log.error("Saved UPNP(%s) isn't available anymore", dev_url);
}
upnp_log.notice("Starting UPNP search");
int upnperror = 0;
UPNPDev* devlist = upnpDiscover(2000, nullptr, nullptr, 0, 0, 2, &upnperror);
if (!devlist)
{
upnp_log.error("No UPNP device was found");
return;
}
const UPNPDev* dev = devlist;
for (; dev; dev = dev->pNext)
{
if (strstr(dev->st, "InternetGatewayDevice"))
break;
}
if (dev)
{
int desc_xml_size = 0;
int status_code = 0;
char* desc_xml = static_cast<char*>(miniwget(dev->descURL, &desc_xml_size, 1, &status_code));
if (desc_xml)
{
IGDdatas igd_data{};
UPNPUrls igd_urls{};
parserootdesc(desc_xml, desc_xml_size, &igd_data);
free(desc_xml);
desc_xml = nullptr;
GetUPNPUrls(&igd_urls, &igd_data, dev->descURL, 1);
upnp_log.notice("Found UPnP device type:%s at %s", dev->st, dev->descURL);
m_cfg.set_device_url(dev->descURL);
m_cfg.save();
m_active = true;
}
else
{
upnp_log.error("Failed to retrieve UPNP xml for %s", dev->descURL);
}
}
else
{
upnp_log.error("No UPNP IGD device was found");
}
freeUPNPDevlist(devlist);
}
void upnp_handler::add_port_redir(std::string_view addr, u16 internal_port, std::string_view protocol)
{
if (!m_active)
return;
std::lock_guard lock(m_mutex);
u16 external_port = internal_port;
std::string internal_port_str = fmt::format("%d", internal_port);
int res = 0;
for (u16 external_port = internal_port; external_port < internal_port + 100; external_port++)
{
std::string external_port_str = fmt::format("%d", external_port);
res = UPNP_AddPortMapping(m_igd_urls.controlURL, m_igd_data.first.servicetype, external_port_str.c_str(), internal_port_str.c_str(), addr.data(), "RPCS3", protocol.data(), nullptr, nullptr);
if (res == UPNPCOMMAND_SUCCESS)
{
m_bindings[std::string(protocol)][internal_port] = external_port;
upnp_log.notice("Successfully bound %s:%d(%s) to IGD:%d", addr, internal_port, protocol, external_port);
return;
}
// need more testing, may vary per router, etc, for now assume port conflict silently
// else if (res != 718) // ConflictInMappingEntry
// {
// upnp_log.error("Failed to bind %s:%d(%s) to IGD:%d: %d", addr, internal_port, protocol, external_port, res);
// return;
// }
}
upnp_log.error("Failed to bind %s:%d(%s) to IGD:(%d=>%d): %d", addr, internal_port, protocol, internal_port, external_port, res);
}
void upnp_handler::remove_port_redir(u16 internal_port, std::string_view protocol)
{
if (!m_active)
return;
std::lock_guard lock(m_mutex);
const std::string str_protocol(protocol);
if (!m_bindings.contains(str_protocol) || !::at32(m_bindings, str_protocol).contains(internal_port))
{
upnp_log.error("tried to unbind port mapping %d to IGD(%s) but it isn't bound", internal_port, protocol);
return;
}
const u16 external_port = ::at32(::at32(m_bindings, str_protocol), internal_port);
remove_port_redir_external(external_port, protocol);
ensure(::at32(m_bindings, str_protocol).erase(internal_port));
upnp_log.notice("Successfully deleted port mapping %d to IGD:%d(%s)", internal_port, external_port, protocol);
}
void upnp_handler::remove_port_redir_external(u16 external_port, std::string_view protocol, bool verbose)
{
const std::string str_ext_port = fmt::format("%d", external_port);
if (int res = UPNP_DeletePortMapping(m_igd_urls.controlURL, m_igd_data.first.servicetype, str_ext_port.c_str(), protocol.data(), nullptr); res != 0 && verbose)
upnp_log.error("Failed to delete port mapping IGD:%s(%s): %d", str_ext_port, protocol, res);
}
bool upnp_handler::is_active() const
{
return m_active;
}

View File

@ -0,0 +1,32 @@
#pragma once
#include <unordered_map>
#include <miniupnpc.h>
#include "upnp_config.h"
#include "Utilities/mutex.h"
class upnp_handler
{
public:
~upnp_handler();
void upnp_enable();
void add_port_redir(std::string_view addr, u16 internal_port, std::string_view protocol);
void remove_port_redir(u16 internal_port, std::string_view protocol);
bool is_active() const;
private:
void remove_port_redir_external(u16 external_port, std::string_view protocol, bool verbose = true);
private:
atomic_t<bool> m_active = false;
shared_mutex m_mutex;
cfg_upnp m_cfg;
IGDdatas m_igd_data{};
UPNPUrls m_igd_urls{};
std::unordered_map<std::string, std::unordered_map<u16, u16>> m_bindings;
};

View File

@ -298,6 +298,7 @@ struct cfg_root : cfg::node
cfg::string bind_address{this, "Bind address", "0.0.0.0"};
cfg::string dns{this, "DNS address", "8.8.8.8"};
cfg::string swap_list{this, "IP swap list", ""};
cfg::_bool upnp_enabled{this, "UPNP Enabled", false};
cfg::_enum<np_psn_status> psn_status{this, "PSN status", np_psn_status::disabled};
} net{this};

View File

@ -40,10 +40,10 @@
<ItemDefinitionGroup>
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<AdditionalIncludeDirectories>..\3rdparty\wolfssl\wolfssl;..\3rdparty\flatbuffers\include;..\3rdparty\libusb\libusb\libusb;..\3rdparty\yaml-cpp\yaml-cpp\include;..\3rdparty\SoundTouch\soundtouch\include;..\3rdparty\zlib\zlib;..\llvm\include;..\llvm_build\include;$(VULKAN_SDK)\Include</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\3rdparty\miniupnp\miniupnp\miniupnpc\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\flatbuffers\include;..\3rdparty\libusb\libusb\libusb;..\3rdparty\yaml-cpp\yaml-cpp\include;..\3rdparty\SoundTouch\soundtouch\include;..\3rdparty\zlib\zlib;..\llvm\include;..\llvm_build\include;$(VULKAN_SDK)\Include</AdditionalIncludeDirectories>
<Optimization Condition="'$(Configuration)|$(Platform)'=='Release|x64'">MaxSpeed</Optimization>
<PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">HAVE_VULKAN;HAVE_SDL2;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">HAVE_VULKAN;HAVE_SDL2;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">MINIUPNP_STATICLIB;HAVE_VULKAN;HAVE_SDL2;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">MINIUPNP_STATICLIB;HAVE_VULKAN;HAVE_SDL2;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<PreBuildEvent>
<Command>cmd.exe /c "$(SolutionDir)\Utilities\git-version-gen.cmd"</Command>
@ -82,6 +82,8 @@
<ClCompile Include="Emu\IPC_socket.cpp" />
<ClCompile Include="Emu\localized_string.cpp" />
<ClCompile Include="Emu\NP\rpcn_config.cpp" />
<ClCompile Include="Emu\NP\upnp_config.cpp" />
<ClCompile Include="Emu\NP\upnp_handler.cpp" />
<ClCompile Include="Emu\perf_monitor.cpp" />
<ClCompile Include="Emu\RSX\Common\texture_cache.cpp" />
<ClCompile Include="Emu\RSX\Overlays\HomeMenu\overlay_home_menu.cpp" />
@ -516,6 +518,8 @@
<ClInclude Include="Emu\NP\generated\np2_structs_generated.h" />
<ClInclude Include="Emu\NP\np_handler.h" />
<ClInclude Include="Emu\NP\signaling_handler.h" />
<ClInclude Include="Emu\NP\upnp_config.h" />
<ClInclude Include="Emu\NP\upnp_handler.h" />
<ClInclude Include="Emu\NP\vport0.h" />
<ClInclude Include="Emu\NP\np_allocator.h" />
<ClInclude Include="Emu\NP\np_cache.h" />

View File

@ -1138,6 +1138,12 @@
<ClCompile Include="Emu\RSX\Overlays\HomeMenu\overlay_home_menu_message_box.cpp">
<Filter>Emu\GPU\RSX\Overlays\HomeMenu</Filter>
</ClCompile>
<ClCompile Include="Emu\NP\upnp_handler.cpp">
<Filter>Emu\NP</Filter>
</ClCompile>
<ClCompile Include="Emu\NP\upnp_config.cpp">
<Filter>Emu\NP</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Crypto\aes.h">
@ -2290,6 +2296,12 @@
<ClInclude Include="Emu\RSX\Overlays\HomeMenu\overlay_home_menu_message_box.h">
<Filter>Emu\GPU\RSX\Overlays\HomeMenu</Filter>
</ClInclude>
<ClInclude Include="Emu\NP\upnp_handler.h">
<Filter>Emu\NP</Filter>
</ClInclude>
<ClInclude Include="Emu\NP\upnp_config.h">
<Filter>Emu\NP</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="Emu\RSX\Program\GLSLSnippets\GPUDeswizzle.glsl">

View File

@ -79,7 +79,7 @@
<DisableSpecificWarnings>4577;4467;%(DisableSpecificWarnings)</DisableSpecificWarnings>
<ObjectFileName>$(IntDir)</ObjectFileName>
<Optimization>MaxSpeed</Optimization>
<PreprocessorDefinitions>_WINDOWS;UNICODE;WIN32;WIN64;WIN32_LEAN_AND_MEAN;HAVE_VULKAN;HAVE_SDL2;WITH_DISCORD_RPC;QT_NO_DEBUG;QT_WIDGETS_LIB;QT_GUI_LIB;QT_CORE_LIB;NDEBUG;QT_WINEXTRAS_LIB;QT_CONCURRENT_LIB;QT_MULTIMEDIA_LIB;QT_MULTIMEDIAWIDGETS_LIB;QT_SVG_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>_WINDOWS;UNICODE;WIN32;WIN64;WIN32_LEAN_AND_MEAN;HAVE_VULKAN;MINIUPNP_STATICLIB;HAVE_SDL2;WITH_DISCORD_RPC;QT_NO_DEBUG;QT_WIDGETS_LIB;QT_GUI_LIB;QT_CORE_LIB;NDEBUG;QT_WINEXTRAS_LIB;QT_CONCURRENT_LIB;QT_MULTIMEDIA_LIB;QT_MULTIMEDIAWIDGETS_LIB;QT_SVG_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessToFile>false</PreprocessToFile>
<ProgramDataBaseFileName>$(IntDir)vc$(PlatformToolsetVersion).pdb</ProgramDataBaseFileName>
<RuntimeTypeInfo>true</RuntimeTypeInfo>
@ -88,7 +88,7 @@
<WarningLevel>Level3</WarningLevel>
</ClCompile>
<Link>
<AdditionalDependencies>DbgHelp.lib;Ole32.lib;gdi32.lib;..\hidapi.lib;..\libusb-1.0.lib;winmm.lib;imm32.lib;ksuser.lib;version.lib;OpenAL32.lib;XAudio.lib;GLGSRender.lib;shlwapi.lib;VKGSRender.lib;vulkan-1.lib;wolfssl.lib;libcurl.lib;Wldap32.lib;glslang.lib;OSDependent.lib;OGLCompiler.lib;SPIRV.lib;MachineIndependent.lib;GenericCodeGen.lib;Advapi32.lib;user32.lib;zlib.lib;..\libpng16.lib;asmjit.lib;yaml-cpp.lib;discord-rpc.lib;emucore.lib;dxgi.lib;$(QTDIR)\lib\qtmain.lib;shell32.lib;$(QTDIR)\lib\Qt5Widgets.lib;$(QTDIR)\lib\Qt5Gui.lib;$(QTDIR)\lib\Qt5Core.lib;Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;Qt5WinExtras.lib;Qt5Concurrent.lib;7zlib.lib;SPIRV-Tools.lib;SPIRV-Tools-opt.lib;Qt5Multimedia.lib;Qt5MultimediaWidgets.lib;Qt5Svg.lib;libcubeb.lib;cubeb.lib;soundtouch.lib;Avrt.lib;SDL.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>DbgHelp.lib;Ole32.lib;gdi32.lib;..\hidapi.lib;..\libusb-1.0.lib;winmm.lib;miniupnpc_static.lib;imm32.lib;ksuser.lib;version.lib;OpenAL32.lib;XAudio.lib;GLGSRender.lib;shlwapi.lib;VKGSRender.lib;vulkan-1.lib;wolfssl.lib;libcurl.lib;Wldap32.lib;glslang.lib;OSDependent.lib;OGLCompiler.lib;SPIRV.lib;MachineIndependent.lib;GenericCodeGen.lib;Advapi32.lib;user32.lib;zlib.lib;..\libpng16.lib;asmjit.lib;yaml-cpp.lib;discord-rpc.lib;emucore.lib;dxgi.lib;$(QTDIR)\lib\qtmain.lib;shell32.lib;$(QTDIR)\lib\Qt5Widgets.lib;$(QTDIR)\lib\Qt5Gui.lib;$(QTDIR)\lib\Qt5Core.lib;Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;Qt5WinExtras.lib;Qt5Concurrent.lib;7zlib.lib;SPIRV-Tools.lib;SPIRV-Tools-opt.lib;Qt5Multimedia.lib;Qt5MultimediaWidgets.lib;Qt5Svg.lib;libcubeb.lib;cubeb.lib;soundtouch.lib;Avrt.lib;SDL.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\3rdparty\OpenAL\libs\Win64;..\3rdparty\glslang\build\hlsl\Release;..\3rdparty\glslang\build\SPIRV\Release;..\3rdparty\glslang\build\OGLCompilersDLL\Release;..\3rdparty\glslang\build\glslang\OSDependent\Windows\Release;..\3rdparty\glslang\build\glslang\Release;..\3rdparty\SPIRV\build\source\Release;..\3rdparty\SPIRV\build\source\opt\Release;..\lib\$(CONFIGURATION)-$(PLATFORM);..\3rdparty\XAudio2Redist\libs;..\3rdparty\discord-rpc\lib;$(QTDIR)\lib;%(AdditionalLibraryDirectories);$(VULKAN_SDK)\Lib</AdditionalLibraryDirectories>
<AdditionalOptions>"/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" %(AdditionalOptions)</AdditionalOptions>
<DataExecutionPrevention>true</DataExecutionPrevention>
@ -130,7 +130,7 @@
<DisableSpecificWarnings>4577;4467;%(DisableSpecificWarnings)</DisableSpecificWarnings>
<ObjectFileName>$(IntDir)</ObjectFileName>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>_WINDOWS;UNICODE;WIN32;WIN64;WIN32_LEAN_AND_MEAN;HAVE_VULKAN;QT_WIDGETS_LIB;QT_GUI_LIB;QT_CORE_LIB;QT_WINEXTRAS_LIB;QT_CONCURRENT_LIB;QT_MULTIMEDIA_LIB;QT_MULTIMEDIAWIDGETS_LIB;QT_SVG_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>_WINDOWS;UNICODE;WIN32;WIN64;WIN32_LEAN_AND_MEAN;HAVE_VULKAN;MINIUPNP_STATICLIB;QT_WIDGETS_LIB;QT_GUI_LIB;QT_CORE_LIB;QT_WINEXTRAS_LIB;QT_CONCURRENT_LIB;QT_MULTIMEDIA_LIB;QT_MULTIMEDIAWIDGETS_LIB;QT_SVG_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessToFile>false</PreprocessToFile>
<RuntimeTypeInfo>true</RuntimeTypeInfo>
<SuppressStartupBanner>true</SuppressStartupBanner>
@ -139,7 +139,7 @@
<ProgramDataBaseFileName>$(IntDir)vc$(PlatformToolsetVersion).pdb</ProgramDataBaseFileName>
</ClCompile>
<Link>
<AdditionalDependencies>DbgHelp.lib;Ole32.lib;gdi32.lib;..\hidapi.lib;..\libusb-1.0.lib;winmm.lib;ksuser.lib;OpenAL32.lib;XAudio.lib;GLGSRender.lib;shlwapi.lib;VKGSRender.lib;vulkan-1.lib;wolfssl.lib;libcurl.lib;Wldap32.lib;glslangd.lib;OSDependentd.lib;OGLCompilerd.lib;SPIRVd.lib;MachineIndependentd.lib;GenericCodeGend.lib;Advapi32.lib;user32.lib;zlib.lib;..\libpng16.lib;asmjit.lib;yaml-cpp.lib;discord-rpc.lib;emucore.lib;dxgi.lib;$(QTDIR)\lib\qtmaind.lib;shell32.lib;$(QTDIR)\lib\Qt5Widgetsd.lib;$(QTDIR)\lib\Qt5Guid.lib;$(QTDIR)\lib\Qt5Cored.lib;Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;Qt5WinExtrasd.lib;Qt5Concurrentd.lib;7zlib.lib;SPIRV-Tools.lib;SPIRV-Tools-opt.lib;Qt5Multimediad.lib;Qt5MultimediaWidgetsd.lib;Qt5Svgd.lib;libcubeb.lib;cubeb.lib;soundtouch.lib;Avrt.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>DbgHelp.lib;Ole32.lib;gdi32.lib;..\hidapi.lib;..\libusb-1.0.lib;winmm.lib;miniupnpc_static.lib;ksuser.lib;OpenAL32.lib;XAudio.lib;GLGSRender.lib;shlwapi.lib;VKGSRender.lib;vulkan-1.lib;wolfssl.lib;libcurl.lib;Wldap32.lib;glslangd.lib;OSDependentd.lib;OGLCompilerd.lib;SPIRVd.lib;MachineIndependentd.lib;GenericCodeGend.lib;Advapi32.lib;user32.lib;zlib.lib;..\libpng16.lib;asmjit.lib;yaml-cpp.lib;discord-rpc.lib;emucore.lib;dxgi.lib;$(QTDIR)\lib\qtmaind.lib;shell32.lib;$(QTDIR)\lib\Qt5Widgetsd.lib;$(QTDIR)\lib\Qt5Guid.lib;$(QTDIR)\lib\Qt5Cored.lib;Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;Qt5WinExtrasd.lib;Qt5Concurrentd.lib;7zlib.lib;SPIRV-Tools.lib;SPIRV-Tools-opt.lib;Qt5Multimediad.lib;Qt5MultimediaWidgetsd.lib;Qt5Svgd.lib;libcubeb.lib;cubeb.lib;soundtouch.lib;Avrt.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\3rdparty\OpenAL\libs\Win64;..\3rdparty\glslang\build\hlsl\Debug;..\3rdparty\glslang\build\SPIRV\Debug;..\3rdparty\glslang\build\OGLCompilersDLL\Debug;..\3rdparty\glslang\build\glslang\OSDependent\Windows\Debug;..\3rdparty\glslang\build\glslang\Debug;..\3rdparty\SPIRV\build\source\opt\Debug;..\3rdparty\XAudio2Redist\libs;..\3rdparty\discord-rpc\lib;..\lib\$(CONFIGURATION)-$(PLATFORM);$(QTDIR)\lib;%(AdditionalLibraryDirectories);$(VULKAN_SDK)\Lib</AdditionalLibraryDirectories>
<AdditionalOptions>"/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" /VERBOSE %(AdditionalOptions)</AdditionalOptions>
<DataExecutionPrevention>true</DataExecutionPrevention>

View File

@ -173,7 +173,7 @@ usz downloader::update_buffer(char* data, usz size)
if (m_actual_download_size < 0)
{
if (curl_easy_getinfo(m_curl->get_curl(), CURLINFO_CONTENT_LENGTH_DOWNLOAD, &m_actual_download_size) == CURLE_OK && m_actual_download_size > 0)
if (curl_easy_getinfo(m_curl->get_curl(), CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &m_actual_download_size) == CURLE_OK && m_actual_download_size > 0)
{
max = static_cast<int>(m_actual_download_size);
}

View File

@ -173,6 +173,7 @@ enum class emu_settings_type
IpSwapList,
PSNStatus,
BindAddress,
EnableUpnp,
// System
LicenseArea,
@ -353,6 +354,7 @@ inline static const QMap<emu_settings_type, cfg_location> settings_location =
{ emu_settings_type::IpSwapList, { "Net", "IP swap list"}},
{ emu_settings_type::PSNStatus, { "Net", "PSN status"}},
{ emu_settings_type::BindAddress, { "Net", "Bind address"}},
{ emu_settings_type::EnableUpnp, { "Net", "UPNP Enabled"}},
// System
{ emu_settings_type::LicenseArea, { "System", "License Area"}},

View File

@ -1304,6 +1304,9 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std
m_emu_settings->EnhanceLineEdit(ui->edit_swaps, emu_settings_type::IpSwapList);
SubscribeTooltip(ui->gb_edit_swaps, tooltips.settings.dns_swap);
m_emu_settings->EnhanceCheckBox(ui->enable_upnp, emu_settings_type::EnableUpnp);
SubscribeTooltip(ui->enable_upnp, tooltips.settings.enable_upnp);
// Comboboxes
m_emu_settings->EnhanceComboBox(ui->netStatusBox, emu_settings_type::InternetStatus);
@ -1312,8 +1315,10 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std
connect(ui->netStatusBox, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index)
{
ui->edit_dns->setEnabled(index > 0);
ui->enable_upnp->setEnabled(index > 0);
});
ui->edit_dns->setEnabled(ui->netStatusBox->currentIndex() > 0);
ui->enable_upnp->setEnabled(ui->netStatusBox->currentIndex() > 0);
m_emu_settings->EnhanceComboBox(ui->psnStatusBox, emu_settings_type::PSNStatus);
SubscribeTooltip(ui->gb_psnStatusBox, tooltips.settings.psn_status);

View File

@ -2077,7 +2077,7 @@
<property name="title">
<string>Network Configuration</string>
</property>
<layout class="QVBoxLayout" name="gb_network_status_layout" stretch="0,0,0,0,0">
<layout class="QVBoxLayout" name="gb_network_status_layout" stretch="0,0,0,0,0,0">
<item>
<widget class="QGroupBox" name="gb_netStatusBox">
<property name="title">
@ -2147,6 +2147,13 @@
</layout>
</widget>
</item>
<item>
<widget class="QCheckBox" name="enable_upnp">
<property name="text">
<string>Enable UPNP</string>
</property>
</widget>
</item>
<item>
<spacer name="networkTabSpacerLeft">
<property name="orientation">

View File

@ -230,6 +230,7 @@ public:
const QString dns = tr("DNS used to resolve hostnames by applications.");
const QString dns_swap = tr("DNS Swap List.");
const QString bind = tr("Interface IP Address to bind to.");
const QString enable_upnp = tr("Enable UPNP.\nThis will automatically forward ports bound on 0.0.0.0 if your router has UPNP enabled.");
// system