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:
Zephyron
2025-11-13 19:30:40 +10:00
parent ba9dead3ee
commit 19faff40cd
9 changed files with 216 additions and 15 deletions

View File

@@ -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};

View File

@@ -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

View 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

View 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

View 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

View File

@@ -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); });

View File

@@ -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;
} }
} }

View File

@@ -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;
} }
} }

View File

@@ -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;