diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
index 55abba093..53137b2e2 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
@@ -261,7 +261,7 @@ object NativeLibrary {
     /**
      * Begins emulation.
      */
-    external fun run(path: String?)
+    external fun run(path: String?, programIndex: Int = 0)
 
     // Surface Handling
     external fun surfaceChanged(surf: Surface?)
@@ -489,6 +489,12 @@ object NativeLibrary {
         sEmulationActivity.get()!!.onEmulationStopped(status)
     }
 
+    @Keep
+    @JvmStatic
+    fun onProgramChanged(programIndex: Int) {
+        sEmulationActivity.get()!!.onProgramChanged(programIndex)
+    }
+
     /**
      * Logs the Yuzu version, Android version and, CPU.
      */
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
index 26cddecf4..564aaf305 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
@@ -76,7 +76,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
 
     override fun onDestroy() {
         stopForegroundService(this)
-        emulationViewModel.clear()
         super.onDestroy()
     }
 
@@ -446,9 +445,14 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
     }
 
     fun onEmulationStopped(status: Int) {
-        if (status == 0) {
+        if (status == 0 && emulationViewModel.programChanged.value == -1) {
             finish()
         }
+        emulationViewModel.setEmulationStopped(true)
+    }
+
+    fun onProgramChanged(programIndex: Int) {
+        emulationViewModel.setProgramChanged(programIndex)
     }
 
     private fun startMotionSensorListener() {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
index ef393c4be..1f591ced1 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
@@ -424,10 +424,38 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
                     }
                 }
             }
+            launch {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    emulationViewModel.programChanged.collect {
+                        if (it != 0) {
+                            emulationViewModel.setEmulationStarted(false)
+                            binding.drawerLayout.close()
+                            binding.drawerLayout
+                                .setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
+                            ViewUtils.hideView(binding.surfaceInputOverlay)
+                            ViewUtils.showView(binding.loadingIndicator)
+                        }
+                    }
+                }
+            }
+            launch {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    emulationViewModel.emulationStopped.collect {
+                        if (it && emulationViewModel.programChanged.value != -1) {
+                            if (perfStatsUpdater != null) {
+                                perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
+                            }
+                            emulationState.changeProgram(emulationViewModel.programChanged.value)
+                            emulationViewModel.setProgramChanged(-1)
+                            emulationViewModel.setEmulationStopped(false)
+                        }
+                    }
+                }
+            }
         }
     }
 
-    private fun startEmulation() {
+    private fun startEmulation(programIndex: Int = 0) {
         if (!NativeLibrary.isRunning() && !NativeLibrary.isPaused()) {
             if (!DirectoryInitialization.areDirectoriesReady) {
                 DirectoryInitialization.start()
@@ -435,7 +463,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
 
             updateScreenLayout()
 
-            emulationState.run(emulationActivity!!.isActivityRecreated)
+            emulationState.run(emulationActivity!!.isActivityRecreated, programIndex)
         }
     }
 
@@ -833,6 +861,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
     ) {
         private var state: State
         private var surface: Surface? = null
+        lateinit var emulationThread: Thread
 
         init {
             // Starting state is stopped.
@@ -878,7 +907,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
         }
 
         @Synchronized
-        fun run(isActivityRecreated: Boolean) {
+        fun run(isActivityRecreated: Boolean, programIndex: Int = 0) {
             if (isActivityRecreated) {
                 if (NativeLibrary.isRunning()) {
                     state = State.PAUSED
@@ -889,10 +918,20 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
 
             // If the surface is set, run now. Otherwise, wait for it to get set.
             if (surface != null) {
-                runWithValidSurface()
+                runWithValidSurface(programIndex)
             }
         }
 
+        @Synchronized
+        fun changeProgram(programIndex: Int) {
+            emulationThread.join()
+            emulationThread = Thread({
+                Log.debug("[EmulationFragment] Starting emulation thread.")
+                NativeLibrary.run(gamePath, programIndex)
+            }, "NativeEmulation")
+            emulationThread.start()
+        }
+
         // Surface callbacks
         @Synchronized
         fun newSurface(surface: Surface?) {
@@ -932,7 +971,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
             }
         }
 
-        private fun runWithValidSurface() {
+        private fun runWithValidSurface(programIndex: Int = 0) {
             NativeLibrary.surfaceChanged(surface)
             if (!emulationCanStart.invoke()) {
                 return
@@ -940,9 +979,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
 
             when (state) {
                 State.STOPPED -> {
-                    val emulationThread = Thread({
+                    emulationThread = Thread({
                         Log.debug("[EmulationFragment] Starting emulation thread.")
-                        NativeLibrary.run(gamePath)
+                        NativeLibrary.run(gamePath, programIndex)
                     }, "NativeEmulation")
                     emulationThread.start()
                 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt
index b66f47fe7..d024493cd 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt
@@ -15,6 +15,12 @@ class EmulationViewModel : ViewModel() {
     val isEmulationStopping: StateFlow<Boolean> get() = _isEmulationStopping
     private val _isEmulationStopping = MutableStateFlow(false)
 
+    private val _emulationStopped = MutableStateFlow(false)
+    val emulationStopped = _emulationStopped.asStateFlow()
+
+    private val _programChanged = MutableStateFlow(-1)
+    val programChanged = _programChanged.asStateFlow()
+
     val shaderProgress: StateFlow<Int> get() = _shaderProgress
     private val _shaderProgress = MutableStateFlow(0)
 
@@ -35,6 +41,17 @@ class EmulationViewModel : ViewModel() {
         _isEmulationStopping.value = value
     }
 
+    fun setEmulationStopped(value: Boolean) {
+        if (value) {
+            _emulationStarted.value = false
+        }
+        _emulationStopped.value = value
+    }
+
+    fun setProgramChanged(programIndex: Int) {
+        _programChanged.value = programIndex
+    }
+
     fun setShaderProgress(progress: Int) {
         _shaderProgress.value = progress
     }
@@ -56,20 +73,4 @@ class EmulationViewModel : ViewModel() {
     fun setDrawerOpen(value: Boolean) {
         _drawerOpen.value = value
     }
-
-    fun clear() {
-        setEmulationStarted(false)
-        setIsEmulationStopping(false)
-        setShaderProgress(0)
-        setTotalShaders(0)
-        setShaderMessage("")
-    }
-
-    companion object {
-        const val KEY_EMULATION_STARTED = "EmulationStarted"
-        const val KEY_IS_EMULATION_STOPPING = "IsEmulationStarting"
-        const val KEY_SHADER_PROGRESS = "ShaderProgress"
-        const val KEY_TOTAL_SHADERS = "TotalShaders"
-        const val KEY_SHADER_MESSAGE = "ShaderMessage"
-    }
 }
diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp
index 96f2ad3d4..f30100bd8 100644
--- a/src/android/app/src/main/jni/id_cache.cpp
+++ b/src/android/app/src/main/jni/id_cache.cpp
@@ -19,6 +19,7 @@ static jmethodID s_exit_emulation_activity;
 static jmethodID s_disk_cache_load_progress;
 static jmethodID s_on_emulation_started;
 static jmethodID s_on_emulation_stopped;
+static jmethodID s_on_program_changed;
 
 static jclass s_game_class;
 static jmethodID s_game_constructor;
@@ -123,6 +124,10 @@ jmethodID GetOnEmulationStopped() {
     return s_on_emulation_stopped;
 }
 
+jmethodID GetOnProgramChanged() {
+    return s_on_program_changed;
+}
+
 jclass GetGameClass() {
     return s_game_class;
 }
@@ -306,6 +311,8 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
         env->GetStaticMethodID(s_native_library_class, "onEmulationStarted", "()V");
     s_on_emulation_stopped =
         env->GetStaticMethodID(s_native_library_class, "onEmulationStopped", "(I)V");
+    s_on_program_changed =
+        env->GetStaticMethodID(s_native_library_class, "onProgramChanged", "(I)V");
 
     const jclass game_class = env->FindClass("org/yuzu/yuzu_emu/model/Game");
     s_game_class = reinterpret_cast<jclass>(env->NewGlobalRef(game_class));
diff --git a/src/android/app/src/main/jni/id_cache.h b/src/android/app/src/main/jni/id_cache.h
index a002e705d..00e48afc0 100644
--- a/src/android/app/src/main/jni/id_cache.h
+++ b/src/android/app/src/main/jni/id_cache.h
@@ -19,6 +19,7 @@ jmethodID GetExitEmulationActivity();
 jmethodID GetDiskCacheLoadProgress();
 jmethodID GetOnEmulationStarted();
 jmethodID GetOnEmulationStopped();
+jmethodID GetOnProgramChanged();
 
 jclass GetGameClass();
 jmethodID GetGameConstructor();
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index 3fd9a500c..958a77ac7 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -208,7 +208,8 @@ void EmulationSession::InitializeSystem(bool reload) {
     m_system.GetFileSystemController().CreateFactories(*m_vfs);
 }
 
-Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string& filepath) {
+Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string& filepath,
+                                                               const std::size_t program_index) {
     std::scoped_lock lock(m_mutex);
 
     // Create the render window.
@@ -238,7 +239,8 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string
     ConfigureFilesystemProvider(filepath);
 
     // Load the ROM.
-    m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath);
+    m_load_result =
+        m_system.Load(EmulationSession::GetInstance().Window(), filepath, 0, program_index);
     if (m_load_result != Core::SystemResultStatus::Success) {
         return m_load_result;
     }
@@ -248,6 +250,12 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string
     m_system.GetCpuManager().OnGpuReady();
     m_system.RegisterExitCallback([&] { HaltEmulation(); });
 
+    // Register an ExecuteProgram callback such that Core can execute a sub-program
+    m_system.RegisterExecuteProgramCallback([&](std::size_t program_index_) {
+        m_next_program_index = program_index_;
+        EmulationSession::GetInstance().HaltEmulation();
+    });
+
     OnEmulationStarted();
     return Core::SystemResultStatus::Success;
 }
@@ -255,6 +263,11 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string
 void EmulationSession::ShutdownEmulation() {
     std::scoped_lock lock(m_mutex);
 
+    if (m_next_program_index != -1) {
+        ChangeProgram(m_next_program_index);
+        m_next_program_index = -1;
+    }
+
     m_is_running = false;
 
     // Unload user input.
@@ -402,6 +415,12 @@ void EmulationSession::OnEmulationStopped(Core::SystemResultStatus result) {
                               static_cast<jint>(result));
 }
 
+void EmulationSession::ChangeProgram(std::size_t program_index) {
+    JNIEnv* env = IDCache::GetEnvForThread();
+    env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetOnProgramChanged(),
+                              static_cast<jint>(program_index));
+}
+
 u64 EmulationSession::GetProgramId(JNIEnv* env, jstring jprogramId) {
     auto program_id_string = GetJString(env, jprogramId);
     try {
@@ -411,7 +430,8 @@ u64 EmulationSession::GetProgramId(JNIEnv* env, jstring jprogramId) {
     }
 }
 
-static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
+static Core::SystemResultStatus RunEmulation(const std::string& filepath,
+                                             const size_t program_index = 0) {
     MicroProfileOnThreadCreate("EmuThread");
     SCOPE_EXIT({ MicroProfileShutdown(); });
 
@@ -424,7 +444,7 @@ static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
 
     SCOPE_EXIT({ EmulationSession::GetInstance().ShutdownEmulation(); });
 
-    jconst result = EmulationSession::GetInstance().InitializeEmulation(filepath);
+    jconst result = EmulationSession::GetInstance().InitializeEmulation(filepath, program_index);
     if (result != Core::SystemResultStatus::Success) {
         return result;
     }
@@ -689,11 +709,11 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_logSettings(JNIEnv* env, jobject jobj
     Settings::LogSettings();
 }
 
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2(JNIEnv* env, jclass clazz,
-                                                                    jstring j_path) {
+void Java_org_yuzu_yuzu_1emu_NativeLibrary_run(JNIEnv* env, jobject jobj, jstring j_path,
+                                               jint j_program_index) {
     const std::string path = GetJString(env, j_path);
 
-    const Core::SystemResultStatus result{RunEmulation(path)};
+    const Core::SystemResultStatus result{RunEmulation(path, j_program_index)};
     if (result != Core::SystemResultStatus::Success) {
         env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
                                   IDCache::GetExitEmulationActivity(), static_cast<int>(result));
diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h
index dadb138ad..bfe3fccca 100644
--- a/src/android/app/src/main/jni/native.h
+++ b/src/android/app/src/main/jni/native.h
@@ -45,7 +45,8 @@ public:
     const Core::PerfStatsResults& PerfStats();
     void ConfigureFilesystemProvider(const std::string& filepath);
     void InitializeSystem(bool reload);
-    Core::SystemResultStatus InitializeEmulation(const std::string& filepath);
+    Core::SystemResultStatus InitializeEmulation(const std::string& filepath,
+                                                 const std::size_t program_index = 0);
 
     bool IsHandheldOnly();
     void SetDeviceType([[maybe_unused]] int index, int type);
@@ -60,6 +61,7 @@ public:
 private:
     static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max);
     static void OnEmulationStopped(Core::SystemResultStatus result);
+    static void ChangeProgram(std::size_t program_index);
 
 private:
     // Window management
@@ -84,4 +86,7 @@ private:
     // Synchronization
     std::condition_variable_any m_cv;
     mutable std::mutex m_mutex;
+
+    // Program index for next boot
+    std::atomic<s32> m_next_program_index = -1;
 };