#include "stdafx.h"

#include "Emu/Cell/lv2/sys_net.h"
#include "Emu/NP/np_dnshook.h"
#include "Emu/NP/np_handler.h"
#include "lv2_socket_native.h"
#include "sys_net_helpers.h"

#ifdef _WIN32
constexpr SOCKET invalid_socket = INVALID_SOCKET;
#else
constexpr int invalid_socket = -1;
#endif

LOG_CHANNEL(sys_net);

lv2_socket_native::lv2_socket_native(lv2_socket_family family, lv2_socket_type type, lv2_ip_protocol protocol)
	: lv2_socket(family, type, protocol)
{
}

lv2_socket_native::lv2_socket_native(utils::serial& ar, lv2_socket_type type)
	: lv2_socket(make_exact(ar), type)
{
	[[maybe_unused]] const s32 version = GET_SERIALIZATION_VERSION(lv2_net);

#ifdef _WIN32
	ar(so_reuseaddr, so_reuseport);
#else
	std::array<char, 8> dummy{};
	ar(dummy);

	if (dummy != std::array<char, 8>{})
	{
		sys_net.error("[Native] Savestate tried to load Win32 specific data, compatibility may be affected");
	}
#endif

	if (version >= 2)
	{
		// Flag to signal failure of TCP connection on socket start
		ar(feign_tcp_conn_failure);
	}
}

void lv2_socket_native::save(utils::serial& ar)
{
	USING_SERIALIZATION_VERSION(lv2_net);

	lv2_socket::save(ar, true);
#ifdef _WIN32
	ar(so_reuseaddr, so_reuseport);
#else
	ar(std::array<char, 8>{});
#endif

	ar(is_socket_connected());
}

lv2_socket_native::~lv2_socket_native() noexcept
{
	lv2_socket_native::close();
}

s32 lv2_socket_native::create_socket()
{
	ensure(family == SYS_NET_AF_INET);
	ensure(type == SYS_NET_SOCK_STREAM || type == SYS_NET_SOCK_DGRAM);
	ensure(protocol == SYS_NET_IPPROTO_IP || protocol == SYS_NET_IPPROTO_TCP || protocol == SYS_NET_IPPROTO_UDP);

	const int native_domain = AF_INET;

	const int native_type = type == SYS_NET_SOCK_STREAM ? SOCK_STREAM : SOCK_DGRAM;

	int native_proto = protocol == SYS_NET_IPPROTO_TCP ? IPPROTO_TCP :
	                   protocol == SYS_NET_IPPROTO_UDP ? IPPROTO_UDP :
	                                                     IPPROTO_IP;

	auto socket_res = ::socket(native_domain, native_type, native_proto);

	if (socket_res == invalid_socket)
	{
		return -get_last_error(false);
	}

	set_socket(socket_res, family, type, protocol);
	return CELL_OK;
}

void lv2_socket_native::set_socket(socket_type native_socket, lv2_socket_family family, lv2_socket_type type, lv2_ip_protocol protocol)
{
	this->native_socket = native_socket;
	this->family = family;
	this->type = type;
	this->protocol = protocol;

	set_default_buffers();
	set_non_blocking();
}

std::tuple<bool, s32, shared_ptr<lv2_socket>, sys_net_sockaddr> lv2_socket_native::accept(bool is_lock)
{
	std::unique_lock<shared_mutex> lock(mutex, std::defer_lock);

	if (is_lock)
	{
		lock.lock();
	}

	::sockaddr_storage native_addr;
	::socklen_t native_addrlen = sizeof(native_addr);

	if (feign_tcp_conn_failure)
	{
		sys_net.error("Calling socket::accept() from a previously connected socket!");
	}

	socket_type client_socket = ::accept(native_socket, reinterpret_cast<struct sockaddr*>(&native_addr), &native_addrlen);

	if (client_socket != invalid_socket)
	{
		auto newsock = make_single<lv2_socket_native>(family, type, protocol);
		newsock->set_socket(client_socket, family, type, protocol);

		// Sockets inherit non blocking behaviour from their parent
		newsock->so_nbio = so_nbio;

		sys_net_sockaddr ps3_addr = native_addr_to_sys_net_addr(native_addr);

		return {true, 0, std::move(newsock), ps3_addr};
	}

	if (auto result = get_last_error(!so_nbio); result)
	{
		return {true, -result, {}, {}};
	}

	return {false, {}, {}, {}};
}

s32 lv2_socket_native::bind(const sys_net_sockaddr& addr)
{
	std::lock_guard lock(mutex);

	const auto* psa_in = reinterpret_cast<const sys_net_sockaddr_in*>(&addr);

	auto& nph = g_fxo->get<named_thread<np::np_handler>>();
	u32 saddr = nph.get_bind_ip();
	if (saddr == 0)
	{
		// If zero use the supplied address
		saddr = std::bit_cast<u32>(psa_in->sin_addr);
	}

	if (feign_tcp_conn_failure)
	{
		sys_net.error("Calling socket::bind() from a previously connected socket!");
	}

	::sockaddr_in native_addr{};
	native_addr.sin_family = AF_INET;
	native_addr.sin_port = std::bit_cast<u16>(psa_in->sin_port);
	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(native_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(native_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;
	}

	auto error = get_last_error(false);

#ifdef __linux__
	if (error == SYS_NET_EACCES && std::bit_cast<be_t<u16>, u16>(native_addr.sin_port) < 1024)
	{
		sys_net.error("The game tried to bind a port < 1024 which is privileged on Linux\n"
					  "Consider setting rpcs3 privileges for it with: setcap 'cap_net_bind_service=+ep' /path/to/rpcs3");
	}
#endif

	return -error;
}

std::optional<s32> lv2_socket_native::connect(const sys_net_sockaddr& addr)
{
	std::lock_guard lock(mutex);

	const auto* psa_in = reinterpret_cast<const sys_net_sockaddr_in*>(&addr);

	::sockaddr_in native_addr = sys_net_addr_to_native_addr(addr);
	::socklen_t native_addr_len = sizeof(native_addr);

	sys_net.notice("[Native] Attempting to connect on %s:%d", native_addr.sin_addr, std::bit_cast<be_t<u16>, u16>(native_addr.sin_port));

	auto& nph = g_fxo->get<named_thread<np::np_handler>>();
	if (!nph.get_net_status() && is_ip_public_address(native_addr))
	{
		return -SYS_NET_EADDRNOTAVAIL;
	}

	if (psa_in->sin_port == 53)
	{
		// Add socket to the dns hook list
		sys_net.notice("[Native] sys_net_bnet_connect: using DNS...");
		auto& dnshook = g_fxo->get<np::dnshook>();
		dnshook.add_dns_spy(lv2_id);
	}

#ifdef _WIN32
	bool was_connecting = connecting;
#endif

	if (feign_tcp_conn_failure)
	{
		// As if still connected
		return -SYS_NET_EALREADY;
	}

	if (::connect(native_socket, reinterpret_cast<struct sockaddr*>(&native_addr), native_addr_len) == 0)
	{
		return CELL_OK;
	}

	sys_net_error result = get_last_error(!so_nbio);

#ifdef _WIN32
	// See https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-connect
	if (was_connecting && (result == SYS_NET_EINVAL || result == SYS_NET_EWOULDBLOCK))
		return -SYS_NET_EALREADY;
#endif

	if (result)
	{
		if (result == SYS_NET_EWOULDBLOCK || result == SYS_NET_EINPROGRESS)
		{
			result = SYS_NET_EINPROGRESS;
#ifdef _WIN32
			connecting = true;
#endif
			this->poll_queue(null_ptr, lv2_socket::poll_t::write, [this](bs_t<lv2_socket::poll_t> events) -> bool
				{
					if (events & lv2_socket::poll_t::write)
					{
						int native_error;
						::socklen_t size = sizeof(native_error);
						if (::getsockopt(native_socket, SOL_SOCKET, SO_ERROR, reinterpret_cast<char*>(&native_error), &size) != 0 || size != sizeof(int))
						{
							so_error = 1;
						}
						else
						{
							// TODO: check error formats (both native and translated)
							so_error = native_error ? convert_error(false, native_error) : 0;
						}

						return true;
					}

					events += lv2_socket::poll_t::write;
					return false;
				});
		}

		return -result;
	}

#ifdef _WIN32
	connecting = true;
#endif

	return std::nullopt;
}

s32 lv2_socket_native::connect_followup()
{
	int native_error;
	::socklen_t size = sizeof(native_error);
	if (::getsockopt(native_socket, SOL_SOCKET, SO_ERROR, reinterpret_cast<char*>(&native_error), &size) != 0 || size != sizeof(int))
	{
		return -1;
	}

	// TODO: check error formats (both native and translated)
	return native_error ? -convert_error(false, native_error) : 0;
}

std::pair<s32, sys_net_sockaddr> lv2_socket_native::getpeername()
{
	std::lock_guard lock(mutex);

	::sockaddr_storage native_addr;
	::socklen_t native_addrlen = sizeof(native_addr);

	if (::getpeername(native_socket, reinterpret_cast<struct sockaddr*>(&native_addr), &native_addrlen) == 0)
	{
		ensure(native_addr.ss_family == AF_INET);

		sys_net_sockaddr sn_addr = native_addr_to_sys_net_addr(native_addr);

		return {CELL_OK, sn_addr};
	}

	return {-get_last_error(false), {}};
}

std::pair<s32, sys_net_sockaddr> lv2_socket_native::getsockname()
{
	std::lock_guard lock(mutex);

	::sockaddr_storage native_addr;
	::socklen_t native_addrlen = sizeof(native_addr);

	if (::getsockname(native_socket, reinterpret_cast<struct sockaddr*>(&native_addr), &native_addrlen) == 0)
	{
		ensure(native_addr.ss_family == AF_INET);

		sys_net_sockaddr sn_addr = native_addr_to_sys_net_addr(native_addr);

		return {CELL_OK, sn_addr};
	}
#ifdef _WIN32
	else
	{
		// windows doesn't support getsockname for sockets that are not bound
		if (get_native_error() == WSAEINVAL)
		{
			return {CELL_OK, {}};
		}
	}
#endif

	return {-get_last_error(false), {}};
}

std::tuple<s32, lv2_socket::sockopt_data, u32> lv2_socket_native::getsockopt(s32 level, s32 optname, u32 len)
{
	std::lock_guard lock(mutex);

	sockopt_data out_val;
	u32 out_len = sizeof(out_val);

	int native_level = -1;
	int native_opt = -1;

	union
	{
		char ch[128];
		int _int = 0;
		::timeval timeo;
		::linger linger;
	} native_val;
	::socklen_t native_len = sizeof(native_val);

	if (level == SYS_NET_SOL_SOCKET)
	{
		native_level = SOL_SOCKET;

		switch (optname)
		{
		case SYS_NET_SO_NBIO:
		{
			// Special
			out_val._int = so_nbio;
			out_len = sizeof(s32);
			return {CELL_OK, out_val, out_len};
		}
		case SYS_NET_SO_ERROR:
		{
			// Special
			out_val._int = std::exchange(so_error, 0);
			out_len = sizeof(s32);
			return {CELL_OK, out_val, out_len};
		}
		case SYS_NET_SO_KEEPALIVE:
		{
			native_opt = SO_KEEPALIVE;
			break;
		}
		case SYS_NET_SO_SNDBUF:
		{
			native_opt = SO_SNDBUF;
			break;
		}
		case SYS_NET_SO_RCVBUF:
		{
			native_opt = SO_RCVBUF;
			break;
		}
		case SYS_NET_SO_SNDLOWAT:
		{
			native_opt = SO_SNDLOWAT;
			break;
		}
		case SYS_NET_SO_RCVLOWAT:
		{
			native_opt = SO_RCVLOWAT;
			break;
		}
		case SYS_NET_SO_BROADCAST:
		{
			native_opt = SO_BROADCAST;
			break;
		}
#ifdef _WIN32
		case SYS_NET_SO_REUSEADDR:
		{
			out_val._int = so_reuseaddr;
			out_len = sizeof(s32);
			return {CELL_OK, out_val, out_len};
		}
		case SYS_NET_SO_REUSEPORT:
		{
			out_val._int = so_reuseport;
			out_len = sizeof(s32);
			return {CELL_OK, out_val, out_len};
		}
#else
		case SYS_NET_SO_REUSEADDR:
		{
			native_opt = SO_REUSEADDR;
			break;
		}
		case SYS_NET_SO_REUSEPORT:
		{
			native_opt = SO_REUSEPORT;
			break;
		}
#endif
		case SYS_NET_SO_SNDTIMEO:
		case SYS_NET_SO_RCVTIMEO:
		{
			if (len < sizeof(sys_net_timeval))
				return {-SYS_NET_EINVAL, {}, {}};

			native_opt = optname == SYS_NET_SO_SNDTIMEO ? SO_SNDTIMEO : SO_RCVTIMEO;
			break;
		}
		case SYS_NET_SO_LINGER:
		{
			if (len < sizeof(sys_net_linger))
				return {-SYS_NET_EINVAL, {}, {}};

			native_opt = SO_LINGER;
			break;
		}
		default:
		{
			sys_net.error("sys_net_bnet_getsockopt(s=%d, SOL_SOCKET): unknown option (0x%x)", lv2_id, optname);
			return {-SYS_NET_EINVAL, {}, {}};
		}
		}
	}
	else if (level == SYS_NET_IPPROTO_TCP)
	{
		native_level = IPPROTO_TCP;

		switch (optname)
		{
		case SYS_NET_TCP_MAXSEG:
		{
			// Special (no effect)
			out_val._int = so_tcp_maxseg;
			out_len = sizeof(s32);
			return {CELL_OK, out_val, out_len};
		}
		case SYS_NET_TCP_NODELAY:
		{
			native_opt = TCP_NODELAY;
			break;
		}
		default:
		{
			sys_net.error("sys_net_bnet_getsockopt(s=%d, IPPROTO_TCP): unknown option (0x%x)", lv2_id, optname);
			return {-SYS_NET_EINVAL, {}, {}};
		}
		}
	}
	else if (level == SYS_NET_IPPROTO_IP)
	{
		native_level = IPPROTO_IP;
		switch (optname)
		{
		case SYS_NET_IP_HDRINCL:
		{
			native_opt = IP_HDRINCL;
			break;
		}
		case SYS_NET_IP_TOS:
		{
			native_opt = IP_TOS;
			break;
		}
		case SYS_NET_IP_TTL:
		{
			native_opt = IP_TTL;
			break;
		}
		case SYS_NET_IP_MULTICAST_IF:
		{
			native_opt = IP_MULTICAST_IF;
			break;
		}
		case SYS_NET_IP_MULTICAST_TTL:
		{
			native_opt = IP_MULTICAST_TTL;
			break;
		}
		case SYS_NET_IP_MULTICAST_LOOP:
		{
			native_opt = IP_MULTICAST_LOOP;
			break;
		}
		case SYS_NET_IP_ADD_MEMBERSHIP:
		{
			native_opt = IP_ADD_MEMBERSHIP;
			break;
		}
		case SYS_NET_IP_DROP_MEMBERSHIP:
		{
			native_opt = IP_DROP_MEMBERSHIP;
			break;
		}
		case SYS_NET_IP_TTLCHK:
		{
			sys_net.error("sys_net_bnet_getsockopt(IPPROTO_IP, SYS_NET_IP_TTLCHK): stubbed option");
			return {CELL_OK, out_val, out_len};
		}
		case SYS_NET_IP_MAXTTL:
		{
			sys_net.error("sys_net_bnet_getsockopt(IPPROTO_IP, SYS_NET_IP_MAXTTL): stubbed option");
			return {CELL_OK, out_val, out_len};
		}
		case SYS_NET_IP_DONTFRAG:
		{
#ifdef _WIN32
			native_opt = IP_DONTFRAGMENT;
#else
			native_opt = IP_DF;
#endif
			break;
		}
		default:
		{
			sys_net.error("sys_net_bnet_getsockopt(s=%d, IPPROTO_IP): unknown option (0x%x)", lv2_id, optname);
			return {-SYS_NET_EINVAL, {}, {}};
		}
		}
	}
	else
	{
		sys_net.error("sys_net_bnet_getsockopt(s=%d): unknown level (0x%x)", lv2_id, level);
		return {-SYS_NET_EINVAL, {}, {}};
	}

	if (::getsockopt(native_socket, native_level, native_opt, native_val.ch, &native_len) != 0)
	{
		return {-get_last_error(false), {}, {}};
	}

	if (level == SYS_NET_SOL_SOCKET)
	{
		switch (optname)
		{
		case SYS_NET_SO_SNDTIMEO:
		case SYS_NET_SO_RCVTIMEO:
		{
			// TODO
			out_val.timeo = {::narrow<s64>(native_val.timeo.tv_sec), ::narrow<s64>(native_val.timeo.tv_usec)};
			out_len = sizeof(sys_net_timeval);
			return {CELL_OK, out_val, out_len};
		}
		case SYS_NET_SO_LINGER:
		{
			// TODO
			out_val.linger = {::narrow<s32>(native_val.linger.l_onoff), ::narrow<s32>(native_val.linger.l_linger)};
			out_len = sizeof(sys_net_linger);
			return {CELL_OK, out_val, out_len};
		}
		default: break;
		}
	}

	// Fallback to int
	out_val._int = native_val._int;
	out_len = sizeof(s32);
	return {CELL_OK, out_val, out_len};
}

s32 lv2_socket_native::setsockopt(s32 level, s32 optname, const std::vector<u8>& optval)
{
	std::lock_guard lock(mutex);

	int native_int = 0;
	int native_level = -1;
	int native_opt = -1;
	const void* native_val = &native_int;
	::socklen_t native_len = sizeof(int);
	::linger native_linger;
	::ip_mreq native_mreq;

#ifdef _WIN32
	u32 native_timeo;
#else
	::timeval native_timeo;
#endif

	native_int = *reinterpret_cast<const be_t<s32>*>(optval.data());

	if (level == SYS_NET_SOL_SOCKET)
	{
		native_level = SOL_SOCKET;

		switch (optname)
		{
		case SYS_NET_SO_NBIO:
		{
			// Special
			so_nbio = native_int;
			return {};
		}
		case SYS_NET_SO_KEEPALIVE:
		{
			native_opt = SO_KEEPALIVE;
			break;
		}
		case SYS_NET_SO_SNDBUF:
		{
			native_opt = SO_SNDBUF;
			break;
		}
		case SYS_NET_SO_RCVBUF:
		{
			native_opt = SO_RCVBUF;
			break;
		}
		case SYS_NET_SO_SNDLOWAT:
		{
			native_opt = SO_SNDLOWAT;
			break;
		}
		case SYS_NET_SO_RCVLOWAT:
		{
			native_opt = SO_RCVLOWAT;
			break;
		}
		case SYS_NET_SO_BROADCAST:
		{
			native_opt = SO_BROADCAST;
			break;
		}
#ifdef _WIN32
		case SYS_NET_SO_REUSEADDR:
		{
			native_opt = SO_REUSEADDR;
			so_reuseaddr = native_int;
			native_int = so_reuseaddr || so_reuseport ? 1 : 0;
			break;
		}
		case SYS_NET_SO_REUSEPORT:
		{
			native_opt = SO_REUSEADDR;
			so_reuseport = native_int;
			native_int = so_reuseaddr || so_reuseport ? 1 : 0;
			break;
		}
#else
		case SYS_NET_SO_REUSEADDR:
		{
			native_opt = SO_REUSEADDR;
			break;
		}
		case SYS_NET_SO_REUSEPORT:
		{
			native_opt = SO_REUSEPORT;
			break;
		}
#endif
		case SYS_NET_SO_SNDTIMEO:
		case SYS_NET_SO_RCVTIMEO:
		{
			if (optval.size() < sizeof(sys_net_timeval))
				return -SYS_NET_EINVAL;

			native_opt = optname == SYS_NET_SO_SNDTIMEO ? SO_SNDTIMEO : SO_RCVTIMEO;
			native_val = &native_timeo;
			native_len = sizeof(native_timeo);

			const int tv_sec = ::narrow<int>(reinterpret_cast<const sys_net_timeval*>(optval.data())->tv_sec);
			const int tv_usec = ::narrow<int>(reinterpret_cast<const sys_net_timeval*>(optval.data())->tv_usec);
#ifdef _WIN32
			native_timeo = tv_sec * 1000;
			native_timeo += tv_usec / 1000;
#else
			native_timeo.tv_sec = tv_sec;
			native_timeo.tv_usec = tv_usec;
#endif
			// TODO: Overflow detection?
			(optname == SYS_NET_SO_SNDTIMEO ? so_sendtimeo : so_rcvtimeo) = tv_usec + tv_sec * 1000000;
			break;
		}
		case SYS_NET_SO_LINGER:
		{
			if (optval.size() < sizeof(sys_net_linger))
				return -SYS_NET_EINVAL;

			// TODO
			native_opt = SO_LINGER;
			native_val = &native_linger;
			native_len = sizeof(native_linger);
			native_linger.l_onoff = reinterpret_cast<const sys_net_linger*>(optval.data())->l_onoff;
			native_linger.l_linger = reinterpret_cast<const sys_net_linger*>(optval.data())->l_linger;
			break;
		}
		case SYS_NET_SO_USECRYPTO:
		{
			// TODO
			sys_net.error("sys_net_bnet_setsockopt(s=%d, SOL_SOCKET): Stubbed option (0x%x) (SYS_NET_SO_USECRYPTO)", lv2_id, optname);
			return {};
		}
		case SYS_NET_SO_USESIGNATURE:
		{
			// TODO
			sys_net.error("sys_net_bnet_setsockopt(s=%d, SOL_SOCKET): Stubbed option (0x%x) (SYS_NET_SO_USESIGNATURE)", lv2_id, optname);
			return {};
		}
		default:
		{
			sys_net.error("sys_net_bnet_setsockopt(s=%d, SOL_SOCKET): unknown option (0x%x)", lv2_id, optname);
			return -SYS_NET_EINVAL;
		}
		}
	}
	else if (level == SYS_NET_IPPROTO_TCP)
	{
		native_level = IPPROTO_TCP;

		switch (optname)
		{
		case SYS_NET_TCP_MAXSEG:
		{
			// Special (no effect)
			so_tcp_maxseg = native_int;
			return {};
		}
		case SYS_NET_TCP_NODELAY:
		{
			native_opt = TCP_NODELAY;
			break;
		}
		default:
		{
			sys_net.error("sys_net_bnet_setsockopt(s=%d, IPPROTO_TCP): unknown option (0x%x)", lv2_id, optname);
			return -SYS_NET_EINVAL;
		}
		}
	}
	else if (level == SYS_NET_IPPROTO_IP)
	{
		native_level = IPPROTO_IP;

		switch (optname)
		{
		case SYS_NET_IP_HDRINCL:
		{
			native_opt = IP_HDRINCL;
			break;
		}
		case SYS_NET_IP_TOS:
		{
			native_opt = IP_TOS;
			break;
		}
		case SYS_NET_IP_TTL:
		{
			native_opt = IP_TTL;
			break;
		}
		case SYS_NET_IP_MULTICAST_IF:
		{
			native_opt = IP_MULTICAST_IF;
			break;
		}
		case SYS_NET_IP_MULTICAST_TTL:
		{
			native_opt = IP_MULTICAST_TTL;
			break;
		}
		case SYS_NET_IP_MULTICAST_LOOP:
		{
			native_opt = IP_MULTICAST_LOOP;
			break;
		}
		case SYS_NET_IP_ADD_MEMBERSHIP:
		case SYS_NET_IP_DROP_MEMBERSHIP:
		{
			if (optval.size() < sizeof(sys_net_ip_mreq))
				return -SYS_NET_EINVAL;

			native_opt = optname == SYS_NET_IP_ADD_MEMBERSHIP ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP;
			native_val = &native_mreq;
			native_len = sizeof(::ip_mreq);
			native_mreq.imr_interface.s_addr = std::bit_cast<u32>(reinterpret_cast<const sys_net_ip_mreq*>(optval.data())->imr_interface);
			native_mreq.imr_multiaddr.s_addr = std::bit_cast<u32>(reinterpret_cast<const sys_net_ip_mreq*>(optval.data())->imr_multiaddr);
			break;
		}
		case SYS_NET_IP_TTLCHK:
		{
			sys_net.error("sys_net_bnet_setsockopt(s=%d, IPPROTO_IP): Stubbed option (0x%x) (SYS_NET_IP_TTLCHK)", lv2_id, optname);
			break;
		}
		case SYS_NET_IP_MAXTTL:
		{
			sys_net.error("sys_net_bnet_setsockopt(s=%d, IPPROTO_IP): Stubbed option (0x%x) (SYS_NET_IP_MAXTTL)", lv2_id, optname);
			break;
		}
		case SYS_NET_IP_DONTFRAG:
		{
#ifdef _WIN32
			native_opt = IP_DONTFRAGMENT;
#else
			native_opt = IP_DF;
#endif
			break;
		}
		default:
		{
			sys_net.error("sys_net_bnet_setsockopt(s=%d, IPPROTO_IP): unknown option (0x%x)", lv2_id, optname);
			return -SYS_NET_EINVAL;
		}
		}
	}
	else
	{
		sys_net.error("sys_net_bnet_setsockopt(s=%d): unknown level (0x%x)", lv2_id, level);
		return -SYS_NET_EINVAL;
	}

	if (::setsockopt(native_socket, native_level, native_opt, static_cast<const char*>(native_val), native_len) == 0)
	{
		return {};
	}

	return -get_last_error(false);
}

s32 lv2_socket_native::listen(s32 backlog)
{
	std::lock_guard lock(mutex);

	if (::listen(native_socket, backlog) == 0)
	{
		return CELL_OK;
	}

	return -get_last_error(false);
}

std::optional<std::tuple<s32, std::vector<u8>, sys_net_sockaddr>> lv2_socket_native::recvfrom(s32 flags, u32 len, bool is_lock)
{
	std::unique_lock<shared_mutex> lock(mutex, std::defer_lock);

	if (is_lock)
	{
		lock.lock();
	}

	if (feign_tcp_conn_failure)
	{
		// As if just lost the connection
		feign_tcp_conn_failure = false;
		return {{-SYS_NET_ECONNRESET, {}, {}}};
	}

	int native_flags = 0;
	::sockaddr_storage native_addr{};
	::socklen_t native_addrlen = sizeof(native_addr);
	std::vector<u8> res_buf(len);

	auto& dnshook = g_fxo->get<np::dnshook>();
	if (dnshook.is_dns(lv2_id) && dnshook.is_dns_queue(lv2_id))
	{
		auto& nph = g_fxo->get<named_thread<np::np_handler>>();
		const auto packet = dnshook.get_dns_packet(lv2_id);
		ensure(packet.size() < len);
		memcpy(res_buf.data(), packet.data(), packet.size());
		native_addr.ss_family = AF_INET;
		(reinterpret_cast<::sockaddr_in*>(&native_addr))->sin_port = std::bit_cast<u16, be_t<u16>>(53); // htons(53)
		(reinterpret_cast<::sockaddr_in*>(&native_addr))->sin_addr.s_addr = nph.get_dns_ip();
		const auto sn_addr = native_addr_to_sys_net_addr(native_addr);

		return {{::narrow<s32>(packet.size()), res_buf, sn_addr}};
	}

	if (flags & SYS_NET_MSG_PEEK)
	{
		native_flags |= MSG_PEEK;
	}

	if (flags & SYS_NET_MSG_WAITALL)
	{
		native_flags |= MSG_WAITALL;
	}

	auto native_result = ::recvfrom(native_socket, reinterpret_cast<char*>(res_buf.data()), len, native_flags, reinterpret_cast<struct sockaddr*>(&native_addr), &native_addrlen);

	if (native_result >= 0)
	{
		const auto sn_addr = native_addr_to_sys_net_addr(native_addr);
		return {{::narrow<s32>(native_result), res_buf, sn_addr}};
	}
#ifdef _WIN32
	else
	{
		// Windows returns an error when trying to peek at a message and buffer not long enough to contain the whole message, should be ignored
		if ((native_flags & MSG_PEEK) && get_native_error() == WSAEMSGSIZE)
		{
			const auto sn_addr = native_addr_to_sys_net_addr(native_addr);
			return {{len, res_buf, sn_addr}};
		}
		// Windows will return WSASHUTDOWN when the connection is shutdown, POSIX just returns EOF (0) in this situation.
		if (get_native_error() == WSAESHUTDOWN)
		{
			const auto sn_addr = native_addr_to_sys_net_addr(native_addr);
			return {{0, {}, sn_addr}};
		}
	}
	const auto result = get_last_error(!so_nbio && (flags & SYS_NET_MSG_DONTWAIT) == 0, connecting);
#else
	const auto result = get_last_error(!so_nbio && (flags & SYS_NET_MSG_DONTWAIT) == 0);
#endif

	if (result)
	{
		return {{-result, {}, {}}};
	}

	return std::nullopt;
}

std::optional<s32> lv2_socket_native::sendto(s32 flags, const std::vector<u8>& buf, std::optional<sys_net_sockaddr> opt_sn_addr, bool is_lock)
{
	std::unique_lock<shared_mutex> lock(mutex, std::defer_lock);

	if (is_lock)
	{
		lock.lock();
	}

	int native_flags = 0;
	int native_result = -1;
	std::optional<sockaddr_in> native_addr = std::nullopt;

	if (opt_sn_addr)
	{
		native_addr = sys_net_addr_to_native_addr(*opt_sn_addr);
		sys_net.trace("[Native] Attempting to send to %s:%d", (*native_addr).sin_addr, std::bit_cast<be_t<u16>, u16>((*native_addr).sin_port));

		auto& nph = g_fxo->get<named_thread<np::np_handler>>();
		if (!nph.get_net_status() && is_ip_public_address(*native_addr))
		{
			return -SYS_NET_EADDRNOTAVAIL;
		}
	}
	else if (feign_tcp_conn_failure)
	{
		// As if just lost the connection
		feign_tcp_conn_failure = false;
		return -SYS_NET_ECONNRESET;
	}

	sys_net_error result{};

	if (flags & SYS_NET_MSG_WAITALL)
	{
		native_flags |= MSG_WAITALL;
	}

	auto& dnshook = g_fxo->get<np::dnshook>();
	if (opt_sn_addr && type == SYS_NET_SOCK_DGRAM && reinterpret_cast<const sys_net_sockaddr_in*>(&*opt_sn_addr)->sin_port == 53)
	{
		dnshook.add_dns_spy(lv2_id);
	}

	if (dnshook.is_dns(lv2_id))
	{
		const s32 ret_analyzer = dnshook.analyze_dns_packet(lv2_id, reinterpret_cast<const u8*>(buf.data()), ::size32(buf));

		// Check if the packet is intercepted
		if (ret_analyzer >= 0)
		{
			return {ret_analyzer};
		}
	}

	native_result = ::sendto(native_socket, reinterpret_cast<const char*>(buf.data()), ::narrow<int>(buf.size()), native_flags, native_addr ? reinterpret_cast<struct sockaddr*>(&native_addr.value()) : nullptr, native_addr ? sizeof(sockaddr_in) : 0);

	if (native_result >= 0)
	{
		return {native_result};
	}

#ifdef _WIN32
	result = get_last_error(!so_nbio && (flags & SYS_NET_MSG_DONTWAIT) == 0, connecting);
#else
	result = get_last_error(!so_nbio && (flags & SYS_NET_MSG_DONTWAIT) == 0);
#endif

	if (result)
	{
		return {-result};
	}

	// Note that this can only happen if the send buffer is full
	return std::nullopt;
}

std::optional<s32> lv2_socket_native::sendmsg(s32 flags, const sys_net_msghdr& msg, bool is_lock)
{
	std::unique_lock<shared_mutex> lock(mutex, std::defer_lock);

	if (is_lock)
	{
		lock.lock();
	}

	int native_flags = 0;
	int native_result = -1;

	sys_net_error result{};

	if (flags & SYS_NET_MSG_WAITALL)
	{
		native_flags |= MSG_WAITALL;
	}

	if (feign_tcp_conn_failure)
	{
		// As if just lost the connection
		feign_tcp_conn_failure = false;
		return {-SYS_NET_ECONNRESET};
	}

	for (int i = 0; i < msg.msg_iovlen; i++)
	{
		auto iov_base = msg.msg_iov[i].iov_base;
		const u32 len = msg.msg_iov[i].iov_len;
		const std::vector<u8> buf_copy(vm::_ptr<const char>(iov_base.addr()), vm::_ptr<const char>(iov_base.addr()) + len);

		native_result = ::send(native_socket, reinterpret_cast<const char*>(buf_copy.data()), ::narrow<int>(buf_copy.size()), native_flags);

		if (native_result >= 0)
		{
			return {native_result};
		}
	}

	result = get_last_error(!so_nbio && (flags & SYS_NET_MSG_DONTWAIT) == 0);

	if (result)
	{
		return {-result};
	}

	return std::nullopt;
}

void lv2_socket_native::close()
{
	std::lock_guard lock(mutex);

	np::close_socket(native_socket);
	native_socket = {};

	if (auto dnshook = g_fxo->try_get<np::dnshook>())
	{
		dnshook->remove_dns_spy(lv2_id);
	}

	if (bound_port && g_fxo->is_init<named_thread<np::np_handler>>())
	{
		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)
{
	std::lock_guard lock(mutex);

	if (feign_tcp_conn_failure)
	{
		// As if still connected
		return CELL_OK;
	}

#ifdef _WIN32
	const int native_how =
		how == SYS_NET_SHUT_RD ? SD_RECEIVE :
		how == SYS_NET_SHUT_WR ? SD_SEND :
								 SD_BOTH;
#else
	const int native_how =
		how == SYS_NET_SHUT_RD ? SHUT_RD :
		how == SYS_NET_SHUT_WR ? SHUT_WR :
								 SHUT_RDWR;
#endif

	if (::shutdown(native_socket, native_how) == 0)
	{
		return CELL_OK;
	}

	return -get_last_error(false);
}

s32 lv2_socket_native::poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd)
{
	// Check for fake packet for dns interceptions
	auto& dnshook = g_fxo->get<np::dnshook>();
	if (sn_pfd.events & SYS_NET_POLLIN && dnshook.is_dns(sn_pfd.fd) && dnshook.is_dns_queue(sn_pfd.fd))
	{
		sn_pfd.revents |= SYS_NET_POLLIN;
		return 1;
	}
	if (sn_pfd.events & ~(SYS_NET_POLLIN | SYS_NET_POLLOUT | SYS_NET_POLLERR))
	{
		sys_net.warning("sys_net_bnet_poll(fd=%d): events=0x%x", sn_pfd.fd, sn_pfd.events);
	}

	native_pfd.fd = native_socket;

	if (sn_pfd.events & SYS_NET_POLLIN)
	{
		native_pfd.events |= POLLIN;
	}
	if (sn_pfd.events & SYS_NET_POLLOUT)
	{
		native_pfd.events |= POLLOUT;
	}

	return 0;
}

std::tuple<bool, bool, bool> lv2_socket_native::select(bs_t<lv2_socket::poll_t> selected, pollfd& native_pfd)
{
	native_pfd.fd = native_socket;
	if (selected & lv2_socket::poll_t::read)
	{
		native_pfd.events |= POLLIN;
	}
	if (selected & lv2_socket::poll_t::write)
	{
		native_pfd.events |= POLLOUT;
	}

	return {};
}

void lv2_socket_native::set_default_buffers()
{
	// Those are the default PS3 values
	u32 default_RCVBUF = (type == SYS_NET_SOCK_STREAM) ? 65535 : 9216;
	if (::setsockopt(native_socket, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<const char*>(&default_RCVBUF), sizeof(default_RCVBUF)) != 0)
	{
		sys_net.error("Error setting default SO_RCVBUF on sys_net_bnet_socket socket");
	}
	u32 default_SNDBUF = 131072;
	if (::setsockopt(native_socket, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<const char*>(&default_SNDBUF), sizeof(default_SNDBUF)) != 0)
	{
		sys_net.error("Error setting default SO_SNDBUF on sys_net_bnet_socket socket");
	}
}

void lv2_socket_native::set_non_blocking()
{
	// Set non-blocking
	// This is done to avoid having threads stuck on blocking socket functions
	// Blocking functions just put the thread to sleep and delegate the waking up to network_thread which polls the sockets
	np::set_socket_non_blocking(native_socket);
}

bool lv2_socket_native::is_socket_connected()
{
	if (type != SYS_NET_SOCK_STREAM)
	{
		return false;
	}

	std::lock_guard lock(mutex);

	int listening = 0;
	socklen_t len = sizeof(listening);

	if (::getsockopt(native_socket, SOL_SOCKET, SO_ACCEPTCONN, reinterpret_cast<char*>(&listening), &len) == -1)
	{
		return false;
	}

	if (listening)
	{
		// Would be handled in other ways
		return false;
	}

	fd_set readfds, writefds;
	struct timeval timeout{0, 0}; // Zero timeout

	FD_ZERO(&readfds);
	FD_ZERO(&writefds);
	FD_SET(native_socket, &readfds);
	FD_SET(native_socket, &writefds);

	// Use select to check for readability and writability
	const int result = ::select(1, &readfds, &writefds, NULL, &timeout);

	if (result < 0)
	{
		// Error occurred
		return false;
	}

	// Socket is connected if it's readable or writable
	return FD_ISSET(native_socket, &readfds) || FD_ISSET(native_socket, &writefds);
}
