add info boxes, fix case sensitive path compares, many other changes (see comments)

- nca::ParseControl now gets the nacp base on the language.
- xci returns a more descriptive error and logs info during install.
- replace volatile with atmoic, label atmoic methods with volatile as per the standard.
- s2s usb install skips verifying the content as its being installed from another switch.
- fix game / save menu bug where it would only load the nacp if it has started yet, now it will force load even if its in progress.
  this fixes save file zips having empty names.
- game menu saves the application type, to be later used for displaying if its a gamecard (inserted or not), launched etc.
This commit is contained in:
ITotalJustice
2025-07-19 17:33:28 +01:00
parent ee68ca54b8
commit 4b06700187
20 changed files with 324 additions and 188 deletions

View File

@@ -576,6 +576,7 @@ enum class SphairaResult : Result {
NspBadMagic, NspBadMagic,
XciBadMagic, XciBadMagic,
XciSecurePartitionNotFound,
EsBadTitleKeyType, EsBadTitleKeyType,
EsPersonalisedTicketDeviceIdMissmatch, EsPersonalisedTicketDeviceIdMissmatch,
@@ -710,6 +711,7 @@ enum : Result {
MAKE_SPHAIRA_RESULT_ENUM(UsbDsBadDeviceSpeed), MAKE_SPHAIRA_RESULT_ENUM(UsbDsBadDeviceSpeed),
MAKE_SPHAIRA_RESULT_ENUM(NspBadMagic), MAKE_SPHAIRA_RESULT_ENUM(NspBadMagic),
MAKE_SPHAIRA_RESULT_ENUM(XciBadMagic), MAKE_SPHAIRA_RESULT_ENUM(XciBadMagic),
MAKE_SPHAIRA_RESULT_ENUM(XciSecurePartitionNotFound),
MAKE_SPHAIRA_RESULT_ENUM(EsBadTitleKeyType), MAKE_SPHAIRA_RESULT_ENUM(EsBadTitleKeyType),
MAKE_SPHAIRA_RESULT_ENUM(EsPersonalisedTicketDeviceIdMissmatch), MAKE_SPHAIRA_RESULT_ENUM(EsPersonalisedTicketDeviceIdMissmatch),
MAKE_SPHAIRA_RESULT_ENUM(EsFailedDecryptPersonalisedTicket), MAKE_SPHAIRA_RESULT_ENUM(EsFailedDecryptPersonalisedTicket),

View File

@@ -533,4 +533,10 @@ struct FsNativeSave final : FsNative {
} }
}; };
struct FsNativeId final : FsNative {
FsNativeId(u64 program_id, FsFileSystemType type, const FsPath& path, FsContentAttributes attr = FsContentAttributes_All) {
m_open_result = fsOpenFileSystemWithId(&m_fs, program_id, type, path, attr);
}
};
} // namespace fs } // namespace fs

View File

@@ -13,6 +13,7 @@ namespace sphaira::ui::menu::game {
struct Entry { struct Entry {
u64 app_id{}; u64 app_id{};
u8 type{};
NacpLanguageEntry lang{}; NacpLanguageEntry lang{};
int image{}; int image{};
bool selected{}; bool selected{};

View File

@@ -2,6 +2,7 @@
#include "ui/widget.hpp" #include "ui/widget.hpp"
#include "ui/list.hpp" #include "ui/list.hpp"
#include "ui/scrolling_text.hpp"
#include <memory> #include <memory>
#include <concepts> #include <concepts>
#include <utility> #include <utility>
@@ -10,11 +11,17 @@ namespace sphaira::ui {
class SidebarEntryBase : public Widget { class SidebarEntryBase : public Widget {
public: public:
SidebarEntryBase(std::string&& title); explicit SidebarEntryBase(const std::string& title, const std::string& info);
virtual auto Draw(NVGcontext* vg, Theme* theme) -> void override;
using Widget::Draw;
virtual void Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left);
protected: protected:
std::string m_title; std::string m_title;
private:
std::string m_info;
ScrollingText m_scolling_title{};
}; };
template<typename T> template<typename T>
@@ -25,11 +32,11 @@ public:
using Callback = std::function<void(bool&)>; using Callback = std::function<void(bool&)>;
public: public:
SidebarEntryBool(std::string title, bool option, Callback cb, std::string true_str = "On", std::string false_str = "Off"); explicit SidebarEntryBool(const std::string& title, bool option, Callback cb, const std::string& info = "", const std::string& true_str = "On", const std::string& false_str = "Off");
SidebarEntryBool(std::string title, bool& option, std::string true_str = "On", std::string false_str = "Off"); explicit SidebarEntryBool(const std::string& title, bool& option, const std::string& info = "", const std::string& true_str = "On", const std::string& false_str = "Off");
private: private:
auto Draw(NVGcontext* vg, Theme* theme) -> void override; void Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left) override;
bool m_option; bool m_option;
Callback m_callback; Callback m_callback;
@@ -42,8 +49,9 @@ public:
using Callback = std::function<void()>; using Callback = std::function<void()>;
public: public:
SidebarEntryCallback(std::string title, Callback cb, bool pop_on_click = false); explicit SidebarEntryCallback(const std::string& title, Callback cb, const std::string& info);
auto Draw(NVGcontext* vg, Theme* theme) -> void override; explicit SidebarEntryCallback(const std::string& title, Callback cb, bool pop_on_click = false, const std::string& info = "");
void Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left) override;
private: private:
Callback m_callback; Callback m_callback;
@@ -57,11 +65,11 @@ public:
using Callback = std::function<void(s64& index)>; using Callback = std::function<void(s64& index)>;
public: public:
explicit SidebarEntryArray(std::string title, Items items, Callback cb, s64 index = 0); explicit SidebarEntryArray(const std::string& title, const Items& items, Callback cb, s64 index = 0, const std::string& info = "");
SidebarEntryArray(std::string title, Items items, Callback cb, std::string index); explicit SidebarEntryArray(const std::string& title, const Items& items, Callback cb, const std::string& index, const std::string& info = "");
SidebarEntryArray(std::string title, Items items, std::string& index); explicit SidebarEntryArray(const std::string& title, const Items& items, std::string& index, const std::string& info = "");
auto Draw(NVGcontext* vg, Theme* theme) -> void override; void Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left) override;
auto OnFocusGained() noexcept -> void override; auto OnFocusGained() noexcept -> void override;
auto OnFocusLost() noexcept -> void override; auto OnFocusLost() noexcept -> void override;
@@ -74,39 +82,16 @@ private:
float m_text_yoff{}; float m_text_yoff{};
}; };
template <typename T>
class SidebarEntrySlider final : public SidebarEntryBase {
public:
SidebarEntrySlider(std::string title, T& value, T min, T max)
: SidebarEntryBase{title}
, m_value{value}
, m_min{min}
, m_max{max} {
}
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
auto Update(Controller* controller, TouchInfo* touch) -> void override;
private:
T& m_value;
T m_min;
T m_max;
T m_step{};
Vec4 m_bar{};
Vec4 m_bar_fill{};
};
class Sidebar final : public Widget { class Sidebar final : public Widget {
public: public:
enum class Side { LEFT, RIGHT }; enum class Side { LEFT, RIGHT };
using Items = std::vector<std::unique_ptr<SidebarEntryBase>>; using Items = std::vector<std::unique_ptr<SidebarEntryBase>>;
public: public:
Sidebar(std::string title, Side side, Items&& items); explicit Sidebar(const std::string& title, Side side, Items&& items);
Sidebar(std::string title, Side side); explicit Sidebar(const std::string& title, Side side);
Sidebar(std::string title, std::string sub, Side side, Items&& items); explicit Sidebar(const std::string& title, const std::string& sub, Side side, Items&& items);
Sidebar(std::string title, std::string sub, Side side); explicit Sidebar(const std::string& title, const std::string& sub, Side side);
auto Update(Controller* controller, TouchInfo* touch) -> void override; auto Update(Controller* controller, TouchInfo* touch) -> void override;
auto Draw(NVGcontext* vg, Theme* theme) -> void override; auto Draw(NVGcontext* vg, Theme* theme) -> void override;

View File

@@ -6,11 +6,13 @@
namespace sphaira::ns { namespace sphaira::ns {
enum ApplicationRecordType { enum ApplicationRecordType {
// installed ApplicationRecordType_Running = 0x0,
ApplicationRecordType_Installed = 0x3, ApplicationRecordType_Installed = 0x3,
ApplicationRecordType_Downloading = 0x4,
// application is gamecard, but gamecard isn't insterted // application is gamecard, but gamecard isn't insterted
ApplicationRecordType_GamecardMissing = 0x5, ApplicationRecordType_GamecardMissing = 0x5,
// archived ApplicationRecordType_Downloaded = 0x6,
ApplicationRecordType_Updated = 0xA,
ApplicationRecordType_Archived = 0xB, ApplicationRecordType_Archived = 0xB,
}; };

View File

@@ -1598,7 +1598,7 @@ void App::DisplayThemeOptions(bool left_side) {
options->Add<ui::SidebarEntryArray>("Select Theme"_i18n, theme_items, [](s64& index_out){ options->Add<ui::SidebarEntryArray>("Select Theme"_i18n, theme_items, [](s64& index_out){
App::SetTheme(index_out); App::SetTheme(index_out);
}, App::GetThemeIndex()); }, App::GetThemeIndex(), "Customise the look of Sphaira by changing the theme"_i18n);
options->Add<ui::SidebarEntryBool>("Music"_i18n, App::GetThemeMusicEnable(), [](bool& enable){ options->Add<ui::SidebarEntryBool>("Music"_i18n, App::GetThemeMusicEnable(), [](bool& enable){
App::SetThemeMusicEnable(enable); App::SetThemeMusicEnable(enable);
@@ -1703,15 +1703,17 @@ void App::DisplayAdvancedOptions(bool left_side) {
options->Add<ui::SidebarEntryBool>("Logging"_i18n, App::GetLogEnable(), [](bool& enable){ options->Add<ui::SidebarEntryBool>("Logging"_i18n, App::GetLogEnable(), [](bool& enable){
App::SetLogEnable(enable); App::SetLogEnable(enable);
}); }, "Logs to /config/sphaira/log.txt"_i18n);
options->Add<ui::SidebarEntryBool>("Replace hbmenu on exit"_i18n, App::GetReplaceHbmenuEnable(), [](bool& enable){ options->Add<ui::SidebarEntryBool>("Replace hbmenu on exit"_i18n, App::GetReplaceHbmenuEnable(), [](bool& enable){
App::SetReplaceHbmenuEnable(enable); App::SetReplaceHbmenuEnable(enable);
}); }, "When enabled, it replaces /hbmenu.nro with Sphaira, creating a backup of hbmenu to /switch/hbmenu.nro\n\n" \
"Disabling will give you the option to restore hbmenu."_i18n);
options->Add<ui::SidebarEntryBool>("Boost CPU during transfer"_i18n, App::GetApp()->m_progress_boost_mode.Get(), [](bool& enable){ options->Add<ui::SidebarEntryBool>("Boost CPU during transfer"_i18n, App::GetApp()->m_progress_boost_mode.Get(), [](bool& enable){
App::GetApp()->m_progress_boost_mode.Set(enable); App::GetApp()->m_progress_boost_mode.Set(enable);
}); }, "Enables boost mode during transfers which can improve transfer speed. "\
"This sets the CPU to 1785mhz and lowers the GPU 76mhz"_i18n);
options->Add<ui::SidebarEntryArray>("Text scroll speed"_i18n, text_scroll_speed_items, [](s64& index_out){ options->Add<ui::SidebarEntryArray>("Text scroll speed"_i18n, text_scroll_speed_items, [](s64& index_out){
App::SetTextScrollSpeed(index_out); App::SetTextScrollSpeed(index_out);
@@ -1776,7 +1778,7 @@ void App::DisplayAdvancedOptions(bool left_side) {
fs.DeleteFile(erpt_path); fs.DeleteFile(erpt_path);
fs.CreateDirectory(erpt_path); fs.CreateDirectory(erpt_path);
} }
}); }, "Disables error reports generated in /atmosphere/erpt_reports."_i18n);
} }
void App::DisplayInstallOptions(bool left_side) { void App::DisplayInstallOptions(bool left_side) {
@@ -1789,15 +1791,15 @@ void App::DisplayInstallOptions(bool left_side) {
options->Add<ui::SidebarEntryBool>("Enable sysmmc"_i18n, App::GetInstallSysmmcEnable(), [](bool& enable){ options->Add<ui::SidebarEntryBool>("Enable sysmmc"_i18n, App::GetInstallSysmmcEnable(), [](bool& enable){
App::SetInstallSysmmcEnable(enable); App::SetInstallSysmmcEnable(enable);
}); }, "Enables installing whilst in sysMMC mode."_i18n);
options->Add<ui::SidebarEntryBool>("Enable emummc"_i18n, App::GetInstallEmummcEnable(), [](bool& enable){ options->Add<ui::SidebarEntryBool>("Enable emummc"_i18n, App::GetInstallEmummcEnable(), [](bool& enable){
App::SetInstallEmummcEnable(enable); App::SetInstallEmummcEnable(enable);
}); }, "Enables installing whilst in emuMMC mode."_i18n);
options->Add<ui::SidebarEntryBool>("Show install warning"_i18n, App::GetApp()->m_install_prompt.Get(), [](bool& enable){ options->Add<ui::SidebarEntryBool>("Show install warning"_i18n, App::GetApp()->m_install_prompt.Get(), [](bool& enable){
App::GetApp()->m_install_prompt.Set(enable); App::GetApp()->m_install_prompt.Set(enable);
}); }, "When enabled, a warning is show when attempting to install a forwarder."_i18n);
options->Add<ui::SidebarEntryArray>("Install location"_i18n, install_items, [](s64& index_out){ options->Add<ui::SidebarEntryArray>("Install location"_i18n, install_items, [](s64& index_out){
App::SetInstallSdEnable(index_out); App::SetInstallSdEnable(index_out);
@@ -1805,67 +1807,85 @@ void App::DisplayInstallOptions(bool left_side) {
options->Add<ui::SidebarEntryBool>("Allow downgrade"_i18n, App::GetApp()->m_allow_downgrade.Get(), [](bool& enable){ options->Add<ui::SidebarEntryBool>("Allow downgrade"_i18n, App::GetApp()->m_allow_downgrade.Get(), [](bool& enable){
App::GetApp()->m_allow_downgrade.Set(enable); App::GetApp()->m_allow_downgrade.Set(enable);
}); }, "Allows for installing title updates that are lower than the currently installed update."_i18n);
options->Add<ui::SidebarEntryBool>("Skip if already installed"_i18n, App::GetApp()->m_skip_if_already_installed.Get(), [](bool& enable){ options->Add<ui::SidebarEntryBool>("Skip if already installed"_i18n, App::GetApp()->m_skip_if_already_installed.Get(), [](bool& enable){
App::GetApp()->m_skip_if_already_installed.Set(enable); App::GetApp()->m_skip_if_already_installed.Set(enable);
}); }, "Skips installing titles / ncas if they're already installed."_i18n);
options->Add<ui::SidebarEntryBool>("Ticket only"_i18n, App::GetApp()->m_ticket_only.Get(), [](bool& enable){ options->Add<ui::SidebarEntryBool>("Ticket only"_i18n, App::GetApp()->m_ticket_only.Get(), [](bool& enable){
App::GetApp()->m_ticket_only.Set(enable); App::GetApp()->m_ticket_only.Set(enable);
}); }, "Installs tickets only, useful if the title was already installed however the tickets were missing or corrupted."_i18n);
options->Add<ui::SidebarEntryBool>("Skip base"_i18n, App::GetApp()->m_skip_base.Get(), [](bool& enable){ options->Add<ui::SidebarEntryBool>("Skip base"_i18n, App::GetApp()->m_skip_base.Get(), [](bool& enable){
App::GetApp()->m_skip_base.Set(enable); App::GetApp()->m_skip_base.Set(enable);
}); }, "Skips installing the base application."_i18n);
options->Add<ui::SidebarEntryBool>("Skip patch"_i18n, App::GetApp()->m_skip_patch.Get(), [](bool& enable){ options->Add<ui::SidebarEntryBool>("Skip patch"_i18n, App::GetApp()->m_skip_patch.Get(), [](bool& enable){
App::GetApp()->m_skip_patch.Set(enable); App::GetApp()->m_skip_patch.Set(enable);
}); }, "Skips installing updates."_i18n);
options->Add<ui::SidebarEntryBool>("Skip dlc"_i18n, App::GetApp()->m_skip_addon.Get(), [](bool& enable){ options->Add<ui::SidebarEntryBool>("Skip dlc"_i18n, App::GetApp()->m_skip_addon.Get(), [](bool& enable){
App::GetApp()->m_skip_addon.Set(enable); App::GetApp()->m_skip_addon.Set(enable);
}); }, "Skips installing DLC."_i18n);
options->Add<ui::SidebarEntryBool>("Skip data patch"_i18n, App::GetApp()->m_skip_data_patch.Get(), [](bool& enable){ options->Add<ui::SidebarEntryBool>("Skip data patch"_i18n, App::GetApp()->m_skip_data_patch.Get(), [](bool& enable){
App::GetApp()->m_skip_data_patch.Set(enable); App::GetApp()->m_skip_data_patch.Set(enable);
}); }, "Skips installing DLC update (data patch)."_i18n);
options->Add<ui::SidebarEntryBool>("Skip ticket"_i18n, App::GetApp()->m_skip_ticket.Get(), [](bool& enable){ options->Add<ui::SidebarEntryBool>("Skip ticket"_i18n, App::GetApp()->m_skip_ticket.Get(), [](bool& enable){
App::GetApp()->m_skip_ticket.Set(enable); App::GetApp()->m_skip_ticket.Set(enable);
}); }, "Skips installing tickets, not recommended."_i18n);
options->Add<ui::SidebarEntryBool>("Skip NCA hash verify"_i18n, App::GetApp()->m_skip_nca_hash_verify.Get(), [](bool& enable){ options->Add<ui::SidebarEntryBool>("Skip NCA hash verify"_i18n, App::GetApp()->m_skip_nca_hash_verify.Get(), [](bool& enable){
App::GetApp()->m_skip_nca_hash_verify.Set(enable); App::GetApp()->m_skip_nca_hash_verify.Set(enable);
}); }, "Enables the option to skip sha256 verification. This is a hash over the entire NCA. "\
"It is used to verify that the NCA is valid / not corrupted. "\
"You may have seen the option for \"checking for corrupted data\" when a corrupted game is installed. "\
"That check performs various hash checks, including the hash over the NCA.\n\n"\
"It is recommended to keep this disabled."_i18n);
options->Add<ui::SidebarEntryBool>("Skip RSA header verify"_i18n, App::GetApp()->m_skip_rsa_header_fixed_key_verify.Get(), [](bool& enable){ options->Add<ui::SidebarEntryBool>("Skip RSA header verify"_i18n, App::GetApp()->m_skip_rsa_header_fixed_key_verify.Get(), [](bool& enable){
App::GetApp()->m_skip_rsa_header_fixed_key_verify.Set(enable); App::GetApp()->m_skip_rsa_header_fixed_key_verify.Set(enable);
}); }, "Enables the option to skip RSA NCA fixed key verification. "\
"This is a hash over the NCA header. It is used to verify that the header has not been modified. "\
"The header is signed by nintendo, thus it cannot be forged, and is reliable to detect modified NCA headers (such as NSP/XCI converts).\n\n"\
"It is recommended to keep this disabled, unless you need to install nsp/xci converts."_i18n);
options->Add<ui::SidebarEntryBool>("Skip RSA NPDM verify"_i18n, App::GetApp()->m_skip_rsa_npdm_fixed_key_verify.Get(), [](bool& enable){ options->Add<ui::SidebarEntryBool>("Skip RSA NPDM verify"_i18n, App::GetApp()->m_skip_rsa_npdm_fixed_key_verify.Get(), [](bool& enable){
App::GetApp()->m_skip_rsa_npdm_fixed_key_verify.Set(enable); App::GetApp()->m_skip_rsa_npdm_fixed_key_verify.Set(enable);
}); }, "Enables the option to skip RSA NPDM fixed key verification.\n\n"\
"Currently, this option is stubbed (not implemented)."_i18n);
options->Add<ui::SidebarEntryBool>("Ignore distribution bit"_i18n, App::GetApp()->m_ignore_distribution_bit.Get(), [](bool& enable){ options->Add<ui::SidebarEntryBool>("Ignore distribution bit"_i18n, App::GetApp()->m_ignore_distribution_bit.Get(), [](bool& enable){
App::GetApp()->m_ignore_distribution_bit.Set(enable); App::GetApp()->m_ignore_distribution_bit.Set(enable);
}); }, "If set, it will ignore the distribution bit in the NCA header. "\
"The distribution bit is used to signify whether a NCA is Eshop or GameCard. "\
"You cannot (normally) launch install games that have the distruction bit set to GameCard.\n\n"\
"It is recommended to keep this disabled."_i18n);
options->Add<ui::SidebarEntryBool>("Convert to common ticket"_i18n, App::GetApp()->m_convert_to_common_ticket.Get(), [](bool& enable){ options->Add<ui::SidebarEntryBool>("Convert to common ticket"_i18n, App::GetApp()->m_convert_to_common_ticket.Get(), [](bool& enable){
App::GetApp()->m_convert_to_common_ticket.Set(enable); App::GetApp()->m_convert_to_common_ticket.Set(enable);
}); }, "[Requires keys] Converts personalised tickets to common (fake) tickets.\n\n"\
"It is recommended to keep this enabled."_i18n);
options->Add<ui::SidebarEntryBool>("Convert to standard crypto"_i18n, App::GetApp()->m_convert_to_standard_crypto.Get(), [](bool& enable){ options->Add<ui::SidebarEntryBool>("Convert to standard crypto"_i18n, App::GetApp()->m_convert_to_standard_crypto.Get(), [](bool& enable){
App::GetApp()->m_convert_to_standard_crypto.Set(enable); App::GetApp()->m_convert_to_standard_crypto.Set(enable);
}); }, "[Requires keys] Converts titlekey to standard crypto, also known as \"ticketless\".\n\n"\
"It is recommended to keep this disabled."_i18n);
options->Add<ui::SidebarEntryBool>("Lower master key"_i18n, App::GetApp()->m_lower_master_key.Get(), [](bool& enable){ options->Add<ui::SidebarEntryBool>("Lower master key"_i18n, App::GetApp()->m_lower_master_key.Get(), [](bool& enable){
App::GetApp()->m_lower_master_key.Set(enable); App::GetApp()->m_lower_master_key.Set(enable);
}); }, "[Requires keys] Encrypts the keak (key area key) with master key 0, which allows the game to be launched on every fw. "\
"Implicitly performs standard crypto.\n\n"\
"Do note that just because the game can be launched on any fw (as it can be decrypted), doesn't mean it will work. It is strongly recommened to update your firmware and Atmosphere version in order to play the game, rather than enabling this option.\n\n"\
"It is recommended to keep this disabled."_i18n);
options->Add<ui::SidebarEntryBool>("Lower system version"_i18n, App::GetApp()->m_lower_system_version.Get(), [](bool& enable){ options->Add<ui::SidebarEntryBool>("Lower system version"_i18n, App::GetApp()->m_lower_system_version.Get(), [](bool& enable){
App::GetApp()->m_lower_system_version.Set(enable); App::GetApp()->m_lower_system_version.Set(enable);
}); }, "Sets the system_firmware field in the cnmt extended header to 0. "\
"Note: if the master key is higher than fw version, the game still won't launch as the fw won't have the key to decrypt keak (see above).\n\n"\
"It is recommended to keep this disabled."_i18n);
} }
void App::DisplayDumpOptions(bool left_side) { void App::DisplayDumpOptions(bool left_side) {

View File

@@ -31,7 +31,7 @@ const char* INI_PATH = "/config/ftpsrv/config.ini";
constexpr int THREAD_PRIO = PRIO_PREEMPTIVE; constexpr int THREAD_PRIO = PRIO_PREEMPTIVE;
constexpr int THREAD_CORE = 2; constexpr int THREAD_CORE = 2;
FtpSrvConfig g_ftpsrv_config = {0}; FtpSrvConfig g_ftpsrv_config = {0};
volatile bool g_should_exit = false; std::atomic_bool g_should_exit = false;
bool g_is_running{false}; bool g_is_running{false};
Thread g_thread; Thread g_thread;
Mutex g_mutex{}; Mutex g_mutex{};

View File

@@ -27,7 +27,7 @@ struct InstallSharedData {
constexpr int THREAD_PRIO = PRIO_PREEMPTIVE; constexpr int THREAD_PRIO = PRIO_PREEMPTIVE;
constexpr int THREAD_CORE = 2; constexpr int THREAD_CORE = 2;
volatile bool g_should_exit = false; std::atomic_bool g_should_exit = false;
bool g_is_running{false}; bool g_is_running{false};
Mutex g_mutex{}; Mutex g_mutex{};
InstallSharedData g_shared_data{}; InstallSharedData g_shared_data{};

View File

@@ -7,6 +7,7 @@
#include <vector> #include <vector>
#include <algorithm> #include <algorithm>
#include <cstring> #include <cstring>
#include <atomic>
#include <minizip/unzip.h> #include <minizip/unzip.h>
#include <minizip/zip.h> #include <minizip/zip.h>
@@ -73,7 +74,7 @@ public:
struct ThreadData { struct ThreadData {
ThreadData(ui::ProgressBox* _pbox, s64 size, ReadCallback _rfunc, WriteCallback _wfunc, u64 buffer_size); ThreadData(ui::ProgressBox* _pbox, s64 size, ReadCallback _rfunc, WriteCallback _wfunc, u64 buffer_size);
auto GetResults() -> Result; auto GetResults() volatile -> Result;
void WakeAllThreads(); void WakeAllThreads();
void SetReadResult(Result result) { void SetReadResult(Result result) {
@@ -88,7 +89,7 @@ struct ThreadData {
pull_result = result; pull_result = result;
} }
auto GetWriteOffset() const { auto GetWriteOffset() volatile const -> s64 {
return write_offset; return write_offset;
} }
@@ -131,12 +132,12 @@ private:
const s64 write_size; const s64 write_size;
// these are shared between threads // these are shared between threads
volatile s64 read_offset{}; std::atomic<s64> read_offset{};
volatile s64 write_offset{}; std::atomic<s64> write_offset{};
volatile Result read_result{}; std::atomic<Result> read_result{};
volatile Result write_result{}; std::atomic<Result> write_result{};
volatile Result pull_result{}; std::atomic<Result> pull_result{};
}; };
ThreadData::ThreadData(ui::ProgressBox* _pbox, s64 size, ReadCallback _rfunc, WriteCallback _wfunc, u64 buffer_size) ThreadData::ThreadData(ui::ProgressBox* _pbox, s64 size, ReadCallback _rfunc, WriteCallback _wfunc, u64 buffer_size)
@@ -154,11 +155,11 @@ ThreadData::ThreadData(ui::ProgressBox* _pbox, s64 size, ReadCallback _rfunc, Wr
condvarInit(std::addressof(can_pull_write)); condvarInit(std::addressof(can_pull_write));
} }
auto ThreadData::GetResults() -> Result { auto ThreadData::GetResults() volatile -> Result {
R_UNLESS(!pbox->ShouldExit(), Result_TransferCancelled); R_UNLESS(!pbox->ShouldExit(), Result_TransferCancelled);
R_TRY(read_result); R_TRY(read_result.load());
R_TRY(write_result); R_TRY(write_result.load());
R_TRY(pull_result); R_TRY(pull_result.load());
R_SUCCEED(); R_SUCCEED();
} }
@@ -323,7 +324,9 @@ Result TransferInternal(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, Wri
} }
// single threaded pull buffer is not supported. // single threaded pull buffer is not supported.
R_UNLESS(mode != Mode::MultiThreaded || !sfunc, 0x1); log_write("checking invalid transfer mode: %u %u\n", mode == Mode::MultiThreaded, !sfunc);
R_UNLESS(mode == Mode::MultiThreaded || !sfunc, 0x1);
log_write("valid transfer mode\n");
// todo: support single threaded pull buffer. // todo: support single threaded pull buffer.
if (mode == Mode::SingleThreaded) { if (mode == Mode::SingleThreaded) {
@@ -374,8 +377,7 @@ Result TransferInternal(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, Wri
R_TRY(t_data.GetResults()); R_TRY(t_data.GetResults());
return t_data.Pull(data, size, bytes_read); return t_data.Pull(data, size, bytes_read);
})); }));
} } else {
else {
log_write("[THREAD] doing normal\n"); log_write("[THREAD] doing normal\n");
R_TRY(start_threads()); R_TRY(start_threads());
log_write("[THREAD] started threads\n"); log_write("[THREAD] started threads\n");

View File

@@ -100,6 +100,7 @@ auto GetCodeMessage(Result rc) -> const char* {
case Result_UsbDsBadDeviceSpeed: return "SphairaError_UsbDsBadDeviceSpeed"; case Result_UsbDsBadDeviceSpeed: return "SphairaError_UsbDsBadDeviceSpeed";
case Result_NspBadMagic: return "SphairaError_NspBadMagic"; case Result_NspBadMagic: return "SphairaError_NspBadMagic";
case Result_XciBadMagic: return "SphairaError_XciBadMagic"; case Result_XciBadMagic: return "SphairaError_XciBadMagic";
case Result_XciSecurePartitionNotFound: return "SphairaError_XciSecurePartitionNotFound";
case Result_EsBadTitleKeyType: return "SphairaError_EsBadTitleKeyType"; case Result_EsBadTitleKeyType: return "SphairaError_EsBadTitleKeyType";
case Result_EsPersonalisedTicketDeviceIdMissmatch: return "SphairaError_EsPersonalisedTicketDeviceIdMissmatch"; case Result_EsPersonalisedTicketDeviceIdMissmatch: return "SphairaError_EsPersonalisedTicketDeviceIdMissmatch";
case Result_EsFailedDecryptPersonalisedTicket: return "SphairaError_EsFailedDecryptPersonalisedTicket"; case Result_EsFailedDecryptPersonalisedTicket: return "SphairaError_EsFailedDecryptPersonalisedTicket";

View File

@@ -84,6 +84,10 @@ constexpr std::string_view ZIP_EXTENSIONS[] = {
"zip", "zip",
}; };
// case insensitive check
auto IsSamePath(std::string_view a, std::string_view b) -> bool {
return a.length() == b.length() && !strncasecmp(a.data(), b.data(), a.length());
}
struct RomDatabaseEntry { struct RomDatabaseEntry {
// uses the naming scheme from retropie. // uses the naming scheme from retropie.
@@ -95,12 +99,12 @@ struct RomDatabaseEntry {
// compares against all of the above strings. // compares against all of the above strings.
auto IsDatabase(std::string_view name) const { auto IsDatabase(std::string_view name) const {
if (name == folder || name == database) { if (IsSamePath(name, folder) || IsSamePath(name, database)) {
return true; return true;
} }
for (const auto& str : alias) { for (const auto& str : alias) {
if (!str.empty() && name == str) { if (!str.empty() && IsSamePath(name, str)) {
return true; return true;
} }
} }
@@ -348,7 +352,7 @@ FsView::FsView(Menu* menu, const fs::FsPath& path, const FsEntry& entry, ViewSid
Scan(GetNewPathCurrent()); Scan(GetNewPathCurrent());
} else { } else {
// special case for nro // special case for nro
if (IsSd() && entry.GetExtension() == "nro") { if (IsSd() && IsSamePath(entry.GetExtension(), "nro")) {
App::Push<OptionBox>("Launch "_i18n + entry.GetName() + '?', App::Push<OptionBox>("Launch "_i18n + entry.GetName() + '?',
"No"_i18n, "Launch"_i18n, 1, [this](auto op_index){ "No"_i18n, "Launch"_i18n, 1, [this](auto op_index){
if (op_index && *op_index) { if (op_index && *op_index) {
@@ -598,7 +602,7 @@ void FsView::SetIndex(s64 index) {
m_list->SetYoff(); m_list->SetYoff();
} }
if (IsSd() && !m_entries_current.empty() && !GetEntry().checked_internal_extension && GetEntry().extension == "zip") { if (IsSd() && !m_entries_current.empty() && !GetEntry().checked_internal_extension && IsSamePath(GetEntry().extension, "zip")) {
GetEntry().checked_internal_extension = true; GetEntry().checked_internal_extension = true;
if (auto zfile = unzOpen64(GetNewPathCurrent())) { if (auto zfile = unzOpen64(GetNewPathCurrent())) {
@@ -623,7 +627,7 @@ void FsView::SetIndex(s64 index) {
} }
void FsView::InstallForwarder() { void FsView::InstallForwarder() {
if (GetEntry().GetExtension() == "nro") { if (IsSamePath(GetEntry().GetExtension(), "nro")) {
if (R_FAILED(homebrew::Menu::InstallHomebrewFromPath(GetNewPathCurrent()))) { if (R_FAILED(homebrew::Menu::InstallHomebrewFromPath(GetNewPathCurrent()))) {
log_write("failed to create forwarder\n"); log_write("failed to create forwarder\n");
} }
@@ -1364,13 +1368,12 @@ auto FsView::CheckIfUpdateFolder() -> Result {
R_UNLESS(m_entries.size() > 150 && m_entries.size() < 300, Result_FileBrowserDirNotDaybreak); R_UNLESS(m_entries.size() > 150 && m_entries.size() < 300, Result_FileBrowserDirNotDaybreak);
// check that all entries end in .nca // check that all entries end in .nca
const auto nca_ext = std::string_view{".nca"};
for (auto& e : m_entries) { for (auto& e : m_entries) {
// check that we are at the bottom level // check that we are at the bottom level
R_UNLESS(e.type == FsDirEntryType_File, Result_FileBrowserDirNotDaybreak); R_UNLESS(e.type == FsDirEntryType_File, Result_FileBrowserDirNotDaybreak);
const auto ext = std::strrchr(e.name, '.'); const auto ext = std::strrchr(e.name, '.');
R_UNLESS(ext && ext == nca_ext, Result_FileBrowserDirNotDaybreak); R_UNLESS(ext && IsSamePath(ext, ".nca"), Result_FileBrowserDirNotDaybreak);
} }
R_SUCCEED(); R_SUCCEED();
@@ -1680,7 +1683,7 @@ void FsView::DisplayOptions() {
} }
if (IsSd() && m_entries_current.size() && !m_selected_count) { if (IsSd() && m_entries_current.size() && !m_selected_count) {
if (App::GetInstallEnable() && GetEntry().IsFile() && (GetEntry().GetExtension() == "nro" || !m_menu->FindFileAssocFor().empty())) { if (App::GetInstallEnable() && GetEntry().IsFile() && (IsSamePath(GetEntry().GetExtension(), "nro") || !m_menu->FindFileAssocFor().empty())) {
options->Add<SidebarEntryCallback>("Install Forwarder"_i18n, [this](){; options->Add<SidebarEntryCallback>("Install Forwarder"_i18n, [this](){;
if (App::GetInstallPrompt()) { if (App::GetInstallPrompt()) {
App::Push<OptionBox>( App::Push<OptionBox>(

View File

@@ -208,7 +208,7 @@ void LoadResultIntoEntry(Entry& e, title::ThreadResultData* result) {
} }
void LoadControlEntry(Entry& e, bool force_image_load = false) { void LoadControlEntry(Entry& e, bool force_image_load = false) {
if (e.status == title::NacpLoadStatus::None) { if (e.status != title::NacpLoadStatus::Loaded) {
LoadResultIntoEntry(e, title::Get(e.app_id)); LoadResultIntoEntry(e, title::Get(e.app_id));
} }
@@ -784,25 +784,12 @@ void Menu::ScanHomebrew() {
for (s32 i = 0; i < record_count; i++) { for (s32 i = 0; i < record_count; i++) {
const auto& e = record_list[i]; const auto& e = record_list[i];
#if 0
u8 unk_x09 = e.unk_x09;
u64 unk_x0a;// = e.unk_x0a;
u8 unk_x10 = e.unk_x10;
u64 unk_x11;// = e.unk_x11;
memcpy(&unk_x0a, e.unk_x0a, sizeof(e.unk_x0a));
memcpy(&unk_x11, e.unk_x11, sizeof(e.unk_x11));
log_write("ID: %016lx got type: %u unk_x09: %u unk_x0a: %zu unk_x10: %u unk_x11: %zu\n", e.app_id, e.type,
unk_x09,
unk_x0a,
unk_x10,
unk_x11
);
#endif
if (hide_forwarders && (e.application_id & 0x0500000000000000) == 0x0500000000000000) { if (hide_forwarders && (e.application_id & 0x0500000000000000) == 0x0500000000000000) {
continue; continue;
} }
m_entries.emplace_back(e.application_id); m_entries.emplace_back(e.application_id, e.type);
} }
offset += record_count; offset += record_count;

View File

@@ -18,7 +18,7 @@ enum class InstallState {
constexpr u64 MAX_BUFFER_SIZE = 1024ULL*1024ULL*8ULL; constexpr u64 MAX_BUFFER_SIZE = 1024ULL*1024ULL*8ULL;
constexpr u64 MAX_BUFFER_RESERVE_SIZE = 1024ULL*1024ULL*32ULL; constexpr u64 MAX_BUFFER_RESERVE_SIZE = 1024ULL*1024ULL*32ULL;
volatile InstallState INSTALL_STATE{InstallState::None}; std::atomic<InstallState> INSTALL_STATE{InstallState::None};
} // namespace } // namespace

View File

@@ -257,7 +257,7 @@ MainMenu::MainMenu() {
options->Add<SidebarEntryCallback>("Theme"_i18n, [](){ options->Add<SidebarEntryCallback>("Theme"_i18n, [](){
App::DisplayThemeOptions(); App::DisplayThemeOptions();
}); }, "Customise the look of Sphaira by changing the theme"_i18n);
options->Add<SidebarEntryCallback>("Network"_i18n, [this](){ options->Add<SidebarEntryCallback>("Network"_i18n, [this](){
auto options = std::make_unique<Sidebar>("Network Options"_i18n, Sidebar::Side::LEFT); auto options = std::make_unique<Sidebar>("Network Options"_i18n, Sidebar::Side::LEFT);
@@ -302,7 +302,8 @@ MainMenu::MainMenu() {
options->Add<SidebarEntryBool>("Hdd write protect"_i18n, App::GetWriteProtect(), [](bool& enable){ options->Add<SidebarEntryBool>("Hdd write protect"_i18n, App::GetWriteProtect(), [](bool& enable){
App::SetWriteProtect(enable); App::SetWriteProtect(enable);
}); });
}); }, "Toggle FTP, MTP, HDD and NXlink\n\n" \
"If Sphaira has a update available, you can download it from this menu"_i18n);
options->Add<SidebarEntryArray>("Language"_i18n, language_items, [](s64& index_out){ options->Add<SidebarEntryArray>("Language"_i18n, language_items, [](s64& index_out){
App::SetLanguage(index_out); App::SetLanguage(index_out);
@@ -310,7 +311,7 @@ MainMenu::MainMenu() {
options->Add<SidebarEntryCallback>("Misc"_i18n, [](){ options->Add<SidebarEntryCallback>("Misc"_i18n, [](){
App::DisplayMiscOptions(); App::DisplayMiscOptions();
}); }, "View and launch one of Sphaira's menus"_i18n);
options->Add<SidebarEntryCallback>("Advanced"_i18n, [](){ options->Add<SidebarEntryCallback>("Advanced"_i18n, [](){
App::DisplayAdvancedOptions(); App::DisplayAdvancedOptions();

View File

@@ -240,7 +240,7 @@ void LoadResultIntoEntry(Entry& e, title::ThreadResultData* result) {
} }
void LoadControlEntry(Entry& e, bool force_image_load = false) { void LoadControlEntry(Entry& e, bool force_image_load = false) {
if (e.status == title::NacpLoadStatus::None) { if (e.status != title::NacpLoadStatus::Loaded) {
if (e.save_data_type == FsSaveDataType_System || e.save_data_type == FsSaveDataType_SystemBcat) { if (e.save_data_type == FsSaveDataType_System || e.save_data_type == FsSaveDataType_SystemBcat) {
FakeNacpEntryForSystem(e); FakeNacpEntryForSystem(e);
} else { } else {

View File

@@ -112,10 +112,19 @@ void Menu::Update(Controller* controller, TouchInfo* touch) {
App::Push<ui::ProgressBox>(0, "Installing "_i18n, "", [this](auto pbox) -> Result { App::Push<ui::ProgressBox>(0, "Installing "_i18n, "", [this](auto pbox) -> Result {
ON_SCOPE_EXIT(m_usb_source->Finished(FINISHED_TIMEOUT)); ON_SCOPE_EXIT(m_usb_source->Finished(FINISHED_TIMEOUT));
// if we are doing s2s install, skip verifying the nca contents.
yati::ConfigOverride config_override{};
if (m_usb_source->IsStream()) {
config_override.skip_nca_hash_verify = true;
config_override.skip_rsa_header_fixed_key_verify = true;
config_override.skip_rsa_npdm_fixed_key_verify = true;
}
log_write("inside progress box\n"); log_write("inside progress box\n");
for (const auto& file_name : m_names) { for (const auto& file_name : m_names) {
pbox->SetTitle(file_name);
m_usb_source->SetFileNameForTranfser(file_name); m_usb_source->SetFileNameForTranfser(file_name);
const auto rc = yati::InstallFromSource(pbox, m_usb_source.get(), file_name); const auto rc = yati::InstallFromSource(pbox, m_usb_source.get(), file_name, config_override);
if (R_FAILED(rc)) { if (R_FAILED(rc)) {
m_usb_source->SignalCancel(); m_usb_source->SignalCancel();
log_write("exiting usb install\n"); log_write("exiting usb install\n");

View File

@@ -25,24 +25,63 @@ auto DistanceBetweenY(Vec4 va, Vec4 vb) -> Vec4 {
} // namespace } // namespace
SidebarEntryBase::SidebarEntryBase(std::string&& title) SidebarEntryBase::SidebarEntryBase(const std::string& title, const std::string& info)
: m_title{std::forward<decltype(title)>(title)} { : m_title{title}, m_info{info} {
} }
auto SidebarEntryBase::Draw(NVGcontext* vg, Theme* theme) -> void { void SidebarEntryBase::Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left) {
// draw spacers or highlight box if in focus (selected) // draw spacers or highlight box if in focus (selected)
if (HasFocus()) { if (HasFocus()) {
gfx::drawRectOutline(vg, theme, 4.f, m_pos); gfx::drawRectOutline(vg, theme, 4.f, m_pos);
if (!m_info.empty()) {
// reset clip here as the box will draw oob.
nvgSave(vg);
nvgScissor(vg, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
ON_SCOPE_EXIT(nvgRestore(vg));
Vec4 info_box{};
info_box.y = 86;
info_box.w = 400;
if (left) {
info_box.x = root_pos.x + root_pos.w + 10;
} else {
info_box.x = root_pos.x - info_box.w - 10;
}
const float info_pad = 30;
const float title_font_size = 18;
const float info_font_size = 18;
const float pad_after_title = title_font_size + info_pad;
const float x = info_box.x + info_pad;
const auto end_w = info_box.w - info_pad * 2;
float bounds[4];
nvgFontSize(vg, info_font_size);
nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
nvgTextLineHeight(vg, 1.7);
nvgTextBoxBounds(vg, 0, 0, end_w, m_info.c_str(), nullptr, bounds);
info_box.h = pad_after_title + info_pad * 2 + bounds[3] - bounds[1];
gfx::drawRect(vg, info_box, theme->GetColour(ThemeEntryID_SIDEBAR), 5);
float y = info_box.y + info_pad;
m_scolling_title.Draw(vg, true, x, y, end_w, title_font_size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), m_title.c_str());
y += pad_after_title;
gfx::drawTextBox(vg, x, y, info_font_size, end_w, theme->GetColour(ThemeEntryID_TEXT), m_info.c_str());
}
} }
} }
SidebarEntryBool::SidebarEntryBool(std::string title, bool option, Callback cb, std::string true_str, std::string false_str) SidebarEntryBool::SidebarEntryBool(const std::string& title, bool option, Callback cb, const std::string& info, const std::string& true_str, const std::string& false_str)
: SidebarEntryBase{std::move(title)} : SidebarEntryBase{title, info}
, m_option{option} , m_option{option}
, m_callback{cb} , m_callback{cb}
, m_true_str{std::move(true_str)} , m_true_str{true_str}
, m_false_str{std::move(false_str)} { , m_false_str{false_str} {
if (m_true_str == "On") { if (m_true_str == "On") {
m_true_str = i18n::get(m_true_str); m_true_str = i18n::get(m_true_str);
@@ -58,15 +97,15 @@ SidebarEntryBool::SidebarEntryBool(std::string title, bool option, Callback cb,
}); });
} }
SidebarEntryBool::SidebarEntryBool(std::string title, bool& option, std::string true_str, std::string false_str) SidebarEntryBool::SidebarEntryBool(const std::string& title, bool& option, const std::string& info, const std::string& true_str, const std::string& false_str)
: SidebarEntryBool{std::move(title), option, Callback{} } { : SidebarEntryBool{title, option, Callback{}, info, true_str, false_str} {
m_callback = [](bool& option){ m_callback = [](bool& option){
option ^= 1; option ^= 1;
}; };
} }
auto SidebarEntryBool::Draw(NVGcontext* vg, Theme* theme) -> void { void SidebarEntryBool::Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left) {
SidebarEntryBase::Draw(vg, theme); SidebarEntryBase::Draw(vg, theme, root_pos, left);
// if (HasFocus()) { // if (HasFocus()) {
// gfx::drawText(vg, Vec2{m_pos.x + 15.f, m_pos.y + (m_pos.h / 2.f)}, 20.f, theme->GetColour(ThemeEntryID_TEXT_SELECTED), m_title.c_str(), NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); // gfx::drawText(vg, Vec2{m_pos.x + 15.f, m_pos.y + (m_pos.h / 2.f)}, 20.f, theme->GetColour(ThemeEntryID_TEXT_SELECTED), m_title.c_str(), NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
@@ -82,8 +121,8 @@ auto SidebarEntryBool::Draw(NVGcontext* vg, Theme* theme) -> void {
} }
} }
SidebarEntryCallback::SidebarEntryCallback(std::string title, Callback cb, bool pop_on_click) SidebarEntryCallback::SidebarEntryCallback(const std::string& title, Callback cb, bool pop_on_click, const std::string& info)
: SidebarEntryBase{std::move(title)} : SidebarEntryBase{title, info}
, m_callback{cb} , m_callback{cb}
, m_pop_on_click{pop_on_click} { , m_pop_on_click{pop_on_click} {
SetAction(Button::A, Action{"OK"_i18n, [this](){ SetAction(Button::A, Action{"OK"_i18n, [this](){
@@ -95,8 +134,13 @@ SidebarEntryCallback::SidebarEntryCallback(std::string title, Callback cb, bool
}); });
} }
auto SidebarEntryCallback::Draw(NVGcontext* vg, Theme* theme) -> void { SidebarEntryCallback::SidebarEntryCallback(const std::string& title, Callback cb, const std::string& info)
SidebarEntryBase::Draw(vg, theme); : SidebarEntryCallback{title, cb, false, info} {
}
void SidebarEntryCallback::Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left) {
SidebarEntryBase::Draw(vg, theme, root_pos, left);
// if (HasFocus()) { // if (HasFocus()) {
// gfx::drawText(vg, Vec2{m_pos.x + 15.f, m_pos.y + (m_pos.h / 2.f)}, 20.f, theme->GetColour(ThemeEntryID_TEXT_SELECTED), m_title.c_str(), NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); // gfx::drawText(vg, Vec2{m_pos.x + 15.f, m_pos.y + (m_pos.h / 2.f)}, 20.f, theme->GetColour(ThemeEntryID_TEXT_SELECTED), m_title.c_str(), NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
@@ -105,8 +149,8 @@ auto SidebarEntryCallback::Draw(NVGcontext* vg, Theme* theme) -> void {
// } // }
} }
SidebarEntryArray::SidebarEntryArray(std::string title, Items items, std::string& index) SidebarEntryArray::SidebarEntryArray(const std::string& title, const Items& items, std::string& index, const std::string& info)
: SidebarEntryArray{std::move(title), std::move(items), Callback{}, 0} { : SidebarEntryArray{title, items, Callback{}, 0, info} {
const auto it = std::find(m_items.cbegin(), m_items.cend(), index); const auto it = std::find(m_items.cbegin(), m_items.cend(), index);
if (it != m_items.cend()) { if (it != m_items.cend()) {
@@ -120,8 +164,8 @@ SidebarEntryArray::SidebarEntryArray(std::string title, Items items, std::string
}; };
} }
SidebarEntryArray::SidebarEntryArray(std::string title, Items items, Callback cb, std::string index) SidebarEntryArray::SidebarEntryArray(const std::string& title, const Items& items, Callback cb, const std::string& index, const std::string& info)
: SidebarEntryArray{std::move(title), std::move(items), cb, 0} { : SidebarEntryArray{title, items, cb, 0, info} {
const auto it = std::find(m_items.cbegin(), m_items.cend(), index); const auto it = std::find(m_items.cbegin(), m_items.cend(), index);
if (it != m_items.cend()) { if (it != m_items.cend()) {
@@ -129,9 +173,9 @@ SidebarEntryArray::SidebarEntryArray(std::string title, Items items, Callback cb
} }
} }
SidebarEntryArray::SidebarEntryArray(std::string title, Items items, Callback cb, s64 index) SidebarEntryArray::SidebarEntryArray(const std::string& title, const Items& items, Callback cb, s64 index, const std::string& info)
: SidebarEntryBase{std::forward<decltype(title)>(title)} : SidebarEntryBase{title, info}
, m_items{std::move(items)} , m_items{items}
, m_callback{cb} , m_callback{cb}
, m_index{index} { , m_index{index} {
@@ -153,8 +197,8 @@ SidebarEntryArray::SidebarEntryArray(std::string title, Items items, Callback cb
}); });
} }
auto SidebarEntryArray::Draw(NVGcontext* vg, Theme* theme) -> void { void SidebarEntryArray::Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left) {
SidebarEntryBase::Draw(vg, theme); SidebarEntryBase::Draw(vg, theme, root_pos, left);
const auto& text_entry = m_items[m_index]; const auto& text_entry = m_items[m_index];
@@ -212,17 +256,17 @@ auto SidebarEntryArray::OnFocusLost() noexcept -> void {
m_text_yoff = 0; m_text_yoff = 0;
} }
Sidebar::Sidebar(std::string title, Side side, Items&& items) Sidebar::Sidebar(const std::string& title, Side side, Items&& items)
: Sidebar{std::move(title), "", side, std::forward<decltype(items)>(items)} { : Sidebar{title, "", side, std::forward<decltype(items)>(items)} {
} }
Sidebar::Sidebar(std::string title, Side side) Sidebar::Sidebar(const std::string& title, Side side)
: Sidebar{std::move(title), "", side, {}} { : Sidebar{title, "", side, {}} {
} }
Sidebar::Sidebar(std::string title, std::string sub, Side side, Items&& items) Sidebar::Sidebar(const std::string& title, const std::string& sub, Side side, Items&& items)
: m_title{std::move(title)} : m_title{title}
, m_sub{std::move(sub)} , m_sub{sub}
, m_side{side} , m_side{side}
, m_items{std::forward<decltype(items)>(items)} { , m_items{std::forward<decltype(items)>(items)} {
switch (m_side) { switch (m_side) {
@@ -249,8 +293,8 @@ Sidebar::Sidebar(std::string title, std::string sub, Side side, Items&& items)
m_list->SetScrollBarPos(GetX() + GetW() - 20, m_base_pos.y - 10, pos.h - m_base_pos.y + 48); m_list->SetScrollBarPos(GetX() + GetW() - 20, m_base_pos.y - 10, pos.h - m_base_pos.y + 48);
} }
Sidebar::Sidebar(std::string title, std::string sub, Side side) Sidebar::Sidebar(const std::string& title, const std::string& sub, Side side)
: Sidebar{std::move(title), sub, side, {}} { : Sidebar{title, sub, side, {}} {
} }
@@ -275,6 +319,30 @@ auto Sidebar::Update(Controller* controller, TouchInfo* touch) -> void {
} }
auto Sidebar::Draw(NVGcontext* vg, Theme* theme) -> void { auto Sidebar::Draw(NVGcontext* vg, Theme* theme) -> void {
// Vec4 info_box{};
// info_box.y = m_top_bar.y;
// info_box.w = 300;
// info_box.h = 250;
// if (m_side == Side::LEFT) {
// info_box.x = m_pos.x + m_pos.w + 10;
// } else {
// info_box.x = m_pos.x - info_box.w - 10;
// }
// const float info_pad = 30;
// const float info_font_size = 18;
// const char* msg = "Skips verifying the nca header signature";
// float bounds[4];
// nvgFontSize(vg, info_font_size);
// nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
// nvgTextLineHeight(vg, 1.7);
// nvgTextBoxBounds(vg, info_box.x + info_pad, info_box.y + info_pad, info_box.w - info_pad * 2, msg, nullptr, bounds);
// info_box.h = info_pad * 2 + bounds[3] - bounds[1];
// gfx::drawRect(vg, info_box, theme->GetColour(ThemeEntryID_SIDEBAR), 5);
// gfx::drawTextBox(vg, bounds[0], bounds[1], info_font_size, info_box.w - info_pad * 2, theme->GetColour(ThemeEntryID_TEXT), msg);
gfx::drawRect(vg, m_pos, theme->GetColour(ThemeEntryID_SIDEBAR)); gfx::drawRect(vg, m_pos, theme->GetColour(ThemeEntryID_SIDEBAR));
gfx::drawText(vg, m_title_pos, m_title_size, theme->GetColour(ThemeEntryID_TEXT), m_title.c_str()); gfx::drawText(vg, m_title_pos, m_title_size, theme->GetColour(ThemeEntryID_TEXT), m_title.c_str());
if (!m_sub.empty()) { if (!m_sub.empty()) {
@@ -293,7 +361,7 @@ auto Sidebar::Draw(NVGcontext* vg, Theme* theme) -> void {
} }
m_items[i]->SetY(y); m_items[i]->SetY(y);
m_items[i]->Draw(vg, theme); m_items[i]->Draw(vg, theme, m_pos, m_side == Side::LEFT);
}); });
} }

View File

@@ -63,11 +63,15 @@ Result Hfs0GetPartition(source::Base* source, s64 off, Hfs0& out) {
Result Xci::GetCollections(Collections& out) { Result Xci::GetCollections(Collections& out) {
Hfs0 root{}; Hfs0 root{};
R_TRY(Hfs0GetPartition(m_source, HFS0_HEADER_OFFSET, root)); R_TRY(Hfs0GetPartition(m_source, HFS0_HEADER_OFFSET, root));
log_write("[XCI] got root partition\n");
for (u32 i = 0; i < root.header.total_files; i++) { for (u32 i = 0; i < root.header.total_files; i++) {
if (root.string_table[i] == "secure") { if (root.string_table[i] == "secure") {
log_write("[XCI] found secure partition\n");
Hfs0 secure{}; Hfs0 secure{};
R_TRY(Hfs0GetPartition(m_source, root.data_offset + root.file_table[i].data_offset, secure)); R_TRY(Hfs0GetPartition(m_source, root.data_offset + root.file_table[i].data_offset, secure));
log_write("[XCI] got secure partition\n");
for (u32 i = 0; i < secure.header.total_files; i++) { for (u32 i = 0; i < secure.header.total_files; i++) {
CollectionEntry entry; CollectionEntry entry;
@@ -78,10 +82,12 @@ Result Xci::GetCollections(Collections& out) {
} }
R_SUCCEED(); R_SUCCEED();
} else {
log_write("[XCI] skipping partition %u | %s\n", i, root.string_table[i].c_str());
} }
} }
return 0x1; return Result_XciSecurePartitionNotFound;
} }
} // namespace sphaira::yati::container } // namespace sphaira::yati::container

View File

@@ -152,68 +152,110 @@ Result VerifyFixedKey(const Header& header) {
} }
Result ParseCnmt(const fs::FsPath& path, u64 program_id, ncm::PackagedContentMeta& header, std::vector<u8>& extended_header, std::vector<NcmPackagedContentInfo>& infos) { Result ParseCnmt(const fs::FsPath& path, u64 program_id, ncm::PackagedContentMeta& header, std::vector<u8>& extended_header, std::vector<NcmPackagedContentInfo>& infos) {
FsFileSystem fs; fs::FsNativeId fs{program_id, FsFileSystemType_ContentMeta, path, FsContentAttributes_All};
R_TRY(fsOpenFileSystemWithId(std::addressof(fs), program_id, FsFileSystemType_ContentMeta, path, FsContentAttributes_All)); R_TRY(fs.GetFsOpenResult());
ON_SCOPE_EXIT(fsFsClose(std::addressof(fs)));
FsDir dir; fs::Dir dir;
R_TRY(fsFsOpenDirectory(std::addressof(fs), fs::FsPath{"/"}, FsDirOpenMode_ReadFiles, std::addressof(dir))); R_TRY(fs.OpenDirectory("/", FsDirOpenMode_ReadFiles, &dir));
ON_SCOPE_EXIT(fsDirClose(std::addressof(dir)));
s64 total_entries; s64 total_entries;
FsDirectoryEntry buf; FsDirectoryEntry buf;
R_TRY(fsDirRead(std::addressof(dir), std::addressof(total_entries), 1, std::addressof(buf))); R_TRY(dir.Read(std::addressof(total_entries), 1, std::addressof(buf)));
FsFile file; fs::File file;
R_TRY(fsFsOpenFile(std::addressof(fs), fs::AppendPath("/", buf.name), FsOpenMode_Read, std::addressof(file))); R_TRY(fs.OpenFile(fs::AppendPath("/", buf.name), FsOpenMode_Read, &file));
ON_SCOPE_EXIT(fsFileClose(std::addressof(file)));
s64 offset{}; s64 offset{};
u64 bytes_read; u64 bytes_read;
R_TRY(fsFileRead(std::addressof(file), offset, std::addressof(header), sizeof(header), 0, std::addressof(bytes_read))); R_TRY(file.Read(offset, std::addressof(header), sizeof(header), 0, std::addressof(bytes_read)));
offset += bytes_read; offset += bytes_read;
// read extended header // read extended header
extended_header.resize(header.meta_header.extended_header_size); extended_header.resize(header.meta_header.extended_header_size);
R_TRY(fsFileRead(std::addressof(file), offset, extended_header.data(), extended_header.size(), 0, std::addressof(bytes_read))); R_TRY(file.Read(offset, extended_header.data(), extended_header.size(), 0, std::addressof(bytes_read)));
offset += bytes_read; offset += bytes_read;
// read infos. // read infos.
infos.resize(header.meta_header.content_count); infos.resize(header.meta_header.content_count);
R_TRY(fsFileRead(std::addressof(file), offset, infos.data(), infos.size() * sizeof(NcmPackagedContentInfo), 0, std::addressof(bytes_read))); R_TRY(file.Read(offset, infos.data(), infos.size() * sizeof(NcmPackagedContentInfo), 0, std::addressof(bytes_read)));
offset += bytes_read; offset += bytes_read;
R_SUCCEED(); R_SUCCEED();
} }
Result ParseControl(const fs::FsPath& path, u64 program_id, void* nacp_out, s64 nacp_size, std::vector<u8>* icon_out, s64 nacp_off) { Result ParseControl(const fs::FsPath& path, u64 program_id, void* nacp_out, s64 nacp_size, std::vector<u8>* icon_out, s64 nacp_off) {
FsFileSystem fs; fs::FsNativeId fs{program_id, FsFileSystemType_ContentControl, path, FsContentAttributes_All};
R_TRY(fsOpenFileSystemWithId(std::addressof(fs), program_id, FsFileSystemType_ContentControl, path, FsContentAttributes_All)); R_TRY(fs.GetFsOpenResult());
ON_SCOPE_EXIT(fsFsClose(std::addressof(fs)));
// read nacp. // read nacp.
if (nacp_out) { if (nacp_out) {
FsFile file; fs::File file;
R_TRY(fsFsOpenFile(std::addressof(fs), fs::FsPath{"/control.nacp"}, FsOpenMode_Read, std::addressof(file))); R_TRY(fs.OpenFile("/control.nacp", FsOpenMode_Read, &file));
ON_SCOPE_EXIT(fsFileClose(std::addressof(file)));
u64 bytes_read; u64 bytes_read;
R_TRY(fsFileRead(&file, nacp_off, nacp_out, nacp_size, 0, &bytes_read)); R_TRY(file.Read(nacp_off, nacp_out, nacp_size, 0, &bytes_read));
} }
// read icon. // read icon.
if (icon_out) { if (icon_out) {
// todo: use matching icon based on the language version. static bool checked_lang = false;
FsFile file; static SetLanguage setLanguage = SetLanguage_ENUS;
R_TRY(fsFsOpenFile(std::addressof(fs), fs::FsPath{"/icon_AmericanEnglish.dat"}, FsOpenMode_Read, std::addressof(file)));
ON_SCOPE_EXIT(fsFileClose(std::addressof(file))); if (!checked_lang) {
checked_lang = true;
u64 languageCode;
if (R_SUCCEEDED(setGetSystemLanguage(&languageCode))) {
setMakeLanguage(languageCode, &setLanguage);
}
}
static const char* icon_names[] = {
[SetLanguage_JA] = "icon_Japanese.dat",
[SetLanguage_ENUS] = "icon_AmericanEnglish.dat",
[SetLanguage_FR] = "icon_French.dat",
[SetLanguage_DE] = "icon_German.dat",
[SetLanguage_IT] = "icon_Italian.dat",
[SetLanguage_ES] = "icon_Spanish.dat",
[SetLanguage_ZHCN] = "icon_Chinese.dat",
[SetLanguage_KO] = "icon_Korean.dat",
[SetLanguage_NL] = "icon_Dutch.dat",
[SetLanguage_PT] = "icon_Portuguese.dat",
[SetLanguage_RU] = "icon_Russian.dat",
[SetLanguage_ZHTW] = "icon_Taiwanese.dat",
[SetLanguage_ENGB] = "icon_BritishEnglish.dat",
[SetLanguage_FRCA] = "icon_CanadianFrench.dat",
[SetLanguage_ES419] = "icon_LatinAmericanSpanish.dat",
};
// load all icon entries and try and find the one that we want.
fs::Dir dir;
R_TRY(fs.OpenDirectory("/", FsDirOpenMode_ReadFiles, &dir));
std::vector<FsDirectoryEntry> entries;
R_TRY(dir.ReadAll(entries));
for (const auto& e : entries) {
if (!std::strcmp(e.name, icon_names[setLanguage])) {
fs::File file;
R_TRY(fs.OpenFile(fs::AppendPath("/", e.name), FsOpenMode_Read, &file));
icon_out->resize(e.file_size);
u64 bytes_read;
R_TRY(file.Read(0, icon_out->data(), icon_out->size(), 0, &bytes_read));
R_SUCCEED();
}
}
// otherwise, fallback to US icon.
fs::File file;
R_TRY(fs.OpenFile(fs::AppendPath("/", icon_names[SetLanguage_ENUS]), FsOpenMode_Read, &file));
s64 size; s64 size;
R_TRY(fsFileGetSize(std::addressof(file), std::addressof(size))); R_TRY(file.GetSize(&size));
icon_out->resize(size); icon_out->resize(size);
u64 bytes_read; u64 bytes_read;
R_TRY(fsFileRead(&file, 0, icon_out->data(), icon_out->size(), 0, &bytes_read)); R_TRY(file.Read(0, icon_out->data(), icon_out->size(), 0, &bytes_read));
} }
R_SUCCEED(); R_SUCCEED();

View File

@@ -20,6 +20,7 @@
#include <zstd.h> #include <zstd.h>
#include <minIni.h> #include <minIni.h>
#include <algorithm> #include <algorithm>
#include <atomic>
namespace sphaira::yati { namespace sphaira::yati {
namespace { namespace {
@@ -154,7 +155,7 @@ struct ThreadData {
max_buffer_size = std::max(read_buffer_size, INFLATE_BUFFER_MAX); max_buffer_size = std::max(read_buffer_size, INFLATE_BUFFER_MAX);
} }
auto GetResults() -> Result; auto GetResults() volatile -> Result;
void WakeAllThreads(); void WakeAllThreads();
Result Read(void* buf, s64 size, u64* bytes_read); Result Read(void* buf, s64 size, u64* bytes_read);
@@ -241,14 +242,14 @@ struct ThreadData {
u64 max_buffer_size{}; u64 max_buffer_size{};
// these are shared between threads // these are shared between threads
volatile s64 read_offset{}; std::atomic<s64> read_offset{};
volatile s64 decompress_offset{}; std::atomic<s64> decompress_offset{};
volatile s64 write_offset{}; std::atomic<s64> write_offset{};
volatile s64 write_size{}; std::atomic<s64> write_size{};
volatile Result read_result{}; std::atomic<Result> read_result{};
volatile Result decompress_result{}; std::atomic<Result> decompress_result{};
volatile Result write_result{}; std::atomic<Result> write_result{};
}; };
struct Yati { struct Yati {
@@ -290,11 +291,11 @@ struct Yati {
keys::Keys keys{}; keys::Keys keys{};
}; };
auto ThreadData::GetResults() -> Result { auto ThreadData::GetResults() volatile -> Result {
R_TRY(yati->pbox->ShouldExitResult()); R_TRY(yati->pbox->ShouldExitResult());
R_TRY(read_result); R_TRY(read_result.load());
R_TRY(decompress_result); R_TRY(decompress_result.load());
R_TRY(write_result); R_TRY(write_result.load());
R_SUCCEED(); R_SUCCEED();
} }
@@ -380,7 +381,7 @@ Result Yati::readFuncInternal(ThreadData* t) {
temp_buf.reserve(t->max_buffer_size); temp_buf.reserve(t->max_buffer_size);
while (t->read_offset < t->nca->size && R_SUCCEEDED(t->GetResults())) { while (t->read_offset < t->nca->size && R_SUCCEEDED(t->GetResults())) {
const auto buffer_offset = t->read_offset; const auto buffer_offset = t->read_offset.load();
// read more data // read more data
s64 read_size = t->read_buffer_size; s64 read_size = t->read_buffer_size;
@@ -434,7 +435,7 @@ Result Yati::readFuncInternal(ThreadData* t) {
R_TRY(t->Read(blocks.data(), blocks.size() * sizeof(ncz::Block), std::addressof(bytes_read))); R_TRY(t->Read(blocks.data(), blocks.size() * sizeof(ncz::Block), std::addressof(bytes_read)));
// calculate offsets for each block. // calculate offsets for each block.
auto block_offset = t->read_offset; auto block_offset = t->read_offset.load();
for (const auto& block : blocks) { for (const auto& block : blocks) {
t->ncz_blocks.emplace_back(block_offset, block.size); t->ncz_blocks.emplace_back(block_offset, block.size);
block_offset += block.size; block_offset += block.size;
@@ -564,7 +565,7 @@ Result Yati::decompressFuncInternal(ThreadData* t) {
} }
t->write_size = header.size; t->write_size = header.size;
log_write("setting placeholder size: %zu\n", t->write_size); log_write("setting placeholder size: %zu\n", t->write_size.load());
R_TRY(ncmContentStorageSetPlaceHolderSize(std::addressof(cs), std::addressof(t->nca->placeholder_id), t->write_size)); R_TRY(ncmContentStorageSetPlaceHolderSize(std::addressof(cs), std::addressof(t->nca->placeholder_id), t->write_size));
if (!config.ignore_distribution_bit && header.distribution_type == nca::DistributionType_GameCard) { if (!config.ignore_distribution_bit && header.distribution_type == nca::DistributionType_GameCard) {
@@ -1443,7 +1444,7 @@ Result InstallInternalStream(ui::ProgressBox* pbox, source::Base* source, contai
Result InstallFromFile(ui::ProgressBox* pbox, fs::Fs* fs, const fs::FsPath& path, const ConfigOverride& override) { Result InstallFromFile(ui::ProgressBox* pbox, fs::Fs* fs, const fs::FsPath& path, const ConfigOverride& override) {
auto source = std::make_unique<source::File>(fs, path); auto source = std::make_unique<source::File>(fs, path);
// auto source = std::make_unique<source::StreamFile>(fs, path); // enable for testing. // auto source = std::make_unique<source::StreamFile>(fs, path, override); // enable for testing.
return InstallFromSource(pbox, source.get(), path, override); return InstallFromSource(pbox, source.get(), path, override);
} }
@@ -1465,7 +1466,7 @@ Result InstallFromSource(ui::ProgressBox* pbox, source::Base* source, const fs::
Result InstallFromContainer(ui::ProgressBox* pbox, container::Base* container, const ConfigOverride& override) { Result InstallFromContainer(ui::ProgressBox* pbox, container::Base* container, const ConfigOverride& override) {
container::Collections collections; container::Collections collections;
R_TRY(container->GetCollections(collections)); R_TRY(container->GetCollections(collections));
return InstallFromCollections(pbox, container->GetSource(), collections); return InstallFromCollections(pbox, container->GetSource(), collections, override);
} }
Result InstallFromCollections(ui::ProgressBox* pbox, source::Base* source, const container::Collections& collections, const ConfigOverride& override) { Result InstallFromCollections(ui::ProgressBox* pbox, source::Base* source, const container::Collections& collections, const ConfigOverride& override) {