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:
@@ -576,6 +576,7 @@ enum class SphairaResult : Result {
|
||||
|
||||
NspBadMagic,
|
||||
XciBadMagic,
|
||||
XciSecurePartitionNotFound,
|
||||
|
||||
EsBadTitleKeyType,
|
||||
EsPersonalisedTicketDeviceIdMissmatch,
|
||||
@@ -710,6 +711,7 @@ enum : Result {
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UsbDsBadDeviceSpeed),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(NspBadMagic),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(XciBadMagic),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(XciSecurePartitionNotFound),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(EsBadTitleKeyType),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(EsPersonalisedTicketDeviceIdMissmatch),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(EsFailedDecryptPersonalisedTicket),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace sphaira::ui::menu::game {
|
||||
|
||||
struct Entry {
|
||||
u64 app_id{};
|
||||
u8 type{};
|
||||
NacpLanguageEntry lang{};
|
||||
int image{};
|
||||
bool selected{};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "ui/widget.hpp"
|
||||
#include "ui/list.hpp"
|
||||
#include "ui/scrolling_text.hpp"
|
||||
#include <memory>
|
||||
#include <concepts>
|
||||
#include <utility>
|
||||
@@ -10,11 +11,17 @@ namespace sphaira::ui {
|
||||
|
||||
class SidebarEntryBase : public Widget {
|
||||
public:
|
||||
SidebarEntryBase(std::string&& title);
|
||||
virtual auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
explicit SidebarEntryBase(const std::string& title, const std::string& info);
|
||||
|
||||
using Widget::Draw;
|
||||
virtual void Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left);
|
||||
|
||||
protected:
|
||||
std::string m_title;
|
||||
|
||||
private:
|
||||
std::string m_info;
|
||||
ScrollingText m_scolling_title{};
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
@@ -25,11 +32,11 @@ public:
|
||||
using Callback = std::function<void(bool&)>;
|
||||
|
||||
public:
|
||||
SidebarEntryBool(std::string title, bool option, Callback cb, std::string true_str = "On", 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, Callback cb, const std::string& info = "", const std::string& true_str = "On", const 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:
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
void Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left) override;
|
||||
|
||||
bool m_option;
|
||||
Callback m_callback;
|
||||
@@ -42,8 +49,9 @@ public:
|
||||
using Callback = std::function<void()>;
|
||||
|
||||
public:
|
||||
SidebarEntryCallback(std::string title, Callback cb, bool pop_on_click = false);
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
explicit SidebarEntryCallback(const std::string& title, Callback cb, const std::string& info);
|
||||
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:
|
||||
Callback m_callback;
|
||||
@@ -57,11 +65,11 @@ public:
|
||||
using Callback = std::function<void(s64& index)>;
|
||||
|
||||
public:
|
||||
explicit SidebarEntryArray(std::string title, Items items, Callback cb, s64 index = 0);
|
||||
SidebarEntryArray(std::string title, Items items, Callback cb, std::string index);
|
||||
SidebarEntryArray(std::string title, Items items, std::string& index);
|
||||
explicit SidebarEntryArray(const std::string& title, const Items& items, Callback cb, s64 index = 0, const std::string& info = "");
|
||||
explicit SidebarEntryArray(const std::string& title, const Items& items, Callback cb, const std::string& index, const std::string& info = "");
|
||||
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 OnFocusLost() noexcept -> void override;
|
||||
|
||||
@@ -74,39 +82,16 @@ private:
|
||||
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 {
|
||||
public:
|
||||
enum class Side { LEFT, RIGHT };
|
||||
using Items = std::vector<std::unique_ptr<SidebarEntryBase>>;
|
||||
|
||||
public:
|
||||
Sidebar(std::string title, Side side, Items&& items);
|
||||
Sidebar(std::string title, Side side);
|
||||
Sidebar(std::string title, std::string sub, Side side, Items&& items);
|
||||
Sidebar(std::string title, std::string sub, Side side);
|
||||
explicit Sidebar(const std::string& title, Side side, Items&& items);
|
||||
explicit Sidebar(const std::string& title, Side side);
|
||||
explicit Sidebar(const std::string& title, const std::string& sub, Side side, Items&& items);
|
||||
explicit Sidebar(const std::string& title, const std::string& sub, Side side);
|
||||
|
||||
auto Update(Controller* controller, TouchInfo* touch) -> void override;
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
|
||||
@@ -6,11 +6,13 @@
|
||||
namespace sphaira::ns {
|
||||
|
||||
enum ApplicationRecordType {
|
||||
// installed
|
||||
ApplicationRecordType_Running = 0x0,
|
||||
ApplicationRecordType_Installed = 0x3,
|
||||
ApplicationRecordType_Downloading = 0x4,
|
||||
// application is gamecard, but gamecard isn't insterted
|
||||
ApplicationRecordType_GamecardMissing = 0x5,
|
||||
// archived
|
||||
ApplicationRecordType_Downloaded = 0x6,
|
||||
ApplicationRecordType_Updated = 0xA,
|
||||
ApplicationRecordType_Archived = 0xB,
|
||||
};
|
||||
|
||||
|
||||
@@ -1598,7 +1598,7 @@ void App::DisplayThemeOptions(bool left_side) {
|
||||
|
||||
options->Add<ui::SidebarEntryArray>("Select Theme"_i18n, theme_items, [](s64& 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){
|
||||
App::SetThemeMusicEnable(enable);
|
||||
@@ -1703,15 +1703,17 @@ void App::DisplayAdvancedOptions(bool left_side) {
|
||||
|
||||
options->Add<ui::SidebarEntryBool>("Logging"_i18n, App::GetLogEnable(), [](bool& enable){
|
||||
App::SetLogEnable(enable);
|
||||
});
|
||||
}, "Logs to /config/sphaira/log.txt"_i18n);
|
||||
|
||||
options->Add<ui::SidebarEntryBool>("Replace hbmenu on exit"_i18n, App::GetReplaceHbmenuEnable(), [](bool& 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){
|
||||
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){
|
||||
App::SetTextScrollSpeed(index_out);
|
||||
@@ -1776,7 +1778,7 @@ void App::DisplayAdvancedOptions(bool left_side) {
|
||||
fs.DeleteFile(erpt_path);
|
||||
fs.CreateDirectory(erpt_path);
|
||||
}
|
||||
});
|
||||
}, "Disables error reports generated in /atmosphere/erpt_reports."_i18n);
|
||||
}
|
||||
|
||||
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){
|
||||
App::SetInstallSysmmcEnable(enable);
|
||||
});
|
||||
}, "Enables installing whilst in sysMMC mode."_i18n);
|
||||
|
||||
options->Add<ui::SidebarEntryBool>("Enable emummc"_i18n, App::GetInstallEmummcEnable(), [](bool& 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){
|
||||
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){
|
||||
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){
|
||||
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){
|
||||
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){
|
||||
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){
|
||||
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){
|
||||
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){
|
||||
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){
|
||||
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){
|
||||
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){
|
||||
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){
|
||||
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){
|
||||
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){
|
||||
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){
|
||||
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){
|
||||
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){
|
||||
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){
|
||||
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) {
|
||||
|
||||
@@ -31,7 +31,7 @@ const char* INI_PATH = "/config/ftpsrv/config.ini";
|
||||
constexpr int THREAD_PRIO = PRIO_PREEMPTIVE;
|
||||
constexpr int THREAD_CORE = 2;
|
||||
FtpSrvConfig g_ftpsrv_config = {0};
|
||||
volatile bool g_should_exit = false;
|
||||
std::atomic_bool g_should_exit = false;
|
||||
bool g_is_running{false};
|
||||
Thread g_thread;
|
||||
Mutex g_mutex{};
|
||||
|
||||
@@ -27,7 +27,7 @@ struct InstallSharedData {
|
||||
|
||||
constexpr int THREAD_PRIO = PRIO_PREEMPTIVE;
|
||||
constexpr int THREAD_CORE = 2;
|
||||
volatile bool g_should_exit = false;
|
||||
std::atomic_bool g_should_exit = false;
|
||||
bool g_is_running{false};
|
||||
Mutex g_mutex{};
|
||||
InstallSharedData g_shared_data{};
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <atomic>
|
||||
#include <minizip/unzip.h>
|
||||
#include <minizip/zip.h>
|
||||
|
||||
@@ -73,7 +74,7 @@ public:
|
||||
struct ThreadData {
|
||||
ThreadData(ui::ProgressBox* _pbox, s64 size, ReadCallback _rfunc, WriteCallback _wfunc, u64 buffer_size);
|
||||
|
||||
auto GetResults() -> Result;
|
||||
auto GetResults() volatile -> Result;
|
||||
void WakeAllThreads();
|
||||
|
||||
void SetReadResult(Result result) {
|
||||
@@ -88,7 +89,7 @@ struct ThreadData {
|
||||
pull_result = result;
|
||||
}
|
||||
|
||||
auto GetWriteOffset() const {
|
||||
auto GetWriteOffset() volatile const -> s64 {
|
||||
return write_offset;
|
||||
}
|
||||
|
||||
@@ -131,12 +132,12 @@ private:
|
||||
const s64 write_size;
|
||||
|
||||
// these are shared between threads
|
||||
volatile s64 read_offset{};
|
||||
volatile s64 write_offset{};
|
||||
std::atomic<s64> read_offset{};
|
||||
std::atomic<s64> write_offset{};
|
||||
|
||||
volatile Result read_result{};
|
||||
volatile Result write_result{};
|
||||
volatile Result pull_result{};
|
||||
std::atomic<Result> read_result{};
|
||||
std::atomic<Result> write_result{};
|
||||
std::atomic<Result> pull_result{};
|
||||
};
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
auto ThreadData::GetResults() -> Result {
|
||||
auto ThreadData::GetResults() volatile -> Result {
|
||||
R_UNLESS(!pbox->ShouldExit(), Result_TransferCancelled);
|
||||
R_TRY(read_result);
|
||||
R_TRY(write_result);
|
||||
R_TRY(pull_result);
|
||||
R_TRY(read_result.load());
|
||||
R_TRY(write_result.load());
|
||||
R_TRY(pull_result.load());
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
@@ -323,7 +324,9 @@ Result TransferInternal(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, Wri
|
||||
}
|
||||
|
||||
// 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.
|
||||
if (mode == Mode::SingleThreaded) {
|
||||
@@ -374,8 +377,7 @@ Result TransferInternal(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, Wri
|
||||
R_TRY(t_data.GetResults());
|
||||
return t_data.Pull(data, size, bytes_read);
|
||||
}));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
log_write("[THREAD] doing normal\n");
|
||||
R_TRY(start_threads());
|
||||
log_write("[THREAD] started threads\n");
|
||||
|
||||
@@ -100,6 +100,7 @@ auto GetCodeMessage(Result rc) -> const char* {
|
||||
case Result_UsbDsBadDeviceSpeed: return "SphairaError_UsbDsBadDeviceSpeed";
|
||||
case Result_NspBadMagic: return "SphairaError_NspBadMagic";
|
||||
case Result_XciBadMagic: return "SphairaError_XciBadMagic";
|
||||
case Result_XciSecurePartitionNotFound: return "SphairaError_XciSecurePartitionNotFound";
|
||||
case Result_EsBadTitleKeyType: return "SphairaError_EsBadTitleKeyType";
|
||||
case Result_EsPersonalisedTicketDeviceIdMissmatch: return "SphairaError_EsPersonalisedTicketDeviceIdMissmatch";
|
||||
case Result_EsFailedDecryptPersonalisedTicket: return "SphairaError_EsFailedDecryptPersonalisedTicket";
|
||||
|
||||
@@ -84,6 +84,10 @@ constexpr std::string_view ZIP_EXTENSIONS[] = {
|
||||
"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 {
|
||||
// uses the naming scheme from retropie.
|
||||
@@ -95,12 +99,12 @@ struct RomDatabaseEntry {
|
||||
|
||||
// compares against all of the above strings.
|
||||
auto IsDatabase(std::string_view name) const {
|
||||
if (name == folder || name == database) {
|
||||
if (IsSamePath(name, folder) || IsSamePath(name, database)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const auto& str : alias) {
|
||||
if (!str.empty() && name == str) {
|
||||
if (!str.empty() && IsSamePath(name, str)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -348,7 +352,7 @@ FsView::FsView(Menu* menu, const fs::FsPath& path, const FsEntry& entry, ViewSid
|
||||
Scan(GetNewPathCurrent());
|
||||
} else {
|
||||
// special case for nro
|
||||
if (IsSd() && entry.GetExtension() == "nro") {
|
||||
if (IsSd() && IsSamePath(entry.GetExtension(), "nro")) {
|
||||
App::Push<OptionBox>("Launch "_i18n + entry.GetName() + '?',
|
||||
"No"_i18n, "Launch"_i18n, 1, [this](auto op_index){
|
||||
if (op_index && *op_index) {
|
||||
@@ -598,7 +602,7 @@ void FsView::SetIndex(s64 index) {
|
||||
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;
|
||||
|
||||
if (auto zfile = unzOpen64(GetNewPathCurrent())) {
|
||||
@@ -623,7 +627,7 @@ void FsView::SetIndex(s64 index) {
|
||||
}
|
||||
|
||||
void FsView::InstallForwarder() {
|
||||
if (GetEntry().GetExtension() == "nro") {
|
||||
if (IsSamePath(GetEntry().GetExtension(), "nro")) {
|
||||
if (R_FAILED(homebrew::Menu::InstallHomebrewFromPath(GetNewPathCurrent()))) {
|
||||
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);
|
||||
|
||||
// check that all entries end in .nca
|
||||
const auto nca_ext = std::string_view{".nca"};
|
||||
for (auto& e : m_entries) {
|
||||
// check that we are at the bottom level
|
||||
R_UNLESS(e.type == FsDirEntryType_File, Result_FileBrowserDirNotDaybreak);
|
||||
|
||||
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();
|
||||
@@ -1680,7 +1683,7 @@ void FsView::DisplayOptions() {
|
||||
}
|
||||
|
||||
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](){;
|
||||
if (App::GetInstallPrompt()) {
|
||||
App::Push<OptionBox>(
|
||||
|
||||
@@ -208,7 +208,7 @@ void LoadResultIntoEntry(Entry& e, title::ThreadResultData* result) {
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -784,25 +784,12 @@ void Menu::ScanHomebrew() {
|
||||
|
||||
for (s32 i = 0; i < record_count; 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) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m_entries.emplace_back(e.application_id);
|
||||
m_entries.emplace_back(e.application_id, e.type);
|
||||
}
|
||||
|
||||
offset += record_count;
|
||||
|
||||
@@ -18,7 +18,7 @@ enum class InstallState {
|
||||
|
||||
constexpr u64 MAX_BUFFER_SIZE = 1024ULL*1024ULL*8ULL;
|
||||
constexpr u64 MAX_BUFFER_RESERVE_SIZE = 1024ULL*1024ULL*32ULL;
|
||||
volatile InstallState INSTALL_STATE{InstallState::None};
|
||||
std::atomic<InstallState> INSTALL_STATE{InstallState::None};
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -257,7 +257,7 @@ MainMenu::MainMenu() {
|
||||
|
||||
options->Add<SidebarEntryCallback>("Theme"_i18n, [](){
|
||||
App::DisplayThemeOptions();
|
||||
});
|
||||
}, "Customise the look of Sphaira by changing the theme"_i18n);
|
||||
|
||||
options->Add<SidebarEntryCallback>("Network"_i18n, [this](){
|
||||
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){
|
||||
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){
|
||||
App::SetLanguage(index_out);
|
||||
@@ -310,7 +311,7 @@ MainMenu::MainMenu() {
|
||||
|
||||
options->Add<SidebarEntryCallback>("Misc"_i18n, [](){
|
||||
App::DisplayMiscOptions();
|
||||
});
|
||||
}, "View and launch one of Sphaira's menus"_i18n);
|
||||
|
||||
options->Add<SidebarEntryCallback>("Advanced"_i18n, [](){
|
||||
App::DisplayAdvancedOptions();
|
||||
|
||||
@@ -240,7 +240,7 @@ void LoadResultIntoEntry(Entry& e, title::ThreadResultData* result) {
|
||||
}
|
||||
|
||||
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) {
|
||||
FakeNacpEntryForSystem(e);
|
||||
} else {
|
||||
|
||||
@@ -112,10 +112,19 @@ void Menu::Update(Controller* controller, TouchInfo* touch) {
|
||||
App::Push<ui::ProgressBox>(0, "Installing "_i18n, "", [this](auto pbox) -> Result {
|
||||
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");
|
||||
for (const auto& file_name : m_names) {
|
||||
pbox->SetTitle(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)) {
|
||||
m_usb_source->SignalCancel();
|
||||
log_write("exiting usb install\n");
|
||||
|
||||
@@ -25,24 +25,63 @@ auto DistanceBetweenY(Vec4 va, Vec4 vb) -> Vec4 {
|
||||
|
||||
} // namespace
|
||||
|
||||
SidebarEntryBase::SidebarEntryBase(std::string&& title)
|
||||
: m_title{std::forward<decltype(title)>(title)} {
|
||||
SidebarEntryBase::SidebarEntryBase(const std::string& title, const std::string& info)
|
||||
: 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)
|
||||
if (HasFocus()) {
|
||||
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)
|
||||
: SidebarEntryBase{std::move(title)}
|
||||
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{title, info}
|
||||
, m_option{option}
|
||||
, m_callback{cb}
|
||||
, m_true_str{std::move(true_str)}
|
||||
, m_false_str{std::move(false_str)} {
|
||||
, m_true_str{true_str}
|
||||
, m_false_str{false_str} {
|
||||
|
||||
if (m_true_str == "On") {
|
||||
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{std::move(title), option, Callback{} } {
|
||||
SidebarEntryBool::SidebarEntryBool(const std::string& title, bool& option, const std::string& info, const std::string& true_str, const std::string& false_str)
|
||||
: SidebarEntryBool{title, option, Callback{}, info, true_str, false_str} {
|
||||
m_callback = [](bool& option){
|
||||
option ^= 1;
|
||||
};
|
||||
}
|
||||
|
||||
auto SidebarEntryBool::Draw(NVGcontext* vg, Theme* theme) -> void {
|
||||
SidebarEntryBase::Draw(vg, theme);
|
||||
void SidebarEntryBool::Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left) {
|
||||
SidebarEntryBase::Draw(vg, theme, root_pos, left);
|
||||
|
||||
// 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);
|
||||
@@ -82,8 +121,8 @@ auto SidebarEntryBool::Draw(NVGcontext* vg, Theme* theme) -> void {
|
||||
}
|
||||
}
|
||||
|
||||
SidebarEntryCallback::SidebarEntryCallback(std::string title, Callback cb, bool pop_on_click)
|
||||
: SidebarEntryBase{std::move(title)}
|
||||
SidebarEntryCallback::SidebarEntryCallback(const std::string& title, Callback cb, bool pop_on_click, const std::string& info)
|
||||
: SidebarEntryBase{title, info}
|
||||
, m_callback{cb}
|
||||
, m_pop_on_click{pop_on_click} {
|
||||
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 {
|
||||
SidebarEntryBase::Draw(vg, theme);
|
||||
SidebarEntryCallback::SidebarEntryCallback(const std::string& title, Callback cb, const std::string& info)
|
||||
: 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()) {
|
||||
// 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{std::move(title), std::move(items), Callback{}, 0} {
|
||||
SidebarEntryArray::SidebarEntryArray(const std::string& title, const Items& items, std::string& index, const std::string& info)
|
||||
: SidebarEntryArray{title, items, Callback{}, 0, info} {
|
||||
|
||||
const auto it = std::find(m_items.cbegin(), m_items.cend(), index);
|
||||
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{std::move(title), std::move(items), cb, 0} {
|
||||
SidebarEntryArray::SidebarEntryArray(const std::string& title, const Items& items, Callback cb, const std::string& index, const std::string& info)
|
||||
: SidebarEntryArray{title, items, cb, 0, info} {
|
||||
|
||||
const auto it = std::find(m_items.cbegin(), m_items.cend(), index);
|
||||
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)
|
||||
: SidebarEntryBase{std::forward<decltype(title)>(title)}
|
||||
, m_items{std::move(items)}
|
||||
SidebarEntryArray::SidebarEntryArray(const std::string& title, const Items& items, Callback cb, s64 index, const std::string& info)
|
||||
: SidebarEntryBase{title, info}
|
||||
, m_items{items}
|
||||
, m_callback{cb}
|
||||
, m_index{index} {
|
||||
|
||||
@@ -153,8 +197,8 @@ SidebarEntryArray::SidebarEntryArray(std::string title, Items items, Callback cb
|
||||
});
|
||||
}
|
||||
|
||||
auto SidebarEntryArray::Draw(NVGcontext* vg, Theme* theme) -> void {
|
||||
SidebarEntryBase::Draw(vg, theme);
|
||||
void SidebarEntryArray::Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left) {
|
||||
SidebarEntryBase::Draw(vg, theme, root_pos, left);
|
||||
|
||||
const auto& text_entry = m_items[m_index];
|
||||
|
||||
@@ -212,17 +256,17 @@ auto SidebarEntryArray::OnFocusLost() noexcept -> void {
|
||||
m_text_yoff = 0;
|
||||
}
|
||||
|
||||
Sidebar::Sidebar(std::string title, Side side, Items&& items)
|
||||
: Sidebar{std::move(title), "", side, std::forward<decltype(items)>(items)} {
|
||||
Sidebar::Sidebar(const std::string& title, Side side, Items&& items)
|
||||
: Sidebar{title, "", side, std::forward<decltype(items)>(items)} {
|
||||
}
|
||||
|
||||
Sidebar::Sidebar(std::string title, Side side)
|
||||
: Sidebar{std::move(title), "", side, {}} {
|
||||
Sidebar::Sidebar(const std::string& title, Side side)
|
||||
: Sidebar{title, "", side, {}} {
|
||||
}
|
||||
|
||||
Sidebar::Sidebar(std::string title, std::string sub, Side side, Items&& items)
|
||||
: m_title{std::move(title)}
|
||||
, m_sub{std::move(sub)}
|
||||
Sidebar::Sidebar(const std::string& title, const std::string& sub, Side side, Items&& items)
|
||||
: m_title{title}
|
||||
, m_sub{sub}
|
||||
, m_side{side}
|
||||
, m_items{std::forward<decltype(items)>(items)} {
|
||||
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);
|
||||
}
|
||||
|
||||
Sidebar::Sidebar(std::string title, std::string sub, Side side)
|
||||
: Sidebar{std::move(title), sub, side, {}} {
|
||||
Sidebar::Sidebar(const std::string& title, const std::string& sub, Side 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 {
|
||||
// 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::drawText(vg, m_title_pos, m_title_size, theme->GetColour(ThemeEntryID_TEXT), m_title.c_str());
|
||||
if (!m_sub.empty()) {
|
||||
@@ -293,7 +361,7 @@ auto Sidebar::Draw(NVGcontext* vg, Theme* theme) -> void {
|
||||
}
|
||||
|
||||
m_items[i]->SetY(y);
|
||||
m_items[i]->Draw(vg, theme);
|
||||
m_items[i]->Draw(vg, theme, m_pos, m_side == Side::LEFT);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -63,11 +63,15 @@ Result Hfs0GetPartition(source::Base* source, s64 off, Hfs0& out) {
|
||||
Result Xci::GetCollections(Collections& out) {
|
||||
Hfs0 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++) {
|
||||
if (root.string_table[i] == "secure") {
|
||||
log_write("[XCI] found secure partition\n");
|
||||
|
||||
Hfs0 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++) {
|
||||
CollectionEntry entry;
|
||||
@@ -78,10 +82,12 @@ Result Xci::GetCollections(Collections& out) {
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -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) {
|
||||
FsFileSystem fs;
|
||||
R_TRY(fsOpenFileSystemWithId(std::addressof(fs), program_id, FsFileSystemType_ContentMeta, path, FsContentAttributes_All));
|
||||
ON_SCOPE_EXIT(fsFsClose(std::addressof(fs)));
|
||||
fs::FsNativeId fs{program_id, FsFileSystemType_ContentMeta, path, FsContentAttributes_All};
|
||||
R_TRY(fs.GetFsOpenResult());
|
||||
|
||||
FsDir dir;
|
||||
R_TRY(fsFsOpenDirectory(std::addressof(fs), fs::FsPath{"/"}, FsDirOpenMode_ReadFiles, std::addressof(dir)));
|
||||
ON_SCOPE_EXIT(fsDirClose(std::addressof(dir)));
|
||||
fs::Dir dir;
|
||||
R_TRY(fs.OpenDirectory("/", FsDirOpenMode_ReadFiles, &dir));
|
||||
|
||||
s64 total_entries;
|
||||
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;
|
||||
R_TRY(fsFsOpenFile(std::addressof(fs), fs::AppendPath("/", buf.name), FsOpenMode_Read, std::addressof(file)));
|
||||
ON_SCOPE_EXIT(fsFileClose(std::addressof(file)));
|
||||
fs::File file;
|
||||
R_TRY(fs.OpenFile(fs::AppendPath("/", buf.name), FsOpenMode_Read, &file));
|
||||
|
||||
s64 offset{};
|
||||
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;
|
||||
|
||||
// read extended header
|
||||
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;
|
||||
|
||||
// read infos.
|
||||
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;
|
||||
|
||||
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) {
|
||||
FsFileSystem fs;
|
||||
R_TRY(fsOpenFileSystemWithId(std::addressof(fs), program_id, FsFileSystemType_ContentControl, path, FsContentAttributes_All));
|
||||
ON_SCOPE_EXIT(fsFsClose(std::addressof(fs)));
|
||||
fs::FsNativeId fs{program_id, FsFileSystemType_ContentControl, path, FsContentAttributes_All};
|
||||
R_TRY(fs.GetFsOpenResult());
|
||||
|
||||
// read nacp.
|
||||
if (nacp_out) {
|
||||
FsFile file;
|
||||
R_TRY(fsFsOpenFile(std::addressof(fs), fs::FsPath{"/control.nacp"}, FsOpenMode_Read, std::addressof(file)));
|
||||
ON_SCOPE_EXIT(fsFileClose(std::addressof(file)));
|
||||
fs::File file;
|
||||
R_TRY(fs.OpenFile("/control.nacp", FsOpenMode_Read, &file));
|
||||
|
||||
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.
|
||||
if (icon_out) {
|
||||
// todo: use matching icon based on the language version.
|
||||
FsFile file;
|
||||
R_TRY(fsFsOpenFile(std::addressof(fs), fs::FsPath{"/icon_AmericanEnglish.dat"}, FsOpenMode_Read, std::addressof(file)));
|
||||
ON_SCOPE_EXIT(fsFileClose(std::addressof(file)));
|
||||
static bool checked_lang = false;
|
||||
static SetLanguage setLanguage = SetLanguage_ENUS;
|
||||
|
||||
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;
|
||||
R_TRY(fsFileGetSize(std::addressof(file), std::addressof(size)));
|
||||
R_TRY(file.GetSize(&size));
|
||||
icon_out->resize(size);
|
||||
|
||||
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();
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <zstd.h>
|
||||
#include <minIni.h>
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
|
||||
namespace sphaira::yati {
|
||||
namespace {
|
||||
@@ -154,7 +155,7 @@ struct ThreadData {
|
||||
max_buffer_size = std::max(read_buffer_size, INFLATE_BUFFER_MAX);
|
||||
}
|
||||
|
||||
auto GetResults() -> Result;
|
||||
auto GetResults() volatile -> Result;
|
||||
void WakeAllThreads();
|
||||
|
||||
Result Read(void* buf, s64 size, u64* bytes_read);
|
||||
@@ -241,14 +242,14 @@ struct ThreadData {
|
||||
u64 max_buffer_size{};
|
||||
|
||||
// these are shared between threads
|
||||
volatile s64 read_offset{};
|
||||
volatile s64 decompress_offset{};
|
||||
volatile s64 write_offset{};
|
||||
volatile s64 write_size{};
|
||||
std::atomic<s64> read_offset{};
|
||||
std::atomic<s64> decompress_offset{};
|
||||
std::atomic<s64> write_offset{};
|
||||
std::atomic<s64> write_size{};
|
||||
|
||||
volatile Result read_result{};
|
||||
volatile Result decompress_result{};
|
||||
volatile Result write_result{};
|
||||
std::atomic<Result> read_result{};
|
||||
std::atomic<Result> decompress_result{};
|
||||
std::atomic<Result> write_result{};
|
||||
};
|
||||
|
||||
struct Yati {
|
||||
@@ -290,11 +291,11 @@ struct Yati {
|
||||
keys::Keys keys{};
|
||||
};
|
||||
|
||||
auto ThreadData::GetResults() -> Result {
|
||||
auto ThreadData::GetResults() volatile -> Result {
|
||||
R_TRY(yati->pbox->ShouldExitResult());
|
||||
R_TRY(read_result);
|
||||
R_TRY(decompress_result);
|
||||
R_TRY(write_result);
|
||||
R_TRY(read_result.load());
|
||||
R_TRY(decompress_result.load());
|
||||
R_TRY(write_result.load());
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
@@ -380,7 +381,7 @@ Result Yati::readFuncInternal(ThreadData* t) {
|
||||
temp_buf.reserve(t->max_buffer_size);
|
||||
|
||||
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
|
||||
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)));
|
||||
|
||||
// calculate offsets for each block.
|
||||
auto block_offset = t->read_offset;
|
||||
auto block_offset = t->read_offset.load();
|
||||
for (const auto& block : blocks) {
|
||||
t->ncz_blocks.emplace_back(block_offset, block.size);
|
||||
block_offset += block.size;
|
||||
@@ -564,7 +565,7 @@ Result Yati::decompressFuncInternal(ThreadData* t) {
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
container::Collections 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) {
|
||||
|
||||
Reference in New Issue
Block a user