From a19c93fbf8dad335db914e0789c7c54f256da013 Mon Sep 17 00:00:00 2001
From: crueter <crueter@noreply.localhost>
Date: Wed, 7 May 2025 20:15:25 +0000
Subject: [PATCH] Firmware 20.0.0 Initial Implementation & Android: Uninstall
 Firmware Button

Co-authored-by: Pavel Barabanov <pavelbarabanov94@gmail.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/89
Co-authored-by: crueter <crueter@noreply.localhost>
Co-committed-by: crueter <crueter@noreply.localhost>
---
 .../yuzu_emu/fragments/InstallableFragment.kt |  5 +++
 .../org/yuzu/yuzu_emu/ui/main/MainActivity.kt | 26 ++++++++++++++-
 .../app/src/main/res/values/strings.xml       |  5 +++
 src/core/hle/service/am/process_creation.cpp  | 33 +++++++++++--------
 .../all_system_applet_proxies_service.cpp     | 22 +++++++++++++
 .../all_system_applet_proxies_service.h       |  1 +
 .../am/service/applet_common_functions.cpp    |  5 +++
 .../am/service/library_applet_creator.cpp     |  4 ++-
 .../am/service/process_winding_controller.cpp |  7 +++-
 .../am/service/process_winding_controller.h   |  2 +-
 .../service/am/service/self_controller.cpp    |  5 +++
 .../hle/service/am/service/self_controller.h  |  1 +
 src/core/hle/service/caps/caps_a.cpp          | 11 +++++++
 src/core/hle/service/caps/caps_a.h            |  4 +++
 .../ns/application_manager_interface.cpp      | 20 +++++++++++
 .../ns/application_manager_interface.h        |  3 ++
 .../service/pctl/parental_control_service.cpp | 19 +++++++++--
 .../service/pctl/parental_control_service.h   |  2 ++
 18 files changed, 155 insertions(+), 20 deletions(-)

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt
index 22976900f2..9c39f7b166 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt
@@ -133,6 +133,11 @@ class InstallableFragment : Fragment() {
                 R.string.install_firmware_description,
                 install = { mainActivity.getFirmware.launch(arrayOf("application/zip")) }
             ),
+            Installable(
+                 R.string.uninstall_firmware,
+                 R.string.uninstall_firmware_description,
+                 install = { mainActivity.uninstallFirmware() }
+             ),
             Installable(
                 R.string.install_prod_keys,
                 R.string.install_prod_keys_description,
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index 79780fa1f0..468c758c2f 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -376,7 +376,31 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
                 messageToShow
             }.show(supportFragmentManager, ProgressDialogFragment.TAG)
         }
-
+    fun uninstallFirmware() {
+        val firmwarePath = File(DirectoryInitialization.userDirectory + "/nand/system/Contents/registered/")
+        ProgressDialogFragment.newInstance(
+            this,
+            R.string.firmware_uninstalling
+        ) { progressCallback, _ ->
+            var messageToShow: Any
+            try {
+                // Ensure the firmware directory exists before attempting to delete
+                if (firmwarePath.exists()) {
+                    firmwarePath.deleteRecursively()
+                    // Optionally reinitialize the system or perform other necessary steps
+                    NativeLibrary.initializeSystem(true)
+                    homeViewModel.setCheckKeys(true)
+                    messageToShow = getString(R.string.firmware_uninstalled_success)
+                } else {
+                    messageToShow = getString(R.string.firmware_uninstalled_failure)
+                }
+            } catch (e: Exception) {
+                Log.error("[MainActivity] Firmware uninstall failed - ${e.message}")
+                messageToShow = getString(R.string.fatal_error)
+            }
+            messageToShow
+        }.show(supportFragmentManager, ProgressDialogFragment.TAG)
+    }
     val getAmiiboKey =
         registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
             if (result == null) {
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index c0de9394e3..00a6a9adf7 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -143,6 +143,11 @@
     <string name="firmware_installed_success">Firmware installed successfully</string>
     <string name="firmware_installed_failure">Firmware installation failed</string>
     <string name="firmware_installed_failure_description">Make sure the firmware nca files are at the root of the zip and try again.</string>
+    <string name="uninstall_firmware">Uninstall firmware</string>
+    <string name="uninstall_firmware_description">Uninstalling the firmware will remove it from the device and may affect game compatibility.</string>
+    <string name="firmware_uninstalling">Uninstalling firmware</string>
+    <string name="firmware_uninstalled_success">Firmware uninstalled successfully</string>
+    <string name="firmware_uninstalled_failure">Firmware uninstallation failed</string>
     <string name="share_log">Share debug logs</string>
     <string name="share_log_description">Share eden\'s log file to debug issues</string>
     <string name="share_log_missing">No log file found</string>
diff --git a/src/core/hle/service/am/process_creation.cpp b/src/core/hle/service/am/process_creation.cpp
index aaa03c4c39..81a8fb0b44 100644
--- a/src/core/hle/service/am/process_creation.cpp
+++ b/src/core/hle/service/am/process_creation.cpp
@@ -60,24 +60,29 @@ std::unique_ptr<Process> CreateProcessImpl(std::unique_ptr<Loader::AppLoader>& o
 } // Anonymous namespace
 
 std::unique_ptr<Process> CreateProcess(Core::System& system, u64 program_id,
-                                      u8 minimum_key_generation, u8 maximum_key_generation) {
-    FileSys::VirtualFile nca_raw = system.GetContentProviderUnion()
-        .GetEntryRaw(program_id, FileSys::ContentRecordType::Program);
+                                       u8 minimum_key_generation, u8 maximum_key_generation) {
+    // Attempt to load program NCA.
+    FileSys::VirtualFile nca_raw{};
 
+    // Get the program NCA from storage.
+    auto& storage = system.GetContentProviderUnion();
+    nca_raw = storage.GetEntryRaw(program_id, FileSys::ContentRecordType::Program);
+
+    // Ensure we retrieved a program NCA.
     if (!nca_raw) {
         return nullptr;
     }
 
-    FileSys::NCA nca(nca_raw);
-    if (nca.GetStatus() != Loader::ResultStatus::Success) {
-        return nullptr;
-    }
-
-    u8 current_gen = nca.GetKeyGeneration();
-    if (minimum_key_generation > 0 && (current_gen < minimum_key_generation ||
-                                      current_gen > maximum_key_generation)) {
-        LOG_WARNING(Service_LDR, "Program {:016X} has unsupported generation {}. "
-                   "Attempting to load anyway...", program_id, current_gen);
+    // Ensure we have a suitable version.
+    if (minimum_key_generation > 0) {
+        FileSys::NCA nca(nca_raw);
+        if (nca.GetStatus() == Loader::ResultStatus::Success &&
+            (nca.GetKeyGeneration() < minimum_key_generation ||
+             nca.GetKeyGeneration() > maximum_key_generation)) {
+            LOG_WARNING(Service_LDR, "Skipping program {:016X} with generation {}", program_id,
+                        nca.GetKeyGeneration());
+            return nullptr;
+        }
     }
 
     std::unique_ptr<Loader::AppLoader> loader;
@@ -101,7 +106,7 @@ std::unique_ptr<Process> CreateApplicationProcess(std::vector<u8>& out_control,
         out_control = nacp.GetRawBytes();
     } else {
         out_control.resize(sizeof(FileSys::RawNACP));
-        std::fill(out_control.begin(), out_control.end(), (u8) 0);
+        std::fill(out_control.begin(), out_control.end(), 0);
     }
 
     auto& storage = system.GetContentProviderUnion();
diff --git a/src/core/hle/service/am/service/all_system_applet_proxies_service.cpp b/src/core/hle/service/am/service/all_system_applet_proxies_service.cpp
index 5a787494a8..fc9c77fc36 100644
--- a/src/core/hle/service/am/service/all_system_applet_proxies_service.cpp
+++ b/src/core/hle/service/am/service/all_system_applet_proxies_service.cpp
@@ -18,6 +18,7 @@ IAllSystemAppletProxiesService::IAllSystemAppletProxiesService(Core::System& sys
     // clang-format off
     static const FunctionInfo functions[] = {
         {100, D<&IAllSystemAppletProxiesService::OpenSystemAppletProxy>, "OpenSystemAppletProxy"},
+        {110, D<&IAllSystemAppletProxiesService::OpenSystemAppletProxyForDebug>, "OpenSystemAppletProxyForDebug"},
         {200, D<&IAllSystemAppletProxiesService::OpenLibraryAppletProxyOld>, "OpenLibraryAppletProxyOld"},
         {201, D<&IAllSystemAppletProxiesService::OpenLibraryAppletProxy>, "OpenLibraryAppletProxy"},
         {300, nullptr, "OpenOverlayAppletProxy"},
@@ -25,6 +26,7 @@ IAllSystemAppletProxiesService::IAllSystemAppletProxiesService(Core::System& sys
         {400, nullptr, "CreateSelfLibraryAppletCreatorForDevelop"},
         {410, nullptr, "GetSystemAppletControllerForDebug"},
         {450, D<&IAllSystemAppletProxiesService::GetSystemProcessCommonFunctions>, "GetSystemProcessCommonFunctions"}, // 19.0.0+
+        {460, nullptr, "Unknown460"},
         {1000, nullptr, "GetDebugFunctions"},
     };
     // clang-format on
@@ -49,6 +51,26 @@ Result IAllSystemAppletProxiesService::OpenSystemAppletProxy(
     }
 }
 
+Result IAllSystemAppletProxiesService::OpenSystemAppletProxyForDebug(
+    Out<SharedPointer<ISystemAppletProxy>> out_proxy, ClientProcessId pid) {
+    LOG_DEBUG(Service_AM, "OpenSystemAppletProxyForDebug called");
+
+    auto process = system.ApplicationProcess();
+    if (!process) {
+        LOG_ERROR(Service_AM, "No application process available");
+        R_THROW(ResultUnknown);
+    }
+
+    if (const auto applet = GetAppletFromProcessId(pid)) {
+        *out_proxy = std::make_shared<ISystemAppletProxy>(
+            system, applet, process, m_window_system);
+        R_SUCCEED();
+    }
+
+    LOG_ERROR(Service_AM, "Applet not found for pid={}", pid.pid);
+    R_THROW(ResultUnknown);
+}
+
 Result IAllSystemAppletProxiesService::OpenLibraryAppletProxy(
     Out<SharedPointer<ILibraryAppletProxy>> out_library_applet_proxy, ClientProcessId pid,
     InCopyHandle<Kernel::KProcess> process_handle,
diff --git a/src/core/hle/service/am/service/all_system_applet_proxies_service.h b/src/core/hle/service/am/service/all_system_applet_proxies_service.h
index a3111c4c9b..72730ea55a 100644
--- a/src/core/hle/service/am/service/all_system_applet_proxies_service.h
+++ b/src/core/hle/service/am/service/all_system_applet_proxies_service.h
@@ -27,6 +27,7 @@ private:
     Result OpenSystemAppletProxy(Out<SharedPointer<ISystemAppletProxy>> out_system_applet_proxy,
                                  ClientProcessId pid,
                                  InCopyHandle<Kernel::KProcess> process_handle);
+    Result OpenSystemAppletProxyForDebug(Out<SharedPointer<ISystemAppletProxy>> out_proxy, ClientProcessId pid);
     Result OpenLibraryAppletProxy(Out<SharedPointer<ILibraryAppletProxy>> out_library_applet_proxy,
                                   ClientProcessId pid,
                                   InCopyHandle<Kernel::KProcess> process_handle,
diff --git a/src/core/hle/service/am/service/applet_common_functions.cpp b/src/core/hle/service/am/service/applet_common_functions.cpp
index a051000af4..ed203e979a 100644
--- a/src/core/hle/service/am/service/applet_common_functions.cpp
+++ b/src/core/hle/service/am/service/applet_common_functions.cpp
@@ -32,6 +32,11 @@ IAppletCommonFunctions::IAppletCommonFunctions(Core::System& system_,
         {91, nullptr, "OpenNamedChannelAsChild"},
         {100, nullptr, "SetApplicationCoreUsageMode"},
         {300, D<&IAppletCommonFunctions::GetCurrentApplicationId>, "GetCurrentApplicationId"},
+        {310, nullptr, "IsSystemAppletHomeMenu"}, //19.0.0+
+        {311, nullptr, "Unknown311"},
+        {320, nullptr, "SetGpuTimeSliceBoost"}, //19.0.0+
+        {321, nullptr, "SetGpuTimeSliceBoostDueToApplication"}, //19.0.0+
+        {350, nullptr, "Unknown350"},
     };
     // clang-format on
 
diff --git a/src/core/hle/service/am/service/library_applet_creator.cpp b/src/core/hle/service/am/service/library_applet_creator.cpp
index 3ffb03bc97..413388d40a 100644
--- a/src/core/hle/service/am/service/library_applet_creator.cpp
+++ b/src/core/hle/service/am/service/library_applet_creator.cpp
@@ -111,9 +111,11 @@ std::shared_ptr<ILibraryAppletAccessor> CreateGuestApplet(Core::System& system,
         Firmware1500 = 15,
         Firmware1600 = 16,
         Firmware1700 = 17,
+        Firmware1800 = 18,
+        Firmware1900 = 19,
     };
 
-    auto process = CreateProcess(system, program_id, Firmware1400, Firmware1700);
+    auto process = CreateProcess(system, program_id, Firmware1400, Firmware1900);
     if (!process) {
         // Couldn't initialize the guest process
         return {};
diff --git a/src/core/hle/service/am/service/process_winding_controller.cpp b/src/core/hle/service/am/service/process_winding_controller.cpp
index 10df830d70..a150248e71 100644
--- a/src/core/hle/service/am/service/process_winding_controller.cpp
+++ b/src/core/hle/service/am/service/process_winding_controller.cpp
@@ -15,7 +15,7 @@ IProcessWindingController::IProcessWindingController(Core::System& system_,
     static const FunctionInfo functions[] = {
         {0, D<&IProcessWindingController::GetLaunchReason>, "GetLaunchReason"},
         {11, D<&IProcessWindingController::OpenCallingLibraryApplet>, "OpenCallingLibraryApplet"},
-        {21, nullptr, "PushContext"},
+        {21, D<&IProcessWindingController::PushContext>, "PushContext"},
         {22, nullptr, "PopContext"},
         {23, nullptr, "CancelWindingReservation"},
         {30, nullptr, "WindAndDoReserved"},
@@ -51,4 +51,9 @@ Result IProcessWindingController::OpenCallingLibraryApplet(
     R_SUCCEED();
 }
 
+Result IProcessWindingController::PushContext() {
+    LOG_WARNING(Service_AM, "(STUBBED) called");
+    R_SUCCEED();
+}
+
 } // namespace Service::AM
diff --git a/src/core/hle/service/am/service/process_winding_controller.h b/src/core/hle/service/am/service/process_winding_controller.h
index 4408af1f1d..bcf341d94c 100644
--- a/src/core/hle/service/am/service/process_winding_controller.h
+++ b/src/core/hle/service/am/service/process_winding_controller.h
@@ -21,7 +21,7 @@ private:
     Result GetLaunchReason(Out<AppletProcessLaunchReason> out_launch_reason);
     Result OpenCallingLibraryApplet(
         Out<SharedPointer<ILibraryAppletAccessor>> out_calling_library_applet);
-
+    Result PushContext();
     const std::shared_ptr<Applet> m_applet;
 };
 
diff --git a/src/core/hle/service/am/service/self_controller.cpp b/src/core/hle/service/am/service/self_controller.cpp
index 1db02b88fd..fa36c93060 100644
--- a/src/core/hle/service/am/service/self_controller.cpp
+++ b/src/core/hle/service/am/service/self_controller.cpp
@@ -67,6 +67,7 @@ ISelfController::ISelfController(Core::System& system_, std::shared_ptr<Applet>
         {110, nullptr, "SetApplicationAlbumUserData"},
         {120, D<&ISelfController::SaveCurrentScreenshot>, "SaveCurrentScreenshot"},
         {130, D<&ISelfController::SetRecordVolumeMuted>, "SetRecordVolumeMuted"},
+        {230, D<&ISelfController::Unknown230>, "Unknown230"},
         {1000, nullptr, "GetDebugStorageChannel"},
     };
     // clang-format on
@@ -394,6 +395,10 @@ Result ISelfController::SaveCurrentScreenshot(Capture::AlbumReportOption album_r
 
     R_SUCCEED();
 }
+Result ISelfController::Unknown230() {
+    LOG_WARNING(Service_AM, "(STUBBED) called - function 230 (0xE6)");
+    R_SUCCEED();
+}
 
 Result ISelfController::SetRecordVolumeMuted(bool muted) {
     LOG_WARNING(Service_AM, "(STUBBED) called. muted={}", muted);
diff --git a/src/core/hle/service/am/service/self_controller.h b/src/core/hle/service/am/service/self_controller.h
index eca083cfe5..a384846231 100644
--- a/src/core/hle/service/am/service/self_controller.h
+++ b/src/core/hle/service/am/service/self_controller.h
@@ -62,6 +62,7 @@ private:
     Result GetAccumulatedSuspendedTickChangedEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
     Result SetAlbumImageTakenNotificationEnabled(bool enabled);
     Result SaveCurrentScreenshot(Capture::AlbumReportOption album_report_option);
+    Result Unknown230();
     Result SetRecordVolumeMuted(bool muted);
 
     Kernel::KProcess* const m_process;
diff --git a/src/core/hle/service/caps/caps_a.cpp b/src/core/hle/service/caps/caps_a.cpp
index 52228b830a..588cf2f09b 100644
--- a/src/core/hle/service/caps/caps_a.cpp
+++ b/src/core/hle/service/caps/caps_a.cpp
@@ -53,6 +53,8 @@ IAlbumAccessorService::IAlbumAccessorService(Core::System& system_,
         {8021, nullptr, "GetAlbumEntryFromApplicationAlbumEntryAruid"},
         {10011, nullptr, "SetInternalErrorConversionEnabled"},
         {50000, nullptr, "LoadMakerNoteInfoForDebug"},
+        {50011, C<&IAlbumAccessorService::GetAlbumAccessResultForDebug>, "GetAlbumAccessResultForDebug"},
+        {50012, C<&IAlbumAccessorService::SetAlbumAccessResultForDebug>, "SetAlbumAccessResultForDebug"},
         {60002, nullptr, "OpenAccessorSession"},
     };
     // clang-format on
@@ -137,6 +139,15 @@ Result IAlbumAccessorService::LoadAlbumScreenShotThumbnailImageEx1(
     R_RETURN(TranslateResult(result));
 }
 
+Result IAlbumAccessorService::GetAlbumAccessResultForDebug() {
+    LOG_DEBUG(Service_Capture, "(STUBBED) called.");
+    R_SUCCEED();
+}
+
+Result IAlbumAccessorService::SetAlbumAccessResultForDebug() {
+    LOG_DEBUG(Service_Capture, "(STUBBED) called.");
+    R_SUCCEED();
+}
 Result IAlbumAccessorService::TranslateResult(Result in_result) {
     if (in_result.IsSuccess()) {
         return in_result;
diff --git a/src/core/hle/service/caps/caps_a.h b/src/core/hle/service/caps/caps_a.h
index c7a5208e32..9590e71e70 100644
--- a/src/core/hle/service/caps/caps_a.h
+++ b/src/core/hle/service/caps/caps_a.h
@@ -50,6 +50,10 @@ private:
         OutArray<u8, BufferAttr_HipcMapAlias | BufferAttr_HipcMapTransferAllowsNonSecure> out_image,
         OutArray<u8, BufferAttr_HipcMapAlias> out_buffer);
 
+    Result GetAlbumAccessResultForDebug();
+
+    Result SetAlbumAccessResultForDebug();
+
     Result TranslateResult(Result in_result);
 
     std::shared_ptr<AlbumManager> manager = nullptr;
diff --git a/src/core/hle/service/ns/application_manager_interface.cpp b/src/core/hle/service/ns/application_manager_interface.cpp
index 7a91727f97..814e0b6e62 100644
--- a/src/core/hle/service/ns/application_manager_interface.cpp
+++ b/src/core/hle/service/ns/application_manager_interface.cpp
@@ -303,6 +303,9 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
         {3013, nullptr, "IsGameCardEnabled"},
         {3014, nullptr, "IsLocalContentShareEnabled"},
         {3050, nullptr, "ListAssignELicenseTaskResult"},
+        {4022, D<&IApplicationManagerInterface::Unknown4022>, "Unknown4022"},
+        {4023, D<&IApplicationManagerInterface::Unknown4023>, "Unknown4023"},
+        {4088, D<&IApplicationManagerInterface::Unknown4088>, "Unknown4088"},
         {9999, nullptr, "GetApplicationCertificate"},
     };
     // clang-format on
@@ -509,6 +512,23 @@ Result IApplicationManagerInterface::CheckApplicationLaunchVersion(u64 applicati
     R_SUCCEED();
 }
 
+Result IApplicationManagerInterface::Unknown4022(Out<u32> out_unknown) {
+    LOG_WARNING(Service_NS, "(STUBBED) Unknown4022 called");
+    *out_unknown = 0;
+    R_SUCCEED();
+}
+
+Result IApplicationManagerInterface::Unknown4023(Out<u32> out_unknown) {
+    LOG_WARNING(Service_NS, "(STUBBED) Unknown4022 called");
+
+    *out_unknown = 0;
+    R_SUCCEED();
+}
+Result IApplicationManagerInterface::Unknown4088() {
+    LOG_WARNING(Service_NS, "(STUBBED) Unknown4088 called");
+    R_SUCCEED();
+}
+
 Result IApplicationManagerInterface::GetApplicationTerminateResult(Out<Result> out_result,
                                                                    u64 application_id) {
     LOG_WARNING(Service_NS, "(STUBBED) called. application_id={:016X}", application_id);
diff --git a/src/core/hle/service/ns/application_manager_interface.h b/src/core/hle/service/ns/application_manager_interface.h
index f33d269b35..e96e50c8e1 100644
--- a/src/core/hle/service/ns/application_manager_interface.h
+++ b/src/core/hle/service/ns/application_manager_interface.h
@@ -48,6 +48,9 @@ public:
     Result IsApplicationUpdateRequested(Out<bool> out_update_required, Out<u32> out_update_version,
                                         u64 application_id);
     Result CheckApplicationLaunchVersion(u64 application_id);
+    Result Unknown4022(Out<u32> out_unknown);
+    Result Unknown4023(Out<u32> out_unknown);
+    Result Unknown4088();
     Result GetApplicationTerminateResult(Out<Result> out_result, u64 application_id);
 
 private:
diff --git a/src/core/hle/service/pctl/parental_control_service.cpp b/src/core/hle/service/pctl/parental_control_service.cpp
index ad14c7740b..a65088de9c 100644
--- a/src/core/hle/service/pctl/parental_control_service.cpp
+++ b/src/core/hle/service/pctl/parental_control_service.cpp
@@ -81,12 +81,12 @@ IParentalControlService::IParentalControlService(Core::System& system_, Capabili
         {1451, D<&IParentalControlService::StartPlayTimer>, "StartPlayTimer"},
         {1452, D<&IParentalControlService::StopPlayTimer>, "StopPlayTimer"},
         {1453, D<&IParentalControlService::IsPlayTimerEnabled>, "IsPlayTimerEnabled"},
-        {1454, nullptr, "GetPlayTimerRemainingTime"},
+        {1454, D<&IParentalControlService::GetPlayTimerRemainingTime>, "GetPlayTimerRemainingTime"},
         {1455, D<&IParentalControlService::IsRestrictedByPlayTimer>, "IsRestrictedByPlayTimer"},
         {1456, D<&IParentalControlService::GetPlayTimerSettingsOld>, "GetPlayTimerSettingsOld"},
         {1457, D<&IParentalControlService::GetPlayTimerEventToRequestSuspension>, "GetPlayTimerEventToRequestSuspension"},
         {1458, D<&IParentalControlService::IsPlayTimerAlarmDisabled>, "IsPlayTimerAlarmDisabled"},
-        {1459, nullptr, "GetPlayTimerRemainingTimeDisplayInfo"}, // 20.0.0+
+        {1459, D<&IParentalControlService::GetPlayTimerRemainingTimeDisplayInfo>, "GetPlayTimerRemainingTimeDisplayInfo"}, // 20.0.0+
         {1471, nullptr, "NotifyWrongPinCodeInputManyTimes"},
         {1472, nullptr, "CancelNetworkRequest"},
         {1473, D<&IParentalControlService::GetUnlinkedEvent>, "GetUnlinkedEvent"},
@@ -388,6 +388,12 @@ Result IParentalControlService::IsPlayTimerEnabled(Out<bool> out_is_play_timer_e
     R_SUCCEED();
 }
 
+Result IParentalControlService::GetPlayTimerRemainingTime(Out<s32> out_remaining_minutes) {
+    *out_remaining_minutes = 0;
+    LOG_WARNING(Service_PCTL, "(STUBBED) called, remaining_minutes={}", *out_remaining_minutes);
+    R_SUCCEED();
+}
+
 Result IParentalControlService::IsRestrictedByPlayTimer(Out<bool> out_is_restricted_by_play_timer) {
     *out_is_restricted_by_play_timer = false;
     LOG_WARNING(Service_PCTL, "(STUBBED) called, restricted={}", *out_is_restricted_by_play_timer);
@@ -422,6 +428,15 @@ Result IParentalControlService::IsPlayTimerAlarmDisabled(Out<bool> out_play_time
     R_SUCCEED();
 }
 
+Result IParentalControlService::GetPlayTimerRemainingTimeDisplayInfo(
+    Out<s32> out_remaining_minutes, Out<u32> out_unknown) {
+    *out_remaining_minutes = 0;
+    *out_unknown = 0;
+    LOG_WARNING(Service_PCTL, "(STUBBED) called, remaining_minutes={}, unknown={}",
+        *out_remaining_minutes, *out_unknown);
+    R_SUCCEED();
+}
+
 Result IParentalControlService::GetUnlinkedEvent(OutCopyHandle<Kernel::KReadableEvent> out_event) {
     LOG_INFO(Service_PCTL, "called");
     *out_event = unlinked_event.GetHandle();
diff --git a/src/core/hle/service/pctl/parental_control_service.h b/src/core/hle/service/pctl/parental_control_service.h
index d58c75f380..5b72ed1f03 100644
--- a/src/core/hle/service/pctl/parental_control_service.h
+++ b/src/core/hle/service/pctl/parental_control_service.h
@@ -45,10 +45,12 @@ private:
     Result StartPlayTimer();
     Result StopPlayTimer();
     Result IsPlayTimerEnabled(Out<bool> out_is_play_timer_enabled);
+    Result GetPlayTimerRemainingTime(Out<s32> out_remaining_minutes);
     Result IsRestrictedByPlayTimer(Out<bool> out_is_restricted_by_play_timer);
     Result GetPlayTimerSettingsOld(Out<PlayTimerSettings> out_play_timer_settings);
     Result GetPlayTimerEventToRequestSuspension(OutCopyHandle<Kernel::KReadableEvent> out_event);
     Result IsPlayTimerAlarmDisabled(Out<bool> out_play_timer_alarm_disabled);
+    Result GetPlayTimerRemainingTimeDisplayInfo(Out<s32> out_remaining_minutes, Out<u32> out_unknown);
     Result GetUnlinkedEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
     Result GetStereoVisionRestriction(Out<bool> out_stereo_vision_restriction);
     Result SetStereoVisionRestriction(bool stereo_vision_restriction);