diff options
| -rw-r--r-- | src/core/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/core/hle/kernel/k_address_space_info.cpp | 6 | ||||
| -rw-r--r-- | src/core/hle/kernel/svc/svc_physical_memory.cpp | 7 | ||||
| -rw-r--r-- | src/core/hle/service/audio/nx2_audio_user_client.h | 16 | ||||
| -rw-r--r-- | src/core/hle/service/nx2_streamplay.h | 65 | ||||
| -rw-r--r-- | src/core/hle/service/sm/sm.cpp | 9 | ||||
| -rw-r--r-- | src/qt_common/game_list/model.cpp | 23 | ||||
| -rw-r--r-- | src/qt_common/game_list/model.h | 1 | ||||
| -rw-r--r-- | src/qt_common/game_list/worker.cpp | 12 | ||||
| -rw-r--r-- | src/qt_common/game_list/worker.h | 3 |
10 files changed, 132 insertions, 11 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 56fa653738..0075838d49 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1036,6 +1036,7 @@ add_library(core STATIC hle/service/server_manager.h hle/service/service.cpp hle/service/service.h + hle/service/nx2_streamplay.h hle/service/nx2_stub_interface.h hle/service/nx2_gsv.h hle/service/services.cpp diff --git a/src/core/hle/kernel/k_address_space_info.cpp b/src/core/hle/kernel/k_address_space_info.cpp index 23258071e1..13a669cff5 100644 --- a/src/core/hle/kernel/k_address_space_info.cpp +++ b/src/core/hle/kernel/k_address_space_info.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -32,7 +35,8 @@ constexpr std::array<KAddressSpaceInfo, 13> AddressSpaceInfos{{ { .bit_width = 39, .address = 128_MiB , .size = 512_GiB - 128_MiB, .type = KAddressSpaceInfo::Type::Map39Bit, }, #endif { .bit_width = 39, .address = Size_Invalid, .size = 64_GiB , .type = KAddressSpaceInfo::Type::MapSmall }, - { .bit_width = 39, .address = Size_Invalid, .size = 8_GiB , .type = KAddressSpaceInfo::Type::Heap, }, + // Switch 2 titles size their heap from up to 12 GiB of memory, which doesn't fit Switch 1's 8 GiB. + { .bit_width = 39, .address = Size_Invalid, .size = 16_GiB , .type = KAddressSpaceInfo::Type::Heap, }, { .bit_width = 39, .address = Size_Invalid, .size = 64_GiB , .type = KAddressSpaceInfo::Type::Alias, }, { .bit_width = 39, .address = Size_Invalid, .size = 2_GiB , .type = KAddressSpaceInfo::Type::Stack, }, }}; diff --git a/src/core/hle/kernel/svc/svc_physical_memory.cpp b/src/core/hle/kernel/svc/svc_physical_memory.cpp index 4fde85fdaa..280731e488 100644 --- a/src/core/hle/kernel/svc/svc_physical_memory.cpp +++ b/src/core/hle/kernel/svc/svc_physical_memory.cpp @@ -4,10 +4,12 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include <algorithm> #include <cstdlib> #include "core/core.h" #include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_system_control.h" #include "core/hle/kernel/svc.h" namespace Kernel::Svc { @@ -18,7 +20,10 @@ Result SetHeapSize(Core::System& system, u64* out_address, u64 size) { // Validate size. R_UNLESS(Common::IsAligned(size, HeapSizeAlignment), ResultInvalidSize); - R_UNLESS(size < MainMemorySizeMax, ResultInvalidSize); + // Switch 2 titles size their heap from the total memory, which exceeds Switch 1's 8 GiB. + const size_t max_heap_size = + (std::max)(MainMemorySizeMax, KSystemControl::Init::GetIntendedMemorySize()); + R_UNLESS(size < max_heap_size, ResultInvalidSize); // Set the heap size. KProcessAddress address{}; diff --git a/src/core/hle/service/audio/nx2_audio_user_client.h b/src/core/hle/service/audio/nx2_audio_user_client.h index 2d1a158c84..f053ad62af 100644 --- a/src/core/hle/service/audio/nx2_audio_user_client.h +++ b/src/core/hle/service/audio/nx2_audio_user_client.h @@ -14,14 +14,16 @@ namespace Service::Audio { // Switch 2 "aud:u" (nn::audio::CreateUserClientSessionByHipc). The SDK reaches the Switch 1 // audio interfaces through it: command 2 returns an object used exactly like // IAudioRendererManager (vtable slot 1 = GetWorkBufferSize with a Switch 1 parameter block), so -// it is backed by Eden's audren:u implementation. The nx2: name keeps unknown commands -// auto-stubbed and logged. +// it is backed by Eden's audren:u implementation. Command 1 is nn::audio::OpenAudioOut's +// manager, called like IAudioOutManager (slot 1 = OpenAudioOut), backed by audout:u. The nx2: name +// keeps unknown commands auto-stubbed and logged. class INx2AudioUserClient final : public ServiceFramework<INx2AudioUserClient> { public: explicit INx2AudioUserClient(Core::System& system_) : ServiceFramework{system_, "nx2:aud:u"} { // clang-format off static const FunctionInfo functions[] = { + {1, &INx2AudioUserClient::GetAudioOutManager, "GetAudioOutManager (guessed)"}, {2, &INx2AudioUserClient::GetAudioRendererManager, "GetAudioRendererManager (guessed)"}, }; // clang-format on @@ -29,6 +31,16 @@ public: } private: + void GetAudioOutManager(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u32 arg = rp.Pop<u32>(); + LOG_WARNING(Service_Audio, "(STUBBED) Switch 2 aud:u 1 arg={}, returning IAudioOutManager", + arg); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface<IAudioOutManager>(ctx, system); + } + void GetAudioRendererManager(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u32 arg = rp.Pop<u32>(); diff --git a/src/core/hle/service/nx2_streamplay.h b/src/core/hle/service/nx2_streamplay.h new file mode 100644 index 0000000000..4dc839d04b --- /dev/null +++ b/src/core/hle/service/nx2_streamplay.h @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "core/hle/kernel/k_event.h" +#include "core/hle/service/ipc_helpers.h" +#include "core/hle/service/kernel_helpers.h" +#include "core/hle/service/service.h" + +namespace Service { + +// User service returned by stpl:u 0 (nn::streamplay::detail::GetUserService). The SDK attaches a +// system event to the handle command 300 returns right after opening it, so that one needs a real +// event; everything else falls through to the nx2 auto stub (zero sizes, nothing streaming). +class INx2StreamPlayUserService final : public ServiceFramework<INx2StreamPlayUserService> { +public: + explicit INx2StreamPlayUserService(Core::System& system_) + : ServiceFramework{system_, "nx2:stpl:u::0"}, service_context{system_, "stpl:u"} { + static const FunctionInfo functions[] = { + {300, &INx2StreamPlayUserService::GetEvent, "GetEvent (guessed)"}, + }; + RegisterHandlers(functions); + event = service_context.CreateEvent("INx2StreamPlayUserService:Event"); + } + + ~INx2StreamPlayUserService() override { + service_context.CloseEvent(event); + } + +private: + void GetEvent(HLERequestContext& ctx) { + LOG_WARNING(Service, "(STUBBED) stpl:u::0 {}, returning an event", ctx.GetCommand()); + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(ctx, event->GetReadableEvent()); + } + + KernelHelpers::ServiceContext service_context; + Kernel::KEvent* event; +}; + +// Switch 2 stream play (GameShare), stpl:u. Command 0 (u32 0x12) opens the user service the SDK +// keeps for the process; without an object the first nn::streamplay call jumps through null. +class INx2StreamPlayService final : public ServiceFramework<INx2StreamPlayService> { +public: + explicit INx2StreamPlayService(Core::System& system_) + : ServiceFramework{system_, "nx2:stpl:u"} { + static const FunctionInfo functions[] = { + {0, &INx2StreamPlayService::GetUserService, "GetUserService (guessed)"}, + }; + RegisterHandlers(functions); + } + +private: + void GetUserService(HLERequestContext& ctx) { + LOG_WARNING(Service, "(STUBBED) stpl:u 0 [8]={:#x}, returning a user service", + ctx.CommandBuffer()[8]); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface<INx2StreamPlayUserService>(ctx, system); + } +}; + +} // namespace Service diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp index 60f0dd89cb..406d6c9028 100644 --- a/src/core/hle/service/sm/sm.cpp +++ b/src/core/hle/service/sm/sm.cpp @@ -23,6 +23,7 @@ #include "core/hle/service/cmif_types.h" #include "core/hle/service/hle_ipc.h" #include "core/hle/service/ipc_helpers.h" +#include "core/hle/service/nx2_streamplay.h" #include "core/hle/service/nx2_stub_interface.h" #include "core/hle/service/server_manager.h" #include "core/hle/service/sm/sm.h" @@ -194,8 +195,12 @@ Kernel::KClientSession* CreateStubSession(Core::System& system, HLERequestContex auto manager = std::make_shared<SessionRequestManager>(kernel, ctx.GetManager()->GetServerManager()); - manager->SetSessionHandler(std::make_shared<INx2StubInterface>( - system, INx2StubInterface::InternName(fmt::format("{}{}", Nx2StubPrefix, name)))); + if (name == "stpl:u") { + manager->SetSessionHandler(std::make_shared<INx2StreamPlayService>(system)); + } else { + manager->SetSessionHandler(std::make_shared<INx2StubInterface>( + system, INx2StubInterface::InternName(fmt::format("{}{}", Nx2StubPrefix, name)))); + } ctx.GetManager()->GetServerManager().RegisterSession(&session->GetServerSession(), manager); return &session->GetClientSession(); } diff --git a/src/qt_common/game_list/model.cpp b/src/qt_common/game_list/model.cpp index 708ef576c6..4f3916b04d 100644 --- a/src/qt_common/game_list/model.cpp +++ b/src/qt_common/game_list/model.cpp @@ -49,7 +49,7 @@ GameListModel::~GameListModel() = default; void GameListModel::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) { emit PopulatingStarted(); - current_worker.reset(); + StopWorker(); removeRows(0, rowCount()); current_worker = std::make_unique<GameListWorker>(vfs, provider, game_dirs, @@ -157,8 +157,27 @@ void GameListModel::RemoveFavorite(u64 program_id) { } } +void GameListModel::StopWorker() { + if (!current_worker) { + return; + } + + // The worker may still be inside ProcessEvents further up the stack (a nested event loop + // can trigger a repopulate), so stop it now but defer freeing it to the event loop. + current_worker->disconnect(); + current_worker->Stop(); + current_worker.release()->deleteLater(); +} + void GameListModel::Repopulate() { - current_worker.reset(); + // CreateFactories replaces the content providers a running game reads from. Stopping + // emulation repopulates the list anyway, so skip watcher-driven reloads until then. + if (QtCommon::system->IsPoweredOn()) { + LOG_INFO(Frontend, "Emulation is running, deferring game list reload."); + return; + } + + StopWorker(); QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs); PopulateAsync(UISettings::values.game_dirs); } diff --git a/src/qt_common/game_list/model.h b/src/qt_common/game_list/model.h index 98614ae33a..b850895d2f 100644 --- a/src/qt_common/game_list/model.h +++ b/src/qt_common/game_list/model.h @@ -78,6 +78,7 @@ private: void AddFavorite(u64 program_id); void RemoveFavorite(u64 program_id); void Repopulate(); + void StopWorker(); bool m_flat = false; diff --git a/src/qt_common/game_list/worker.cpp b/src/qt_common/game_list/worker.cpp index 14afe2f276..d18f84089a 100644 --- a/src/qt_common/game_list/worker.cpp +++ b/src/qt_common/game_list/worker.cpp @@ -313,12 +313,18 @@ GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_, GameListWorker::~GameListWorker() { this->disconnect(); - stop_requested.store(true); - processing_completed.Wait(); + Stop(); +} + +void GameListWorker::Stop() { + if (!stop_requested.exchange(true)) { + processing_completed.Wait(); + } } void GameListWorker::ProcessEvents(GameListModel* model) { - while (true) { + // An event may spin a nested event loop that replaces this worker, in which case stop. + while (model->current_worker.get() == this) { std::function<void(GameListModel*)> func; { // Lock queue to protect concurrent modification. diff --git a/src/qt_common/game_list/worker.h b/src/qt_common/game_list/worker.h index c007498d4b..cbc1fa7021 100644 --- a/src/qt_common/game_list/worker.h +++ b/src/qt_common/game_list/worker.h @@ -52,6 +52,9 @@ public: /// Starts the processing of directory tree information. void run() override; + /// Requests the worker to stop and blocks until processing has finished. + void Stop(); + public: /** * Synchronously processes any events queued by the worker. |
