mirror of
https://git.citron-emu.org/citron/emulator
synced 2025-12-20 02:53:57 +00:00
fix: socket assertion crashes and add Nex service stub
- Replace socket option assertion failures with proper error returns - Add WSAENOPROTOOPT/ENOPROTOOPT error handling - Fix LINGER and option value size validation - Add Network::Errno::OTHER translation - Implement basic Nex service stub for error code 2306-0520 Fixes crashes in Minecraft and other games when encountering unsupported socket operations or attempting to use Nintendo's online services. Signed-off-by: Zephyron <zephyron@citron-emu.org>
This commit is contained in:
@@ -663,12 +663,12 @@ struct Values {
|
|||||||
Setting<bool> airplane_mode{linkage, false, "airplane_mode", Category::Network};
|
Setting<bool> airplane_mode{linkage, false, "airplane_mode", Category::Network};
|
||||||
Setting<std::string> network_interface{linkage, std::string(), "network_interface",
|
Setting<std::string> network_interface{linkage, std::string(), "network_interface",
|
||||||
Category::Network};
|
Category::Network};
|
||||||
Setting<std::string> lobby_api_url{linkage, "https://api.ynet-fun.xyz", "lobby_api_url",
|
Setting<std::string> lobby_api_url{linkage, "api.ynet-fun.xyz", "lobby_api_url",
|
||||||
Category::Network};
|
Category::Network};
|
||||||
|
|
||||||
// WebService
|
// WebService
|
||||||
Setting<bool> enable_telemetry{linkage, false, "enable_telemetry", Category::WebService};
|
Setting<bool> enable_telemetry{linkage, false, "enable_telemetry", Category::WebService};
|
||||||
Setting<std::string> web_api_url{linkage, "https://api.ynet-fun.xyz", "web_api_url",
|
Setting<std::string> web_api_url{linkage, "api.ynet-fun.xyz", "web_api_url",
|
||||||
Category::WebService};
|
Category::WebService};
|
||||||
Setting<std::string> citron_username{linkage, std::string(), "citron_username",
|
Setting<std::string> citron_username{linkage, std::string(), "citron_username",
|
||||||
Category::WebService};
|
Category::WebService};
|
||||||
|
|||||||
@@ -833,6 +833,9 @@ add_library(core STATIC
|
|||||||
hle/service/nim/nim.h
|
hle/service/nim/nim.h
|
||||||
hle/service/npns/npns.cpp
|
hle/service/npns/npns.cpp
|
||||||
hle/service/npns/npns.h
|
hle/service/npns/npns.h
|
||||||
|
hle/service/nex/nex.cpp
|
||||||
|
hle/service/nex/nex.h
|
||||||
|
hle/service/nex/nex_results.h
|
||||||
hle/service/ns/account_proxy_interface.cpp
|
hle/service/ns/account_proxy_interface.cpp
|
||||||
hle/service/ns/account_proxy_interface.h
|
hle/service/ns/account_proxy_interface.h
|
||||||
hle/service/ns/application_manager_interface.cpp
|
hle/service/ns/application_manager_interface.cpp
|
||||||
|
|||||||
140
src/core/hle/service/nex/nex.cpp
Normal file
140
src/core/hle/service/nex/nex.cpp
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "core/hle/service/nex/nex.h"
|
||||||
|
#include "core/hle/service/nex/nex_results.h"
|
||||||
|
#include "core/hle/service/ipc_helpers.h"
|
||||||
|
#include "core/hle/service/server_manager.h"
|
||||||
|
#include "core/hle/service/service.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
|
||||||
|
namespace Service::Nex {
|
||||||
|
|
||||||
|
class INexService final : public ServiceFramework<INexService> {
|
||||||
|
public:
|
||||||
|
explicit INexService(Core::System& system_) : ServiceFramework{system_, "nex"} {
|
||||||
|
// clang-format off
|
||||||
|
static const FunctionInfo functions[] = {
|
||||||
|
{0, &INexService::Initialize, "Initialize"},
|
||||||
|
{1, &INexService::Finalize, "Finalize"},
|
||||||
|
{2, &INexService::CreateClient, "CreateClient"},
|
||||||
|
{3, &INexService::DestroyClient, "DestroyClient"},
|
||||||
|
{4, &INexService::Connect, "Connect"},
|
||||||
|
{5, &INexService::Disconnect, "Disconnect"},
|
||||||
|
{10, &INexService::GetConnectionState, "GetConnectionState"},
|
||||||
|
{11, &INexService::GetServerTime, "GetServerTime"},
|
||||||
|
{20, &INexService::CreateMatchmakeSession, "CreateMatchmakeSession"},
|
||||||
|
{21, &INexService::JoinMatchmakeSession, "JoinMatchmakeSession"},
|
||||||
|
{22, &INexService::LeaveMatchmakeSession, "LeaveMatchmakeSession"},
|
||||||
|
{30, &INexService::SendData, "SendData"},
|
||||||
|
{31, &INexService::ReceiveData, "ReceiveData"},
|
||||||
|
{40, &INexService::GetServiceURL, "GetServiceURL"},
|
||||||
|
{41, &INexService::SetServiceURL, "SetServiceURL"},
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
RegisterHandlers(functions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Initialize(HLERequestContext& ctx) {
|
||||||
|
LOG_WARNING(Service, "(STUBBED) Nex service called");
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultNexNotAvailable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Finalize(HLERequestContext& ctx) {
|
||||||
|
LOG_WARNING(Service, "(STUBBED) Nex service called");
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateClient(HLERequestContext& ctx) {
|
||||||
|
LOG_WARNING(Service, "(STUBBED) Nex service called");
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultNexNotAvailable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DestroyClient(HLERequestContext& ctx) {
|
||||||
|
LOG_WARNING(Service, "(STUBBED) Nex service called");
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Connect(HLERequestContext& ctx) {
|
||||||
|
LOG_WARNING(Service, "(STUBBED) Nex service called");
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultNexConnectionFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Disconnect(HLERequestContext& ctx) {
|
||||||
|
LOG_WARNING(Service, "(STUBBED) Nex service called");
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetConnectionState(HLERequestContext& ctx) {
|
||||||
|
LOG_WARNING(Service, "(STUBBED) Nex service called");
|
||||||
|
IPC::ResponseBuilder rb{ctx, 3};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
rb.Push<u32>(0); // Not connected
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetServerTime(HLERequestContext& ctx) {
|
||||||
|
LOG_WARNING(Service, "(STUBBED) Nex service called");
|
||||||
|
IPC::ResponseBuilder rb{ctx, 3};
|
||||||
|
rb.Push(ResultNexNotAvailable);
|
||||||
|
rb.Push<u64>(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateMatchmakeSession(HLERequestContext& ctx) {
|
||||||
|
LOG_WARNING(Service, "(STUBBED) Nex service called");
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultNexNotAvailable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void JoinMatchmakeSession(HLERequestContext& ctx) {
|
||||||
|
LOG_WARNING(Service, "(STUBBED) Nex service called");
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultNexNotAvailable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LeaveMatchmakeSession(HLERequestContext& ctx) {
|
||||||
|
LOG_WARNING(Service, "(STUBBED) Nex service called");
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendData(HLERequestContext& ctx) {
|
||||||
|
LOG_WARNING(Service, "(STUBBED) Nex service called");
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultNexNotAvailable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReceiveData(HLERequestContext& ctx) {
|
||||||
|
LOG_WARNING(Service, "(STUBBED) Nex service called");
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultNexNotAvailable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetServiceURL(HLERequestContext& ctx) {
|
||||||
|
LOG_WARNING(Service, "(STUBBED) Nex service called");
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultNexNotAvailable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetServiceURL(HLERequestContext& ctx) {
|
||||||
|
LOG_WARNING(Service, "(STUBBED) Nex service called");
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultNexNotAvailable);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void LoopProcess(Core::System& system) {
|
||||||
|
auto server_manager = std::make_unique<ServerManager>(system);
|
||||||
|
|
||||||
|
server_manager->RegisterNamedService("nex", std::make_shared<INexService>(system));
|
||||||
|
ServerManager::RunServer(std::move(server_manager));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Service::Nex
|
||||||
12
src/core/hle/service/nex/nex.h
Normal file
12
src/core/hle/service/nex/nex.h
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
class System;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Service::Nex {
|
||||||
|
void LoopProcess(Core::System& system);
|
||||||
|
} // namespace Service::Nex
|
||||||
16
src/core/hle/service/nex/nex_results.h
Normal file
16
src/core/hle/service/nex/nex_results.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/hle/result.h"
|
||||||
|
|
||||||
|
namespace Service::Nex {
|
||||||
|
|
||||||
|
constexpr Result ResultNexNotAvailable{ErrorModule::Nex, 520};
|
||||||
|
constexpr Result ResultNexConnectionFailed{ErrorModule::Nex, 1};
|
||||||
|
constexpr Result ResultNexTimeout{ErrorModule::Nex, 2};
|
||||||
|
constexpr Result ResultNexInvalidState{ErrorModule::Nex, 3};
|
||||||
|
constexpr Result ResultNexNotInitialized{ErrorModule::Nex, 4};
|
||||||
|
|
||||||
|
} // namespace Service::Nex
|
||||||
@@ -38,6 +38,7 @@
|
|||||||
#include "core/hle/service/nfc/nfc.h"
|
#include "core/hle/service/nfc/nfc.h"
|
||||||
#include "core/hle/service/nfp/nfp.h"
|
#include "core/hle/service/nfp/nfp.h"
|
||||||
#include "core/hle/service/ngc/ngc.h"
|
#include "core/hle/service/ngc/ngc.h"
|
||||||
|
#include "core/hle/service/nex/nex.h"
|
||||||
#include "core/hle/service/nifm/nifm.h"
|
#include "core/hle/service/nifm/nifm.h"
|
||||||
#include "core/hle/service/nim/nim.h"
|
#include "core/hle/service/nim/nim.h"
|
||||||
#include "core/hle/service/npns/npns.h"
|
#include "core/hle/service/npns/npns.h"
|
||||||
@@ -114,6 +115,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
|
|||||||
kernel.RunOnGuestCoreProcess("nfc", [&] { NFC::LoopProcess(system); });
|
kernel.RunOnGuestCoreProcess("nfc", [&] { NFC::LoopProcess(system); });
|
||||||
kernel.RunOnGuestCoreProcess("nfp", [&] { NFP::LoopProcess(system); });
|
kernel.RunOnGuestCoreProcess("nfp", [&] { NFP::LoopProcess(system); });
|
||||||
kernel.RunOnGuestCoreProcess("ngc", [&] { NGC::LoopProcess(system); });
|
kernel.RunOnGuestCoreProcess("ngc", [&] { NGC::LoopProcess(system); });
|
||||||
|
kernel.RunOnGuestCoreProcess("nex", [&] { Nex::LoopProcess(system); });
|
||||||
kernel.RunOnGuestCoreProcess("nifm", [&] { NIFM::LoopProcess(system); });
|
kernel.RunOnGuestCoreProcess("nifm", [&] { NIFM::LoopProcess(system); });
|
||||||
kernel.RunOnGuestCoreProcess("nim", [&] { NIM::LoopProcess(system); });
|
kernel.RunOnGuestCoreProcess("nim", [&] { NIM::LoopProcess(system); });
|
||||||
kernel.RunOnGuestCoreProcess("npns", [&] { NPNS::LoopProcess(system); });
|
kernel.RunOnGuestCoreProcess("npns", [&] { NPNS::LoopProcess(system); });
|
||||||
|
|||||||
@@ -788,8 +788,8 @@ Errno BSD::GetSockOptImpl(s32 fd, u32 level, OptName optname, std::vector<u8>& o
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (level != static_cast<u32>(SocketLevel::SOCKET)) {
|
if (level != static_cast<u32>(SocketLevel::SOCKET)) {
|
||||||
UNIMPLEMENTED_MSG("Unknown getsockopt level");
|
LOG_WARNING(Service, "(STUBBED) Unknown getsockopt level={}, returning INVAL", level);
|
||||||
return Errno::SUCCESS;
|
return Errno::INVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
Network::SocketBase* const socket = file_descriptors[fd]->socket.get();
|
Network::SocketBase* const socket = file_descriptors[fd]->socket.get();
|
||||||
@@ -819,32 +819,52 @@ Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, std::span<const u8
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (level != static_cast<u32>(SocketLevel::SOCKET)) {
|
if (level != static_cast<u32>(SocketLevel::SOCKET)) {
|
||||||
UNIMPLEMENTED_MSG("Unknown setsockopt level");
|
LOG_WARNING(Service, "(STUBBED) Unknown setsockopt level={}, returning INVAL", level);
|
||||||
return Errno::SUCCESS;
|
return Errno::INVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
Network::SocketBase* const socket = file_descriptors[fd]->socket.get();
|
Network::SocketBase* const socket = file_descriptors[fd]->socket.get();
|
||||||
|
|
||||||
if (optname == OptName::LINGER) {
|
if (optname == OptName::LINGER) {
|
||||||
ASSERT(optval.size() == sizeof(Linger));
|
if (optval.size() != sizeof(Linger)) {
|
||||||
|
LOG_WARNING(Service, "LINGER optval size mismatch: expected {}, got {}", sizeof(Linger),
|
||||||
|
optval.size());
|
||||||
|
return Errno::INVAL;
|
||||||
|
}
|
||||||
auto linger = GetValue<Linger>(optval);
|
auto linger = GetValue<Linger>(optval);
|
||||||
ASSERT(linger.onoff == 0 || linger.onoff == 1);
|
if (linger.onoff != 0 && linger.onoff != 1) {
|
||||||
|
LOG_WARNING(Service, "Invalid LINGER onoff value: {}", linger.onoff);
|
||||||
|
return Errno::INVAL;
|
||||||
|
}
|
||||||
|
|
||||||
return Translate(socket->SetLinger(linger.onoff != 0, linger.linger));
|
return Translate(socket->SetLinger(linger.onoff != 0, linger.linger));
|
||||||
}
|
}
|
||||||
|
|
||||||
ASSERT(optval.size() == sizeof(u32));
|
if (optval.size() != sizeof(u32)) {
|
||||||
|
LOG_WARNING(Service, "optval size mismatch: expected {}, got {} for optname={}", sizeof(u32),
|
||||||
|
optval.size(), static_cast<u32>(optname));
|
||||||
|
return Errno::INVAL;
|
||||||
|
}
|
||||||
auto value = GetValue<u32>(optval);
|
auto value = GetValue<u32>(optval);
|
||||||
|
|
||||||
switch (optname) {
|
switch (optname) {
|
||||||
case OptName::REUSEADDR:
|
case OptName::REUSEADDR:
|
||||||
ASSERT(value == 0 || value == 1);
|
if (value != 0 && value != 1) {
|
||||||
|
LOG_WARNING(Service, "Invalid REUSEADDR value: {}", value);
|
||||||
|
return Errno::INVAL;
|
||||||
|
}
|
||||||
return Translate(socket->SetReuseAddr(value != 0));
|
return Translate(socket->SetReuseAddr(value != 0));
|
||||||
case OptName::KEEPALIVE:
|
case OptName::KEEPALIVE:
|
||||||
ASSERT(value == 0 || value == 1);
|
if (value != 0 && value != 1) {
|
||||||
|
LOG_WARNING(Service, "Invalid KEEPALIVE value: {}", value);
|
||||||
|
return Errno::INVAL;
|
||||||
|
}
|
||||||
return Translate(socket->SetKeepAlive(value != 0));
|
return Translate(socket->SetKeepAlive(value != 0));
|
||||||
case OptName::BROADCAST:
|
case OptName::BROADCAST:
|
||||||
ASSERT(value == 0 || value == 1);
|
if (value != 0 && value != 1) {
|
||||||
|
LOG_WARNING(Service, "Invalid BROADCAST value: {}", value);
|
||||||
|
return Errno::INVAL;
|
||||||
|
}
|
||||||
return Translate(socket->SetBroadcast(value != 0));
|
return Translate(socket->SetBroadcast(value != 0));
|
||||||
case OptName::SNDBUF:
|
case OptName::SNDBUF:
|
||||||
return Translate(socket->SetSndBuf(value));
|
return Translate(socket->SetSndBuf(value));
|
||||||
@@ -858,8 +878,9 @@ Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, std::span<const u8
|
|||||||
LOG_WARNING(Service, "(STUBBED) setting NOSIGPIPE to {}", value);
|
LOG_WARNING(Service, "(STUBBED) setting NOSIGPIPE to {}", value);
|
||||||
return Errno::SUCCESS;
|
return Errno::SUCCESS;
|
||||||
default:
|
default:
|
||||||
UNIMPLEMENTED_MSG("Unimplemented optname={}", optname);
|
LOG_WARNING(Service, "(STUBBED) Unimplemented optname={} (0x{:x}), returning INVAL",
|
||||||
return Errno::SUCCESS;
|
static_cast<u32>(optname), static_cast<u32>(optname));
|
||||||
|
return Errno::INVAL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,9 +38,12 @@ Errno Translate(Network::Errno value) {
|
|||||||
return Errno::CONNRESET;
|
return Errno::CONNRESET;
|
||||||
case Network::Errno::INPROGRESS:
|
case Network::Errno::INPROGRESS:
|
||||||
return Errno::INPROGRESS;
|
return Errno::INPROGRESS;
|
||||||
|
case Network::Errno::OTHER:
|
||||||
|
// Map OTHER to INVAL as a reasonable default for unknown errors
|
||||||
|
return Errno::INVAL;
|
||||||
default:
|
default:
|
||||||
UNIMPLEMENTED_MSG("Unimplemented errno={}", value);
|
UNIMPLEMENTED_MSG("Unimplemented errno={}", value);
|
||||||
return Errno::SUCCESS;
|
return Errno::INVAL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -158,6 +158,8 @@ Errno TranslateNativeError(int e, CallType call_type = CallType::Other) {
|
|||||||
return Errno::TIMEDOUT;
|
return Errno::TIMEDOUT;
|
||||||
case WSAEINPROGRESS:
|
case WSAEINPROGRESS:
|
||||||
return Errno::INPROGRESS;
|
return Errno::INPROGRESS;
|
||||||
|
case WSAENOPROTOOPT:
|
||||||
|
return Errno::INVAL;
|
||||||
default:
|
default:
|
||||||
UNIMPLEMENTED_MSG("Unimplemented errno={}", e);
|
UNIMPLEMENTED_MSG("Unimplemented errno={}", e);
|
||||||
return Errno::OTHER;
|
return Errno::OTHER;
|
||||||
@@ -297,6 +299,8 @@ Errno TranslateNativeError(int e, CallType call_type = CallType::Other) {
|
|||||||
return Errno::TIMEDOUT;
|
return Errno::TIMEDOUT;
|
||||||
case EINPROGRESS:
|
case EINPROGRESS:
|
||||||
return Errno::INPROGRESS;
|
return Errno::INPROGRESS;
|
||||||
|
case ENOPROTOOPT:
|
||||||
|
return Errno::INVAL;
|
||||||
default:
|
default:
|
||||||
UNIMPLEMENTED_MSG("Unimplemented errno={} ({})", e, strerror(e));
|
UNIMPLEMENTED_MSG("Unimplemented errno={} ({})", e, strerror(e));
|
||||||
return Errno::OTHER;
|
return Errno::OTHER;
|
||||||
|
|||||||
Reference in New Issue
Block a user