// Copyright 2018 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include "common/archives.h" #include "common/file_util.h" #include "common/settings.h" #include "core/core.h" #include "core/frontend/input.h" #include "core/hle/applets/applet.h" #include "core/hle/applets/erreula.h" #include "core/hle/applets/mii_selector.h" #include "core/hle/applets/mint.h" #include "core/hle/applets/swkbd.h" #include "core/hle/service/am/am.h" #include "core/hle/service/apt/applet_manager.h" #include "core/hle/service/apt/errors.h" #include "core/hle/service/apt/ns.h" #include "core/hle/service/cfg/cfg.h" #include "core/hle/service/gsp/gsp_gpu.h" #include "video_core/utils.h" SERVICE_CONSTRUCT_IMPL(Service::APT::AppletManager) namespace Service::APT { /// The interval at which the home button update callback will be called, 16.6ms static constexpr u64 button_update_interval_us = 16666; /// The interval at which the HLE Applet update callback will be called, 16.6ms. static constexpr u64 hle_applet_update_interval_us = 16666; struct AppletTitleData { // There are two possible applet ids for each applet. std::array applet_ids; // There's a specific TitleId per region for each applet. static constexpr std::size_t NumRegions = 7; std::array title_ids; std::array n3ds_title_ids = {0, 0, 0, 0, 0, 0, 0}; }; static constexpr std::size_t NumApplets = 29; static constexpr std::array applet_titleids = {{ {{AppletId::HomeMenu, AppletId::None}, {0x4003000008202, 0x4003000008F02, 0x4003000009802, 0x4003000008202, 0x400300000A102, 0x400300000A902, 0x400300000B102}}, {{AppletId::AlternateMenu, AppletId::None}, {0x4003000008102, 0x4003000008102, 0x4003000008102, 0x4003000008102, 0x4003000008102, 0x4003000008102, 0x4003000008102}}, {{AppletId::Camera, AppletId::None}, {0x4003000008402, 0x4003000009002, 0x4003000009902, 0x4003000008402, 0x400300000A202, 0x400300000AA02, 0x400300000B202}}, {{AppletId::FriendList, AppletId::None}, {0x4003000008D02, 0x4003000009602, 0x4003000009F02, 0x4003000008D02, 0x400300000A702, 0x400300000AF02, 0x400300000B702}}, {{AppletId::GameNotes, AppletId::None}, {0x4003000008702, 0x4003000009302, 0x4003000009C02, 0x4003000008702, 0x400300000A502, 0x400300000AD02, 0x400300000B502}}, {{AppletId::InternetBrowser, AppletId::None}, {0x4003000008802, 0x4003000009402, 0x4003000009D02, 0x4003000008802, 0x400300000A602, 0x400300000AE02, 0x400300000B602}, {0x4003020008802, 0x4003020009402, 0x4003020009D02, 0x4003020008802, 0, 0x400302000AE02, 0}}, {{AppletId::InstructionManual, AppletId::None}, {0x4003000008602, 0x4003000009202, 0x4003000009B02, 0x4003000008602, 0x400300000A402, 0x400300000AC02, 0x400300000B402}}, {{AppletId::Notifications, AppletId::None}, {0x4003000008E02, 0x4003000009702, 0x400300000A002, 0x4003000008E02, 0x400300000A802, 0x400300000B002, 0x400300000B802}}, {{AppletId::Miiverse, AppletId::None}, {0x400300000BC02, 0x400300000BD02, 0x400300000BE02, 0x400300000BC02, 0x4003000009E02, 0x4003000009502, 0x400300000B902}}, // These values obtained from an older NS dump firmware 4.5 {{AppletId::MiiversePost, AppletId::None}, {0x400300000BA02, 0x400300000BA02, 0x400300000BA02, 0x400300000BA02, 0x400300000BA02, 0x400300000BA02, 0x400300000BA02}}, // {AppletId::MiiversePost, AppletId::None, 0x4003000008302, 0x4003000008B02, 0x400300000BA02, // 0x4003000008302, 0x0, 0x0, 0x0}, {{AppletId::AmiiboSettings, AppletId::None}, {0x4003000009502, 0x4003000009E02, 0x400300000B902, 0x4003000009502, 0x0, 0x4003000008C02, 0x400300000BF02}}, {{AppletId::SoftwareKeyboard1, AppletId::SoftwareKeyboard2}, {0x400300000C002, 0x400300000C802, 0x400300000D002, 0x400300000C002, 0x400300000D802, 0x400300000DE02, 0x400300000E402}}, {{AppletId::Ed1, AppletId::Ed2}, {0x400300000C102, 0x400300000C902, 0x400300000D102, 0x400300000C102, 0x400300000D902, 0x400300000DF02, 0x400300000E502}}, {{AppletId::PnoteApp, AppletId::PnoteApp2}, {0x400300000C302, 0x400300000CB02, 0x400300000D302, 0x400300000C302, 0x400300000DB02, 0x400300000E102, 0x400300000E702}}, {{AppletId::SnoteApp, AppletId::SnoteApp2}, {0x400300000C402, 0x400300000CC02, 0x400300000D402, 0x400300000C402, 0x400300000DC02, 0x400300000E202, 0x400300000E802}}, {{AppletId::Error, AppletId::Error2}, {0x400300000C502, 0x400300000C502, 0x400300000C502, 0x400300000C502, 0x400300000CF02, 0x400300000CF02, 0x400300000CF02}}, {{AppletId::Mint, AppletId::Mint2}, {0x400300000C602, 0x400300000CE02, 0x400300000D602, 0x400300000C602, 0x400300000DD02, 0x400300000E302, 0x400300000E902}}, {{AppletId::Extrapad, AppletId::Extrapad2}, {0x400300000CD02, 0x400300000CD02, 0x400300000CD02, 0x400300000CD02, 0x400300000D502, 0x400300000D502, 0x400300000D502}}, {{AppletId::Memolib, AppletId::Memolib2}, {0x400300000F602, 0x400300000F602, 0x400300000F602, 0x400300000F602, 0x400300000F602, 0x400300000F602, 0x400300000F602}}, // TODO(Subv): Fill in the rest of the titleids }}; static u64 GetTitleIdForApplet(AppletId id, u32 region_value) { ASSERT_MSG(id != AppletId::None, "Invalid applet id"); auto itr = std::find_if(applet_titleids.begin(), applet_titleids.end(), [id](const AppletTitleData& data) { return data.applet_ids[0] == id || data.applet_ids[1] == id; }); ASSERT_MSG(itr != applet_titleids.end(), "Unknown applet id 0x{:#05X}", id); auto n3ds_title_id = itr->n3ds_title_ids[region_value]; if (n3ds_title_id != 0 && Settings::values.is_new_3ds.GetValue()) { return n3ds_title_id; } return itr->title_ids[region_value]; } static bool IsSystemAppletId(AppletId applet_id) { return (static_cast(applet_id) & static_cast(AppletId::AnySystemApplet)) != 0; } static bool IsApplicationAppletId(AppletId applet_id) { return (static_cast(applet_id) & static_cast(AppletId::Application)) != 0; } AppletManager::AppletSlot AppletManager::GetAppletSlotFromId(AppletId id) { if (id == AppletId::Application) { if (GetAppletSlot(AppletSlot::Application)->applet_id != AppletId::None) return AppletSlot::Application; return AppletSlot::Error; } if (id == AppletId::AnySystemApplet) { if (GetAppletSlot(AppletSlot::SystemApplet)->applet_id != AppletId::None) return AppletSlot::SystemApplet; // The Home Menu is also a system applet, but it lives in its own slot to be able to run // concurrently with other system applets. if (GetAppletSlot(AppletSlot::HomeMenu)->applet_id != AppletId::None) return AppletSlot::HomeMenu; return AppletSlot::Error; } if (id == AppletId::AnyLibraryApplet || id == AppletId::AnySysLibraryApplet) { auto slot_data = GetAppletSlot(AppletSlot::LibraryApplet); if (slot_data->applet_id == AppletId::None) return AppletSlot::Error; auto applet_pos = slot_data->attributes.applet_pos.Value(); if ((id == AppletId::AnyLibraryApplet && applet_pos == AppletPos::Library) || (id == AppletId::AnySysLibraryApplet && applet_pos == AppletPos::SysLibrary)) return AppletSlot::LibraryApplet; return AppletSlot::Error; } if (id == AppletId::HomeMenu || id == AppletId::AlternateMenu) { if (GetAppletSlot(AppletSlot::HomeMenu)->applet_id != AppletId::None) return AppletSlot::HomeMenu; return AppletSlot::Error; } for (std::size_t slot = 0; slot < applet_slots.size(); ++slot) { if (applet_slots[slot].applet_id == id) { return static_cast(slot); } } return AppletSlot::Error; } AppletManager::AppletSlot AppletManager::GetAppletSlotFromAttributes(AppletAttributes attributes) { // Mapping from AppletPos to AppletSlot static constexpr std::array applet_position_slots = { AppletSlot::Application, AppletSlot::LibraryApplet, AppletSlot::SystemApplet, AppletSlot::LibraryApplet, AppletSlot::Error, AppletSlot::LibraryApplet}; auto applet_pos_value = static_cast(attributes.applet_pos.Value()); if (applet_pos_value >= applet_position_slots.size()) return AppletSlot::Error; auto slot = applet_position_slots[applet_pos_value]; if (slot == AppletSlot::Error) return AppletSlot::Error; // The Home Menu is a system applet, however, it has its own applet slot so that it can run // concurrently with other system applets. if (slot == AppletSlot::SystemApplet && attributes.is_home_menu) return AppletSlot::HomeMenu; return slot; } AppletManager::AppletSlot AppletManager::GetAppletSlotFromPos(AppletPos pos) { AppletId applet_id; switch (pos) { case AppletPos::Application: applet_id = AppletId::Application; break; case AppletPos::Library: applet_id = AppletId::AnyLibraryApplet; break; case AppletPos::System: applet_id = AppletId::AnySystemApplet; break; case AppletPos::SysLibrary: applet_id = AppletId::AnySysLibraryApplet; break; default: return AppletSlot::Error; } return GetAppletSlotFromId(applet_id); } void AppletManager::CancelAndSendParameter(const MessageParameter& parameter) { LOG_DEBUG( Service_APT, "Sending parameter from {:03X} to {:03X} with signal {:08X} and size {:08X}", parameter.sender_id, parameter.destination_id, parameter.signal, parameter.buffer.size()); // If the applet is being HLEd, send directly to the applet. const auto applet = hle_applets[parameter.destination_id]; if (applet != nullptr) { applet->ReceiveParameter(parameter); } else { // Otherwise, send the parameter the LLE way. next_parameter = parameter; if (parameter.signal == SignalType::RequestForSysApplet) { // APT handles RequestForSysApplet messages itself. LOG_DEBUG(Service_APT, "Replying to RequestForSysApplet from {:03X}", parameter.sender_id); if (parameter.buffer.size() >= sizeof(CaptureBufferInfo)) { SetCaptureInfo(parameter.buffer); CaptureFrameBuffers(); } next_parameter->sender_id = parameter.destination_id; next_parameter->destination_id = parameter.sender_id; next_parameter->signal = SignalType::Response; next_parameter->buffer.clear(); next_parameter->object = nullptr; } else if (IsSystemAppletId(parameter.sender_id) && IsApplicationAppletId(parameter.destination_id) && parameter.object) { // When a message is sent from a system applet to an application, APT // replaces its object with the zero handle. next_parameter->object = nullptr; } // Signal the event to let the receiver know that a new parameter is ready to be read auto slot = GetAppletSlotFromId(next_parameter->destination_id); if (slot != AppletSlot::Error) { GetAppletSlot(slot)->parameter_event->Signal(); } else { LOG_DEBUG(Service_APT, "No applet was registered with ID {:03X}", next_parameter->destination_id); } } } Result AppletManager::SendParameter(const MessageParameter& parameter) { // A new parameter can not be sent if the previous one hasn't been consumed yet if (next_parameter) { LOG_WARNING(Service_APT, "Parameter from {:03X} to {:03X} blocked by pending parameter.", parameter.sender_id, parameter.destination_id); return {ErrCodes::ParameterPresent, ErrorModule::Applet, ErrorSummary::InvalidState, ErrorLevel::Status}; } CancelAndSendParameter(parameter); return ResultSuccess; } ResultVal AppletManager::GlanceParameter(AppletId app_id) { if (!next_parameter) { return Result(ErrorDescription::NoData, ErrorModule::Applet, ErrorSummary::InvalidState, ErrorLevel::Status); } if (next_parameter->destination_id != app_id) { return Result(ErrorDescription::NotFound, ErrorModule::Applet, ErrorSummary::NotFound, ErrorLevel::Status); } auto parameter = *next_parameter; // Note: The NS module always clears the DSPSleep and DSPWakeup signals even in GlanceParameter. if (next_parameter->signal == SignalType::DspSleep || next_parameter->signal == SignalType::DspWakeup) { next_parameter = {}; } return parameter; } ResultVal AppletManager::ReceiveParameter(AppletId app_id) { auto result = GlanceParameter(app_id); if (result.Succeeded()) { LOG_DEBUG(Service_APT, "Received parameter from {:03X} to {:03X} with signal {:08X} and size {:08X}", result->sender_id, result->destination_id, result->signal, result->buffer.size()); // Clear the parameter next_parameter = {}; } return result; } bool AppletManager::CancelParameter(bool check_sender, AppletId sender_appid, bool check_receiver, AppletId receiver_appid) { auto cancellation_success = next_parameter && (!check_sender || next_parameter->sender_id == sender_appid) && (!check_receiver || next_parameter->destination_id == receiver_appid); if (cancellation_success) next_parameter = {}; return cancellation_success; } ResultVal AppletManager::GetLockHandle( AppletAttributes attributes) { auto corrected_attributes = attributes; if (attributes.applet_pos == AppletPos::Library || attributes.applet_pos == AppletPos::SysLibrary || attributes.applet_pos == AppletPos::AutoLibrary) { auto corrected_pos = last_library_launcher_slot == AppletSlot::Application ? AppletPos::Library : AppletPos::SysLibrary; corrected_attributes.applet_pos.Assign(corrected_pos); LOG_DEBUG(Service_APT, "Corrected applet attributes from {:08X} to {:08X}", attributes.raw, corrected_attributes.raw); } return GetLockHandleResult{corrected_attributes, 0, lock}; } ResultVal AppletManager::Initialize(AppletId app_id, AppletAttributes attributes) { auto slot = GetAppletSlotFromAttributes(attributes); // Note: The real NS service does not check if the attributes value is valid before accessing // the data in the array ASSERT_MSG(slot != AppletSlot::Error, "Invalid application attributes"); auto slot_data = GetAppletSlot(slot); if (slot_data->registered) { LOG_WARNING(Service_APT, "Applet attempted to register in occupied slot {:02X}", slot); return Result(ErrorDescription::AlreadyExists, ErrorModule::Applet, ErrorSummary::InvalidState, ErrorLevel::Status); } LOG_DEBUG(Service_APT, "Initializing applet with ID {:03X} and attributes {:08X}.", app_id, attributes.raw); slot_data->applet_id = static_cast(app_id); // Note: In the real console the title id of a given applet slot is set by the APT module when // calling StartApplication. slot_data->title_id = system.Kernel().GetCurrentProcess()->codeset->program_id; slot_data->attributes.raw = attributes.raw; // Applications need to receive a Wakeup signal to actually start up, this signal is usually // sent by the Home Menu after starting the app by way of APT::WakeupApplication. However, // if nothing is running yet the signal should be sent by APT::Initialize itself. if (active_slot == AppletSlot::Error) { active_slot = slot; // APT automatically calls enable on the first registered applet. Enable(attributes); // Wake up the applet. SendParameter({ .sender_id = AppletId::None, .destination_id = app_id, .signal = SignalType::Wakeup, }); } return InitializeResult{slot_data->notification_event, slot_data->parameter_event}; } Result AppletManager::Enable(AppletAttributes attributes) { auto slot = GetAppletSlotFromAttributes(attributes); if (slot == AppletSlot::Error) { LOG_WARNING(Service_APT, "Attempted to register with attributes {:08X}, but could not find slot.", attributes.raw); return {ErrCodes::InvalidAppletSlot, ErrorModule::Applet, ErrorSummary::InvalidState, ErrorLevel::Status}; } LOG_DEBUG(Service_APT, "Registering applet with attributes {:08X}.", attributes.raw); auto slot_data = GetAppletSlot(slot); slot_data->registered = true; if (slot_data->applet_id != AppletId::None && slot_data->attributes.applet_pos == AppletPos::System && slot_data->attributes.is_home_menu) { slot_data->attributes.raw |= attributes.raw; LOG_DEBUG(Service_APT, "Updated home menu attributes to {:08X}.", slot_data->attributes.raw); } // Send any outstanding parameters to the newly-registered application if (delayed_parameter && delayed_parameter->destination_id == slot_data->applet_id) { // TODO: Real APT would loop trying to send the parameter until it succeeds, // essentially waiting for existing parameters to be delivered. CancelAndSendParameter(*delayed_parameter); delayed_parameter.reset(); } return ResultSuccess; } Result AppletManager::Finalize(AppletId app_id) { auto slot = GetAppletSlotFromId(app_id); if (slot == AppletSlot::Error) { return {ErrorDescription::NotFound, ErrorModule::Applet, ErrorSummary::NotFound, ErrorLevel::Status}; } auto slot_data = GetAppletSlot(slot); slot_data->Reset(); auto inactive = active_slot == AppletSlot::Error; if (!inactive) { auto active_slot_data = GetAppletSlot(active_slot); inactive = active_slot_data->applet_id == AppletId::None || active_slot_data->attributes.applet_pos.Value() == AppletPos::Invalid; } if (inactive) { active_slot = GetAppletSlotFromPos(AppletPos::System); } return ResultSuccess; } u32 AppletManager::CountRegisteredApplet() { return static_cast(std::count_if(applet_slots.begin(), applet_slots.end(), [](auto& slot_data) { return slot_data.registered; })); } bool AppletManager::IsRegistered(AppletId app_id) { auto slot = GetAppletSlotFromId(app_id); return slot != AppletSlot::Error && GetAppletSlot(slot)->registered; } ResultVal AppletManager::GetAttribute(AppletId app_id) { auto slot = GetAppletSlotFromId(app_id); if (slot == AppletSlot::Error) { return Result(ErrorDescription::NotFound, ErrorModule::Applet, ErrorSummary::NotFound, ErrorLevel::Status); } auto slot_data = GetAppletSlot(slot); if (!slot_data->registered) { return Result(ErrorDescription::NotFound, ErrorModule::Applet, ErrorSummary::NotFound, ErrorLevel::Status); } return slot_data->attributes; } ResultVal AppletManager::InquireNotification(AppletId app_id) { auto slot = GetAppletSlotFromId(app_id); if (slot != AppletSlot::Error) { auto slot_data = GetAppletSlot(slot); if (slot_data->registered) { auto notification = slot_data->notification; slot_data->notification = Notification::None; return notification; } } return Result(ErrorDescription::NotFound, ErrorModule::Applet, ErrorSummary::NotFound, ErrorLevel::Status); } Result AppletManager::SendNotification(Notification notification) { if (active_slot != AppletSlot::Error) { const auto slot_data = GetAppletSlot(active_slot); if (slot_data->registered) { slot_data->notification = notification; slot_data->notification_event->Signal(); return ResultSuccess; } } return {ErrorDescription::NotFound, ErrorModule::Applet, ErrorSummary::NotFound, ErrorLevel::Status}; } void AppletManager::SendNotificationToAll(Notification notification) { for (auto& slot_data : applet_slots) { if (slot_data.registered) { slot_data.notification = notification; slot_data.notification_event->Signal(); } } } Result AppletManager::CreateHLEApplet(AppletId id, AppletId parent, bool preload) { switch (id) { case AppletId::SoftwareKeyboard1: case AppletId::SoftwareKeyboard2: hle_applets[id] = std::make_shared( system, id, parent, preload, shared_from_this()); break; case AppletId::Ed1: case AppletId::Ed2: hle_applets[id] = std::make_shared(system, id, parent, preload, shared_from_this()); break; case AppletId::Error: case AppletId::Error2: hle_applets[id] = std::make_shared(system, id, parent, preload, shared_from_this()); break; case AppletId::Mint: case AppletId::Mint2: hle_applets[id] = std::make_shared(system, id, parent, preload, shared_from_this()); break; default: LOG_ERROR(Service_APT, "Could not create applet {}", id); // TODO(Subv): Find the right error code return Result(ErrorDescription::NotFound, ErrorModule::Applet, ErrorSummary::NotSupported, ErrorLevel::Permanent); } AppletAttributes attributes; attributes.applet_pos.Assign(AppletPos::AutoLibrary); attributes.is_home_menu.Assign(false); const auto lock_handle_data = GetLockHandle(attributes); Initialize(id, lock_handle_data->corrected_attributes); Enable(lock_handle_data->corrected_attributes); if (preload) { FinishPreloadingLibraryApplet(id); } // Schedule the update event system.CoreTiming().ScheduleEvent(usToCycles(hle_applet_update_interval_us), hle_applet_update_event, static_cast(id)); return ResultSuccess; } Result AppletManager::PrepareToStartLibraryApplet(AppletId applet_id) { // The real APT service returns an error if there's a pending APT parameter when this function // is called. if (next_parameter) { return {ErrCodes::ParameterPresent, ErrorModule::Applet, ErrorSummary::InvalidState, ErrorLevel::Status}; } if (GetAppletSlot(AppletSlot::LibraryApplet)->registered) { return {ErrorDescription::AlreadyExists, ErrorModule::Applet, ErrorSummary::InvalidState, ErrorLevel::Status}; } last_library_launcher_slot = active_slot; last_prepared_library_applet = applet_id; capture_buffer_info.reset(); if (Settings::values.lle_applets) { auto cfg = Service::CFG::GetModule(system); auto process = NS::LaunchTitle(FS::MediaType::NAND, GetTitleIdForApplet(applet_id, cfg->GetRegionValue())); if (process) { return ResultSuccess; } } // If we weren't able to load the native applet title, try to fallback to an HLE implementation. if (hle_applets[applet_id] != nullptr) { LOG_WARNING(Service_APT, "Applet has already been started id={:03X}", applet_id); return ResultSuccess; } else { auto parent = GetAppletSlotId(last_library_launcher_slot); LOG_DEBUG(Service_APT, "Creating HLE applet {:03X} with parent {:03X}", applet_id, parent); return CreateHLEApplet(applet_id, parent, false); } } Result AppletManager::PreloadLibraryApplet(AppletId applet_id) { if (GetAppletSlot(AppletSlot::LibraryApplet)->registered) { return {ErrorDescription::AlreadyExists, ErrorModule::Applet, ErrorSummary::InvalidState, ErrorLevel::Status}; } last_library_launcher_slot = active_slot; last_prepared_library_applet = applet_id; if (Settings::values.lle_applets) { auto cfg = Service::CFG::GetModule(system); auto process = NS::LaunchTitle(FS::MediaType::NAND, GetTitleIdForApplet(applet_id, cfg->GetRegionValue())); if (process) { return ResultSuccess; } } // If we weren't able to load the native applet title, try to fallback to an HLE implementation. if (hle_applets[applet_id] != nullptr) { LOG_WARNING(Service_APT, "Applet has already been started id={:08X}", applet_id); return ResultSuccess; } else { auto parent = GetAppletSlotId(last_library_launcher_slot); LOG_DEBUG(Service_APT, "Creating HLE applet {:03X} with parent {:03X}", applet_id, parent); return CreateHLEApplet(applet_id, parent, true); } } Result AppletManager::FinishPreloadingLibraryApplet(AppletId applet_id) { // TODO(Subv): This function should fail depending on the applet preparation state. GetAppletSlot(AppletSlot::LibraryApplet)->loaded = true; return ResultSuccess; } Result AppletManager::StartLibraryApplet(AppletId applet_id, std::shared_ptr object, const std::vector& buffer) { active_slot = AppletSlot::LibraryApplet; auto send_res = SendParameter({ .sender_id = GetAppletSlotId(last_library_launcher_slot), .destination_id = applet_id, .signal = SignalType::Wakeup, .object = std::move(object), .buffer = buffer, }); if (send_res.IsError()) { active_slot = last_library_launcher_slot; return send_res; } return ResultSuccess; } Result AppletManager::PrepareToCloseLibraryApplet(bool not_pause, bool exiting, bool jump_home) { if (next_parameter) { return {ErrCodes::ParameterPresent, ErrorModule::Applet, ErrorSummary::InvalidState, ErrorLevel::Status}; } if (!not_pause) library_applet_closing_command = SignalType::WakeupByPause; else if (jump_home) library_applet_closing_command = SignalType::WakeupToJumpHome; else if (exiting) library_applet_closing_command = SignalType::WakeupByCancel; else library_applet_closing_command = SignalType::WakeupByExit; return ResultSuccess; } Result AppletManager::CloseLibraryApplet(std::shared_ptr object, const std::vector& buffer) { auto slot = GetAppletSlot(AppletSlot::LibraryApplet); auto destination_id = GetAppletSlotId(last_library_launcher_slot); active_slot = last_library_launcher_slot; MessageParameter param = { .sender_id = slot->applet_id, .destination_id = destination_id, .signal = library_applet_closing_command, .object = std::move(object), .buffer = buffer, }; if (library_applet_closing_command != SignalType::WakeupByPause) { CancelAndSendParameter(param); // TODO: Terminate the running applet title slot->Reset(); } else { SendParameter(param); } return ResultSuccess; } Result AppletManager::CancelLibraryApplet(bool app_exiting) { if (next_parameter) { return {ErrCodes::ParameterPresent, ErrorModule::Applet, ErrorSummary::InvalidState, ErrorLevel::Status}; } auto slot = GetAppletSlot(AppletSlot::LibraryApplet); if (!slot->registered) { return {ErrCodes::InvalidAppletSlot, ErrorModule::Applet, ErrorSummary::InvalidState, ErrorLevel::Status}; } return SendParameter({ .sender_id = GetAppletSlotId(last_library_launcher_slot), .destination_id = slot->applet_id, .signal = SignalType::WakeupByCancel, }); } Result AppletManager::SendDspSleep(AppletId from_applet_id, std::shared_ptr object) { auto lib_slot = GetAppletSlotFromPos(AppletPos::Library); auto lib_app_id = lib_slot != AppletSlot::Error ? GetAppletSlot(lib_slot)->applet_id : AppletId::None; if (from_applet_id == lib_app_id) { SendParameter({ .sender_id = from_applet_id, .destination_id = AppletId::Application, .signal = SignalType::DspSleep, .object = std::move(object), }); return ResultSuccess; } auto sys_lib_slot = GetAppletSlotFromPos(AppletPos::SysLibrary); auto sys_lib_app_id = sys_lib_slot != AppletSlot::Error ? GetAppletSlot(sys_lib_slot)->applet_id : AppletId::None; if (from_applet_id == sys_lib_app_id) { auto sys_slot = GetAppletSlotFromPos(AppletPos::System); auto sys_app_id = sys_slot != AppletSlot::Error ? GetAppletSlot(sys_slot)->applet_id : AppletId::None; SendParameter({ .sender_id = from_applet_id, .destination_id = sys_app_id, .signal = SignalType::DspSleep, .object = std::move(object), }); return ResultSuccess; } return ResultSuccess; } Result AppletManager::SendDspWakeUp(AppletId from_applet_id, std::shared_ptr object) { auto lib_slot = GetAppletSlotFromPos(AppletPos::Library); auto lib_app_id = lib_slot != AppletSlot::Error ? GetAppletSlot(lib_slot)->applet_id : AppletId::None; if (from_applet_id == lib_app_id) { SendParameter({ .sender_id = from_applet_id, .destination_id = AppletId::Application, .signal = SignalType::DspSleep, .object = std::move(object), }); } else { auto sys_lib_slot = GetAppletSlotFromPos(AppletPos::SysLibrary); auto sys_lib_app_id = sys_lib_slot != AppletSlot::Error ? GetAppletSlot(sys_lib_slot)->applet_id : AppletId::None; if (from_applet_id == sys_lib_app_id) { auto sys_slot = GetAppletSlotFromPos(AppletPos::System); auto sys_app_id = sys_slot != AppletSlot::Error ? GetAppletSlot(sys_slot)->applet_id : AppletId::None; SendParameter({ .sender_id = from_applet_id, .destination_id = sys_app_id, .signal = SignalType::DspSleep, .object = std::move(object), }); } } return ResultSuccess; } Result AppletManager::PrepareToStartSystemApplet(AppletId applet_id) { // The real APT service returns an error if there's a pending APT parameter when this function // is called. if (next_parameter) { return {ErrCodes::ParameterPresent, ErrorModule::Applet, ErrorSummary::InvalidState, ErrorLevel::Status}; } last_system_launcher_slot = active_slot; return ResultSuccess; } Result AppletManager::StartSystemApplet(AppletId applet_id, std::shared_ptr object, const std::vector& buffer) { auto source_applet_id = AppletId::Application; if (last_system_launcher_slot != AppletSlot::Error) { const auto launcher_slot_data = GetAppletSlot(last_system_launcher_slot); source_applet_id = launcher_slot_data->applet_id; // APT generally clears and terminates the caller of StartSystemApplet. This helps in // situations such as a system applet launching another system applet, which would // otherwise deadlock. // TODO: In real APT, the check for AppletSlot::Application does not exist; there is // TODO: something wrong with our implementation somewhere that makes this necessary. // TODO: Otherwise, games that attempt to launch system applets will be cleared and // TODO: emulation will crash. if (!launcher_slot_data->registered || (last_system_launcher_slot != AppletSlot::Application && !launcher_slot_data->attributes.no_exit_on_system_applet)) { launcher_slot_data->Reset(); // TODO: Implement launcher process termination. } } // If a system applet is not already registered, it is started by APT. const auto slot_id = applet_id == AppletId::HomeMenu ? AppletSlot::HomeMenu : AppletSlot::SystemApplet; if (!GetAppletSlot(slot_id)->registered) { auto cfg = Service::CFG::GetModule(system); auto process = NS::LaunchTitle(FS::MediaType::NAND, GetTitleIdForApplet(applet_id, cfg->GetRegionValue())); if (!process) { // TODO: Find the right error code. return {ErrorDescription::NotFound, ErrorModule::Applet, ErrorSummary::NotSupported, ErrorLevel::Permanent}; } } active_slot = slot_id; SendApplicationParameterAfterRegistration({ .sender_id = source_applet_id, .destination_id = applet_id, .signal = SignalType::Wakeup, .object = std::move(object), .buffer = buffer, }); return ResultSuccess; } Result AppletManager::PrepareToCloseSystemApplet() { if (next_parameter) { return {ErrCodes::ParameterPresent, ErrorModule::Applet, ErrorSummary::InvalidState, ErrorLevel::Status}; } return ResultSuccess; } Result AppletManager::CloseSystemApplet(std::shared_ptr object, const std::vector& buffer) { ASSERT_MSG(active_slot == AppletSlot::HomeMenu || active_slot == AppletSlot::SystemApplet, "Attempting to close a system applet from a non-system applet."); auto slot = GetAppletSlot(active_slot); auto closed_applet_id = slot->applet_id; active_slot = last_system_launcher_slot; slot->Reset(); if (ordered_to_close_sys_applet) { ordered_to_close_sys_applet = false; active_slot = AppletSlot::Application; CancelAndSendParameter({ .sender_id = closed_applet_id, .destination_id = AppletId::Application, .signal = SignalType::WakeupByExit, .object = std::move(object), .buffer = buffer, }); } // TODO: Terminate the running applet title return ResultSuccess; } Result AppletManager::OrderToCloseSystemApplet() { if (active_slot == AppletSlot::Error) { return {ErrCodes::InvalidAppletSlot, ErrorModule::Applet, ErrorSummary::InvalidState, ErrorLevel::Status}; } auto active_slot_data = GetAppletSlot(active_slot); if (active_slot_data->applet_id == AppletId::None || active_slot_data->attributes.applet_pos != AppletPos::Application) { return {ErrCodes::InvalidAppletSlot, ErrorModule::Applet, ErrorSummary::InvalidState, ErrorLevel::Status}; } auto system_slot = GetAppletSlotFromPos(AppletPos::System); if (system_slot == AppletSlot::Error) { return {ErrorDescription::NotFound, ErrorModule::Applet, ErrorSummary::NotFound, ErrorLevel::Status}; } auto system_slot_data = GetAppletSlot(system_slot); if (!system_slot_data->registered) { return {ErrorDescription::NotFound, ErrorModule::Applet, ErrorSummary::NotFound, ErrorLevel::Status}; } ordered_to_close_sys_applet = true; active_slot = system_slot; SendParameter({ .sender_id = AppletId::Application, .destination_id = system_slot_data->applet_id, .signal = SignalType::WakeupByCancel, }); return ResultSuccess; } Result AppletManager::PrepareToJumpToHomeMenu() { if (next_parameter) { return {ErrCodes::ParameterPresent, ErrorModule::Applet, ErrorSummary::InvalidState, ErrorLevel::Status}; } last_jump_to_home_slot = active_slot; capture_buffer_info.reset(); if (last_jump_to_home_slot == AppletSlot::Application) { EnsureHomeMenuLoaded(); } return ResultSuccess; } Result AppletManager::JumpToHomeMenu(std::shared_ptr object, const std::vector& buffer) { if (last_jump_to_home_slot != AppletSlot::Error) { auto slot_data = GetAppletSlot(last_jump_to_home_slot); if (slot_data->applet_id != AppletId::None) { MessageParameter param; param.object = std::move(object); param.buffer = buffer; switch (slot_data->attributes.applet_pos) { case AppletPos::Application: active_slot = AppletSlot::HomeMenu; param.destination_id = AppletId::HomeMenu; param.sender_id = AppletId::Application; param.signal = SignalType::WakeupByPause; SendParameter(param); break; case AppletPos::Library: param.destination_id = slot_data->applet_id; param.sender_id = slot_data->applet_id; param.signal = SignalType::WakeupByCancel; SendParameter(param); break; case AppletPos::System: if (slot_data->attributes.is_home_menu) { param.destination_id = slot_data->applet_id; param.sender_id = slot_data->applet_id; param.signal = SignalType::WakeupToJumpHome; SendParameter(param); } break; case AppletPos::SysLibrary: { const auto system_slot_data = GetAppletSlot(AppletSlot::SystemApplet); param.destination_id = slot_data->applet_id; param.sender_id = slot_data->applet_id; param.signal = system_slot_data->registered ? SignalType::WakeupByCancel : SignalType::WakeupToJumpHome; SendParameter(param); break; } default: break; } } } return ResultSuccess; } Result AppletManager::PrepareToLeaveHomeMenu() { if (!GetAppletSlot(AppletSlot::Application)->registered) { return {ErrCodes::InvalidAppletSlot, ErrorModule::Applet, ErrorSummary::InvalidState, ErrorLevel::Status}; } if (next_parameter) { return {ErrCodes::ParameterPresent, ErrorModule::Applet, ErrorSummary::InvalidState, ErrorLevel::Status}; } return ResultSuccess; } Result AppletManager::LeaveHomeMenu(std::shared_ptr object, const std::vector& buffer) { active_slot = AppletSlot::Application; SendParameter({ .sender_id = AppletId::HomeMenu, .destination_id = AppletId::Application, .signal = SignalType::WakeupByPause, .object = std::move(object), .buffer = buffer, }); return ResultSuccess; } Result AppletManager::OrderToCloseApplication() { if (active_slot == AppletSlot::Error) { return {ErrCodes::InvalidAppletSlot, ErrorModule::Applet, ErrorSummary::InvalidState, ErrorLevel::Status}; } auto active_slot_data = GetAppletSlot(active_slot); if (active_slot_data->applet_id == AppletId::None || active_slot_data->attributes.applet_pos != AppletPos::System) { return {ErrCodes::InvalidAppletSlot, ErrorModule::Applet, ErrorSummary::InvalidState, ErrorLevel::Status}; } ordered_to_close_application = true; active_slot = AppletSlot::Application; SendParameter({ .sender_id = AppletId::HomeMenu, .destination_id = AppletId::Application, .signal = SignalType::WakeupByCancel, }); return ResultSuccess; } Result AppletManager::PrepareToCloseApplication(bool return_to_sys) { if (active_slot == AppletSlot::Error) { return {ErrCodes::InvalidAppletSlot, ErrorModule::Applet, ErrorSummary::InvalidState, ErrorLevel::Status}; } auto active_slot_data = GetAppletSlot(active_slot); if (active_slot_data->applet_id == AppletId::None || active_slot_data->attributes.applet_pos != AppletPos::Application) { return {ErrCodes::InvalidAppletSlot, ErrorModule::Applet, ErrorSummary::InvalidState, ErrorLevel::Status}; } auto system_slot_data = GetAppletSlot(AppletSlot::SystemApplet); auto home_menu_slot_data = GetAppletSlot(AppletSlot::HomeMenu); if (!application_cancelled && return_to_sys) { // TODO: Left side of the OR also includes "&& !power_button_clicked", but this isn't // implemented yet. if (!ordered_to_close_application || !system_slot_data->registered) { application_close_target = AppletSlot::HomeMenu; } else { application_close_target = AppletSlot::SystemApplet; } } else { application_close_target = AppletSlot::Error; } if (application_close_target != AppletSlot::HomeMenu && !system_slot_data->registered && !home_menu_slot_data->registered) { return {ErrCodes::InvalidAppletSlot, ErrorModule::Applet, ErrorSummary::InvalidState, ErrorLevel::Status}; } if (next_parameter) { return {ErrCodes::ParameterPresent, ErrorModule::Applet, ErrorSummary::InvalidState, ErrorLevel::Status}; } if (application_close_target == AppletSlot::HomeMenu) { // Real APT would make sure home menu is loaded here. However, this is only really // needed if the home menu wasn't loaded in the first place. Since we want to // preserve normal behavior when the user loaded the game directly without going // through home menu, we skip this. Then, later we just close to the game list // when the application finishes closing. // EnsureHomeMenuLoaded(); } return ResultSuccess; } Result AppletManager::CloseApplication(std::shared_ptr object, const std::vector& buffer) { ordered_to_close_application = false; application_cancelled = false; GetAppletSlot(AppletSlot::Application)->Reset(); if (application_close_target != AppletSlot::Error) { // If exiting to the home menu and it is not loaded, exit to game list. if (application_close_target == AppletSlot::HomeMenu && !GetAppletSlot(application_close_target)->registered) { system.RequestShutdown(); } else { active_slot = application_close_target; CancelAndSendParameter({ .sender_id = AppletId::Application, .destination_id = GetAppletSlot(application_close_target)->applet_id, .signal = SignalType::WakeupByExit, .object = std::move(object), .buffer = buffer, }); } } // TODO: Terminate the application process. return ResultSuccess; } ResultVal AppletManager::GetAppletManInfo( AppletPos requested_applet_pos) { auto active_applet_pos = AppletPos::Invalid; auto active_applet_id = AppletId::None; if (active_slot != AppletSlot::Error) { auto active_slot_data = GetAppletSlot(active_slot); if (active_slot_data->applet_id != AppletId::None) { active_applet_pos = active_slot_data->attributes.applet_pos; active_applet_id = active_slot_data->applet_id; } } auto requested_applet_id = AppletId::None; auto requested_slot = GetAppletSlotFromPos(requested_applet_pos); if (requested_slot != AppletSlot::Error) { auto requested_slot_data = GetAppletSlot(requested_slot); if (requested_slot_data->registered) { requested_applet_id = requested_slot_data->applet_id; } } return AppletManInfo{ .active_applet_pos = active_applet_pos, .requested_applet_id = requested_applet_id, .home_menu_applet_id = AppletId::HomeMenu, .active_applet_id = active_applet_id, }; } ResultVal AppletManager::GetAppletInfo(AppletId app_id) { auto slot = GetAppletSlotFromId(app_id); if (slot == AppletSlot::Error) { return Result(ErrorDescription::NotFound, ErrorModule::Applet, ErrorSummary::NotFound, ErrorLevel::Status); } auto slot_data = GetAppletSlot(slot); if (!slot_data->registered) { return Result(ErrorDescription::NotFound, ErrorModule::Applet, ErrorSummary::NotFound, ErrorLevel::Status); } auto media_type = Service::AM::GetTitleMediaType(slot_data->title_id); return AppletInfo{ .title_id = slot_data->title_id, .media_type = media_type, .registered = slot_data->registered, .loaded = slot_data->loaded, .attributes = slot_data->attributes.raw, }; } ResultVal AppletManager::Unknown54(u32 in_param) { auto slot_data = GetAppletSlot(AppletSlot::Application); if (slot_data->applet_id == AppletId::None) { return Result{ErrCodes::AppNotRunning, ErrorModule::Applet, ErrorSummary::InvalidState, ErrorLevel::Permanent}; } if (in_param >= 0x80) { // TODO: Add error description name when the parameter is known. return Result{10, ErrorModule::Applet, ErrorSummary::InvalidArgument, ErrorLevel::Usage}; } // TODO: Figure out what this logic is actually for. auto check_target = in_param >= 0x40 ? Service::FS::MediaType::GameCard : Service::FS::MediaType::SDMC; auto check_update = in_param == 0x01 || in_param == 0x42; auto app_media_type = Service::AM::GetTitleMediaType(slot_data->title_id); auto app_update_media_type = Service::AM::GetTitleMediaType(Service::AM::GetTitleUpdateId(slot_data->title_id)); if (app_media_type == check_target || (check_update && app_update_media_type == check_target)) { return Service::FS::MediaType::SDMC; } else { return Service::FS::MediaType::NAND; } } TargetPlatform AppletManager::GetTargetPlatform() { if (Settings::values.is_new_3ds.GetValue() && !new_3ds_mode_blocked) { return TargetPlatform::New3ds; } else { return TargetPlatform::Old3ds; } } ApplicationRunningMode AppletManager::GetApplicationRunningMode() { auto slot_data = GetAppletSlot(AppletSlot::Application); if (slot_data->applet_id == AppletId::None) { return ApplicationRunningMode::NoApplication; } // APT checks whether the system is a New 3DS and the 804MHz CPU speed is enabled to determine // the result. auto new_3ds_mode = GetTargetPlatform() == TargetPlatform::New3ds && system.Kernel().GetNew3dsHwCapabilities().enable_804MHz_cpu; if (slot_data->registered) { return new_3ds_mode ? ApplicationRunningMode::New3dsRegistered : ApplicationRunningMode::Old3dsRegistered; } else { return new_3ds_mode ? ApplicationRunningMode::New3dsUnregistered : ApplicationRunningMode::Old3dsUnregistered; } } Result AppletManager::PrepareToDoApplicationJump(u64 title_id, FS::MediaType media_type, ApplicationJumpFlags flags) { // A running application can not launch another application directly because the applet state // for the Application slot is already in use. The way this is implemented in hardware is to // launch the Home Menu and tell it to launch our desired application. ASSERT_MSG(flags != ApplicationJumpFlags::UseStoredParameters, "Unimplemented application jump flags 1"); // Save the title data to send it to the Home Menu when DoApplicationJump is called. auto application_slot_data = GetAppletSlot(AppletSlot::Application); app_jump_parameters.current_title_id = application_slot_data->title_id; app_jump_parameters.current_media_type = Service::AM::GetTitleMediaType(application_slot_data->title_id); if (flags == ApplicationJumpFlags::UseCurrentParameters) { app_jump_parameters.next_title_id = app_jump_parameters.current_title_id; app_jump_parameters.next_media_type = app_jump_parameters.current_media_type; } else { app_jump_parameters.next_title_id = title_id; app_jump_parameters.next_media_type = media_type; } app_jump_parameters.flags = flags; // Note: The real console uses the Home Menu to perform the application jump, therefore the menu // needs to be running. The real APT module starts the Home Menu here if it's not already // running, we don't have to do this. See `EnsureHomeMenuLoaded` for launching the Home Menu. return ResultSuccess; } Result AppletManager::DoApplicationJump(const DeliverArg& arg) { // Note: The real console uses the Home Menu to perform the application jump, it goes // OldApplication->Home Menu->NewApplication. We do not need to use the Home Menu to do this so // we launch the new application directly. In the real APT service, the Home Menu must be // running to do this, otherwise error 0xC8A0CFF0 is returned. auto application_slot_data = GetAppletSlot(AppletSlot::Application); auto title_id = application_slot_data->title_id; application_slot_data->Reset(); // Set the delivery parameters. deliver_arg = arg; if (app_jump_parameters.flags != ApplicationJumpFlags::UseCurrentParameters) { // The source program ID is not updated when using flags 0x2. deliver_arg->source_program_id = title_id; } if (GetAppletSlot(AppletSlot::HomeMenu)->registered) { // If the home menu is running, use it to jump to the next application. // The home menu will call GetProgramIdOnApplicationJump and // PrepareToStartApplication/StartApplication to launch the title. active_slot = AppletSlot::HomeMenu; SendParameter({ .sender_id = AppletId::Application, .destination_id = AppletId::HomeMenu, .signal = SignalType::WakeupToLaunchApplication, }); // TODO: APT terminates the application here, usually it will exit itself properly though. return ResultSuccess; } else { // Otherwise, work around the missing home menu by launching the title directly. // TODO: The emulator does not support terminating the old process immediately. // We could call TerminateProcess but references to the process are still held elsewhere, // preventing clean up. This code is left commented for when this is implemented, for now we // cannot use NS as the old process resources would interfere with the new ones. /* auto process = NS::LaunchTitle(app_jump_parameters.next_media_type, app_jump_parameters.next_title_id); if (!process) { LOG_CRITICAL(Service_APT, "Failed to launch title during application jump, exiting."); system.RequestShutdown(); } return ResultSuccess; */ NS::RebootToTitle(system, app_jump_parameters.next_media_type, app_jump_parameters.next_title_id); return ResultSuccess; } } Result AppletManager::PrepareToStartApplication(u64 title_id, FS::MediaType media_type) { if (active_slot == AppletSlot::Error || GetAppletSlot(active_slot)->attributes.applet_pos != AppletPos::System) { return {ErrCodes::InvalidAppletSlot, ErrorModule::Applet, ErrorSummary::InvalidState, ErrorLevel::Status}; } // TODO(Subv): This should return 0xc8a0cff0 if the applet preparation state is already set if (GetAppletSlot(AppletSlot::Application)->registered) { return {ErrorDescription::AlreadyExists, ErrorModule::Applet, ErrorSummary::InvalidState, ErrorLevel::Status}; } ASSERT_MSG(!app_start_parameters, "Trying to prepare an application when another is already prepared"); app_start_parameters.emplace(); app_start_parameters->next_title_id = title_id; app_start_parameters->next_media_type = media_type; capture_buffer_info.reset(); return ResultSuccess; } Result AppletManager::StartApplication(const std::vector& parameter, const std::vector& hmac, bool paused) { // The delivery argument is always unconditionally set. deliver_arg.emplace(DeliverArg{parameter, hmac}); // Note: APT first checks if we can launch the application via AM::CheckDemoLaunchRights and // returns 0xc8a12403 if we can't. We intentionally do not implement that check. // TODO(Subv): The APT service performs several checks here related to the exheader flags of the // process we're launching and other things like title id blacklists. We do not yet implement // any of that. // TODO(Subv): The real APT service doesn't seem to check whether the titleid to launch is set // or not, it either launches NATIVE_FIRM if some internal state is set, or fails when calling // PM::LaunchTitle. We should research more about that. ASSERT_MSG(app_start_parameters, "Trying to start an application without preparing it first."); active_slot = AppletSlot::Application; // Launch the title directly. auto process = NS::LaunchTitle(app_start_parameters->next_media_type, app_start_parameters->next_title_id); if (!process) { LOG_CRITICAL(Service_APT, "Failed to launch title during application start, exiting."); system.RequestShutdown(); } app_start_parameters.reset(); if (!paused) { return WakeupApplication(nullptr, {}); } return ResultSuccess; } Result AppletManager::WakeupApplication(std::shared_ptr object, const std::vector& buffer) { // Send a Wakeup signal via the apt parameter to the application once it registers itself. // The real APT service does this by spin waiting on another thread until the application is // registered. SendApplicationParameterAfterRegistration({ .sender_id = AppletId::HomeMenu, .destination_id = AppletId::Application, .signal = SignalType::Wakeup, .object = std::move(object), .buffer = buffer, }); return ResultSuccess; } Result AppletManager::CancelApplication() { auto application_slot_data = GetAppletSlot(AppletSlot::Application); if (application_slot_data->applet_id == AppletId::None) { return {ErrCodes::InvalidAppletSlot, ErrorModule::Applet, ErrorSummary::InvalidState, ErrorLevel::Status}; } application_cancelled = true; SendApplicationParameterAfterRegistration({ .sender_id = active_slot != AppletSlot::Error ? GetAppletSlot(active_slot)->applet_id : AppletId::None, .destination_id = AppletId::Application, .signal = SignalType::WakeupByCancel, }); return ResultSuccess; } void AppletManager::SendApplicationParameterAfterRegistration(const MessageParameter& parameter) { auto slot = GetAppletSlotFromId(parameter.destination_id); // If the application is already registered, immediately send the parameter if (slot != AppletSlot::Error && GetAppletSlot(slot)->registered) { CancelAndSendParameter(parameter); return; } // Otherwise queue it until the Application calls APT::Enable delayed_parameter = parameter; } void AppletManager::EnsureHomeMenuLoaded() { // TODO(Subv): The real APT service sends signal 12 (WakeupByCancel) to the currently running // System applet, waits for it to finish, and then launches the Home Menu. ASSERT_MSG(!GetAppletSlot(AppletSlot::SystemApplet)->registered, "A system applet is already running"); if (GetAppletSlot(AppletSlot::HomeMenu)->registered) { // The Home Menu is already running. return; } auto cfg = Service::CFG::GetModule(system); auto menu_title_id = GetTitleIdForApplet(AppletId::HomeMenu, cfg->GetRegionValue()); auto process = NS::LaunchTitle(FS::MediaType::NAND, menu_title_id); if (!process) { LOG_WARNING(Service_APT, "The Home Menu failed to launch, application jumping will not work."); } } static u32 GetDisplayBufferModePixelSize(DisplayBufferMode mode) { switch (mode) { // NOTE: APT does in fact use pixel size 3 for R8G8B8A8 captures. case DisplayBufferMode::R8G8B8A8: case DisplayBufferMode::R8G8B8: return 3; case DisplayBufferMode::R5G6B5: case DisplayBufferMode::R5G5B5A1: case DisplayBufferMode::R4G4B4A4: return 2; case DisplayBufferMode::Unimportable: return 0; default: UNREACHABLE_MSG("Unknown display buffer mode {}", mode); return 0; } } static void CaptureFrameBuffer(Core::System& system, u32 capture_offset, VAddr src, u32 height, DisplayBufferMode mode) { const auto bpp = GetDisplayBufferModePixelSize(mode); if (bpp == 0) { return; } system.Memory().RasterizerFlushVirtualRegion(src, GSP::FRAMEBUFFER_WIDTH * height * bpp, Memory::FlushMode::Flush); // Address in VRAM that APT copies framebuffer captures to. constexpr VAddr screen_capture_base_vaddr = Memory::VRAM_VADDR + 0x500000; const auto dst_vaddr = screen_capture_base_vaddr + capture_offset; auto dst_ptr = system.Memory().GetPointer(dst_vaddr); if (!dst_ptr) { LOG_ERROR(Service_APT, "Could not retrieve framebuffer capture destination buffer, skipping screen."); return; } const auto src_ptr = system.Memory().GetPointer(src); if (!src_ptr) { LOG_ERROR(Service_APT, "Could not retrieve framebuffer capture source buffer, skipping screen."); return; } for (u32 y = 0; y < height; y++) { for (u32 x = 0; x < GSP::FRAMEBUFFER_WIDTH; x++) { const auto dst_offset = VideoCore::GetMortonOffset(x, y, bpp) + (y & ~7) * GSP::FRAMEBUFFER_WIDTH_POW2 * bpp; const auto src_offset = bpp * (GSP::FRAMEBUFFER_WIDTH * y + x); std::memcpy(dst_ptr + dst_offset, src_ptr + src_offset, bpp); } } system.Memory().RasterizerFlushVirtualRegion( dst_vaddr, GSP::FRAMEBUFFER_WIDTH_POW2 * height * bpp, Memory::FlushMode::Invalidate); } void AppletManager::CaptureFrameBuffers() { CaptureFrameBuffer(system, capture_info->bottom_screen_left_offset, GSP::FRAMEBUFFER_SAVE_AREA_BOTTOM, GSP::BOTTOM_FRAMEBUFFER_HEIGHT, capture_info->bottom_screen_format); CaptureFrameBuffer(system, capture_info->top_screen_left_offset, GSP::FRAMEBUFFER_SAVE_AREA_TOP_LEFT, GSP::TOP_FRAMEBUFFER_HEIGHT, capture_info->top_screen_format); if (capture_info->is_3d) { CaptureFrameBuffer(system, capture_info->top_screen_right_offset, GSP::FRAMEBUFFER_SAVE_AREA_TOP_RIGHT, GSP::TOP_FRAMEBUFFER_HEIGHT, capture_info->top_screen_format); } } void AppletManager::LoadInputDevices() { home_button = Input::CreateDevice( Settings::values.current_input_profile.buttons[Settings::NativeButton::Home]); power_button = Input::CreateDevice( Settings::values.current_input_profile.buttons[Settings::NativeButton::Power]); } /// Handles updating the current Applet every time it's called. void AppletManager::HLEAppletUpdateEvent(std::uintptr_t user_data, s64 cycles_late) { const auto id = static_cast(user_data); const auto applet = hle_applets[id]; if (applet == nullptr) { // Dead applet, exit event loop. LOG_WARNING(Service_APT, "Attempted to update missing applet id={:03X}", id); return; } if (applet->IsActive()) { applet->Update(); } // If the applet is still running after the last update, reschedule the event if (applet->IsRunning()) { system.CoreTiming().ScheduleEvent(usToCycles(hle_applet_update_interval_us) - cycles_late, hle_applet_update_event, user_data); } else { // Otherwise the applet has terminated, in which case we should clean it up hle_applets[id] = nullptr; } } void AppletManager::ButtonUpdateEvent(std::uintptr_t user_data, s64 cycles_late) { if (is_device_reload_pending.exchange(false)) { LoadInputDevices(); } // NOTE: We technically do support loading and jumping to home menu even if it isn't // initially registered. However since the home menu suspend is not bug-free, we don't // want normal users who didn't launch the home menu accidentally pressing the home // button binding and freezing their game, so for now, gate it to only environments // where the home menu was already loaded by the user (last condition). if (GetAppletSlot(AppletSlot::HomeMenu)->registered) { const bool home_state = home_button->GetStatus(); if (home_state && !last_home_button_state) { SendNotification(Notification::HomeButtonSingle); } last_home_button_state = home_state; const bool power_state = power_button->GetStatus(); if (power_state && !last_power_button_state) { SendNotificationToAll(Notification::PowerButtonClick); } last_power_button_state = power_state; } // Reschedule recurrent event system.CoreTiming().ScheduleEvent(usToCycles(button_update_interval_us) - cycles_late, button_update_event); } AppletManager::AppletManager(Core::System& system) : system(system) { lock = system.Kernel().CreateMutex(false, "APT_U:Lock"); for (std::size_t slot = 0; slot < applet_slots.size(); ++slot) { auto& slot_data = applet_slots[slot]; slot_data.slot = static_cast(slot); slot_data.applet_id = AppletId::None; slot_data.attributes.raw = 0; slot_data.registered = false; slot_data.loaded = false; slot_data.notification_event = system.Kernel().CreateEvent(Kernel::ResetType::OneShot, "APT:Notification"); slot_data.parameter_event = system.Kernel().CreateEvent(Kernel::ResetType::OneShot, "APT:Parameter"); } hle_applet_update_event = system.CoreTiming().RegisterEvent( "HLE Applet Update Event", [this](std::uintptr_t user_data, s64 cycles_late) { HLEAppletUpdateEvent(user_data, cycles_late); }); button_update_event = system.CoreTiming().RegisterEvent( "APT Button Update Event", [this](std::uintptr_t user_data, s64 cycles_late) { ButtonUpdateEvent(user_data, cycles_late); }); system.CoreTiming().ScheduleEvent(usToCycles(button_update_interval_us), button_update_event); } AppletManager::~AppletManager() { system.CoreTiming().RemoveEvent(hle_applet_update_event); system.CoreTiming().RemoveEvent(button_update_event); } void AppletManager::ReloadInputDevices() { is_device_reload_pending.store(true); } } // namespace Service::APT