mirror of
https://github.com/RPCSX/rpcsx.git
synced 2026-01-09 10:10:01 +01:00
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
1913 lines
43 KiB
C++
1913 lines
43 KiB
C++
#include "stdafx.h"
|
|
#include "sys_net.h"
|
|
|
|
#include "Emu/IdManager.h"
|
|
#include "Emu/Cell/PPUThread.h"
|
|
#include "Utilities/Thread.h"
|
|
|
|
#include "sys_sync.h"
|
|
|
|
#ifdef _WIN32
|
|
#include <winsock2.h>
|
|
#include <WS2tcpip.h>
|
|
#else
|
|
#ifdef __clang__
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
|
#endif
|
|
#include <errno.h>
|
|
#include <sys/time.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/ip.h>
|
|
#include <netinet/tcp.h>
|
|
#include <arpa/inet.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <poll.h>
|
|
#ifdef __clang__
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
#endif
|
|
|
|
#include "Emu/NP/np_handler.h"
|
|
#include "Emu/NP/np_helpers.h"
|
|
#include "Emu/NP/np_dnshook.h"
|
|
|
|
#include <chrono>
|
|
#include <shared_mutex>
|
|
|
|
#include "sys_net/network_context.h"
|
|
#include "sys_net/lv2_socket.h"
|
|
#include "sys_net/lv2_socket_native.h"
|
|
#include "sys_net/lv2_socket_raw.h"
|
|
#include "sys_net/lv2_socket_p2p.h"
|
|
#include "sys_net/lv2_socket_p2ps.h"
|
|
#include "sys_net/sys_net_helpers.h"
|
|
|
|
LOG_CHANNEL(sys_net);
|
|
LOG_CHANNEL(sys_net_dump);
|
|
|
|
template <>
|
|
void fmt_class_string<sys_net_error>::format(std::string& out, u64 arg)
|
|
{
|
|
format_enum(out, arg, [](auto error)
|
|
{
|
|
switch (s32 _error = error)
|
|
{
|
|
#define SYS_NET_ERROR_CASE(x) \
|
|
case -x: return "-" #x; \
|
|
case x: \
|
|
return #x
|
|
SYS_NET_ERROR_CASE(SYS_NET_ENOENT);
|
|
SYS_NET_ERROR_CASE(SYS_NET_EINTR);
|
|
SYS_NET_ERROR_CASE(SYS_NET_EBADF);
|
|
SYS_NET_ERROR_CASE(SYS_NET_ENOMEM);
|
|
SYS_NET_ERROR_CASE(SYS_NET_EACCES);
|
|
SYS_NET_ERROR_CASE(SYS_NET_EFAULT);
|
|
SYS_NET_ERROR_CASE(SYS_NET_EBUSY);
|
|
SYS_NET_ERROR_CASE(SYS_NET_EINVAL);
|
|
SYS_NET_ERROR_CASE(SYS_NET_EMFILE);
|
|
SYS_NET_ERROR_CASE(SYS_NET_ENOSPC);
|
|
SYS_NET_ERROR_CASE(SYS_NET_EPIPE);
|
|
SYS_NET_ERROR_CASE(SYS_NET_EAGAIN);
|
|
static_assert(SYS_NET_EWOULDBLOCK == SYS_NET_EAGAIN);
|
|
SYS_NET_ERROR_CASE(SYS_NET_EINPROGRESS);
|
|
SYS_NET_ERROR_CASE(SYS_NET_EALREADY);
|
|
SYS_NET_ERROR_CASE(SYS_NET_EDESTADDRREQ);
|
|
SYS_NET_ERROR_CASE(SYS_NET_EMSGSIZE);
|
|
SYS_NET_ERROR_CASE(SYS_NET_EPROTOTYPE);
|
|
SYS_NET_ERROR_CASE(SYS_NET_ENOPROTOOPT);
|
|
SYS_NET_ERROR_CASE(SYS_NET_EPROTONOSUPPORT);
|
|
SYS_NET_ERROR_CASE(SYS_NET_EOPNOTSUPP);
|
|
SYS_NET_ERROR_CASE(SYS_NET_EPFNOSUPPORT);
|
|
SYS_NET_ERROR_CASE(SYS_NET_EAFNOSUPPORT);
|
|
SYS_NET_ERROR_CASE(SYS_NET_EADDRINUSE);
|
|
SYS_NET_ERROR_CASE(SYS_NET_EADDRNOTAVAIL);
|
|
SYS_NET_ERROR_CASE(SYS_NET_ENETDOWN);
|
|
SYS_NET_ERROR_CASE(SYS_NET_ENETUNREACH);
|
|
SYS_NET_ERROR_CASE(SYS_NET_ECONNABORTED);
|
|
SYS_NET_ERROR_CASE(SYS_NET_ECONNRESET);
|
|
SYS_NET_ERROR_CASE(SYS_NET_ENOBUFS);
|
|
SYS_NET_ERROR_CASE(SYS_NET_EISCONN);
|
|
SYS_NET_ERROR_CASE(SYS_NET_ENOTCONN);
|
|
SYS_NET_ERROR_CASE(SYS_NET_ESHUTDOWN);
|
|
SYS_NET_ERROR_CASE(SYS_NET_ETOOMANYREFS);
|
|
SYS_NET_ERROR_CASE(SYS_NET_ETIMEDOUT);
|
|
SYS_NET_ERROR_CASE(SYS_NET_ECONNREFUSED);
|
|
SYS_NET_ERROR_CASE(SYS_NET_EHOSTDOWN);
|
|
SYS_NET_ERROR_CASE(SYS_NET_EHOSTUNREACH);
|
|
#undef SYS_NET_ERROR_CASE
|
|
}
|
|
|
|
return unknown;
|
|
});
|
|
}
|
|
|
|
template <>
|
|
void fmt_class_string<lv2_socket_type>::format(std::string& out, u64 arg)
|
|
{
|
|
format_enum(out, arg, [](auto value)
|
|
{
|
|
switch (value)
|
|
{
|
|
case SYS_NET_SOCK_STREAM: return "STREAM";
|
|
case SYS_NET_SOCK_DGRAM: return "DGRAM";
|
|
case SYS_NET_SOCK_RAW: return "RAW";
|
|
case SYS_NET_SOCK_DGRAM_P2P: return "DGRAM-P2P";
|
|
case SYS_NET_SOCK_STREAM_P2P: return "STREAM-P2P";
|
|
}
|
|
|
|
return unknown;
|
|
});
|
|
}
|
|
|
|
template <>
|
|
void fmt_class_string<lv2_socket_family>::format(std::string& out, u64 arg)
|
|
{
|
|
format_enum(out, arg, [](auto value)
|
|
{
|
|
switch (value)
|
|
{
|
|
case SYS_NET_AF_UNSPEC: return "UNSPEC";
|
|
case SYS_NET_AF_LOCAL: return "LOCAL";
|
|
case SYS_NET_AF_INET: return "INET";
|
|
case SYS_NET_AF_INET6: return "INET6";
|
|
}
|
|
|
|
return unknown;
|
|
});
|
|
}
|
|
|
|
template <>
|
|
void fmt_class_string<lv2_ip_protocol>::format(std::string& out, u64 arg)
|
|
{
|
|
format_enum(out, arg, [](auto value)
|
|
{
|
|
switch (value)
|
|
{
|
|
case SYS_NET_IPPROTO_IP: return "IPPROTO_IP";
|
|
case SYS_NET_IPPROTO_ICMP: return "IPPROTO_ICMP";
|
|
case SYS_NET_IPPROTO_IGMP: return "IPPROTO_IGMP";
|
|
case SYS_NET_IPPROTO_TCP: return "IPPROTO_TCP";
|
|
case SYS_NET_IPPROTO_UDP: return "IPPROTO_UDP";
|
|
case SYS_NET_IPPROTO_ICMPV6: return "IPPROTO_ICMPV6";
|
|
}
|
|
|
|
return unknown;
|
|
});
|
|
}
|
|
|
|
template <>
|
|
void fmt_class_string<lv2_tcp_option>::format(std::string& out, u64 arg)
|
|
{
|
|
format_enum(out, arg, [](auto value)
|
|
{
|
|
switch (value)
|
|
{
|
|
case SYS_NET_TCP_NODELAY: return "TCP_NODELAY";
|
|
case SYS_NET_TCP_MAXSEG: return "TCP_MAXSEG";
|
|
case SYS_NET_TCP_MSS_TO_ADVERTISE: return "TCP_MSS_TO_ADVERTISE";
|
|
}
|
|
|
|
return unknown;
|
|
});
|
|
}
|
|
|
|
template <>
|
|
void fmt_class_string<lv2_socket_option>::format(std::string& out, u64 arg)
|
|
{
|
|
format_enum(out, arg, [](auto value)
|
|
{
|
|
switch (value)
|
|
{
|
|
case SYS_NET_SO_SNDBUF: return "SO_SNDBUF";
|
|
case SYS_NET_SO_RCVBUF: return "SO_RCVBUF";
|
|
case SYS_NET_SO_SNDLOWAT: return "SO_SNDLOWAT";
|
|
case SYS_NET_SO_RCVLOWAT: return "SO_RCVLOWAT";
|
|
case SYS_NET_SO_SNDTIMEO: return "SO_SNDTIMEO";
|
|
case SYS_NET_SO_RCVTIMEO: return "SO_RCVTIMEO";
|
|
case SYS_NET_SO_ERROR: return "SO_ERROR";
|
|
case SYS_NET_SO_TYPE: return "SO_TYPE";
|
|
case SYS_NET_SO_NBIO: return "SO_NBIO";
|
|
case SYS_NET_SO_TPPOLICY: return "SO_TPPOLICY";
|
|
case SYS_NET_SO_REUSEADDR: return "SO_REUSEADDR";
|
|
case SYS_NET_SO_KEEPALIVE: return "SO_KEEPALIVE";
|
|
case SYS_NET_SO_BROADCAST: return "SO_BROADCAST";
|
|
case SYS_NET_SO_LINGER: return "SO_LINGER";
|
|
case SYS_NET_SO_OOBINLINE: return "SO_OOBINLINE";
|
|
case SYS_NET_SO_REUSEPORT: return "SO_REUSEPORT";
|
|
case SYS_NET_SO_ONESBCAST: return "SO_ONESBCAST";
|
|
case SYS_NET_SO_USECRYPTO: return "SO_USECRYPTO";
|
|
case SYS_NET_SO_USESIGNATURE: return "SO_USESIGNATURE";
|
|
case SYS_NET_SOL_SOCKET: return "SOL_SOCKET";
|
|
}
|
|
|
|
return unknown;
|
|
});
|
|
}
|
|
|
|
template <>
|
|
void fmt_class_string<lv2_ip_option>::format(std::string& out, u64 arg)
|
|
{
|
|
format_enum(out, arg, [](auto value)
|
|
{
|
|
switch (value)
|
|
{
|
|
case SYS_NET_IP_HDRINCL: return "IP_HDRINCL";
|
|
case SYS_NET_IP_TOS: return "IP_TOS";
|
|
case SYS_NET_IP_TTL: return "IP_TTL";
|
|
case SYS_NET_IP_MULTICAST_IF: return "IP_MULTICAST_IF";
|
|
case SYS_NET_IP_MULTICAST_TTL: return "IP_MULTICAST_TTL";
|
|
case SYS_NET_IP_MULTICAST_LOOP: return "IP_MULTICAST_LOOP";
|
|
case SYS_NET_IP_ADD_MEMBERSHIP: return "IP_ADD_MEMBERSHIP";
|
|
case SYS_NET_IP_DROP_MEMBERSHIP: return "IP_DROP_MEMBERSHIP";
|
|
case SYS_NET_IP_TTLCHK: return "IP_TTLCHK";
|
|
case SYS_NET_IP_MAXTTL: return "IP_MAXTTL";
|
|
case SYS_NET_IP_DONTFRAG: return "IP_DONTFRAG";
|
|
}
|
|
|
|
return unknown;
|
|
});
|
|
}
|
|
|
|
template <>
|
|
void fmt_class_string<struct in_addr>::format(std::string& out, u64 arg)
|
|
{
|
|
const u8* data = reinterpret_cast<const u8*>(&get_object(arg));
|
|
|
|
fmt::append(out, "%u.%u.%u.%u", data[0], data[1], data[2], data[3]);
|
|
}
|
|
|
|
lv2_socket::lv2_socket(utils::serial& ar, lv2_socket_type _type)
|
|
: family(ar)
|
|
, type(_type)
|
|
, protocol(ar)
|
|
, so_nbio(ar)
|
|
, so_error(ar)
|
|
, so_tcp_maxseg(ar)
|
|
#ifdef _WIN32
|
|
, so_reuseaddr(ar)
|
|
, so_reuseport(ar)
|
|
{
|
|
#else
|
|
{
|
|
// Try to match structure between different platforms
|
|
ar.pos += 8;
|
|
#endif
|
|
|
|
const s32 version = GET_SERIALIZATION_VERSION(lv2_net);
|
|
|
|
if (version >= 2)
|
|
{
|
|
ar(so_rcvtimeo, so_sendtimeo);
|
|
}
|
|
|
|
lv2_id = idm::last_id();
|
|
|
|
ar(last_bound_addr);
|
|
}
|
|
|
|
std::shared_ptr<lv2_socket> lv2_socket::load(utils::serial& ar)
|
|
{
|
|
const lv2_socket_type type{ar};
|
|
|
|
std::shared_ptr<lv2_socket> sock_lv2;
|
|
|
|
switch (type)
|
|
{
|
|
case SYS_NET_SOCK_STREAM:
|
|
case SYS_NET_SOCK_DGRAM:
|
|
{
|
|
auto lv2_native = std::make_shared<lv2_socket_native>(ar, type);
|
|
ensure(lv2_native->create_socket() >= 0);
|
|
sock_lv2 = std::move(lv2_native);
|
|
break;
|
|
}
|
|
case SYS_NET_SOCK_RAW: sock_lv2 = std::make_shared<lv2_socket_raw>(ar, type); break;
|
|
case SYS_NET_SOCK_DGRAM_P2P: sock_lv2 = std::make_shared<lv2_socket_p2p>(ar, type); break;
|
|
case SYS_NET_SOCK_STREAM_P2P: sock_lv2 = std::make_shared<lv2_socket_p2ps>(ar, type); break;
|
|
}
|
|
|
|
if (std::memcmp(&sock_lv2->last_bound_addr, std::array<u8, 16>{}.data(), 16))
|
|
{
|
|
// NOTE: It is allowed fail
|
|
sock_lv2->bind(sock_lv2->last_bound_addr);
|
|
}
|
|
|
|
return sock_lv2;
|
|
}
|
|
|
|
void lv2_socket::save(utils::serial& ar, bool save_only_this_class)
|
|
{
|
|
USING_SERIALIZATION_VERSION(lv2_net);
|
|
|
|
if (save_only_this_class)
|
|
{
|
|
ar(family, protocol, so_nbio, so_error, so_tcp_maxseg);
|
|
#ifdef _WIN32
|
|
ar(so_reuseaddr, so_reuseport);
|
|
#else
|
|
ar(std::array<char, 8>{});
|
|
#endif
|
|
ar(so_rcvtimeo, so_sendtimeo);
|
|
ar(last_bound_addr);
|
|
return;
|
|
}
|
|
|
|
ar(type);
|
|
|
|
switch (type)
|
|
{
|
|
case SYS_NET_SOCK_STREAM:
|
|
case SYS_NET_SOCK_DGRAM:
|
|
{
|
|
static_cast<lv2_socket_native*>(this)->save(ar);
|
|
break;
|
|
}
|
|
case SYS_NET_SOCK_RAW: static_cast<lv2_socket_raw*>(this)->save(ar); break;
|
|
case SYS_NET_SOCK_DGRAM_P2P: static_cast<lv2_socket_p2p*>(this)->save(ar); break;
|
|
case SYS_NET_SOCK_STREAM_P2P: static_cast<lv2_socket_p2ps*>(this)->save(ar); break;
|
|
}
|
|
}
|
|
|
|
void sys_net_dump_data(std::string_view desc, const u8* data, s32 len)
|
|
{
|
|
if (sys_net_dump.trace)
|
|
{
|
|
auto data_dump = fmt::format("%s:\n", desc);
|
|
const char hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
|
|
|
|
for (s32 index = 0; index < len; index++)
|
|
{
|
|
if ((index % 16) == 0)
|
|
data_dump += '\n';
|
|
|
|
data_dump += hex[(data[index] >> 4) & 15];
|
|
data_dump += hex[(data[index]) & 15];
|
|
data_dump += ' ';
|
|
}
|
|
sys_net.trace("%s", data_dump);
|
|
}
|
|
}
|
|
|
|
error_code sys_net_bnet_accept(ppu_thread& ppu, s32 s, vm::ptr<sys_net_sockaddr> addr, vm::ptr<u32> paddrlen)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_net.warning("sys_net_bnet_accept(s=%d, addr=*0x%x, paddrlen=*0x%x)", s, addr, paddrlen);
|
|
|
|
if (addr.operator bool() != paddrlen.operator bool() || (paddrlen && *paddrlen < addr.size()))
|
|
{
|
|
return -SYS_NET_EINVAL;
|
|
}
|
|
|
|
s32 result = 0;
|
|
sys_net_sockaddr sn_addr{};
|
|
std::shared_ptr<lv2_socket> new_socket{};
|
|
|
|
const auto sock = idm::check<lv2_socket>(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock)
|
|
{
|
|
const auto [success, res, res_socket, res_addr] = sock.accept();
|
|
|
|
if (success)
|
|
{
|
|
result = res;
|
|
sn_addr = res_addr;
|
|
new_socket = std::move(res_socket);
|
|
return true;
|
|
}
|
|
|
|
auto lock = sock.lock();
|
|
|
|
sock.poll_queue(idm::get_unlocked<named_thread<ppu_thread>>(ppu.id), lv2_socket::poll_t::read, [&](bs_t<lv2_socket::poll_t> events) -> bool
|
|
{
|
|
if (events & lv2_socket::poll_t::read)
|
|
{
|
|
auto [success, res, res_socket, res_addr] = sock.accept(false);
|
|
if (success)
|
|
{
|
|
result = res;
|
|
sn_addr = res_addr;
|
|
new_socket = std::move(res_socket);
|
|
lv2_obj::awake(&ppu);
|
|
return success;
|
|
}
|
|
}
|
|
|
|
sock.set_poll_event(lv2_socket::poll_t::read);
|
|
return false;
|
|
});
|
|
|
|
lv2_obj::prepare_for_sleep(ppu);
|
|
lv2_obj::sleep(ppu);
|
|
return false;
|
|
});
|
|
|
|
if (!sock)
|
|
{
|
|
return -SYS_NET_EBADF;
|
|
}
|
|
|
|
if (!sock.ret)
|
|
{
|
|
while (auto state = ppu.state.fetch_sub(cpu_flag::signal))
|
|
{
|
|
if (is_stopped(state))
|
|
{
|
|
return {};
|
|
}
|
|
|
|
if (state & cpu_flag::signal)
|
|
{
|
|
break;
|
|
}
|
|
|
|
ppu.state.wait(state);
|
|
}
|
|
|
|
if (ppu.gpr[3] == static_cast<u64>(-SYS_NET_EINTR))
|
|
{
|
|
return -SYS_NET_EINTR;
|
|
}
|
|
|
|
if (result < 0)
|
|
{
|
|
return sys_net_error{result};
|
|
}
|
|
}
|
|
|
|
if (result < 0)
|
|
{
|
|
return sys_net_error{result};
|
|
}
|
|
|
|
s32 id_ps3 = result;
|
|
|
|
if (!id_ps3)
|
|
{
|
|
ensure(new_socket);
|
|
id_ps3 = idm::import_existing<lv2_socket>(new_socket);
|
|
if (id_ps3 == id_manager::id_traits<lv2_socket>::invalid)
|
|
{
|
|
return -SYS_NET_EMFILE;
|
|
}
|
|
}
|
|
|
|
static_cast<void>(ppu.test_stopped());
|
|
|
|
if (addr)
|
|
{
|
|
*paddrlen = sizeof(sys_net_sockaddr_in);
|
|
*addr = sn_addr;
|
|
}
|
|
|
|
// Socket ID
|
|
return not_an_error(id_ps3);
|
|
}
|
|
|
|
error_code sys_net_bnet_bind(ppu_thread& ppu, s32 s, vm::cptr<sys_net_sockaddr> addr, u32 addrlen)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_net.warning("sys_net_bnet_bind(s=%d, addr=*0x%x, addrlen=%u)", s, addr, addrlen);
|
|
|
|
if (!addr || addrlen < addr.size())
|
|
{
|
|
return -SYS_NET_EINVAL;
|
|
}
|
|
|
|
if (!idm::check<lv2_socket>(s))
|
|
{
|
|
return -SYS_NET_EBADF;
|
|
}
|
|
|
|
const sys_net_sockaddr sn_addr = *addr;
|
|
|
|
// 0 presumably defaults to AF_INET(to check?)
|
|
if (sn_addr.sa_family != SYS_NET_AF_INET && sn_addr.sa_family != SYS_NET_AF_UNSPEC)
|
|
{
|
|
sys_net.error("sys_net_bnet_bind: unsupported sa_family (%d)", sn_addr.sa_family);
|
|
return -SYS_NET_EAFNOSUPPORT;
|
|
}
|
|
|
|
const auto sock = idm::check<lv2_socket>(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) -> s32
|
|
{
|
|
return sock.bind(sn_addr);
|
|
});
|
|
|
|
if (!sock)
|
|
{
|
|
return -SYS_NET_EBADF;
|
|
}
|
|
|
|
if (sock.ret)
|
|
{
|
|
return sys_net_error{sock.ret};
|
|
}
|
|
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code sys_net_bnet_connect(ppu_thread& ppu, s32 s, vm::ptr<sys_net_sockaddr> addr, u32 addrlen)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_net.warning("sys_net_bnet_connect(s=%d, addr=*0x%x, addrlen=%u)", s, addr, addrlen);
|
|
|
|
if (!addr || addrlen < addr.size())
|
|
{
|
|
return -SYS_NET_EINVAL;
|
|
}
|
|
|
|
if (addr->sa_family != SYS_NET_AF_INET)
|
|
{
|
|
sys_net.error("sys_net_bnet_connect(s=%d): unsupported sa_family (%d)", s, addr->sa_family);
|
|
return -SYS_NET_EAFNOSUPPORT;
|
|
}
|
|
|
|
if (!idm::check<lv2_socket>(s))
|
|
{
|
|
return -SYS_NET_EBADF;
|
|
}
|
|
|
|
s32 result = 0;
|
|
sys_net_sockaddr sn_addr = *addr;
|
|
|
|
const auto sock = idm::check<lv2_socket>(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock)
|
|
{
|
|
const auto success = sock.connect(sn_addr);
|
|
|
|
if (success)
|
|
{
|
|
result = *success;
|
|
return true;
|
|
}
|
|
|
|
auto lock = sock.lock();
|
|
|
|
sock.poll_queue(idm::get_unlocked<named_thread<ppu_thread>>(ppu.id), lv2_socket::poll_t::write, [&](bs_t<lv2_socket::poll_t> events) -> bool
|
|
{
|
|
if (events & lv2_socket::poll_t::write)
|
|
{
|
|
result = sock.connect_followup();
|
|
|
|
lv2_obj::awake(&ppu);
|
|
return true;
|
|
}
|
|
sock.set_poll_event(lv2_socket::poll_t::write);
|
|
return false;
|
|
});
|
|
|
|
lv2_obj::prepare_for_sleep(ppu);
|
|
lv2_obj::sleep(ppu);
|
|
return false;
|
|
});
|
|
|
|
if (!sock)
|
|
{
|
|
return -SYS_NET_EBADF;
|
|
}
|
|
|
|
if (sock.ret)
|
|
{
|
|
if (result < 0)
|
|
{
|
|
return sys_net_error{result};
|
|
}
|
|
|
|
return not_an_error(result);
|
|
}
|
|
|
|
if (!sock.ret)
|
|
{
|
|
while (auto state = ppu.state.fetch_sub(cpu_flag::signal))
|
|
{
|
|
if (is_stopped(state))
|
|
{
|
|
return {};
|
|
}
|
|
|
|
if (state & cpu_flag::signal)
|
|
{
|
|
break;
|
|
}
|
|
|
|
ppu.state.wait(state);
|
|
}
|
|
|
|
if (ppu.gpr[3] == static_cast<u64>(-SYS_NET_EINTR))
|
|
{
|
|
return -SYS_NET_EINTR;
|
|
}
|
|
|
|
if (result)
|
|
{
|
|
if (result < 0)
|
|
{
|
|
return sys_net_error{result};
|
|
}
|
|
|
|
return not_an_error(result);
|
|
}
|
|
}
|
|
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code sys_net_bnet_getpeername(ppu_thread& ppu, s32 s, vm::ptr<sys_net_sockaddr> addr, vm::ptr<u32> paddrlen)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_net.warning("sys_net_bnet_getpeername(s=%d, addr=*0x%x, paddrlen=*0x%x)", s, addr, paddrlen);
|
|
|
|
// Note: paddrlen is both an input and output argument
|
|
if (!addr || !paddrlen || *paddrlen < addr.size())
|
|
{
|
|
return -SYS_NET_EINVAL;
|
|
}
|
|
|
|
const auto sock = idm::check<lv2_socket>(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) -> s32
|
|
{
|
|
auto [res, sn_addr] = sock.getpeername();
|
|
|
|
if (res == CELL_OK)
|
|
{
|
|
*paddrlen = sizeof(sys_net_sockaddr);
|
|
*addr = sn_addr;
|
|
}
|
|
|
|
return res;
|
|
});
|
|
|
|
if (!sock)
|
|
{
|
|
return -SYS_NET_EBADF;
|
|
}
|
|
|
|
if (sock.ret < 0)
|
|
{
|
|
return sys_net_error{sock.ret};
|
|
}
|
|
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code sys_net_bnet_getsockname(ppu_thread& ppu, s32 s, vm::ptr<sys_net_sockaddr> addr, vm::ptr<u32> paddrlen)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_net.warning("sys_net_bnet_getsockname(s=%d, addr=*0x%x, paddrlen=*0x%x)", s, addr, paddrlen);
|
|
|
|
// Note: paddrlen is both an input and output argument
|
|
if (!addr || !paddrlen || *paddrlen < addr.size())
|
|
{
|
|
return -SYS_NET_EINVAL;
|
|
}
|
|
|
|
const auto sock = idm::check<lv2_socket>(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) -> s32
|
|
{
|
|
auto [res, sn_addr] = sock.getsockname();
|
|
|
|
if (res == CELL_OK)
|
|
{
|
|
*paddrlen = sizeof(sys_net_sockaddr);
|
|
*addr = sn_addr;
|
|
}
|
|
|
|
return res;
|
|
});
|
|
|
|
if (!sock)
|
|
{
|
|
return -SYS_NET_EBADF;
|
|
}
|
|
|
|
if (sock.ret < 0)
|
|
{
|
|
return sys_net_error{sock.ret};
|
|
}
|
|
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code sys_net_bnet_getsockopt(ppu_thread& ppu, s32 s, s32 level, s32 optname, vm::ptr<void> optval, vm::ptr<u32> optlen)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
switch (level)
|
|
{
|
|
case SYS_NET_SOL_SOCKET:
|
|
sys_net.warning("sys_net_bnet_getsockopt(s=%d, level=SYS_NET_SOL_SOCKET, optname=%s, optval=*0x%x, optlen=%u)", s, static_cast<lv2_socket_option>(optname), optval, optlen);
|
|
break;
|
|
case SYS_NET_IPPROTO_TCP:
|
|
sys_net.warning("sys_net_bnet_getsockopt(s=%d, level=SYS_NET_IPPROTO_TCP, optname=%s, optval=*0x%x, optlen=%u)", s, static_cast<lv2_tcp_option>(optname), optval, optlen);
|
|
break;
|
|
case SYS_NET_IPPROTO_IP:
|
|
sys_net.warning("sys_net_bnet_getsockopt(s=%d, level=SYS_NET_IPPROTO_IP, optname=%s, optval=*0x%x, optlen=%u)", s, static_cast<lv2_ip_option>(optname), optval, optlen);
|
|
break;
|
|
default:
|
|
sys_net.warning("sys_net_bnet_getsockopt(s=%d, level=0x%x, optname=0x%x, optval=*0x%x, optlen=%u)", s, level, optname, optval, optlen);
|
|
break;
|
|
}
|
|
|
|
if (!optval || !optlen)
|
|
{
|
|
return -SYS_NET_EINVAL;
|
|
}
|
|
|
|
const u32 len = *optlen;
|
|
|
|
if (!len)
|
|
{
|
|
return -SYS_NET_EINVAL;
|
|
}
|
|
|
|
const auto sock = idm::check<lv2_socket>(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) -> s32
|
|
{
|
|
if (len < sizeof(s32))
|
|
{
|
|
return -SYS_NET_EINVAL;
|
|
}
|
|
|
|
const auto [res, out_val, out_len] = sock.getsockopt(level, optname, *optlen);
|
|
|
|
if (res == CELL_OK)
|
|
{
|
|
std::memcpy(optval.get_ptr(), out_val.ch, out_len);
|
|
*optlen = out_len;
|
|
}
|
|
|
|
return res;
|
|
});
|
|
|
|
if (!sock)
|
|
{
|
|
return -SYS_NET_EBADF;
|
|
}
|
|
|
|
if (sock.ret < 0)
|
|
{
|
|
return sys_net_error{sock.ret};
|
|
}
|
|
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code sys_net_bnet_listen(ppu_thread& ppu, s32 s, s32 backlog)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_net.warning("sys_net_bnet_listen(s=%d, backlog=%d)", s, backlog);
|
|
|
|
if (backlog <= 0)
|
|
{
|
|
return -SYS_NET_EINVAL;
|
|
}
|
|
|
|
const auto sock = idm::check<lv2_socket>(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) -> s32
|
|
{
|
|
return sock.listen(backlog);
|
|
});
|
|
|
|
if (!sock)
|
|
{
|
|
return -SYS_NET_EBADF;
|
|
}
|
|
|
|
if (sock.ret < 0)
|
|
{
|
|
return sys_net_error{sock.ret};
|
|
}
|
|
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code sys_net_bnet_recvfrom(ppu_thread& ppu, s32 s, vm::ptr<void> buf, u32 len, s32 flags, vm::ptr<sys_net_sockaddr> addr, vm::ptr<u32> paddrlen)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_net.warning("sys_net_bnet_recvfrom(s=%d, buf=*0x%x, len=%u, flags=0x%x, addr=*0x%x, paddrlen=*0x%x)", s, buf, len, flags, addr, paddrlen);
|
|
|
|
// If addr is null, paddrlen must be null as well
|
|
if (!buf || !len || addr.operator bool() != paddrlen.operator bool())
|
|
{
|
|
return -SYS_NET_EINVAL;
|
|
}
|
|
|
|
if (flags & ~(SYS_NET_MSG_PEEK | SYS_NET_MSG_DONTWAIT | SYS_NET_MSG_WAITALL | SYS_NET_MSG_USECRYPTO | SYS_NET_MSG_USESIGNATURE))
|
|
{
|
|
fmt::throw_exception("sys_net_bnet_recvfrom(s=%d): unknown flags (0x%x)", flags);
|
|
}
|
|
|
|
s32 result = 0;
|
|
sys_net_sockaddr sn_addr{};
|
|
|
|
const auto sock = idm::check<lv2_socket>(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock)
|
|
{
|
|
const auto success = sock.recvfrom(flags, len);
|
|
|
|
if (success)
|
|
{
|
|
const auto& [res, vec, res_addr] = *success;
|
|
if (res > 0)
|
|
{
|
|
sn_addr = res_addr;
|
|
std::memcpy(buf.get_ptr(), vec.data(), res);
|
|
sys_net_dump_data("recvfrom", vec.data(), res);
|
|
}
|
|
|
|
result = res;
|
|
return true;
|
|
}
|
|
|
|
auto lock = sock.lock();
|
|
|
|
sock.poll_queue(idm::get_unlocked<named_thread<ppu_thread>>(ppu.id), lv2_socket::poll_t::read, [&](bs_t<lv2_socket::poll_t> events) -> bool
|
|
{
|
|
if (events & lv2_socket::poll_t::read)
|
|
{
|
|
const auto success = sock.recvfrom(flags, len, false);
|
|
|
|
if (success)
|
|
{
|
|
const auto& [res, vec, res_addr] = *success;
|
|
if (res > 0)
|
|
{
|
|
sn_addr = res_addr;
|
|
std::memcpy(buf.get_ptr(), vec.data(), res);
|
|
sys_net_dump_data("recvfrom", vec.data(), res);
|
|
}
|
|
result = res;
|
|
lv2_obj::awake(&ppu);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (sock.so_rcvtimeo && get_guest_system_time() - ppu.start_time > sock.so_rcvtimeo)
|
|
{
|
|
result = -SYS_NET_EWOULDBLOCK;
|
|
lv2_obj::awake(&ppu);
|
|
return true;
|
|
}
|
|
|
|
sock.set_poll_event(lv2_socket::poll_t::read);
|
|
return false;
|
|
});
|
|
|
|
lv2_obj::prepare_for_sleep(ppu);
|
|
lv2_obj::sleep(ppu);
|
|
return false;
|
|
});
|
|
|
|
if (!sock)
|
|
{
|
|
return -SYS_NET_EBADF;
|
|
}
|
|
|
|
if (!sock.ret)
|
|
{
|
|
|
|
while (auto state = ppu.state.fetch_sub(cpu_flag::signal))
|
|
{
|
|
if (is_stopped(state))
|
|
{
|
|
return {};
|
|
}
|
|
|
|
if (state & cpu_flag::signal)
|
|
{
|
|
break;
|
|
}
|
|
|
|
ppu.state.wait(state);
|
|
}
|
|
|
|
if (ppu.gpr[3] == static_cast<u64>(-SYS_NET_EINTR))
|
|
{
|
|
return -SYS_NET_EINTR;
|
|
}
|
|
}
|
|
|
|
static_cast<void>(ppu.test_stopped());
|
|
|
|
if (result == -SYS_NET_EWOULDBLOCK)
|
|
{
|
|
return not_an_error(result);
|
|
}
|
|
|
|
if (result >= 0)
|
|
{
|
|
if (addr)
|
|
{
|
|
*paddrlen = sizeof(sys_net_sockaddr_in);
|
|
*addr = sn_addr;
|
|
}
|
|
|
|
return not_an_error(result);
|
|
}
|
|
|
|
return sys_net_error{result};
|
|
}
|
|
|
|
error_code sys_net_bnet_recvmsg(ppu_thread& ppu, s32 s, vm::ptr<sys_net_msghdr> msg, s32 flags)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_net.todo("sys_net_bnet_recvmsg(s=%d, msg=*0x%x, flags=0x%x)", s, msg, flags);
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code sys_net_bnet_sendmsg(ppu_thread& ppu, s32 s, vm::cptr<sys_net_msghdr> msg, s32 flags)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_net.warning("sys_net_bnet_sendmsg(s=%d, msg=*0x%x, flags=0x%x)", s, msg, flags);
|
|
|
|
if (flags & ~(SYS_NET_MSG_DONTWAIT | SYS_NET_MSG_WAITALL | SYS_NET_MSG_USECRYPTO | SYS_NET_MSG_USESIGNATURE))
|
|
{
|
|
fmt::throw_exception("sys_net_bnet_sendmsg(s=%d): unknown flags (0x%x)", flags);
|
|
}
|
|
|
|
s32 result{};
|
|
|
|
const auto sock = idm::check<lv2_socket>(s, [&](lv2_socket& sock)
|
|
{
|
|
auto netmsg = msg.get_ptr();
|
|
const auto success = sock.sendmsg(flags, *netmsg);
|
|
|
|
if (success)
|
|
{
|
|
result = *success;
|
|
|
|
return true;
|
|
}
|
|
|
|
sock.poll_queue(idm::get_unlocked<named_thread<ppu_thread>>(ppu.id), lv2_socket::poll_t::write, [&](bs_t<lv2_socket::poll_t> events) -> bool
|
|
{
|
|
if (events & lv2_socket::poll_t::write)
|
|
{
|
|
const auto success = sock.sendmsg(flags, *netmsg, false);
|
|
|
|
if (success)
|
|
{
|
|
result = *success;
|
|
lv2_obj::awake(&ppu);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
sock.set_poll_event(lv2_socket::poll_t::write);
|
|
return false;
|
|
});
|
|
|
|
lv2_obj::sleep(ppu);
|
|
return false;
|
|
});
|
|
|
|
if (!sock)
|
|
{
|
|
return -SYS_NET_EBADF;
|
|
}
|
|
|
|
if (!sock.ret)
|
|
{
|
|
while (true)
|
|
{
|
|
const auto state = ppu.state.fetch_sub(cpu_flag::signal);
|
|
if (is_stopped(state) || state & cpu_flag::signal)
|
|
{
|
|
break;
|
|
}
|
|
thread_ctrl::wait_on(ppu.state, state);
|
|
}
|
|
|
|
if (ppu.gpr[3] == static_cast<u64>(-SYS_NET_EINTR))
|
|
{
|
|
return -SYS_NET_EINTR;
|
|
}
|
|
}
|
|
|
|
if (result >= 0 || result == -SYS_NET_EWOULDBLOCK)
|
|
{
|
|
return not_an_error(result);
|
|
}
|
|
|
|
|
|
return sys_net_error{result};
|
|
}
|
|
|
|
error_code sys_net_bnet_sendto(ppu_thread& ppu, s32 s, vm::cptr<void> buf, u32 len, s32 flags, vm::cptr<sys_net_sockaddr> addr, u32 addrlen)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_net.warning("sys_net_bnet_sendto(s=%d, buf=*0x%x, len=%u, flags=0x%x, addr=*0x%x, addrlen=%u)", s, buf, len, flags, addr, addrlen);
|
|
|
|
if (flags & ~(SYS_NET_MSG_DONTWAIT | SYS_NET_MSG_WAITALL | SYS_NET_MSG_USECRYPTO | SYS_NET_MSG_USESIGNATURE))
|
|
{
|
|
fmt::throw_exception("sys_net_bnet_sendto(s=%d): unknown flags (0x%x)", flags);
|
|
}
|
|
|
|
if (addr && addrlen < 8)
|
|
{
|
|
sys_net.error("sys_net_bnet_sendto(s=%d): bad addrlen (%u)", s, addrlen);
|
|
return -SYS_NET_EINVAL;
|
|
}
|
|
|
|
if (addr && addr->sa_family != SYS_NET_AF_INET)
|
|
{
|
|
sys_net.error("sys_net_bnet_sendto(s=%d): unsupported sa_family (%d)", s, addr->sa_family);
|
|
return -SYS_NET_EAFNOSUPPORT;
|
|
}
|
|
|
|
sys_net_dump_data("sendto", static_cast<const u8 *>(buf.get_ptr()), len);
|
|
|
|
const std::optional<sys_net_sockaddr> sn_addr = addr ? std::optional<sys_net_sockaddr>(*addr) : std::nullopt;
|
|
const std::vector<u8> buf_copy(vm::_ptr<const char>(buf.addr()), vm::_ptr<const char>(buf.addr()) + len);
|
|
s32 result{};
|
|
|
|
const auto sock = idm::check<lv2_socket>(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock)
|
|
{
|
|
auto success = sock.sendto(flags, buf_copy, sn_addr);
|
|
|
|
if (success)
|
|
{
|
|
result = *success;
|
|
return true;
|
|
}
|
|
|
|
auto lock = sock.lock();
|
|
|
|
// Enable write event
|
|
sock.poll_queue(idm::get_unlocked<named_thread<ppu_thread>>(ppu.id), lv2_socket::poll_t::write, [&](bs_t<lv2_socket::poll_t> events) -> bool
|
|
{
|
|
if (events & lv2_socket::poll_t::write)
|
|
{
|
|
auto success = sock.sendto(flags, buf_copy, sn_addr, false);
|
|
if (success)
|
|
{
|
|
result = *success;
|
|
lv2_obj::awake(&ppu);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (sock.so_sendtimeo && get_guest_system_time() - ppu.start_time > sock.so_sendtimeo)
|
|
{
|
|
result = -SYS_NET_EWOULDBLOCK;
|
|
lv2_obj::awake(&ppu);
|
|
return true;
|
|
}
|
|
|
|
sock.set_poll_event(lv2_socket::poll_t::write);
|
|
return false;
|
|
});
|
|
|
|
lv2_obj::prepare_for_sleep(ppu);
|
|
lv2_obj::sleep(ppu);
|
|
return false;
|
|
});
|
|
|
|
if (!sock)
|
|
{
|
|
return -SYS_NET_EBADF;
|
|
}
|
|
|
|
if (!sock.ret)
|
|
{
|
|
while (true)
|
|
{
|
|
const auto state = ppu.state.fetch_sub(cpu_flag::signal);
|
|
if (is_stopped(state) || state & cpu_flag::signal)
|
|
{
|
|
break;
|
|
}
|
|
ppu.state.wait(state);
|
|
}
|
|
|
|
if (ppu.gpr[3] == static_cast<u64>(-SYS_NET_EINTR))
|
|
{
|
|
return -SYS_NET_EINTR;
|
|
}
|
|
}
|
|
|
|
if (result >= 0 || result == -SYS_NET_EWOULDBLOCK)
|
|
{
|
|
return not_an_error(result);
|
|
}
|
|
|
|
return sys_net_error{result};
|
|
}
|
|
|
|
error_code sys_net_bnet_setsockopt(ppu_thread& ppu, s32 s, s32 level, s32 optname, vm::cptr<void> optval, u32 optlen)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
switch (level)
|
|
{
|
|
case SYS_NET_SOL_SOCKET:
|
|
sys_net.warning("sys_net_bnet_setsockopt(s=%d, level=SYS_NET_SOL_SOCKET, optname=%s, optval=*0x%x, optlen=%u)", s, static_cast<lv2_socket_option>(optname), optval, optlen);
|
|
break;
|
|
case SYS_NET_IPPROTO_TCP:
|
|
sys_net.warning("sys_net_bnet_setsockopt(s=%d, level=SYS_NET_IPPROTO_TCP, optname=%s, optval=*0x%x, optlen=%u)", s, static_cast<lv2_tcp_option>(optname), optval, optlen);
|
|
break;
|
|
case SYS_NET_IPPROTO_IP:
|
|
sys_net.warning("sys_net_bnet_setsockopt(s=%d, level=SYS_NET_IPPROTO_IP, optname=%s, optval=*0x%x, optlen=%u)", s, static_cast<lv2_ip_option>(optname), optval, optlen);
|
|
break;
|
|
default:
|
|
sys_net.warning("sys_net_bnet_setsockopt(s=%d, level=0x%x, optname=0x%x, optval=*0x%x, optlen=%u)", s, level, optname, optval, optlen);
|
|
break;
|
|
}
|
|
|
|
switch (optlen)
|
|
{
|
|
case 1:
|
|
sys_net.warning("optval: 0x%02X", *static_cast<const u8*>(optval.get_ptr()));
|
|
break;
|
|
case 2:
|
|
sys_net.warning("optval: 0x%04X", *static_cast<const be_t<u16>*>(optval.get_ptr()));
|
|
break;
|
|
case 4:
|
|
sys_net.warning("optval: 0x%08X", *static_cast<const be_t<u32>*>(optval.get_ptr()));
|
|
break;
|
|
case 8:
|
|
sys_net.warning("optval: 0x%016X", *static_cast<const be_t<u64>*>(optval.get_ptr()));
|
|
break;
|
|
}
|
|
|
|
if (optlen < sizeof(s32))
|
|
{
|
|
return -SYS_NET_EINVAL;
|
|
}
|
|
|
|
std::vector<u8> optval_copy(vm::_ptr<u8>(optval.addr()), vm::_ptr<u8>(optval.addr() + optlen));
|
|
|
|
const auto sock = idm::check<lv2_socket>(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) -> s32
|
|
{
|
|
return sock.setsockopt(level, optname, optval_copy);
|
|
});
|
|
|
|
if (!sock)
|
|
{
|
|
return -SYS_NET_EBADF;
|
|
}
|
|
|
|
if (sock.ret < 0)
|
|
{
|
|
return sys_net_error{sock.ret};
|
|
}
|
|
|
|
return not_an_error(sock.ret);
|
|
}
|
|
|
|
error_code sys_net_bnet_shutdown(ppu_thread& ppu, s32 s, s32 how)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_net.warning("sys_net_bnet_shutdown(s=%d, how=%d)", s, how);
|
|
|
|
if (how < 0 || how > 2)
|
|
{
|
|
return -SYS_NET_EINVAL;
|
|
}
|
|
|
|
const auto sock = idm::check<lv2_socket>(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) -> s32
|
|
{
|
|
return sock.shutdown(how);
|
|
});
|
|
|
|
if (!sock)
|
|
{
|
|
return -SYS_NET_EBADF;
|
|
}
|
|
|
|
if (sock.ret < 0)
|
|
{
|
|
return sys_net_error{sock.ret};
|
|
}
|
|
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code sys_net_bnet_socket(ppu_thread& ppu, lv2_socket_family family, lv2_socket_type type, lv2_ip_protocol protocol)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_net.warning("sys_net_bnet_socket(family=%s, type=%s, protocol=%s)", family, type, protocol);
|
|
|
|
if (family != SYS_NET_AF_INET)
|
|
{
|
|
sys_net.error("sys_net_bnet_socket(): unknown family (%d)", family);
|
|
}
|
|
|
|
if (type != SYS_NET_SOCK_STREAM && type != SYS_NET_SOCK_DGRAM && type != SYS_NET_SOCK_RAW && type != SYS_NET_SOCK_DGRAM_P2P && type != SYS_NET_SOCK_STREAM_P2P)
|
|
{
|
|
sys_net.error("sys_net_bnet_socket(): unsupported type (%d)", type);
|
|
return -SYS_NET_EPROTONOSUPPORT;
|
|
}
|
|
|
|
std::shared_ptr<lv2_socket> sock_lv2;
|
|
|
|
switch (type)
|
|
{
|
|
case SYS_NET_SOCK_STREAM:
|
|
case SYS_NET_SOCK_DGRAM:
|
|
{
|
|
auto lv2_native = std::make_shared<lv2_socket_native>(family, type, protocol);
|
|
if (s32 result = lv2_native->create_socket(); result < 0)
|
|
{
|
|
return sys_net_error{result};
|
|
}
|
|
|
|
sock_lv2 = std::move(lv2_native);
|
|
break;
|
|
}
|
|
case SYS_NET_SOCK_RAW: sock_lv2 = std::make_shared<lv2_socket_raw>(family, type, protocol); break;
|
|
case SYS_NET_SOCK_DGRAM_P2P: sock_lv2 = std::make_shared<lv2_socket_p2p>(family, type, protocol); break;
|
|
case SYS_NET_SOCK_STREAM_P2P: sock_lv2 = std::make_shared<lv2_socket_p2ps>(family, type, protocol); break;
|
|
}
|
|
|
|
const s32 s = idm::import_existing<lv2_socket>(sock_lv2);
|
|
|
|
// Can't allocate more than 1000 sockets
|
|
if (s == id_manager::id_traits<lv2_socket>::invalid)
|
|
{
|
|
return -SYS_NET_EMFILE;
|
|
}
|
|
|
|
sock_lv2->set_lv2_id(s);
|
|
|
|
return not_an_error(s);
|
|
}
|
|
|
|
error_code sys_net_bnet_close(ppu_thread& ppu, s32 s)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_net.warning("sys_net_bnet_close(s=%d)", s);
|
|
|
|
auto sock = idm::withdraw<lv2_socket>(s);
|
|
|
|
if (!sock)
|
|
{
|
|
return -SYS_NET_EBADF;
|
|
}
|
|
|
|
if (sock->get_queue_size())
|
|
{
|
|
sock->abort_socket(0);
|
|
}
|
|
|
|
sock->close();
|
|
|
|
{
|
|
// Ensures the socket has no lingering copy from the network thread
|
|
std::lock_guard nw_lock(g_fxo->get<network_context>().s_nw_mutex);
|
|
sock.reset();
|
|
}
|
|
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code sys_net_bnet_poll(ppu_thread& ppu, vm::ptr<sys_net_pollfd> fds, s32 nfds, s32 ms)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_net.warning("sys_net_bnet_poll(fds=*0x%x, nfds=%d, ms=%d)", fds, nfds, ms);
|
|
|
|
if (nfds <= 0)
|
|
{
|
|
return not_an_error(0);
|
|
}
|
|
|
|
atomic_t<s32> signaled{0};
|
|
|
|
u64 timeout = ms < 0 ? 0 : ms * 1000ull;
|
|
|
|
std::vector<sys_net_pollfd> fds_buf;
|
|
|
|
{
|
|
fds_buf.assign(fds.get_ptr(), fds.get_ptr() + nfds);
|
|
|
|
lv2_obj::prepare_for_sleep(ppu);
|
|
|
|
std::unique_lock nw_lock(g_fxo->get<network_context>().s_nw_mutex);
|
|
std::shared_lock lock(id_manager::g_mutex);
|
|
|
|
::pollfd _fds[1024]{};
|
|
#ifdef _WIN32
|
|
bool connecting[1024]{};
|
|
#endif
|
|
|
|
for (s32 i = 0; i < nfds; i++)
|
|
{
|
|
_fds[i].fd = -1;
|
|
fds_buf[i].revents = 0;
|
|
|
|
if (fds_buf[i].fd < 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (auto sock = idm::check_unlocked<lv2_socket>(fds_buf[i].fd))
|
|
{
|
|
signaled += sock->poll(fds_buf[i], _fds[i]);
|
|
#ifdef _WIN32
|
|
connecting[i] = sock->is_connecting();
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
fds_buf[i].revents |= SYS_NET_POLLNVAL;
|
|
signaled++;
|
|
}
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
windows_poll(_fds, nfds, 0, connecting);
|
|
#else
|
|
::poll(_fds, nfds, 0);
|
|
#endif
|
|
for (s32 i = 0; i < nfds; i++)
|
|
{
|
|
if (_fds[i].revents & (POLLIN | POLLHUP))
|
|
fds_buf[i].revents |= SYS_NET_POLLIN;
|
|
if (_fds[i].revents & POLLOUT)
|
|
fds_buf[i].revents |= SYS_NET_POLLOUT;
|
|
if (_fds[i].revents & POLLERR)
|
|
fds_buf[i].revents |= SYS_NET_POLLERR;
|
|
|
|
if (fds_buf[i].revents)
|
|
{
|
|
signaled++;
|
|
}
|
|
}
|
|
|
|
if (ms == 0 || signaled)
|
|
{
|
|
lock.unlock();
|
|
nw_lock.unlock();
|
|
std::memcpy(fds.get_ptr(), fds_buf.data(), nfds * sizeof(fds[0]));
|
|
return not_an_error(signaled);
|
|
}
|
|
|
|
for (s32 i = 0; i < nfds; i++)
|
|
{
|
|
if (fds_buf[i].fd < 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (auto sock = idm::check_unlocked<lv2_socket>(fds_buf[i].fd))
|
|
{
|
|
auto lock = sock->lock();
|
|
|
|
#ifdef _WIN32
|
|
sock->set_connecting(connecting[i]);
|
|
#endif
|
|
|
|
bs_t<lv2_socket::poll_t> selected = +lv2_socket::poll_t::error;
|
|
|
|
if (fds_buf[i].events & SYS_NET_POLLIN)
|
|
selected += lv2_socket::poll_t::read;
|
|
if (fds_buf[i].events & SYS_NET_POLLOUT)
|
|
selected += lv2_socket::poll_t::write;
|
|
// if (fds_buf[i].events & SYS_NET_POLLPRI) // Unimplemented
|
|
// selected += lv2_socket::poll::error;
|
|
|
|
sock->poll_queue(idm::get_unlocked<named_thread<ppu_thread>>(ppu.id), selected, [sock, selected, &fds_buf, i, &signaled, &ppu](bs_t<lv2_socket::poll_t> events)
|
|
{
|
|
if (events & selected)
|
|
{
|
|
if (events & selected & lv2_socket::poll_t::read)
|
|
fds_buf[i].revents |= SYS_NET_POLLIN;
|
|
if (events & selected & lv2_socket::poll_t::write)
|
|
fds_buf[i].revents |= SYS_NET_POLLOUT;
|
|
if (events & selected & lv2_socket::poll_t::error)
|
|
fds_buf[i].revents |= SYS_NET_POLLERR;
|
|
|
|
signaled++;
|
|
g_fxo->get<network_context>().s_to_awake.emplace_back(&ppu);
|
|
return true;
|
|
}
|
|
|
|
sock->set_poll_event(selected);
|
|
return false;
|
|
});
|
|
}
|
|
}
|
|
|
|
lv2_obj::sleep(ppu, timeout);
|
|
}
|
|
|
|
bool has_timedout = false;
|
|
|
|
while (auto state = ppu.state.fetch_sub(cpu_flag::signal))
|
|
{
|
|
if (is_stopped(state))
|
|
{
|
|
return {};
|
|
}
|
|
|
|
if (state & cpu_flag::signal)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (timeout)
|
|
{
|
|
if (lv2_obj::wait_timeout(timeout, &ppu))
|
|
{
|
|
// Wait for rescheduling
|
|
if (ppu.check_state())
|
|
{
|
|
return {};
|
|
}
|
|
|
|
std::lock_guard nw_lock(g_fxo->get<network_context>().s_nw_mutex);
|
|
|
|
if (signaled)
|
|
{
|
|
break;
|
|
}
|
|
|
|
has_timedout = network_clear_queue(ppu);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ppu.state.wait(state);
|
|
}
|
|
}
|
|
|
|
std::memcpy(fds.get_ptr(), fds_buf.data(), nfds * sizeof(fds[0]));
|
|
|
|
if (!has_timedout && !signaled)
|
|
{
|
|
return -SYS_NET_EINTR;
|
|
}
|
|
|
|
return not_an_error(signaled);
|
|
}
|
|
|
|
error_code sys_net_bnet_select(ppu_thread& ppu, s32 nfds, vm::ptr<sys_net_fd_set> readfds, vm::ptr<sys_net_fd_set> writefds, vm::ptr<sys_net_fd_set> exceptfds, vm::ptr<sys_net_timeval> _timeout)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_net.warning("sys_net_bnet_select(nfds=%d, readfds=*0x%x, writefds=*0x%x, exceptfds=*0x%x, timeout=*0x%x)", nfds, readfds, writefds, exceptfds, _timeout);
|
|
|
|
atomic_t<s32> signaled{0};
|
|
|
|
if (exceptfds)
|
|
{
|
|
struct log_t
|
|
{
|
|
atomic_t<bool> logged = false;
|
|
};
|
|
|
|
if (!g_fxo->get<log_t>().logged.exchange(true))
|
|
{
|
|
sys_net.error("sys_net_bnet_select(): exceptfds not implemented");
|
|
}
|
|
}
|
|
|
|
sys_net_fd_set rread{}, _readfds{};
|
|
sys_net_fd_set rwrite{}, _writefds{};
|
|
sys_net_fd_set rexcept{}, _exceptfds{};
|
|
u64 timeout = !_timeout ? 0 : _timeout->tv_sec * 1000000ull + _timeout->tv_usec;
|
|
|
|
if (nfds > 0 && nfds <= 1024)
|
|
{
|
|
if (readfds)
|
|
_readfds = *readfds;
|
|
if (writefds)
|
|
_writefds = *writefds;
|
|
if (exceptfds)
|
|
_exceptfds = *exceptfds;
|
|
|
|
std::lock_guard nw_lock(g_fxo->get<network_context>().s_nw_mutex);
|
|
|
|
reader_lock lock(id_manager::g_mutex);
|
|
|
|
::pollfd _fds[1024]{};
|
|
#ifdef _WIN32
|
|
bool connecting[1024]{};
|
|
#endif
|
|
|
|
for (s32 i = 0; i < nfds; i++)
|
|
{
|
|
_fds[i].fd = -1;
|
|
bs_t<lv2_socket::poll_t> selected{};
|
|
|
|
if (readfds && _readfds.bit(i))
|
|
selected += lv2_socket::poll_t::read;
|
|
if (writefds && _writefds.bit(i))
|
|
selected += lv2_socket::poll_t::write;
|
|
// if (exceptfds && _exceptfds.bit(i))
|
|
// selected += lv2_socket::poll::error;
|
|
|
|
if (selected)
|
|
{
|
|
selected += lv2_socket::poll_t::error;
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (auto sock = idm::check_unlocked<lv2_socket>((lv2_socket::id_base & -1024) + i))
|
|
{
|
|
auto [read_set, write_set, except_set] = sock->select(selected, _fds[i]);
|
|
|
|
if (read_set || write_set || except_set)
|
|
{
|
|
signaled++;
|
|
}
|
|
|
|
if (read_set)
|
|
{
|
|
rread.set(i);
|
|
}
|
|
|
|
if (write_set)
|
|
{
|
|
rwrite.set(i);
|
|
}
|
|
|
|
if (except_set)
|
|
{
|
|
rexcept.set(i);
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
connecting[i] = sock->is_connecting();
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
return -SYS_NET_EBADF;
|
|
}
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
windows_poll(_fds, nfds, 0, connecting);
|
|
#else
|
|
::poll(_fds, nfds, 0);
|
|
#endif
|
|
for (s32 i = 0; i < nfds; i++)
|
|
{
|
|
bool sig = false;
|
|
if (_fds[i].revents & (POLLIN | POLLHUP | POLLERR))
|
|
sig = true, rread.set(i);
|
|
if (_fds[i].revents & (POLLOUT | POLLERR))
|
|
sig = true, rwrite.set(i);
|
|
|
|
if (sig)
|
|
{
|
|
signaled++;
|
|
}
|
|
}
|
|
|
|
if ((_timeout && !timeout) || signaled)
|
|
{
|
|
if (readfds)
|
|
*readfds = rread;
|
|
if (writefds)
|
|
*writefds = rwrite;
|
|
if (exceptfds)
|
|
*exceptfds = rexcept;
|
|
return not_an_error(signaled);
|
|
}
|
|
|
|
for (s32 i = 0; i < nfds; i++)
|
|
{
|
|
bs_t<lv2_socket::poll_t> selected{};
|
|
|
|
if (readfds && _readfds.bit(i))
|
|
selected += lv2_socket::poll_t::read;
|
|
if (writefds && _writefds.bit(i))
|
|
selected += lv2_socket::poll_t::write;
|
|
// if (exceptfds && _exceptfds.bit(i))
|
|
// selected += lv2_socket::poll_t::error;
|
|
|
|
if (selected)
|
|
{
|
|
selected += lv2_socket::poll_t::error;
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (auto sock = idm::check_unlocked<lv2_socket>((lv2_socket::id_base & -1024) + i))
|
|
{
|
|
auto lock = sock->lock();
|
|
#ifdef _WIN32
|
|
sock->set_connecting(connecting[i]);
|
|
#endif
|
|
|
|
sock->poll_queue(idm::get_unlocked<named_thread<ppu_thread>>(ppu.id), selected, [sock, selected, i, &rread, &rwrite, &rexcept, &signaled, &ppu](bs_t<lv2_socket::poll_t> events)
|
|
{
|
|
if (events & selected)
|
|
{
|
|
if (selected & lv2_socket::poll_t::read && events & (lv2_socket::poll_t::read + lv2_socket::poll_t::error))
|
|
rread.set(i);
|
|
if (selected & lv2_socket::poll_t::write && events & (lv2_socket::poll_t::write + lv2_socket::poll_t::error))
|
|
rwrite.set(i);
|
|
// if (events & (selected & lv2_socket::poll::error))
|
|
// rexcept.set(i);
|
|
|
|
signaled++;
|
|
g_fxo->get<network_context>().s_to_awake.emplace_back(&ppu);
|
|
return true;
|
|
}
|
|
|
|
sock->set_poll_event(selected);
|
|
return false;
|
|
});
|
|
}
|
|
else
|
|
{
|
|
return -SYS_NET_EBADF;
|
|
}
|
|
}
|
|
|
|
lv2_obj::sleep(ppu, timeout);
|
|
}
|
|
else
|
|
{
|
|
return -SYS_NET_EINVAL;
|
|
}
|
|
|
|
bool has_timedout = false;
|
|
|
|
while (auto state = ppu.state.fetch_sub(cpu_flag::signal))
|
|
{
|
|
if (is_stopped(state))
|
|
{
|
|
return {};
|
|
}
|
|
|
|
if (state & cpu_flag::signal)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (timeout)
|
|
{
|
|
if (lv2_obj::wait_timeout(timeout, &ppu))
|
|
{
|
|
// Wait for rescheduling
|
|
if (ppu.check_state())
|
|
{
|
|
return {};
|
|
}
|
|
|
|
std::lock_guard nw_lock(g_fxo->get<network_context>().s_nw_mutex);
|
|
|
|
if (signaled)
|
|
{
|
|
break;
|
|
}
|
|
|
|
has_timedout = network_clear_queue(ppu);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ppu.state.wait(state);
|
|
}
|
|
}
|
|
|
|
if (readfds)
|
|
*readfds = rread;
|
|
if (writefds)
|
|
*writefds = rwrite;
|
|
if (exceptfds)
|
|
*exceptfds = rexcept;
|
|
|
|
if (!has_timedout && !signaled)
|
|
{
|
|
return -SYS_NET_EINTR;
|
|
}
|
|
|
|
return not_an_error(signaled);
|
|
}
|
|
|
|
error_code _sys_net_open_dump(ppu_thread& ppu, s32 len, s32 flags)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_net.todo("_sys_net_open_dump(len=%d, flags=0x%x)", len, flags);
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code _sys_net_read_dump(ppu_thread& ppu, s32 id, vm::ptr<void> buf, s32 len, vm::ptr<s32> pflags)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_net.todo("_sys_net_read_dump(id=0x%x, buf=*0x%x, len=%d, pflags=*0x%x)", id, buf, len, pflags);
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code _sys_net_close_dump(ppu_thread& ppu, s32 id, vm::ptr<s32> pflags)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_net.todo("_sys_net_close_dump(id=0x%x, pflags=*0x%x)", id, pflags);
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code _sys_net_write_dump(ppu_thread& ppu, s32 id, vm::cptr<void> buf, s32 len, u32 unknown)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_net.todo("_sys_net_write_dump(id=0x%x, buf=*0x%x, len=%d, unk=0x%x)", id, buf, len, unknown);
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code lv2_socket::abort_socket(s32 flags)
|
|
{
|
|
decltype(queue) qcopy;
|
|
{
|
|
std::lock_guard lock(mutex);
|
|
|
|
if (queue.empty())
|
|
{
|
|
if (flags & SYS_NET_ABORT_STRICT_CHECK)
|
|
{
|
|
// Strict error checking: ENOENT if nothing happened
|
|
return -SYS_NET_ENOENT;
|
|
}
|
|
|
|
// TODO: Abort the subsequent function called on this socket (need to investigate correct behaviour)
|
|
return CELL_OK;
|
|
}
|
|
|
|
qcopy = std::move(queue);
|
|
events.store({});
|
|
}
|
|
|
|
for (auto& [ppu, _] : qcopy)
|
|
{
|
|
if (!ppu)
|
|
continue;
|
|
|
|
sys_net.warning("lv2_socket::abort_socket(): waking up \"%s\": (func: %s, r3=0x%x, r4=0x%x, r5=0x%x, r6=0x%x)", ppu->get_name(), ppu->current_function, ppu->gpr[3], ppu->gpr[4], ppu->gpr[5], ppu->gpr[6]);
|
|
ppu->gpr[3] = static_cast<u64>(-SYS_NET_EINTR);
|
|
lv2_obj::append(ppu.get());
|
|
}
|
|
|
|
lv2_obj::awake_all();
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code sys_net_abort(ppu_thread& ppu, s32 type, u64 arg, s32 flags)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_net.warning("sys_net_abort(type=%d, arg=0x%x, flags=0x%x)", type, arg, flags);
|
|
|
|
enum abort_type : s32
|
|
{
|
|
_socket,
|
|
resolver,
|
|
type_2, // ??
|
|
type_3, // ??
|
|
all,
|
|
};
|
|
|
|
switch (type)
|
|
{
|
|
case _socket:
|
|
{
|
|
std::lock_guard nw_lock(g_fxo->get<network_context>().s_nw_mutex);
|
|
|
|
const auto sock = idm::get<lv2_socket>(static_cast<u32>(arg));
|
|
|
|
if (!sock)
|
|
{
|
|
return -SYS_NET_EBADF;
|
|
}
|
|
|
|
return sock->abort_socket(flags);
|
|
}
|
|
case all:
|
|
{
|
|
std::vector<u32> sockets;
|
|
|
|
idm::select<lv2_socket>([&](u32 id, lv2_socket&)
|
|
{
|
|
sockets.emplace_back(id);
|
|
});
|
|
|
|
s32 failed = 0;
|
|
|
|
for (u32 id : sockets)
|
|
{
|
|
const auto sock = idm::withdraw<lv2_socket>(id);
|
|
|
|
if (!sock)
|
|
{
|
|
failed++;
|
|
continue;
|
|
}
|
|
|
|
if (sock->get_queue_size())
|
|
sys_net.error("ABORT 4");
|
|
|
|
sock->close();
|
|
|
|
sys_net.success("lv2_socket::handle_abort(): Closed socket %d", id);
|
|
}
|
|
|
|
// Ensures the socket has no lingering copy from the network thread
|
|
g_fxo->get<network_context>().s_nw_mutex.lock_unlock();
|
|
|
|
return not_an_error(::narrow<s32>(sockets.size()) - failed);
|
|
}
|
|
case resolver:
|
|
case type_2:
|
|
case type_3:
|
|
{
|
|
break;
|
|
}
|
|
default: return -SYS_NET_EINVAL;
|
|
}
|
|
|
|
return CELL_OK;
|
|
}
|
|
|
|
struct net_infoctl_cmd_9_t
|
|
{
|
|
be_t<u32> zero;
|
|
vm::bptr<char> server_name;
|
|
// More (TODO)
|
|
};
|
|
|
|
error_code sys_net_infoctl(ppu_thread& ppu, s32 cmd, vm::ptr<void> arg)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_net.todo("sys_net_infoctl(cmd=%d, arg=*0x%x)", cmd, arg);
|
|
|
|
// TODO
|
|
switch (cmd)
|
|
{
|
|
case 9:
|
|
{
|
|
constexpr auto nameserver = "nameserver \0"sv;
|
|
|
|
char buffer[nameserver.size() + 80]{};
|
|
std::memcpy(buffer, nameserver.data(), nameserver.size());
|
|
|
|
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
|
|
const auto dns_str = np::ip_to_string(nph.get_dns_ip());
|
|
std::memcpy(buffer + nameserver.size() - 1, dns_str.data(), dns_str.size());
|
|
|
|
std::string_view name{buffer};
|
|
vm::static_ptr_cast<net_infoctl_cmd_9_t>(arg)->zero = 0;
|
|
std::memcpy(vm::static_ptr_cast<net_infoctl_cmd_9_t>(arg)->server_name.get_ptr(), name.data(), name.size());
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code sys_net_control(ppu_thread& ppu, u32 arg1, s32 arg2, vm::ptr<void> arg3, s32 arg4)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_net.todo("sys_net_control(0x%x, %d, *0x%x, %d)", arg1, arg2, arg3, arg4);
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code sys_net_bnet_ioctl(ppu_thread& ppu, s32 arg1, u32 arg2, u32 arg3)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_net.todo("sys_net_bnet_ioctl(%d, 0x%x, 0x%x)", arg1, arg2, arg3);
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code sys_net_bnet_sysctl(ppu_thread& ppu, u32 arg1, u32 arg2, u32 arg3, vm::ptr<void> arg4, u32 arg5, u32 arg6)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_net.todo("sys_net_bnet_sysctl(0x%x, 0x%x, 0x%x, *0x%x, 0x%x, 0x%x)", arg1, arg2, arg3, arg4, arg5, arg6);
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code sys_net_eurus_post_command(ppu_thread& ppu, s32 arg1, u32 arg2, u32 arg3)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_net.todo("sys_net_eurus_post_command(%d, 0x%x, 0x%x)", arg1, arg2, arg3);
|
|
return CELL_OK;
|
|
}
|