|
|
|
@ -27,19 +27,48 @@ namespace Core { |
|
|
|
|
|
|
|
/*static*/ System System::s_instance; |
|
|
|
|
|
|
|
System::System() = default; |
|
|
|
namespace { |
|
|
|
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::Mode::Read); |
|
|
|
std::vector<FileSys::VirtualFile> concat; |
|
|
|
for (u8 i = 0; i < 0x10; ++i) { |
|
|
|
auto next = dir->GetFile(fmt::format("{:02X}", i)); |
|
|
|
if (next != nullptr) |
|
|
|
concat.push_back(std::move(next)); |
|
|
|
else { |
|
|
|
next = dir->GetFile(fmt::format("{:02x}", i)); |
|
|
|
if (next != nullptr) |
|
|
|
concat.push_back(std::move(next)); |
|
|
|
else |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
System::~System() = default; |
|
|
|
if (concat.empty()) |
|
|
|
return nullptr; |
|
|
|
|
|
|
|
return FileSys::ConcatenateFiles(concat, dir->GetName()); |
|
|
|
} |
|
|
|
|
|
|
|
return vfs->OpenFile(path, FileSys::Mode::Read); |
|
|
|
} |
|
|
|
|
|
|
|
/// Runs a CPU core while the system is powered on
|
|
|
|
static void RunCpuCore(std::shared_ptr<Cpu> cpu_state) { |
|
|
|
void RunCpuCore(std::shared_ptr<Cpu> cpu_state) { |
|
|
|
while (Core::System::GetInstance().IsPoweredOn()) { |
|
|
|
cpu_state->RunLoop(true); |
|
|
|
} |
|
|
|
} |
|
|
|
} // Anonymous namespace
|
|
|
|
|
|
|
|
Cpu& System::CurrentCpuCore() { |
|
|
|
// If multicore is enabled, use host thread to figure out the current CPU core
|
|
|
|
struct System::Impl { |
|
|
|
Cpu& CurrentCpuCore() { |
|
|
|
if (Settings::values.use_multi_core) { |
|
|
|
const auto& search = thread_to_cpu.find(std::this_thread::get_id()); |
|
|
|
ASSERT(search != thread_to_cpu.end()); |
|
|
|
@ -49,9 +78,9 @@ Cpu& System::CurrentCpuCore() { |
|
|
|
|
|
|
|
// Otherwise, use single-threaded mode active_core variable
|
|
|
|
return *cpu_cores[active_core]; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
System::ResultStatus System::RunLoop(bool tight_loop) { |
|
|
|
ResultStatus RunLoop(bool tight_loop) { |
|
|
|
status = ResultStatus::Success; |
|
|
|
|
|
|
|
// Update thread_to_cpu in case Core 0 is run from a different host thread
|
|
|
|
@ -84,112 +113,9 @@ System::ResultStatus System::RunLoop(bool tight_loop) { |
|
|
|
} |
|
|
|
|
|
|
|
return status; |
|
|
|
} |
|
|
|
|
|
|
|
System::ResultStatus System::SingleStep() { |
|
|
|
return RunLoop(false); |
|
|
|
} |
|
|
|
|
|
|
|
static 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::Mode::Read); |
|
|
|
std::vector<FileSys::VirtualFile> concat; |
|
|
|
for (u8 i = 0; i < 0x10; ++i) { |
|
|
|
auto next = dir->GetFile(fmt::format("{:02X}", i)); |
|
|
|
if (next != nullptr) |
|
|
|
concat.push_back(std::move(next)); |
|
|
|
else { |
|
|
|
next = dir->GetFile(fmt::format("{:02x}", i)); |
|
|
|
if (next != nullptr) |
|
|
|
concat.push_back(std::move(next)); |
|
|
|
else |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (concat.empty()) |
|
|
|
return nullptr; |
|
|
|
|
|
|
|
return FileSys::ConcatenateFiles(concat, dir->GetName()); |
|
|
|
} |
|
|
|
|
|
|
|
return vfs->OpenFile(path, FileSys::Mode::Read); |
|
|
|
} |
|
|
|
|
|
|
|
System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) { |
|
|
|
app_loader = Loader::GetLoader(GetGameFileFromPath(virtual_filesystem, filepath)); |
|
|
|
|
|
|
|
if (!app_loader) { |
|
|
|
LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath); |
|
|
|
return ResultStatus::ErrorGetLoader; |
|
|
|
} |
|
|
|
std::pair<boost::optional<u32>, Loader::ResultStatus> system_mode = |
|
|
|
app_loader->LoadKernelSystemMode(); |
|
|
|
|
|
|
|
if (system_mode.second != Loader::ResultStatus::Success) { |
|
|
|
LOG_CRITICAL(Core, "Failed to determine system mode (Error {})!", |
|
|
|
static_cast<int>(system_mode.second)); |
|
|
|
|
|
|
|
return ResultStatus::ErrorSystemMode; |
|
|
|
} |
|
|
|
|
|
|
|
ResultStatus init_result{Init(emu_window)}; |
|
|
|
if (init_result != ResultStatus::Success) { |
|
|
|
LOG_CRITICAL(Core, "Failed to initialize system (Error {})!", |
|
|
|
static_cast<int>(init_result)); |
|
|
|
System::Shutdown(); |
|
|
|
return init_result; |
|
|
|
} |
|
|
|
|
|
|
|
const Loader::ResultStatus load_result{app_loader->Load(current_process)}; |
|
|
|
if (load_result != Loader::ResultStatus::Success) { |
|
|
|
LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast<int>(load_result)); |
|
|
|
System::Shutdown(); |
|
|
|
|
|
|
|
return static_cast<ResultStatus>(static_cast<u32>(ResultStatus::ErrorLoader) + |
|
|
|
static_cast<u32>(load_result)); |
|
|
|
} |
|
|
|
status = ResultStatus::Success; |
|
|
|
return status; |
|
|
|
} |
|
|
|
|
|
|
|
void System::PrepareReschedule() { |
|
|
|
CurrentCpuCore().PrepareReschedule(); |
|
|
|
} |
|
|
|
|
|
|
|
PerfStats::Results System::GetAndResetPerfStats() { |
|
|
|
return perf_stats.GetAndResetStats(CoreTiming::GetGlobalTimeUs()); |
|
|
|
} |
|
|
|
|
|
|
|
const std::shared_ptr<Kernel::Scheduler>& System::Scheduler(size_t core_index) { |
|
|
|
ASSERT(core_index < NUM_CPU_CORES); |
|
|
|
return cpu_cores[core_index]->Scheduler(); |
|
|
|
} |
|
|
|
|
|
|
|
Kernel::KernelCore& System::Kernel() { |
|
|
|
return kernel; |
|
|
|
} |
|
|
|
|
|
|
|
const Kernel::KernelCore& System::Kernel() const { |
|
|
|
return kernel; |
|
|
|
} |
|
|
|
|
|
|
|
ARM_Interface& System::ArmInterface(size_t core_index) { |
|
|
|
ASSERT(core_index < NUM_CPU_CORES); |
|
|
|
return cpu_cores[core_index]->ArmInterface(); |
|
|
|
} |
|
|
|
|
|
|
|
Cpu& System::CpuCore(size_t core_index) { |
|
|
|
ASSERT(core_index < NUM_CPU_CORES); |
|
|
|
return *cpu_cores[core_index]; |
|
|
|
} |
|
|
|
|
|
|
|
System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) { |
|
|
|
ResultStatus Init(Frontend::EmuWindow& emu_window) { |
|
|
|
LOG_DEBUG(HW_Memory, "initialized OK"); |
|
|
|
|
|
|
|
CoreTiming::Init(); |
|
|
|
@ -238,9 +164,46 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) { |
|
|
|
perf_stats.BeginSystemFrame(); |
|
|
|
|
|
|
|
return ResultStatus::Success; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void System::Shutdown() { |
|
|
|
ResultStatus Load(Frontend::EmuWindow& emu_window, const std::string& filepath) { |
|
|
|
app_loader = Loader::GetLoader(GetGameFileFromPath(virtual_filesystem, filepath)); |
|
|
|
|
|
|
|
if (!app_loader) { |
|
|
|
LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath); |
|
|
|
return ResultStatus::ErrorGetLoader; |
|
|
|
} |
|
|
|
std::pair<boost::optional<u32>, Loader::ResultStatus> system_mode = |
|
|
|
app_loader->LoadKernelSystemMode(); |
|
|
|
|
|
|
|
if (system_mode.second != Loader::ResultStatus::Success) { |
|
|
|
LOG_CRITICAL(Core, "Failed to determine system mode (Error {})!", |
|
|
|
static_cast<int>(system_mode.second)); |
|
|
|
|
|
|
|
return ResultStatus::ErrorSystemMode; |
|
|
|
} |
|
|
|
|
|
|
|
ResultStatus init_result{Init(emu_window)}; |
|
|
|
if (init_result != ResultStatus::Success) { |
|
|
|
LOG_CRITICAL(Core, "Failed to initialize system (Error {})!", |
|
|
|
static_cast<int>(init_result)); |
|
|
|
Shutdown(); |
|
|
|
return init_result; |
|
|
|
} |
|
|
|
|
|
|
|
const Loader::ResultStatus load_result{app_loader->Load(current_process)}; |
|
|
|
if (load_result != Loader::ResultStatus::Success) { |
|
|
|
LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast<int>(load_result)); |
|
|
|
Shutdown(); |
|
|
|
|
|
|
|
return static_cast<ResultStatus>(static_cast<u32>(ResultStatus::ErrorLoader) + |
|
|
|
static_cast<u32>(load_result)); |
|
|
|
} |
|
|
|
status = ResultStatus::Success; |
|
|
|
return status; |
|
|
|
} |
|
|
|
|
|
|
|
void Shutdown() { |
|
|
|
// Log last frame performance stats
|
|
|
|
auto perf_results = GetAndResetPerfStats(); |
|
|
|
Telemetry().AddField(Telemetry::FieldType::Performance, "Shutdown_EmulationSpeed", |
|
|
|
@ -280,14 +243,218 @@ void System::Shutdown() { |
|
|
|
app_loader.reset(); |
|
|
|
|
|
|
|
LOG_DEBUG(Core, "Shutdown OK"); |
|
|
|
} |
|
|
|
|
|
|
|
Loader::ResultStatus GetGameName(std::string& out) const { |
|
|
|
if (app_loader == nullptr) |
|
|
|
return Loader::ResultStatus::ErrorNotInitialized; |
|
|
|
return app_loader->ReadTitle(out); |
|
|
|
} |
|
|
|
|
|
|
|
void SetStatus(ResultStatus new_status, const char* details = nullptr) { |
|
|
|
status = new_status; |
|
|
|
if (details) { |
|
|
|
status_details = details; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
PerfStats::Results GetAndResetPerfStats() { |
|
|
|
return perf_stats.GetAndResetStats(CoreTiming::GetGlobalTimeUs()); |
|
|
|
} |
|
|
|
|
|
|
|
Kernel::KernelCore kernel; |
|
|
|
/// RealVfsFilesystem instance
|
|
|
|
FileSys::VirtualFilesystem virtual_filesystem; |
|
|
|
/// AppLoader used to load the current executing application
|
|
|
|
std::unique_ptr<Loader::AppLoader> app_loader; |
|
|
|
std::unique_ptr<VideoCore::RendererBase> renderer; |
|
|
|
std::unique_ptr<Tegra::GPU> gpu_core; |
|
|
|
std::shared_ptr<Tegra::DebugContext> debug_context; |
|
|
|
Kernel::SharedPtr<Kernel::Process> current_process; |
|
|
|
std::shared_ptr<ExclusiveMonitor> cpu_exclusive_monitor; |
|
|
|
std::shared_ptr<CpuBarrier> cpu_barrier; |
|
|
|
std::array<std::shared_ptr<Cpu>, NUM_CPU_CORES> cpu_cores; |
|
|
|
std::array<std::unique_ptr<std::thread>, NUM_CPU_CORES - 1> cpu_core_threads; |
|
|
|
size_t active_core{}; ///< Active core, only used in single thread mode
|
|
|
|
|
|
|
|
/// Service manager
|
|
|
|
std::shared_ptr<Service::SM::ServiceManager> service_manager; |
|
|
|
|
|
|
|
/// Telemetry session for this emulation session
|
|
|
|
std::unique_ptr<Core::TelemetrySession> telemetry_session; |
|
|
|
|
|
|
|
ResultStatus status = ResultStatus::Success; |
|
|
|
std::string status_details = ""; |
|
|
|
|
|
|
|
/// Map of guest threads to CPU cores
|
|
|
|
std::map<std::thread::id, std::shared_ptr<Cpu>> thread_to_cpu; |
|
|
|
|
|
|
|
Core::PerfStats perf_stats; |
|
|
|
Core::FrameLimiter frame_limiter; |
|
|
|
}; |
|
|
|
|
|
|
|
System::System() : impl{std::make_unique<Impl>()} {} |
|
|
|
System::~System() = default; |
|
|
|
|
|
|
|
Cpu& System::CurrentCpuCore() { |
|
|
|
return impl->CurrentCpuCore(); |
|
|
|
} |
|
|
|
|
|
|
|
System::ResultStatus System::RunLoop(bool tight_loop) { |
|
|
|
return impl->RunLoop(tight_loop); |
|
|
|
} |
|
|
|
|
|
|
|
System::ResultStatus System::SingleStep() { |
|
|
|
return RunLoop(false); |
|
|
|
} |
|
|
|
|
|
|
|
void System::InvalidateCpuInstructionCaches() { |
|
|
|
for (auto& cpu : impl->cpu_cores) { |
|
|
|
cpu->ArmInterface().ClearInstructionCache(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) { |
|
|
|
return impl->Load(emu_window, filepath); |
|
|
|
} |
|
|
|
|
|
|
|
bool System::IsPoweredOn() const { |
|
|
|
return impl->cpu_barrier && impl->cpu_barrier->IsAlive(); |
|
|
|
} |
|
|
|
|
|
|
|
void System::PrepareReschedule() { |
|
|
|
CurrentCpuCore().PrepareReschedule(); |
|
|
|
} |
|
|
|
|
|
|
|
PerfStats::Results System::GetAndResetPerfStats() { |
|
|
|
return impl->GetAndResetPerfStats(); |
|
|
|
} |
|
|
|
|
|
|
|
Core::TelemetrySession& System::TelemetrySession() const { |
|
|
|
return *impl->telemetry_session; |
|
|
|
} |
|
|
|
|
|
|
|
ARM_Interface& System::CurrentArmInterface() { |
|
|
|
return CurrentCpuCore().ArmInterface(); |
|
|
|
} |
|
|
|
|
|
|
|
size_t System::CurrentCoreIndex() { |
|
|
|
return CurrentCpuCore().CoreIndex(); |
|
|
|
} |
|
|
|
|
|
|
|
Kernel::Scheduler& System::CurrentScheduler() { |
|
|
|
return *CurrentCpuCore().Scheduler(); |
|
|
|
} |
|
|
|
|
|
|
|
const std::shared_ptr<Kernel::Scheduler>& System::Scheduler(size_t core_index) { |
|
|
|
ASSERT(core_index < NUM_CPU_CORES); |
|
|
|
return impl->cpu_cores[core_index]->Scheduler(); |
|
|
|
} |
|
|
|
|
|
|
|
Kernel::SharedPtr<Kernel::Process>& System::CurrentProcess() { |
|
|
|
return impl->current_process; |
|
|
|
} |
|
|
|
|
|
|
|
ARM_Interface& System::ArmInterface(size_t core_index) { |
|
|
|
ASSERT(core_index < NUM_CPU_CORES); |
|
|
|
return impl->cpu_cores[core_index]->ArmInterface(); |
|
|
|
} |
|
|
|
|
|
|
|
Cpu& System::CpuCore(size_t core_index) { |
|
|
|
ASSERT(core_index < NUM_CPU_CORES); |
|
|
|
return *impl->cpu_cores[core_index]; |
|
|
|
} |
|
|
|
|
|
|
|
ExclusiveMonitor& System::Monitor() { |
|
|
|
return *impl->cpu_exclusive_monitor; |
|
|
|
} |
|
|
|
|
|
|
|
Tegra::GPU& System::GPU() { |
|
|
|
return *impl->gpu_core; |
|
|
|
} |
|
|
|
|
|
|
|
const Tegra::GPU& System::GPU() const { |
|
|
|
return *impl->gpu_core; |
|
|
|
} |
|
|
|
|
|
|
|
VideoCore::RendererBase& System::Renderer() { |
|
|
|
return *impl->renderer; |
|
|
|
} |
|
|
|
|
|
|
|
const VideoCore::RendererBase& System::Renderer() const { |
|
|
|
return *impl->renderer; |
|
|
|
} |
|
|
|
|
|
|
|
Kernel::KernelCore& System::Kernel() { |
|
|
|
return impl->kernel; |
|
|
|
} |
|
|
|
|
|
|
|
const Kernel::KernelCore& System::Kernel() const { |
|
|
|
return impl->kernel; |
|
|
|
} |
|
|
|
|
|
|
|
Core::PerfStats& System::GetPerfStats() { |
|
|
|
return impl->perf_stats; |
|
|
|
} |
|
|
|
|
|
|
|
const Core::PerfStats& System::GetPerfStats() const { |
|
|
|
return impl->perf_stats; |
|
|
|
} |
|
|
|
|
|
|
|
Core::FrameLimiter& System::FrameLimiter() { |
|
|
|
return impl->frame_limiter; |
|
|
|
} |
|
|
|
|
|
|
|
const Core::FrameLimiter& System::FrameLimiter() const { |
|
|
|
return impl->frame_limiter; |
|
|
|
} |
|
|
|
|
|
|
|
Loader::ResultStatus System::GetGameName(std::string& out) const { |
|
|
|
return impl->GetGameName(out); |
|
|
|
} |
|
|
|
|
|
|
|
void System::SetStatus(ResultStatus new_status, const char* details) { |
|
|
|
impl->SetStatus(new_status, details); |
|
|
|
} |
|
|
|
|
|
|
|
const std::string& System::GetStatusDetails() const { |
|
|
|
return impl->status_details; |
|
|
|
} |
|
|
|
|
|
|
|
Loader::AppLoader& System::GetAppLoader() const { |
|
|
|
return *impl->app_loader; |
|
|
|
} |
|
|
|
|
|
|
|
void System::SetGPUDebugContext(std::shared_ptr<Tegra::DebugContext> context) { |
|
|
|
impl->debug_context = std::move(context); |
|
|
|
} |
|
|
|
|
|
|
|
std::shared_ptr<Tegra::DebugContext> System::GetGPUDebugContext() const { |
|
|
|
return impl->debug_context; |
|
|
|
} |
|
|
|
|
|
|
|
void System::SetFilesystem(FileSys::VirtualFilesystem vfs) { |
|
|
|
impl->virtual_filesystem = std::move(vfs); |
|
|
|
} |
|
|
|
|
|
|
|
FileSys::VirtualFilesystem System::GetFilesystem() const { |
|
|
|
return impl->virtual_filesystem; |
|
|
|
} |
|
|
|
|
|
|
|
System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) { |
|
|
|
return impl->Init(emu_window); |
|
|
|
} |
|
|
|
|
|
|
|
void System::Shutdown() { |
|
|
|
impl->Shutdown(); |
|
|
|
} |
|
|
|
|
|
|
|
Service::SM::ServiceManager& System::ServiceManager() { |
|
|
|
return *service_manager; |
|
|
|
return *impl->service_manager; |
|
|
|
} |
|
|
|
|
|
|
|
const Service::SM::ServiceManager& System::ServiceManager() const { |
|
|
|
return *service_manager; |
|
|
|
return *impl->service_manager; |
|
|
|
} |
|
|
|
|
|
|
|
} // namespace Core
|