// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/settings.h" #include "common/uuid.h" #include "core/core.h" #include "core/core_timing.h" #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/am/applet_data_broker.h" #include "core/hle/service/am/applet_manager.h" #include "core/hle/service/am/frontend/applet_cabinet.h" #include "core/hle/service/am/frontend/applet_controller.h" #include "core/hle/service/am/frontend/applet_mii_edit_types.h" #include "core/hle/service/am/frontend/applet_software_keyboard_types.h" #include "core/hle/service/am/service/storage.h" #include "hid_core/hid_types.h" namespace Service::AM { namespace { constexpr u32 LaunchParameterAccountPreselectedUserMagic = 0xC79497CA; struct LaunchParameterAccountPreselectedUser { u32 magic; u32 is_account_selected; Common::UUID current_user; INSERT_PADDING_BYTES(0x70); }; static_assert(sizeof(LaunchParameterAccountPreselectedUser) == 0x88); AppletStorageChannel& InitializeFakeCallerApplet(Core::System& system, std::shared_ptr& applet) { applet->caller_applet_broker = std::make_shared(system); return applet->caller_applet_broker->GetInData(); } void PushInShowAlbum(Core::System& system, AppletStorageChannel& channel) { const CommonArguments arguments{ .arguments_version = CommonArgumentVersion::Version3, .size = CommonArgumentSize::Version3, .library_version = 1, .theme_color = ThemeColor::BasicBlack, .play_startup_sound = true, .system_tick = system.CoreTiming().GetClockTicks(), }; std::vector argument_data(sizeof(arguments)); std::vector settings_data{2}; std::memcpy(argument_data.data(), &arguments, sizeof(arguments)); channel.Push(std::make_shared(system, std::move(argument_data))); channel.Push(std::make_shared(system, std::move(settings_data))); } void PushInShowController(Core::System& system, AppletStorageChannel& channel) { const CommonArguments common_args = { .arguments_version = CommonArgumentVersion::Version3, .size = CommonArgumentSize::Version3, .library_version = static_cast(Frontend::ControllerAppletVersion::Version8), .theme_color = ThemeColor::BasicBlack, .play_startup_sound = true, .system_tick = system.CoreTiming().GetClockTicks(), }; Frontend::ControllerSupportArgNew user_args = { .header = {.player_count_min = 1, .player_count_max = 4, .enable_take_over_connection = true, .enable_left_justify = false, .enable_permit_joy_dual = true, .enable_single_mode = false, .enable_identification_color = false}, .identification_colors = {}, .enable_explain_text = false, .explain_text = {}, }; Frontend::ControllerSupportArgPrivate private_args = { .arg_private_size = sizeof(Frontend::ControllerSupportArgPrivate), .arg_size = sizeof(Frontend::ControllerSupportArgNew), .is_home_menu = true, .flag_1 = true, .mode = Frontend::ControllerSupportMode::ShowControllerSupport, .caller = Frontend::ControllerSupportCaller:: Application, // switchbrew: Always zero except with // ShowControllerFirmwareUpdateForSystem/ShowControllerKeyRemappingForSystem, // which sets this to the input param .style_set = Core::HID::NpadStyleSet::None, .joy_hold_type = 0, }; std::vector common_args_data(sizeof(common_args)); std::vector private_args_data(sizeof(private_args)); std::vector user_args_data(sizeof(user_args)); std::memcpy(common_args_data.data(), &common_args, sizeof(common_args)); std::memcpy(private_args_data.data(), &private_args, sizeof(private_args)); std::memcpy(user_args_data.data(), &user_args, sizeof(user_args)); channel.Push(std::make_shared(system, std::move(common_args_data))); channel.Push(std::make_shared(system, std::move(private_args_data))); channel.Push(std::make_shared(system, std::move(user_args_data))); } void PushInShowCabinetData(Core::System& system, AppletStorageChannel& channel) { const CommonArguments arguments{ .arguments_version = CommonArgumentVersion::Version3, .size = CommonArgumentSize::Version3, .library_version = static_cast(Frontend::CabinetAppletVersion::Version1), .theme_color = ThemeColor::BasicBlack, .play_startup_sound = true, .system_tick = system.CoreTiming().GetClockTicks(), }; const Frontend::StartParamForAmiiboSettings amiibo_settings{ .param_1 = 0, .applet_mode = system.GetFrontendAppletHolder().GetCabinetMode(), .flags = Frontend::CabinetFlags::None, .amiibo_settings_1 = 0, .device_handle = 0, .tag_info{}, .register_info{}, .amiibo_settings_3{}, }; std::vector argument_data(sizeof(arguments)); std::vector settings_data(sizeof(amiibo_settings)); std::memcpy(argument_data.data(), &arguments, sizeof(arguments)); std::memcpy(settings_data.data(), &amiibo_settings, sizeof(amiibo_settings)); channel.Push(std::make_shared(system, std::move(argument_data))); channel.Push(std::make_shared(system, std::move(settings_data))); } void PushInShowMiiEditData(Core::System& system, AppletStorageChannel& channel) { struct MiiEditV3 { Frontend::MiiEditAppletInputCommon common; Frontend::MiiEditAppletInputV3 input; }; static_assert(sizeof(MiiEditV3) == 0x100, "MiiEditV3 has incorrect size."); MiiEditV3 mii_arguments{ .common = { .version = Frontend::MiiEditAppletVersion::Version3, .applet_mode = Frontend::MiiEditAppletMode::ShowMiiEdit, }, .input{}, }; std::vector argument_data(sizeof(mii_arguments)); std::memcpy(argument_data.data(), &mii_arguments, sizeof(mii_arguments)); channel.Push(std::make_shared(system, std::move(argument_data))); } void PushInShowSoftwareKeyboard(Core::System& system, AppletStorageChannel& channel) { const CommonArguments arguments{ .arguments_version = CommonArgumentVersion::Version3, .size = CommonArgumentSize::Version3, .library_version = static_cast(Frontend::SwkbdAppletVersion::Version524301), .theme_color = ThemeColor::BasicBlack, .play_startup_sound = true, .system_tick = system.CoreTiming().GetClockTicks(), }; std::vector initial_string(0); const Frontend::SwkbdConfigCommon swkbd_config{ .type = Frontend::SwkbdType::Qwerty, .ok_text{}, .left_optional_symbol_key{}, .right_optional_symbol_key{}, .use_prediction = false, .key_disable_flags{}, .initial_cursor_position = Frontend::SwkbdInitialCursorPosition::Start, .header_text{}, .sub_text{}, .guide_text{}, .max_text_length = 500, .min_text_length = 0, .password_mode = Frontend::SwkbdPasswordMode::Disabled, .text_draw_type = Frontend::SwkbdTextDrawType::Box, .enable_return_button = true, .use_utf8 = false, .use_blur_background = true, .initial_string_offset{}, .initial_string_length = static_cast(initial_string.size()), .user_dictionary_offset{}, .user_dictionary_entries{}, .use_text_check = false, }; Frontend::SwkbdConfigNew swkbd_config_new{}; std::vector argument_data(sizeof(arguments)); std::vector swkbd_data(sizeof(swkbd_config) + sizeof(swkbd_config_new)); std::vector work_buffer(swkbd_config.initial_string_length * sizeof(char16_t)); std::memcpy(argument_data.data(), &arguments, sizeof(arguments)); std::memcpy(swkbd_data.data(), &swkbd_config, sizeof(swkbd_config)); std::memcpy(swkbd_data.data() + sizeof(swkbd_config), &swkbd_config_new, sizeof(Frontend::SwkbdConfigNew)); std::memcpy(work_buffer.data(), initial_string.data(), swkbd_config.initial_string_length * sizeof(char16_t)); channel.Push(std::make_shared(system, std::move(argument_data))); channel.Push(std::make_shared(system, std::move(swkbd_data))); channel.Push(std::make_shared(system, std::move(work_buffer))); } } // namespace AppletManager::AppletManager(Core::System& system) : m_system(system) {} AppletManager::~AppletManager() { this->Reset(); } void AppletManager::InsertApplet(std::shared_ptr applet) { std::scoped_lock lk{m_lock}; m_applets.emplace(applet->aruid, std::move(applet)); } void AppletManager::TerminateAndRemoveApplet(AppletResourceUserId aruid) { std::shared_ptr applet; bool should_stop = false; { std::scoped_lock lk{m_lock}; const auto it = m_applets.find(aruid); if (it == m_applets.end()) { return; } applet = it->second; m_applets.erase(it); should_stop = m_applets.empty(); } // Terminate process. applet->process->Terminate(); // If there were no applets left, stop emulation. if (should_stop) { m_system.Exit(); } } void AppletManager::CreateAndInsertByFrontendAppletParameters( AppletResourceUserId aruid, const FrontendAppletParameters& params) { // TODO: this should be run inside AM so that the events will have a parent process // TODO: have am create the guest process auto applet = std::make_shared(m_system, std::make_unique(m_system)); applet->aruid = aruid; applet->program_id = params.program_id; applet->applet_id = params.applet_id; applet->type = params.applet_type; applet->previous_program_index = params.previous_program_index; // Push UserChannel data from previous application if (params.launch_type == LaunchType::ApplicationInitiated) { applet->user_channel_launch_parameter.swap(m_system.GetUserChannel()); } // TODO: Read whether we need a preselected user from NACP? // TODO: This can be done quite easily from loader { LaunchParameterAccountPreselectedUser lp{}; lp.magic = LaunchParameterAccountPreselectedUserMagic; lp.is_account_selected = 1; Account::ProfileManager profile_manager{}; const auto uuid = profile_manager.GetUser(static_cast(Settings::values.current_user)); ASSERT(uuid.has_value() && uuid->IsValid()); lp.current_user = *uuid; std::vector buffer(sizeof(LaunchParameterAccountPreselectedUser)); std::memcpy(buffer.data(), &lp, buffer.size()); applet->preselected_user_launch_parameter.push_back(std::move(buffer)); } // Starting from frontend, some applets require input data. switch (applet->applet_id) { case AppletId::Cabinet: PushInShowCabinetData(m_system, InitializeFakeCallerApplet(m_system, applet)); break; case AppletId::MiiEdit: PushInShowMiiEditData(m_system, InitializeFakeCallerApplet(m_system, applet)); break; case AppletId::PhotoViewer: PushInShowAlbum(m_system, InitializeFakeCallerApplet(m_system, applet)); break; case AppletId::SoftwareKeyboard: PushInShowSoftwareKeyboard(m_system, InitializeFakeCallerApplet(m_system, applet)); break; case AppletId::Controller: PushInShowController(m_system, InitializeFakeCallerApplet(m_system, applet)); break; default: break; } // Applet was started by frontend, so it is foreground. applet->message_queue.PushMessage(AppletMessage::ChangeIntoForeground); applet->message_queue.PushMessage(AppletMessage::FocusStateChanged); applet->focus_state = FocusState::InFocus; this->InsertApplet(std::move(applet)); } std::shared_ptr AppletManager::GetByAppletResourceUserId(AppletResourceUserId aruid) const { std::scoped_lock lk{m_lock}; if (const auto it = m_applets.find(aruid); it != m_applets.end()) { return it->second; } return {}; } void AppletManager::Reset() { std::scoped_lock lk{m_lock}; m_applets.clear(); } void AppletManager::RequestExit() { std::scoped_lock lk{m_lock}; for (const auto& [aruid, applet] : m_applets) { applet->message_queue.RequestExit(); } } void AppletManager::RequestResume() { std::scoped_lock lk{m_lock}; for (const auto& [aruid, applet] : m_applets) { applet->message_queue.RequestResume(); } } void AppletManager::OperationModeChanged() { std::scoped_lock lk{m_lock}; for (const auto& [aruid, applet] : m_applets) { applet->message_queue.OperationModeChanged(); } } void AppletManager::FocusStateChanged() { std::scoped_lock lk{m_lock}; for (const auto& [aruid, applet] : m_applets) { applet->message_queue.FocusStateChanged(); } } } // namespace Service::AM