diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 39aebd5f48..615080ec43 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1089,6 +1089,8 @@ add_library(core STATIC hle/service/ssl/ssl.h hle/service/ssl/ssl_backend.h hle/service/ssl/ssl_types.h + hle/service/ulsf/ulsf.cpp + hle/service/ulsf/ulsf.h hle/service/usb/usb.cpp hle/service/usb/usb.h hle/service/vi/application_display_service.cpp diff --git a/src/core/hle/kernel/init/init_slab_setup.cpp b/src/core/hle/kernel/init/init_slab_setup.cpp index a0e20bbbb4..da5e90df6e 100644 --- a/src/core/hle/kernel/init/init_slab_setup.cpp +++ b/src/core/hle/kernel/init/init_slab_setup.cpp @@ -83,7 +83,8 @@ constexpr size_t SlabCountKDeviceAddressSpace = 300; constexpr size_t SlabCountKSession = 1133; constexpr size_t SlabCountKLightSession = 100; constexpr size_t SlabCountKObjectName = 7; -constexpr size_t SlabCountKResourceLimit = 5; +// TODO(lizzie): divergence that allows ulauncher to work +constexpr size_t SlabCountKResourceLimit = 5 + 1; constexpr size_t SlabCountKDebug = Core::Hardware::NUM_CPU_CORES; constexpr size_t SlabCountKIoPool = 1; constexpr size_t SlabCountKIoRegion = 6; diff --git a/src/core/hle/service/am/am_types.h b/src/core/hle/service/am/am_types.h index beb52b74dd..dfd5ab49d2 100644 --- a/src/core/hle/service/am/am_types.h +++ b/src/core/hle/service/am/am_types.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -94,7 +94,9 @@ enum class AppletId : u32 { LoginShare = 0x18, WebAuth = 0x19, MyPage = 0x1A, - Lhub = 0x35 + Lhub = 0x35, + // Homebrew -- uses same ProgramId as qlaunch + UlauncherUmenu = 0xF000'0000, }; enum class AppletProgramId : u64 { diff --git a/src/core/hle/service/am/applet_manager.cpp b/src/core/hle/service/am/applet_manager.cpp index c2920f91ae..6d27285b05 100644 --- a/src/core/hle/service/am/applet_manager.cpp +++ b/src/core/hle/service/am/applet_manager.cpp @@ -24,6 +24,20 @@ namespace Service::AM { namespace { +constexpr size_t NroPathSize = 512; +constexpr size_t NroArgvSize = 2048; +constexpr size_t MenuCaptionSize = 1024; +struct UloaderTargetInput { + u32 magic; + bool target_once; + bool is_auto_game_recording; + std::array unused; + std::array nro_path; + std::array nro_argv; + std::array menu_caption; +}; +static_assert(sizeof(UloaderTargetInput) == 3592); + constexpr u32 LaunchParameterAccountPreselectedUserMagic = 0xC79497CA; struct LaunchParameterAccountPreselectedUser { @@ -121,6 +135,44 @@ void PushInShowController(Core::System& system, AppletStorageChannel& channel) { channel.Push(std::make_shared(system, std::move(user_args_data))); } +void PushInShowUlauncherUmenu(Core::System& system, AppletStorageChannel& channel) { + typedef std::array AccountUid; + const CommonArguments common_args = { + .arguments_version = CommonArgumentVersion::Version3, + .size = CommonArgumentSize::Version3, + .library_version = 2, + .theme_color = ThemeColor::BasicBlack, + .play_startup_sound = true, + .system_tick = system.CoreTiming().GetClockTicks(), + }; + struct UmenuInput { + AccountUid selected_user; + UloaderTargetInput suspended_hb_target_ipt; // Set if homebrew (launched as an application) is currently suspended + u64 suspended_app_id; // Set if any normal application is suspended + std::array last_menu_fs_path; //FS_MAX_PATH + std::array last_menu_path; + u32 last_menu_index; + bool reload_theme_cache; + bool warned_about_outdated_theme; + u32 last_added_app_count; + u32 last_deleted_app_count; + u32 in_verify_app_count; + } user_args = {}; + static_assert(sizeof(UmenuInput) == 5176); + + auto const user = system.GetProfileManager().GetUser(0); + user_args.selected_user[0] = user->uuid[0]; + user_args.selected_user[1] = user->uuid[1]; + user_args.warned_about_outdated_theme = true; + + std::vector common_args_data(sizeof(common_args)); + std::vector user_args_data(sizeof(user_args)); + std::memcpy(common_args_data.data(), std::addressof(common_args), sizeof(common_args)); + std::memcpy(user_args_data.data(), std::addressof(user_args), sizeof(user_args)); + channel.Push(std::make_shared(system, std::move(common_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, @@ -292,8 +344,18 @@ void AppletManager::SetWindowSystem(WindowSystem* window_system) { applet->previous_program_index = params.previous_program_index; // Push UserChannel data from previous application + // Or ulaunch initialization where we push parameters willingly! if (params.launch_type == LaunchType::ApplicationInitiated) { applet->user_channel_launch_parameter.swap(m_system.GetUserChannel()); + } else if (params.launch_type == LaunchType::FrontendUlaunchInitiated) { + UloaderTargetInput target_ipt = {}; + target_ipt.magic = 0x49444C55; // "ULDI" + target_ipt.nro_path = {"sdmc:/hbmenu.nro"}; + target_ipt.menu_caption = {"Loaded by uLoader v1.2.4 - uLaunch's custom hbloader replacement ;)"}; + std::vector v(sizeof(target_ipt)); + std::memcpy(v.data(), std::addressof(target_ipt), sizeof(target_ipt)); + applet->user_channel_launch_parameter.clear(); + applet->user_channel_launch_parameter.push_back(std::move(v)); } // TODO: Read whether we need a preselected user from NACP? @@ -335,6 +397,9 @@ void AppletManager::SetWindowSystem(WindowSystem* window_system) { case AppletId::Controller: PushInShowController(m_system, InitializeFakeCallerApplet(m_system, applet)); break; + case AppletId::UlauncherUmenu: + PushInShowUlauncherUmenu(m_system, InitializeFakeCallerApplet(m_system, applet)); + break; default: break; } @@ -342,7 +407,7 @@ void AppletManager::SetWindowSystem(WindowSystem* window_system) { // Applet was started by frontend, so it is foreground. applet->lifecycle_manager.SetFocusState(FocusState::InFocus); - if (applet->applet_id == AppletId::QLaunch) { + if (applet->applet_id == AppletId::QLaunch || applet->applet_id == AppletId::UlauncherUmenu) { applet->lifecycle_manager.SetFocusHandlingMode(false); applet->lifecycle_manager.SetOutOfFocusSuspendingEnabled(false); m_window_system->TrackApplet(applet, false); diff --git a/src/core/hle/service/am/applet_manager.h b/src/core/hle/service/am/applet_manager.h index 2b0714adf7..8c2378aca3 100644 --- a/src/core/hle/service/am/applet_manager.h +++ b/src/core/hle/service/am/applet_manager.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -26,6 +26,9 @@ class WindowSystem; enum class LaunchType { FrontendInitiated, ApplicationInitiated, + // Special masquerade for AMS + uLaunch CFW + FrontendUlaunchInitiated = 0x800, + FrontendUmenuInitiated, }; struct FrontendAppletParameters { 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 3de5740237..764264f81b 100644 --- a/src/core/hle/service/am/service/library_applet_creator.cpp +++ b/src/core/hle/service/am/service/library_applet_creator.cpp @@ -55,6 +55,7 @@ AppletProgramId AppletIdToProgramId(AppletId applet_id) { case AppletId::OverlayDisplay: return AppletProgramId::OverlayDisplay; case AppletId::QLaunch: + case AppletId::UlauncherUmenu: //reuses same id as Qlaunch return AppletProgramId::QLaunch; case AppletId::Starter: return AppletProgramId::Starter; diff --git a/src/core/hle/service/am/service/library_applet_self_accessor.cpp b/src/core/hle/service/am/service/library_applet_self_accessor.cpp index 4a1d1f97d3..248c6fa03f 100644 --- a/src/core/hle/service/am/service/library_applet_self_accessor.cpp +++ b/src/core/hle/service/am/service/library_applet_self_accessor.cpp @@ -8,18 +8,23 @@ #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/registered_cache.h" +#include "core/hle/result.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/applets.h" +#include "core/hle/service/am/process_creation.h" #include "core/hle/service/am/service/library_applet_self_accessor.h" #include "core/hle/service/am/service/storage.h" +#include "core/hle/service/am/window_system.h" #include "core/hle/service/cmif_serialization.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/glue/glue_manager.h" #include "core/hle/service/ns/application_manager_interface.h" #include "core/hle/service/ns/service_getter_interface.h" +#include "core/hle/service/server_manager.h" #include "core/hle/service/sm/sm.h" +#include "core/loader/loader.h" namespace Service::AM { @@ -44,8 +49,9 @@ AppletIdentityInfo GetCallerIdentity(Applet& applet) { ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_, std::shared_ptr applet) - : ServiceFramework{system_, "ILibraryAppletSelfAccessor"}, m_applet{std::move(applet)}, - m_broker{m_applet->caller_applet_broker} { + : ServiceFramework{system_, "ILibraryAppletSelfAccessor"}, m_applet{std::move(applet)} + , m_broker{m_applet->caller_applet_broker} +{ // clang-format off static const FunctionInfo functions[] = { {0, D<&ILibraryAppletSelfAccessor::PopInData>, "PopInData"}, @@ -96,9 +102,130 @@ Result ILibraryAppletSelfAccessor::PopInData(Out> out_st R_RETURN(m_broker->GetInData().Pop(out_storage)); } +// uLauncher emulation +static Result UloaderCreateApplication(Core::System& system, u64 program_id, std::shared_ptr caller_applet) { + // Get the program NCA from storage. + auto& storage = system.GetContentProviderUnion(); + FileSys::VirtualFile nca_raw = storage.GetEntryRaw(program_id, FileSys::ContentRecordType::Program); + // Ensure we retrieved a program NCA. + R_UNLESS(nca_raw != nullptr, ResultUnknown); + std::vector control; + std::unique_ptr loader; + Loader::ResultStatus result; + auto process = CreateApplicationProcess(control, loader, result, system, nca_raw, program_id, 0); + R_UNLESS(process != nullptr, ResultUnknown); + const auto applet = std::make_shared(system, std::move(process), true); + applet->program_id = program_id; + applet->applet_id = AppletId::Application; + applet->type = AppletType::Application; + applet->library_applet_mode = LibraryAppletMode::AllForeground; + + applet->caller_applet = caller_applet; + applet->caller_applet_broker = std::make_shared(system); + applet->frontend = caller_applet->frontend; + caller_applet->child_applets.push_back(applet); + + system.GetAppletManager().GetWindowSystem()->TrackApplet(applet, true); + R_SUCCEED(); +} + Result ILibraryAppletSelfAccessor::PushOutData(SharedPointer storage) { LOG_INFO(Service_AM, "called"); m_broker->GetOutData().Push(storage); + + if (m_applet->applet_id == AppletId::UlauncherUmenu) { + enum class SystemMessage : u32 { + Invalid, + SetSelectedUser, + LaunchApplication, + ResumeApplication, + TerminateApplication, + LaunchHomebrewLibraryApplet, + LaunchHomebrewApplication, + ChooseHomebrew, + OpenWebPage, + OpenAlbum, + RestartMenu, + ReloadConfig, + UpdateMenuPaths, + UpdateMenuIndex, + OpenUserPage, + OpenMiiEdit, + OpenAddUser, + OpenNetConnect, + ListAddedApplications, + ListDeletedApplications, + OpenCabinet, + StartVerifyApplication, + ListInVerifyApplications, + NotifyWarnedAboutOutdatedTheme, + TerminateMenu, + OpenControllerKeyRemapping + }; + struct CommandCommonHeader { + u32 magic; + u32 val; + }; + std::shared_ptr req_storage; + m_broker->GetOutData().Pop(&req_storage); + auto req_data = req_storage->GetData(); + CommandCommonHeader req_cmd{}; + std::memcpy(&req_cmd, req_data.data(), sizeof(req_cmd)); + + LOG_WARNING(Service_AM, "uLauncher:IPC {}, {}", req_cmd.magic, req_cmd.val); + switch (SystemMessage(req_cmd.val)) { + case SystemMessage::SetSelectedUser: + break; + case SystemMessage::LaunchApplication: { + // all applet proxies OpenSystemAppletProxy + // applet proxy GetApplicationCreator + // application creator CreateApplication + u64 args_value{}; + std::memcpy(std::addressof(args_value), req_data.data() + sizeof(req_cmd), sizeof(args_value)); + LOG_WARNING(Service_AM, "program_id={:016x}", args_value); + UloaderCreateApplication(system, args_value, m_applet); + break; + } + case SystemMessage::ResumeApplication: + case SystemMessage::TerminateApplication: + case SystemMessage::LaunchHomebrewLibraryApplet: + case SystemMessage::LaunchHomebrewApplication: + case SystemMessage::ChooseHomebrew: + case SystemMessage::OpenWebPage: + case SystemMessage::OpenAlbum: + case SystemMessage::RestartMenu: + case SystemMessage::ReloadConfig: + case SystemMessage::UpdateMenuPaths: + case SystemMessage::UpdateMenuIndex: + case SystemMessage::OpenUserPage: + case SystemMessage::OpenMiiEdit: + case SystemMessage::OpenAddUser: + case SystemMessage::OpenNetConnect: + case SystemMessage::ListAddedApplications: + case SystemMessage::ListDeletedApplications: + case SystemMessage::OpenCabinet: + case SystemMessage::StartVerifyApplication: + case SystemMessage::ListInVerifyApplications: + case SystemMessage::NotifyWarnedAboutOutdatedTheme: + break; + case SystemMessage::TerminateMenu: + system.GetUserChannel() = m_applet->user_channel_launch_parameter; + system.ExecuteProgram(0); + break; + case SystemMessage::OpenControllerKeyRemapping: + break; + case SystemMessage::Invalid: + break; + } + + CommandCommonHeader res_cmd{}; + std::vector res_data(0x8000); + res_cmd.magic = 0x21494D53; + res_cmd.val = u32(ResultSuccess.raw); + std::memcpy(res_data.data(), &res_cmd, sizeof(res_cmd)); + m_broker->GetInData().Push(std::make_shared(system, std::move(res_data))); + } + R_SUCCEED(); } diff --git a/src/core/hle/service/am/window_system.cpp b/src/core/hle/service/am/window_system.cpp index 5f20a98322..c40011f702 100644 --- a/src/core/hle/service/am/window_system.cpp +++ b/src/core/hle/service/am/window_system.cpp @@ -59,7 +59,7 @@ void WindowSystem::Update() { void WindowSystem::TrackApplet(std::shared_ptr applet, bool is_application) { std::scoped_lock lk{m_lock}; - if (applet->applet_id == AppletId::QLaunch) { + if (applet->applet_id == AppletId::QLaunch || applet->applet_id == AppletId::UlauncherUmenu) { ASSERT(m_home_menu == nullptr); m_home_menu = applet.get(); } else if (applet->applet_id == AppletId::OverlayDisplay) { diff --git a/src/core/hle/service/btm/btm_system_core.cpp b/src/core/hle/service/btm/btm_system_core.cpp index f1e623be16..318657fc32 100644 --- a/src/core/hle/service/btm/btm_system_core.cpp +++ b/src/core/hle/service/btm/btm_system_core.cpp @@ -29,7 +29,7 @@ IBtmSystemCore::IBtmSystemCore(Core::System& system_) {10, nullptr, "StartAudioDeviceDiscovery"}, {11, nullptr, "StopAudioDeviceDiscovery"}, {12, nullptr, "IsDiscoveryingAudioDevice"}, - {13, nullptr, "GetDiscoveredAudioDevice"}, + {13, C<&IBtmSystemCore::GetDiscoveredAudioDevice>, "GetDiscoveredAudioDevice"}, {14, C<&IBtmSystemCore::AcquireAudioDeviceConnectionEvent>, "AcquireAudioDeviceConnectionEvent"}, {15, nullptr, "ConnectAudioDevice"}, {16, nullptr, "IsConnectingAudioDevice"}, @@ -93,6 +93,11 @@ Result IBtmSystemCore::AcquireRadioEvent(Out out_is_valid, R_SUCCEED(); } +Result IBtmSystemCore::GetDiscoveredAudioDevice(OutArray, BufferAttr_HipcPointer> out_audio_devices, s32 count, Out out_total) { + LOG_WARNING(Service_BTM, "(STUBBED) called"); + R_SUCCEED(); +} + Result IBtmSystemCore::AcquireAudioDeviceConnectionEvent( OutCopyHandle out_event) { LOG_WARNING(Service_BTM, "(STUBBED) called"); diff --git a/src/core/hle/service/btm/btm_system_core.h b/src/core/hle/service/btm/btm_system_core.h index 06498b21ea..3fc4c0d56c 100644 --- a/src/core/hle/service/btm/btm_system_core.h +++ b/src/core/hle/service/btm/btm_system_core.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -34,9 +37,8 @@ private: Result DisableRadio(); Result IsRadioEnabled(Out out_is_enabled); - Result AcquireRadioEvent(Out out_is_valid, - OutCopyHandle out_event); - + Result AcquireRadioEvent(Out out_is_valid, OutCopyHandle out_event); + Result GetDiscoveredAudioDevice(OutArray, BufferAttr_HipcPointer> out_audio_devices, s32 count, Out out_total); Result AcquireAudioDeviceConnectionEvent(OutCopyHandle out_event); Result GetConnectedAudioDevices( diff --git a/src/core/hle/service/services.cpp b/src/core/hle/service/services.cpp index 636f54ad49..da55f9dc65 100644 --- a/src/core/hle/service/services.cpp +++ b/src/core/hle/service/services.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -62,6 +62,7 @@ #include "core/hle/service/sockets/sockets.h" #include "core/hle/service/spl/spl_module.h" #include "core/hle/service/ssl/ssl.h" +#include "core/hle/service/ulsf/ulsf.h" #include "core/hle/service/usb/usb.h" #include "core/hle/service/vi/vi.h" @@ -144,7 +145,8 @@ Services::Services(std::shared_ptr& sm, Core::System& system {"ro", &RO::LoopProcess}, {"spl", &SPL::LoopProcess}, {"ssl", &SSL::LoopProcess}, - {"usb", &USB::LoopProcess} + {"usb", &USB::LoopProcess}, + {"ulsf", &ULSF::LoopProcess}, }) kernel.RunOnGuestCoreProcess(std::string(e.first), [&system, f = e.second] { f(system); }); } diff --git a/src/core/hle/service/set/system_settings_server.cpp b/src/core/hle/service/set/system_settings_server.cpp index 0ed6d28dd4..7fa74e6689 100644 --- a/src/core/hle/service/set/system_settings_server.cpp +++ b/src/core/hle/service/set/system_settings_server.cpp @@ -142,10 +142,10 @@ ISystemSettingsServer::ISystemSettingsServer(Core::System& system_) {22, C<&ISystemSettingsServer::SetEulaVersions>, "SetEulaVersions"}, {23, C<&ISystemSettingsServer::GetColorSetId>, "GetColorSetId"}, {24, C<&ISystemSettingsServer::SetColorSetId>, "SetColorSetId"}, - {25, nullptr, "GetConsoleInformationUploadFlag"}, - {26, nullptr, "SetConsoleInformationUploadFlag"}, - {27, nullptr, "GetAutomaticApplicationDownloadFlag"}, - {28, nullptr, "SetAutomaticApplicationDownloadFlag"}, + {25, C<&ISystemSettingsServer::GetConsoleInformationUploadFlag>, "GetConsoleInformationUploadFlag"}, + {26, C<&ISystemSettingsServer::SetConsoleInformationUploadFlag>, "SetConsoleInformationUploadFlag"}, + {27, C<&ISystemSettingsServer::GetAutomaticApplicationDownloadFlag>, "GetAutomaticApplicationDownloadFlag"}, + {28, C<&ISystemSettingsServer::SetAutomaticApplicationDownloadFlag>, "SetAutomaticApplicationDownloadFlag"}, {29, C<&ISystemSettingsServer::GetNotificationSettings>, "GetNotificationSettings"}, {30, C<&ISystemSettingsServer::SetNotificationSettings>, "SetNotificationSettings"}, {31, C<&ISystemSettingsServer::GetAccountNotificationSettings>, "GetAccountNotificationSettings"}, @@ -160,8 +160,8 @@ ISystemSettingsServer::ISystemSettingsServer(Core::System& system_) {42, nullptr, "SetEdid"}, {43, C<&ISystemSettingsServer::GetAudioOutputMode>, "GetAudioOutputMode"}, {44, C<&ISystemSettingsServer::SetAudioOutputMode>, "SetAudioOutputMode"}, - {45, C<&ISystemSettingsServer::GetSpeakerAutoMuteFlag> , "GetSpeakerAutoMuteFlag"}, - {46, C<&ISystemSettingsServer::SetSpeakerAutoMuteFlag> , "SetSpeakerAutoMuteFlag"}, + {45, C<&ISystemSettingsServer::GetSpeakerAutoMuteFlag>, "GetSpeakerAutoMuteFlag"}, + {46, C<&ISystemSettingsServer::SetSpeakerAutoMuteFlag>, "SetSpeakerAutoMuteFlag"}, {47, C<&ISystemSettingsServer::GetQuestFlag>, "GetQuestFlag"}, {48, C<&ISystemSettingsServer::SetQuestFlag>, "SetQuestFlag"}, {49, nullptr, "GetDataDeletionSettings"}, @@ -180,8 +180,8 @@ ISystemSettingsServer::ISystemSettingsServer(Core::System& system_) {62, C<&ISystemSettingsServer::GetDebugModeFlag>, "GetDebugModeFlag"}, {63, C<&ISystemSettingsServer::GetPrimaryAlbumStorage>, "GetPrimaryAlbumStorage"}, {64, C<&ISystemSettingsServer::SetPrimaryAlbumStorage>, "SetPrimaryAlbumStorage"}, - {65, nullptr, "GetUsb30EnableFlag"}, - {66, nullptr, "SetUsb30EnableFlag"}, + {65, C<&ISystemSettingsServer::GetUsb30EnableFlag>, "GetUsb30EnableFlag"}, + {66, C<&ISystemSettingsServer::SetUsb30EnableFlag>, "SetUsb30EnableFlag"}, {67, C<&ISystemSettingsServer::GetBatteryLot>, "GetBatteryLot"}, {68, C<&ISystemSettingsServer::GetSerialNumber>, "GetSerialNumber"}, {69, C<&ISystemSettingsServer::GetNfcEnableFlag>, "GetNfcEnableFlag"}, @@ -1074,6 +1074,45 @@ Result ISystemSettingsServer::SetNfcEnableFlag(bool nfc_enable_flag) { R_SUCCEED(); } +Result ISystemSettingsServer::GetConsoleInformationUploadFlag(Out out_flag) { + LOG_INFO(Service_SET, "called {}", m_system_settings.console_information_upload_flag); + *out_flag = m_system_settings.console_information_upload_flag; + R_SUCCEED(); +} + +Result ISystemSettingsServer::SetConsoleInformationUploadFlag(bool flag) { + LOG_INFO(Service_SET, "called {}", flag); + m_system_settings.usb_30_enable_flag = flag; + SetSaveNeeded(); + R_SUCCEED(); +} + +Result ISystemSettingsServer::GetAutomaticApplicationDownloadFlag(Out out_flag) { + LOG_INFO(Service_SET, "called {}", m_system_settings.usb_30_enable_flag); + *out_flag = m_system_settings.automatic_application_download_flag; + R_SUCCEED(); +} + +Result ISystemSettingsServer::SetAutomaticApplicationDownloadFlag(bool flag) { + LOG_INFO(Service_SET, "called {}", flag); + m_system_settings.automatic_application_download_flag = flag; + SetSaveNeeded(); + R_SUCCEED(); +} + +Result ISystemSettingsServer::GetUsb30EnableFlag(Out out_usb30_enable_flag) { + LOG_INFO(Service_SET, "called, usb30_enable_flag={}", m_system_settings.usb_30_enable_flag); + *out_usb30_enable_flag = m_system_settings.usb_30_enable_flag; + R_SUCCEED(); +} + +Result ISystemSettingsServer::SetUsb30EnableFlag(bool usb30_enable_flag) { + LOG_INFO(Service_SET, "called, usb30_enable_flag={}", usb30_enable_flag); + m_system_settings.usb_30_enable_flag = usb30_enable_flag; + SetSaveNeeded(); + R_SUCCEED(); +} + Result ISystemSettingsServer::GetSleepSettings(Out out_sleep_settings) { LOG_INFO(Service_SET, "called, flags={}, handheld_sleep_plan={}, console_sleep_plan={}", m_system_settings.sleep_settings.flags.raw, diff --git a/src/core/hle/service/set/system_settings_server.h b/src/core/hle/service/set/system_settings_server.h index 2a160dfc6b..1d56e61f88 100644 --- a/src/core/hle/service/set/system_settings_server.h +++ b/src/core/hle/service/set/system_settings_server.h @@ -109,6 +109,12 @@ public: Result SetPrimaryAlbumStorage(PrimaryAlbumStorage primary_album_storage); Result GetBatteryLot(Out out_battery_lot); Result GetSerialNumber(Out out_console_serial); + Result GetConsoleInformationUploadFlag(Out out_flag); + Result SetConsoleInformationUploadFlag(bool flag); + Result GetAutomaticApplicationDownloadFlag(Out out_flag); + Result SetAutomaticApplicationDownloadFlag(bool flag); + Result GetUsb30EnableFlag(Out out_usb30_enable_flag); + Result SetUsb30EnableFlag(bool usb30_enable_flag); Result GetNfcEnableFlag(Out out_nfc_enable_flag); Result SetNfcEnableFlag(bool nfc_enable_flag); Result GetSleepSettings(Out out_sleep_settings); diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp index 1095dcf6c3..5332fa3615 100644 --- a/src/core/hle/service/sm/sm.cpp +++ b/src/core/hle/service/sm/sm.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -11,6 +14,7 @@ #include "core/hle/kernel/k_scoped_resource_reservation.h" #include "core/hle/kernel/k_server_port.h" #include "core/hle/result.h" +#include "core/hle/service/cmif_types.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/server_manager.h" #include "core/hle/service/sm/sm.h" @@ -250,15 +254,37 @@ void SM::UnregisterService(HLERequestContext& ctx) { rb.Push(service_manager.UnregisterService(name)); } +void SM::AtmosphereHasService(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + std::string name(PopServiceName(rp)); + LOG_WARNING(Service_SM, "(stubbed) called with name={}", name); + IPC::ResponseBuilder rb{ctx, 3}; + Kernel::KClientPort* out_client_port = nullptr; + rb.Push(ResultSuccess); + rb.Push(service_manager.GetServicePort(&out_client_port, name) == ResultSuccess); +} + SM::SM(ServiceManager& service_manager_, Core::System& system_) - : ServiceFramework{system_, "sm:", 4}, - service_manager{service_manager_}, kernel{system_.Kernel()} { + : ServiceFramework{system_, "sm:", 4} + , service_manager{service_manager_} + , kernel{system_.Kernel()} +{ RegisterHandlers({ {0, &SM::Initialize, "Initialize"}, {1, &SM::GetServiceCmif, "GetService"}, {2, &SM::RegisterServiceCmif, "RegisterService"}, {3, &SM::UnregisterService, "UnregisterService"}, {4, nullptr, "DetachClient"}, + // TODO: are these non-TIPC as well? + {65000, nullptr, "AtmosphereInstallMitm"}, + {65001, nullptr, "AtmosphereUninstallMitm"}, + {65002, nullptr, "Deprecated_AtmosphereAssociatePidTidForMitm"}, + {65003, nullptr, "AtmosphereAcknowledgeMitmSession"}, + {65004, nullptr, "AtmosphereHasMitm"}, + {65005, nullptr, "AtmosphereWaitMitm"}, + {65006, nullptr, "AtmosphereDeclareFutureMitm"}, + {65100, &SM::AtmosphereHasService, "AtmosphereHasService"}, + {65101, nullptr, "AtmosphereWaitService"}, }); RegisterHandlersTipc({ {0, &SM::Initialize, "Initialize"}, @@ -266,6 +292,15 @@ SM::SM(ServiceManager& service_manager_, Core::System& system_) {2, &SM::RegisterServiceTipc, "RegisterService"}, {3, &SM::UnregisterService, "UnregisterService"}, {4, nullptr, "DetachClient"}, + {65000, nullptr, "AtmosphereInstallMitm"}, + {65001, nullptr, "AtmosphereUninstallMitm"}, + {65002, nullptr, "Deprecated_AtmosphereAssociatePidTidForMitm"}, + {65003, nullptr, "AtmosphereAcknowledgeMitmSession"}, + {65004, nullptr, "AtmosphereHasMitm"}, + {65005, nullptr, "AtmosphereWaitMitm"}, + {65006, nullptr, "AtmosphereDeclareFutureMitm"}, + {65100, &SM::AtmosphereHasService, "AtmosphereHasService"}, + {65101, nullptr, "AtmosphereWaitService"}, }); } diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h index 65e6876926..b566452ad7 100644 --- a/src/core/hle/service/sm/sm.h +++ b/src/core/hle/service/sm/sm.h @@ -47,6 +47,7 @@ private: void RegisterServiceCmif(HLERequestContext& ctx); void RegisterServiceTipc(HLERequestContext& ctx); void UnregisterService(HLERequestContext& ctx); + void AtmosphereHasService(HLERequestContext& ctx); Result GetServiceImpl(Kernel::KClientSession** out_client_session, HLERequestContext& ctx); void RegisterServiceImpl(HLERequestContext& ctx, std::string name, u32 max_session_count, diff --git a/src/core/hle/service/ulsf/ulsf.cpp b/src/core/hle/service/ulsf/ulsf.cpp new file mode 100644 index 0000000000..cac4958ecf --- /dev/null +++ b/src/core/hle/service/ulsf/ulsf.cpp @@ -0,0 +1,137 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include "core/hle/result.h" +#include "core/hle/service/cmif_serialization.h" +#include "core/hle/service/cmif_types.h" +#include "core/hle/service/ipc_helpers.h" +#include "core/hle/service/ulsf/ulsf.h" +#include "core/hle/service/server_manager.h" + +namespace Service::ULSF { + +ULSF_U::ULSF_U(Core::System& system_) : ServiceFramework{system_, "ulsf:u"} { + static const FunctionInfo functions[] = { + {0, &ULSF_U::GetVersion, "GetVersion"}, + }; + RegisterHandlers(functions); +} +ULSF_U::~ULSF_U() = default; + +// Result ULSF_U::GetVersion(Out out_version) { +// LOG_WARNING(Service_SM, "(stubbed)"); +// *out_version = {}; +// R_SUCCEED(); +// } + +void ULSF_U::GetVersion(HLERequestContext& ctx) { + LOG_WARNING(Service_SM, "(stubbed)"); + ULauncherVersion version{1, 6, 7}; + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + rb.PushRaw(version); +} + +enum class MenuMessage : u32 { + Invalid, + HomeRequest, + SdCardEjected, + GameCardMountFailure, + PreviousLaunchFailure, + ChosenHomebrew, + FinishedSleep, + ApplicationRecordsChanged, + ApplicationVerifyProgress, + ApplicationVerifyResult +}; +struct MenuMessageContext { + MenuMessage msg; + union { + struct { + Result mount_rc; + } gc_mount_failure; + struct { + char nro_path[0x301]; + } chosen_hb; + struct { + bool records_added_or_deleted; + } app_records_changed; + struct { + u64 app_id; + u64 done; + u64 total; + } app_verify_progress; + struct { + u64 app_id; + Result rc; + Result detail_rc; + } app_verify_rc; + }; +}; + +class ULSF_P final : public ServiceFramework { +public: + explicit ULSF_P(Core::System& system_) : ServiceFramework{system_, "ulsf:p"} { + static const FunctionInfo functions[] = { + {0, C<&ULSF_P::Initialize>, "Initialize"}, + {1, C<&ULSF_P::TryPopMessageContext>, "TryPopMessageContext"}, + }; + RegisterHandlers(functions); + } + ~ULSF_P() = default; + + Result Initialize(u64 pid) { + LOG_WARNING(Service_SM, "(stubbed) pid={}", pid); + R_SUCCEED(); + } + Result TryPopMessageContext(OutLargeData out_menu_message) { + //LOG_WARNING(Service_SM, "(stubbed)"); +// *out_menu_message = {}; +// R_SUCCEED(); + R_THROW(Kernel::ResultInvalidAddress); + } +}; + +class AVM final : public ServiceFramework { +public: + explicit AVM(Core::System& system_): ServiceFramework{system_, "avm"} { + static const FunctionInfo functions[] = { + {0, &AVM::Cmd0, "Cmd0"}, + }; + RegisterHandlers(functions); + } + ~AVM() = default; + void Cmd0(HLERequestContext& ctx) { + LOG_WARNING(Service_SM, "(stubbed)"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } +}; +class WLAN final : public ServiceFramework { +public: + explicit WLAN(Core::System& system_): ServiceFramework{system_, "wlan"} { + static const FunctionInfo functions[] = { + {0, &WLAN::Cmd0, "Cmd0"}, + }; + RegisterHandlers(functions); + } + ~WLAN() = default; + void Cmd0(HLERequestContext& ctx) { + LOG_WARNING(Service_SM, "(stubbed)"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } +}; + +void LoopProcess(Core::System& system) { + auto server_manager = std::make_unique(system); + server_manager->RegisterNamedService("ulsf:u", std::make_shared(system)); + server_manager->RegisterNamedService("ulsf:p", std::make_shared(system)); + + server_manager->RegisterNamedService("avm", std::make_shared(system)); + server_manager->RegisterNamedService("wlan", std::make_shared(system)); + ServerManager::RunServer(std::move(server_manager)); +} + +} diff --git a/src/core/hle/service/ulsf/ulsf.h b/src/core/hle/service/ulsf/ulsf.h new file mode 100644 index 0000000000..61fca1ddc4 --- /dev/null +++ b/src/core/hle/service/ulsf/ulsf.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/common_types.h" +#include "core/hle/service/cmif_types.h" +#include "core/hle/service/service.h" + +namespace Service::ULSF { + +struct ULauncherVersion { + u8 major; + u8 minor; + u8 micro; +}; + +class ULSF_U final : public ServiceFramework { +public: + explicit ULSF_U(Core::System& system_); + ~ULSF_U(); + //Result GetVersion(Out out_version); + void GetVersion(HLERequestContext& ctx); +}; + +void LoopProcess(Core::System& system); + +} diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index afa5cdbc36..073c1e1584 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -12,6 +12,7 @@ #include "core/file_sys/content_archive.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" +#include "core/file_sys/program_metadata.h" #include "core/file_sys/romfs_factory.h" #include "core/hle/kernel/k_page_table.h" #include "core/hle/kernel/k_process.h" @@ -204,7 +205,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect const bool should_pass_arguments = std::strcmp(module, "rtld") == 0; const auto tentative_next_load_addr = AppLoader_NSO::LoadModule( - process, system, *module_file, code_size, should_pass_arguments, false, {}, + process, system, *module_file, code_size, should_pass_arguments, false, nullptr, metadata, {}, patch_ctx.GetPatchers(), patch_ctx.GetLastIndex()); if (!tentative_next_load_addr) { return {ResultStatus::ErrorLoadingNSO, {}}; @@ -251,8 +252,9 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect const VAddr load_addr{next_load_addr}; const bool should_pass_arguments = std::strcmp(module, "rtld") == 0; + FileSys::ProgramMetadata tmp_metadata{}; const auto tentative_next_load_addr = AppLoader_NSO::LoadModule( - process, system, *module_file, load_addr, should_pass_arguments, true, pm, + process, system, *module_file, load_addr, should_pass_arguments, true, nullptr, metadata, pm, patch_ctx.GetPatchers(), patch_ctx.GetIndex(i)); if (!tentative_next_load_addr) { return {ResultStatus::ErrorLoadingNSO, {}}; diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 482c853542..e6574ee398 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -17,10 +17,14 @@ #include "common/swap.h" #include "core/core.h" #include "core/file_sys/patch_manager.h" +#include "core/file_sys/romfs_factory.h" +#include "core/file_sys/vfs/vfs_types.h" #include "core/hle/kernel/code_set.h" #include "core/hle/kernel/k_page_table.h" #include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_thread.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/loader/loader.h" #include "core/loader/nso.h" #include "core/memory.h" @@ -66,7 +70,7 @@ FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& in_file) { return FileType::NSO; } -std::optional AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::System& system, const FileSys::VfsFile& nso_file, VAddr load_base, bool should_pass_arguments, bool load_into_process, std::optional pm, std::vector* patches, s32 patch_index) { +std::optional AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::System& system, const FileSys::VfsFile& nso_file, const VAddr load_base, const bool should_pass_arguments, const bool load_into_process, VAddr* out_load_base, FileSys::ProgramMetadata metadata, std::optional pm, std::vector* patches, s32 patch_index) { if (nso_file.GetSize() < sizeof(NSOHeader)) return std::nullopt; NSOHeader nso_header{}; @@ -216,9 +220,19 @@ std::optional AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core:: } } + const bool is_hbl = true; + if (process + .LoadFromMetadata(metadata, image_size, 0, 0, is_hbl) + .IsError()) { + return false; + } + + auto const new_load_base = process.GetEntryPoint().GetValue(); + if (out_load_base) + *out_load_base = new_load_base; // no change // Load codeset for current process - process.LoadModule(std::move(codeset), load_base); - return load_base + image_size; + process.LoadModule(std::move(codeset), new_load_base); + return new_load_base + image_size; } AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::KProcess& process, Core::System& system) { @@ -226,20 +240,34 @@ AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::KProcess& process, Core::S return {ResultStatus::ErrorAlreadyLoaded, {}}; } - modules.clear(); + FileSys::VirtualFile npdm_file{}; + metadata = FileSys::ProgramMetadata::GetDefault(); + if (auto const dir = file->GetContainingDirectory()) { + npdm_file = dir->GetFile("main.npdm"); + if (npdm_file) { + metadata.Load(npdm_file); + } + } + modules.clear(); // Load module - const VAddr base_address = GetInteger(process.GetEntryPoint()); - if (!LoadModule(process, system, *file, base_address, true, true)) { + VAddr base_address = GetInteger(process.GetEntryPoint()); + if (!LoadModule(process, system, *file, base_address, true, true, &base_address, metadata)) { return {ResultStatus::ErrorLoadingNSO, {}}; } modules.insert_or_assign(base_address, file->GetName()); LOG_DEBUG(Loader, "loaded module {} @ {:#X}", file->GetName(), base_address); + if (npdm_file) { + LOG_WARNING(Loader, "creating associated rom-fs factories for likely standalone NSO"); + u64 program_id{}; + ReadProgramId(program_id); + system.GetFileSystemController().RegisterProcess(process.GetProcessId(), program_id, std::make_unique(*this, system.GetContentProvider(), system.GetFileSystemController())); + } + is_loaded = true; - return {ResultStatus::Success, LoadParameters{Kernel::KThread::DefaultThreadPriority, - Core::Memory::DEFAULT_STACK_SIZE}}; + return {ResultStatus::Success, LoadParameters{Kernel::KThread::DefaultThreadPriority, Core::Memory::DEFAULT_STACK_SIZE}}; } ResultStatus AppLoader_NSO::ReadNSOModules(Modules& out_modules) { @@ -247,4 +275,18 @@ ResultStatus AppLoader_NSO::ReadNSOModules(Modules& out_modules) { return ResultStatus::Success; } +ResultStatus AppLoader_NSO::ReadProgramId(u64& out_program_id) { + if (metadata.GetTitleID() == 0) + return ResultStatus::ErrorNoControl; + out_program_id = metadata.GetTitleID(); + return ResultStatus::Success; +} + +ResultStatus AppLoader_NSO::ReadTitle(std::string& out_title) { + auto const raw_name = metadata.GetName(); + out_title.resize(raw_name.size()); + std::memcpy(out_title.data(), raw_name.data(), raw_name.size()); + return ResultStatus::Success; +} + } // namespace Loader diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h index 6356697e33..fec9045469 100644 --- a/src/core/loader/nso.h +++ b/src/core/loader/nso.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -9,6 +12,7 @@ #include "common/common_types.h" #include "common/swap.h" #include "core/file_sys/patch_manager.h" +#include "core/file_sys/program_metadata.h" #include "core/loader/loader.h" namespace Core { @@ -90,18 +94,20 @@ public: } static std::optional LoadModule(Kernel::KProcess& process, Core::System& system, - const FileSys::VfsFile& nso_file, VAddr load_base, - bool should_pass_arguments, bool load_into_process, - std::optional pm = {}, - std::vector* patches = nullptr, - s32 patch_index = -1); + const FileSys::VfsFile& nso_file, const VAddr load_base, + const bool should_pass_arguments, const bool load_into_process, VAddr* out_load_base, + FileSys::ProgramMetadata metadata, + std::optional pm = {}, std::vector* patches = nullptr, s32 patch_index = -1); LoadResult Load(Kernel::KProcess& process, Core::System& system) override; ResultStatus ReadNSOModules(Modules& out_modules) override; + ResultStatus ReadProgramId(u64& out_program_id) override; + ResultStatus ReadTitle(std::string& title) override; private: Modules modules; + FileSys::ProgramMetadata metadata; }; } // namespace Loader diff --git a/src/yuzu/main_window.cpp b/src/yuzu/main_window.cpp index 4d5a3c43f9..c2e6473c51 100644 --- a/src/yuzu/main_window.cpp +++ b/src/yuzu/main_window.cpp @@ -512,11 +512,12 @@ MainWindow::MainWindow(bool has_broken_vulkan) return; } - QString game_path; + QString game_path{}; bool should_launch_qlaunch = false; bool should_launch_hlaunch = false; + bool should_launch_hlaunch_uloader = false; + bool should_launch_ulaunch = false; bool should_launch_setup = false; - bool has_gamepath = false; bool is_fullscreen = false; // Preserves drag/drop functionality @@ -550,7 +551,6 @@ MainWindow::MainWindow(bool has_broken_vulkan) } else if (args[i] == QStringLiteral("-g") && i < args.size() - 1) { // Launch game at path game_path = args[++i]; - has_gamepath = true; } else if (args[i] == QStringLiteral("-input-profile") && i < args.size() - 1) { auto& players = Settings::values.players.GetValue(); players[0].profile_name = args[++i].toStdString(); @@ -558,16 +558,19 @@ MainWindow::MainWindow(bool has_broken_vulkan) should_launch_qlaunch = true; } else if (args[i] == QStringLiteral("-hlaunch")) { should_launch_hlaunch = true; + } else if (args[i] == QStringLiteral("-hlaunch-uloader")) { + should_launch_hlaunch_uloader = true; + } else if (args[i] == QStringLiteral("-ulaunch")) { + should_launch_ulaunch = true; } else if (args[i] == QStringLiteral("-setup")) { should_launch_setup = true; } else { game_path = args[i]; - has_gamepath = true; } } // Override fullscreen setting if gamepath or argument is provided - if (has_gamepath || is_fullscreen) { + if (!game_path.isEmpty() || is_fullscreen) { ui->action_Fullscreen->setChecked(is_fullscreen); } @@ -580,8 +583,22 @@ MainWindow::MainWindow(bool has_broken_vulkan) LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::QLaunch), std::nullopt); } else if (should_launch_hlaunch) { std::filesystem::path const sd_dir = Common::FS::GetEdenPathString(Common::FS::EdenPath::SDMCDir); - auto const hbl_path = (sd_dir / "atmosphere" / "hbl.nsp").string(); - BootGame(QString::fromStdString(hbl_path), ApplicationAppletParameters()); + auto const path = (sd_dir / "atmosphere" / "hbl.nsp").string(); + BootGame(QString::fromStdString(path), ApplicationAppletParameters()); + } else if (should_launch_hlaunch_uloader) { + std::filesystem::path const sd_dir = Common::FS::GetEdenPathString(Common::FS::EdenPath::SDMCDir); + auto const path = (sd_dir / "ulaunch" / "bin" / "uLoader" / "application" / "main").string(); + auto params = ApplicationAppletParameters(); + params.launch_type = Service::AM::LaunchType::FrontendUlaunchInitiated; + BootGame(QString::fromStdString(path), params); + } else if (should_launch_ulaunch) { + std::filesystem::path const sd_dir = Common::FS::GetEdenPathString(Common::FS::EdenPath::SDMCDir); + auto const path = (sd_dir / "ulaunch" / "bin" / "uMenu" / "main").string(); + auto const program_id = 0x010000000000FFFF; + auto const applet_id = Service::AM::AppletId::UlauncherUmenu; + auto params = LibraryAppletParameters(program_id, applet_id); + QtCommon::system->GetFrontendAppletHolder().SetCurrentAppletId(applet_id); + BootGame(QString::fromStdString(path), params); } } } @@ -2779,7 +2796,7 @@ void MainWindow::OnMenuLoadFolder() { const QDir dir{dir_path}; const QStringList matching_main = dir.entryList({QStringLiteral("main")}, QDir::Files); - if (matching_main.size() == 1) { + if (matching_main.size() > 0) { BootGame(dir.path() + QDir::separator() + matching_main[0], ApplicationAppletParameters()); } else { QMessageBox::warning(this, tr("Invalid Directory Selected"),