mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-31 18:46:08 +00:00
c0a85d0e53
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions). - [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md) - [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability. ------------------- This one deserves a long story, but imma try to resume: During investigating Absolum 1.2 black screen of death upon loading intro video, i've discovered guest was aborting for failing to allocate room for the video. By logging everything prior to crash and decoding guest side instructions managed to confirm its media allocator was reading data faster than it was updating free available bucket list. Since the IStorage::Read was happening 247 times before the crash, i've decided to add a very small sleep there, and boom, not only Absolum but some other titles got the same issue fixed. But i was unsatisfied with the sleep and kept tracking guest instructions upstream in order to find a sync point for the read worker and the memory allocation update. But unfortunately the media allocator helpers live in guest, accessing memory directly via MMU, so any sync signaling would need to come from some dynarmic hack. It's been 6 days now, so i've decided to polish the sleep: Moved it upstream to where i could have access for proper predicate, and added machinery to service and nvhost to support that. Now the sleep is restricted only for nvdec istorage reads. Any other reads will flow normally. TL;DR: currently our code is so blazing async that guest is capable to request reads before its very self refresh it have freed room to do so. The sleep accepted as broadly stable was 600 us (MICROseconds), and it affects ONLY nvdec chunk reading. Reports confirm that now videos are smoother now. Code was polished at my knowledge limits. Mostly machinery to track when a request comes from a process with nvdec active, and is istorage read. I can provide more details if it comes to be needed. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4316 Reviewed-by: MaranBr <maranbr@eden-emu.dev> Reviewed-by: Samuel <lizzie@eden-emu.dev>
1042 lines
32 KiB
C++
1042 lines
32 KiB
C++
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <memory>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
|
|
#include "game_settings.h"
|
|
#include "audio_core/audio_core.h"
|
|
#include "common/fs/fs.h"
|
|
#include "common/logging.h"
|
|
#include "common/adpf.h"
|
|
#include "common/settings.h"
|
|
#include "common/settings_enums.h"
|
|
#include "common/string_util.h"
|
|
#include "core/arm/exclusive_monitor.h"
|
|
#include "core/core.h"
|
|
|
|
#include "launch_timestamp_cache.h"
|
|
#include "core/core_timing.h"
|
|
#include "core/cpu_manager.h"
|
|
#include "core/debugger/debugger.h"
|
|
#include "core/device_memory.h"
|
|
#include "core/file_sys/fs_filesystem.h"
|
|
#include "core/file_sys/patch_manager.h"
|
|
#include "core/file_sys/registered_cache.h"
|
|
#include "core/file_sys/romfs_factory.h"
|
|
#include "core/file_sys/savedata_factory.h"
|
|
#include "core/file_sys/vfs/vfs_concat.h"
|
|
#include "core/file_sys/vfs/vfs_real.h"
|
|
#include "core/gpu_dirty_memory_manager.h"
|
|
#include "core/hle/kernel/k_memory_manager.h"
|
|
#include "core/hle/kernel/k_process.h"
|
|
#include "core/hle/kernel/k_resource_limit.h"
|
|
#include "core/hle/kernel/k_scheduler.h"
|
|
#include "core/hle/kernel/kernel.h"
|
|
#include "core/hle/kernel/physical_core.h"
|
|
#include "core/hle/service/acc/profile_manager.h"
|
|
#include "core/hle/service/am/applet_manager.h"
|
|
#include "core/hle/service/am/frontend/applets.h"
|
|
#include "core/hle/service/am/process_creation.h"
|
|
#include "core/hle/service/apm/apm_controller.h"
|
|
#include "core/hle/service/filesystem/filesystem.h"
|
|
#include "core/hle/service/glue/glue_manager.h"
|
|
#include "core/hle/service/glue/time/static.h"
|
|
#include "core/hle/service/psc/time/static.h"
|
|
#include "core/hle/service/psc/time/steady_clock.h"
|
|
#include "core/hle/service/psc/time/system_clock.h"
|
|
#include "core/hle/service/psc/time/time_zone_service.h"
|
|
#include "core/hle/service/service.h"
|
|
#include "core/hle/service/services.h"
|
|
#include "core/hle/service/set/system_settings_server.h"
|
|
#include "core/hle/service/sm/sm.h"
|
|
#include "core/internal_network/network.h"
|
|
#include "core/loader/loader.h"
|
|
#include "core/memory.h"
|
|
#include "core/memory/cheat_engine.h"
|
|
#include "core/perf_stats.h"
|
|
#include "core/reporter.h"
|
|
#include "core/tools/freezer.h"
|
|
#include "core/tools/renderdoc.h"
|
|
#include "hid_core/hid_core.h"
|
|
#include "network/network.h"
|
|
#include "video_core/host1x/host1x.h"
|
|
#include "video_core/renderer_base.h"
|
|
#include "video_core/video_core.h"
|
|
|
|
namespace Core {
|
|
|
|
FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
|
|
const std::string& path) {
|
|
// To account for split 00+01+etc files.
|
|
std::string dir_name;
|
|
std::string filename;
|
|
Common::SplitPath(path, &dir_name, &filename, nullptr);
|
|
|
|
if (filename == "00") {
|
|
const auto dir = vfs->OpenDirectory(dir_name, FileSys::OpenMode::Read);
|
|
std::vector<FileSys::VirtualFile> concat;
|
|
|
|
for (u32 i = 0; i < 0x10; ++i) {
|
|
const auto file_name = fmt::format("{:02X}", i);
|
|
auto next = dir->GetFile(file_name);
|
|
|
|
if (next != nullptr) {
|
|
concat.push_back(std::move(next));
|
|
} else {
|
|
next = dir->GetFile(file_name);
|
|
|
|
if (next == nullptr) {
|
|
break;
|
|
}
|
|
|
|
concat.push_back(std::move(next));
|
|
}
|
|
}
|
|
|
|
return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(dir->GetName(),
|
|
std::move(concat));
|
|
}
|
|
|
|
if (Common::FS::IsDir(path)) {
|
|
return vfs->OpenFile(path + "/main", FileSys::OpenMode::Read);
|
|
}
|
|
|
|
return vfs->OpenFile(path, FileSys::OpenMode::Read);
|
|
}
|
|
|
|
struct System::Impl {
|
|
explicit Impl(System& system)
|
|
: kernel{system}, fs_controller{system}, hid_core{kernel}, cpu_manager{system},
|
|
reporter{system}, applet_manager{system}, frontend_applets{system}, profile_manager{} {}
|
|
|
|
u64 program_id;
|
|
|
|
void Initialize(System& system) {
|
|
device_memory.emplace();
|
|
|
|
is_multicore = Settings::values.use_multi_core.GetValue();
|
|
extended_memory_layout = Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
|
|
|
|
core_timing.SetMulticore(is_multicore);
|
|
core_timing.Initialize([&system]() { system.RegisterHostThread(); });
|
|
|
|
// Create a default fs if one doesn't already exist.
|
|
if (virtual_filesystem == nullptr) {
|
|
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
|
|
}
|
|
if (content_provider == nullptr) {
|
|
content_provider = std::make_unique<FileSys::ContentProviderUnion>();
|
|
}
|
|
|
|
// Create default implementations of applets if one is not provided.
|
|
frontend_applets.SetDefaultAppletsIfMissing();
|
|
|
|
auto const is_async_gpu = Settings::values.use_asynchronous_gpu_emulation.GetValue();
|
|
|
|
kernel.SetMulticore(is_multicore);
|
|
cpu_manager.SetMulticore(is_multicore);
|
|
cpu_manager.SetAsyncGpu(is_async_gpu);
|
|
}
|
|
|
|
void ReinitializeIfNecessary(System& system) {
|
|
const bool must_reinitialize =
|
|
!device_memory.has_value() ||
|
|
is_multicore != Settings::values.use_multi_core.GetValue() ||
|
|
extended_memory_layout != (Settings::values.memory_layout_mode.GetValue() !=
|
|
Settings::MemoryLayout::Memory_4Gb);
|
|
|
|
if (!must_reinitialize) {
|
|
return;
|
|
}
|
|
|
|
LOG_DEBUG(Kernel, "Re-initializing");
|
|
|
|
is_multicore = Settings::values.use_multi_core.GetValue();
|
|
extended_memory_layout =
|
|
Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
|
|
|
|
Initialize(system);
|
|
}
|
|
|
|
void RefreshTime(System& system) {
|
|
if (!system.IsPoweredOn()) {
|
|
return;
|
|
}
|
|
|
|
auto settings_service =
|
|
system.ServiceManager().GetService<Service::Set::ISystemSettingsServer>("set:sys",
|
|
true);
|
|
auto static_service_a =
|
|
system.ServiceManager().GetService<Service::Glue::Time::StaticService>("time:a", true);
|
|
|
|
auto static_service_s =
|
|
system.ServiceManager().GetService<Service::PSC::Time::StaticService>("time:s", true);
|
|
|
|
std::shared_ptr<Service::PSC::Time::SystemClock> user_clock;
|
|
static_service_a->GetStandardUserSystemClock(&user_clock);
|
|
|
|
std::shared_ptr<Service::PSC::Time::SystemClock> local_clock;
|
|
static_service_a->GetStandardLocalSystemClock(&local_clock);
|
|
|
|
std::shared_ptr<Service::PSC::Time::SystemClock> network_clock;
|
|
static_service_s->GetStandardNetworkSystemClock(&network_clock);
|
|
|
|
std::shared_ptr<Service::Glue::Time::TimeZoneService> timezone_service;
|
|
static_service_a->GetTimeZoneService(&timezone_service);
|
|
|
|
Service::PSC::Time::LocationName name{};
|
|
auto new_name = Settings::GetTimeZoneString(Settings::values.time_zone_index.GetValue());
|
|
std::memcpy(name.data(), new_name.data(), (std::min)(name.size(), new_name.size()));
|
|
|
|
timezone_service->SetDeviceLocationName(name);
|
|
|
|
u64 time_offset = 0;
|
|
if (Settings::values.custom_rtc_enabled) {
|
|
time_offset = Settings::values.custom_rtc_offset.GetValue();
|
|
}
|
|
|
|
const auto posix_time = std::chrono::system_clock::now().time_since_epoch();
|
|
const u64 current_time =
|
|
+std::chrono::duration_cast<std::chrono::seconds>(posix_time).count();
|
|
const u64 new_time = current_time + time_offset;
|
|
|
|
Service::PSC::Time::SystemClockContext context{};
|
|
settings_service->SetUserSystemClockContext(context);
|
|
user_clock->SetCurrentTime(new_time);
|
|
|
|
local_clock->SetCurrentTime(new_time);
|
|
|
|
network_clock->GetSystemClockContext(&context);
|
|
settings_service->SetNetworkSystemClockContext(context);
|
|
network_clock->SetCurrentTime(new_time);
|
|
}
|
|
|
|
void Run() {
|
|
std::unique_lock<std::mutex> lk(suspend_guard);
|
|
|
|
kernel.SuspendEmulation(false);
|
|
core_timing.SyncPause(false);
|
|
is_paused.store(false, std::memory_order_relaxed);
|
|
}
|
|
|
|
void Pause() {
|
|
std::unique_lock<std::mutex> lk(suspend_guard);
|
|
|
|
core_timing.SyncPause(true);
|
|
kernel.SuspendEmulation(true);
|
|
is_paused.store(true, std::memory_order_relaxed);
|
|
}
|
|
|
|
bool IsPaused() const {
|
|
return is_paused.load(std::memory_order_relaxed);
|
|
}
|
|
|
|
std::unique_lock<std::mutex> StallApplication() {
|
|
std::unique_lock<std::mutex> lk(suspend_guard);
|
|
kernel.SuspendEmulation(true);
|
|
core_timing.SyncPause(true);
|
|
return lk;
|
|
}
|
|
|
|
void UnstallApplication() {
|
|
if (!IsPaused()) {
|
|
core_timing.SyncPause(false);
|
|
kernel.SuspendEmulation(false);
|
|
}
|
|
}
|
|
|
|
void NotifyNVDECChannelOpen(u64 process_id) {
|
|
std::scoped_lock lock{nvdec_active_mutex};
|
|
++nvdec_active_channels[process_id];
|
|
}
|
|
|
|
void NotifyNVDECChannelClose(u64 process_id) {
|
|
std::scoped_lock lock{nvdec_active_mutex};
|
|
const auto it = nvdec_active_channels.find(process_id);
|
|
if (it == nvdec_active_channels.end()) {
|
|
return;
|
|
}
|
|
if (--it->second == 0) {
|
|
nvdec_active_channels.erase(it);
|
|
}
|
|
}
|
|
|
|
bool GetNVDECActive() {
|
|
std::scoped_lock lock{nvdec_active_mutex};
|
|
return !nvdec_active_channels.empty();
|
|
}
|
|
|
|
bool IsNVDECActiveForProcess(u64 process_id) {
|
|
std::scoped_lock lock{nvdec_active_mutex};
|
|
return nvdec_active_channels.contains(process_id);
|
|
}
|
|
|
|
void InitializeDebugger(System& system, u16 port) {
|
|
debugger.emplace(system, port);
|
|
}
|
|
|
|
void InitializeKernel(System& system) {
|
|
LOG_DEBUG(Core, "initialized OK");
|
|
|
|
// Setting changes may require a full system reinitialization (e.g., disabling multicore).
|
|
ReinitializeIfNecessary(system);
|
|
kernel.Initialize();
|
|
}
|
|
|
|
SystemResultStatus SetupForApplicationProcess(System& system, Frontend::EmuWindow& emu_window) {
|
|
host1x_core.emplace(system);
|
|
VideoCore::CreateGPU(gpu_core, emu_window, system);
|
|
if (!gpu_core)
|
|
return SystemResultStatus::ErrorVideoCore;
|
|
|
|
audio_core.emplace(system);
|
|
|
|
service_manager = std::make_shared<Service::SM::ServiceManager>(kernel);
|
|
services.emplace(service_manager, system, stop_event.get_token());
|
|
|
|
is_powered_on = true;
|
|
exit_locked = false;
|
|
exit_requested = false;
|
|
|
|
if (Settings::values.enable_renderdoc_hotkey) {
|
|
renderdoc_api.emplace();
|
|
}
|
|
|
|
LOG_DEBUG(Core, "Initialized OK");
|
|
|
|
return SystemResultStatus::Success;
|
|
}
|
|
|
|
SystemResultStatus Load(System& system, Frontend::EmuWindow& emu_window, const std::string& filepath, Service::AM::FrontendAppletParameters& params) {
|
|
InitializeKernel(system);
|
|
|
|
if (params.applet_type == Service::AM::AppletType::Application) {
|
|
current_application_filepath = filepath;
|
|
}
|
|
|
|
const auto file = GetGameFileFromPath(virtual_filesystem, filepath);
|
|
|
|
// Create the application process
|
|
Loader::ResultStatus load_result{};
|
|
std::vector<u8> control;
|
|
auto process = Service::AM::CreateApplicationProcess(control, app_loader, load_result, system, file, params.program_id, params.program_index);
|
|
if (load_result != Loader::ResultStatus::Success) {
|
|
LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", load_result);
|
|
ShutdownMainProcess();
|
|
return SystemResultStatus(u32(SystemResultStatus::ErrorLoader) + u32(load_result));
|
|
}
|
|
|
|
if (!app_loader) {
|
|
LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
|
|
return SystemResultStatus::ErrorGetLoader;
|
|
}
|
|
|
|
if (app_loader->ReadProgramId(params.program_id) != Loader::ResultStatus::Success) {
|
|
LOG_ERROR(Core, "Failed to find program id for ROM!");
|
|
}
|
|
|
|
std::string name = "Unknown program";
|
|
if (app_loader->ReadTitle(name) != Loader::ResultStatus::Success) {
|
|
LOG_ERROR(Core, "Failed to read title for ROM!");
|
|
}
|
|
|
|
LOG_INFO(Core, "Loading {} ({:016X}) ...", name, params.program_id);
|
|
|
|
// Expose program id to dump sites and other global readers.
|
|
Settings::SetCurrentProgramID(params.program_id);
|
|
|
|
// Track launch time for frontend launches
|
|
LaunchTimestampCache::SaveLaunchTimestamp(params.program_id);
|
|
|
|
// Make the process created be the application
|
|
kernel.SetApplicationProcess(process->GetHandle());
|
|
|
|
// Set up the rest of the system.
|
|
SystemResultStatus init_result{SetupForApplicationProcess(system, emu_window)};
|
|
if (init_result != SystemResultStatus::Success) {
|
|
LOG_CRITICAL(Core, "Failed to initialize system (Error {})!", int(init_result));
|
|
ShutdownMainProcess();
|
|
return init_result;
|
|
}
|
|
// Waiting for GPU before initializing CPU
|
|
cpu_manager.Initialize();
|
|
|
|
// Initialize cheat engine
|
|
if (cheat_engine) {
|
|
cheat_engine->Initialize();
|
|
}
|
|
|
|
// Register with applet manager
|
|
// All threads are started, begin main process execution, now that we're in the clear
|
|
applet_manager.CreateAndInsertByFrontendAppletParameters(std::move(process), params);
|
|
|
|
if (Settings::values.gamecard_inserted) {
|
|
if (Settings::values.gamecard_current_game) {
|
|
fs_controller.SetGameCard(GetGameFileFromPath(virtual_filesystem, filepath));
|
|
} else if (!Settings::values.gamecard_path.GetValue().empty()) {
|
|
const auto& gamecard_path = Settings::values.gamecard_path.GetValue();
|
|
fs_controller.SetGameCard(GetGameFileFromPath(virtual_filesystem, gamecard_path));
|
|
}
|
|
}
|
|
|
|
perf_stats.emplace(params.program_id);
|
|
// Reset counters and set time origin to current frame
|
|
GetAndResetPerfStats();
|
|
perf_stats->BeginSystemFrame();
|
|
|
|
const FileSys::PatchManager pm(params.program_id, system.GetFileSystemController(), system.GetContentProvider());
|
|
auto const metadata = pm.GetControlMetadata();
|
|
std::string title_version = metadata.first != nullptr ? metadata.first->GetVersionString() : "";
|
|
|
|
if (app_loader->ReadProgramId(program_id) != Loader::ResultStatus::Success) {
|
|
LOG_ERROR(Core, "Failed to find program id for ROM");
|
|
}
|
|
|
|
GameSettings::LoadOverrides(program_id, gpu_core->Renderer());
|
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
|
Network::GameInfo game_info;
|
|
game_info.name = name;
|
|
game_info.id = params.program_id;
|
|
game_info.version = title_version;
|
|
room_member->SendGameInfo(game_info);
|
|
}
|
|
return SystemResultStatus::Success;
|
|
}
|
|
|
|
void ShutdownMainProcess() {
|
|
SetShuttingDown(true);
|
|
Common::ADPF::Shutdown();
|
|
|
|
// Reset per-game flags
|
|
Settings::values.use_squashed_iterated_blend = false;
|
|
|
|
is_powered_on = false;
|
|
exit_locked = false;
|
|
exit_requested = false;
|
|
if (gpu_core)
|
|
gpu_core->NotifyShutdown();
|
|
|
|
stop_event.request_stop();
|
|
core_timing.SyncPause(false);
|
|
Network::CancelPendingSocketOperations();
|
|
kernel.SuspendEmulation(true);
|
|
kernel.CloseServices();
|
|
kernel.ShutdownCores();
|
|
services.reset();
|
|
service_manager.reset();
|
|
fs_controller.Reset();
|
|
cheat_engine.reset();
|
|
core_timing.ClearPendingEvents();
|
|
app_loader.reset();
|
|
audio_core.reset();
|
|
gpu_core.reset();
|
|
host1x_core.reset();
|
|
perf_stats.reset();
|
|
cpu_manager.Shutdown();
|
|
debugger.reset();
|
|
kernel.Shutdown();
|
|
stop_event = {};
|
|
Network::RestartSocketOperations();
|
|
|
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
|
Network::GameInfo game_info{};
|
|
room_member->SendGameInfo(game_info);
|
|
}
|
|
|
|
// Reset all glue registrations
|
|
arp_manager.ResetAll();
|
|
|
|
LOG_DEBUG(Core, "Shutdown OK");
|
|
}
|
|
|
|
bool IsShuttingDown() const {
|
|
return is_shutting_down;
|
|
}
|
|
|
|
void SetShuttingDown(bool shutting_down) {
|
|
is_shutting_down = shutting_down;
|
|
}
|
|
|
|
Loader::ResultStatus GetGameName(std::string& out) const {
|
|
return app_loader ? app_loader->ReadTitle(out) : Loader::ResultStatus::ErrorNotInitialized;
|
|
}
|
|
|
|
PerfStatsResults GetAndResetPerfStats() {
|
|
return perf_stats->GetAndResetStats(core_timing.GetGlobalTimeUs());
|
|
}
|
|
|
|
Timing::CoreTiming core_timing;
|
|
Kernel::KernelCore kernel;
|
|
/// RealVfsFilesystem instance
|
|
FileSys::VirtualFilesystem virtual_filesystem;
|
|
Service::FileSystem::FileSystemController fs_controller;
|
|
Core::HID::HIDCore hid_core;
|
|
CpuManager cpu_manager;
|
|
Reporter reporter;
|
|
/// Applets
|
|
Service::AM::AppletManager applet_manager;
|
|
Service::AM::Frontend::FrontendAppletHolder frontend_applets;
|
|
/// APM (Performance) services
|
|
Service::APM::Controller apm_controller{core_timing};
|
|
/// Service State
|
|
Service::Glue::ARPManager arp_manager;
|
|
Service::Account::ProfileManager profile_manager;
|
|
/// Network instance
|
|
Network::NetworkInstance network_instance;
|
|
Core::SpeedLimiter speed_limiter;
|
|
ExecuteProgramCallback execute_program_callback;
|
|
ExitCallback exit_callback;
|
|
ApplicationChangedCallback application_changed_callback;
|
|
|
|
std::optional<Service::Services> services;
|
|
std::optional<Core::Debugger> debugger;
|
|
std::optional<Service::KernelHelpers::ServiceContext> general_channel_context;
|
|
std::optional<Service::Event> general_channel_event;
|
|
std::optional<Core::PerfStats> perf_stats;
|
|
std::optional<Tegra::Host1x::Host1x> host1x_core;
|
|
std::optional<Core::DeviceMemory> device_memory;
|
|
std::optional<AudioCore::AudioCore> audio_core;
|
|
std::optional<Memory::CheatEngine> cheat_engine;
|
|
std::optional<Tools::Freezer> memory_freezer;
|
|
std::optional<Tools::RenderdocAPI> renderdoc_api;
|
|
std::optional<Tegra::GPU> gpu_core;
|
|
|
|
std::array<Core::GPUDirtyMemoryManager, Core::Hardware::NUM_CPU_CORES> gpu_dirty_memory_managers;
|
|
std::vector<std::vector<u8>> user_channel;
|
|
std::vector<std::vector<u8>> general_channel;
|
|
|
|
std::array<u64, Core::Hardware::NUM_CPU_CORES> dynarmic_ticks{};
|
|
std::array<u8, 0x20> build_id{};
|
|
|
|
std::string current_application_filepath;
|
|
|
|
/// Service manager
|
|
std::shared_ptr<Service::SM::ServiceManager> service_manager;
|
|
/// ContentProviderUnion instance
|
|
std::unique_ptr<FileSys::ContentProviderUnion> content_provider;
|
|
/// AppLoader used to load the current executing application
|
|
std::unique_ptr<Loader::AppLoader> app_loader;
|
|
std::stop_source stop_event;
|
|
|
|
mutable std::mutex suspend_guard;
|
|
std::mutex general_channel_mutex;
|
|
std::mutex nvdec_active_mutex;
|
|
std::unordered_map<u64, u32> nvdec_active_channels;
|
|
std::atomic_bool is_paused{};
|
|
std::atomic_bool is_shutting_down{};
|
|
std::atomic_bool is_powered_on{};
|
|
bool is_multicore : 1 = false;
|
|
bool extended_memory_layout : 1 = false;
|
|
bool exit_locked : 1 = false;
|
|
bool exit_requested : 1 = false;
|
|
|
|
void EnsureGeneralChannelInitialized(System& system) {
|
|
if (!general_channel_event) {
|
|
general_channel_context.emplace(system, "GeneralChannel");
|
|
general_channel_event.emplace(*general_channel_context);
|
|
}
|
|
}
|
|
};
|
|
|
|
System::System() : impl{std::make_unique<Impl>(*this)} {}
|
|
|
|
System::~System() = default;
|
|
|
|
CpuManager& System::GetCpuManager() {
|
|
return impl->cpu_manager;
|
|
}
|
|
|
|
const CpuManager& System::GetCpuManager() const {
|
|
return impl->cpu_manager;
|
|
}
|
|
|
|
void System::Initialize() {
|
|
impl->Initialize(*this);
|
|
}
|
|
|
|
void System::Run() {
|
|
impl->Run();
|
|
}
|
|
|
|
void System::Pause() {
|
|
impl->Pause();
|
|
}
|
|
|
|
bool System::IsPaused() const {
|
|
return impl->IsPaused();
|
|
}
|
|
|
|
void System::ShutdownMainProcess() {
|
|
impl->ShutdownMainProcess();
|
|
}
|
|
|
|
bool System::IsShuttingDown() const {
|
|
return impl->IsShuttingDown();
|
|
}
|
|
|
|
void System::SetShuttingDown(bool shutting_down) {
|
|
impl->SetShuttingDown(shutting_down);
|
|
}
|
|
|
|
void System::DetachDebugger() {
|
|
if (impl->debugger) {
|
|
impl->debugger->NotifyShutdown();
|
|
}
|
|
}
|
|
|
|
std::unique_lock<std::mutex> System::StallApplication() {
|
|
return impl->StallApplication();
|
|
}
|
|
|
|
void System::UnstallApplication() {
|
|
impl->UnstallApplication();
|
|
}
|
|
|
|
void System::NotifyNVDECChannelOpen(u64 process_id) {
|
|
impl->NotifyNVDECChannelOpen(process_id);
|
|
}
|
|
|
|
void System::NotifyNVDECChannelClose(u64 process_id) {
|
|
impl->NotifyNVDECChannelClose(process_id);
|
|
}
|
|
|
|
bool System::GetNVDECActive() {
|
|
return impl->GetNVDECActive();
|
|
}
|
|
|
|
bool System::IsNVDECActiveForProcess(u64 process_id) {
|
|
return impl->IsNVDECActiveForProcess(process_id);
|
|
}
|
|
|
|
void System::InitializeDebugger() {
|
|
impl->InitializeDebugger(*this, Settings::values.gdbstub_port.GetValue());
|
|
}
|
|
|
|
SystemResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath,
|
|
Service::AM::FrontendAppletParameters& params) {
|
|
return impl->Load(*this, emu_window, filepath, params);
|
|
}
|
|
|
|
bool System::IsPoweredOn() const {
|
|
return impl->is_powered_on.load(std::memory_order::relaxed);
|
|
}
|
|
|
|
void System::PrepareReschedule(const u32 core_index) {
|
|
impl->kernel.PrepareReschedule(core_index);
|
|
}
|
|
|
|
size_t System::GetCurrentHostThreadID() const {
|
|
return impl->kernel.GetCurrentHostThreadID();
|
|
}
|
|
|
|
std::span<GPUDirtyMemoryManager> System::GetGPUDirtyMemoryManager() {
|
|
return impl->gpu_dirty_memory_managers;
|
|
}
|
|
|
|
void System::GatherGPUDirtyMemory(std::function<void(PAddr, size_t)>& callback) {
|
|
for (auto& manager : impl->gpu_dirty_memory_managers) {
|
|
manager.Gather(callback);
|
|
}
|
|
}
|
|
|
|
PerfStatsResults System::GetAndResetPerfStats() {
|
|
return impl->GetAndResetPerfStats();
|
|
}
|
|
|
|
Kernel::PhysicalCore& System::CurrentPhysicalCore() {
|
|
return impl->kernel.CurrentPhysicalCore();
|
|
}
|
|
|
|
const Kernel::PhysicalCore& System::CurrentPhysicalCore() const {
|
|
return impl->kernel.CurrentPhysicalCore();
|
|
}
|
|
|
|
/// Gets the global scheduler
|
|
Kernel::GlobalSchedulerContext& System::GlobalSchedulerContext() {
|
|
return impl->kernel.GlobalSchedulerContext();
|
|
}
|
|
|
|
/// Gets the global scheduler
|
|
const Kernel::GlobalSchedulerContext& System::GlobalSchedulerContext() const {
|
|
return impl->kernel.GlobalSchedulerContext();
|
|
}
|
|
|
|
Kernel::KProcess* System::ApplicationProcess() {
|
|
return impl->kernel.ApplicationProcess();
|
|
}
|
|
|
|
Core::DeviceMemory& System::DeviceMemory() {
|
|
return *impl->device_memory;
|
|
}
|
|
|
|
const Core::DeviceMemory& System::DeviceMemory() const {
|
|
return *impl->device_memory;
|
|
}
|
|
|
|
const Kernel::KProcess* System::ApplicationProcess() const {
|
|
return impl->kernel.ApplicationProcess();
|
|
}
|
|
|
|
Memory::Memory& System::ApplicationMemory() {
|
|
return impl->kernel.ApplicationProcess()->GetMemory();
|
|
}
|
|
|
|
const Core::Memory::Memory& System::ApplicationMemory() const {
|
|
return impl->kernel.ApplicationProcess()->GetMemory();
|
|
}
|
|
|
|
Tegra::GPU& System::GPU() {
|
|
return *impl->gpu_core;
|
|
}
|
|
|
|
const Tegra::GPU& System::GPU() const {
|
|
return *impl->gpu_core;
|
|
}
|
|
|
|
Tegra::Host1x::Host1x& System::Host1x() {
|
|
return *impl->host1x_core;
|
|
}
|
|
|
|
const Tegra::Host1x::Host1x& System::Host1x() const {
|
|
return *impl->host1x_core;
|
|
}
|
|
|
|
VideoCore::RendererBase& System::Renderer() {
|
|
return impl->gpu_core->Renderer();
|
|
}
|
|
|
|
const VideoCore::RendererBase& System::Renderer() const {
|
|
return impl->gpu_core->Renderer();
|
|
}
|
|
|
|
Kernel::KernelCore& System::Kernel() {
|
|
return impl->kernel;
|
|
}
|
|
|
|
const Kernel::KernelCore& System::Kernel() const {
|
|
return impl->kernel;
|
|
}
|
|
|
|
HID::HIDCore& System::HIDCore() {
|
|
return impl->hid_core;
|
|
}
|
|
|
|
const HID::HIDCore& System::HIDCore() const {
|
|
return impl->hid_core;
|
|
}
|
|
|
|
AudioCore::AudioCore& System::AudioCore() {
|
|
return *impl->audio_core;
|
|
}
|
|
|
|
const AudioCore::AudioCore& System::AudioCore() const {
|
|
return *impl->audio_core;
|
|
}
|
|
|
|
Timing::CoreTiming& System::CoreTiming() {
|
|
return impl->core_timing;
|
|
}
|
|
|
|
const Timing::CoreTiming& System::CoreTiming() const {
|
|
return impl->core_timing;
|
|
}
|
|
|
|
Core::PerfStats& System::GetPerfStats() {
|
|
return *impl->perf_stats;
|
|
}
|
|
|
|
const Core::PerfStats& System::GetPerfStats() const {
|
|
return *impl->perf_stats;
|
|
}
|
|
|
|
Core::SpeedLimiter& System::SpeedLimiter() {
|
|
return impl->speed_limiter;
|
|
}
|
|
|
|
const Core::SpeedLimiter& System::SpeedLimiter() const {
|
|
return impl->speed_limiter;
|
|
}
|
|
|
|
u64 System::GetApplicationProcessProgramID() const {
|
|
const auto* const process = impl->kernel.ApplicationProcess();
|
|
return process != nullptr ? process->GetProgramId() : 0;
|
|
}
|
|
|
|
u64 System::GetProgramIdForProcessId(u64 process_id) const {
|
|
auto process = impl->kernel.GetProcessByProcessId(process_id);
|
|
return process.IsNull() ? 0 : process->GetProgramId();
|
|
}
|
|
|
|
u64 System::ResolveCallerProgramId(u64 process_id) const {
|
|
if (const auto program_id = this->GetProgramIdForProcessId(process_id); program_id != 0) {
|
|
return program_id;
|
|
}
|
|
|
|
const auto fallback = this->GetApplicationProcessProgramID();
|
|
LOG_WARNING(Core,
|
|
"Could not resolve caller process_id={}, falling back to application {:016X}",
|
|
process_id, fallback);
|
|
return fallback;
|
|
}
|
|
|
|
Loader::ResultStatus System::GetGameName(std::string& out) const {
|
|
return impl->GetGameName(out);
|
|
}
|
|
|
|
Loader::AppLoader& System::GetAppLoader() {
|
|
return *impl->app_loader;
|
|
}
|
|
|
|
const Loader::AppLoader& System::GetAppLoader() const {
|
|
return *impl->app_loader;
|
|
}
|
|
|
|
void System::SetFilesystem(FileSys::VirtualFilesystem vfs) {
|
|
impl->virtual_filesystem = std::move(vfs);
|
|
}
|
|
|
|
FileSys::VirtualFilesystem System::GetFilesystem() const {
|
|
return impl->virtual_filesystem;
|
|
}
|
|
|
|
void System::RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
|
|
const std::array<u8, 32>& build_id, u64 main_region_begin,
|
|
u64 main_region_size) {
|
|
impl->cheat_engine.emplace(*this, list, build_id);
|
|
impl->cheat_engine->SetMainMemoryParameters(main_region_begin, main_region_size);
|
|
}
|
|
|
|
void System::SetFrontendAppletSet(Service::AM::Frontend::FrontendAppletSet&& set) {
|
|
impl->frontend_applets.SetFrontendAppletSet(std::move(set));
|
|
}
|
|
|
|
Service::AM::Frontend::FrontendAppletHolder& System::GetFrontendAppletHolder() {
|
|
return impl->frontend_applets;
|
|
}
|
|
|
|
const Service::AM::Frontend::FrontendAppletHolder& System::GetFrontendAppletHolder() const {
|
|
return impl->frontend_applets;
|
|
}
|
|
|
|
Service::AM::AppletManager& System::GetAppletManager() {
|
|
return impl->applet_manager;
|
|
}
|
|
|
|
void System::SetContentProvider(std::unique_ptr<FileSys::ContentProviderUnion> provider) {
|
|
impl->content_provider = std::move(provider);
|
|
}
|
|
|
|
FileSys::ContentProvider& System::GetContentProvider() {
|
|
return *impl->content_provider;
|
|
}
|
|
|
|
const FileSys::ContentProvider& System::GetContentProvider() const {
|
|
return *impl->content_provider;
|
|
}
|
|
|
|
FileSys::ContentProviderUnion& System::GetContentProviderUnion() {
|
|
return *impl->content_provider;
|
|
}
|
|
|
|
const FileSys::ContentProviderUnion& System::GetContentProviderUnion() const {
|
|
return *impl->content_provider;
|
|
}
|
|
|
|
Service::FileSystem::FileSystemController& System::GetFileSystemController() {
|
|
return impl->fs_controller;
|
|
}
|
|
|
|
const Service::FileSystem::FileSystemController& System::GetFileSystemController() const {
|
|
return impl->fs_controller;
|
|
}
|
|
|
|
void System::RegisterContentProvider(FileSys::ContentProviderUnionSlot slot,
|
|
FileSys::ContentProvider* provider) {
|
|
impl->content_provider->SetSlot(slot, provider);
|
|
}
|
|
|
|
const Reporter& System::GetReporter() const {
|
|
return impl->reporter;
|
|
}
|
|
|
|
Service::Glue::ARPManager& System::GetARPManager() {
|
|
return impl->arp_manager;
|
|
}
|
|
|
|
const Service::Glue::ARPManager& System::GetARPManager() const {
|
|
return impl->arp_manager;
|
|
}
|
|
|
|
Service::APM::Controller& System::GetAPMController() {
|
|
return impl->apm_controller;
|
|
}
|
|
|
|
const Service::APM::Controller& System::GetAPMController() const {
|
|
return impl->apm_controller;
|
|
}
|
|
|
|
Service::Account::ProfileManager& System::GetProfileManager() {
|
|
return impl->profile_manager;
|
|
}
|
|
|
|
const Service::Account::ProfileManager& System::GetProfileManager() const {
|
|
return impl->profile_manager;
|
|
}
|
|
|
|
void System::SetExitLocked(bool locked) {
|
|
impl->exit_locked = locked;
|
|
}
|
|
|
|
bool System::GetExitLocked() const {
|
|
return impl->exit_locked;
|
|
}
|
|
|
|
void System::SetExitRequested(bool requested) {
|
|
impl->exit_requested = requested;
|
|
}
|
|
|
|
bool System::GetExitRequested() const {
|
|
return impl->exit_requested;
|
|
}
|
|
|
|
void System::SetApplicationProcessBuildID(const CurrentBuildProcessID& id) {
|
|
impl->build_id = id;
|
|
}
|
|
|
|
const System::CurrentBuildProcessID& System::GetApplicationProcessBuildID() const {
|
|
return impl->build_id;
|
|
}
|
|
|
|
Service::SM::ServiceManager& System::ServiceManager() {
|
|
return *impl->service_manager;
|
|
}
|
|
|
|
const Service::SM::ServiceManager& System::ServiceManager() const {
|
|
return *impl->service_manager;
|
|
}
|
|
|
|
void System::RegisterCoreThread(std::size_t id) {
|
|
impl->kernel.RegisterCoreThread(id);
|
|
}
|
|
|
|
void System::RegisterHostThread() {
|
|
impl->kernel.RegisterHostThread();
|
|
}
|
|
|
|
bool System::IsMulticore() const {
|
|
return impl->is_multicore;
|
|
}
|
|
|
|
bool System::DebuggerEnabled() const {
|
|
return Settings::values.use_gdbstub.GetValue();
|
|
}
|
|
|
|
Core::Debugger& System::GetDebugger() {
|
|
return *impl->debugger;
|
|
}
|
|
|
|
const Core::Debugger& System::GetDebugger() const {
|
|
return *impl->debugger;
|
|
}
|
|
|
|
Tools::RenderdocAPI& System::GetRenderdocAPI() {
|
|
return *impl->renderdoc_api;
|
|
}
|
|
|
|
void System::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) {
|
|
return impl->kernel.RunServer(std::move(server_manager));
|
|
}
|
|
|
|
void System::RegisterExecuteProgramCallback(ExecuteProgramCallback&& callback) {
|
|
impl->execute_program_callback = std::move(callback);
|
|
}
|
|
|
|
void System::ExecuteProgram(std::size_t program_index) {
|
|
if (impl->execute_program_callback) {
|
|
impl->execute_program_callback(program_index);
|
|
} else {
|
|
LOG_CRITICAL(Core, "execute_program_callback must be initialized by the frontend");
|
|
}
|
|
}
|
|
|
|
const std::string& System::GetCurrentApplicationFilePath() const {
|
|
return impl->current_application_filepath;
|
|
}
|
|
|
|
/// @brief Gets a reference to the user channel stack.
|
|
/// It is used to transfer data between programs.
|
|
std::vector<std::vector<u8>>& System::GetUserChannel() {
|
|
return impl->user_channel;
|
|
}
|
|
|
|
std::vector<std::vector<u8>>& System::GetGeneralChannel() {
|
|
return impl->general_channel;
|
|
}
|
|
|
|
void System::PushGeneralChannelData(std::vector<u8>&& data) {
|
|
std::scoped_lock lk{impl->general_channel_mutex};
|
|
impl->EnsureGeneralChannelInitialized(*this);
|
|
const bool was_empty = impl->general_channel.empty();
|
|
impl->general_channel.push_back(std::move(data));
|
|
if (was_empty) {
|
|
impl->general_channel_event->Signal(impl->kernel);
|
|
}
|
|
}
|
|
|
|
bool System::TryPopGeneralChannel(std::vector<u8>& out_data) {
|
|
std::scoped_lock lk{impl->general_channel_mutex};
|
|
if (!impl->general_channel_event || impl->general_channel.empty()) {
|
|
return false;
|
|
}
|
|
out_data = std::move(impl->general_channel.back());
|
|
impl->general_channel.pop_back();
|
|
if (impl->general_channel.empty()) {
|
|
impl->general_channel_event->Clear(impl->kernel);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Service::Event& System::GetGeneralChannelEvent() {
|
|
std::scoped_lock lk{impl->general_channel_mutex};
|
|
impl->EnsureGeneralChannelInitialized(*this);
|
|
return *impl->general_channel_event;
|
|
}
|
|
|
|
void System::RegisterExitCallback(ExitCallback&& callback) {
|
|
impl->exit_callback = std::move(callback);
|
|
}
|
|
|
|
void System::Exit() {
|
|
if (impl->exit_callback) {
|
|
impl->exit_callback();
|
|
} else {
|
|
LOG_CRITICAL(Core, "exit_callback must be initialized by the frontend");
|
|
}
|
|
}
|
|
|
|
void System::RegisterApplicationChangedCallback(ApplicationChangedCallback&& callback) {
|
|
impl->application_changed_callback = std::move(callback);
|
|
}
|
|
|
|
void System::NotifyApplicationChanged(u64 program_id) {
|
|
//LOG_DEBUG(Core, "Running application changed to {:016X}", program_id);
|
|
|
|
if (impl->application_changed_callback) {
|
|
impl->application_changed_callback(program_id);
|
|
}
|
|
}
|
|
|
|
void System::ApplySettings() {
|
|
impl->RefreshTime(*this);
|
|
|
|
if (IsPoweredOn()) {
|
|
Renderer().RefreshBaseSettings();
|
|
}
|
|
}
|
|
|
|
} // namespace Core
|