Update for new Plutonium, huge code cleanup/rewrite

This commit is contained in:
xor 2022-02-06 15:47:49 +01:00
parent 033afd2267
commit 4d915bc653
75 changed files with 5794 additions and 9293 deletions

2
.gitignore vendored
View file

@ -1,6 +1,6 @@
build/
.vscode/
Out/
lib/
SdOut/
*.elf
*.npdm

@ -1 +1 @@
Subproject commit 470139aaedd7d1051d9f7abdffb8536347daa7aa
Subproject commit 0777e00e65958c82e0b87de3dc9fcde8eca609b0

View file

@ -2,28 +2,28 @@
#pragma once
#include <stratosphere.hpp>
#include <am/am_Application.hpp>
#include <am/am_LibraryApplet.hpp>
namespace ecs {
Result RegisterExternalContent(u64 program_id, const std::string &exefs_path);
Result LaunchApplet(u64 program_id, u32 la_version, void *args, size_t args_size);
Result LaunchSystemProcess(u64 program_id, const std::string &argv_str);
Result RegisterExternalContent(const u64 program_id, const std::string &exefs_path);
Result LaunchSystemProcess(const u64 program_id, const std::string &argv_str);
inline Result RegisterLaunchAsApplet(u64 program_id, u32 la_version, const std::string &exefs_path, void *args, size_t args_size) {
R_TRY(RegisterExternalContent(program_id, exefs_path));
R_TRY(LaunchApplet(program_id, la_version, args, args_size));
inline Result RegisterLaunchAsApplet(const u64 program_id, const u32 la_version, const std::string &exefs_path, const void *args, const size_t args_size) {
UL_RC_TRY(RegisterExternalContent(program_id, exefs_path));
UL_RC_TRY(am::LibraryAppletStart(am::LibraryAppletGetAppletIdForProgramId(program_id), la_version, args, args_size));
return ResultSuccess;
}
inline Result RegisterLaunchAsApplication(u64 program_id, const std::string &exefs_path, void *args, size_t args_size, AccountUid uid) {
R_TRY(RegisterExternalContent(program_id, exefs_path));
R_TRY(am::ApplicationStart(program_id, false, uid, args, args_size));
inline Result RegisterLaunchAsApplication(const u64 program_id, const std::string &exefs_path, const void *args, const size_t args_size, const AccountUid uid) {
UL_RC_TRY(RegisterExternalContent(program_id, exefs_path));
UL_RC_TRY(am::ApplicationStart(program_id, false, uid, args, args_size));
return ResultSuccess;
}
inline Result RegisterLaunchAsSystemProcess(u64 program_id, const std::string &exefs_path, const std::string &argv_str) {
R_TRY(RegisterExternalContent(program_id, exefs_path));
R_TRY(LaunchSystemProcess(program_id, argv_str));
inline Result RegisterLaunchAsSystemProcess(const u64 program_id, const std::string &exefs_path, const std::string &argv_str) {
UL_RC_TRY(RegisterExternalContent(program_id, exefs_path));
UL_RC_TRY(LaunchSystemProcess(program_id, argv_str));
return ResultSuccess;
}

View file

@ -1,6 +1,5 @@
#include <stratosphere.hpp>
#include <dmi/dmi_DaemonMenuInteraction.hpp>
#include <functional>
namespace ipc {
@ -41,12 +40,13 @@ namespace ipc {
};
Result Initialize();
ServerManager &GetGlobalManager();
Allocator &GetServerAllocator();
Allocator &GetManagerAllocator();
ams::Result RegisterSession(const ams::os::NativeHandle session_handle, ams::sf::cmif::ServiceObjectHolder &&obj);
template<typename Impl, typename T, typename ...Args>
inline auto MakeShared(Args ...args) {
return ObjectFactory::CreateSharedEmplaced<Impl, T>(std::addressof(GetServerAllocator()), args...);
return ObjectFactory::CreateSharedEmplaced<Impl, T>(std::addressof(GetManagerAllocator()), args...);
}
}

View file

@ -1,5 +1,5 @@
#include <ecs/ecs_ExternalContent.hpp>
#include <ipc/ipc_GlobalManager.hpp>
#include <ipc/ipc_Manager.hpp>
#include <db/db_Save.hpp>
#include <os/os_Titles.hpp>
#include <os/os_HomeMenu.hpp>
@ -19,17 +19,19 @@ extern "C" {
u32 __nx_fsdev_direntry_cache_size = 0;
// Needed by libnx's usbcomms to allocate internal buffers...
// Note: operator new(size_t, std::align_val_t) isn't redefined in ams, so we need to stick with C-style memory functions
// (by default that operator internally calls _memalign_r, which isn't redefined by ams either, so it leads to crashes)
void *__libnx_alloc(size_t size) {
return operator new(size);
return malloc(size);
}
void *__libnx_aligned_alloc(size_t align, size_t size) {
return operator new(size, std::align_val_t(align));
return aligned_alloc(align, size);
}
void __libnx_free(void *ptr) {
return operator delete(ptr);
return free(ptr);
}
}
@ -43,8 +45,8 @@ namespace {
enum class UsbMode : u32 {
Invalid,
RawRGBA,
JPEG
PlainRgba,
Jpeg
};
AccountUid g_SelectedUser = {};
@ -61,14 +63,17 @@ namespace {
u8 *g_UsbViewerBuffer = nullptr;
u8 *g_UsbViewerReadBuffer = nullptr;
cfg::Config g_Config = {};
constexpr size_t UsbViewerThreadStackSize = 16_KB;
ams::os::ThreadType g_UsbViewerThread;
alignas(ams::os::ThreadStackAlignment) u8 g_UsbViewerThreadStack[0x4000];
alignas(ams::os::ThreadStackAlignment) u8 g_UsbViewerThreadStack[UsbViewerThreadStackSize];
UsbMode g_UsbViewerMode = UsbMode::Invalid;
SetSysFirmwareVersion g_FwVersion = {};
// In the USB packet, the first u32 stores the USB mode (raw RGBA or JPEG, depending on what the console supports)
// In the USB packet, the first u32 stores the USB mode (plain RGBA or JPEG, depending on what the console supports)
constexpr size_t UsbPacketSize = RawRGBAScreenBufferSize + sizeof(UsbMode);
constexpr size_t UsbPacketSize = PlainRgbaScreenBufferSize + sizeof(UsbMode);
constexpr size_t HeapSize = 10_MB;
alignas(ams::os::MemoryPageSize) constinit u8 g_HeapBuffer[HeapSize];
@ -78,8 +83,9 @@ namespace {
namespace {
dmi::DaemonStatus CreateStatus() {
dmi::DaemonStatus status = {};
status.selected_user = g_SelectedUser;
dmi::DaemonStatus status = {
.selected_user = g_SelectedUser
};
memcpy(status.fw_version, g_FwVersion.display_version, sizeof(status.fw_version));
@ -101,24 +107,20 @@ namespace {
appletStartSleepSequence(true);
}
inline Result LaunchMenu(dmi::MenuStartMode st_mode, dmi::DaemonStatus status) {
u64 menu_program_id;
UL_ASSERT_TRUE(g_Config.GetEntry(cfg::ConfigEntryId::MenuTakeoverProgramId, menu_program_id));
return ecs::RegisterLaunchAsApplet(menu_program_id, static_cast<u32>(st_mode), "/ulaunch/bin/uMenu", &status, sizeof(status));
inline Result LaunchMenu(const dmi::MenuStartMode st_mode, const dmi::DaemonStatus &status) {
return ecs::RegisterLaunchAsApplet(am::LibraryAppletGetMenuProgramId(), static_cast<u32>(st_mode), "/ulaunch/bin/uMenu", std::addressof(status), sizeof(status));
}
void HandleHomeButton() {
if(am::LibraryAppletIsActive() && !am::LibraryAppletIsMenu()) {
// An applet is opened (which is not our menu), thus close it and reopen the menu
am::LibraryAppletTerminate();
auto status = CreateStatus();
UL_ASSERT(LaunchMenu(dmi::MenuStartMode::Menu, status));
UL_RC_ASSERT(LaunchMenu(dmi::MenuStartMode::Menu, CreateStatus()));
}
else if(am::ApplicationIsActive() && am::ApplicationHasForeground()) {
// Hide the application currently on focus and open our menu
am::HomeMenuSetForeground();
auto status = CreateStatus();
UL_ASSERT(LaunchMenu(dmi::MenuStartMode::MenuApplicationSuspended, status));
UL_RC_ASSERT(LaunchMenu(dmi::MenuStartMode::MenuApplicationSuspended, CreateStatus()));
}
else if(am::LibraryAppletIsMenu()) {
// Send a message to our menu to handle itself the home press
@ -129,11 +131,11 @@ namespace {
Result HandleGeneralChannel() {
AppletStorage sams_st;
R_TRY(appletPopFromGeneralChannel(&sams_st));
UL_RC_TRY(appletPopFromGeneralChannel(&sams_st));
UL_ON_SCOPE_EXIT({ appletStorageClose(&sams_st); });
os::SystemAppletMessage sams = {};
R_TRY(appletStorageRead(&sams_st, 0, &sams, sizeof(sams)));
UL_RC_TRY(appletStorageRead(&sams_st, 0, &sams, sizeof(sams)));
if(sams.magic == os::SystemAppletMessage::Magic) {
switch(sams.general_channel_message) {
// Usually this doesn't happen, HOME is detected by applet messages...?
@ -167,7 +169,7 @@ namespace {
// We're qlaunch, not using appletMainLoop, thus we have to take care of this manually...
u8 tmp_mode = 0;
R_TRY(serviceDispatchOut(appletGetServiceSession_CommonStateGetter(), 5, tmp_mode));
UL_RC_TRY(serviceDispatchOut(appletGetServiceSession_CommonStateGetter(), 5, tmp_mode));
g_OperationMode = static_cast<AppletOperationMode>(tmp_mode);
return ResultSuccess;
@ -175,7 +177,7 @@ namespace {
Result HandleAppletMessage() {
u32 raw_msg = 0;
R_TRY(appletGetMessage(&raw_msg));
UL_RC_TRY(appletGetMessage(&raw_msg));
const auto msg = static_cast<os::AppletMessage>(raw_msg);
switch(msg) {
@ -207,15 +209,15 @@ namespace {
if(am::LibraryAppletIsMenu()) {
char web_url[500] = {};
u64 app_id = 0;
hb::HbTargetParams ipt = {};
dmi::dmn::ReceiveCommand([&](dmi::DaemonMessage msg, dmi::dmn::DaemonScopedStorageReader &reader) -> Result {
hb::HbTargetParams params = {};
dmi::dmn::ReceiveCommand([&](const dmi::DaemonMessage msg, dmi::dmn::DaemonScopedStorageReader &reader) -> Result {
switch(msg) {
case dmi::DaemonMessage::SetSelectedUser: {
R_TRY(reader.Pop(g_SelectedUser));
UL_RC_TRY(reader.Pop(g_SelectedUser));
break;
}
case dmi::DaemonMessage::LaunchApplication: {
R_TRY(reader.Pop(app_id));
UL_RC_TRY(reader.Pop(app_id));
break;
}
case dmi::DaemonMessage::ResumeApplication: {
@ -227,16 +229,16 @@ namespace {
break;
}
case dmi::DaemonMessage::LaunchHomebrewLibraryApplet: {
R_TRY(reader.Pop(g_HbTargetLaunchFlag));
UL_RC_TRY(reader.Pop(g_HbTargetLaunchFlag));
break;
}
case dmi::DaemonMessage::LaunchHomebrewApplication: {
R_TRY(reader.Pop(app_id));
R_TRY(reader.Pop(ipt));
UL_RC_TRY(reader.Pop(app_id));
UL_RC_TRY(reader.Pop(params));
break;
}
case dmi::DaemonMessage::OpenWebPage: {
R_TRY(reader.PopData(web_url, sizeof(web_url)));
UL_RC_TRY(reader.PopData(web_url, sizeof(web_url)));
break;
}
case dmi::DaemonMessage::OpenAlbum: {
@ -254,7 +256,7 @@ namespace {
}
return ResultSuccess;
},
[&](dmi::DaemonMessage msg, dmi::dmn::DaemonScopedStorageWriter &writer) -> Result {
[&](const dmi::DaemonMessage msg, dmi::dmn::DaemonScopedStorageWriter &writer) -> Result {
switch(msg) {
case dmi::DaemonMessage::SetSelectedUser: {
// ...
@ -302,8 +304,8 @@ namespace {
return dmn::ResultAlreadyQueued;
}
g_HbTargetApplicationLaunchFlag = ipt;
g_HbTargetApplicationLaunchFlagCopy = ipt;
g_HbTargetApplicationLaunchFlag = params;
g_HbTargetApplicationLaunchFlagCopy = params;
g_ApplicationLaunchFlag = app_id;
break;
}
@ -330,10 +332,10 @@ namespace {
}
}
void UsbViewerRGBAThread(void*) {
void UsbViewerRgbaThread(void*) {
while(true) {
bool tmp_flag;
appletGetLastForegroundCaptureImageEx(g_UsbViewerReadBuffer, RawRGBAScreenBufferSize, &tmp_flag);
appletGetLastForegroundCaptureImageEx(g_UsbViewerReadBuffer, PlainRgbaScreenBufferSize, &tmp_flag);
appletUpdateLastForegroundCaptureImage();
usbCommsWrite(g_UsbViewerBuffer, UsbPacketSize);
}
@ -342,21 +344,23 @@ namespace {
void UsbViewerJPEGThread(void*) {
while(true) {
u64 tmp_size;
capsscCaptureJpegScreenShot(&tmp_size, g_UsbViewerReadBuffer, RawRGBAScreenBufferSize, ViLayerStack_Default, UINT64_MAX);
capsscCaptureJpegScreenShot(&tmp_size, g_UsbViewerReadBuffer, PlainRgbaScreenBufferSize, ViLayerStack_Default, UINT64_MAX);
usbCommsWrite(g_UsbViewerBuffer, UsbPacketSize);
}
}
void PrepareUsbViewer() {
g_UsbViewerBuffer = new (std::align_val_t(0x1000)) u8[UsbPacketSize]();
g_UsbViewerBuffer = reinterpret_cast<u8*>(__libnx_aligned_alloc(ams::os::MemoryPageSize, UsbPacketSize));
memset(g_UsbViewerBuffer, 0, UsbPacketSize);
// Skip the first u32 of the buffer, since the mode is stored there
g_UsbViewerReadBuffer = g_UsbViewerBuffer + sizeof(UsbMode);
u64 tmp_size;
if(R_SUCCEEDED(capsscCaptureJpegScreenShot(&tmp_size, g_UsbViewerReadBuffer, RawRGBAScreenBufferSize, ViLayerStack_Default, UINT64_MAX))) {
g_UsbViewerMode = UsbMode::JPEG;
if(R_SUCCEEDED(capsscCaptureJpegScreenShot(&tmp_size, g_UsbViewerReadBuffer, PlainRgbaScreenBufferSize, ViLayerStack_Default, UINT64_MAX))) {
g_UsbViewerMode = UsbMode::Jpeg;
}
else {
g_UsbViewerMode = UsbMode::RawRGBA;
g_UsbViewerMode = UsbMode::PlainRgba;
capsscExit();
}
*reinterpret_cast<UsbMode*>(g_UsbViewerBuffer) = g_UsbViewerMode;
@ -367,12 +371,12 @@ namespace {
HandleAppletMessage();
HandleMenuMessage();
bool sth_done = false;
auto sth_done = false;
// A valid version will always be >= 0x20000
if(g_WebAppletLaunchFlag.version > 0) {
if(!am::LibraryAppletIsActive()) {
// TODO: applet startup sound?
UL_ASSERT(am::WebAppletStart(&g_WebAppletLaunchFlag));
UL_RC_ASSERT(am::WebAppletStart(&g_WebAppletLaunchFlag));
sth_done = true;
g_WebAppletLaunchFlag = {};
@ -380,8 +384,7 @@ namespace {
}
if(g_MenuRestartFlag) {
if(!am::LibraryAppletIsActive()) {
auto status = CreateStatus();
UL_ASSERT(LaunchMenu(dmi::MenuStartMode::StartupScreen, status));
UL_RC_ASSERT(LaunchMenu(dmi::MenuStartMode::StartupScreen, CreateStatus()));
sth_done = true;
g_MenuRestartFlag = false;
@ -393,7 +396,7 @@ namespace {
u8 album_arg;
} album_data = { AlbumLaArg_ShowAllAlbumFilesForHomeMenu };
// TODO: applet startup sound?
UL_ASSERT(am::LibraryAppletStart(AppletId_LibraryAppletPhotoViewer, 0x10000, &album_data, sizeof(album_data)));
UL_RC_ASSERT(am::LibraryAppletStart(AppletId_LibraryAppletPhotoViewer, 0x10000, &album_data, sizeof(album_data)));
sth_done = true;
g_AlbumAppletLaunchFlag = false;
@ -402,16 +405,16 @@ namespace {
if(g_ApplicationLaunchFlag > 0) {
if(!am::LibraryAppletIsActive()) {
if(strlen(g_HbTargetApplicationLaunchFlag.nro_path)) {
auto params = hb::HbTargetParams::Create(g_HbTargetApplicationLaunchFlag.nro_path, g_HbTargetApplicationLaunchFlag.nro_argv, false);
UL_ASSERT(ecs::RegisterLaunchAsApplication(g_ApplicationLaunchFlag, "/ulaunch/bin/uHbTarget/app", &params, sizeof(params), g_SelectedUser));
const auto params = hb::HbTargetParams::Create(g_HbTargetApplicationLaunchFlag.nro_path, g_HbTargetApplicationLaunchFlag.nro_argv, false);
UL_RC_ASSERT(ecs::RegisterLaunchAsApplication(g_ApplicationLaunchFlag, "/ulaunch/bin/uHbTarget/app", &params, sizeof(params), g_SelectedUser));
g_HbTargetOpenedAsApplication = true;
g_HbTargetApplicationLaunchFlag.nro_path[0] = '\0';
}
else {
// Ensure the application is launchable
UL_ASSERT(nsTouchApplication(g_ApplicationLaunchFlag));
UL_ASSERT(am::ApplicationStart(g_ApplicationLaunchFlag, false, g_SelectedUser));
UL_RC_ASSERT(nsTouchApplication(g_ApplicationLaunchFlag));
UL_RC_ASSERT(am::ApplicationStart(g_ApplicationLaunchFlag, false, g_SelectedUser));
}
sth_done = true;
g_ApplicationLaunchFlag = 0;
@ -419,22 +422,21 @@ namespace {
}
if(strlen(g_HbTargetLaunchFlag.nro_path)) {
if(!am::LibraryAppletIsActive()) {
auto params = hb::HbTargetParams::Create(g_HbTargetLaunchFlag.nro_path, g_HbTargetLaunchFlag.nro_argv, false);
const auto params = hb::HbTargetParams::Create(g_HbTargetLaunchFlag.nro_path, g_HbTargetLaunchFlag.nro_argv, false);
u64 homebrew_applet_program_id;
UL_ASSERT_TRUE(g_Config.GetEntry(cfg::ConfigEntryId::HomebrewAppletTakeoverProgramId, homebrew_applet_program_id));
UL_ASSERT(ecs::RegisterLaunchAsApplet(homebrew_applet_program_id, 0, "/ulaunch/bin/uHbTarget/applet", &params, sizeof(params)));
UL_RC_ASSERT(ecs::RegisterLaunchAsApplet(homebrew_applet_program_id, 0, "/ulaunch/bin/uHbTarget/applet", &params, sizeof(params)));
sth_done = true;
g_HbTargetLaunchFlag.nro_path[0] = '\0';
}
}
if(!am::LibraryAppletIsActive()) {
auto cur_id = am::LibraryAppletGetId();
const auto cur_id = am::LibraryAppletGetId();
u64 homebrew_applet_program_id;
UL_ASSERT_TRUE(g_Config.GetEntry(cfg::ConfigEntryId::HomebrewAppletTakeoverProgramId, homebrew_applet_program_id));
if((cur_id == AppletId_LibraryAppletWeb) || (cur_id == AppletId_LibraryAppletPhotoViewer) || (cur_id == homebrew_applet_program_id)) {
auto status = CreateStatus();
UL_ASSERT(LaunchMenu(dmi::MenuStartMode::Menu, status));
UL_RC_ASSERT(LaunchMenu(dmi::MenuStartMode::Menu, CreateStatus()));
sth_done = true;
}
@ -448,26 +450,26 @@ namespace {
// Throw the application's result if it actually ended with a result
auto terminate_rc = ResultSuccess;
if(R_SUCCEEDED(nsGetApplicationTerminateResult(am::ApplicationGetId(), &terminate_rc))) {
UL_ASSERT(terminate_rc);
UL_RC_ASSERT(terminate_rc);
}
// Reopen uMenu in launch-error mode
auto status = CreateStatus();
UL_ASSERT(LaunchMenu(dmi::MenuStartMode::MenuLaunchFailure, status));
UL_RC_ASSERT(LaunchMenu(dmi::MenuStartMode::MenuLaunchFailure, CreateStatus()));
g_HbTargetOpenedAsApplication = false;
}
}
svcSleepThread(10'000'000ul);
}
Result LaunchUsbViewerThread() {
void(*thread_entry)(void*) = nullptr;
switch(g_UsbViewerMode) {
case UsbMode::RawRGBA: {
thread_entry = &UsbViewerRGBAThread;
case UsbMode::PlainRgba: {
thread_entry = &UsbViewerRgbaThread;
break;
}
case UsbMode::JPEG: {
case UsbMode::Jpeg: {
thread_entry = &UsbViewerJPEGThread;
break;
}
@ -475,19 +477,19 @@ namespace {
return ResultSuccess;
}
R_TRY(ams::os::CreateThread(&g_UsbViewerThread, thread_entry, nullptr, g_UsbViewerThreadStack, sizeof(g_UsbViewerThreadStack), 10).GetValue());
UL_RC_TRY(ams::os::CreateThread(&g_UsbViewerThread, thread_entry, nullptr, g_UsbViewerThreadStack, sizeof(g_UsbViewerThreadStack), 10));
ams::os::StartThread(&g_UsbViewerThread);
return ResultSuccess;
}
void Initialize() {
UL_ASSERT(appletLoadAndApplyIdlePolicySettings());
UL_RC_ASSERT(appletLoadAndApplyIdlePolicySettings());
UpdateOperationMode();
UL_ASSERT(setsysGetFirmwareVersion(&g_FwVersion));
UL_RC_ASSERT(setsysGetFirmwareVersion(&g_FwVersion));
UL_ASSERT(db::Mount());
UL_RC_ASSERT(db::Mount());
// Remove last present error report
fs::DeleteFile(UL_ASSERTION_LOG_FILE);
@ -515,25 +517,24 @@ namespace {
bool viewer_usb_enabled;
UL_ASSERT_TRUE(g_Config.GetEntry(cfg::ConfigEntryId::ViewerUsbEnabled, viewer_usb_enabled));
if(viewer_usb_enabled) {
UL_ASSERT(usbCommsInitialize());
UL_ASSERT(capsscInitialize());
UL_RC_ASSERT(usbCommsInitialize());
UL_RC_ASSERT(capsscInitialize());
PrepareUsbViewer();
UL_ASSERT(LaunchUsbViewerThread());
UL_RC_ASSERT(LaunchUsbViewerThread());
}
UL_ASSERT(ipc::Initialize());
UL_RC_ASSERT(ipc::Initialize());
}
void Finalize() {
bool viewer_usb_enabled;
UL_ASSERT_TRUE(g_Config.GetEntry(cfg::ConfigEntryId::ViewerUsbEnabled, viewer_usb_enabled));
if(viewer_usb_enabled) {
if(g_UsbViewerBuffer != nullptr) {
usbCommsExit();
if(g_UsbViewerMode == UsbMode::JPEG) {
if(g_UsbViewerMode == UsbMode::Jpeg) {
capsscExit();
}
operator delete[](g_UsbViewerBuffer, std::align_val_t(0x1000));
__libnx_free(g_UsbViewerBuffer);
g_UsbViewerBuffer = nullptr;
}
nsExit();
@ -553,20 +554,17 @@ namespace ams {
void InitializeSystemModule() {
__nx_applet_type = AppletType_SystemApplet;
UL_AMS_ASSERT(sm::Initialize());
UL_RC_ASSERT(sm::Initialize());
UL_ASSERT(appletInitialize());
UL_ASSERT(fsInitialize());
UL_ASSERT(nsInitialize());
UL_ASSERT(pminfoInitialize());
UL_ASSERT(ldrShellInitialize());
UL_ASSERT(pmshellInitialize());
UL_ASSERT(setsysInitialize());
UL_RC_ASSERT(appletInitialize());
UL_RC_ASSERT(fsInitialize());
UL_RC_ASSERT(nsInitialize());
UL_RC_ASSERT(pminfoInitialize());
UL_RC_ASSERT(ldrShellInitialize());
UL_RC_ASSERT(pmshellInitialize());
UL_RC_ASSERT(setsysInitialize());
fsdevMountSdmc();
// TODO: disabling this doesn't really avoid ams aborting with new fws, shall we try to avoid that in a different way?
/* ams::CheckApiVersion(); */
}
void FinalizeSystemModule() {}
@ -574,6 +572,8 @@ namespace ams {
void Startup() {
// Initialize the global malloc-free/new-delete allocator
init::InitializeAllocator(g_HeapBuffer, HeapSize);
os::SetThreadNamePointer(os::GetCurrentThread(), "ul.daemon.Main");
}
}
@ -590,8 +590,7 @@ namespace ams {
Initialize();
// After having initialized everything, launch our menu
auto status = CreateStatus();
UL_ASSERT(LaunchMenu(dmi::MenuStartMode::StartupScreen, status));
UL_RC_ASSERT(LaunchMenu(dmi::MenuStartMode::StartupScreen, CreateStatus()));
// Loop forever, since qlaunch should NEVER terminate (AM would crash in that case)
while(true) {

View file

@ -1,12 +1,10 @@
#include <ecs/ecs_ExternalContent.hpp>
#include <ipc/ipc_GlobalManager.hpp>
#include <am/am_LibraryApplet.hpp>
#include <am/am_HomeMenu.hpp>
#include <ipc/ipc_Manager.hpp>
#include <stratosphere/fssrv/fssrv_interface_adapters.hpp>
namespace {
inline Result ldrShellAtmosphereRegisterExternalCode(u64 app_id, Handle *out_h) {
inline Result ldrShellAtmosphereRegisterExternalCode(const u64 app_id, Handle *out_h) {
return serviceDispatchIn(ldrShellGetServiceSession(), 65000, app_id,
.out_handle_attrs = { SfOutHandleAttr_HipcMove },
.out_handles = out_h,
@ -17,38 +15,29 @@ namespace {
namespace ecs {
Result RegisterExternalContent(u64 app_id, const std::string &exefs_path) {
Result RegisterExternalContent(const u64 app_id, const std::string &exefs_path) {
auto move_h = INVALID_HANDLE;
R_TRY(ldrShellAtmosphereRegisterExternalCode(app_id, &move_h));
UL_RC_TRY(ldrShellAtmosphereRegisterExternalCode(app_id, &move_h));
FsFileSystem sd_fs;
R_TRY(fsOpenSdCardFileSystem(&sd_fs));
UL_RC_TRY(fsOpenSdCardFileSystem(&sd_fs));
std::unique_ptr<ams::fs::fsa::IFileSystem> remote_sd_fs = std::make_unique<ams::fs::RemoteFileSystem>(sd_fs);
auto subdir_fs = std::make_shared<ams::fssystem::SubDirectoryFileSystem>(std::move(remote_sd_fs), exefs_path.c_str());
auto sd_ifs_ipc = ipc::MakeShared<ams::fssrv::sf::IFileSystem, ams::fssrv::impl::FileSystemInterfaceAdapter>(std::move(subdir_fs), false);
R_TRY(ipc::GetGlobalManager().RegisterSession(move_h, ams::sf::cmif::ServiceObjectHolder(std::move(sd_ifs_ipc))).GetValue());
UL_RC_TRY(ipc::RegisterSession(move_h, ams::sf::cmif::ServiceObjectHolder(std::move(sd_ifs_ipc))));
return ResultSuccess;
}
Result LaunchApplet(u64 program_id, u32 la_version, void *args, size_t args_size) {
auto appletid = am::LibraryAppletGetAppletIdForProgramId(program_id);
if(appletid == 0) {
return 0xDEAD;
}
R_TRY(am::LibraryAppletStart(appletid, la_version, args, args_size));
return ResultSuccess;
}
Result LaunchSystemProcess(u64 program_id, const std::string &argv_str) {
R_TRY(ldrShellSetProgramArguments(program_id, argv_str.c_str(), argv_str.length()));
Result LaunchSystemProcess(const u64 program_id, const std::string &argv_str) {
UL_RC_TRY(ldrShellSetProgramArguments(program_id, argv_str.c_str(), argv_str.length()));
NcmProgramLocation loc = {
.program_id = program_id,
.storageID = NcmStorageId_BuiltInSystem,
.storageID = NcmStorageId_BuiltInSystem
};
u64 pid;
R_TRY(pmshellLaunchProgram(0, &loc, &pid));
UL_RC_TRY(pmshellLaunchProgram(0, &loc, &pid));
return ResultSuccess;
}

View file

@ -1,58 +0,0 @@
#include <ipc/ipc_GlobalManager.hpp>
#include <ipc/ipc_IPrivateService.hpp>
// IPublicService...
namespace {
ipc::ServerManager g_GlobalManager;
ams::os::ThreadType g_GlobalManagerThread;
alignas(ams::os::ThreadStackAlignment) u8 g_GlobalManagerThreadStack[0x8000];
ams::os::Mutex g_ServerAllocatorLock(false);
void GlobalManagerThread(void*) {
UL_AMS_ASSERT(g_GlobalManager.RegisterServer(ipc::Port_PrivateService, ipc::PrivateServiceName, ipc::MaxPrivateSessions));
// UL_ASSERT(g_GlobalManager.RegisterServer<ipc::IPublicService>(PublicServiceName, MaxPublicSessions).GetValue());
g_GlobalManager.LoopProcess();
}
alignas(0x40) constinit u8 g_server_allocator_buffer[0x8000];
ams::lmem::HeapHandle g_server_heap_handle;
ipc::Allocator g_server_allocator;
void InitializeHeap() {
g_server_heap_handle = ams::lmem::CreateExpHeap(g_server_allocator_buffer, sizeof(g_server_allocator_buffer), ams::lmem::CreateOption_None);
g_server_allocator.Attach(g_server_heap_handle);
}
}
namespace ipc {
ams::Result ServerManager::OnNeedsToAccept(int port_index, Server *server) {
switch(port_index) {
case Port_PrivateService: {
return this->AcceptImpl(server, MakeShared<ams::sf::ul::IPrivateService, ipc::PrivateService>());
}
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
::Result Initialize() {
InitializeHeap();
R_TRY(ams::os::CreateThread(&g_GlobalManagerThread, &GlobalManagerThread, nullptr, g_GlobalManagerThreadStack, sizeof(g_GlobalManagerThreadStack), 10).GetValue());
ams::os::StartThread(&g_GlobalManagerThread);
return ::ResultSuccess;
}
ServerManager &GetGlobalManager() {
return g_GlobalManager;
}
Allocator &GetServerAllocator() {
std::scoped_lock lk(g_ServerAllocatorLock);
return g_server_allocator;
}
}

View file

@ -10,9 +10,9 @@ namespace ipc {
ams::Result PrivateService::Initialize(const ams::sf::ClientProcessId &client_pid) {
if(!this->initialized) {
u64 program_id = 0;
R_TRY(pminfoGetProgramId(&program_id, client_pid.process_id.value));
UL_RC_TRY(pminfoGetProgramId(&program_id, client_pid.process_id.value));
const auto last_menu_program_id = am::LibraryAppletGetProgramIdForAppletId(am::LibraryAppletGetMenuAppletId());
const auto last_menu_program_id = am::LibraryAppletGetMenuProgramId();
// If Menu hasn't been launched it's program ID will be 0 (invalid), thus a single (program_id != last_menu_program_id) check isn't enough
// If any of the IDs is invalid, something unexpected is happening...
if((last_menu_program_id == 0) || (program_id == 0) || (program_id != last_menu_program_id)) {

View file

@ -0,0 +1,63 @@
#include <ipc/ipc_Manager.hpp>
#include <ipc/ipc_IPrivateService.hpp>
namespace {
ipc::ServerManager g_Manager;
constexpr size_t IpcManagerThreadStackSize = 32_KB;
ams::os::ThreadType g_ManagerThread;
alignas(ams::os::ThreadStackAlignment) u8 g_ManagerThreadStack[IpcManagerThreadStackSize];
ams::os::Mutex g_ManagerAllocatorLock(false);
constexpr size_t ServerAllocatorHeapSize = 32_KB;
alignas(0x40) constinit u8 g_ManagerAllocatorHeap[ServerAllocatorHeapSize];
ams::lmem::HeapHandle g_ManagerAllocatorHeapHandle;
ipc::Allocator g_ManagerAllocator;
void IpcManagerThread(void*) {
ams::os::SetThreadNamePointer(ams::os::GetCurrentThread(), "ul.daemon.IpcManager");
UL_RC_ASSERT(g_Manager.RegisterServer(ipc::Port_PrivateService, ipc::PrivateServiceName, ipc::MaxPrivateSessions));
// UL_RC_ASSERT(g_Manager.RegisterServer<ipc::IPublicService>(PublicServiceName, MaxPublicSessions));
g_Manager.LoopProcess();
}
void InitializeHeap() {
g_ManagerAllocatorHeapHandle = ams::lmem::CreateExpHeap(g_ManagerAllocatorHeap, sizeof(g_ManagerAllocatorHeap), ams::lmem::CreateOption_None);
g_ManagerAllocator.Attach(g_ManagerAllocatorHeapHandle);
}
}
namespace ipc {
ams::Result ServerManager::OnNeedsToAccept(int port_index, Server *server) {
switch(port_index) {
case Port_PrivateService: {
return this->AcceptImpl(server, MakeShared<ams::sf::ul::IPrivateService, ipc::PrivateService>());
}
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
Result Initialize() {
InitializeHeap();
UL_RC_TRY(ams::os::CreateThread(&g_ManagerThread, &IpcManagerThread, nullptr, g_ManagerThreadStack, sizeof(g_ManagerThreadStack), 10));
ams::os::StartThread(&g_ManagerThread);
return ResultSuccess;
}
Allocator &GetManagerAllocator() {
std::scoped_lock lk(g_ManagerAllocatorLock);
return g_ManagerAllocator;
}
ams::Result RegisterSession(const ams::os::NativeHandle session_handle, ams::sf::cmif::ServiceObjectHolder &&obj) {
return g_Manager.RegisterSession(session_handle, std::move(obj));
}
}

View file

@ -11,79 +11,25 @@ extern "C" {
u32 __nx_fsdev_direntry_cache_size = 1;
bool __nx_fsdev_support_cwd = false;
void hb_hbl_Target(Handle process_handle, const char *path, const char *argv, int do_once);
void hb_hbl_Target(const Handle process_handle, const char *path, const char *argv, const int do_once);
}
namespace {
inline Result DoWithSmSession(std::function<Result()> fn) {
R_TRY(smInitialize());
R_TRY(fn());
smExit();
return ResultSuccess;
}
namespace impl {
Handle g_process_handle = INVALID_HANDLE;
u64 g_process_id = 0;
u64 g_program_id = 0;
Result ProcessInfoReceiverImpl(Handle session) {
auto base = armGetTls();
hipcMakeRequestInline(base);
s32 idx = 0;
R_TRY(svcReplyAndReceive(&idx, &session, 1, INVALID_HANDLE, UINT64_MAX));
auto r = hipcParseRequest(base);
g_process_handle = r.data.copy_handles[0];
g_process_id = r.pid;
svcCloseHandle(session);
return ResultSuccess;
}
Result GetProgramIdImpl() {
auto rc = svcGetInfo(&g_program_id, InfoType_ProgramId, CUR_PROCESS_HANDLE, 0);
if(R_SUCCEEDED(rc)) {
return ResultSuccess;
}
// Let's try with pminfo
R_TRY(DoWithSmSession([&]() {
R_TRY(pminfoInitialize());
return ResultSuccess;
}));
UL_ON_SCOPE_EXIT({
pminfoExit();
});
R_TRY(pminfoGetProgramId(&g_program_id, impl::g_process_id));
return ResultSuccess;
}
void ProcessInfoReceiver(void *arg) {
auto session = static_cast<Handle>(reinterpret_cast<uintptr_t>(arg));
ProcessInfoReceiverImpl(session);
}
}
NX_CONSTEXPR bool IsApplet(u64 program_id) {
inline constexpr bool IsApplet(const u64 program_id) {
return (0x0100000000001000 <= program_id) && (program_id <= 0x0100000000001FFF);
}
NX_CONSTEXPR bool IsApplication(u64 program_id) {
return (0x0100000000010000 <= program_id); // (For forwarders :p) /* && (program_id <= 0x01FFFFFFFFFFFFFF); */
inline constexpr bool IsApplication(const u64 program_id) {
return (0x0100000000010000 <= program_id);
}
NX_CONSTEXPR bool IsSystemProcess(u64 program_id) {
inline constexpr bool IsSystemProcess(const u64 program_id) {
return (0x0100000000000000 <= program_id) && (program_id <= 0x01000000000007FF);
}
NX_CONSTEXPR AppletType DetectAppletType(u64 program_id) {
inline constexpr AppletType DetectAppletType(const u64 program_id) {
if(IsApplet(program_id)) {
// OverlayApplet and SystemApplet are impossible in this case
return AppletType_LibraryApplet;
@ -92,17 +38,87 @@ namespace {
// hbloader uses this instead of normal Application...
return AppletType_SystemApplication;
}
else {
return AppletType_None;
}
}
inline bool TryParseTargetParamsFromStorage(AppletStorage *st, hb::HbTargetParams &params) {
}
namespace {
Handle g_ProcessHandle = INVALID_HANDLE;
u64 g_ProcessId = 0;
u64 g_ProgramId = 0;
void ProcessInfoReceiver(void *arg) {
const auto session = static_cast<Handle>(reinterpret_cast<uintptr_t>(arg));
auto base = armGetTls();
hipcMakeRequestInline(base);
s32 idx = 0;
svcReplyAndReceive(&idx, &session, 1, INVALID_HANDLE, UINT64_MAX);
const auto request = hipcParseRequest(base);
g_ProcessHandle = request.data.copy_handles[0];
g_ProcessId = request.pid;
svcCloseHandle(session);
}
Result LoadProcessInfo() {
Handle server_h;
Handle client_h;
// Create our own session, and close it on exit if success
UL_RC_TRY(svcCreateSession(&server_h, &client_h, 0, 0));
UL_ON_SCOPE_EXIT({ svcCloseHandle(client_h); });
Thread receiver_thr;
auto thread_arg = reinterpret_cast<void*>(static_cast<uintptr_t>(server_h));
// Create a thread to handle our request, and close it on exit if success
UL_RC_TRY(threadCreate(&receiver_thr, &ProcessInfoReceiver, thread_arg, nullptr, 0x1000, 0x2B, -2));
UL_ON_SCOPE_EXIT({
threadWaitForExit(&receiver_thr);
threadClose(&receiver_thr);
});
UL_RC_TRY(threadStart(&receiver_thr));
hipcMakeRequestInline(armGetTls(),
.send_pid = 1,
.num_copy_handles = 1,
).copy_handles[0] = CUR_PROCESS_HANDLE;
// Will always fail, since we never actually respond to the request
UL_RC_TRY(svcSendSyncRequest(client_h));
return ResultSuccess;
}
Result LoadProgramId() {
if(R_SUCCEEDED(svcGetInfo(&g_ProgramId, InfoType_ProgramId, CUR_PROCESS_HANDLE, 0))) {
return ResultSuccess;
}
// Let's try with pminfo
UL_RC_TRY(pminfoInitialize());
UL_ON_SCOPE_EXIT({ pminfoExit(); });
UL_RC_TRY(pminfoGetProgramId(&g_ProgramId, g_ProcessId));
return ResultSuccess;
}
}
namespace {
bool TryParseTargetParamsFromStorage(AppletStorage *st, hb::HbTargetParams &params) {
// Ensure size is correct
s64 st_size = 0;
R_TRY(appletStorageGetSize(st, &st_size));
UL_RC_TRY(appletStorageGetSize(st, &st_size));
hb::HbTargetParams tmp_params;
if(static_cast<u64>(st_size) >= sizeof(tmp_params)) {
// Read params
R_TRY(appletStorageRead(st, 0, &tmp_params, sizeof(tmp_params)));
UL_RC_TRY(appletStorageRead(st, 0, &tmp_params, sizeof(tmp_params)));
if(tmp_params.magic == UL_HB_HBTARGET_MAGIC_U32) {
params = tmp_params;
return true;
@ -111,65 +127,51 @@ namespace {
return false;
}
bool ParseTargetParams(hb::HbTargetParams &params, u64 cur_program_id, int argc, char **argv) {
bool ret = false;
bool ParseTargetParams(const u64 cur_program_id, const int argc, char **argv, hb::HbTargetParams &out_params) {
auto ret = false;
// Load arguments from applet storages
if(IsApplet(cur_program_id)) {
// Initialize sm, initialize applet, exit sm
R_TRY(DoWithSmSession([&]() {
R_TRY(appletInitialize());
return ResultSuccess;
}));
UL_RC_TRY(appletInitialize());
// Ensure applet is exited in the end
UL_ON_SCOPE_EXIT({
appletExit();
});
UL_ON_SCOPE_EXIT({ appletExit(); });
// We don't make use of the common args storage
AppletStorage common_args_st;
R_TRY(appletPopInData(&common_args_st));
UL_RC_TRY(appletPopInData(&common_args_st));
appletStorageClose(&common_args_st);
// Get our storage
AppletStorage hbtarget_st;
R_TRY(appletPopInData(&hbtarget_st));
UL_ON_SCOPE_EXIT({
appletStorageClose(&hbtarget_st);
});
UL_RC_TRY(appletPopInData(&hbtarget_st));
UL_ON_SCOPE_EXIT({ appletStorageClose(&hbtarget_st); });
// Try parse params
ret = TryParseTargetParamsFromStorage(&hbtarget_st, params);
ret = TryParseTargetParamsFromStorage(&hbtarget_st, out_params);
}
// Load arguments from application launch parameter
else if(IsApplication(cur_program_id)) {
// Initialize sm, initialize applet, exit sm
R_TRY(DoWithSmSession([&]() {
R_TRY(appletInitialize());
return ResultSuccess;
}));
UL_RC_TRY(appletInitialize());
// Ensure applet is exited in the end
UL_ON_SCOPE_EXIT({
appletExit();
});
UL_ON_SCOPE_EXIT({ appletExit(); });
// Get our storage from user arguments
AppletStorage hbtarget_st;
R_TRY(appletPopLaunchParameter(&hbtarget_st, AppletLaunchParameterKind_UserChannel));
UL_ON_SCOPE_EXIT({
appletStorageClose(&hbtarget_st);
});
UL_RC_TRY(appletPopLaunchParameter(&hbtarget_st, AppletLaunchParameterKind_UserChannel));
UL_ON_SCOPE_EXIT({ appletStorageClose(&hbtarget_st); });
// Try parse params
ret = TryParseTargetParamsFromStorage(&hbtarget_st, params);
ret = TryParseTargetParamsFromStorage(&hbtarget_st, out_params);
}
// Load arguments from system process argv
else if(IsSystemProcess(cur_program_id)) {
// Check that 4 strings are sent
if(argc >= 4) {
std::string magic = argv[0];
const std::string magic = argv[0];
// NRO paths start with 'sdmc:/' and spaces are replaced with 0xFF
std::string nro_path = argv[1];
@ -178,99 +180,61 @@ namespace {
std::string nro_argv = argv[2];
// This must be '0' or '1'.
std::string target_once = argv[3];
const std::string target_once = argv[3];
// Matches magic?
if(magic == UL_HB_HBTARGET_MAGIC) {
if(!nro_path.empty()) {
if(!nro_argv.empty()) {
bool target_once_v = true;
if((magic == UL_HB_HBTARGET_MAGIC) && !nro_path.empty() && !nro_argv.empty()) {
auto target_once_v = true;
try {
target_once_v = (bool)std::stoi(target_once);
target_once_v = static_cast<bool>(std::stoi(target_once));
std::replace(nro_path.begin(), nro_path.end(), (char)0xFF, ' ');
std::replace(nro_argv.begin(), nro_argv.end(), (char)0xFF, ' ');
std::replace(nro_path.begin(), nro_path.end(), static_cast<char>(0xFF), ' ');
std::replace(nro_argv.begin(), nro_argv.end(), static_cast<char>(0xFF), ' ');
strcpy(params.nro_path, nro_path.c_str());
strcpy(params.nro_argv, nro_argv.c_str());
params.target_once = target_once_v;
strcpy(out_params.nro_path, nro_path.c_str());
strcpy(out_params.nro_argv, nro_argv.c_str());
out_params.target_once = target_once_v;
ret = true;
}
catch(...) {}
}
}
}
}
}
if(ret) {
if(strlen(params.nro_path) == 0) {
if(strlen(out_params.nro_path) == 0) {
ret = false;
}
}
if(ret) {
if(strlen(params.nro_argv) == 0) {
strcpy(params.nro_argv, params.nro_path);
if(strlen(out_params.nro_argv) == 0) {
strcpy(out_params.nro_argv, out_params.nro_path);
}
}
return ret;
}
Result LoadProcessInfo() {
Handle server_h;
Handle client_h;
// Create our own session, and close it on exit if success
R_TRY(svcCreateSession(&server_h, &client_h, 0, 0));
UL_ON_SCOPE_EXIT({
svcCloseHandle(client_h);
});
Thread receiver_thr;
auto thread_arg = reinterpret_cast<void*>(static_cast<uintptr_t>(server_h));
// Create a thread to handle our request, and close it on exit if success
R_TRY(threadCreate(&receiver_thr, &impl::ProcessInfoReceiver, thread_arg, nullptr, 0x1000, 0x2B, -2));
UL_ON_SCOPE_EXIT({
threadWaitForExit(&receiver_thr);
threadClose(&receiver_thr);
});
R_TRY(threadStart(&receiver_thr));
hipcMakeRequestInline(armGetTls(),
.send_pid = 1,
.num_copy_handles = 1,
).copy_handles[0] = CUR_PROCESS_HANDLE;
R_TRY(svcSendSyncRequest(client_h));
return ResultSuccess;
}
Result LoadProgramId() {
return impl::GetProgramIdImpl();
}
Handle GetOwnProcessHandle() {
return impl::g_process_handle;
}
u64 GetOwnProgramId() {
return impl::g_program_id;
}
}
int main(int argc, char **argv) {
// Avoid checking the result (will always fail), we just care about the process handle
LoadProcessInfo();
LoadProgramId();
if(g_ProcessHandle == INVALID_HANDLE) {
return 0;
}
auto program_id = GetOwnProgramId();
auto process_handle = GetOwnProcessHandle();
UL_RC_ASSERT(smInitialize());
UL_RC_ASSERT(LoadProgramId());
__nx_applet_type = DetectAppletType(program_id);
__nx_applet_type = DetectAppletType(g_ProgramId);
hb::HbTargetParams params = {};
auto ok = ParseTargetParams(params, program_id, argc, argv);
if(ok) {
hb_hbl_Target(process_handle, params.nro_path, params.nro_argv, params.target_once);
const auto params_ok = ParseTargetParams(g_ProgramId, argc, argv, params);
smExit();
if(params_ok) {
hb_hbl_Target(g_ProcessHandle, params.nro_path, params.nro_argv, params.target_once);
}
return 0;
}

View file

@ -6,25 +6,23 @@
namespace am {
struct ApplicationSelectedUserArgument {
static constexpr u32 SelectedUserMagic = 0xC79497CA;
static constexpr u32 Magic = 0xC79497CA;
u32 magic;
u8 unk_1;
u8 pad[3];
AccountUid uid;
u8 unk2[0x3E8];
u8 unk_2[0x3E8];
static inline constexpr ApplicationSelectedUserArgument Create(const AccountUid uid) {
ApplicationSelectedUserArgument arg = {};
arg.magic = SelectedUserMagic;
arg.unk_1 = 1;
arg.uid = uid;
return arg;
}
return {
.magic = Magic,
.unk_1 = 1,
.uid = uid
};
static_assert(sizeof(ApplicationSelectedUserArgument) == 0x400, "ApplicationSelectedUserArgument");
}
};
static_assert(sizeof(ApplicationSelectedUserArgument) == 0x400);
bool ApplicationIsActive();
void ApplicationTerminate();

View file

@ -5,9 +5,6 @@
namespace am {
bool LibraryAppletIsActive();
void LibraryAppletSetMenuAppletId(const AppletId id);
AppletId LibraryAppletGetMenuAppletId();
bool LibraryAppletIsMenu();
void LibraryAppletTerminate();
Result LibraryAppletStart(const AppletId id, const u32 la_version, const void *in_data, const size_t in_size);
Result LibraryAppletSend(const void *data, const size_t size);
@ -24,4 +21,16 @@ namespace am {
AppletId LibraryAppletGetId();
bool LibraryAppletIsMenu();
void LibraryAppletSetMenuAppletId(const AppletId id);
AppletId LibraryAppletGetMenuAppletId();
inline void LibraryAppletSetMenuProgramId(const u64 id) {
LibraryAppletSetMenuAppletId(LibraryAppletGetAppletIdForProgramId(id));
}
inline u64 LibraryAppletGetMenuProgramId() {
return LibraryAppletGetProgramIdForAppletId(LibraryAppletGetMenuAppletId());
}
}

View file

@ -15,21 +15,21 @@ namespace cfg {
struct TitleRecord {
std::string json_name; // Empty for non-SD, normal title records
u32 title_type;
TitleType title_type; // Title type
std::string sub_folder; // Empty for root, name for a certain folder
std::string icon; // Custom icon, if specified
u64 app_id; // TitleType::Installed
hb::HbTargetParams nro_target; // TitleType::Homebrew
u64 app_id; // For TitleType::Installed
hb::HbTargetParams nro_target; // For TitleType::Homebrew
// Optional NACP params
std::string name;
std::string author;
std::string version;
inline bool Equals(const TitleRecord &other) {
inline bool Equals(const TitleRecord &other) const {
if(this->title_type == other.title_type) {
switch(static_cast<TitleType>(this->title_type)) {
switch(this->title_type) {
case TitleType::Installed: {
return this->app_id == other.app_id;
}
@ -67,6 +67,10 @@ namespace cfg {
std::string base_name;
std::string path;
ThemeManifest manifest;
inline bool IsDefault() {
return this->base_name.empty();
}
};
struct RecordStrings {
@ -108,7 +112,7 @@ namespace cfg {
std::string str_value;
template<typename T>
bool Get(T &out_t) const {
inline bool Get(T &out_t) const {
switch(this->header.type) {
case ConfigEntryType::Bool: {
if constexpr(std::is_same_v<T, bool>) {
@ -142,7 +146,7 @@ namespace cfg {
}
template<typename T>
bool Set(const T &t) {
inline bool Set(const T &t) {
switch(this->header.type) {
case ConfigEntryType::Bool: {
if constexpr(std::is_same_v<T, bool>) {
@ -188,7 +192,7 @@ namespace cfg {
std::vector<ConfigEntry> entries;
template<typename T>
bool SetEntry(const ConfigEntryId id, const T &t) {
inline bool SetEntry(const ConfigEntryId id, const T &t) {
for(auto &entry : this->entries) {
if(entry.header.id == id) {
return entry.Set(t);
@ -242,12 +246,13 @@ namespace cfg {
}
template<typename T>
bool GetEntry(const ConfigEntryId id, T &out_t) const {
inline bool GetEntry(const ConfigEntryId id, T &out_t) const {
for(const auto &entry : this->entries) {
if(entry.header.id == id) {
return entry.Get(out_t);
}
}
// Default values
switch(id) {
case ConfigEntryId::MenuTakeoverProgramId: {
@ -305,7 +310,7 @@ namespace cfg {
}
};
static constexpr u32 CurrentThemeFormatVersion = 1;
constexpr u32 CurrentThemeFormatVersion = 1;
#define CFG_THEME_DEFAULT "romfs:/default"
#define CFG_LANG_DEFAULT "romfs:/LangDefault.json"
@ -314,17 +319,15 @@ namespace cfg {
TitleList LoadTitleList();
std::vector<TitleRecord> QueryAllHomebrew(const std::string &base = "sdmc:/switch");
void CacheEverything(const std::string &hb_base_path = "sdmc:/switch");
std::string GetRecordIconPath(TitleRecord record);
RecordInformation GetRecordInformation(TitleRecord record);
std::string GetRecordIconPath(const TitleRecord &record);
std::string GetRecordJsonPath(const TitleRecord &record);
RecordInformation GetRecordInformation(const TitleRecord &record);
Theme LoadTheme(const std::string &base_name);
std::vector<Theme> LoadThemes();
std::string GetAssetByTheme(const Theme &base, const std::string &resource_base);
inline bool ThemeIsDefault(const Theme &base) {
return base.base_name.empty();
}
inline std::string GetLanguageJSONPath(const std::string &lang) {
return UL_BASE_SD_DIR "/lang/" + lang + ".json";
}
@ -336,17 +339,21 @@ namespace cfg {
void SaveConfig(const Config &cfg);
void SaveRecord(TitleRecord &record);
void RemoveRecord(TitleRecord &record);
bool MoveRecordTo(TitleList &list, TitleRecord record, const std::string &folder);
TitleFolder &FindFolderByName(TitleList &list, const std::string &name);
void RenameFolder(TitleList &list, const std::string &old_name, const std::string &new_name);
bool ExistsRecord(TitleList &list, TitleRecord record);
void SaveRecord(const TitleRecord &record);
inline std::string GetTitleCacheIconPath(u64 app_id) {
return UL_BASE_SD_DIR "/title/" + util::FormatApplicationId(app_id) + ".jpg";
inline void RemoveRecord(const TitleRecord &record) {
fs::DeleteFile(cfg::GetRecordJsonPath(record));
}
std::string GetNROCacheIconPath(const std::string &path);
bool MoveRecordTo(TitleList &list, const TitleRecord &record, const std::string &folder);
TitleFolder &FindFolderByName(TitleList &list, const std::string &name);
void RenameFolder(TitleList &list, const std::string &old_name, const std::string &new_name);
bool ExistsRecord(const TitleList &list, const TitleRecord &record);
inline std::string GetTitleCacheIconPath(const u64 app_id) {
return UL_TITLE_CACHE_PATH "/" + util::FormatApplicationId(app_id) + ".jpg";
}
std::string GetNroCacheIconPath(const std::string &path);
}

View file

@ -4,7 +4,7 @@
namespace db {
static constexpr u64 HomeMenuSaveDataId = 0x8000000000001010;
constexpr u64 HomeMenuSaveDataId = 0x8000000000001010;
Result Mount();
void Unmount();

View file

@ -8,7 +8,7 @@ constexpr const char PrivateServiceName[] = "ulsf:p";
namespace dmi {
enum class MenuStartMode {
enum class MenuStartMode : u32 {
Invalid,
StartupScreen,
Menu,
@ -21,7 +21,7 @@ namespace dmi {
HomeRequest
};
enum class DaemonMessage {
enum class DaemonMessage : u32 {
Invalid,
SetSelectedUser,
LaunchApplication,
@ -31,7 +31,7 @@ namespace dmi {
LaunchHomebrewApplication,
OpenWebPage,
OpenAlbum,
RestartMenu,
RestartMenu
};
struct DaemonStatus {
@ -41,7 +41,7 @@ namespace dmi {
char fw_version[0x18]; // System version (sent by uDaemon so that it contains Atmosphere/EmuMMC info)
};
using CommandFunction = Result(*)(void*, size_t, bool);
using CommandFunction = Result(*)(void*, const size_t, const bool);
struct CommandCommonHeader {
u32 magic;
@ -53,7 +53,7 @@ namespace dmi {
namespace impl {
using PopStorageFunction = Result(*)(AppletStorage*, bool);
using PopStorageFunction = Result(*)(AppletStorage*, const bool);
using PushStorageFunction = Result(*)(AppletStorage*);
template<PushStorageFunction PushStorageFn>
@ -63,33 +63,35 @@ namespace dmi {
size_t cur_offset;
public:
ScopedStorageWriterBase() : st({}), cur_offset(0) {}
ScopedStorageWriterBase() : st(), cur_offset(0) {}
~ScopedStorageWriterBase() {
UL_ASSERT(this->PushStorage(&this->st));
UL_RC_ASSERT(PushStorage(&this->st));
appletStorageClose(&this->st);
}
Result PushStorage(AppletStorage *st) {
static inline Result PushStorage(AppletStorage *st) {
return PushStorageFn(st);
}
void Initialize(AppletStorage st) {
inline void Initialize(const AppletStorage &st) {
this->st = st;
}
Result PushData(void *data, size_t size) {
inline Result PushData(const void *data, const size_t size) {
if((cur_offset + size) <= CommandStorageSize) {
R_TRY(appletStorageWrite(&this->st, this->cur_offset, data, size));
UL_RC_TRY(appletStorageWrite(&this->st, this->cur_offset, data, size));
this->cur_offset += size;
return ResultSuccess;
}
return 0xBAFF;
else {
return ResultOutOfPushSpace;
}
}
template<typename T>
Result Push(T t) {
return PushData(&t, sizeof(T));
inline Result Push(const T t) {
return this->PushData(&t, sizeof(T));
}
};
@ -100,39 +102,41 @@ namespace dmi {
size_t cur_offset;
public:
ScopedStorageReaderBase() : st({}), cur_offset(0) {}
ScopedStorageReaderBase() : st(), cur_offset(0) {}
~ScopedStorageReaderBase() {
appletStorageClose(&this->st);
}
Result PopStorage(AppletStorage *st, bool wait) {
static inline Result PopStorage(AppletStorage *st, const bool wait) {
return PopStorageFn(st, wait);
}
void Initialize(AppletStorage st) {
inline void Initialize(const AppletStorage &st) {
this->st = st;
}
Result PopData(void *out_data, size_t size) {
inline Result PopData(void *out_data, const size_t size) {
if((cur_offset + size) <= CommandStorageSize) {
R_TRY(appletStorageRead(&this->st, this->cur_offset, out_data, size));
UL_RC_TRY(appletStorageRead(&this->st, this->cur_offset, out_data, size));
this->cur_offset += size;
return ResultSuccess;
}
return 0xBAFF;
else {
return ResultOutOfPopSpace;
}
}
template<typename T>
Result Pop(T &out_t) {
return PopData(&out_t, sizeof(T));
inline Result Pop(T &out_t) {
return this->PopData(std::addressof(out_t), sizeof(T));
}
};
template<typename StorageReader>
inline Result OpenStorageReader(StorageReader &reader, bool wait) {
inline Result OpenStorageReader(StorageReader &reader, const bool wait) {
AppletStorage st = {};
R_TRY(reader.PopStorage(&st, wait));
UL_RC_TRY(StorageReader::PopStorage(&st, wait));
reader.Initialize(st);
return ResultSuccess;
@ -141,62 +145,69 @@ namespace dmi {
template<typename StorageWriter>
inline Result OpenStorageWriter(StorageWriter &writer) {
AppletStorage st = {};
R_TRY(appletCreateStorage(&st, CommandStorageSize));
UL_RC_TRY(appletCreateStorage(&st, CommandStorageSize));
writer.Initialize(st);
return ResultSuccess;
}
template<typename StorageWriter, typename StorageReader, typename MessageType>
Result SendCommandImpl(MessageType msg_type, std::function<Result(StorageWriter&)> push_fn, std::function<Result(StorageReader&)> pop_fn) {
CommandCommonHeader header = { CommandMagic, static_cast<u32>(msg_type) };
inline Result SendCommandImpl(const MessageType msg_type, std::function<Result(StorageWriter&)> push_fn, std::function<Result(StorageReader&)> pop_fn) {
{
const CommandCommonHeader in_header = {
.magic = CommandMagic,
.val = static_cast<u32>(msg_type)
};
StorageWriter writer;
R_TRY(OpenStorageWriter(writer));
R_TRY(writer.Push(header));
UL_RC_TRY(OpenStorageWriter(writer));
UL_RC_TRY(writer.Push(in_header));
R_TRY(push_fn(writer));
UL_RC_TRY(push_fn(writer));
}
{
CommandCommonHeader out_header = {};
StorageReader reader;
R_TRY(OpenStorageReader(reader, true));
R_TRY(reader.Pop(header));
if(header.magic != CommandMagic) {
return 0xB0FA;
UL_RC_TRY(OpenStorageReader(reader, true));
UL_RC_TRY(reader.Pop(out_header));
if(out_header.magic != CommandMagic) {
return ResultInvalidOutHeaderMagic;
}
R_TRY(header.val);
UL_RC_TRY(out_header.val);
R_TRY(pop_fn(reader));
UL_RC_TRY(pop_fn(reader));
}
return ResultSuccess;
}
template<typename StorageWriter, typename StorageReader, typename MessageType>
Result ReceiveCommandImpl(std::function<Result(MessageType, StorageReader&)> pop_fn, std::function<Result(MessageType, StorageWriter&)> push_fn) {
CommandCommonHeader header = {};
inline Result ReceiveCommandImpl(std::function<Result(const MessageType, StorageReader&)> pop_fn, std::function<Result(const MessageType, StorageWriter&)> push_fn) {
CommandCommonHeader in_out_header = {};
auto msg_type = MessageType();
{
StorageReader reader;
R_TRY(OpenStorageReader(reader, false));
R_TRY(reader.Pop(header));
if(header.magic != CommandMagic) {
return 0xBAFA;
UL_RC_TRY(OpenStorageReader(reader, false));
UL_RC_TRY(reader.Pop(in_out_header));
if(in_out_header.magic != CommandMagic) {
return dmi::ResultInvalidInHeaderMagic;
}
msg_type = static_cast<MessageType>(header.val);
header.val = pop_fn(msg_type, reader);
msg_type = static_cast<MessageType>(in_out_header.val);
in_out_header.val = pop_fn(msg_type, reader);
}
{
StorageWriter writer;
R_TRY(OpenStorageWriter(writer));
R_TRY(writer.Push(header));
UL_RC_TRY(OpenStorageWriter(writer));
UL_RC_TRY(writer.Push(in_out_header));
if(R_SUCCEEDED(header.val)) {
R_TRY(push_fn(msg_type, writer));
if(R_SUCCEEDED(in_out_header.val)) {
UL_RC_TRY(push_fn(msg_type, writer));
}
}
@ -207,18 +218,15 @@ namespace dmi {
namespace dmn {
Result PopStorage(AppletStorage *st, bool wait);
Result PopStorage(AppletStorage *st, const bool wait);
Result PushStorage(AppletStorage *st);
using DaemonScopedStorageReader = impl::ScopedStorageReaderBase<&PopStorage>;
using DaemonScopedStorageWriter = impl::ScopedStorageWriterBase<&PushStorage>;
inline Result SendCommand(MenuMessage msg, std::function<Result(DaemonScopedStorageWriter&)> push_fn, std::function<Result(DaemonScopedStorageReader&)> pop_fn) {
return impl::SendCommandImpl(msg, push_fn, pop_fn);
}
// Daemon only receives commands from Menu
inline Result ReceiveCommand(std::function<Result(DaemonMessage, DaemonScopedStorageReader&)> pop_fn, std::function<Result(DaemonMessage, DaemonScopedStorageWriter&)> push_fn) {
inline Result ReceiveCommand(std::function<Result(const DaemonMessage, DaemonScopedStorageReader&)> pop_fn, std::function<Result(const DaemonMessage, DaemonScopedStorageWriter&)> push_fn) {
return impl::ReceiveCommandImpl(pop_fn, push_fn);
}
@ -226,19 +234,16 @@ namespace dmi {
namespace menu {
Result PopStorage(AppletStorage *st, bool wait);
Result PopStorage(AppletStorage *st, const bool wait);
Result PushStorage(AppletStorage *st);
using MenuScopedStorageReader = impl::ScopedStorageReaderBase<&PopStorage>;
using MenuScopedStorageWriter = impl::ScopedStorageWriterBase<&PushStorage>;
inline Result SendCommand(DaemonMessage msg, std::function<Result(MenuScopedStorageWriter&)> push_fn, std::function<Result(MenuScopedStorageReader&)> pop_fn) {
return impl::SendCommandImpl(msg, push_fn, pop_fn);
}
// Menu only sends commands to Daemon
inline Result ReceiveCommand(std::function<Result(MenuMessage, MenuScopedStorageReader&)> pop_fn, std::function<Result(MenuMessage, MenuScopedStorageWriter&)> push_fn) {
return impl::ReceiveCommandImpl(pop_fn, push_fn);
inline Result SendCommand(const DaemonMessage msg, std::function<Result(MenuScopedStorageWriter&)> push_fn, std::function<Result(MenuScopedStorageReader&)> pop_fn) {
return impl::SendCommandImpl(msg, push_fn, pop_fn);
}
}

View file

@ -10,8 +10,10 @@ namespace fs {
if(stat(path.c_str(), &st) == 0) {
return st.st_mode & Mode;
}
else {
return false;
}
}
inline bool ExistsFile(const std::string &path) {
return ExistsBase<S_IFREG>(path);
@ -45,44 +47,56 @@ namespace fs {
remove(path.c_str());
}
inline bool WriteFile(const std::string &path, const void *data, size_t size, bool overwrite) {
inline void CleanDirectory(const std::string &path) {
DeleteDirectory(path);
CreateDirectory(path);
}
inline bool WriteFile(const std::string &path, const void *data, const size_t size, const bool overwrite) {
auto f = fopen(path.c_str(), overwrite ? "wb" : "ab+");
if(f) {
fwrite(data, 1, size, f);
fclose(f);
return true;
}
else {
return false;
}
}
inline bool ReadFile(const std::string &path, void *data, size_t size) {
inline bool ReadFile(const std::string &path, void *data, const size_t size) {
auto f = fopen(path.c_str(), "rb");
if(f) {
fread(data, 1, size, f);
fclose(f);
return true;
}
else {
return false;
}
}
inline size_t GetFileSize(const std::string &path) {
struct stat st;
if(stat(path.c_str(), &st) == 0) {
return st.st_size;
}
else {
return 0;
}
}
#define UL_FS_FOR(dir, name_var, path_var, ...) ({ \
auto dp = opendir(dir.c_str()); \
const std::string dir_str = dir; \
auto dp = opendir(dir_str.c_str()); \
if(dp) { \
while(true) { \
auto dt = readdir(dp); \
if(dt == nullptr) { \
break; \
} \
std::string name_var = dt->d_name; \
std::string path_var = dir + "/" + dt->d_name; \
const std::string name_var = dt->d_name; \
const std::string path_var = dir_str + "/" + dt->d_name; \
__VA_ARGS__ \
} \
closedir(dp); \

View file

@ -29,20 +29,21 @@ namespace hb {
std::string nro_argv_copy = this->nro_argv;
std::replace(nro_argv_copy.begin(), nro_argv_copy.end(), ' ', (char)0xFF);
argv += " " + nro_argv_copy;
u32 num_val = this->target_once ? 1 : 0;
argv += " " + std::to_string(num_val);
const auto target_once_num = this->target_once ? 1 : 0;
argv += " " + std::to_string(target_once_num);
return argv;
}
static inline HbTargetParams Create(const std::string &nro_path, const std::string &nro_argv, bool target_once) {
HbTargetParams params = {};
params.magic = UL_HB_HBTARGET_MAGIC_U32;
static inline HbTargetParams Create(const std::string &nro_path, const std::string &nro_argv, const bool target_once) {
HbTargetParams params = {
.magic = UL_HB_HBTARGET_MAGIC_U32,
.target_once = target_once
};
strcpy(params.nro_path, nro_path.c_str());
strcpy(params.nro_argv, nro_argv.c_str());
params.target_once = target_once;
return params;
}
};
}

File diff suppressed because it is too large Load diff

View file

@ -4,6 +4,7 @@
namespace net {
// TODO: use libnx's nifm stuff?
struct NetworkProfileData {
u8 unk[0x117];
char wifi_name[0x20 + 1];

View file

@ -4,8 +4,8 @@
namespace os {
std::string GetIconCacheImagePath(AccountUid user_id);
Result QuerySystemAccounts(std::vector<AccountUid> &out_accounts, bool dump_icon);
Result GetAccountName(std::string &out_name, AccountUid user_id);
std::string GetIconCacheImagePath(const AccountUid user_id);
Result QuerySystemAccounts(const bool dump_icon, std::vector<AccountUid> &out_accounts);
Result GetAccountName(const AccountUid user_id, std::string &out_name);
}

View file

@ -14,28 +14,25 @@ namespace os {
OverlayAutoBrightnessChanged = 14,
OverlayAirplaneModeChanged = 15,
HomeButtonHold = 16,
OverlayHidden = 17,
OverlayHidden = 17
};
struct SystemAppletMessage {
static constexpr u32 Magic = 0x534D4153; // "SAMS" -> System applet message...?
u32 magic;
u32 unk;
GeneralChannelMessage general_channel_message;
u8 data[0x3F4];
u8 unk_reserved[0x3F4];
static inline constexpr SystemAppletMessage Create(GeneralChannelMessage msg) {
return SystemAppletMessage {
static inline constexpr SystemAppletMessage Create(const GeneralChannelMessage msg) {
return {
.magic = Magic,
.general_channel_message = msg
};
}
};
// 1024 bytes are always sent, so let's read it all.
static_assert(sizeof(SystemAppletMessage) == 0x400, "System applet message");
static_assert(sizeof(SystemAppletMessage) == 0x400);
// TODO: actual names N uses?
enum class AppletMessage : u32 {
@ -47,9 +44,9 @@ namespace os {
BackFromSleep = 26,
ChangeOperationMode = 30,
ChangePerformanceMode = 31,
SdCardOut = 33,
SdCardOut = 33
};
Result PushSystemAppletMessage(SystemAppletMessage msg);
Result PushSystemAppletMessage(const SystemAppletMessage msg);
}

View file

@ -46,6 +46,14 @@ namespace os {
};
constexpr size_t LanguageNameCount = sizeof(LanguageNameList) / sizeof(const char*);
inline SetLanguage GetSystemLanguage() {
u64 lang_code = 0;
auto lang = SetLanguage_ENUS;
setGetLanguageCode(&lang_code);
setMakeLanguage(lang_code, &lang);
return lang;
}
u32 GetBatteryLevel();
bool IsConsoleCharging();
std::string GetCurrentTime();

View file

@ -5,23 +5,6 @@
namespace os {
#define UL_OS_FOR_EACH_APP_RECORD(rec, ...) ({ \
NsApplicationRecord rec = {}; \
s32 offset = 0; \
s32 count = 0; \
while(true) { \
auto rc = nsListApplicationRecord(&rec, 1, offset, &count); \
if(R_FAILED(rc) || (count < 1)) { \
break; \
} \
if(rec.application_id != 0) { \
__VA_ARGS__ \
} \
offset++; \
rec = {}; \
} \
})
constexpr u32 MaxTitleCount = 64000;
std::vector<cfg::TitleRecord> QueryInstalledTitles();

View file

@ -17,9 +17,13 @@
#include <functional>
#include <thread>
// JSON utils
#include <json.hpp>
using JSON = nlohmann::json;
// Library defines
#define UL_BASE_DIR "ulaunch"
#define UL_BASE_SD_DIR "sdmc:/" UL_BASE_DIR
#define UL_DB_MOUNT_NAME "ul_save"
@ -27,21 +31,21 @@ using JSON = nlohmann::json;
#define UL_BASE_DB_DIR UL_DB_MOUNT_PATH UL_BASE_DIR
#define UL_ENTRIES_PATH UL_BASE_SD_DIR "/entries"
#define UL_THEMES_PATH UL_BASE_SD_DIR "/themes"
#define UL_NRO_CACHE_PATH UL_BASE_SD_DIR "/nro"
#define UL_TITLE_CACHE_PATH UL_BASE_SD_DIR "/titles"
#define UL_ASSERTION_LOG_FILE UL_BASE_SD_DIR "/err.log"
#ifndef UL_VERSION
#error uLaunch's build version isn't defined
#endif
// Scope utils
#include <ul_Scope.hpp>
static constexpr size_t RawRGBAScreenBufferSize = 1280 * 720 * 4;
// Size utils
#define STL_FIND_IF(stl_item, var_name, cond) std::find_if(stl_item.begin(), stl_item.end(), [&](const auto &var_name){ return (cond); })
#define STL_FOUND(stl_item, find_ret) (find_ret != stl_item.end())
#define STL_UNWRAP(find_ret) (*find_ret)
#define STL_REMOVE_IF(stl_item, var_name, cond) stl_item.erase(std::remove_if(stl_item.begin(), stl_item.end(), [&](const auto &var_name){ return (cond); }), stl_item.end())
#include <ul_Result.hpp>
static constexpr size_t PlainRgbaScreenBufferSize = 1280 * 720 * 4;
inline constexpr size_t operator ""_KB(unsigned long long n) {
return n * 0x400;
@ -55,36 +59,40 @@ inline constexpr size_t operator ""_GB(unsigned long long n) {
return operator ""_MB(n) * 0x400;
}
#define UL_ASSERTION_LOG_FILE UL_BASE_SD_DIR "/err.log"
// STL utils
inline void NORETURN OnAssertionFailed(const char *log_buf, size_t log_buf_len, const Result rc) {
#define STL_FIND_IF(stl_item, var_name, cond) std::find_if(stl_item.begin(), stl_item.end(), [&](const auto &var_name){ return (cond); })
#define STL_FOUND(stl_item, find_ret) (find_ret != stl_item.end())
#define STL_UNWRAP(find_ret) (*find_ret)
#define STL_REMOVE_IF(stl_item, var_name, cond) stl_item.erase(std::remove_if(stl_item.begin(), stl_item.end(), [&](const auto &var_name){ return (cond); }), stl_item.end())
// Result and assertion utils
#include <ul_Result.hpp>
template<typename ...Args>
inline void NORETURN OnAssertionFailed(const char *log_fmt, Args &&...args) {
// TODO: unique log file for each assertion faial (a la crash report?)
auto log_f = fopen(UL_ASSERTION_LOG_FILE, "wb"); \
if(log_f) {
fwrite(log_buf, 1, log_buf_len, log_f);
fprintf(log_f, log_fmt, args...);
fprintf(log_f, "\n");
fclose(log_f);
}
fatalThrow(rc);
fatalThrow(misc::ResultAssertionFailed);
}
#define UL_ASSERT_LOG_LEN 0x400
#define UL_ASSERT(expr) ({ \
const auto _tmp_rc = (expr); \
#define UL_RC_ASSERT(expr) ({ \
const auto _tmp_rc = res::TransformIntoResult(expr); \
if(R_FAILED(_tmp_rc)) { \
char log_buf[UL_ASSERT_LOG_LEN] = {}; \
const auto log_buf_len = std::sprintf(log_buf, "%s asserted 0x%X...", #expr, _tmp_rc); \
OnAssertionFailed(log_buf, log_buf_len, _tmp_rc); \
OnAssertionFailed("%s asserted 0x%X...", #expr, _tmp_rc); \
} \
})
#define UL_ASSERT_TRUE(expr) ({ \
const auto _tmp_expr = (expr); \
if(!_tmp_expr) { \
char logbuf[UL_ASSERT_LOG_LEN] = {}; \
const auto log_buf_len = sprintf(logbuf, "%s failed...", #expr); \
OnAssertionFailed(logbuf, log_buf_len, 0xDEAD); \
OnAssertionFailed("%s asserted to be false...", #expr); \
} \
})
#define UL_AMS_ASSERT(expr) UL_ASSERT(ams::Result(expr).GetValue())

View file

@ -1,64 +1,94 @@
#pragma once
#include <switch.h>
#include <string>
#ifdef ATMOSPHERE
#include <stratosphere.hpp>
#endif
// All 2380-**** results are from us
constexpr u32 Module = 380;
constexpr u32 SubmoduleOffset = 100;
#define RES_DEFINE_SUBMODULE(val) constexpr u32 Submodule = val
#define RES_DEFINE(name, val) constexpr Result Result ## name = MAKERESULT(Module + Submodule * SubmoduleOffset, val)
#ifndef R_TRY
#define R_TRY(res_expr) ({ \
const auto _tmp_r_try_rc = static_cast<Result>(res_expr); \
if (R_FAILED(_tmp_r_try_rc)) { \
return _tmp_r_try_rc; \
} \
})
#endif
#define UL_RC_DEFINE_SUBMODULE(val) constexpr u32 Submodule = val
#define UL_RC_DEFINE(name, val) constexpr Result Result ## name = MAKERESULT(Module, Submodule * SubmoduleOffset + val)
constexpr Result ResultSuccess = 0;
namespace misc {
RES_DEFINE_SUBMODULE(0);
RES_DEFINE(InvalidJsonFile, 1);
UL_RC_DEFINE_SUBMODULE(0);
UL_RC_DEFINE(AssertionFailed, 1);
UL_RC_DEFINE(UnexpectedTransform, 2);
UL_RC_DEFINE(InvalidJsonFile, 3);
}
namespace dmn {
RES_DEFINE_SUBMODULE(1);
RES_DEFINE(ApplicationActive, 1);
RES_DEFINE(InvalidSelectedUser, 2);
RES_DEFINE(AlreadyQueued, 3);
RES_DEFINE(ApplicationNotActive, 4);
UL_RC_DEFINE_SUBMODULE(1);
UL_RC_DEFINE(ApplicationActive, 1);
UL_RC_DEFINE(InvalidSelectedUser, 2);
UL_RC_DEFINE(AlreadyQueued, 3);
UL_RC_DEFINE(ApplicationNotActive, 4);
}
namespace menu {
RES_DEFINE_SUBMODULE(2);
RES_DEFINE(RomfsFileNotFound, 1);
UL_RC_DEFINE_SUBMODULE(2);
UL_RC_DEFINE(RomfsFileNotFound, 1);
}
namespace ipc {
RES_DEFINE_SUBMODULE(3);
RES_DEFINE(InvalidProcess, 1);
UL_RC_DEFINE_SUBMODULE(3);
UL_RC_DEFINE(InvalidProcess, 1);
}
namespace dmi {
UL_RC_DEFINE_SUBMODULE(4);
UL_RC_DEFINE(OutOfPushSpace, 1);
UL_RC_DEFINE(OutOfPopSpace, 2);
UL_RC_DEFINE(InvalidInHeaderMagic, 3);
UL_RC_DEFINE(InvalidOutHeaderMagic, 4);
UL_RC_DEFINE(WaitTimeout, 5);
}
namespace res {
template<typename T>
inline ::Result TransformIntoResult(const T t) {
return misc::ResultUnexpectedTransform;
}
template<>
inline ::Result TransformIntoResult<::Result>(const ::Result rc) {
return rc;
}
#ifdef ATMOSPHERE
template<>
inline ::Result TransformIntoResult<ams::Result>(const ams::Result ams_rc) {
return ams_rc.GetValue();
}
#endif
Result GetResultByModuleAndName(const std::string &mod, const std::string &name);
std::string GetModuleByResult(const Result rc);
std::string GetNameByResult(const Result rc);
}
#define UL_RC_TRY(res_expr) ({ \
const auto _tmp_rc = res::TransformIntoResult(res_expr); \
if (R_FAILED(_tmp_rc)) { \
return _tmp_rc; \
} \
})

View file

@ -1,9 +1,9 @@
#pragma once
#include <switch.h>
#include <functional>
class OnScopeExit {
public:
using Fn = std::function<void()>;
@ -16,7 +16,6 @@ class OnScopeExit {
~OnScopeExit() {
(this->exit_fn)();
}
};
#define UL_CONCAT_IMPL(x,y) x##y
@ -24,3 +23,17 @@ class OnScopeExit {
#define UL_UNIQUE_VAR_NAME(prefix) UL_CONCAT(prefix ## _, __COUNTER__)
#define UL_ON_SCOPE_EXIT(...) OnScopeExit UL_UNIQUE_VAR_NAME(on_scope_exit) ([&]() { __VA_ARGS__ })
class ScopedLock {
private:
Mutex &lock;
public:
ScopedLock(Mutex &lock) : lock(lock) {
mutexLock(std::addressof(lock));
}
~ScopedLock() {
mutexUnlock(std::addressof(this->lock));
}
};

View file

@ -37,24 +37,24 @@ namespace am {
appletApplicationClose(&g_ApplicationHolder);
if(system) {
R_TRY(appletCreateSystemApplication(&g_ApplicationHolder, app_id));
UL_RC_TRY(appletCreateSystemApplication(&g_ApplicationHolder, app_id));
}
else {
R_TRY(appletCreateApplication(&g_ApplicationHolder, app_id));
UL_RC_TRY(appletCreateApplication(&g_ApplicationHolder, app_id));
}
if(accountUidIsValid(&user_id)) {
auto selected_user_arg = ApplicationSelectedUserArgument::Create(user_id);
R_TRY(ApplicationSend(&selected_user_arg, sizeof(selected_user_arg), AppletLaunchParameterKind_PreselectedUser));
const auto selected_user_arg = ApplicationSelectedUserArgument::Create(user_id);
UL_RC_TRY(ApplicationSend(&selected_user_arg, sizeof(selected_user_arg), AppletLaunchParameterKind_PreselectedUser));
}
if(size > 0) {
R_TRY(ApplicationSend(data, size));
UL_RC_TRY(ApplicationSend(data, size));
}
R_TRY(appletUnlockForeground());
R_TRY(appletApplicationStart(&g_ApplicationHolder));
R_TRY(ApplicationSetForeground());
UL_RC_TRY(appletUnlockForeground());
UL_RC_TRY(appletApplicationStart(&g_ApplicationHolder));
UL_RC_TRY(ApplicationSetForeground());
g_LastApplicationId = app_id;
return 0;
@ -65,19 +65,18 @@ namespace am {
}
Result ApplicationSetForeground() {
R_TRY(appletApplicationRequestForApplicationToGetForeground(&g_ApplicationHolder));
UL_RC_TRY(appletApplicationRequestForApplicationToGetForeground(&g_ApplicationHolder));
g_DaemonHasFocus = false;
return ResultSuccess;
}
Result ApplicationSend(const void *data, const size_t size, const AppletLaunchParameterKind kind) {
AppletStorage st;
R_TRY(appletCreateStorage(&st, size));
UL_ON_SCOPE_EXIT({
appletStorageClose(&st);
});
R_TRY(appletStorageWrite(&st, 0, data, size));
R_TRY(appletApplicationPushLaunchParameter(&g_ApplicationHolder, kind, &st));
UL_RC_TRY(appletCreateStorage(&st, size));
UL_ON_SCOPE_EXIT({ appletStorageClose(&st); });
UL_RC_TRY(appletStorageWrite(&st, 0, data, size));
UL_RC_TRY(appletApplicationPushLaunchParameter(&g_ApplicationHolder, kind, &st));
return ResultSuccess;
}
@ -86,8 +85,11 @@ namespace am {
}
bool ApplicationNeedsUser(const u64 app_id) {
NsApplicationControlData ctdata = {};
nsGetApplicationControlData(NsApplicationControlSource_Storage, app_id, &ctdata, sizeof(ctdata), nullptr);
return ctdata.nacp.startup_user_account > 0;
auto control_data = new NsApplicationControlData();
nsGetApplicationControlData(NsApplicationControlSource_Storage, app_id, control_data, sizeof(NsApplicationControlData), nullptr);
const auto needs_user = control_data->nacp.startup_user_account > 0;
delete control_data;
return needs_user;
}
}

View file

@ -1,5 +1,4 @@
#include <am/am_HomeMenu.hpp>
#include <am/am_LibraryApplet.hpp>
namespace am {
@ -10,7 +9,7 @@ namespace am {
}
Result HomeMenuSetForeground() {
R_TRY(appletRequestToGetForeground());
UL_RC_TRY(appletRequestToGetForeground());
g_DaemonHasFocus = true;
return ResultSuccess;
}

View file

@ -46,18 +46,6 @@ namespace am {
return !appletHolderCheckFinished(&g_AppletHolder);
}
void LibraryAppletSetMenuAppletId(AppletId id) {
g_MenuAppletId = id;
}
AppletId LibraryAppletGetMenuAppletId() {
return g_MenuAppletId;
}
bool LibraryAppletIsMenu() {
return LibraryAppletIsActive() && (g_MenuAppletId != AppletId_None) && (LibraryAppletGetId() == g_MenuAppletId);
}
void LibraryAppletTerminate() {
// Give it 15 seconds
appletHolderRequestExitOrTerminate(&g_AppletHolder, 15'000'000'000ul);
@ -68,14 +56,15 @@ namespace am {
LibraryAppletTerminate();
}
appletHolderClose(&g_AppletHolder);
LibAppletArgs la_args;
libappletArgsCreate(&la_args, la_version);
R_TRY(appletCreateLibraryApplet(&g_AppletHolder, id, LibAppletMode_AllForeground));
R_TRY(libappletArgsPush(&la_args, &g_AppletHolder));
UL_RC_TRY(appletCreateLibraryApplet(&g_AppletHolder, id, LibAppletMode_AllForeground));
UL_RC_TRY(libappletArgsPush(&la_args, &g_AppletHolder));
if(in_size > 0) {
R_TRY(LibraryAppletSend(in_data, in_size));
UL_RC_TRY(LibraryAppletSend(in_data, in_size));
}
R_TRY(appletHolderStart(&g_AppletHolder));
UL_RC_TRY(appletHolderStart(&g_AppletHolder));
g_LastAppletId = id;
return ResultSuccess;
}
@ -125,4 +114,17 @@ namespace am {
}
return last_id_copy;
}
bool LibraryAppletIsMenu() {
return LibraryAppletIsActive() && (g_MenuAppletId != AppletId_None) && (LibraryAppletGetId() == g_MenuAppletId);
}
void LibraryAppletSetMenuAppletId(const AppletId id) {
g_MenuAppletId = id;
}
AppletId LibraryAppletGetMenuAppletId() {
return g_MenuAppletId;
}
}

View file

@ -8,23 +8,23 @@ namespace cfg {
namespace {
void CacheHomebrew(const std::string &nro_path) {
auto nroimg = GetNROCacheIconPath(nro_path);
void DoCacheHomebrew(const std::string &nro_path) {
const auto cache_nro_icon_path = GetNroCacheIconPath(nro_path);
auto f = fopen(nro_path.c_str(), "rb");
if(f) {
fseek(f, sizeof(NroStart), SEEK_SET);
NroHeader hdr = {};
if(fread(&hdr, sizeof(hdr), 1, f) == 1) {
fseek(f, hdr.size, SEEK_SET);
NroAssetHeader ahdr = {};
if(fread(&ahdr, sizeof(ahdr), 1, f) == 1) {
if(ahdr.magic == NROASSETHEADER_MAGIC) {
if((ahdr.icon.offset > 0) && (ahdr.icon.size > 0)) {
auto iconbuf = new u8[ahdr.icon.size]();
fseek(f, hdr.size + ahdr.icon.offset, SEEK_SET);
fread(iconbuf, ahdr.icon.size, 1, f);
fs::WriteFile(nroimg, iconbuf, ahdr.icon.size, true);
delete[] iconbuf;
NroHeader nro_header = {};
if(fread(&nro_header, sizeof(nro_header), 1, f) == 1) {
fseek(f, nro_header.size, SEEK_SET);
NroAssetHeader asset_header = {};
if(fread(&asset_header, sizeof(asset_header), 1, f) == 1) {
if(asset_header.magic == NROASSETHEADER_MAGIC) {
if((asset_header.icon.offset > 0) && (asset_header.icon.size > 0)) {
auto icon_buf = new u8[asset_header.icon.size]();
fseek(f, nro_header.size + asset_header.icon.offset, SEEK_SET);
fread(icon_buf, asset_header.icon.size, 1, f);
fs::WriteFile(cache_nro_icon_path, icon_buf, asset_header.icon.size, true);
delete[] icon_buf;
}
}
}
@ -33,46 +33,46 @@ namespace cfg {
}
}
void CacheInstalledTitles() {
UL_OS_FOR_EACH_APP_RECORD(rec, {
auto fname = cfg::GetTitleCacheIconPath(rec.application_id);
if(!fs::ExistsFile(fname)) {
NsApplicationControlData control = {};
rc = nsGetApplicationControlData(NsApplicationControlSource_Storage, rec.application_id, &control, sizeof(control), nullptr);
if(R_SUCCEEDED(rc)) {
auto fname = cfg::GetTitleCacheIconPath(rec.application_id);
fs::WriteFile(fname, control.icon, sizeof(control.icon), true);
}
}
});
}
void CacheAllHomebrew(const std::string &hb_base_path) {
void CacheHomebrew(const std::string &hb_base_path) {
UL_FS_FOR(hb_base_path, name, path, {
if(dt->d_type & DT_DIR) {
CacheAllHomebrew(path);
CacheHomebrew(path);
}
else if(util::StringEndsWith(name, ".nro")) {
CacheHomebrew(path);
DoCacheHomebrew(path);
}
});
}
void ProcessStringsFromNACP(RecordStrings &strs, NacpStruct *nacp) {
NacpLanguageEntry *lent = nullptr;
nacpGetLanguageEntry(nacp, &lent);
if(lent == nullptr) {
void CacheInstalledTitles() {
const auto titles = os::QueryInstalledTitles();
auto control_data = new NsApplicationControlData();
for(const auto &title: titles) {
const auto cache_icon_path = cfg::GetTitleCacheIconPath(title.app_id);
if(R_SUCCEEDED(nsGetApplicationControlData(NsApplicationControlSource_Storage, title.app_id, control_data, sizeof(NsApplicationControlData), nullptr))) {
fs::WriteFile(cache_icon_path, control_data->icon, sizeof(control_data->icon), true);
}
}
delete control_data;
}
void ProcessStringsFromNacp(RecordStrings &strs, NacpStruct *nacp) {
NacpLanguageEntry *lang_entry = nullptr;
nacpGetLanguageEntry(nacp, &lang_entry);
if(lang_entry == nullptr) {
for(u32 i = 0; i < 16; i++) {
lent = &nacp->lang[i];
if((strlen(lent->name) > 0) && (strlen(lent->author) > 0)) {
lang_entry = &nacp->lang[i];
if((strlen(lang_entry->name) > 0) && (strlen(lang_entry->author) > 0)) {
break;
}
lent = nullptr;
lang_entry = nullptr;
}
}
if(lent != nullptr) {
strs.name = lent->name;
strs.author = lent->author;
if(lang_entry != nullptr) {
strs.name = lang_entry->name;
strs.author = lang_entry->author;
strs.version = nacp->display_version;
}
}
@ -83,7 +83,7 @@ namespace cfg {
std::vector<TitleRecord> nros;
UL_FS_FOR(base, name, path, {
if(dt->d_type & DT_DIR) {
auto hb = QueryAllHomebrew(path);
const auto hb = QueryAllHomebrew(path);
if(!hb.empty()) {
nros.insert(nros.begin(), hb.begin(), hb.end());
}
@ -91,7 +91,7 @@ namespace cfg {
else {
if(util::StringEndsWith(name, ".nro")) {
TitleRecord rec = {};
rec.title_type = (u32)TitleType::Homebrew;
rec.title_type = TitleType::Homebrew;
strcpy(rec.nro_target.nro_path, path.c_str());
nros.push_back(rec);
}
@ -101,29 +101,44 @@ namespace cfg {
}
void CacheEverything(const std::string &hb_base_path) {
fs::CleanDirectory(UL_TITLE_CACHE_PATH);
CacheInstalledTitles();
CacheAllHomebrew(hb_base_path);
fs::CleanDirectory(UL_NRO_CACHE_PATH);
CacheHomebrew(hb_base_path);
}
std::string GetRecordIconPath(TitleRecord record) {
std::string icon = record.icon;
if(icon.empty()) {
const auto type = static_cast<TitleType>(record.title_type);
if(type == TitleType::Homebrew) {
icon = GetNROCacheIconPath(record.nro_target.nro_path);
std::string GetRecordIconPath(const TitleRecord &record) {
auto icon_path = record.icon;
if(icon_path.empty()) {
if(record.title_type == TitleType::Homebrew) {
icon_path = GetNroCacheIconPath(record.nro_target.nro_path);
}
else if(type == TitleType::Installed) {
icon = GetTitleCacheIconPath(record.app_id);
else if(record.title_type == TitleType::Installed) {
icon_path = GetTitleCacheIconPath(record.app_id);
}
}
return icon;
return icon_path;
}
RecordInformation GetRecordInformation(TitleRecord record) {
std::string GetRecordJsonPath(const TitleRecord &record) {
auto json_name = record.json_name;
if(json_name.empty()) {
if(record.title_type == TitleType::Homebrew) {
json_name = std::to_string(fs::GetFileSize(record.nro_target.nro_path)) + ".json";
}
else if(record.title_type == TitleType::Installed) {
const auto app_id_str = util::FormatApplicationId(record.app_id);
json_name = app_id_str + ".json";
}
}
return UL_ENTRIES_PATH "/" + json_name;
}
RecordInformation GetRecordInformation(const TitleRecord &record) {
RecordInformation info = {};
info.icon_path = GetRecordIconPath(record);
const auto type = static_cast<TitleType>(record.title_type);
if(type == TitleType::Homebrew) {
if(record.title_type == TitleType::Homebrew) {
auto f = fopen(record.nro_target.nro_path, "rb");
if(f) {
fseek(f, sizeof(NroStart), SEEK_SET);
@ -137,7 +152,7 @@ namespace cfg {
NacpStruct nacp = {};
fseek(f, hdr.size + ahdr.nacp.offset, SEEK_SET);
fread(&nacp, 1, ahdr.nacp.size, f);
ProcessStringsFromNACP(info.strings, &nacp);
ProcessStringsFromNacp(info.strings, &nacp);
}
}
}
@ -146,9 +161,10 @@ namespace cfg {
}
}
else {
NsApplicationControlData cdata = {};
nsGetApplicationControlData(NsApplicationControlSource_Storage, record.app_id, &cdata, sizeof(cdata), nullptr);
ProcessStringsFromNACP(info.strings, &cdata.nacp);
auto control_data = new NsApplicationControlData();
nsGetApplicationControlData(NsApplicationControlSource_Storage, record.app_id, control_data, sizeof(NsApplicationControlData), nullptr);
ProcessStringsFromNacp(info.strings, &control_data->nacp);
delete control_data;
}
if(!record.name.empty()) {
info.strings.name = record.name;
@ -163,32 +179,34 @@ namespace cfg {
}
Theme LoadTheme(const std::string &base_name) {
Theme theme = {};
theme.base_name = base_name;
auto themedir = std::string(UL_THEMES_PATH) + "/" + base_name;
auto metajson = themedir + "/theme/Manifest.json";
if(base_name.empty() || !fs::ExistsFile(metajson)) {
themedir = CFG_THEME_DEFAULT;
Theme theme = {
.base_name = base_name
};
auto theme_dir = UL_THEMES_PATH "/" + base_name;
auto manifest_path = theme_dir + "/theme/Manifest.json";
if(base_name.empty() || !fs::ExistsFile(manifest_path)) {
theme_dir = CFG_THEME_DEFAULT;
}
metajson = themedir + "/theme/Manifest.json";
JSON meta;
auto rc = util::LoadJSONFromFile(meta, metajson);
if(R_SUCCEEDED(rc)) {
theme.manifest.name = meta.value("name", "'" + base_name + "'");
theme.manifest.format_version = meta.value("format_version", 0);
theme.manifest.release = meta.value("release", "");
theme.manifest.description = meta.value("description", "");
theme.manifest.author = meta.value("author", "");
theme.path = themedir;
manifest_path = theme_dir + "/theme/Manifest.json";
JSON manifest_json;
if(R_SUCCEEDED(util::LoadJSONFromFile(manifest_json, manifest_path))) {
theme.manifest.name = manifest_json.value("name", "'" + base_name + "'");
theme.manifest.format_version = manifest_json.value("format_version", 0);
theme.manifest.release = manifest_json.value("release", "");
theme.manifest.description = manifest_json.value("description", "");
theme.manifest.author = manifest_json.value("author", "");
theme.path = theme_dir;
return theme;
}
return LoadTheme("");
}
std::vector<Theme> LoadThemes() {
std::vector<Theme> themes;
UL_FS_FOR(std::string(UL_THEMES_PATH), name, path, {
auto theme = LoadTheme(name);
UL_FS_FOR(UL_THEMES_PATH, name, path, {
const auto theme = LoadTheme(name);
if(!theme.path.empty()) {
themes.push_back(theme);
}
@ -201,10 +219,12 @@ namespace cfg {
if(fs::ExistsFile(base_res)) {
return base_res;
}
base_res = std::string(CFG_THEME_DEFAULT) + "/" + resource_base;
base_res = CFG_THEME_DEFAULT "/" + resource_base;
if(fs::ExistsFile(base_res)) {
return base_res;
}
return "";
}
@ -305,9 +325,9 @@ namespace cfg {
}
}
void SaveRecord(TitleRecord &record) {
void SaveRecord(const TitleRecord &record) {
auto entry = JSON::object();
entry["type"] = record.title_type;
entry["type"] = static_cast<u32>(record.title_type);
entry["folder"] = record.sub_folder;
if(!record.name.empty()) {
@ -323,16 +343,7 @@ namespace cfg {
entry["icon"] = record.icon;
}
// Prepare JSON path
std::string basepath = UL_ENTRIES_PATH;
std::string json = basepath;
const auto type = static_cast<TitleType>(record.title_type);
if(type == TitleType::Homebrew) {
auto jsonname = std::to_string(fs::GetFileSize(record.nro_target.nro_path)) + ".json";
if(record.json_name.empty()) {
record.json_name = jsonname;
}
json += "/" + jsonname;
if(record.title_type == TitleType::Homebrew) {
entry["nro_path"] = record.nro_target.nro_path;
if(strlen(record.nro_target.nro_argv)) {
if(strcasecmp(record.nro_target.nro_path, record.nro_target.nro_argv) != 0) {
@ -340,147 +351,50 @@ namespace cfg {
}
}
}
else if(type == TitleType::Installed) {
auto strappid = util::FormatApplicationId(record.app_id);
auto jsonname = strappid + ".json";
if(record.json_name.empty()) {
record.json_name = jsonname;
}
json += "/" + jsonname;
entry["application_id"] = strappid;
}
if(!record.json_name.empty()) {
json = basepath + "/" + record.json_name;
}
if(fs::ExistsFile(json)) {
fs::DeleteFile(json);
else if(record.title_type == TitleType::Installed) {
entry["application_id"] = util::FormatApplicationId(record.app_id);
}
std::ofstream ofs(json);
const auto json_path = GetRecordJsonPath(record);
if(fs::ExistsFile(json_path)) {
fs::DeleteFile(json_path);
}
std::ofstream ofs(json_path);
ofs << std::setw(4) << entry;
ofs.close();
}
void RemoveRecord(TitleRecord &record) {
// Prepare JSON path
std::string basepath = UL_ENTRIES_PATH;
std::string json = basepath;
const auto type = static_cast<TitleType>(record.title_type);
if(type == TitleType::Homebrew) {
auto jsonname = std::to_string(fs::GetFileSize(record.nro_target.nro_path)) + ".json";
if(record.json_name.empty()) {
record.json_name = jsonname;
}
json += "/" + jsonname;
}
else if(type == TitleType::Installed) {
auto strappid = util::FormatApplicationId(record.app_id);
auto jsonname = strappid + ".json";
if(record.json_name.empty()) {
record.json_name = jsonname;
}
json += "/" + jsonname;
}
if(!record.json_name.empty()) {
json = basepath + "/" + record.json_name;
}
fs::DeleteFile(json);
}
bool MoveTitleToDirectory(TitleList &list, u64 app_id, const std::string &folder) {
bool MoveRecordTo(TitleList &list, const TitleRecord &record, const std::string &folder_name) {
bool title_found = false;
TitleRecord record_copy = {};
std::string recjson;
std::string record_json_name;
// Search in root first
auto find = STL_FIND_IF(list.root.titles, tit, (tit.app_id == app_id));
if(STL_FOUND(list.root.titles, find)) {
if(folder.empty()) return true; // It is already on root...?
recjson = STL_UNWRAP(find).json_name;
list.root.titles.erase(find);
title_found = true;
}
// If not found yet, search on all dirs if the title is present
if(!title_found) {
for(auto &fld: list.folders) {
auto find = STL_FIND_IF(fld.titles, entry, (entry.app_id == app_id));
if(STL_FOUND(fld.titles, find)) {
// It is already on that folder...?
if(fld.name == folder) {
return true;
}
recjson = STL_UNWRAP(find).json_name;
fld.titles.erase(find);
title_found = true;
break;
}
}
}
if(title_found) {
TitleRecord title = {};
title.app_id = app_id;
title.title_type = (u32)TitleType::Installed;
title.json_name = recjson;
title.sub_folder = folder;
// Add (move) it to root again
if(folder.empty()) {
list.root.titles.push_back(title);
}
// Add it to the new folder
else {
auto find2 = STL_FIND_IF(list.folders, fld, (fld.name == folder));
if(STL_FOUND(list.folders, find2)) {
STL_UNWRAP(find2).titles.push_back(title);
}
else {
TitleFolder fld = {};
fld.name = folder;
fld.titles.push_back(title);
list.folders.push_back(fld);
}
}
SaveRecord(title);
}
return title_found;
}
bool MoveRecordTo(TitleList &list, TitleRecord record, const std::string &folder) {
bool title_found = false;
TitleRecord record_copy = {};
std::string recjson;
// Search in root first
auto find = STL_FIND_IF(list.root.titles, tit, record.Equals(tit));
if(STL_FOUND(list.root.titles, find)) {
const auto find_in_root = STL_FIND_IF(list.root.titles, title_item, record.Equals(title_item));
if(STL_FOUND(list.root.titles, find_in_root)) {
// It is already on root...?
if(folder.empty()) {
if(folder_name.empty()) {
return true;
}
recjson = STL_UNWRAP(find).json_name;
record_json_name = STL_UNWRAP(find_in_root).json_name;
list.root.titles.erase(find);
list.root.titles.erase(find_in_root);
title_found = true;
}
// If not found yet, search on all dirs if the title is present
if(!title_found) {
for(auto &fld: list.folders) {
auto find = STL_FIND_IF(fld.titles, tit, record.Equals(tit));
if(STL_FOUND(fld.titles, find)) {
for(auto &folder: list.folders) {
const auto find_in_folder = STL_FIND_IF(folder.titles, title_item, record.Equals(title_item));
if(STL_FOUND(folder.titles, find_in_folder)) {
// It is already on that folder...?
if(fld.name == folder) {
if(folder.name == folder_name) {
return true;
}
recjson = STL_UNWRAP(find).json_name;
record_json_name = STL_UNWRAP(find_in_folder).json_name;
fld.titles.erase(find);
folder.titles.erase(find_in_folder);
title_found = true;
break;
}
@ -489,24 +403,24 @@ namespace cfg {
if(title_found) {
TitleRecord title = record;
title.json_name = recjson;
title.sub_folder = folder;
title.json_name = record_json_name;
title.sub_folder = folder_name;
// Add (move) it to root again
if(folder.empty()) {
if(folder_name.empty()) {
list.root.titles.push_back(title);
}
// Add it to the new folder
else {
auto find2 = STL_FIND_IF(list.folders, fld, (fld.name == folder));
if(STL_FOUND(list.folders, find2)) {
STL_UNWRAP(find2).titles.push_back(title);
const auto find_folder = STL_FIND_IF(list.folders, folder_item, (folder_item.name == folder_name));
if(STL_FOUND(list.folders, find_folder)) {
STL_UNWRAP(find_folder).titles.push_back(title);
}
else {
TitleFolder fld = {};
fld.name = folder;
fld.titles.push_back(title);
list.folders.push_back(fld);
TitleFolder folder = {};
folder.name = folder_name;
folder.titles.push_back(title);
list.folders.push_back(folder);
}
}
@ -537,25 +451,25 @@ namespace cfg {
}
}
bool ExistsRecord(TitleList &list, TitleRecord record) {
bool ExistsRecord(const TitleList &list, const TitleRecord &record) {
auto title_found = false;
TitleRecord record_copy = {};
std::string recjson;
std::string record_json_name;
// Search in root first
auto find = STL_FIND_IF(list.root.titles, tit, record.Equals(tit));
if(STL_FOUND(list.root.titles, find)) {
if(!STL_UNWRAP(find).json_name.empty()) {
const auto find_in_root = STL_FIND_IF(list.root.titles, title_item, record.Equals(title_item));
if(STL_FOUND(list.root.titles, find_in_root)) {
if(!STL_UNWRAP(find_in_root).json_name.empty()) {
title_found = true;
}
}
// If not found yet, search on all dirs if the title is present
if(!title_found) {
for(auto &fld: list.folders) {
auto find = STL_FIND_IF(fld.titles, tit, record.Equals(tit));
if(STL_FOUND(fld.titles, find)) {
if(!STL_UNWRAP(find).json_name.empty()) {
for(auto &folder: list.folders) {
const auto find_in_folder = STL_FIND_IF(folder.titles, title_item, record.Equals(title_item));
if(STL_FOUND(folder.titles, find_in_folder)) {
if(!STL_UNWRAP(find_in_folder).json_name.empty()) {
title_found = true;
break;
}
@ -572,78 +486,84 @@ namespace cfg {
// Installed titles first
auto titles = os::QueryInstalledTitles();
UL_FS_FOR(std::string(UL_ENTRIES_PATH), name, path, {
UL_FS_FOR(UL_ENTRIES_PATH, name, path, {
JSON entry;
auto rc = util::LoadJSONFromFile(entry, path);
const auto rc = util::LoadJSONFromFile(entry, path);
if(R_SUCCEEDED(rc)) {
auto type = static_cast<TitleType>(entry.value("type", 0));
const auto type = static_cast<TitleType>(entry.value("type", static_cast<u32>(TitleType::Invalid)));
if(type == TitleType::Installed) {
std::string appidstr = entry.value("application_id", "");
if(!appidstr.empty()) {
std::string folder = entry.value("folder", "");
auto appid = util::Get64FromString(appidstr);
if(appid > 0) {
const std::string app_id_str = entry.value("application_id", "");
if(!app_id_str.empty()) {
const std::string folder = entry.value("folder", "");
const auto app_id = util::Get64FromString(app_id_str);
if(app_id > 0) {
if(!folder.empty()) {
TitleRecord rec = {};
rec.json_name = name;
rec.app_id = appid;
rec.title_type = static_cast<u32>(TitleType::Installed);
rec.name = entry.value("name", "");
rec.author = entry.value("author", "");
rec.version = entry.value("version", "");
rec.icon = entry.value("icon", "");
TitleRecord rec = {
.json_name = name,
.title_type = type,
.sub_folder = folder,
.icon = entry.value("icon", ""),
.app_id = app_id,
.name = entry.value("name", ""),
.author = entry.value("author", ""),
.version = entry.value("version", "")
};
auto find = STL_FIND_IF(titles, tit, (tit.app_id == appid));
if(STL_FOUND(titles, find)) {
titles.erase(find);
const auto find_title = STL_FIND_IF(titles, title_item, title_item.app_id == app_id);
if(STL_FOUND(titles, find_title)) {
titles.erase(find_title);
}
auto find2 = STL_FIND_IF(list.folders, fld, (fld.name == folder));
if(STL_FOUND(list.folders, find2)) {
STL_UNWRAP(find2).titles.push_back(rec);
const auto find_folder = STL_FIND_IF(list.folders, folder_item, folder_item.name == folder);
if(STL_FOUND(list.folders, find_folder)) {
STL_UNWRAP(find_folder).titles.push_back(rec);
}
else {
TitleFolder fld = {};
fld.name = folder;
fld.titles.push_back(rec);
list.folders.push_back(fld);
TitleFolder title_folder = {
.name = folder
};
title_folder.titles.push_back(rec);
list.folders.push_back(title_folder);
}
}
}
}
}
else if(type == TitleType::Homebrew) {
std::string nropath = entry.value("nro_path", "");
if(fs::ExistsFile(nropath)) {
TitleRecord rec = {};
rec.json_name = name;
rec.title_type = (u32)type;
rec.name = entry.value("name", "");
rec.author = entry.value("author", "");
rec.version = entry.value("version", "");
std::string argv = entry.value("nro_argv", "");
strcpy(rec.nro_target.nro_path, nropath.c_str());
const std::string nro_path = entry.value("nro_path", "");
if(fs::ExistsFile(nro_path)) {
const std::string folder = entry.value("folder", "");
TitleRecord rec = {
.json_name = name,
.title_type = type,
.sub_folder = folder,
.icon = entry.value("icon", ""),
.name = entry.value("name", ""),
.author = entry.value("author", ""),
.version = entry.value("version", "")
};
const std::string argv = entry.value("nro_argv", "");
strcpy(rec.nro_target.nro_path, nro_path.c_str());
if(!argv.empty()) {
strcpy(rec.nro_target.nro_argv, argv.c_str());
}
std::string folder = entry.value("folder", "");
rec.sub_folder = folder;
rec.icon = entry.value("icon", "");
if(folder.empty()) {
list.root.titles.push_back(rec);
}
else {
auto find = STL_FIND_IF(list.folders, fld, (fld.name == folder));
if(STL_FOUND(list.folders, find)) {
STL_UNWRAP(find).titles.push_back(rec);
const auto find_folder = STL_FIND_IF(list.folders, folder_item, folder_item.name == folder);
if(STL_FOUND(list.folders, find_folder)) {
STL_UNWRAP(find_folder).titles.push_back(rec);
}
else {
TitleFolder fld = {};
fld.name = folder;
fld.titles.push_back(rec);
list.folders.push_back(fld);
TitleFolder title_folder = {
.name = folder
};
title_folder.titles.push_back(rec);
list.folders.push_back(title_folder);
}
}
}
@ -651,21 +571,26 @@ namespace cfg {
}
});
// Add cached icons to non-custom title entries
for(auto &title: titles) {
title.icon = cfg::GetTitleCacheIconPath(title.app_id);
}
list.root.titles.insert(list.root.titles.end(), titles.begin(), titles.end());
return list;
}
std::string GetNROCacheIconPath(const std::string &path) {
char pathcopy[FS_MAX_PATH] = {0};
strcpy(pathcopy, path.c_str());
std::string GetNroCacheIconPath(const std::string &path) {
char path_copy[FS_MAX_PATH] = {};
strcpy(path_copy, path.c_str());
u8 hash[0x20] = {0};
sha256CalculateHash(hash, pathcopy, FS_MAX_PATH);
std::string out = UL_BASE_SD_DIR "/nro/";
sha256CalculateHash(hash, path_copy, FS_MAX_PATH);
std::stringstream strm;
strm << out;
strm << UL_NRO_CACHE_PATH "/";
// Use the first half of the hash, like N does with NCAs.
for(u32 i = 0; i < sizeof(hash) / 2; i++) {
strm << std::setw(2) << std::setfill('0') << std::hex << std::nouppercase << (u32)hash[i];
strm << std::setw(2) << std::setfill('0') << std::hex << std::nouppercase << static_cast<u32>(hash[i]);
}
strm << ".jpg";
return strm.str();

View file

@ -8,7 +8,7 @@ namespace db {
attr.system_save_data_id = HomeMenuSaveDataId;
attr.save_data_type = FsSaveDataType_System;
R_TRY(fsOpenSaveDataFileSystemBySystemSaveDataId(&savefs, FsSaveDataSpaceId_System, &attr));
UL_RC_TRY(fsOpenSaveDataFileSystemBySystemSaveDataId(&savefs, FsSaveDataSpaceId_System, &attr));
fsdevMountDevice(UL_DB_MOUNT_NAME, savefs);
return ResultSuccess;

View file

@ -8,22 +8,20 @@ namespace dmi {
constexpr u32 MaxRetryCount = 10000;
inline Result LoopWait(Result(*cmd_fn)(AppletStorage*), AppletStorage *st, bool wait) {
inline Result LoopWait(Result(*cmd_fn)(AppletStorage*), AppletStorage *st, const bool wait) {
if(!wait) {
return cmd_fn(st);
}
u32 count = 0;
while(true) {
auto rc = cmd_fn(st);
if(R_SUCCEEDED(rc)) {
if(R_SUCCEEDED(cmd_fn(st))) {
break;
}
count++;
if(count > MaxRetryCount) {
return 0xCAFA;
return ResultWaitTimeout;
}
svcSleepThread(10'000'000);
@ -40,7 +38,7 @@ namespace dmi {
return LoopWait(&am::LibraryAppletPush, st, false);
}
Result PopStorage(AppletStorage *st, bool wait) {
Result PopStorage(AppletStorage *st, const bool wait) {
return LoopWait(&am::LibraryAppletPop, st, wait);
}
@ -52,7 +50,7 @@ namespace dmi {
return LoopWait(&appletPushOutData, st, false);
}
Result PopStorage(AppletStorage *st, bool wait) {
Result PopStorage(AppletStorage *st, const bool wait) {
return LoopWait(&appletPopInData, st, wait);
}

View file

@ -4,8 +4,8 @@
namespace net {
Result Initialize() {
R_TRY(nifmInitialize(NifmServiceType_System));
R_TRY(wlaninfInitialize());
UL_RC_TRY(nifmInitialize(NifmServiceType_System));
UL_RC_TRY(wlaninfInitialize());
return ResultSuccess;
}

View file

@ -5,33 +5,31 @@
namespace os {
std::string GetIconCacheImagePath(AccountUid user_id) {
auto uidstr = util::Format128NintendoStyle(user_id);
return UL_BASE_SD_DIR "/user/" + uidstr + ".jpg";
std::string GetIconCacheImagePath(const AccountUid user_id) {
const auto uid_str = util::Format128NintendoStyle(user_id);
return UL_BASE_SD_DIR "/user/" + uid_str + ".jpg";
}
Result QuerySystemAccounts(std::vector<AccountUid> &out_accounts, bool dump_icon) {
Result QuerySystemAccounts(const bool dump_icon, std::vector<AccountUid> &out_accounts) {
AccountUid uids[ACC_USER_LIST_SIZE] = {};
s32 acc_count = 0;
R_TRY(accountListAllUsers(uids, ACC_USER_LIST_SIZE, &acc_count));
UL_RC_TRY(accountListAllUsers(uids, ACC_USER_LIST_SIZE, &acc_count));
for(s32 i = 0; i < acc_count; i++) {
auto uid = uids[i];
const auto uid = uids[i];
out_accounts.push_back(uid);
if(dump_icon) {
AccountProfile prof;
auto rc = accountGetProfile(&prof, uid);
if(R_SUCCEEDED(rc)) {
u32 imgsz = 0;
rc = accountProfileGetImageSize(&prof, &imgsz);
if(imgsz > 0) {
auto imgbuf = new u8[imgsz]();
u32 tmpsz;
rc = accountProfileLoadImage(&prof, imgbuf, imgsz, &tmpsz);
if(R_SUCCEEDED(rc)) {
auto iconcache = GetIconCacheImagePath(uid);
fs::WriteFile(iconcache, imgbuf, imgsz, true);
if(R_SUCCEEDED(accountGetProfile(&prof, uid))) {
u32 img_size = 0;
accountProfileGetImageSize(&prof, &img_size);
if(img_size > 0) {
auto img_buf = new u8[img_size]();
u32 tmp_size;
if(R_SUCCEEDED(accountProfileLoadImage(&prof, img_buf, img_size, &tmp_size))) {
const auto cache_icon_path = GetIconCacheImagePath(uid);
fs::WriteFile(cache_icon_path, img_buf, img_size, true);
}
delete[] imgbuf;
delete[] img_buf;
}
accountProfileClose(&prof);
}
@ -40,16 +38,15 @@ namespace os {
return ResultSuccess;
}
Result GetAccountName(std::string &out_name, AccountUid user_id) {
std::string name;
Result GetAccountName(const AccountUid user_id, std::string &out_name) {
AccountProfile prof;
R_TRY(accountGetProfile(&prof, user_id));
UL_ON_SCOPE_EXIT({
accountProfileClose(&prof);
});
UL_RC_TRY(accountGetProfile(&prof, user_id));
UL_ON_SCOPE_EXIT({ accountProfileClose(&prof); });
AccountProfileBase pbase;
AccountUserData udata;
R_TRY(accountProfileGet(&prof, &udata, &pbase));
UL_RC_TRY(accountProfileGet(&prof, &udata, &pbase));
out_name = pbase.nickname;
return ResultSuccess;
}

View file

@ -2,14 +2,13 @@
namespace os {
Result PushSystemAppletMessage(SystemAppletMessage msg) {
Result PushSystemAppletMessage(const SystemAppletMessage msg) {
AppletStorage st;
R_TRY(appletCreateStorage(&st, sizeof(msg)));
UL_ON_SCOPE_EXIT({
appletStorageClose(&st);
});
R_TRY(appletStorageWrite(&st, 0, &msg, sizeof(msg)));
R_TRY(appletPushToGeneralChannel(&st));
UL_RC_TRY(appletCreateStorage(&st, sizeof(msg)));
UL_ON_SCOPE_EXIT({ appletStorageClose(&st); });
UL_RC_TRY(appletStorageWrite(&st, 0, &msg, sizeof(msg)));
UL_RC_TRY(appletPushToGeneralChannel(&st));
return ResultSuccess;
}
}

View file

@ -16,10 +16,11 @@ namespace os {
}
std::string GetCurrentTime() {
auto time_val = time(nullptr);
auto local_time = localtime(&time_val);
auto h = local_time->tm_hour;
auto min = local_time->tm_min;
const auto time_val = time(nullptr);
const auto local_time = localtime(&time_val);
const auto h = local_time->tm_hour;
const auto min = local_time->tm_min;
char str[0x10] = {};
sprintf(str, "%02d:%02d", h, min);
return str;

View file

@ -6,15 +6,15 @@ namespace os {
std::vector<cfg::TitleRecord> titles;
auto records_buf = new NsApplicationRecord[MaxTitleCount]();
s32 record_count;
UL_ASSERT(nsListApplicationRecord(records_buf, MaxTitleCount, 0, &record_count));
UL_RC_ASSERT(nsListApplicationRecord(records_buf, MaxTitleCount, 0, &record_count));
for(s32 i = 0; i < record_count; i++) {
const auto &record = records_buf[i];
if(record.application_id == 0) {
continue;
}
const cfg::TitleRecord rec = {
.title_type = static_cast<u32>(cfg::TitleType::Installed),
.app_id = record.application_id,
.title_type = cfg::TitleType::Installed,
.app_id = record.application_id
};
titles.push_back(std::move(rec));
}

View file

@ -11,23 +11,30 @@ namespace res {
const char *name;
};
#define _RES_INFO_DEFINE(mod, name) { mod::Result ## name, #mod, #name }
#define _UL_RC_INFO_DEFINE(mod, name) { mod::Result ## name, #mod, #name }
constexpr ResultInfoImpl g_ResultInfoTableImpl[] = {
_UL_RC_INFO_DEFINE(misc, AssertionFailed),
_UL_RC_INFO_DEFINE(misc, UnexpectedTransform),
_UL_RC_INFO_DEFINE(misc, InvalidJsonFile),
static ResultInfoImpl g_ResultInfoTableImpl[] = {
_RES_INFO_DEFINE(misc, InvalidJsonFile),
_UL_RC_INFO_DEFINE(dmn, ApplicationActive),
_UL_RC_INFO_DEFINE(dmn, InvalidSelectedUser),
_UL_RC_INFO_DEFINE(dmn, AlreadyQueued),
_UL_RC_INFO_DEFINE(dmn, ApplicationNotActive),
_RES_INFO_DEFINE(dmn, ApplicationActive),
_RES_INFO_DEFINE(dmn, InvalidSelectedUser),
_RES_INFO_DEFINE(dmn, AlreadyQueued),
_RES_INFO_DEFINE(dmn, ApplicationNotActive),
_UL_RC_INFO_DEFINE(menu, RomfsFileNotFound),
_RES_INFO_DEFINE(menu, RomfsFileNotFound),
_UL_RC_INFO_DEFINE(ipc, InvalidProcess),
_RES_INFO_DEFINE(ipc, InvalidProcess),
_UL_RC_INFO_DEFINE(dmi, OutOfPushSpace),
_UL_RC_INFO_DEFINE(dmi, OutOfPopSpace),
_UL_RC_INFO_DEFINE(dmi, InvalidInHeaderMagic),
_UL_RC_INFO_DEFINE(dmi, InvalidOutHeaderMagic),
};
#undef _UL_RC_INFO_DEFINE
constexpr size_t ResultInfoTableImplCount = sizeof(g_ResultInfoTableImpl) / sizeof(ResultInfoImpl);
inline constexpr ResultInfoImpl &GetResultInfo(const u32 idx) {
inline constexpr ResultInfoImpl GetResultInfo(const u32 idx) {
return g_ResultInfoTableImpl[idx];
}
@ -53,7 +60,7 @@ namespace res {
}
}
return "Unknown module";
return "";
}
std::string GetNameByResult(const Result rc) {
@ -64,7 +71,7 @@ namespace res {
}
}
return "Unknown name";
return "";
}
}

View file

@ -27,7 +27,7 @@ EXEFS_SRC := exefs_src
#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
ARCH := -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE -Wl,-wrap,libappletStart -Wl,-wrap,libappletLaunch
ARCH := -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE -Wl,-wrap,libappletLaunch -Wl,-wrap,libappletStart
CFLAGS := -g -Wall -O2 -ffunction-sections \
$(ARCH) $(DEFINES)
@ -45,7 +45,7 @@ LIBS := -lpu -lfreetype -lSDL2_mixer -lopusfile -lopus -lmodplug -lmpg123 -lvorb
# list of directories containing libraries, this must be the top level containing
# include and lib
#---------------------------------------------------------------------------------
LIBDIRS := $(PORTLIBS) $(LIBNX) $(TOPDIR)/../Plutonium/Plutonium/out
LIBDIRS := $(PORTLIBS) $(LIBNX) $(CURDIR)/../Plutonium/Plutonium
#---------------------------------------------------------------------------------

View file

@ -9,6 +9,6 @@ namespace am {
Result InitializeDaemonMessageHandler();
void ExitDaemonMessageHandler();
void RegisterOnMessageDetect(OnMessageCallback callback, dmi::MenuMessage desired_msg);
void RegisterOnMessageDetect(OnMessageCallback callback, const dmi::MenuMessage desired_msg);
}

View file

@ -1,8 +0,0 @@
#pragma once
namespace am {
void RegisterLibAppletHomeButtonDetection();
}

View file

@ -0,0 +1,8 @@
#pragma once
namespace am {
void RegisterLibnxLibappletHomeButtonDetection();
}

View file

@ -4,7 +4,7 @@
namespace am {
Result ReadDataFromStorage(void *data, size_t data_size);
Result ReadDataFromStorage(void *out_data, const size_t data_size);
Result ReadStartMode(dmi::MenuStartMode &out_start_mode);
}

View file

@ -8,52 +8,82 @@
namespace ui {
class ClickableImage : public pu::ui::elm::Element {
public:
using OnClickCallback = std::function<void()>;
private:
inline void Dispose() {
if(this->ntex != nullptr) {
pu::ui::render::DeleteTexture(this->ntex);
this->ntex = nullptr;
}
}
static constexpr u64 TouchActionTimeMilliseconds = 200;
static constexpr u8 RedColorMod = 0xC8;
static constexpr u8 GreenColorMod = 0xC8;
static constexpr u8 BlueColorMod = 0xFF;
protected:
std::string img;
pu::sdl2::Texture ntex;
pu::sdl2::Texture img_tex;
s32 x;
s32 y;
s32 w;
s32 h;
std::function<void()> cb;
std::chrono::steady_clock::time_point touchtp;
OnClickCallback cb;
std::chrono::steady_clock::time_point touch_tp;
bool touched;
public:
ClickableImage(s32 x, s32 y, const std::string &img) : Element(), ntex(nullptr), x(x), y(y), w(0), h(0), cb([&](){}), touched(false) {
ClickableImage(const s32 x, const s32 y, const std::string &img) : Element(), img_tex(nullptr), x(x), y(y), w(0), h(0), cb(), touched(false) {
this->SetImage(img);
}
~ClickableImage() {
this->Dispose();
}
PU_SMART_CTOR(ClickableImage)
s32 GetX();
void SetX(s32 x);
s32 GetY();
void SetY(s32 y);
s32 GetWidth();
void SetWidth(s32 w);
s32 GetHeight();
void SetHeight(s32 h);
std::string GetImage();
void SetImage(const std::string &img);
bool IsImageValid();
void SetOnClick(std::function<void()> cb);
void OnRender(pu::ui::render::Renderer::Ref &drawer, s32 x, s32 y);
void OnInput(u64 down, u64 up, u64 held, pu::ui::Touch touch_pos);
~ClickableImage();
inline s32 GetX() override {
return this->x;
}
inline void SetX(const s32 x) {
this->x = x;
}
inline s32 GetY() override {
return this->y;
}
inline void SetY(const s32 y) {
this->y = y;
}
inline s32 GetWidth() override {
return this->w;
}
inline void SetWidth(const s32 w) {
this->w = w;
}
inline s32 GetHeight() override {
return this->h;
}
inline void SetHeight(const s32 h) {
this->h = h;
}
inline std::string GetImage() {
return this->img;
}
void SetImage(const std::string &img);
inline bool IsImageValid() {
return this->img_tex != nullptr;
}
inline void SetOnClick(OnClickCallback cb) {
this->cb = cb;
}
void OnRender(pu::ui::render::Renderer::Ref &drawer, const s32 x, const s32 y) override;
void OnInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const pu::ui::TouchPoint touch_pos) override;
};
}

View file

@ -1,23 +1,21 @@
#pragma once
#include <pu/Plutonium>
#include <ul_Include.hpp>
#include <pu/Plutonium>
namespace ui {
class IMenuLayout : public pu::ui::Layout {
private:
std::atomic_bool home_pressed;
public:
IMenuLayout();
void OnInput(u64 down, u64 up, u64 held, pu::ui::Touch touch_pos);
virtual void OnMenuInput(u64 down, u64 up, u64 held, pu::ui::Touch touch_pos) = 0;
void OnInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const pu::ui::TouchPoint touch_pos);
void DoOnHomeButtonPress();
virtual void OnMenuInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const pu::ui::TouchPoint touch_pos) = 0;
virtual bool OnHomeButtonPress() = 0;
};
}

View file

@ -1,27 +1,24 @@
#pragma once
#include <ul_Include.hpp>
#include <ui/ui_IMenuLayout.hpp>
#include <cfg/cfg_Config.hpp>
namespace ui {
class LanguagesMenuLayout : public IMenuLayout {
private:
pu::ui::elm::TextBlock::Ref infoText;
pu::ui::elm::Menu::Ref langsMenu;
pu::ui::elm::TextBlock::Ref info_text;
pu::ui::elm::Menu::Ref langs_menu;
void lang_DefaultKey(const u32 idx);
public:
LanguagesMenuLayout();
PU_SMART_CTOR(LanguagesMenuLayout)
void OnMenuInput(u64 down, u64 up, u64 held, pu::ui::Touch touch_pos) override;
void OnMenuInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const pu::ui::TouchPoint touch_pos) override;
bool OnHomeButtonPress() override;
void Reload();
void lang_Click(u32 idx);
};
}

View file

@ -10,6 +10,7 @@
namespace ui {
// TODO: user-defined literal? ---> "ok"_lang
std::string GetLanguageString(const std::string &name);
enum class MenuType {
@ -17,35 +18,37 @@ namespace ui {
Main,
Settings,
Theme,
Languages,
Languages
};
void UiOnHomeButtonDetection();
class MenuApplication : public pu::ui::Application {
private:
dmi::MenuStartMode stmode;
StartupLayout::Ref startupLayout;
MenuLayout::Ref menuLayout;
ThemeMenuLayout::Ref themeMenuLayout;
SettingsMenuLayout::Ref settingsMenuLayout;
LanguagesMenuLayout::Ref languagesMenuLayout;
pu::ui::extras::Toast::Ref notifToast;
dmi::DaemonStatus status;
dmi::MenuStartMode start_mode;
StartupLayout::Ref startup_lyt;
MenuLayout::Ref menu_lyt;
ThemeMenuLayout::Ref theme_menu_lyt;
SettingsMenuLayout::Ref settings_menu_lyt;
LanguagesMenuLayout::Ref languages_menu_lyt;
pu::ui::extras::Toast::Ref notif_toast;
dmi::DaemonStatus daemon_status;
MenuType loaded_menu;
JSON uijson;
JSON bgmjson;
JSON ui_json;
JSON bgm_json;
bool bgm_loop;
u32 bgm_fade_in_ms;
u32 bgm_fade_out_ms;
pu::audio::Music bgm;
pu::ui::Color text_clr;
pu::ui::Color menu_focus_clr;
pu::ui::Color menu_bg_clr;
public:
using Application::Application;
~MenuApplication() {
pu::audio::Delete(this->bgm);
pu::audio::DestroyMusic(this->bgm);
}
PU_SMART_CTOR(MenuApplication)
@ -56,69 +59,155 @@ namespace ui {
void OnLoad() override;
void SetInformation(dmi::MenuStartMode mode, dmi::DaemonStatus status, JSON ui_json);
void LoadMenu();
void LoadStartupMenu();
void LoadThemeMenu();
void LoadSettingsMenu();
void LoadSettingsLanguagesMenu();
inline void SetInformation(const dmi::MenuStartMode start_mode, const dmi::DaemonStatus daemon_status, const JSON ui_json) {
this->start_mode = start_mode;
this->daemon_status = daemon_status;
this->ui_json = ui_json;
}
bool IsSuspended();
bool IsTitleSuspended();
bool IsHomebrewSuspended();
bool EqualsSuspendedHomebrewPath(const std::string &path);
u64 GetSuspendedApplicationId();
void NotifyEndSuspended();
bool LaunchFailed();
void ShowNotification(const std::string &text, u64 timeout = 1500);
inline void LoadMenu() {
this->menu_lyt->SetUser(this->daemon_status.selected_user);
this->LoadLayout(this->menu_lyt);
this->loaded_menu = MenuType::Main;
}
inline void LoadStartupMenu() {
this->StopPlayBGM();
this->startup_lyt->ReloadMenu();
this->LoadLayout(this->startup_lyt);
this->loaded_menu = MenuType::Startup;
}
inline void LoadThemeMenu() {
this->theme_menu_lyt->Reload();
this->LoadLayout(this->theme_menu_lyt);
this->loaded_menu = MenuType::Theme;
}
inline void LoadSettingsMenu() {
this->settings_menu_lyt->Reload(true);
this->LoadLayout(this->settings_menu_lyt);
this->loaded_menu = MenuType::Settings;
}
inline void LoadSettingsLanguagesMenu() {
this->languages_menu_lyt->Reload();
this->LoadLayout(this->languages_menu_lyt);
this->loaded_menu = MenuType::Languages;
}
inline bool IsTitleSuspended() {
return this->daemon_status.app_id != 0;
}
inline bool IsHomebrewSuspended() {
return strlen(this->daemon_status.params.nro_path) > 0;
}
inline bool IsSuspended() {
return this->IsTitleSuspended() || this->IsHomebrewSuspended();
}
inline bool EqualsSuspendedHomebrewPath(const std::string &path) {
return this->daemon_status.params.nro_path == path;
}
inline u64 GetSuspendedApplicationId() {
return this->daemon_status.app_id;
}
inline void NotifyEndSuspended() {
// Blanking the whole status would also blank the selected user, thus we only blank the params
this->daemon_status.params = {};
this->daemon_status.app_id = 0;
}
inline bool LaunchFailed() {
return this->start_mode == dmi::MenuStartMode::MenuLaunchFailure;
}
void ShowNotification(const std::string &text, const u64 timeout = 1500);
template<typename T>
inline T GetUIConfigValue(const std::string &name, T def) {
return this->uijson.value<T>(name, def);
inline T GetUIConfigValue(const std::string &name, const T def) {
return this->ui_json.value<T>(name, def);
}
template<typename Elem>
inline void ApplyConfigForElement(const std::string &menu, const std::string &name, std::shared_ptr<Elem> &Ref, bool also_visible = true) {
if(this->uijson.count(menu)) {
auto jmenu = this->uijson[menu];
if(jmenu.count(name)) {
auto jelem = jmenu[name];
bool set_coords = false;
if(also_visible) {
bool visible = jelem.value("visible", true);
Ref->SetVisible(visible);
inline void ApplyConfigForElement(const std::string &menu, const std::string &name, std::shared_ptr<Elem> &elem, const bool apply_visible = true) {
if(this->ui_json.count(menu)) {
const auto menu_json = this->ui_json[menu];
if(menu_json.count(name)) {
const auto elem_json = menu_json[name];
auto set_coords = false;
if(apply_visible) {
const auto visible = elem_json.value("visible", true);
elem->SetVisible(visible);
set_coords = visible;
}
else {
set_coords = true;
}
if(set_coords) {
if(jelem.count("x")) {
s32 x = jelem["x"];
Ref->SetX(x);
if(elem_json.count("x")) {
const s32 x = elem_json.value("x", 0);
elem->SetX(x);
}
if(jelem.count("y")) {
s32 y = jelem["y"];
Ref->SetY(y);
if(elem_json.count("y")) {
const s32 y = elem_json.value("y", 0);
elem->SetY(y);
}
}
}
}
}
inline pu::ui::Color GetTextColor() {
return this->text_clr;
}
inline pu::ui::Color GetMenuFocusColor() {
return this->menu_focus_clr;
}
inline pu::ui::Color GetMenuBackgroundColor() {
return this->menu_bg_clr;
}
void StartPlayBGM();
void StopPlayBGM();
StartupLayout::Ref &GetStartupLayout();
MenuLayout::Ref &GetMenuLayout();
ThemeMenuLayout::Ref &GetThemeMenuLayout();
SettingsMenuLayout::Ref &GetSettingsMenuLayout();
LanguagesMenuLayout::Ref &GetLanguagesMenuLayout();
inline StartupLayout::Ref &GetStartupLayout() {
return this->startup_lyt;
}
void SetSelectedUser(AccountUid user_id);
AccountUid GetSelectedUser();
MenuType GetCurrentLoadedMenu();
inline MenuLayout::Ref &GetMenuLayout() {
return this->menu_lyt;
}
inline ThemeMenuLayout::Ref &GetThemeMenuLayout() {
return this->theme_menu_lyt;
}
inline SettingsMenuLayout::Ref &GetSettingsMenuLayout() {
return this->settings_menu_lyt;
}
inline LanguagesMenuLayout::Ref &GetLanguagesMenuLayout() {
return this->languages_menu_lyt;
}
void SetSelectedUser(const AccountUid user_id);
inline AccountUid GetSelectedUser() {
return this->daemon_status.selected_user;
}
inline MenuType GetCurrentLoadedMenu() {
return this->loaded_menu;
}
};
}

View file

@ -3,7 +3,7 @@
#include <ul_Include.hpp>
#include <ui/ui_IMenuLayout.hpp>
#include <ui/ui_SideMenu.hpp>
#include <ui/ui_RawData.hpp>
#include <ui/ui_RawRgbaImage.hpp>
#include <ui/ui_ClickableImage.hpp>
#include <ui/ui_QuickMenu.hpp>
#include <ui/ui_Actions.hpp>
@ -12,83 +12,91 @@
namespace ui {
class MenuLayout : public IMenuLayout {
public:
// 16:9 ratio
static constexpr u32 SuspendedImageWidthIncrement = (u32)30.0f;
static constexpr u32 SuspendedImageHeightIncrement = (u32)(SuspendedImageWidthIncrement / (16.0f / 9.0f));
static constexpr u8 SuspendedScreenAlphaIncrement = 10;
private:
void *susptr;
bool last_hasconn;
u32 last_batterylvl;
bool last_charge;
pu::ui::elm::Image::Ref topMenuImage;
pu::ui::elm::Image::Ref connIcon;
ClickableImage::Ref users;
ClickableImage::Ref controller;
ClickableImage::Ref logo;
pu::ui::elm::TextBlock::Ref timeText;
pu::ui::elm::TextBlock::Ref batteryText;
pu::ui::elm::Image::Ref batteryIcon;
ClickableImage::Ref settings;
ClickableImage::Ref themes;
pu::ui::elm::TextBlock::Ref fwText;
SideMenu::Ref itemsMenu;
RawData::Ref bgSuspendedRaw;
pu::ui::elm::TextBlock::Ref itemName;
pu::ui::elm::TextBlock::Ref itemAuthor;
pu::ui::elm::TextBlock::Ref itemVersion;
pu::ui::elm::Image::Ref bannerImage;
pu::ui::elm::Image::Ref guideButtons;
ClickableImage::Ref menuToggle;
QuickMenu::Ref quickMenu;
std::string curfolder;
std::chrono::steady_clock::time_point tp;
bool warnshown;
bool last_has_connection;
u32 last_battery_lvl;
bool last_is_charging;
pu::ui::elm::Image::Ref top_menu_img;
pu::ui::elm::Image::Ref connection_icon;
ClickableImage::Ref users_img;
ClickableImage::Ref controller_img;
ClickableImage::Ref logo_img;
pu::ui::elm::TextBlock::Ref time_text;
pu::ui::elm::TextBlock::Ref battery_text;
pu::ui::elm::Image::Ref battery_icon;
ClickableImage::Ref settings_img;
ClickableImage::Ref themes_img;
pu::ui::elm::TextBlock::Ref fw_text;
SideMenu::Ref items_menu;
RawRgbaImage::Ref suspended_screen_img;
pu::ui::elm::TextBlock::Ref selected_item_name_text;
pu::ui::elm::TextBlock::Ref selected_item_author_text;
pu::ui::elm::TextBlock::Ref selected_item_version_text;
pu::ui::elm::Image::Ref banner_img;
pu::ui::elm::Image::Ref guide_buttons_img;
ClickableImage::Ref menu_totggle_img;
QuickMenu::Ref quick_menu;
std::string cur_folder;
std::chrono::steady_clock::time_point startup_tp;
bool launch_fail_warn_shown;
bool homebrew_mode;
bool select_on;
bool select_dir;
u8 minalpha;
u8 min_alpha;
u32 mode;
s32 rawalpha;
pu::audio::Sfx sfxTitleLaunch;
pu::audio::Sfx sfxMenuToggle;
s32 suspended_screen_alpha;
pu::audio::Sfx title_launch_sfx;
pu::audio::Sfx menu_toggle_sfx;
void DoMoveFolder(const std::string &name);
void menu_Click(const u64 keys_down, const u32 idx);
void menu_OnSelected(const u32 idx);
void menuToggle_Click();
inline void ApplySuspendedRatio(const bool increase) {
auto susp_w = this->suspended_screen_img->GetWidth();
auto susp_h = this->suspended_screen_img->GetHeight();
inline void ApplySuspendedRatio(bool increase) {
auto susp_w = this->bgSuspendedRaw->GetWidth();
auto susp_h = this->bgSuspendedRaw->GetHeight();
// Change size, 16:9 ratio
if(increase) {
susp_w += 16;
susp_h += 9;
susp_w += (s32)SuspendedImageWidthIncrement;
susp_h += (s32)SuspendedImageHeightIncrement;
}
else {
susp_w -= 16;
susp_h -= 9;
susp_w -= (s32)SuspendedImageWidthIncrement;
susp_h -= (s32)SuspendedImageHeightIncrement;
}
auto susp_x = (1280 - susp_w) / 2;
auto susp_y = (720 - susp_h) / 2;
this->bgSuspendedRaw->SetX(susp_x);
this->bgSuspendedRaw->SetY(susp_y);
this->bgSuspendedRaw->SetWidth(susp_w);
this->bgSuspendedRaw->SetHeight(susp_h);
const auto susp_x = (pu::ui::render::ScreenWidth - susp_w) / 2;
const auto susp_y = (pu::ui::render::ScreenHeight - susp_h) / 2;
this->suspended_screen_img->SetX(susp_x);
this->suspended_screen_img->SetY(susp_y);
this->suspended_screen_img->SetWidth(susp_w);
this->suspended_screen_img->SetHeight(susp_h);
}
public:
MenuLayout(void *raw, u8 min_alpha);
MenuLayout(const u8 *captured_screen_buf, const u8 min_alpha);
~MenuLayout();
PU_SMART_CTOR(MenuLayout)
void OnMenuInput(u64 down, u64 up, u64 held, pu::ui::Touch touch_pos) override;
void OnMenuInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const pu::ui::TouchPoint touch_pos) override;
bool OnHomeButtonPress() override;
void menu_Click(u64 down, u32 index);
void menu_OnSelected(u32 index);
void menuToggle_Click();
void MoveFolder(const std::string &name, bool fade);
void SetUser(AccountUid user);
void MoveFolder(const std::string &name, const bool fade);
void SetUser(const AccountUid user);
void HandleCloseSuspended();
void HandleHomebrewLaunch(cfg::TitleRecord &rec);
void HandleHomebrewLaunch(const cfg::TitleRecord &rec);
void HandleMultiselectMoveToFolder(const std::string &folder);
void StopMultiselect();
void DoTerminateApplication();
};
}

View file

@ -5,59 +5,70 @@
namespace ui {
enum class QuickMenuDirection {
Up,
Down,
Left,
Right,
UpLeft,
UpRight,
DownLeft,
DownRight,
None
};
struct QuickMenuSubItem {
std::function<void()> on_select;
pu::sdl2::Texture nicon;
};
void QuickMenuOnHomeButtonDetection();
class QuickMenu : public pu::ui::elm::Element {
public:
static constexpr s32 MainItemSize = 300;
static constexpr s32 SubItemsSize = 150;
static constexpr s32 CommonAreaSize = 50;
static constexpr s32 MainItemX = (1280 - MainItemSize) / 2;
static constexpr s32 MainItemY = (720 - MainItemSize) / 2;
static constexpr s32 MainItemX = (pu::ui::render::ScreenWidth - MainItemSize) / 2;
static constexpr s32 MainItemY = (pu::ui::render::ScreenHeight - MainItemSize) / 2;
static constexpr u8 BackgroundAlphaMax = 0xDC;
static constexpr u8 BackgroundAlphaIncrement = 20;
static constexpr u32 MenuMargin = 200;
static constexpr u32 MenuX = MenuMargin;
static constexpr u32 MenuY = 115;
static constexpr u32 MenuWidth = pu::ui::render::ScreenWidth - 2 * MenuMargin;
static constexpr u32 MenuItemHeight = 60;
static constexpr u32 MenuItemsToShow = 8;
static inline constexpr pu::ui::Color MakeBackgroundColor(const u8 alpha) {
return { 50, 50, 50, alpha };
}
private:
bool on;
s32 bgalpha;
s32 bg_alpha;
pu::ui::elm::Menu::Ref options_menu;
static void OnHomeButtonDetection();
public:
QuickMenu(const std::string &main_icon);
PU_SMART_CTOR(QuickMenu)
static inline void RegisterHomeButtonDetection() {
am::RegisterOnMessageDetect(&QuickMenuOnHomeButtonDetection, dmi::MenuMessage::HomeRequest);
inline constexpr s32 GetX() override {
return 0;
}
s32 GetX();
s32 GetY();
s32 GetWidth();
s32 GetHeight();
inline constexpr s32 GetY() override {
return 0;
}
void Toggle(); // Off if on, on if off (just change to the opposite state)
bool IsOn();
inline constexpr s32 GetWidth() override {
return pu::ui::render::ScreenWidth;
}
void OnRender(pu::ui::render::Renderer::Ref &Drawer, s32 X, s32 Y);
void OnInput(u64 Down, u64 Up, u64 Held, pu::ui::Touch Pos);
inline constexpr s32 GetHeight() override {
return pu::ui::render::ScreenHeight;
}
inline void Toggle() {
this->on = !this->on;
}
inline constexpr bool IsOn() {
return this->on && (this->bg_alpha > 0);
}
void OnRender(pu::ui::render::Renderer::Ref &drawer, const s32 x, const s32 y) override;
void OnInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const pu::ui::TouchPoint touch_pos) override;
static inline void RegisterHomeButtonDetection() {
am::RegisterOnMessageDetect(&OnHomeButtonDetection, dmi::MenuMessage::HomeRequest);
}
};
}

View file

@ -1,40 +0,0 @@
#pragma once
#include <ul_Include.hpp>
#include <pu/Plutonium>
namespace ui {
class RawData : public pu::ui::elm::Element {
protected:
s32 x;
s32 y;
s32 w;
s32 h;
pu::sdl2::Texture ntex;
u8 falpha;
void *ptr;
size_t fullsz;
size_t pitch;
public:
RawData(s32 x, s32 y, void *raw, s32 w, s32 h, u32 pix_num);
PU_SMART_CTOR(RawData)
~RawData();
s32 GetX();
void SetX(s32 x);
s32 GetY();
void SetY(s32 y);
s32 GetWidth();
void SetWidth(s32 w);
s32 GetHeight();
void SetHeight(s32 h);
void SetAlphaFactor(u8 Factor);
void OnRender(pu::ui::render::Renderer::Ref &drawer, s32 x, s32 y);
void OnInput(u64 down, u64 up, u64 held, pu::ui::Touch touch_pos);
};
}

View file

@ -0,0 +1,62 @@
#pragma once
#include <ul_Include.hpp>
#include <pu/Plutonium>
namespace ui {
class RawRgbaImage : public pu::ui::elm::Element {
private:
s32 x;
s32 y;
s32 w;
s32 h;
pu::sdl2::Texture img_tex;
u8 alpha;
public:
RawRgbaImage(const s32 x, const s32 y, const u8 *rgba_data, const s32 w, const s32 h, const u32 pix_num);
PU_SMART_CTOR(RawRgbaImage)
~RawRgbaImage();
inline s32 GetX() override {
return this->x;
}
inline void SetX(const s32 x) {
this->x = x;
}
inline s32 GetY() override {
return this->y;
}
inline void SetY(const s32 y) {
this->y = y;
}
inline s32 GetWidth() override {
return this->w;
}
inline void SetWidth(const s32 w) {
this->w = w;
}
inline s32 GetHeight() override {
return this->h;
}
inline void SetHeight(const s32 h) {
this->h = h;
}
inline void SetAlpha(const u8 alpha) {
this->alpha = alpha;
}
void OnRender(pu::ui::render::Renderer::Ref &drawer, const s32 x, const s32 y) override;
void OnInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const pu::ui::TouchPoint touch_pos) override {}
};
}

View file

@ -7,22 +7,21 @@
namespace ui {
class SettingsMenuLayout : public IMenuLayout {
private:
pu::ui::elm::TextBlock::Ref infoText;
pu::ui::elm::Menu::Ref settingsMenu;
pu::ui::elm::TextBlock::Ref info_text;
pu::ui::elm::Menu::Ref settings_menu;
void PushSettingItem(const std::string &name, const std::string &value_display, const int id);
void setting_DefaultKey(const u32 id);
public:
SettingsMenuLayout();
PU_SMART_CTOR(SettingsMenuLayout)
void OnMenuInput(u64 down, u64 up, u64 held, pu::ui::Touch touch_pos) override;
void OnMenuInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const pu::ui::TouchPoint touch_pos) override;
bool OnHomeButtonPress() override;
void Reload();
void PushSettingItem(const std::string &name, const std::string &value_display, int id);
void setting_Click(u32 id);
void Reload(const bool reset_idx);
};
}

View file

@ -1,13 +1,12 @@
#pragma once
#include <pu/Plutonium>
#include <map>
namespace ui {
class SideMenu : public pu::ui::elm::Element {
public:
static constexpr u32 BaseX = 98;
static constexpr u32 ItemSize = 256;
static constexpr u32 Margin = 20;
static constexpr u32 ItemCount = 4;
@ -15,77 +14,156 @@ namespace ui {
static constexpr u32 FocusMargin = 5;
static constexpr u32 ExtraIconSize = ItemSize + (Margin * 2);
static constexpr u8 MoveAlphaIncrement = 30;
static constexpr u64 ScrollBaseWaitTimeMs = 50;
static constexpr u64 ScrollMoveWaitTimeMs = 200;
using OnSelectCallback = std::function<void(const u64, const u32)>;
using OnSelectionChangedCallback = std::function<void(const u32)>;
private:
s32 y;
u32 selitm;
u32 preselitm;
int suspitm;
u32 baseiconidx;
u8 movalpha;
u32 textx;
u32 texty;
u32 selected_item_idx;
u32 prev_selected_item_idx;
s32 suspended_item_idx;
u32 base_icon_idx;
u8 move_alpha;
u32 text_x;
u32 text_y;
bool enabled;
pu::ui::Color suspclr;
pu::ui::Color textclr;
std::vector<std::string> icons;
std::vector<std::string> icons_texts;
std::vector<bool> icons_mselected;
std::function<void(u64, u32)> onselect;
std::function<void(u32)> onselch;
std::vector<pu::sdl2::Texture> ricons;
std::vector<pu::sdl2::Texture> ricons_texts;
pu::sdl2::Texture cursoricon;
pu::sdl2::Texture suspicon;
pu::sdl2::Texture leftbicon;
pu::sdl2::Texture rightbicon;
pu::sdl2::Texture mselicon;
pu::String textfont;
std::chrono::steady_clock::time_point scrolltp;
bool scrollmoveflag;
std::chrono::steady_clock::time_point scrollmovetp;
u32 scrollflag;
u32 scrolltpvalue;
u32 scrollcount;
pu::ui::Color text_clr;
std::vector<std::string> items_icon_paths;
std::vector<std::string> items_icon_texts;
std::vector<bool> items_multiselected;
OnSelectCallback on_select_cb;
OnSelectionChangedCallback on_selection_changed_cb;
std::vector<pu::sdl2::Texture> rendered_icons;
std::vector<pu::sdl2::Texture> rendered_texts;
pu::sdl2::Texture cursor_icon;
pu::sdl2::Texture suspended_icon;
pu::sdl2::Texture left_border_icon;
pu::sdl2::Texture right_border_icon;
pu::sdl2::Texture multiselect_icon;
std::string text_font;
std::chrono::steady_clock::time_point scroll_tp;
bool scroll_move_flag;
std::chrono::steady_clock::time_point scroll_move_tp;
u32 scroll_flag;
u32 scroll_tp_value;
u32 scroll_count;
inline void DoOnItemSelected(const u64 keys) {
if(this->on_select_cb) {
(this->on_select_cb)(keys, this->selected_item_idx);
}
}
inline void DoOnSelectionChanged() {
if(this->on_selection_changed_cb) {
(this->on_selection_changed_cb)(this->selected_item_idx);
}
}
inline void ClearBorderIcons() {
pu::ui::render::DeleteTexture(this->left_border_icon);
pu::ui::render::DeleteTexture(this->right_border_icon);
}
inline void ClearRenderedItems() {
for(auto &icon_tex: this->rendered_icons) {
pu::ui::render::DeleteTexture(icon_tex);
}
this->rendered_icons.clear();
for(auto &text_tex: this->rendered_texts) {
pu::ui::render::DeleteTexture(text_tex);
}
this->rendered_texts.clear();
this->ClearBorderIcons();
}
bool IsLeftFirst();
bool IsRightLast();
void ReloadIcons(u32 dir);
void MoveReloadIcons(const bool moving_right);
public:
SideMenu(pu::ui::Color suspended_clr, std::string cursor_path, std::string suspended_img_path, std::string multiselect_img_path, u32 txt_x, u32 txt_y, pu::String font_name, pu::ui::Color txt_clr, s32 y);
SideMenu(const pu::ui::Color suspended_clr, const std::string &cursor_path, const std::string &suspended_img_path, const std::string &multiselect_img_path, const s32 txt_x, const s32 txt_y, const std::string &font_name, const pu::ui::Color txt_clr, const s32 y);
PU_SMART_CTOR(SideMenu)
~SideMenu();
s32 GetX() override;
s32 GetY() override;
void SetX(s32 x); // Stubbed
void SetY(s32 y);
s32 GetWidth() override;
s32 GetHeight() override;
void OnRender(pu::ui::render::Renderer::Ref &drawer, s32 x, s32 y) override;
void OnInput(u64 down, u64 up, u64 held, pu::ui::Touch touch_pos) override;
void SetOnItemSelected(std::function<void(u64, u32)> fn);
void SetOnSelectionChanged(std::function<void(u32)> fn);
inline constexpr s32 GetX() override {
return BaseX;
}
inline s32 GetY() override {
return this->y;
}
// Note: stubbed for ApplyConfigForElement to work with this element
inline void SetX(const s32 x) {}
inline void SetY(const s32 y) {
this->y = y;
}
inline constexpr s32 GetWidth() override {
return pu::ui::render::ScreenWidth;
}
inline constexpr s32 GetHeight() override {
return ItemSize + FocusSize + FocusMargin;
}
void OnRender(pu::ui::render::Renderer::Ref &drawer, const s32 x, const s32 y) override;
void OnInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const pu::ui::TouchPoint touch_pos) override;
inline void SetOnItemSelected(OnSelectCallback cb) {
this->on_select_cb = cb;
}
inline void SetOnSelectionChanged(OnSelectionChangedCallback cb) {
this->on_selection_changed_cb = cb;
}
void ClearItems();
void AddItem(const std::string &icon, const std::string &txt = "");
void SetSuspendedItem(u32 idx);
void UnsetSuspendedItem();
void SetSelectedItem(u32 idx);
inline void SetSuspendedItem(const u32 idx) {
if(idx < this->items_icon_paths.size()) {
this->suspended_item_idx = idx;
}
}
inline void ResetSuspendedItem() {
this->suspended_item_idx = -1;
}
inline void Rewind() {
this->ClearRenderedItems();
this->selected_item_idx = 0;
this->scroll_count = 0;
this->base_icon_idx = 0;
}
void HandleMoveLeft();
void HandleMoveRight();
int GetSuspendedItem();
u32 GetSelectedItem();
u32 GetBaseItemIndex();
void SetBaseItemIndex(u32 idx);
void SetBasePositions(u32 selected_idx, u32 base_idx);
void ClearBorderIcons();
inline u32 GetSelectedItem() {
return this->selected_item_idx;
}
void UpdateBorderIcons();
void ResetMultiselections();
void SetItemMultiselected(u32 idx, bool selected);
bool IsItemMultiselected(u32 idx);
void SetItemMultiselected(const u32 idx, const bool selected);
bool IsItemMultiselected(const u32 idx);
bool IsAnyMultiselected();
void SetEnabled(bool enabled);
inline void SetEnabled(const bool enabled) {
this->enabled = enabled;
}
};
}

View file

@ -1,30 +1,26 @@
#pragma once
#include <ul_Include.hpp>
#include <dmi/dmi_DaemonMenuInteraction.hpp>
#include <db/db_Save.hpp>
#include <ui/ui_IMenuLayout.hpp>
namespace ui {
class StartupLayout : public IMenuLayout {
private:
bool loadmenu;
pu::ui::elm::TextBlock::Ref infoText;
pu::ui::elm::Menu::Ref usersMenu;
bool load_menu;
pu::ui::elm::TextBlock::Ref info_text;
pu::ui::elm::Menu::Ref users_menu;
void user_DefaultKey(const AccountUid uid);
void create_DefaultKey();
public:
StartupLayout();
PU_SMART_CTOR(StartupLayout)
void OnMenuInput(u64 down, u64 up, u64 held, pu::ui::Touch touch_pos) override;
void OnMenuInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const pu::ui::TouchPoint touch_pos) override;
bool OnHomeButtonPress() override;
void user_Click(AccountUid uid);
void create_Click();
void ReloadMenu();
};
}

View file

@ -1,33 +1,31 @@
#pragma once
#include <ul_Include.hpp>
#include <ui/ui_IMenuLayout.hpp>
#include <cfg/cfg_Config.hpp>
namespace ui {
class ThemeMenuLayout : public IMenuLayout {
private:
pu::ui::elm::Menu::Ref themesMenu;
pu::ui::elm::TextBlock::Ref curThemeText;
pu::ui::elm::TextBlock::Ref curThemeName;
pu::ui::elm::TextBlock::Ref curThemeAuthor;
pu::ui::elm::TextBlock::Ref curThemeVersion;
pu::ui::elm::Image::Ref curThemeIcon;
pu::ui::elm::Image::Ref curThemeBanner;
std::vector<cfg::Theme> loadedThemes;
pu::ui::elm::Menu::Ref themes_menu;
pu::ui::elm::TextBlock::Ref cur_theme_text;
pu::ui::elm::TextBlock::Ref cur_theme_name_text;
pu::ui::elm::TextBlock::Ref cur_theme_author_text;
pu::ui::elm::TextBlock::Ref cur_theme_version_text;
pu::ui::elm::Image::Ref cur_theme_icon;
pu::ui::elm::Image::Ref cur_theme_banner;
std::vector<cfg::Theme> loaded_themes;
void theme_DefaultKey();
public:
ThemeMenuLayout();
PU_SMART_CTOR(ThemeMenuLayout)
void OnMenuInput(u64 down, u64 up, u64 held, pu::ui::Touch touch_pos) override;
void OnMenuInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const pu::ui::TouchPoint touch_pos) override;
bool OnHomeButtonPress() override;
void Reload();
void theme_Click();
};
}

View file

@ -4,6 +4,8 @@
namespace ui {
// Note: this guard ensures that certain code won't run again while we're already inside its invokation (which would happen when calling render loops)
class TransitionGuard {
private:
bool on_transition;
@ -11,7 +13,7 @@ namespace ui {
public:
TransitionGuard() : on_transition(false) {}
bool Run(std::function<void()> fn) {
inline bool Run(std::function<void()> fn) {
if(this->on_transition) {
return false;
}

View file

@ -43,7 +43,7 @@
"app_take_over_done": "This title was selected to be used for homebrew launching.",
"app_unexpected_error": "A title failed to start, crashed or unexpectedly terminated.\n(Could it be corrupted? If it is a gamecard title, do you have it inserted?)",
"ulaunch_about": "About uLaunch",
"ulaunch_desc": "uLaunch is a free, open source, extended and homebrew-oriented HOME menu replacement.\nNote that some original HOME menu functionalities aren't implemented yet.\n\nIf you're looking for new uLaunch themes, check r/uLaunchThemes subreddit.\nIf you would like to contribute, check uLaunch's GitHub repository",
"ulaunch_desc": "uLaunch is an extended, homebrew-oriented, FOSS HOME menu replacement.\nNote that some original HOME menu functionalities aren't implemented yet.\n\nIf you're looking for new uLaunch themes, check the r/uLaunchThemes subreddit.\nIf you would like to contribute, check uLaunch's GitHub repository",
"control_minus": "Swap the main menu",
"suspended_app": "Suspended title",
"suspended_close": "Would you like to close this title? All unsaved data will be lost.",

View file

@ -1,8 +1,3 @@
#include <pu/Plutonium>
#include <ctime>
#include <chrono>
#include <sstream>
#include <thread>
#include <db/db_Save.hpp>
#include <fs/fs_Stdio.hpp>
#include <cfg/cfg_Config.hpp>
@ -13,7 +8,7 @@
#include <util/util_Convert.hpp>
#include <am/am_LibraryApplet.hpp>
#include <am/am_DaemonMessages.hpp>
#include <am/am_LibAppletWrap.hpp>
#include <am/am_LibnxLibappletWrap.hpp>
#include <am/am_LibraryAppletUtils.hpp>
extern "C" {
@ -29,7 +24,6 @@ extern "C" {
ui::MenuApplication::Ref g_MenuApplication;
ui::TransitionGuard g_TransitionGuard;
u8 *g_ScreenCaptureBuffer;
cfg::TitleList g_EntryList;
std::vector<cfg::TitleRecord> g_HomebrewRecordList;
@ -44,15 +38,15 @@ char g_FwVersion[0x18] = {};
namespace {
void Initialize() {
UL_ASSERT(accountInitialize(AccountServiceType_System));
UL_ASSERT(nsInitialize());
UL_ASSERT(net::Initialize());
UL_ASSERT(psmInitialize());
UL_ASSERT(setsysInitialize());
UL_ASSERT(setInitialize());
UL_RC_ASSERT(accountInitialize(AccountServiceType_System));
UL_RC_ASSERT(nsInitialize());
UL_RC_ASSERT(net::Initialize());
UL_RC_ASSERT(psmInitialize());
UL_RC_ASSERT(setsysInitialize());
UL_RC_ASSERT(setInitialize());
// Initialize uDaemon message handling
UL_ASSERT(am::InitializeDaemonMessageHandler());
UL_RC_ASSERT(am::InitializeDaemonMessageHandler());
// Load menu config and theme
g_Config = cfg::LoadConfig();
@ -77,25 +71,24 @@ namespace {
int main() {
auto start_mode = dmi::MenuStartMode::Invalid;
UL_ASSERT(am::ReadStartMode(start_mode));
UL_RC_ASSERT(am::ReadStartMode(start_mode));
UL_ASSERT_TRUE(start_mode != dmi::MenuStartMode::Invalid);
// Information sent as an extra storage to uMenu
dmi::DaemonStatus status = {};
UL_ASSERT(am::ReadDataFromStorage(&status, sizeof(status)));
UL_RC_ASSERT(am::ReadDataFromStorage(&status, sizeof(status)));
memcpy(g_FwVersion, status.fw_version, sizeof(g_FwVersion));
// Check if our RomFs data exists...
if(!fs::ExistsFile(UL_MENU_ROMFS_BIN)) {
UL_ASSERT(menu::ResultRomfsFileNotFound);
UL_RC_ASSERT(menu::ResultRomfsFileNotFound);
}
// Try to mount it
UL_ASSERT(romfsMountFromFsdev(UL_MENU_ROMFS_BIN, 0, "romfs"));
UL_RC_ASSERT(romfsMountFromFsdev(UL_MENU_ROMFS_BIN, 0, "romfs"));
// After initializing RomFs, start initializing the rest of stuff here
g_ScreenCaptureBuffer = new u8[RawRGBAScreenBufferSize]();
Initialize();
// Cache title and homebrew icons
@ -105,31 +98,35 @@ int main() {
// Get system language and load translations (default one if not present)
u64 lang_code = 0;
UL_ASSERT(setGetLanguageCode(&lang_code));
std::string sys_lang = reinterpret_cast<char*>(&lang_code);
auto lang_path = cfg::GetLanguageJSONPath(sys_lang);
UL_ASSERT(util::LoadJSONFromFile(g_DefaultLanguage, CFG_LANG_DEFAULT));
UL_RC_ASSERT(setGetLanguageCode(&lang_code));
const auto lang_path = cfg::GetLanguageJSONPath(reinterpret_cast<char*>(&lang_code));
UL_RC_ASSERT(util::LoadJSONFromFile(g_DefaultLanguage, CFG_LANG_DEFAULT));
g_MainLanguage = g_DefaultLanguage;
if(fs::ExistsFile(lang_path)) {
auto lang_json = JSON::object();
UL_ASSERT(util::LoadJSONFromFile(lang_json, lang_path));
UL_RC_ASSERT(util::LoadJSONFromFile(lang_json, lang_path));
g_MainLanguage = lang_json;
}
// Get the text sizes to initialize default fonts
auto uijson = JSON::object();
UL_ASSERT(util::LoadJSONFromFile(uijson, cfg::GetAssetByTheme(g_Theme, "ui/UI.json")));
const auto menu_folder_txt_sz = uijson.value<u32>("menu_folder_text_size", 25);
auto ui_json = JSON::object();
UL_RC_ASSERT(util::LoadJSONFromFile(ui_json, cfg::GetAssetByTheme(g_Theme, "ui/UI.json")));
const auto menu_folder_text_size = ui_json.value<u32>("menu_folder_text_size", 25);
const auto default_font_path = cfg::GetAssetByTheme(g_Theme, "ui/Font.ttf");
auto renderer = pu::ui::render::Renderer::New(pu::ui::render::RendererInitOptions(SDL_INIT_EVERYTHING, pu::ui::render::RendererHardwareFlags).WithIMG(pu::ui::render::IMGAllFlags).WithMixer(pu::ui::render::MixerAllFlags).WithTTF(default_font_path).WithDefaultFontSize(menu_folder_txt_sz));
auto renderer_opts = pu::ui::render::RendererInitOptions(SDL_INIT_EVERYTHING, pu::ui::render::RendererHardwareFlags);
renderer_opts.UseTTF(default_font_path);
renderer_opts.UseImage(pu::ui::render::IMGAllFlags);
renderer_opts.UseAudio(pu::ui::render::MixerAllFlags);
renderer_opts.SetExtraDefaultFontSize(menu_folder_text_size);
auto renderer = pu::ui::render::Renderer::New(renderer_opts);
g_MenuApplication = ui::MenuApplication::New(renderer);
g_MenuApplication->SetInformation(start_mode, status, uijson);
g_MenuApplication->SetInformation(start_mode, status, ui_json);
g_MenuApplication->Prepare();
// Register handlers for HOME button press detection
am::RegisterLibAppletHomeButtonDetection();
am::RegisterLibnxLibappletHomeButtonDetection();
ui::MenuApplication::RegisterHomeButtonDetection();
ui::QuickMenu::RegisterHomeButtonDetection();
@ -143,7 +140,6 @@ int main() {
// Exit RomFs manually, since we also initialized it manually
romfsExit();
delete[] g_ScreenCaptureBuffer;
Exit();
return 0;
}

View file

@ -20,8 +20,8 @@ namespace {
return ResultSuccess;
}
R_TRY(smGetService(&g_DaemonPrivateService, PrivateServiceName));
R_TRY(daemonPrivateInitialize(&g_DaemonPrivateService));
UL_RC_TRY(smGetService(&g_DaemonPrivateService, PrivateServiceName));
UL_RC_TRY(daemonPrivateInitialize(&g_DaemonPrivateService));
return ResultSuccess;
}
@ -30,9 +30,9 @@ namespace {
serviceClose(&g_DaemonPrivateService);
}
dmi::MenuMessage daemonGetMessage() {
dmi::MenuMessage daemonPrivateServiceGetMessage() {
auto msg = dmi::MenuMessage::Invalid;
UL_ASSERT(daemonPrivateGetMessage(&g_DaemonPrivateService, &msg));
UL_RC_ASSERT(daemonPrivateGetMessage(&g_DaemonPrivateService, &msg));
return msg;
}
@ -42,7 +42,6 @@ namespace am {
namespace {
bool g_Initialized = false;
std::atomic_bool g_ReceiveThreadShouldStop = false;
Thread g_ReceiverThread;
@ -55,14 +54,16 @@ namespace am {
break;
}
const auto last_msg = daemonGetMessage();
mutexLock(&g_CallbackTableLock);
for(auto &[cb, msg] : g_MessageCallbackTable) {
{
const auto last_msg = daemonPrivateServiceGetMessage();
ScopedLock lk(g_CallbackTableLock);
for(const auto &[cb, msg] : g_MessageCallbackTable) {
if(msg == last_msg) {
cb();
}
}
mutexUnlock(&g_CallbackTableLock);
}
svcSleepThread(10'000'000ul);
}
@ -74,11 +75,11 @@ namespace am {
if(g_Initialized) {
return ResultSuccess;
}
R_TRY(daemonInitializePrivateService());
UL_RC_TRY(daemonInitializePrivateService());
g_ReceiveThreadShouldStop = false;
R_TRY(threadCreate(&g_ReceiverThread, &DaemonMessageReceiverThread, nullptr, nullptr, 0x1000, 49, -2));
R_TRY(threadStart(&g_ReceiverThread));
UL_RC_TRY(threadCreate(&g_ReceiverThread, &DaemonMessageReceiverThread, nullptr, nullptr, 0x1000, 49, -2));
UL_RC_TRY(threadStart(&g_ReceiverThread));
g_Initialized = true;
return ResultSuccess;
@ -97,10 +98,10 @@ namespace am {
g_Initialized = false;
}
void RegisterOnMessageDetect(OnMessageCallback callback, dmi::MenuMessage desired_msg) {
mutexLock(&g_CallbackTableLock);
g_MessageCallbackTable.push_back(std::make_pair(callback, desired_msg));
mutexUnlock(&g_CallbackTableLock);
void RegisterOnMessageDetect(OnMessageCallback callback, const dmi::MenuMessage desired_msg) {
ScopedLock lk(g_CallbackTableLock);
g_MessageCallbackTable.push_back({ callback, desired_msg });
}
}

View file

@ -1,26 +1,23 @@
#include <dmi/dmi_DaemonMenuInteraction.hpp>
#include <am/am_LibAppletWrap.hpp>
#include <am/am_LibnxLibappletWrap.hpp>
#include <am/am_DaemonMessages.hpp>
namespace {
Mutex g_LibAppletWrapLock = {};
bool g_IsAppletRunning = false;
bool g_DetectionHomePressed = false;
std::atomic_bool g_IsAppletRunning = false;
std::atomic_bool g_DetectionHomePressed = false;
}
namespace am {
void OnHomeButtonDetection() {
mutexLock(&g_LibAppletWrapLock);
if(g_IsAppletRunning) {
g_DetectionHomePressed = true;
}
mutexUnlock(&g_LibAppletWrapLock);
}
void RegisterLibAppletHomeButtonDetection() {
void RegisterLibnxLibappletHomeButtonDetection() {
RegisterOnMessageDetect(&OnHomeButtonDetection, dmi::MenuMessage::HomeRequest);
}
@ -28,14 +25,12 @@ namespace am {
extern "C" {
// Wrap libappletStart and libappletLaunch to use our custom waiting system
// Wrap libappletStart and libappletLaunch to use our custom waiting system (mostly so that they respond to HOME menu presses)
Result __wrap_libappletStart(AppletHolder *h) {
R_TRY(appletHolderStart(h));
UL_RC_TRY(appletHolderStart(h));
mutexLock(&g_LibAppletWrapLock);
g_IsAppletRunning = true;
mutexUnlock(&g_LibAppletWrapLock);
while(true) {
if(appletHolderCheckFinished(h)) {
@ -45,10 +40,8 @@ extern "C" {
break;
}
mutexLock(&g_LibAppletWrapLock);
auto home_pressed = g_DetectionHomePressed;
const bool home_pressed = g_DetectionHomePressed;
g_DetectionHomePressed = false;
mutexUnlock(&g_LibAppletWrapLock);
if(home_pressed) {
appletHolderRequestExitOrTerminate(h, 15'000'000'000ul);
@ -60,32 +53,31 @@ extern "C" {
appletHolderJoin(h);
auto rc = ResultSuccess;
auto reason = appletHolderGetExitReason(h);
const auto reason = appletHolderGetExitReason(h);
if(reason == LibAppletExitReason_Canceled || reason == LibAppletExitReason_Abnormal || reason == LibAppletExitReason_Unexpected) {
rc = MAKERESULT(Module_Libnx, LibnxError_LibAppletBadExit);
}
mutexLock(&g_LibAppletWrapLock);
g_IsAppletRunning = false;
mutexUnlock(&g_LibAppletWrapLock);
return rc;
}
Result __wrap_libappletLaunch(AppletId id, LibAppletArgs *commonargs, const void* arg, size_t arg_size, void* reply, size_t reply_size, size_t *out_reply_size) {
// Note: libappletLaunch is exactly the same but we redefine it so that it calls our libappletStart (libnx is already compiled, the libappletStart wrap wouldn't affect libappletLaunch otherwise)
Result __wrap_libappletLaunch(AppletId id, LibAppletArgs *common_args, const void* arg, size_t arg_size, void* reply, size_t reply_size, size_t *out_reply_size) {
AppletHolder holder;
R_TRY(appletCreateLibraryApplet(&holder, id, LibAppletMode_AllForeground));
UL_RC_TRY(appletCreateLibraryApplet(&holder, id, LibAppletMode_AllForeground));
UL_ON_SCOPE_EXIT({
appletHolderClose(&holder);
});
R_TRY(libappletArgsPush(commonargs, &holder));
UL_RC_TRY(libappletArgsPush(common_args, &holder));
if(arg && arg_size) {
R_TRY(libappletPushInData(&holder, arg, arg_size));
UL_RC_TRY(libappletPushInData(&holder, arg, arg_size));
}
R_TRY(__wrap_libappletStart(&holder));
UL_RC_TRY(libappletStart(&holder));
if(reply && reply_size) {
R_TRY(libappletPopOutData(&holder, reply, reply_size, out_reply_size));
UL_RC_TRY(libappletPopOutData(&holder, reply, reply_size, out_reply_size));
}
return ResultSuccess;
}

View file

@ -2,19 +2,18 @@
namespace am {
Result ReadDataFromStorage(void *data, size_t data_size) {
Result ReadDataFromStorage(void *out_data, const size_t data_size) {
AppletStorage st;
R_TRY(appletPopInData(&st));
UL_ON_SCOPE_EXIT({
appletStorageClose(&st);
});
R_TRY(appletStorageRead(&st, 0, data, data_size));
UL_RC_TRY(appletPopInData(&st));
UL_ON_SCOPE_EXIT({ appletStorageClose(&st); });
UL_RC_TRY(appletStorageRead(&st, 0, out_data, data_size));
return ResultSuccess;
}
Result ReadStartMode(dmi::MenuStartMode &out_start_mode) {
LibAppletArgs args = {};
R_TRY(ReadDataFromStorage(&args, sizeof(args)));
UL_RC_TRY(ReadDataFromStorage(&args, sizeof(args)));
out_start_mode = static_cast<dmi::MenuStartMode>(args.LaVersion);
return ResultSuccess;
}

View file

@ -39,27 +39,28 @@ namespace ui::actions {
}
void ShowUserMenu() {
auto uid = g_MenuApplication->GetSelectedUser();
const auto uid = g_MenuApplication->GetSelectedUser();
std::string name;
os::GetAccountName(name, uid);
auto sopt = g_MenuApplication->CreateShowDialog(GetLanguageString("user_settings"), GetLanguageString("user_selected") + ": " + name + "\n" + GetLanguageString("user_option"), { GetLanguageString("user_view_page"), GetLanguageString("user_logoff"), GetLanguageString("cancel") }, true, os::GetIconCacheImagePath(uid));
if(sopt == 0) {
os::GetAccountName(uid, name);
const auto option = g_MenuApplication->CreateShowDialog(GetLanguageString("user_settings"), GetLanguageString("user_selected") + ": " + name + "\n" + GetLanguageString("user_option"), { GetLanguageString("user_view_page"), GetLanguageString("user_logoff"), GetLanguageString("cancel") }, true, os::GetIconCacheImagePath(uid));
if(option == 0) {
friendsLaShowMyProfileForHomeMenu(uid);
}
else if(sopt == 1) {
u32 logoff = 0;
else if(option == 1) {
auto log_off = false;
if(g_MenuApplication->IsSuspended()) {
auto sopt = g_MenuApplication->CreateShowDialog(GetLanguageString("suspended_app"), GetLanguageString("user_logoff_app_suspended"), { GetLanguageString("yes"), GetLanguageString("cancel") }, true);
if(sopt == 0) {
logoff = 2;
const auto option_2 = g_MenuApplication->CreateShowDialog(GetLanguageString("suspended_app"), GetLanguageString("user_logoff_app_suspended"), { GetLanguageString("yes"), GetLanguageString("cancel") }, true);
if(option_2 == 0) {
log_off = true;
}
}
else {
logoff = 1;
log_off = true;
}
if(logoff > 0) {
if(log_off) {
auto &menu_lyt = g_MenuApplication->GetMenuLayout();
if(logoff == 2) {
if(g_MenuApplication->IsSuspended()) {
menu_lyt->DoTerminateApplication();
}
@ -95,8 +96,8 @@ namespace ui::actions {
char url[500] = {0};
swkbdShow(&swkbd, url, 500);
UL_ASSERT(dmi::menu::SendCommand(dmi::DaemonMessage::OpenWebPage, [&](dmi::menu::MenuScopedStorageWriter &writer) {
R_TRY(writer.PushData(url, sizeof(url)));
UL_RC_ASSERT(dmi::menu::SendCommand(dmi::DaemonMessage::OpenWebPage, [&](dmi::menu::MenuScopedStorageWriter &writer) {
UL_RC_TRY(writer.PushData(url, sizeof(url)));
return ResultSuccess;
},
[&](dmi::menu::MenuScopedStorageReader &reader) {
@ -122,7 +123,7 @@ namespace ui::actions {
}
void ShowAlbumApplet() {
UL_ASSERT(dmi::menu::SendCommand(dmi::DaemonMessage::OpenAlbum, [&](dmi::menu::MenuScopedStorageWriter &writer) {
UL_RC_ASSERT(dmi::menu::SendCommand(dmi::DaemonMessage::OpenAlbum, [&](dmi::menu::MenuScopedStorageWriter &writer) {
// ...
return ResultSuccess;
},

View file

@ -6,80 +6,40 @@
namespace ui {
s32 ClickableImage::GetX() {
return this->x;
}
void ClickableImage::SetX(s32 x) {
this->x = x;
}
s32 ClickableImage::GetY() {
return this->y;
}
void ClickableImage::SetY(s32 y) {
this->y = y;
}
s32 ClickableImage::GetWidth() {
return this->w;
}
void ClickableImage::SetWidth(s32 w) {
this->w = w;
}
s32 ClickableImage::GetHeight() {
return this->h;
}
void ClickableImage::SetHeight(s32 h) {
this->h = h;
}
std::string ClickableImage::GetImage() {
return this->img;
ClickableImage::~ClickableImage() {
pu::ui::render::DeleteTexture(this->img_tex);
}
void ClickableImage::SetImage(const std::string &img) {
this->Dispose();
pu::ui::render::DeleteTexture(this->img_tex);
if(fs::ExistsFile(img)) {
this->img = img;
this->ntex = pu::ui::render::LoadImage(img);
this->w = pu::ui::render::GetTextureWidth(this->ntex);
this->h = pu::ui::render::GetTextureHeight(this->ntex);
this->img_tex = pu::ui::render::LoadImage(img);
this->w = pu::ui::render::GetTextureWidth(this->img_tex);
this->h = pu::ui::render::GetTextureHeight(this->img_tex);
}
}
bool ClickableImage::IsImageValid() {
return (ntex != nullptr) && !this->img.empty();
void ClickableImage::OnRender(pu::ui::render::Renderer::Ref &drawer, const s32 x, const s32 y) {
drawer->RenderTexture(this->img_tex, x, y, pu::ui::render::TextureRenderOptions::WithCustomDimensions(this->w, this->h));
}
void ClickableImage::SetOnClick(std::function<void()> cb) {
this->cb = cb;
}
void ClickableImage::OnRender(pu::ui::render::Renderer::Ref &drawer, s32 x, s32 y) {
drawer->RenderTexture(this->ntex, x, y, { -1, w, h, -1 });
}
void ClickableImage::OnInput(u64 down, u64 up, u64 held, pu::ui::Touch touch_pos) {
void ClickableImage::OnInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const pu::ui::TouchPoint touch_pos) {
if(this->touched) {
auto tpnow = std::chrono::steady_clock::now();
auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(tpnow - this->touchtp).count();
if(diff >= 200) {
const auto tp_now = std::chrono::steady_clock::now();
const u64 diff = std::chrono::duration_cast<std::chrono::milliseconds>(tp_now - this->touch_tp).count();
if(diff >= TouchActionTimeMilliseconds) {
this->touched = false;
if(this->cb) {
(this->cb)();
SDL_SetTextureColorMod(this->ntex, 255, 255, 255);
}
SDL_SetTextureColorMod(this->img_tex, 0xFF, 0xFF, 0xFF);
}
}
else if(!touch_pos.IsEmpty()) {
if((touch_pos.X >= this->GetProcessedX()) && (touch_pos.X < (this->GetProcessedX() + w)) && (touch_pos.Y >= this->GetProcessedY()) && (touch_pos.Y < (this->GetProcessedY() + h))) {
this->touchtp = std::chrono::steady_clock::now();
else if(touch_pos.HitsRegion(this->GetProcessedX(), this->GetProcessedY(), this->w, this->h)) {
this->touch_tp = std::chrono::steady_clock::now();
this->touched = true;
SDL_SetTextureColorMod(this->ntex, 200, 200, 255);
}
SDL_SetTextureColorMod(this->img_tex, RedColorMod, GreenColorMod, BlueColorMod);
}
}

View file

@ -7,7 +7,7 @@ namespace ui {
this->SetOnInput(std::bind(&IMenuLayout::OnInput, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
}
void IMenuLayout::OnInput(u64 down, u64 up, u64 held, pu::ui::Touch touch_pos) {
void IMenuLayout::OnInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const pu::ui::TouchPoint touch_pos) {
if(this->home_pressed) {
if(this->OnHomeButtonPress()) {
// Input consumed
@ -21,7 +21,7 @@ namespace ui {
}
*/
this->OnMenuInput(down, up, held, touch_pos);
this->OnMenuInput(keys_down, keys_up, keys_held, touch_pos);
}
void IMenuLayout::DoOnHomeButtonPress() {

View file

@ -18,24 +18,19 @@ namespace ui {
LanguagesMenuLayout::LanguagesMenuLayout() {
this->SetBackgroundImage(cfg::GetAssetByTheme(g_Theme, "ui/Background.png"));
pu::ui::Color textclr = pu::ui::Color::FromHex(g_MenuApplication->GetUIConfigValue<std::string>("text_color", "#e1e1e1ff"));
pu::ui::Color menufocusclr = pu::ui::Color::FromHex(g_MenuApplication->GetUIConfigValue<std::string>("menu_focus_color", "#5ebcffff"));
pu::ui::Color menubgclr = pu::ui::Color::FromHex(g_MenuApplication->GetUIConfigValue<std::string>("menu_bg_color", "#0094ffff"));
this->info_text = pu::ui::elm::TextBlock::New(0, 100, GetLanguageString("lang_info_text"));
this->info_text->SetColor(g_MenuApplication->GetTextColor());
this->info_text->SetHorizontalAlign(pu::ui::elm::HorizontalAlign::Center);
g_MenuApplication->ApplyConfigForElement("languages_menu", "info_text", this->info_text);
this->Add(this->info_text);
this->infoText = pu::ui::elm::TextBlock::New(0, 100, GetLanguageString("lang_info_text"));
this->infoText->SetColor(textclr);
this->infoText->SetHorizontalAlign(pu::ui::elm::HorizontalAlign::Center);
g_MenuApplication->ApplyConfigForElement("languages_menu", "info_text", this->infoText);
this->Add(this->infoText);
this->langsMenu = pu::ui::elm::Menu::New(200, 160, 880, menubgclr, 100, 4);
this->langsMenu->SetOnFocusColor(menufocusclr);
g_MenuApplication->ApplyConfigForElement("languages_menu", "languages_menu_item", this->langsMenu);
this->Add(this->langsMenu);
this->langs_menu = pu::ui::elm::Menu::New(200, 160, 880, g_MenuApplication->GetMenuBackgroundColor(), g_MenuApplication->GetMenuFocusColor(), 100, 4);
g_MenuApplication->ApplyConfigForElement("languages_menu", "languages_menu_item", this->langs_menu);
this->Add(this->langs_menu);
}
void LanguagesMenuLayout::OnMenuInput(u64 down, u64 up, u64 held, pu::ui::Touch touch_pos) {
if(down & HidNpadButton_B) {
void LanguagesMenuLayout::OnMenuInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const pu::ui::TouchPoint touch_pos) {
if(keys_down & HidNpadButton_B) {
g_TransitionGuard.Run([]() {
g_MenuApplication->FadeOut();
g_MenuApplication->LoadSettingsMenu();
@ -53,45 +48,39 @@ namespace ui {
}
void LanguagesMenuLayout::Reload() {
this->langsMenu->ClearItems();
this->langsMenu->SetSelectedIndex(0);
auto textclr = pu::ui::Color::FromHex(g_MenuApplication->GetUIConfigValue<std::string>("text_color", "#e1e1e1ff"));
u64 lcode = 0;
auto ilang = SetLanguage_ENUS;
setGetLanguageCode(&lcode);
setMakeLanguage(lcode, &ilang);
this->langs_menu->ClearItems();
const auto sys_lang = os::GetSystemLanguage();
for(u32 i = 0; i < os::LanguageNameCount; i++) {
std::string name = os::LanguageNameList[i];
if(static_cast<u32>(ilang) == i) {
if(static_cast<u32>(sys_lang) == i) {
name += " " + GetLanguageString("lang_selected");
}
auto litm = pu::ui::elm::MenuItem::New(name);
litm->SetColor(textclr);
litm->AddOnClick(std::bind(&LanguagesMenuLayout::lang_Click, this, i));
this->langsMenu->AddItem(litm);
}
auto lang_item = pu::ui::elm::MenuItem::New(name);
lang_item->SetColor(g_MenuApplication->GetTextColor());
lang_item->AddOnKey(std::bind(&LanguagesMenuLayout::lang_DefaultKey, this, i));
this->langs_menu->AddItem(lang_item);
}
void LanguagesMenuLayout::lang_Click(u32 idx) {
u64 lcode = 0;
SetLanguage ilang = SetLanguage_ENUS;
setGetLanguageCode(&lcode);
setMakeLanguage(lcode, &ilang);
this->langs_menu->SetSelectedIndex(0);
}
if(static_cast<u32>(ilang) == idx) {
void LanguagesMenuLayout::lang_DefaultKey(const u32 idx) {
// TODO: cache system language...
const auto sys_lang = os::GetSystemLanguage();
if(static_cast<u32>(sys_lang) == idx) {
g_MenuApplication->ShowNotification(GetLanguageString("lang_active_this"));
}
else {
auto sopt = g_MenuApplication->CreateShowDialog(GetLanguageString("lang_set"), GetLanguageString("lang_set_conf"), { GetLanguageString("yes"), GetLanguageString("no") }, true);
if(sopt == 0) {
u64 codes[16] = {0};
const auto option = g_MenuApplication->CreateShowDialog(GetLanguageString("lang_set"), GetLanguageString("lang_set_conf"), { GetLanguageString("yes"), GetLanguageString("no") }, true);
if(option == 0) {
u64 lang_codes[os::LanguageNameCount] = {};
s32 tmp;
setGetAvailableLanguageCodes(&tmp, codes, 16);
auto code = codes[this->langsMenu->GetSelectedIndex()];
setGetAvailableLanguageCodes(&tmp, lang_codes, os::LanguageNameCount);
const auto lang_code = lang_codes[this->langs_menu->GetSelectedIndex()];
auto rc = setsysSetLanguageCode(code);
const auto rc = setsysSetLanguageCode(lang_code);
g_MenuApplication->CreateShowDialog(GetLanguageString("lang_set"), R_SUCCEEDED(rc) ? GetLanguageString("lang_set_ok") : GetLanguageString("lang_set_error") + ": " + util::FormatResult(rc), { GetLanguageString("ok") }, true);
if(R_SUCCEEDED(rc)) {
g_TransitionGuard.Run([]() {

View file

@ -3,7 +3,6 @@
extern ui::MenuApplication::Ref g_MenuApplication;
extern ui::TransitionGuard g_TransitionGuard;
extern u8 *g_ScreenCaptureBuffer;
extern cfg::Theme g_Theme;
@ -21,31 +20,37 @@ namespace ui {
}
void MenuApplication::OnLoad() {
u8 *screen_capture_buf = nullptr;
if(this->IsSuspended()) {
screen_capture_buf = new u8[PlainRgbaScreenBufferSize]();
bool flag;
appletGetLastApplicationCaptureImageEx(g_ScreenCaptureBuffer, RawRGBAScreenBufferSize, &flag);
appletGetLastApplicationCaptureImageEx(screen_capture_buf, PlainRgbaScreenBufferSize, &flag);
}
auto jbgm = JSON::object();
util::LoadJSONFromFile(jbgm, cfg::GetAssetByTheme(g_Theme, "sound/BGM.json"));
this->bgmjson = jbgm;
this->bgm_loop = this->bgmjson.value("loop", true);
this->bgm_fade_in_ms = this->bgmjson.value("fade_in_ms", 1500);
this->bgm_fade_out_ms = this->bgmjson.value("fade_out_ms", 500);
this->bgm_json = JSON::object();
util::LoadJSONFromFile(this->bgm_json, cfg::GetAssetByTheme(g_Theme, "sound/BGM.json"));
this->bgm_loop = this->bgm_json.value("loop", true);
this->bgm_fade_in_ms = this->bgm_json.value("fade_in_ms", 1500);
this->bgm_fade_out_ms = this->bgm_json.value("fade_out_ms", 500);
auto toasttextclr = pu::ui::Color::FromHex(GetUIConfigValue<std::string>("toast_text_color", "#e1e1e1ff"));
auto toastbaseclr = pu::ui::Color::FromHex(GetUIConfigValue<std::string>("toast_base_color", "#282828ff"));
this->notifToast = pu::ui::extras::Toast::New("...", "DefaultFont@20", toasttextclr, toastbaseclr);
const auto toast_text_clr = pu::ui::Color::FromHex(GetUIConfigValue<std::string>("toast_text_color", "#e1e1e1ff"));
const auto toast_base_clr = pu::ui::Color::FromHex(GetUIConfigValue<std::string>("toast_base_color", "#282828ff"));
this->notif_toast = pu::ui::extras::Toast::New("...", pu::ui::GetDefaultFont(pu::ui::DefaultFontSize::Medium), toast_text_clr, toast_base_clr);
this->bgm = pu::audio::Open(cfg::GetAssetByTheme(g_Theme, "sound/BGM.mp3"));
this->bgm = pu::audio::OpenMusic(cfg::GetAssetByTheme(g_Theme, "sound/BGM.mp3"));
this->startupLayout = StartupLayout::New();
this->menuLayout = MenuLayout::New(g_ScreenCaptureBuffer, this->uijson.value("suspended_final_alpha", 80));
this->themeMenuLayout = ThemeMenuLayout::New();
this->settingsMenuLayout = SettingsMenuLayout::New();
this->languagesMenuLayout = LanguagesMenuLayout::New();
this->text_clr = pu::ui::Color::FromHex(this->GetUIConfigValue<std::string>("text_color", "#e1e1e1ff"));
this->menu_focus_clr = pu::ui::Color::FromHex(this->GetUIConfigValue<std::string>("menu_focus_color", "#5ebcffff"));
this->menu_bg_clr = pu::ui::Color::FromHex(this->GetUIConfigValue<std::string>("menu_bg_color", "#0094ffff"));
switch(this->stmode) {
const u8 suspended_final_alpha = this->ui_json.value("suspended_final_alpha", 80);
this->startup_lyt = StartupLayout::New();
this->menu_lyt = MenuLayout::New(screen_capture_buf, suspended_final_alpha);
this->theme_menu_lyt = ThemeMenuLayout::New();
this->settings_menu_lyt = SettingsMenuLayout::New();
this->languages_menu_lyt = LanguagesMenuLayout::New();
switch(this->start_mode) {
case dmi::MenuStartMode::StartupScreen: {
this->LoadStartupMenu();
break;
@ -58,124 +63,37 @@ namespace ui {
}
}
void MenuApplication::SetInformation(dmi::MenuStartMode mode, dmi::DaemonStatus status, JSON ui_json) {
this->stmode = mode;
this->status = status;
this->uijson = ui_json;
}
void MenuApplication::LoadMenu() {
this->menuLayout->SetUser(this->status.selected_user);
this->LoadLayout(this->menuLayout);
this->loaded_menu = MenuType::Main;
}
void MenuApplication::LoadStartupMenu() {
this->StopPlayBGM();
this->startupLayout->ReloadMenu();
this->LoadLayout(this->startupLayout);
this->loaded_menu = MenuType::Startup;
}
void MenuApplication::LoadThemeMenu() {
this->themeMenuLayout->Reload();
this->LoadLayout(this->themeMenuLayout);
this->loaded_menu = MenuType::Theme;
}
void MenuApplication::LoadSettingsMenu() {
this->settingsMenuLayout->Reload();
this->LoadLayout(this->settingsMenuLayout);
this->loaded_menu = MenuType::Settings;
}
void MenuApplication::LoadSettingsLanguagesMenu() {
this->languagesMenuLayout->Reload();
this->LoadLayout(this->languagesMenuLayout);
this->loaded_menu = MenuType::Languages;
}
bool MenuApplication::IsSuspended() {
return this->IsTitleSuspended() || this->IsHomebrewSuspended();
}
bool MenuApplication::IsTitleSuspended() {
return this->status.app_id > 0;
}
bool MenuApplication::IsHomebrewSuspended() {
return strlen(this->status.params.nro_path);
}
bool MenuApplication::EqualsSuspendedHomebrewPath(const std::string &path) {
return this->status.params.nro_path == path;
}
u64 MenuApplication::GetSuspendedApplicationId() {
return this->status.app_id;
}
void MenuApplication::NotifyEndSuspended() {
// Blanking the whole status would also blank the selected user...
this->status.params = {};
this->status.app_id = 0;
}
bool MenuApplication::LaunchFailed() {
return this->stmode == dmi::MenuStartMode::MenuLaunchFailure;
}
void MenuApplication::ShowNotification(const std::string &text, u64 timeout) {
void MenuApplication::ShowNotification(const std::string &text, const u64 timeout) {
this->EndOverlay();
this->notifToast->SetText(text);
this->StartOverlayWithTimeout(this->notifToast, timeout);
this->notif_toast->SetText(text);
this->StartOverlayWithTimeout(this->notif_toast, timeout);
}
void MenuApplication::StartPlayBGM() {
if(this->bgm != nullptr) {
int loops = this->bgm_loop ? -1 : 1;
const int loops = this->bgm_loop ? -1 : 1;
if(this->bgm_fade_in_ms > 0) {
pu::audio::PlayWithFadeIn(this->bgm, loops, this->bgm_fade_in_ms);
pu::audio::PlayMusicWithFadeIn(this->bgm, loops, this->bgm_fade_in_ms);
}
else {
pu::audio::Play(this->bgm, loops);
pu::audio::PlayMusic(this->bgm, loops);
}
}
}
void MenuApplication::StopPlayBGM() {
if(this->bgm_fade_out_ms > 0) {
pu::audio::FadeOut(this->bgm_fade_out_ms);
pu::audio::FadeOutMusic(this->bgm_fade_out_ms);
}
else {
pu::audio::Stop();
pu::audio::StopMusic();
}
}
StartupLayout::Ref &MenuApplication::GetStartupLayout() {
return this->startupLayout;
}
void MenuApplication::SetSelectedUser(const AccountUid user_id) {
this->daemon_status.selected_user = user_id;
MenuLayout::Ref &MenuApplication::GetMenuLayout() {
return this->menuLayout;
}
ThemeMenuLayout::Ref &MenuApplication::GetThemeMenuLayout() {
return this->themeMenuLayout;
}
SettingsMenuLayout::Ref &MenuApplication::GetSettingsMenuLayout() {
return this->settingsMenuLayout;
}
LanguagesMenuLayout::Ref &MenuApplication::GetLanguagesMenuLayout() {
return this->languagesMenuLayout;
}
void MenuApplication::SetSelectedUser(AccountUid user_id) {
this->status.selected_user = user_id;
UL_ASSERT(dmi::menu::SendCommand(dmi::DaemonMessage::SetSelectedUser, [&](dmi::menu::MenuScopedStorageWriter &writer) {
UL_RC_ASSERT(dmi::menu::SendCommand(dmi::DaemonMessage::SetSelectedUser, [&](dmi::menu::MenuScopedStorageWriter &writer) {
writer.Push(user_id);
return ResultSuccess;
},
@ -185,12 +103,4 @@ namespace ui {
}));
}
AccountUid MenuApplication::GetSelectedUser() {
return this->status.selected_user;
}
MenuType MenuApplication::GetCurrentLoadedMenu() {
return this->loaded_menu;
}
}

File diff suppressed because it is too large Load diff

View file

@ -14,134 +14,107 @@ namespace ui {
}
void QuickMenu::OnHomeButtonDetection() {
g_HomePressed = true;
}
QuickMenu::QuickMenu(const std::string &main_icon) {
this->on = false;
this->bgalpha = 0;
this->bg_alpha = 0;
auto textclr = pu::ui::Color::FromHex(g_MenuApplication->GetUIConfigValue<std::string>("text_color", "#e1e1e1ff"));
auto menufocusclr = pu::ui::Color::FromHex(g_MenuApplication->GetUIConfigValue<std::string>("menu_focus_color", "#5ebcffff"));
auto menubgclr = pu::ui::Color::FromHex(g_MenuApplication->GetUIConfigValue<std::string>("menu_bg_color", "#0094ffff"));
this->options_menu = pu::ui::elm::Menu::New(200, 115, 880, menubgclr, 60, 8);
this->options_menu->SetOnFocusColor(menufocusclr);
this->options_menu = pu::ui::elm::Menu::New(MenuX, MenuY, MenuWidth, g_MenuApplication->GetMenuBackgroundColor(), g_MenuApplication->GetMenuFocusColor(), MenuItemHeight, MenuItemsToShow);
g_MenuApplication->ApplyConfigForElement("quick_menu", "quick_menu_item", this->options_menu);
auto opt_item = pu::ui::elm::MenuItem::New("Help & information");
opt_item->SetIcon(cfg::GetAssetByTheme(g_Theme, "ui/HelpIcon.png"));
opt_item->AddOnClick(&actions::ShowHelpDialog);
opt_item->SetColor(textclr);
this->options_menu->AddItem(opt_item);
auto help_item = pu::ui::elm::MenuItem::New("Help & information");
help_item->SetIcon(cfg::GetAssetByTheme(g_Theme, "ui/HelpIcon.png"));
help_item->AddOnKey(&actions::ShowHelpDialog);
help_item->SetColor(g_MenuApplication->GetTextColor());
this->options_menu->AddItem(help_item);
opt_item = pu::ui::elm::MenuItem::New("Power options");
opt_item->SetIcon(cfg::GetAssetByTheme(g_Theme, "ui/PowerIcon.png"));
opt_item->AddOnClick(&actions::ShowPowerDialog);
opt_item->SetColor(textclr);
this->options_menu->AddItem(opt_item);
auto power_item = pu::ui::elm::MenuItem::New("Power options");
power_item->SetIcon(cfg::GetAssetByTheme(g_Theme, "ui/PowerIcon.png"));
power_item->AddOnKey(&actions::ShowPowerDialog);
power_item->SetColor(g_MenuApplication->GetTextColor());
this->options_menu->AddItem(power_item);
opt_item = pu::ui::elm::MenuItem::New("Controller options");
opt_item->SetIcon(cfg::GetAssetByTheme(g_Theme, "ui/ControllerIcon.png"));
opt_item->AddOnClick(&actions::ShowControllerSupport);
opt_item->SetColor(textclr);
this->options_menu->AddItem(opt_item);
auto controller_item = pu::ui::elm::MenuItem::New("Controller options");
controller_item->SetIcon(cfg::GetAssetByTheme(g_Theme, "ui/ControllerIcon.png"));
controller_item->AddOnKey(&actions::ShowControllerSupport);
controller_item->SetColor(g_MenuApplication->GetTextColor());
this->options_menu->AddItem(controller_item);
opt_item = pu::ui::elm::MenuItem::New("Open album");
opt_item->SetIcon(cfg::GetAssetByTheme(g_Theme, "ui/AlbumIcon.png"));
opt_item->AddOnClick(&actions::ShowAlbumApplet);
opt_item->SetColor(textclr);
this->options_menu->AddItem(opt_item);
auto album_item = pu::ui::elm::MenuItem::New("Open album");
album_item->SetIcon(cfg::GetAssetByTheme(g_Theme, "ui/AlbumIcon.png"));
album_item->AddOnKey(&actions::ShowAlbumApplet);
album_item->SetColor(g_MenuApplication->GetTextColor());
this->options_menu->AddItem(album_item);
opt_item = pu::ui::elm::MenuItem::New("Open web-page");
opt_item->SetIcon(cfg::GetAssetByTheme(g_Theme, "ui/WebIcon.png"));
opt_item->AddOnClick(&actions::ShowWebPage);
opt_item->SetColor(textclr);
this->options_menu->AddItem(opt_item);
auto web_item = pu::ui::elm::MenuItem::New("Open web-page");
web_item->SetIcon(cfg::GetAssetByTheme(g_Theme, "ui/WebIcon.png"));
web_item->AddOnKey(&actions::ShowWebPage);
web_item->SetColor(g_MenuApplication->GetTextColor());
this->options_menu->AddItem(web_item);
opt_item = pu::ui::elm::MenuItem::New("User menu");
opt_item->SetIcon(cfg::GetAssetByTheme(g_Theme, "ui/UserIcon.png"));
opt_item->AddOnClick(&actions::ShowUserMenu);
opt_item->SetColor(textclr);
this->options_menu->AddItem(opt_item);
auto user_item = pu::ui::elm::MenuItem::New("User menu");
user_item->SetIcon(cfg::GetAssetByTheme(g_Theme, "ui/UserIcon.png"));
user_item->AddOnKey(&actions::ShowUserMenu);
user_item->SetColor(g_MenuApplication->GetTextColor());
this->options_menu->AddItem(user_item);
opt_item = pu::ui::elm::MenuItem::New("Themes menu");
opt_item->SetIcon(cfg::GetAssetByTheme(g_Theme, "ui/ThemesIcon.png"));
opt_item->AddOnClick(&actions::ShowThemesMenu);
opt_item->SetColor(textclr);
this->options_menu->AddItem(opt_item);
auto themes_item = pu::ui::elm::MenuItem::New("Themes menu");
themes_item->SetIcon(cfg::GetAssetByTheme(g_Theme, "ui/ThemesIcon.png"));
themes_item->AddOnKey(&actions::ShowThemesMenu);
themes_item->SetColor(g_MenuApplication->GetTextColor());
this->options_menu->AddItem(themes_item);
opt_item = pu::ui::elm::MenuItem::New("Settings menu");
opt_item->SetIcon(cfg::GetAssetByTheme(g_Theme, "ui/SettingsIcon.png"));
opt_item->AddOnClick(&actions::ShowSettingsMenu);
opt_item->SetColor(textclr);
this->options_menu->AddItem(opt_item);
auto settings_item = pu::ui::elm::MenuItem::New("Settings menu");
settings_item->SetIcon(cfg::GetAssetByTheme(g_Theme, "ui/SettingsIcon.png"));
settings_item->AddOnKey(&actions::ShowSettingsMenu);
settings_item->SetColor(g_MenuApplication->GetTextColor());
this->options_menu->AddItem(settings_item);
}
s32 QuickMenu::GetX() {
return 0;
}
s32 QuickMenu::GetY() {
return 0;
}
s32 QuickMenu::GetWidth() {
return 1280;
}
s32 QuickMenu::GetHeight() {
return 720;
}
void QuickMenu::Toggle() {
this->on = !this->on;
}
bool QuickMenu::IsOn() {
return this->on && (this->bgalpha > 0);
}
void QuickMenu::OnRender(pu::ui::render::Renderer::Ref &drawer, s32 x, s32 y) {
void QuickMenu::OnRender(pu::ui::render::Renderer::Ref &drawer, const s32 x, const s32 y) {
if(!this->on) {
if(this->bgalpha > 0) {
this->bgalpha -= 20;
if(this->bgalpha < 0) {
this->bgalpha = 0;
if(this->bg_alpha > 0) {
this->bg_alpha -= BackgroundAlphaIncrement;
if(this->bg_alpha < 0) {
this->bg_alpha = 0;
}
}
}
else {
if(this->bgalpha < 220) {
this->bgalpha += 20;
if(this->bgalpha > 220) {
this->bgalpha = 220;
if(this->bg_alpha < BackgroundAlphaMax) {
this->bg_alpha += BackgroundAlphaIncrement;
if(this->bg_alpha > BackgroundAlphaMax) {
this->bg_alpha = BackgroundAlphaMax;
}
}
}
this->options_menu->SetVisible(this->bgalpha != 0);
this->options_menu->SetVisible(this->bg_alpha != 0);
auto bgalphau8 = static_cast<u8>(this->bgalpha);
drawer->RenderRectangleFill(MakeBackgroundColor(static_cast<u8>(this->bg_alpha)), this->GetX(), this->GetY(), this->GetWidth(), this->GetHeight());
drawer->RenderRectangleFill({ 50, 50, 50, bgalphau8 }, 0, 0, 1280, 720);
if(this->bgalpha > 0) {
if(this->bgalpha < 220) {
drawer->SetBaseRenderAlpha(bgalphau8);
if(this->bg_alpha > 0) {
if(this->bg_alpha < BackgroundAlphaMax) {
drawer->SetBaseRenderAlpha(static_cast<u8>(this->bg_alpha));
}
this->options_menu->OnRender(drawer, this->options_menu->GetProcessedX(), this->options_menu->GetProcessedY());
if(this->bgalpha < 220) {
drawer->UnsetBaseRenderAlpha();
if(this->bg_alpha < BackgroundAlphaMax) {
drawer->ResetBaseRenderAlpha();
}
}
}
void QuickMenu::OnInput(u64 down, u64 up, u64 held, pu::ui::Touch touch_pos) {
void QuickMenu::OnInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const pu::ui::TouchPoint touch_pos) {
if(this->on) {
this->options_menu->OnInput(down, up, held, touch_pos);
this->options_menu->OnInput(keys_down, keys_up, keys_held, touch_pos);
}
if((down & HidNpadButton_L) || (down & HidNpadButton_R) || (down & HidNpadButton_ZL) || (down & HidNpadButton_ZR)) {
if((keys_down & HidNpadButton_L) || (keys_down & HidNpadButton_R) || (keys_down & HidNpadButton_ZL) || (keys_down & HidNpadButton_ZR)) {
this->Toggle();
}
else if((down & HidNpadButton_B) || (down & HidNpadButton_A)) {
else if((keys_down & HidNpadButton_B) || (keys_down & HidNpadButton_A)) {
// B only valid for toggling off
// A = something selected in the menu
if(this->on) {
@ -159,8 +132,4 @@ namespace ui {
}
}
void QuickMenuOnHomeButtonDetection() {
g_HomePressed = true;
}
}

View file

@ -1,67 +0,0 @@
#include <ui/ui_RawData.hpp>
namespace ui {
RawData::RawData(s32 x, s32 y, void *raw, s32 w, s32 h, u32 pix_num) : x(x), y(y), w(w), h(h), falpha(0xFF), ptr(raw), pitch(w * pix_num) {
if(this->ptr != nullptr) {
this->ntex = SDL_CreateTexture(pu::ui::render::GetMainRenderer(), SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, this->w, this->h);
if(this->ntex != nullptr) {
SDL_UpdateTexture(this->ntex, nullptr, this->ptr, this->pitch);
}
}
}
RawData::~RawData() {
if(this->ntex != nullptr) {
pu::ui::render::DeleteTexture(this->ntex);
this->ntex = nullptr;
}
}
s32 RawData::GetX() {
return this->x;
}
void RawData::SetX(s32 x) {
this->x = x;
}
s32 RawData::GetY() {
return this->y;
}
void RawData::SetY(s32 y) {
this->y = y;
}
s32 RawData::GetWidth() {
return this->w;
}
void RawData::SetWidth(s32 w) {
this->w = w;
}
s32 RawData::GetHeight() {
return this->h;
}
void RawData::SetHeight(s32 h) {
this->h = h;
}
void RawData::SetAlphaFactor(u8 factor) {
this->falpha = factor;
}
void RawData::OnRender(pu::ui::render::Renderer::Ref &drawer, s32 x, s32 y) {
if(this->ntex != nullptr) {
drawer->SetBaseRenderAlpha(this->falpha);
drawer->RenderTexture(this->ntex, x, y, { -1, this->w, this->h, -1 });
drawer->UnsetBaseRenderAlpha();
}
}
void RawData::OnInput(u64 down, u64 up, u64 held, pu::ui::Touch touch_pos) {}
}

View file

@ -0,0 +1,24 @@
#include <ui/ui_RawRgbaImage.hpp>
namespace ui {
RawRgbaImage::RawRgbaImage(const s32 x, const s32 y, const u8 *rgba_data, const s32 w, const s32 h, const u32 pix_num) : x(x), y(y), w(w), h(h), alpha(0xFF) {
if(rgba_data != nullptr) {
this->img_tex = SDL_CreateTexture(pu::ui::render::GetMainRenderer(), SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, this->w, this->h);
if(this->img_tex != nullptr) {
SDL_UpdateTexture(this->img_tex, nullptr, rgba_data, w * pix_num);
}
}
}
RawRgbaImage::~RawRgbaImage() {
pu::ui::render::DeleteTexture(this->img_tex);
}
void RawRgbaImage::OnRender(pu::ui::render::Renderer::Ref &drawer, const s32 x, const s32 y) {
if(this->img_tex != nullptr) {
drawer->RenderTexture(this->img_tex, x, y, pu::ui::render::TextureRenderOptions::WithCustomAlphaAndDimensions(this->alpha, this->w, this->h));
}
}
}

View file

@ -37,24 +37,19 @@ namespace ui {
SettingsMenuLayout::SettingsMenuLayout() {
this->SetBackgroundImage(cfg::GetAssetByTheme(g_Theme, "ui/Background.png"));
pu::ui::Color textclr = pu::ui::Color::FromHex(g_MenuApplication->GetUIConfigValue<std::string>("text_color", "#e1e1e1ff"));
pu::ui::Color menufocusclr = pu::ui::Color::FromHex(g_MenuApplication->GetUIConfigValue<std::string>("menu_focus_color", "#5ebcffff"));
pu::ui::Color menubgclr = pu::ui::Color::FromHex(g_MenuApplication->GetUIConfigValue<std::string>("menu_bg_color", "#0094ffff"));
this->info_text = pu::ui::elm::TextBlock::New(0, 25, GetLanguageString("set_info_text"));
this->info_text->SetColor(g_MenuApplication->GetTextColor());
this->info_text->SetHorizontalAlign(pu::ui::elm::HorizontalAlign::Center);
g_MenuApplication->ApplyConfigForElement("settings_menu", "info_text", this->info_text);
this->Add(this->info_text);
this->infoText = pu::ui::elm::TextBlock::New(0, 100, GetLanguageString("set_info_text"));
this->infoText->SetColor(textclr);
this->infoText->SetHorizontalAlign(pu::ui::elm::HorizontalAlign::Center);
g_MenuApplication->ApplyConfigForElement("settings_menu", "info_text", this->infoText);
this->Add(this->infoText);
this->settingsMenu = pu::ui::elm::Menu::New(50, 160, 1180, menubgclr, 100, 4);
this->settingsMenu->SetOnFocusColor(menufocusclr);
g_MenuApplication->ApplyConfigForElement("settings_menu", "settings_menu_item", this->settingsMenu);
this->Add(this->settingsMenu);
this->settings_menu = pu::ui::elm::Menu::New(50, 80, 1180, g_MenuApplication->GetMenuBackgroundColor(), g_MenuApplication->GetMenuFocusColor(), 100, 6);
g_MenuApplication->ApplyConfigForElement("settings_menu", "settings_menu_item", this->settings_menu);
this->Add(this->settings_menu);
}
void SettingsMenuLayout::OnMenuInput(u64 down, u64 up, u64 held, pu::ui::Touch pos) {
if(down & HidNpadButton_B) {
void SettingsMenuLayout::OnMenuInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const pu::ui::TouchPoint touch_pos) {
if(keys_down & HidNpadButton_B) {
g_TransitionGuard.Run([]() {
g_MenuApplication->FadeOut();
g_MenuApplication->LoadMenu();
@ -71,20 +66,22 @@ namespace ui {
});
}
void SettingsMenuLayout::Reload() {
void SettingsMenuLayout::Reload(const bool reset_idx) {
// TODO: more settings
this->settingsMenu->ClearItems();
this->settingsMenu->SetSelectedIndex(0);
this->settings_menu->ClearItems();
SetSysDeviceNickName console_name = {};
setsysGetDeviceNickname(&console_name);
this->PushSettingItem(GetLanguageString("set_console_nickname"), EncodeForSettings<std::string>(console_name.nickname), 0);
TimeLocationName loc = {};
timeGetDeviceLocationName(&loc);
this->PushSettingItem(GetLanguageString("set_console_timezone"), EncodeForSettings<std::string>(loc.name), -1);
bool viewer_usb_enabled;
UL_ASSERT_TRUE(g_Config.GetEntry(cfg::ConfigEntryId::ViewerUsbEnabled, viewer_usb_enabled));
this->PushSettingItem(GetLanguageString("set_viewer_enabled"), EncodeForSettings(viewer_usb_enabled), 1);
auto connected_wifi_name = GetLanguageString("set_wifi_none");
if(net::HasConnection()) {
net::NetworkProfileData data = {};
@ -99,48 +96,64 @@ namespace ui {
setMakeLanguage(lang_code, &lang_val);
const std::string lang_str = os::LanguageNameList[static_cast<u32>(lang_val)];
this->PushSettingItem(GetLanguageString("set_console_lang"), EncodeForSettings(lang_str), 3);
auto console_info_upload = false;
setsysGetConsoleInformationUploadFlag(&console_info_upload);
this->PushSettingItem(GetLanguageString("set_console_info_upload"), EncodeForSettings(console_info_upload), 4);
auto auto_titles_dl = false;
setsysGetAutomaticApplicationDownloadFlag(&auto_titles_dl);
this->PushSettingItem(GetLanguageString("set_auto_titles_dl"), EncodeForSettings(auto_titles_dl), 5);
auto auto_update = false;
setsysGetAutoUpdateEnableFlag(&auto_update);
this->PushSettingItem(GetLanguageString("set_auto_update"), EncodeForSettings(auto_update), 6);
auto wireless_lan = false;
setsysGetWirelessLanEnableFlag(&wireless_lan);
this->PushSettingItem(GetLanguageString("set_wireless_lan"), EncodeForSettings(wireless_lan), 7);
auto bluetooth = false;
setsysGetBluetoothEnableFlag(&bluetooth);
this->PushSettingItem(GetLanguageString("set_bluetooth"), EncodeForSettings(bluetooth), 8);
auto usb_30 = false;
setsysGetUsb30EnableFlag(&usb_30);
this->PushSettingItem(GetLanguageString("set_usb_30"), EncodeForSettings(usb_30), 9);
auto nfc = false;
setsysGetNfcEnableFlag(&nfc);
this->PushSettingItem(GetLanguageString("set_nfc"), EncodeForSettings(nfc), 10);
SetSysSerialNumber serial = {};
setsysGetSerialNumber(&serial);
this->PushSettingItem(GetLanguageString("set_serial_no"), EncodeForSettings<std::string>(serial.number), -1);
u64 mac = 0;
net::GetMACAddress(&mac);
const auto mac_addr_str = net::FormatMACAddress(mac);
this->PushSettingItem(GetLanguageString("set_mac_addr"), EncodeForSettings(mac_addr_str), -1);
const auto ip_str = net::GetConsoleIPAddress();
this->PushSettingItem("Console IP address", EncodeForSettings(ip_str), -1);
if(reset_idx) {
this->settings_menu->SetSelectedIndex(0);
}
}
void SettingsMenuLayout::PushSettingItem(const std::string &name, const std::string &value_display, int id) {
auto textclr = pu::ui::Color::FromHex(g_MenuApplication->GetUIConfigValue<std::string>("text_color", "#e1e1e1ff"));
auto itm = pu::ui::elm::MenuItem::New(name + ": " + value_display);
itm->AddOnClick(std::bind(&SettingsMenuLayout::setting_Click, this, id));
itm->SetIcon(cfg::GetAssetByTheme(g_Theme, "ui/Setting" + std::string((id < 0) ? "No" : "") + "Editable.png"));
itm->SetColor(textclr);
this->settingsMenu->AddItem(itm);
const auto is_editable = id >= 0;
auto setting_item = pu::ui::elm::MenuItem::New(name + ": " + value_display);
if(is_editable) {
setting_item->AddOnKey(std::bind(&SettingsMenuLayout::setting_DefaultKey, this, id));
}
setting_item->SetIcon(cfg::GetAssetByTheme(g_Theme, "ui/Setting" + std::string(is_editable ? "" : "No") + "Editable.png"));
setting_item->SetColor(g_MenuApplication->GetTextColor());
this->settings_menu->AddItem(setting_item);
}
void SettingsMenuLayout::setting_Click(u32 id) {
void SettingsMenuLayout::setting_DefaultKey(const u32 id) {
bool reload_need = false;
switch(id) {
case 0: {
@ -256,9 +269,10 @@ namespace ui {
break;
}
}
if(reload_need) {
cfg::SaveConfig(g_Config);
this->Reload();
this->Reload(false);
}
}

View file

@ -1,446 +1,347 @@
#include <ui/ui_SideMenu.hpp>
namespace ui
{
SideMenu::SideMenu(pu::ui::Color suspended_clr, std::string cursor_path, std::string suspended_img_path, std::string multiselect_img_path, u32 txt_x, u32 txt_y, pu::String font_name, pu::ui::Color txt_clr, s32 y) : selitm(0), suspitm(-1), baseiconidx(0), movalpha(0), textx(txt_x), texty(txt_y), enabled(true), suspclr(suspended_clr), textclr(txt_clr), onselect([](u32, u64){}), onselch([](u32){}), leftbicon(nullptr), rightbicon(nullptr), textfont(font_name), scrollflag(0), scrolltpvalue(50), scrollcount(0) {
this->cursoricon = pu::ui::render::LoadImage(cursor_path);
this->suspicon = pu::ui::render::LoadImage(suspended_img_path);
this->mselicon = pu::ui::render::LoadImage(multiselect_img_path);
namespace ui {
bool SideMenu::IsLeftFirst() {
auto base_x = GetProcessedX();
constexpr auto first_item_x = BaseX;
for(u32 i = 0; i < this->rendered_icons.size(); i++) {
if((base_x == first_item_x) && (this->selected_item_idx == (this->base_icon_idx + i))) {
return true;
}
base_x += ItemSize + Margin;
}
return false;
}
bool SideMenu::IsRightLast() {
if(this->selected_item_idx == (this->items_icon_paths.size() - 1)) {
return true;
}
auto base_x = GetProcessedX();
constexpr auto last_item_x = BaseX + (Margin + ItemSize) * (ItemCount - 1);
for(u32 i = 0; i < this->rendered_icons.size(); i++) {
if((base_x == last_item_x) && (this->selected_item_idx == (this->base_icon_idx + i))) {
return true;
}
base_x += ItemSize + Margin;
}
return false;
}
void SideMenu::MoveReloadIcons(const bool moving_right) {
if(moving_right) {
auto icon_tex = pu::ui::render::LoadImage(this->items_icon_paths.at(this->selected_item_idx));
this->rendered_icons.push_back(icon_tex);
auto text_tex = pu::ui::render::RenderText(this->text_font, this->items_icon_texts.at(this->selected_item_idx), this->text_clr);
this->rendered_texts.push_back(text_tex);
if(this->rendered_icons.size() > ItemCount) {
pu::ui::render::DeleteTexture(this->rendered_icons.front());
this->rendered_icons.erase(this->rendered_icons.begin());
pu::ui::render::DeleteTexture(this->rendered_texts.front());
this->rendered_texts.erase(this->rendered_texts.begin());
this->base_icon_idx++;
}
}
else {
auto icon_tex = pu::ui::render::LoadImage(this->items_icon_paths.at(this->selected_item_idx));
this->rendered_icons.insert(this->rendered_icons.begin(), icon_tex);
const auto text = this->items_icon_texts.at(this->selected_item_idx);
pu::sdl2::Texture text_tex = nullptr;
if(!text.empty()) {
text_tex = pu::ui::render::RenderText(this->text_font, text, this->text_clr);
}
this->rendered_texts.insert(this->rendered_texts.begin(), text_tex);
this->base_icon_idx--;
if(this->rendered_icons.size() > ItemCount) {
pu::ui::render::DeleteTexture(this->rendered_icons.back());
this->rendered_icons.pop_back();
pu::ui::render::DeleteTexture(this->rendered_texts.back());
this->rendered_texts.pop_back();
}
}
this->UpdateBorderIcons();
}
SideMenu::SideMenu(const pu::ui::Color suspended_clr, const std::string &cursor_path, const std::string &suspended_img_path, const std::string &multiselect_img_path, const s32 txt_x, const s32 txt_y, const std::string &font_name, const pu::ui::Color txt_clr, const s32 y) : selected_item_idx(0), suspended_item_idx(-1), base_icon_idx(0), move_alpha(0), text_x(txt_x), text_y(txt_y), enabled(true), text_clr(txt_clr), on_select_cb(), on_selection_changed_cb(), left_border_icon(nullptr), right_border_icon(nullptr), text_font(font_name), scroll_flag(0), scroll_tp_value(50), scroll_count(0) {
this->cursor_icon = pu::ui::render::LoadImage(cursor_path);
this->suspended_icon = pu::ui::render::LoadImage(suspended_img_path);
this->multiselect_icon = pu::ui::render::LoadImage(multiselect_img_path);
this->SetY(y);
}
SideMenu::~SideMenu() {
if(this->cursoricon != nullptr) {
pu::ui::render::DeleteTexture(this->cursoricon);
this->cursoricon = nullptr;
}
if(this->suspicon != nullptr) {
pu::ui::render::DeleteTexture(this->suspicon);
this->suspicon = nullptr;
}
pu::ui::render::DeleteTexture(this->cursor_icon);
pu::ui::render::DeleteTexture(this->suspended_icon);
this->ClearItems();
}
s32 SideMenu::GetX() {
return 98;
}
s32 SideMenu::GetY() {
return this->y;
}
void SideMenu::SetX(s32 x) {}
void SideMenu::SetY(s32 y) {
this->y = y;
}
s32 SideMenu::GetWidth() {
return 1280;
}
s32 SideMenu::GetHeight() {
return ItemSize + FocusSize + FocusMargin;
}
void SideMenu::OnRender(pu::ui::render::Renderer::Ref &drawer, s32 x, s32 y) {
if(this->icons.empty()) {
void SideMenu::OnRender(pu::ui::render::Renderer::Ref &drawer, const s32 x, const s32 y) {
if(this->items_icon_paths.empty()) {
return;
}
if(this->ricons.empty()) {
for(u32 i = 0; i < std::min(static_cast<size_t>(4), this->icons.size() - this->baseiconidx); i++) {
auto icon = pu::ui::render::LoadImage(this->icons[this->baseiconidx + i]);
auto text = this->icons_texts[this->baseiconidx + i];
this->ricons.push_back(icon);
pu::sdl2::Texture ntext = nullptr;
if(this->rendered_icons.empty()) {
for(u32 i = 0; i < std::min(static_cast<size_t>(ItemCount), this->items_icon_paths.size() - this->base_icon_idx); i++) {
auto icon_tex = pu::ui::render::LoadImage(this->items_icon_paths.at(this->base_icon_idx + i));
const auto text = this->items_icon_texts.at(this->base_icon_idx + i);
this->rendered_icons.push_back(icon_tex);
pu::sdl2::Texture text_tex = nullptr;
if(!text.empty()) {
ntext = pu::ui::render::RenderText(this->textfont, text, this->textclr);
text_tex = pu::ui::render::RenderText(this->text_font, text, this->text_clr);
}
this->ricons_texts.push_back(ntext);
this->rendered_texts.push_back(text_tex);
}
this->UpdateBorderIcons();
(this->onselch)(this->selitm);
this->DoOnSelectionChanged();
}
u32 basex = x;
auto base_x = x;
for(u32 i = 0; i < this->rendered_icons.size(); i++) {
auto icon_tex = this->rendered_icons.at(i);
drawer->RenderTexture(icon_tex, base_x, y, pu::ui::render::TextureRenderOptions::WithCustomDimensions(ItemSize, ItemSize));
for(u32 i = 0; i < this->ricons.size(); i++) {
auto ricon = this->ricons[i];
drawer->RenderTexture(ricon, basex, y, { -1, ItemSize, ItemSize, -1 });
auto ntext = this->ricons_texts[i];
if(ntext != nullptr) {
drawer->RenderTexture(ntext, basex + this->textx, y + this->texty);
auto text_tex = this->rendered_texts.at(i);
if(text_tex != nullptr) {
drawer->RenderTexture(text_tex, base_x + this->text_x, y + this->text_y);
}
if(this->IsItemMultiselected(this->baseiconidx + i)) {
drawer->RenderTexture(this->mselicon, basex - Margin, y - Margin, { -1, ExtraIconSize, ExtraIconSize, -1 });
if(this->IsItemMultiselected(this->base_icon_idx + i)) {
drawer->RenderTexture(this->multiselect_icon, base_x - Margin, y - Margin, pu::ui::render::TextureRenderOptions::WithCustomDimensions(ExtraIconSize, ExtraIconSize));
}
if(this->suspitm >= 0) {
if((this->baseiconidx + i) == static_cast<u32>(suspitm)) {
if(this->suspicon != nullptr) {
drawer->RenderTexture(this->suspicon, basex - Margin, y - Margin, { -1, ExtraIconSize, ExtraIconSize, -1 });
if(this->suspended_item_idx >= 0) {
if((this->base_icon_idx + i) == static_cast<u32>(this->suspended_item_idx)) {
if(this->suspended_icon != nullptr) {
drawer->RenderTexture(this->suspended_icon, base_x - Margin, y - Margin, pu::ui::render::TextureRenderOptions::WithCustomDimensions(ExtraIconSize, ExtraIconSize));
}
}
}
if(this->cursoricon != nullptr) {
if((this->baseiconidx + i) == selitm) {
drawer->RenderTexture(this->cursoricon, basex - Margin, y - Margin, { 255 - movalpha, ExtraIconSize, ExtraIconSize, -1 });
if(this->cursor_icon != nullptr) {
if((this->base_icon_idx + i) == this->selected_item_idx) {
drawer->RenderTexture(this->cursor_icon, base_x - Margin, y - Margin, pu::ui::render::TextureRenderOptions::WithCustomAlphaAndDimensions(0xFF - this->move_alpha, ExtraIconSize, ExtraIconSize));
}
else if((this->baseiconidx + i) == preselitm) {
drawer->RenderTexture(this->cursoricon, basex - Margin, y - Margin, { movalpha, ExtraIconSize, ExtraIconSize, -1 });
else if((this->base_icon_idx + i) == this->prev_selected_item_idx) {
drawer->RenderTexture(this->cursor_icon, base_x - Margin, y - Margin, pu::ui::render::TextureRenderOptions::WithCustomAlphaAndDimensions(this->move_alpha, ExtraIconSize, ExtraIconSize));
}
}
basex += ItemSize + Margin;
base_x += ItemSize + Margin;
}
if(leftbicon != nullptr) {
drawer->RenderTexture(leftbicon, x - ItemSize - Margin, y, { -1, ItemSize, ItemSize, -1 });
if(this->left_border_icon != nullptr) {
drawer->RenderTexture(this->left_border_icon, x - ItemSize - Margin, y, pu::ui::render::TextureRenderOptions::WithCustomDimensions(ItemSize, ItemSize));
}
if(rightbicon != nullptr) {
drawer->RenderTexture(rightbicon, x + ((ItemSize + Margin) * 4), y, { -1, ItemSize, ItemSize, -1 });
if(this->right_border_icon != nullptr) {
drawer->RenderTexture(this->right_border_icon, x + ((ItemSize + Margin) * ItemCount), y, pu::ui::render::TextureRenderOptions::WithCustomDimensions(ItemSize, ItemSize));
}
if(movalpha > 0) {
s32 tmpalpha = movalpha - 30;
if(tmpalpha < 0) {
tmpalpha = 0;
if(move_alpha > 0) {
s32 tmp_alpha = move_alpha - MoveAlphaIncrement;
if(tmp_alpha < 0) {
tmp_alpha = 0;
}
movalpha = static_cast<u8>(tmpalpha);
move_alpha = static_cast<u8>(tmp_alpha);
}
}
void SideMenu::OnInput(u64 down, u64 up, u64 held, pu::ui::Touch touch_pos) {
if(this->ricons.empty()) {
void SideMenu::OnInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const pu::ui::TouchPoint touch_pos) {
if(this->rendered_icons.empty()) {
return;
}
if(!this->enabled) {
return;
}
if(down & HidNpadButton_AnyLeft) {
if(keys_down & HidNpadButton_AnyLeft) {
HandleMoveLeft();
}
else if(down & HidNpadButton_AnyRight) {
else if(keys_down & HidNpadButton_AnyRight) {
HandleMoveRight();
}
else if(!touch_pos.IsEmpty()) {
auto basex = this->GetProcessedX();
auto basey = this->GetProcessedY();
if(this->cursoricon != nullptr) {
for(u32 i = 0; i < this->ricons.size(); i++) {
auto base_x = this->GetProcessedX();
const auto y = this->GetProcessedY();
if(this->cursor_icon != nullptr) {
for(u32 i = 0; i < this->rendered_icons.size(); i++) {
constexpr auto item_size = static_cast<s32>(ItemSize);
if((touch_pos.X >= basex) && (touch_pos.X < (basex + item_size)) && (touch_pos.Y >= basey) && (touch_pos.Y < (basey + item_size))) {
if((this->baseiconidx + i) == selitm) {
(this->onselect)(HidNpadButton_A, this->selitm);
if(touch_pos.HitsRegion(base_x, y, item_size, item_size)) {
if((this->base_icon_idx + i) == this->selected_item_idx) {
this->DoOnItemSelected(HidNpadButton_A);
}
else {
preselitm = selitm;
selitm = this->baseiconidx + i;
movalpha = 0xFF;
(this->onselch)(this->selitm);
this->prev_selected_item_idx = this->selected_item_idx;
this->selected_item_idx = this->base_icon_idx + i;
this->move_alpha = 0xFF;
this->DoOnSelectionChanged();
}
break;
}
basex += ItemSize + Margin;
base_x += ItemSize + Margin;
}
}
}
else {
(this->onselect)(down, this->selitm);
this->DoOnItemSelected(keys_down);
}
if(held & HidNpadButton_AnyLeft) {
if(this->scrollflag == 1) {
auto curtp = std::chrono::steady_clock::now();
auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(curtp - this->scrolltp).count();
if(diff >= 300) {
if(this->scrollmoveflag) {
auto diff2 = std::chrono::duration_cast<std::chrono::milliseconds>(curtp - this->scrollmovetp).count();
if(diff2 >= this->scrolltpvalue) {
if(this->scrollcount >= 5) {
this->scrollcount = 0;
this->scrolltpvalue /= 2;
if(keys_held & HidNpadButton_AnyLeft) {
if(this->scroll_flag == 1) {
const auto cur_tp = std::chrono::steady_clock::now();
const u64 diff = std::chrono::duration_cast<std::chrono::milliseconds>(cur_tp - this->scroll_tp).count();
if(diff >= ScrollMoveWaitTimeMs) {
if(this->scroll_move_flag) {
const u64 move_diff = std::chrono::duration_cast<std::chrono::milliseconds>(cur_tp - this->scroll_move_tp).count();
if(move_diff >= this->scroll_tp_value) {
if(this->scroll_count > ItemCount) {
this->scroll_count = 0;
this->scroll_tp_value /= 2;
}
this->scrollmoveflag = false;
this->scroll_move_flag = false;
this->HandleMoveLeft();
this->scrollcount++;
this->scroll_count++;
}
}
else {
this->scrollmovetp = std::chrono::steady_clock::now();
this->scrollmoveflag = true;
this->scroll_move_tp = std::chrono::steady_clock::now();
this->scroll_move_flag = true;
}
}
}
else {
this->scrollflag = 1;
this->scrolltp = std::chrono::steady_clock::now();
this->scroll_flag = 1;
this->scroll_tp = std::chrono::steady_clock::now();
}
}
else if(held & HidNpadButton_AnyRight) {
if(this->scrollflag == 2) {
auto curtp = std::chrono::steady_clock::now();
auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(curtp - this->scrolltp).count();
if(diff >= 300) {
if(this->scrollmoveflag) {
auto diff2 = std::chrono::duration_cast<std::chrono::milliseconds>(curtp - this->scrollmovetp).count();
if(diff2 >= this->scrolltpvalue) {
if(this->scrollcount >= 5) {
this->scrollcount = 0;
this->scrolltpvalue /= 2;
else if(keys_held & HidNpadButton_AnyRight) {
if(this->scroll_flag == 2) {
const auto cur_tp = std::chrono::steady_clock::now();
const u64 diff = std::chrono::duration_cast<std::chrono::milliseconds>(cur_tp - this->scroll_tp).count();
if(diff >= ScrollMoveWaitTimeMs) {
if(this->scroll_move_flag) {
const u64 move_diff = std::chrono::duration_cast<std::chrono::milliseconds>(cur_tp - this->scroll_move_tp).count();
if(move_diff >= this->scroll_tp_value) {
if(this->scroll_count > ItemCount) {
this->scroll_count = 0;
this->scroll_tp_value /= 2;
}
this->scrollmoveflag = false;
this->scroll_move_flag = false;
this->HandleMoveRight();
this->scrollcount++;
this->scroll_count++;
}
}
else {
this->scrollmovetp = std::chrono::steady_clock::now();
this->scrollmoveflag = true;
this->scroll_move_tp = std::chrono::steady_clock::now();
this->scroll_move_flag = true;
}
}
}
else {
this->scrollflag = 2;
this->scrolltp = std::chrono::steady_clock::now();
this->scroll_flag = 2;
this->scroll_tp = std::chrono::steady_clock::now();
}
}
else {
this->scrollflag = 0;
this->scrollcount = 0;
this->scrolltpvalue = 50;
this->scroll_flag = 0;
this->scroll_count = 0;
this->scroll_tp_value = ScrollBaseWaitTimeMs;
}
}
void SideMenu::SetOnItemSelected(std::function<void(u64, u32)> fn) {
this->onselect = fn;
}
void SideMenu::SetOnSelectionChanged(std::function<void(u32)> fn) {
this->onselch = fn;
}
void SideMenu::ClearItems() {
this->icons.clear();
this->icons_texts.clear();
this->icons_mselected.clear();
for(auto ricon: this->ricons) {
if(ricon != nullptr) {
pu::ui::render::DeleteTexture(ricon);
}
}
this->ricons.clear();
for(auto rtext: this->ricons_texts) {
if(rtext != nullptr) {
pu::ui::render::DeleteTexture(rtext);
}
}
this->ricons_texts.clear();
this->selitm = 0;
this->baseiconidx = 0;
this->suspitm = -1;
this->ClearBorderIcons();
this->ClearRenderedItems();
this->items_icon_paths.clear();
this->items_icon_texts.clear();
this->items_multiselected.clear();
this->selected_item_idx = 0;
this->base_icon_idx = 0;
this->suspended_item_idx = -1;
}
void SideMenu::AddItem(const std::string &icon, const std::string &txt) {
this->icons.push_back(icon);
this->icons_texts.push_back(txt);
this->icons_mselected.push_back(false);
}
void SideMenu::SetSuspendedItem(u32 Index) {
this->suspitm = Index;
}
void SideMenu::UnsetSuspendedItem() {
this->suspitm = -1;
}
void SideMenu::SetSelectedItem(u32 idx) {
if(idx < this->icons.size()) {
this->selitm = idx;
}
this->items_icon_paths.push_back(icon);
this->items_icon_texts.push_back(txt);
this->items_multiselected.push_back(false);
}
void SideMenu::HandleMoveLeft() {
if(selitm > 0) {
bool ilf = IsLeftFirst();
preselitm = selitm;
selitm--;
if(ilf) {
ReloadIcons(1);
if(this->selected_item_idx > 0) {
const auto is_left_first = IsLeftFirst();
this->prev_selected_item_idx = this->selected_item_idx;
this->selected_item_idx--;
if(is_left_first) {
MoveReloadIcons(false);
}
else movalpha = 0xFF;
(this->onselch)(this->selitm);
else {
this->move_alpha = 0xFF;
}
this->DoOnSelectionChanged();
}
}
void SideMenu::HandleMoveRight() {
if((selitm + 1) < this->icons.size()) {
bool irl = IsRightLast();
preselitm = selitm;
selitm++;
if(irl) {
ReloadIcons(2);
if((selected_item_idx + 1) < this->items_icon_paths.size()) {
const auto is_right_last = IsRightLast();
prev_selected_item_idx = selected_item_idx;
selected_item_idx++;
if(is_right_last) {
MoveReloadIcons(true);
}
else movalpha = 0xFF;
(this->onselch)(this->selitm);
else {
this->move_alpha = 0xFF;
}
this->DoOnSelectionChanged();
}
}
int SideMenu::GetSuspendedItem() {
return this->suspitm;
}
u32 SideMenu::GetSelectedItem() {
return this->selitm;
}
bool SideMenu::IsLeftFirst() {
auto basex = GetProcessedX();
for(u32 i = 0; i < this->ricons.size(); i++) {
if((basex == this->GetX()) && (this->selitm == (this->baseiconidx + i))) {
return true;
}
basex += ItemSize + Margin;
}
return false;
}
bool SideMenu::IsRightLast()
{
if(this->selitm == (this->icons.size() - 1)) {
return true;
}
auto basex = GetProcessedX();
for(u32 i = 0; i < this->ricons.size(); i++) {
if((basex == 926) && (this->selitm == (this->baseiconidx + i))) {
return true;
}
basex += ItemSize + Margin;
}
return false;
}
void SideMenu::ReloadIcons(u32 dir) {
switch(dir)
{
// Left
case 1: {
auto icon = pu::ui::render::LoadImage(this->icons[this->selitm]);
this->ricons.insert(this->ricons.begin(), icon);
auto text = this->icons_texts[this->selitm];
pu::sdl2::Texture ntext = nullptr;
if(!text.empty()) {
ntext = pu::ui::render::RenderText(this->textfont, text, this->textclr);
}
this->ricons_texts.insert(this->ricons_texts.begin(), ntext);
this->baseiconidx--;
if(this->ricons.size() == 5) {
pu::ui::render::DeleteTexture(this->ricons.back());
this->ricons.pop_back();
auto ntext = this->ricons_texts.back();
if(ntext != nullptr) {
pu::ui::render::DeleteTexture(ntext);
}
this->ricons_texts.pop_back();
}
break;
}
// Right
case 2: {
auto icon = pu::ui::render::LoadImage(this->icons[this->selitm]);
this->ricons.push_back(icon);
auto ntext = pu::ui::render::RenderText(this->textfont, this->icons_texts[this->selitm], this->textclr);
this->ricons_texts.push_back(ntext);
if(this->ricons.size() == 5) {
pu::ui::render::DeleteTexture(this->ricons.front());
this->ricons.erase(this->ricons.begin());
auto ntext = this->ricons_texts.front();
if(ntext != nullptr) {
pu::ui::render::DeleteTexture(ntext);
}
this->ricons_texts.erase(this->ricons_texts.begin());
this->baseiconidx++;
}
break;
}
}
this->UpdateBorderIcons();
}
u32 SideMenu::GetBaseItemIndex() {
return this->baseiconidx;
}
void SideMenu::SetBaseItemIndex(u32 index) {
this->baseiconidx = index;
}
void SideMenu::SetBasePositions(u32 selected_idx, u32 base_idx) {
if(selected_idx < this->icons.size()) {
this->SetSelectedItem(selected_idx);
this->SetBaseItemIndex(base_idx);
}
}
void SideMenu::ClearBorderIcons() {
if(this->leftbicon != nullptr) {
pu::ui::render::DeleteTexture(this->leftbicon);
}
this->leftbicon = nullptr;
if(this->rightbicon != nullptr) {
pu::ui::render::DeleteTexture(this->rightbicon);
}
this->rightbicon = nullptr;
}
void SideMenu::UpdateBorderIcons()
{
void SideMenu::UpdateBorderIcons() {
this->ClearBorderIcons();
if(this->baseiconidx > 0) {
this->leftbicon = pu::ui::render::LoadImage(this->icons[this->baseiconidx - 1]);
if(this->base_icon_idx > 0) {
this->left_border_icon = pu::ui::render::LoadImage(this->items_icon_paths.at(this->base_icon_idx - 1));
}
if((this->baseiconidx + 4) < this->icons.size()) {
this->rightbicon = pu::ui::render::LoadImage(this->icons[this->baseiconidx + 4]);
if((this->base_icon_idx + ItemCount) < this->items_icon_paths.size()) {
this->right_border_icon = pu::ui::render::LoadImage(this->items_icon_paths.at(this->base_icon_idx + ItemCount));
}
}
void SideMenu::ResetMultiselections()
{
this->icons_mselected.clear();
for(u32 i = 0; i < this->icons.size(); i++) {
this->icons_mselected.push_back(false);
void SideMenu::ResetMultiselections() {
this->items_multiselected.clear();
for(u32 i = 0; i < this->items_icon_paths.size(); i++) {
this->items_multiselected.push_back(false);
}
}
void SideMenu::SetItemMultiselected(u32 index, bool selected) {
if(index < this->icons_mselected.size()) {
this->icons_mselected[index] = selected;
void SideMenu::SetItemMultiselected(const u32 idx, const bool selected) {
if(idx < this->items_multiselected.size()) {
this->items_multiselected.at(idx) = selected;
}
}
bool SideMenu::IsItemMultiselected(u32 index)
{
if(index < this->icons_mselected.size()) {
return this->icons_mselected[index];
bool SideMenu::IsItemMultiselected(const u32 idx) {
if(idx < this->items_multiselected.size()) {
return this->items_multiselected.at(idx);
}
return false;
}
bool SideMenu::IsAnyMultiselected()
{
bool any = false;
for(auto msel: this->icons_mselected) {
if(msel) {
any = true;
break;
bool SideMenu::IsAnyMultiselected() {
for(const auto &multiselected: this->items_multiselected) {
if(multiselected) {
return true;
}
}
return any;
}
void SideMenu::SetEnabled(bool enabled) {
this->enabled = enabled;
return false;
}
}

View file

@ -11,28 +11,38 @@ extern cfg::Config g_Config;
namespace ui {
StartupLayout::StartupLayout() {
this->SetBackgroundImage(cfg::GetAssetByTheme(g_Theme, "ui/Background.png"));
this->loadmenu = false;
auto textclr = pu::ui::Color::FromHex(g_MenuApplication->GetUIConfigValue<std::string>("text_color", "#e1e1e1ff"));
auto menufocusclr = pu::ui::Color::FromHex(g_MenuApplication->GetUIConfigValue<std::string>("menu_focus_color", "#5ebcffff"));
auto menubgclr = pu::ui::Color::FromHex(g_MenuApplication->GetUIConfigValue<std::string>("menu_bg_color", "#0094ffff"));
this->infoText = pu::ui::elm::TextBlock::New(35, 650, GetLanguageString("startup_welcome_info"));
this->infoText->SetColor(textclr);
g_MenuApplication->ApplyConfigForElement("startup_menu", "info_text", this->infoText);
this->Add(this->infoText);
this->usersMenu = pu::ui::elm::Menu::New(200, 60, 880, menubgclr, 100, 5);
this->usersMenu->SetOnFocusColor(menufocusclr);
g_MenuApplication->ApplyConfigForElement("startup_menu", "users_menu_item", this->usersMenu);
this->Add(this->usersMenu);
void StartupLayout::user_DefaultKey(const AccountUid uid) {
this->load_menu = true;
g_MenuApplication->SetSelectedUser(uid);
}
void StartupLayout::OnMenuInput(u64 down, u64 up, u64 held, pu::ui::Touch touch_pos) {
if(this->loadmenu) {
this->loadmenu = false;
void StartupLayout::create_DefaultKey() {
if(R_SUCCEEDED(pselShowUserCreator())) {
g_TransitionGuard.Run([&]() {
g_MenuApplication->FadeOut();
this->ReloadMenu();
g_MenuApplication->FadeIn();
});
}
}
StartupLayout::StartupLayout() {
this->SetBackgroundImage(cfg::GetAssetByTheme(g_Theme, "ui/Background.png"));
this->load_menu = false;
this->info_text = pu::ui::elm::TextBlock::New(35, 650, GetLanguageString("startup_welcome_info"));
this->info_text->SetColor(g_MenuApplication->GetTextColor());
g_MenuApplication->ApplyConfigForElement("startup_menu", "info_text", this->info_text);
this->Add(this->info_text);
this->users_menu = pu::ui::elm::Menu::New(200, 60, 880, g_MenuApplication->GetMenuBackgroundColor(), g_MenuApplication->GetMenuFocusColor(), 100, 5);
g_MenuApplication->ApplyConfigForElement("startup_menu", "users_menu_item", this->users_menu);
this->Add(this->users_menu);
}
void StartupLayout::OnMenuInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const pu::ui::TouchPoint touch_pos) {
if(this->load_menu) {
this->load_menu = false;
g_MenuApplication->StartPlayBGM();
g_TransitionGuard.Run([]() {
g_MenuApplication->FadeOut();
@ -47,49 +57,30 @@ namespace ui {
return true;
}
void StartupLayout::user_Click(AccountUid uid) {
this->loadmenu = true;
g_MenuApplication->SetSelectedUser(uid);
}
void StartupLayout::create_Click() {
auto rc = pselShowUserCreator();
if(R_SUCCEEDED(rc)) {
g_TransitionGuard.Run([&]() {
g_MenuApplication->FadeOut();
this->ReloadMenu();
g_MenuApplication->FadeIn();
});
}
}
void StartupLayout::ReloadMenu() {
this->usersMenu->ClearItems();
this->usersMenu->SetSelectedIndex(0);
this->users_menu->ClearItems();
auto textclr = pu::ui::Color::FromHex(g_MenuApplication->GetUIConfigValue<std::string>("text_color", "#e1e1e1ff"));
std::vector<AccountUid> users;
auto rc = os::QuerySystemAccounts(users, true);
if(R_SUCCEEDED(rc)) {
for(auto &user: users) {
if(R_SUCCEEDED(os::QuerySystemAccounts(true, users))) {
for(const auto &user: users) {
std::string name;
auto rc = os::GetAccountName(name, user);
if(R_SUCCEEDED(rc)) {
auto path = os::GetIconCacheImagePath(user);
auto uitm = pu::ui::elm::MenuItem::New(name);
uitm->SetIcon(path);
uitm->AddOnClick(std::bind(&StartupLayout::user_Click, this, user));
uitm->SetColor(textclr);
this->usersMenu->AddItem(uitm);
if(R_SUCCEEDED(os::GetAccountName(user, name))) {
const auto path = os::GetIconCacheImagePath(user);
auto user_item = pu::ui::elm::MenuItem::New(name);
user_item->SetIcon(path);
user_item->AddOnKey(std::bind(&StartupLayout::user_DefaultKey, this, user));
user_item->SetColor(g_MenuApplication->GetTextColor());
this->users_menu->AddItem(user_item);
}
}
}
auto citm = pu::ui::elm::MenuItem::New(GetLanguageString("startup_new_user"));
citm->SetColor(textclr);
citm->AddOnClick(std::bind(&StartupLayout::create_Click, this));
this->usersMenu->AddItem(citm);
auto create_user_item = pu::ui::elm::MenuItem::New(GetLanguageString("startup_new_user"));
create_user_item->SetColor(g_MenuApplication->GetTextColor());
create_user_item->AddOnKey(std::bind(&StartupLayout::create_DefaultKey, this));
this->users_menu->AddItem(create_user_item);
this->users_menu->SetSelectedIndex(0);
}
}

View file

@ -14,49 +14,47 @@ namespace ui {
ThemeMenuLayout::ThemeMenuLayout() {
this->SetBackgroundImage(cfg::GetAssetByTheme(g_Theme, "ui/Background.png"));
auto textclr = pu::ui::Color::FromHex(g_MenuApplication->GetUIConfigValue<std::string>("text_color", "#e1e1e1ff"));
auto menufocusclr = pu::ui::Color::FromHex(g_MenuApplication->GetUIConfigValue<std::string>("menu_focus_color", "#5ebcffff"));
auto menubgclr = pu::ui::Color::FromHex(g_MenuApplication->GetUIConfigValue<std::string>("menu_bg_color", "#0094ffff"));
this->cur_theme_banner = pu::ui::elm::Image::New(0, 585, cfg::GetAssetByTheme(g_Theme, "ui/BannerTheme.png"));
g_MenuApplication->ApplyConfigForElement("themes_menu", "banner_image", this->cur_theme_banner);
this->Add(this->cur_theme_banner);
this->curThemeBanner = pu::ui::elm::Image::New(0, 585, cfg::GetAssetByTheme(g_Theme, "ui/BannerTheme.png"));
g_MenuApplication->ApplyConfigForElement("themes_menu", "banner_image", this->curThemeBanner);
this->Add(this->curThemeBanner);
this->themes_menu = pu::ui::elm::Menu::New(200, 60, 880, g_MenuApplication->GetMenuBackgroundColor(), g_MenuApplication->GetMenuFocusColor(), 100, 5);
g_MenuApplication->ApplyConfigForElement("themes_menu", "themes_menu_item", this->themes_menu);
this->Add(this->themes_menu);
this->themesMenu = pu::ui::elm::Menu::New(200, 60, 880, menubgclr, 100, 5);
this->themesMenu->SetOnFocusColor(menufocusclr);
g_MenuApplication->ApplyConfigForElement("themes_menu", "themes_menu_item", this->themesMenu);
this->Add(this->themesMenu);
this->cur_theme_text = pu::ui::elm::TextBlock::New(20, 540, GetLanguageString("theme_current") + ":");
this->cur_theme_text->SetFont(pu::ui::GetDefaultFont(pu::ui::DefaultFontSize::Large));
this->cur_theme_text->SetColor(g_MenuApplication->GetTextColor());
g_MenuApplication->ApplyConfigForElement("themes_menu", "current_theme_text", this->cur_theme_text);
this->Add(this->cur_theme_text);
this->curThemeText = pu::ui::elm::TextBlock::New(20, 540, GetLanguageString("theme_current") + ":");
this->curThemeText->SetFont("DefaultFont@30");
this->curThemeText->SetColor(textclr);
g_MenuApplication->ApplyConfigForElement("themes_menu", "current_theme_text", this->curThemeText);
this->Add(this->curThemeText);
this->cur_theme_name_text = pu::ui::elm::TextBlock::New(40, 610, "");
this->cur_theme_name_text->SetFont(pu::ui::GetDefaultFont(pu::ui::DefaultFontSize::Large));
this->cur_theme_name_text->SetColor(g_MenuApplication->GetTextColor());
g_MenuApplication->ApplyConfigForElement("themes_menu", "current_theme_name_text", this->cur_theme_name_text);
this->Add(this->cur_theme_name_text);
this->curThemeName = pu::ui::elm::TextBlock::New(40, 610, "");
this->curThemeName->SetFont("DefaultFont@30");
this->curThemeName->SetColor(textclr);
g_MenuApplication->ApplyConfigForElement("themes_menu", "current_theme_name_text", this->curThemeName);
this->Add(this->curThemeName);
this->curThemeAuthor = pu::ui::elm::TextBlock::New(45, 650, "");
this->curThemeAuthor->SetFont("DefaultFont@20");
this->curThemeAuthor->SetColor(textclr);
g_MenuApplication->ApplyConfigForElement("themes_menu", "current_theme_author_text", this->curThemeAuthor);
this->Add(this->curThemeAuthor);
this->curThemeVersion = pu::ui::elm::TextBlock::New(45, 675, "");
this->curThemeVersion->SetFont("DefaultFont@30");
this->curThemeVersion->SetColor(textclr);
g_MenuApplication->ApplyConfigForElement("themes_menu", "current_theme_version_text", this->curThemeVersion);
this->Add(this->curThemeVersion);
this->curThemeIcon = pu::ui::elm::Image::New(1000, 605, "");
this->curThemeIcon->SetWidth(100);
this->curThemeIcon->SetHeight(100);
g_MenuApplication->ApplyConfigForElement("themes_menu", "current_theme_icon", this->curThemeIcon);
this->Add(this->curThemeIcon);
this->cur_theme_author_text = pu::ui::elm::TextBlock::New(45, 650, "");
this->cur_theme_author_text->SetFont(pu::ui::GetDefaultFont(pu::ui::DefaultFontSize::Medium));
this->cur_theme_author_text->SetColor(g_MenuApplication->GetTextColor());
g_MenuApplication->ApplyConfigForElement("themes_menu", "current_theme_author_text", this->cur_theme_author_text);
this->Add(this->cur_theme_author_text);
this->cur_theme_version_text = pu::ui::elm::TextBlock::New(45, 675, "");
this->cur_theme_version_text->SetFont(pu::ui::GetDefaultFont(pu::ui::DefaultFontSize::Large));
this->cur_theme_version_text->SetColor(g_MenuApplication->GetTextColor());
g_MenuApplication->ApplyConfigForElement("themes_menu", "current_theme_version_text", this->cur_theme_version_text);
this->Add(this->cur_theme_version_text);
this->cur_theme_icon = pu::ui::elm::Image::New(1000, 605, "");
this->cur_theme_icon->SetWidth(100);
this->cur_theme_icon->SetHeight(100);
g_MenuApplication->ApplyConfigForElement("themes_menu", "current_theme_icon", this->cur_theme_icon);
this->Add(this->cur_theme_icon);
}
void ThemeMenuLayout::OnMenuInput(u64 down, u64 up, u64 held, pu::ui::Touch touch_pos) {
if(down & HidNpadButton_B) {
void ThemeMenuLayout::OnMenuInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const pu::ui::TouchPoint touch_pos) {
if(keys_down & HidNpadButton_B) {
g_TransitionGuard.Run([]() {
g_MenuApplication->FadeOut();
g_MenuApplication->LoadMenu();
@ -74,60 +72,59 @@ namespace ui {
}
void ThemeMenuLayout::Reload() {
auto textclr = pu::ui::Color::FromHex(g_MenuApplication->GetUIConfigValue<std::string>("text_color", "#e1e1e1ff"));
if(cfg::ThemeIsDefault(g_Theme)) {
this->curThemeText->SetText(GetLanguageString("theme_no_custom"));
this->curThemeName->SetVisible(false);
this->curThemeAuthor->SetVisible(false);
this->curThemeVersion->SetVisible(false);
this->curThemeBanner->SetVisible(false);
this->curThemeIcon->SetVisible(false);
if(g_Theme.IsDefault()) {
this->cur_theme_text->SetText(GetLanguageString("theme_no_custom"));
this->cur_theme_name_text->SetVisible(false);
this->cur_theme_author_text->SetVisible(false);
this->cur_theme_version_text->SetVisible(false);
this->cur_theme_banner->SetVisible(false);
this->cur_theme_icon->SetVisible(false);
}
else {
this->curThemeText->SetText(GetLanguageString("theme_current") + ":");
this->curThemeName->SetVisible(true);
this->curThemeName->SetText(g_Theme.manifest.name);
this->curThemeAuthor->SetVisible(true);
this->curThemeAuthor->SetText(g_Theme.manifest.author);
this->curThemeVersion->SetVisible(true);
this->curThemeVersion->SetText("v" + g_Theme.manifest.release);
this->curThemeBanner->SetVisible(true);
this->curThemeIcon->SetVisible(true);
this->curThemeIcon->SetImage(g_Theme.path + "/theme/Icon.png");
this->curThemeIcon->SetWidth(100);
this->curThemeIcon->SetHeight(100);
this->cur_theme_text->SetText(GetLanguageString("theme_current") + ":");
this->cur_theme_name_text->SetVisible(true);
this->cur_theme_name_text->SetText(g_Theme.manifest.name);
this->cur_theme_author_text->SetVisible(true);
this->cur_theme_author_text->SetText(g_Theme.manifest.author);
this->cur_theme_version_text->SetVisible(true);
this->cur_theme_version_text->SetText("v" + g_Theme.manifest.release);
this->cur_theme_banner->SetVisible(true);
this->cur_theme_icon->SetVisible(true);
this->cur_theme_icon->SetImage(g_Theme.path + "/theme/Icon.png");
this->cur_theme_icon->SetWidth(100);
this->cur_theme_icon->SetHeight(100);
}
this->themesMenu->ClearItems();
this->themesMenu->SetSelectedIndex(0);
this->themes_menu->ClearItems();
this->loaded_themes.clear();
this->loadedThemes.clear();
this->loadedThemes = cfg::LoadThemes();
this->loaded_themes = cfg::LoadThemes();
auto ditm = pu::ui::elm::MenuItem::New(GetLanguageString("theme_reset"));
ditm->AddOnClick(std::bind(&ThemeMenuLayout::theme_Click, this));
ditm->SetColor(textclr);
ditm->SetIcon("romfs:/Logo.png");
this->themesMenu->AddItem(ditm);
auto theme_reset_item = pu::ui::elm::MenuItem::New(GetLanguageString("theme_reset"));
theme_reset_item->AddOnKey(std::bind(&ThemeMenuLayout::theme_DefaultKey, this));
theme_reset_item->SetColor(g_MenuApplication->GetTextColor());
theme_reset_item->SetIcon("romfs:/Logo.png");
this->themes_menu->AddItem(theme_reset_item);
for(auto &ltheme: this->loadedThemes) {
auto itm = pu::ui::elm::MenuItem::New(ltheme.manifest.name + " (v" + ltheme.manifest.release + ", " + GetLanguageString("theme_by") + " " + ltheme.manifest.author + ")");
itm->AddOnClick(std::bind(&ThemeMenuLayout::theme_Click, this));
itm->SetColor(textclr);
auto iconpath = ltheme.path + "/theme/Icon.png";
itm->SetIcon(iconpath);
this->themesMenu->AddItem(itm);
}
for(const auto &theme: this->loaded_themes) {
auto theme_item = pu::ui::elm::MenuItem::New(theme.manifest.name + " (v" + theme.manifest.release + ", " + GetLanguageString("theme_by") + " " + theme.manifest.author + ")");
theme_item->AddOnKey(std::bind(&ThemeMenuLayout::theme_DefaultKey, this));
theme_item->SetColor(g_MenuApplication->GetTextColor());
theme_item->SetIcon(theme.path + "/theme/Icon.png");
this->themes_menu->AddItem(theme_item);
}
void ThemeMenuLayout::theme_Click() {
auto idx = this->themesMenu->GetSelectedIndex();
this->themes_menu->SetSelectedIndex(0);
}
void ThemeMenuLayout::theme_DefaultKey() {
const auto idx = this->themes_menu->GetSelectedIndex();
if(idx == 0) {
if(cfg::ThemeIsDefault(g_Theme)) {
if(g_Theme.IsDefault()) {
g_MenuApplication->ShowNotification(GetLanguageString("theme_no_custom"));
}
else {
auto sopt = g_MenuApplication->CreateShowDialog(GetLanguageString("theme_reset"), GetLanguageString("theme_reset_conf"), { GetLanguageString("yes"), GetLanguageString("cancel") }, true);
if(sopt == 0) {
const auto option = g_MenuApplication->CreateShowDialog(GetLanguageString("theme_reset"), GetLanguageString("theme_reset_conf"), { GetLanguageString("yes"), GetLanguageString("cancel") }, true);
if(option == 0) {
UL_ASSERT_TRUE(g_Config.SetEntry(cfg::ConfigEntryId::ActiveThemeName, std::string()));
cfg::SaveConfig(g_Config);
@ -135,7 +132,7 @@ namespace ui {
g_MenuApplication->CloseWithFadeOut();
g_MenuApplication->ShowNotification(GetLanguageString("theme_changed"));
UL_ASSERT(dmi::menu::SendCommand(dmi::DaemonMessage::RestartMenu, [&](dmi::menu::MenuScopedStorageWriter &writer) {
UL_RC_ASSERT(dmi::menu::SendCommand(dmi::DaemonMessage::RestartMenu, [&](dmi::menu::MenuScopedStorageWriter &writer) {
// ...
return ResultSuccess;
},
@ -147,23 +144,22 @@ namespace ui {
}
}
else {
idx--;
auto seltheme = this->loadedThemes[idx];
auto iconpath = seltheme.path + "/theme/Icon.png";
if(seltheme.base_name == g_Theme.base_name) {
const auto selected_theme = this->loaded_themes.at(idx - 1);
const auto theme_icon_path = selected_theme.path + "/theme/Icon.png";
if(selected_theme.base_name == g_Theme.base_name) {
g_MenuApplication->ShowNotification(GetLanguageString("theme_active_this"));
}
else {
auto sopt = g_MenuApplication->CreateShowDialog(GetLanguageString("theme_set"), GetLanguageString("theme_set_conf"), { GetLanguageString("yes"), GetLanguageString("cancel") }, true, iconpath);
if(sopt == 0) {
UL_ASSERT_TRUE(g_Config.SetEntry(cfg::ConfigEntryId::ActiveThemeName, seltheme.base_name));
const auto option = g_MenuApplication->CreateShowDialog(GetLanguageString("theme_set"), GetLanguageString("theme_set_conf"), { GetLanguageString("yes"), GetLanguageString("cancel") }, true, theme_icon_path);
if(option == 0) {
UL_ASSERT_TRUE(g_Config.SetEntry(cfg::ConfigEntryId::ActiveThemeName, selected_theme.base_name));
cfg::SaveConfig(g_Config);
g_MenuApplication->StopPlayBGM();
g_MenuApplication->CloseWithFadeOut();
g_MenuApplication->ShowNotification(GetLanguageString("theme_changed"));
UL_ASSERT(dmi::menu::SendCommand(dmi::DaemonMessage::RestartMenu, [&](dmi::menu::MenuScopedStorageWriter &writer) {
UL_RC_ASSERT(dmi::menu::SendCommand(dmi::DaemonMessage::RestartMenu, [&](dmi::menu::MenuScopedStorageWriter &writer) {
// ...
return ResultSuccess;
},

View file

@ -22,7 +22,7 @@ namespace uViewer
private Thread USBThread = null;
private ToolboxForm Toolbox = null;
private static MemoryStream BaseStream = new MemoryStream((int)RawRGBAScreenBufferSize);
private static MemoryStream BaseStream = new MemoryStream((int)PlainRgbaScreenBufferSize);
private static USBMode Mode = USBMode.Invalid;
public delegate void ApplyTypeImplDelegate(PictureBox Box, byte[] Data);
@ -56,12 +56,12 @@ namespace uViewer
private static void ApplyJPEG(PictureBox Box, byte[] JPEG)
{
BaseStream.Position = 0;
BaseStream.Write(JPEG, 4, (int)RawRGBAScreenBufferSize);
BaseStream.Write(JPEG, 4, (int)PlainRgbaScreenBufferSize);
Box.Image = Image.FromStream(BaseStream);
}
public const long RawRGBAScreenBufferSize = 1280 * 720 * 4;
public const long USBPacketSize = RawRGBAScreenBufferSize + 4;
public const long PlainRgbaScreenBufferSize = 1280 * 720 * 4;
public const long USBPacketSize = PlainRgbaScreenBufferSize + 4;
public byte[][] CaptureBlocks = new byte[][]
{