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<std::string> network_interface{linkage, std::string(), "network_interface",
|
||||
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};
|
||||
|
||||
// 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};
|
||||
Setting<std::string> citron_username{linkage, std::string(), "citron_username",
|
||||
Category::WebService};
|
||||
|
||||
@@ -833,6 +833,9 @@ add_library(core STATIC
|
||||
hle/service/nim/nim.h
|
||||
hle/service/npns/npns.cpp
|
||||
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.h
|
||||
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/nfp/nfp.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/nim/nim.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("nfp", [&] { NFP::LoopProcess(system); });
|
||||
kernel.RunOnGuestCoreProcess("ngc", [&] { NGC::LoopProcess(system); });
|
||||
kernel.RunOnGuestCoreProcess("nex", [&] { Nex::LoopProcess(system); });
|
||||
kernel.RunOnGuestCoreProcess("nifm", [&] { NIFM::LoopProcess(system); });
|
||||
kernel.RunOnGuestCoreProcess("nim", [&] { NIM::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)) {
|
||||
UNIMPLEMENTED_MSG("Unknown getsockopt level");
|
||||
return Errno::SUCCESS;
|
||||
LOG_WARNING(Service, "(STUBBED) Unknown getsockopt level={}, returning INVAL", level);
|
||||
return Errno::INVAL;
|
||||
}
|
||||
|
||||
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)) {
|
||||
UNIMPLEMENTED_MSG("Unknown setsockopt level");
|
||||
return Errno::SUCCESS;
|
||||
LOG_WARNING(Service, "(STUBBED) Unknown setsockopt level={}, returning INVAL", level);
|
||||
return Errno::INVAL;
|
||||
}
|
||||
|
||||
Network::SocketBase* const socket = file_descriptors[fd]->socket.get();
|
||||
|
||||
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);
|
||||
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));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
switch (optname) {
|
||||
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));
|
||||
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));
|
||||
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));
|
||||
case OptName::SNDBUF:
|
||||
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);
|
||||
return Errno::SUCCESS;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented optname={}", optname);
|
||||
return Errno::SUCCESS;
|
||||
LOG_WARNING(Service, "(STUBBED) Unimplemented optname={} (0x{:x}), returning INVAL",
|
||||
static_cast<u32>(optname), static_cast<u32>(optname));
|
||||
return Errno::INVAL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,9 +38,12 @@ Errno Translate(Network::Errno value) {
|
||||
return Errno::CONNRESET;
|
||||
case Network::Errno::INPROGRESS:
|
||||
return Errno::INPROGRESS;
|
||||
case Network::Errno::OTHER:
|
||||
// Map OTHER to INVAL as a reasonable default for unknown errors
|
||||
return Errno::INVAL;
|
||||
default:
|
||||
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;
|
||||
case WSAEINPROGRESS:
|
||||
return Errno::INPROGRESS;
|
||||
case WSAENOPROTOOPT:
|
||||
return Errno::INVAL;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented errno={}", e);
|
||||
return Errno::OTHER;
|
||||
@@ -297,6 +299,8 @@ Errno TranslateNativeError(int e, CallType call_type = CallType::Other) {
|
||||
return Errno::TIMEDOUT;
|
||||
case EINPROGRESS:
|
||||
return Errno::INPROGRESS;
|
||||
case ENOPROTOOPT:
|
||||
return Errno::INVAL;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented errno={} ({})", e, strerror(e));
|
||||
return Errno::OTHER;
|
||||
|
||||
Reference in New Issue
Block a user