Merge pull request #4799 from bunnei/execute-program

core: Refactor loader and implement ExecuteProgram
master
bunnei 2020-11-24 15:27:22 +07:00 committed by GitHub
commit 0832da3e40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 146 additions and 36 deletions

@ -145,7 +145,7 @@ struct System::Impl {
} }
ResultStatus Init(System& system, Frontend::EmuWindow& emu_window) { ResultStatus Init(System& system, Frontend::EmuWindow& emu_window) {
LOG_DEBUG(HW_Memory, "initialized OK"); LOG_DEBUG(Core, "initialized OK");
device_memory = std::make_unique<Core::DeviceMemory>(); device_memory = std::make_unique<Core::DeviceMemory>();
@ -208,9 +208,11 @@ struct System::Impl {
return ResultStatus::Success; return ResultStatus::Success;
} }
ResultStatus Load(System& system, Frontend::EmuWindow& emu_window, ResultStatus Load(System& system, Frontend::EmuWindow& emu_window, const std::string& filepath,
const std::string& filepath) { std::size_t program_index) {
app_loader = Loader::GetLoader(system, GetGameFileFromPath(virtual_filesystem, filepath)); app_loader = Loader::GetLoader(system, GetGameFileFromPath(virtual_filesystem, filepath),
program_index);
if (!app_loader) { if (!app_loader) {
LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath); LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
return ResultStatus::ErrorGetLoader; return ResultStatus::ErrorGetLoader;
@ -416,6 +418,8 @@ struct System::Impl {
bool is_multicore{}; bool is_multicore{};
bool is_async_gpu{}; bool is_async_gpu{};
ExecuteProgramCallback execute_program_callback;
std::array<u64, Core::Hardware::NUM_CPU_CORES> dynarmic_ticks{}; std::array<u64, Core::Hardware::NUM_CPU_CORES> dynarmic_ticks{};
std::array<MicroProfileToken, Core::Hardware::NUM_CPU_CORES> microprofile_dynarmic{}; std::array<MicroProfileToken, Core::Hardware::NUM_CPU_CORES> microprofile_dynarmic{};
}; };
@ -451,8 +455,9 @@ void System::Shutdown() {
impl->Shutdown(); impl->Shutdown();
} }
System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) { System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath,
return impl->Load(*this, emu_window, filepath); std::size_t program_index) {
return impl->Load(*this, emu_window, filepath, program_index);
} }
bool System::IsPoweredOn() const { bool System::IsPoweredOn() const {
@ -789,4 +794,16 @@ bool System::IsMulticore() const {
return impl->is_multicore; return impl->is_multicore;
} }
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");
}
}
} // namespace Core } // namespace Core

@ -5,6 +5,7 @@
#pragma once #pragma once
#include <cstddef> #include <cstddef>
#include <functional>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
@ -173,9 +174,11 @@ public:
* @param emu_window Reference to the host-system window used for video output and keyboard * @param emu_window Reference to the host-system window used for video output and keyboard
* input. * input.
* @param filepath String path to the executable application to load on the host file system. * @param filepath String path to the executable application to load on the host file system.
* @param program_index Specifies the index within the container of the program to launch.
* @returns ResultStatus code, indicating if the operation succeeded. * @returns ResultStatus code, indicating if the operation succeeded.
*/ */
[[nodiscard]] ResultStatus Load(Frontend::EmuWindow& emu_window, const std::string& filepath); [[nodiscard]] ResultStatus Load(Frontend::EmuWindow& emu_window, const std::string& filepath,
std::size_t program_index = 0);
/** /**
* Indicates if the emulated system is powered on (all subsystems initialized and able to run an * Indicates if the emulated system is powered on (all subsystems initialized and able to run an
@ -385,6 +388,23 @@ public:
/// Tells if system is running on multicore. /// Tells if system is running on multicore.
[[nodiscard]] bool IsMulticore() const; [[nodiscard]] bool IsMulticore() const;
/// Type used for the frontend to designate a callback for System to re-launch the application
/// using a specified program index.
using ExecuteProgramCallback = std::function<void(std::size_t)>;
/**
* Registers a callback from the frontend for System to re-launch the application using a
* specified program index.
* @param callback Callback from the frontend to relaunch the application.
*/
void RegisterExecuteProgramCallback(ExecuteProgramCallback&& callback);
/**
* Instructs the frontend to re-launch the application using the specified program_index.
* @param program_index Specifies the index within the application of the program to launch.
*/
void ExecuteProgram(std::size_t program_index);
private: private:
System(); System();

@ -29,7 +29,7 @@ constexpr std::array partition_names{
"logo", "logo",
}; };
XCI::XCI(VirtualFile file_) XCI::XCI(VirtualFile file_, std::size_t program_index)
: file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA}, : file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA},
partitions(partition_names.size()), partitions(partition_names.size()),
partitions_raw(partition_names.size()), keys{Core::Crypto::KeyManager::Instance()} { partitions_raw(partition_names.size()), keys{Core::Crypto::KeyManager::Instance()} {
@ -62,7 +62,8 @@ XCI::XCI(VirtualFile file_)
} }
secure_partition = std::make_shared<NSP>( secure_partition = std::make_shared<NSP>(
main_hfs.GetFile(partition_names[static_cast<std::size_t>(XCIPartition::Secure)])); main_hfs.GetFile(partition_names[static_cast<std::size_t>(XCIPartition::Secure)]),
program_index);
ncas = secure_partition->GetNCAsCollapsed(); ncas = secure_partition->GetNCAsCollapsed();
program = program =

@ -78,7 +78,7 @@ enum class XCIPartition : u8 { Update, Normal, Secure, Logo };
class XCI : public ReadOnlyVfsDirectory { class XCI : public ReadOnlyVfsDirectory {
public: public:
explicit XCI(VirtualFile file); explicit XCI(VirtualFile file, std::size_t program_index = 0);
~XCI() override; ~XCI() override;
Loader::ResultStatus GetStatus() const; Loader::ResultStatus GetStatus() const;

@ -20,8 +20,8 @@
namespace FileSys { namespace FileSys {
NSP::NSP(VirtualFile file_) NSP::NSP(VirtualFile file_, std::size_t program_index)
: file(std::move(file_)), status{Loader::ResultStatus::Success}, : file(std::move(file_)), program_index(program_index), status{Loader::ResultStatus::Success},
pfs(std::make_shared<PartitionFilesystem>(file)), keys{Core::Crypto::KeyManager::Instance()} { pfs(std::make_shared<PartitionFilesystem>(file)), keys{Core::Crypto::KeyManager::Instance()} {
if (pfs->GetStatus() != Loader::ResultStatus::Success) { if (pfs->GetStatus() != Loader::ResultStatus::Success) {
status = pfs->GetStatus(); status = pfs->GetStatus();
@ -146,7 +146,7 @@ std::shared_ptr<NCA> NSP::GetNCA(u64 title_id, ContentRecordType type, TitleType
if (extracted) if (extracted)
LOG_WARNING(Service_FS, "called on an NSP that is of type extracted."); LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
const auto title_id_iter = ncas.find(title_id); const auto title_id_iter = ncas.find(title_id + program_index);
if (title_id_iter == ncas.end()) if (title_id_iter == ncas.end())
return nullptr; return nullptr;

@ -27,7 +27,7 @@ enum class ContentRecordType : u8;
class NSP : public ReadOnlyVfsDirectory { class NSP : public ReadOnlyVfsDirectory {
public: public:
explicit NSP(VirtualFile file); explicit NSP(VirtualFile file, std::size_t program_index = 0);
~NSP() override; ~NSP() override;
Loader::ResultStatus GetStatus() const; Loader::ResultStatus GetStatus() const;
@ -69,6 +69,8 @@ private:
VirtualFile file; VirtualFile file;
const std::size_t program_index;
bool extracted = false; bool extracted = false;
Loader::ResultStatus status; Loader::ResultStatus status;
std::map<u64, Loader::ResultStatus> program_status; std::map<u64, Loader::ResultStatus> program_status;

@ -1188,9 +1188,9 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_)
{102, &IApplicationFunctions::SetApplicationCopyrightVisibility, "SetApplicationCopyrightVisibility"}, {102, &IApplicationFunctions::SetApplicationCopyrightVisibility, "SetApplicationCopyrightVisibility"},
{110, &IApplicationFunctions::QueryApplicationPlayStatistics, "QueryApplicationPlayStatistics"}, {110, &IApplicationFunctions::QueryApplicationPlayStatistics, "QueryApplicationPlayStatistics"},
{111, &IApplicationFunctions::QueryApplicationPlayStatisticsByUid, "QueryApplicationPlayStatisticsByUid"}, {111, &IApplicationFunctions::QueryApplicationPlayStatisticsByUid, "QueryApplicationPlayStatisticsByUid"},
{120, nullptr, "ExecuteProgram"}, {120, &IApplicationFunctions::ExecuteProgram, "ExecuteProgram"},
{121, nullptr, "ClearUserChannel"}, {121, &IApplicationFunctions::ClearUserChannel, "ClearUserChannel"},
{122, nullptr, "UnpopToUserChannel"}, {122, &IApplicationFunctions::UnpopToUserChannel, "UnpopToUserChannel"},
{123, &IApplicationFunctions::GetPreviousProgramIndex, "GetPreviousProgramIndex"}, {123, &IApplicationFunctions::GetPreviousProgramIndex, "GetPreviousProgramIndex"},
{124, nullptr, "EnableApplicationAllThreadDumpOnCrash"}, {124, nullptr, "EnableApplicationAllThreadDumpOnCrash"},
{130, &IApplicationFunctions::GetGpuErrorDetectedSystemEvent, "GetGpuErrorDetectedSystemEvent"}, {130, &IApplicationFunctions::GetGpuErrorDetectedSystemEvent, "GetGpuErrorDetectedSystemEvent"},
@ -1561,6 +1561,34 @@ void IApplicationFunctions::QueryApplicationPlayStatisticsByUid(Kernel::HLEReque
rb.Push<u32>(0); rb.Push<u32>(0);
} }
void IApplicationFunctions::ExecuteProgram(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
IPC::RequestParser rp{ctx};
[[maybe_unused]] const auto unk_1 = rp.Pop<u32>();
[[maybe_unused]] const auto unk_2 = rp.Pop<u32>();
const auto program_index = rp.Pop<u64>();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
system.ExecuteProgram(program_index);
}
void IApplicationFunctions::ClearUserChannel(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void IApplicationFunctions::UnpopToUserChannel(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void IApplicationFunctions::GetPreviousProgramIndex(Kernel::HLERequestContext& ctx) { void IApplicationFunctions::GetPreviousProgramIndex(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called"); LOG_WARNING(Service_AM, "(STUBBED) called");

@ -287,6 +287,9 @@ private:
void SetApplicationCopyrightVisibility(Kernel::HLERequestContext& ctx); void SetApplicationCopyrightVisibility(Kernel::HLERequestContext& ctx);
void QueryApplicationPlayStatistics(Kernel::HLERequestContext& ctx); void QueryApplicationPlayStatistics(Kernel::HLERequestContext& ctx);
void QueryApplicationPlayStatisticsByUid(Kernel::HLERequestContext& ctx); void QueryApplicationPlayStatisticsByUid(Kernel::HLERequestContext& ctx);
void ExecuteProgram(Kernel::HLERequestContext& ctx);
void ClearUserChannel(Kernel::HLERequestContext& ctx);
void UnpopToUserChannel(Kernel::HLERequestContext& ctx);
void GetPreviousProgramIndex(Kernel::HLERequestContext& ctx); void GetPreviousProgramIndex(Kernel::HLERequestContext& ctx);
void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx); void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx);
void GetFriendInvitationStorageChannelEvent(Kernel::HLERequestContext& ctx); void GetFriendInvitationStorageChannelEvent(Kernel::HLERequestContext& ctx);

@ -198,10 +198,11 @@ AppLoader::~AppLoader() = default;
* @param system The system context to use. * @param system The system context to use.
* @param file The file to retrieve the loader for * @param file The file to retrieve the loader for
* @param type The file type * @param type The file type
* @param program_index Specifies the index within the container of the program to launch.
* @return std::unique_ptr<AppLoader> a pointer to a loader object; nullptr for unsupported type * @return std::unique_ptr<AppLoader> a pointer to a loader object; nullptr for unsupported type
*/ */
static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileSys::VirtualFile file, static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileSys::VirtualFile file,
FileType type) { FileType type, std::size_t program_index) {
switch (type) { switch (type) {
// Standard ELF file format. // Standard ELF file format.
case FileType::ELF: case FileType::ELF:
@ -222,7 +223,7 @@ static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileSys::V
// NX XCI (nX Card Image) file format. // NX XCI (nX Card Image) file format.
case FileType::XCI: case FileType::XCI:
return std::make_unique<AppLoader_XCI>(std::move(file), system.GetFileSystemController(), return std::make_unique<AppLoader_XCI>(std::move(file), system.GetFileSystemController(),
system.GetContentProvider()); system.GetContentProvider(), program_index);
// NX NAX (NintendoAesXts) file format. // NX NAX (NintendoAesXts) file format.
case FileType::NAX: case FileType::NAX:
@ -231,7 +232,7 @@ static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileSys::V
// NX NSP (Nintendo Submission Package) file format // NX NSP (Nintendo Submission Package) file format
case FileType::NSP: case FileType::NSP:
return std::make_unique<AppLoader_NSP>(std::move(file), system.GetFileSystemController(), return std::make_unique<AppLoader_NSP>(std::move(file), system.GetFileSystemController(),
system.GetContentProvider()); system.GetContentProvider(), program_index);
// NX KIP (Kernel Internal Process) file format // NX KIP (Kernel Internal Process) file format
case FileType::KIP: case FileType::KIP:
@ -246,7 +247,8 @@ static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileSys::V
} }
} }
std::unique_ptr<AppLoader> GetLoader(Core::System& system, FileSys::VirtualFile file) { std::unique_ptr<AppLoader> GetLoader(Core::System& system, FileSys::VirtualFile file,
std::size_t program_index) {
FileType type = IdentifyFile(file); FileType type = IdentifyFile(file);
const FileType filename_type = GuessFromFilename(file->GetName()); const FileType filename_type = GuessFromFilename(file->GetName());
@ -260,7 +262,7 @@ std::unique_ptr<AppLoader> GetLoader(Core::System& system, FileSys::VirtualFile
LOG_DEBUG(Loader, "Loading file {} as {}...", file->GetName(), GetFileTypeString(type)); LOG_DEBUG(Loader, "Loading file {} as {}...", file->GetName(), GetFileTypeString(type));
return GetFileLoader(system, std::move(file), type); return GetFileLoader(system, std::move(file), type, program_index);
} }
} // namespace Loader } // namespace Loader

@ -293,9 +293,11 @@ protected:
* *
* @param system The system context. * @param system The system context.
* @param file The bootable file. * @param file The bootable file.
* @param program_index Specifies the index within the container of the program to launch.
* *
* @return the best loader for this file. * @return the best loader for this file.
*/ */
std::unique_ptr<AppLoader> GetLoader(Core::System& system, FileSys::VirtualFile file); std::unique_ptr<AppLoader> GetLoader(Core::System& system, FileSys::VirtualFile file,
std::size_t program_index = 0);
} // namespace Loader } // namespace Loader

@ -23,8 +23,9 @@ namespace Loader {
AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file, AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file,
const Service::FileSystem::FileSystemController& fsc, const Service::FileSystem::FileSystemController& fsc,
const FileSys::ContentProvider& content_provider) const FileSys::ContentProvider& content_provider,
: AppLoader(file), nsp(std::make_unique<FileSys::NSP>(file)), std::size_t program_index)
: AppLoader(file), nsp(std::make_unique<FileSys::NSP>(file, program_index)),
title_id(nsp->GetProgramTitleID()) { title_id(nsp->GetProgramTitleID()) {
if (nsp->GetStatus() != ResultStatus::Success) { if (nsp->GetStatus() != ResultStatus::Success) {

@ -28,7 +28,8 @@ class AppLoader_NSP final : public AppLoader {
public: public:
explicit AppLoader_NSP(FileSys::VirtualFile file, explicit AppLoader_NSP(FileSys::VirtualFile file,
const Service::FileSystem::FileSystemController& fsc, const Service::FileSystem::FileSystemController& fsc,
const FileSys::ContentProvider& content_provider); const FileSys::ContentProvider& content_provider,
std::size_t program_index);
~AppLoader_NSP() override; ~AppLoader_NSP() override;
/** /**

@ -22,8 +22,9 @@ namespace Loader {
AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file, AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file,
const Service::FileSystem::FileSystemController& fsc, const Service::FileSystem::FileSystemController& fsc,
const FileSys::ContentProvider& content_provider) const FileSys::ContentProvider& content_provider,
: AppLoader(file), xci(std::make_unique<FileSys::XCI>(file)), std::size_t program_index)
: AppLoader(file), xci(std::make_unique<FileSys::XCI>(file, program_index)),
nca_loader(std::make_unique<AppLoader_NCA>(xci->GetProgramNCAFile())) { nca_loader(std::make_unique<AppLoader_NCA>(xci->GetProgramNCAFile())) {
if (xci->GetStatus() != ResultStatus::Success) { if (xci->GetStatus() != ResultStatus::Success) {
return; return;

@ -28,7 +28,8 @@ class AppLoader_XCI final : public AppLoader {
public: public:
explicit AppLoader_XCI(FileSys::VirtualFile file, explicit AppLoader_XCI(FileSys::VirtualFile file,
const Service::FileSystem::FileSystemController& fsc, const Service::FileSystem::FileSystemController& fsc,
const FileSys::ContentProvider& content_provider); const FileSys::ContentProvider& content_provider,
std::size_t program_index);
~AppLoader_XCI() override; ~AppLoader_XCI() override;
/** /**

@ -302,6 +302,12 @@ GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_,
this->setMouseTracking(true); this->setMouseTracking(true);
connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete); connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete);
connect(this, &GRenderWindow::ExecuteProgramSignal, parent, &GMainWindow::OnExecuteProgram,
Qt::QueuedConnection);
}
void GRenderWindow::ExecuteProgram(std::size_t program_index) {
emit ExecuteProgramSignal(program_index);
} }
GRenderWindow::~GRenderWindow() { GRenderWindow::~GRenderWindow() {

@ -166,6 +166,12 @@ public:
std::pair<u32, u32> ScaleTouch(const QPointF& pos) const; std::pair<u32, u32> ScaleTouch(const QPointF& pos) const;
/**
* Instructs the window to re-launch the application using the specified program_index.
* @param program_index Specifies the index within the application of the program to launch.
*/
void ExecuteProgram(std::size_t program_index);
public slots: public slots:
void OnEmulationStarting(EmuThread* emu_thread); void OnEmulationStarting(EmuThread* emu_thread);
void OnEmulationStopping(); void OnEmulationStopping();
@ -175,6 +181,7 @@ signals:
/// Emitted when the window is closed /// Emitted when the window is closed
void Closed(); void Closed();
void FirstFrameDisplayed(); void FirstFrameDisplayed();
void ExecuteProgramSignal(std::size_t program_index);
private: private:
void TouchBeginEvent(const QTouchEvent* event); void TouchBeginEvent(const QTouchEvent* event);

@ -978,7 +978,7 @@ void GMainWindow::AllowOSSleep() {
#endif #endif
} }
bool GMainWindow::LoadROM(const QString& filename) { bool GMainWindow::LoadROM(const QString& filename, std::size_t program_index) {
// Shutdown previous session if the emu thread is still active... // Shutdown previous session if the emu thread is still active...
if (emu_thread != nullptr) if (emu_thread != nullptr)
ShutdownGame(); ShutdownGame();
@ -1003,7 +1003,8 @@ bool GMainWindow::LoadROM(const QString& filename) {
system.RegisterHostThread(); system.RegisterHostThread();
const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())}; const Core::System::ResultStatus result{
system.Load(*render_window, filename.toStdString(), program_index)};
const auto drd_callout = const auto drd_callout =
(UISettings::values.callout_flags & static_cast<u32>(CalloutFlag::DRDDeprecation)) == 0; (UISettings::values.callout_flags & static_cast<u32>(CalloutFlag::DRDDeprecation)) == 0;
@ -1085,14 +1086,18 @@ void GMainWindow::SelectAndSetCurrentUser() {
Settings::values.current_user = dialog.GetIndex(); Settings::values.current_user = dialog.GetIndex();
} }
void GMainWindow::BootGame(const QString& filename) { void GMainWindow::BootGame(const QString& filename, std::size_t program_index) {
LOG_INFO(Frontend, "yuzu starting..."); LOG_INFO(Frontend, "yuzu starting...");
StoreRecentFile(filename); // Put the filename on top of the list StoreRecentFile(filename); // Put the filename on top of the list
u64 title_id{0}; u64 title_id{0};
last_filename_booted = filename;
auto& system = Core::System::GetInstance(); auto& system = Core::System::GetInstance();
const auto v_file = Core::GetGameFileFromPath(vfs, filename.toUtf8().constData()); const auto v_file = Core::GetGameFileFromPath(vfs, filename.toUtf8().constData());
const auto loader = Loader::GetLoader(system, v_file); const auto loader = Loader::GetLoader(system, v_file, program_index);
if (!(loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success)) { if (!(loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success)) {
// Load per game settings // Load per game settings
Config per_game_config(fmt::format("{:016X}", title_id), Config::ConfigType::PerGameConfig); Config per_game_config(fmt::format("{:016X}", title_id), Config::ConfigType::PerGameConfig);
@ -1106,7 +1111,7 @@ void GMainWindow::BootGame(const QString& filename) {
SelectAndSetCurrentUser(); SelectAndSetCurrentUser();
} }
if (!LoadROM(filename)) if (!LoadROM(filename, program_index))
return; return;
// Create and start the emulation thread // Create and start the emulation thread
@ -1114,6 +1119,10 @@ void GMainWindow::BootGame(const QString& filename) {
emit EmulationStarting(emu_thread.get()); emit EmulationStarting(emu_thread.get());
emu_thread->start(); emu_thread->start();
// Register an ExecuteProgram callback such that Core can execute a sub-program
system.RegisterExecuteProgramCallback(
[this](std::size_t program_index) { render_window->ExecuteProgram(program_index); });
connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
// BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views // BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views
// before the CPU continues // before the CPU continues
@ -2136,6 +2145,11 @@ void GMainWindow::OnLoadComplete() {
loading_screen->OnLoadComplete(); loading_screen->OnLoadComplete();
} }
void GMainWindow::OnExecuteProgram(std::size_t program_index) {
ShutdownGame();
BootGame(last_filename_booted, program_index);
}
void GMainWindow::ErrorDisplayDisplayError(QString body) { void GMainWindow::ErrorDisplayDisplayError(QString body) {
QMessageBox::critical(this, tr("Error Display"), body); QMessageBox::critical(this, tr("Error Display"), body);
emit ErrorDisplayFinished(); emit ErrorDisplayFinished();

@ -131,6 +131,7 @@ signals:
public slots: public slots:
void OnLoadComplete(); void OnLoadComplete();
void OnExecuteProgram(std::size_t program_index);
void ControllerSelectorReconfigureControllers( void ControllerSelectorReconfigureControllers(
const Core::Frontend::ControllerParameters& parameters); const Core::Frontend::ControllerParameters& parameters);
void ErrorDisplayDisplayError(QString body); void ErrorDisplayDisplayError(QString body);
@ -154,8 +155,8 @@ private:
void PreventOSSleep(); void PreventOSSleep();
void AllowOSSleep(); void AllowOSSleep();
bool LoadROM(const QString& filename); bool LoadROM(const QString& filename, std::size_t program_index);
void BootGame(const QString& filename); void BootGame(const QString& filename, std::size_t program_index = 0);
void ShutdownGame(); void ShutdownGame();
void ShowTelemetryCallout(); void ShowTelemetryCallout();
@ -317,6 +318,9 @@ private:
// Install progress dialog // Install progress dialog
QProgressDialog* install_progress; QProgressDialog* install_progress;
// Last game booted, used for multi-process apps
QString last_filename_booted;
protected: protected:
void dropEvent(QDropEvent* event) override; void dropEvent(QDropEvent* event) override;
void dragEnterEvent(QDragEnterEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override;