Explorar o código

Merge pull request #4799 from bunnei/execute-program

core: Refactor loader and implement ExecuteProgram
bunnei %!s(int64=5) %!d(string=hai) anos
pai
achega
0832da3e40

+ 23 - 6
src/core/core.cpp

@@ -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,
-                      const std::string& filepath) {
-        app_loader = Loader::GetLoader(system, GetGameFileFromPath(virtual_filesystem, filepath));
+    ResultStatus Load(System& system, Frontend::EmuWindow& emu_window, const std::string& filepath,
+                      std::size_t program_index) {
+        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) {
-    return impl->Load(*this, emu_window, filepath);
+System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& 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

+ 21 - 1
src/core/core.h

@@ -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();
 
 

+ 3 - 2
src/core/file_sys/card_image.cpp

@@ -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 =

+ 1 - 1
src/core/file_sys/card_image.h

@@ -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;

+ 3 - 3
src/core/file_sys/submission_package.cpp

@@ -20,8 +20,8 @@
 
 
 namespace FileSys {
 namespace FileSys {
 
 
-NSP::NSP(VirtualFile file_)
-    : file(std::move(file_)), status{Loader::ResultStatus::Success},
+NSP::NSP(VirtualFile file_, std::size_t program_index)
+    : 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;
 
 

+ 3 - 1
src/core/file_sys/submission_package.h

@@ -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;

+ 31 - 3
src/core/hle/service/am/am.cpp

@@ -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"},
-        {121, nullptr, "ClearUserChannel"},
-        {122, nullptr, "UnpopToUserChannel"},
+        {120, &IApplicationFunctions::ExecuteProgram, "ExecuteProgram"},
+        {121, &IApplicationFunctions::ClearUserChannel, "ClearUserChannel"},
+        {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");
 
 

+ 3 - 0
src/core/hle/service/am/am.h

@@ -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);

+ 7 - 5
src/core/loader/loader.cpp

@@ -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

+ 3 - 1
src/core/loader/loader.h

@@ -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

+ 3 - 2
src/core/loader/nsp.cpp

@@ -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)
-    : AppLoader(file), nsp(std::make_unique<FileSys::NSP>(file)),
+                             const FileSys::ContentProvider& content_provider,
+                             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) {

+ 2 - 1
src/core/loader/nsp.h

@@ -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;
 
 
     /**
     /**

+ 3 - 2
src/core/loader/xci.cpp

@@ -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)
-    : AppLoader(file), xci(std::make_unique<FileSys::XCI>(file)),
+                             const FileSys::ContentProvider& content_provider,
+                             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;

+ 2 - 1
src/core/loader/xci.h

@@ -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;
 
 
     /**
     /**

+ 6 - 0
src/yuzu/bootmanager.cpp

@@ -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() {

+ 7 - 0
src/yuzu/bootmanager.h

@@ -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);

+ 19 - 5
src/yuzu/main.cpp

@@ -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();

+ 6 - 2
src/yuzu/main.h

@@ -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);
-    void BootGame(const QString& filename);
+    bool LoadROM(const QString& filename, std::size_t program_index);
+    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;