mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-09-13 06:58:13 +00:00
f3af5d0c25
- [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 issue was observed in MGS Master Collection Volume II, MGS4: Guns of the Patriots, Chapter 4: Shadow Moses That title is a bundle, and that specific chapter is a program (MGS 1st stage), launched by a program (MGS4), launched by an application (main title bundle). Upon decoding guest panic message, it was exposed that an abort was triggered after trying to create a save in nand/temp/gclvar.bak file. Further instrumentation exposed the error on that file's path, caused from a previous deletion of that folder. In the same session that folder had been already cleaned and created successfully, which evidences an undesired re-cleaning of the temp structure. The issue was the fact that the MGS4 to MGS transition also calls EnsureSaveData>...>SaveDataFactory creator, and that was destroying the previous created temp structure. After that stage, the return from MSG to MSG4 was causing the same issue again. By moving the deletion to the guest application level, the factory creation was called only once, as it should be, and the issue is gone. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4375 Reviewed-by: MaranBr <maranbr@eden-emu.dev> Reviewed-by: lizzie <lizzie@eden-emu.dev>
1046 lines
32 KiB
C++
1046 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) {
|
|
if (params.launch_type == Service::AM::LaunchType::FrontendInitiated) {
|
|
fs_controller.InitTempStorage();
|
|
}
|
|
|
|
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
|