huge changes to everything (see below).
Changelog: - re-enable use in release build. - remove ftpsrv and untitled from builtin ghdl options, as both packages are available in the appstore. - add image viewer (png, jpg, bmp) - add music player (bfstm, bfwav, mp3, wav, ogg) - add idv3 tag parsing support for mp3. - add "decyption" of GTA Vice City mp3. - add usbdvd support for music playback and file browsing. - add nsz export support (solid, block, ldm). - add xcz export support (same as above). - add nro fs proper mount support (romfs, nacp, icon). - add program nca fs support. - add bfsar fs support. - re-write the usb protocol, still wip. replaces tinfoil protocol. - all threads are now create with pre-emptive support with the proper affinity mask set. - fix oob crash in libpulsar when a bfwav was opened that had more than 2 channels. - bump yyjson version. - bump usbhsfs version. - disable nvjpg. - add support for theme music of any supported playback type (bfstm, bfwav, mp3, wav, ogg). - add support for setting background music. - add async exit to blocking threads (download, nxlink, ftpsrv) to reduce exit time. - add support for dumping to pc via usb. - add null, deflate, zstd hash options, mainly used for benchmarking. - add sidebar slider (currently unused). - file_viwer can now be used with any filesystem. - filebrowser will only ever stat file once. previously it would keep stat'ing until it succeeded. - disabled themezer due to the api breaking and i am not willing to keep maintaining it. - disable zlt handling in usbds as it's not needed for my api's because the size is always known. - remove usbds enums and GetSpeed() as i pr'd it to libnx. - added support for mounting nca's from any source, including files, memory, nsps, xcis etc. - split the lru cache into it's own header as it's now used in multiple places (nsz, all mounted options). - add support for fetching and decrypting es personalised tickets. - fix es common ticket converting where i forgot to also convert the cert chain as well. - remove the download default music option. - improve performance of libpulsar when opening a bfsar by remove the large setvbuf option. instead, use the default 1k buffer and handle large buffers manually in sphaira by using a lru cache (todo: just write my own bfsar parser). - during app init and exit, load times have been halved as i now load/exit async. timestamps have also been added to measure how long everything takes. - download now async loads / exits the etag json file to improve init times. - add custom zip io to dumper to support writing a zip to any dest (such as usb). - dumper now returns a proper error if the transfer was cancelled by the user. - fatfs mount now sets the timestamp for files. - fatfs mount handles folders with the archive bit by reporting them as a file. - ftpsrv config is async loaded to speed up load times. - nxlink now tries attempt to connect/accept by handling blocking rather than just bailing out. - added support for minini floats. - thread_file_transfer now spawns 3 threads rather than 2, to have the middle thread be a optional processor (mainly used for compressing/decompressing). - added spinner to progress box, taken from nvg demo. - progress box disables sleep mode on init. - add gamecard detection to game menu to detect a refresh. - handle xci that have the key area prepended. - change gamecard mount fs to use the xci mount code instead of native fs, that way we can see all the partitions rather than just secure. - reformat the ghdl entries to show the timestamp first. - support for exporting saves to pc via usb. - zip fs now uses lru cache.
This commit is contained in:
@@ -2,13 +2,13 @@
|
||||
|
||||
#include "nanovg.h"
|
||||
#include "nanovg/dk_renderer.hpp"
|
||||
#include "pulsar.h"
|
||||
#include "ui/widget.hpp"
|
||||
#include "ui/notification.hpp"
|
||||
#include "owo.hpp"
|
||||
#include "option.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "log.hpp"
|
||||
#include "utils/audio.hpp"
|
||||
|
||||
#ifdef USE_NVJPG
|
||||
#include <nvjpg.hpp>
|
||||
@@ -22,16 +22,7 @@
|
||||
|
||||
namespace sphaira {
|
||||
|
||||
enum SoundEffect {
|
||||
SoundEffect_Music,
|
||||
SoundEffect_Focus,
|
||||
SoundEffect_Scroll,
|
||||
SoundEffect_Limit,
|
||||
SoundEffect_Startup,
|
||||
SoundEffect_Install,
|
||||
SoundEffect_Error,
|
||||
SoundEffect_MAX,
|
||||
};
|
||||
using SoundEffect = audio::SoundEffect;
|
||||
|
||||
enum class LaunchType {
|
||||
Normal,
|
||||
@@ -108,6 +99,10 @@ public:
|
||||
static auto GetLanguage() -> long;
|
||||
static auto GetTextScrollSpeed() -> long;
|
||||
|
||||
static auto GetNszCompressLevel() -> u8;
|
||||
static auto GetNszThreadCount() -> u8;
|
||||
static auto GetNszBlockExponent() -> u8;
|
||||
|
||||
static void SetMtpEnable(bool enable);
|
||||
static void SetFtpEnable(bool enable);
|
||||
static void SetNxlinkEnable(bool enable);
|
||||
@@ -153,8 +148,12 @@ public:
|
||||
|
||||
void LoadTheme(const ThemeMeta& meta);
|
||||
void CloseTheme();
|
||||
void CloseThemeBackgroundMusic();
|
||||
void ScanThemes(const std::string& path);
|
||||
void ScanThemeEntries();
|
||||
void LoadAndPlayThemeMusic();
|
||||
static Result SetDefaultBackgroundMusic(fs::Fs* fs, const fs::FsPath& path);
|
||||
static void SetBackgroundMusicPause(bool pause);
|
||||
|
||||
// helper that converts 1.2.3 to a u32 used for comparisons.
|
||||
static auto GetVersionFromString(const char* str) -> u32;
|
||||
@@ -294,6 +293,7 @@ public:
|
||||
|
||||
option::OptionBool m_log_enabled{INI_SECTION, "log_enabled", false};
|
||||
option::OptionBool m_replace_hbmenu{INI_SECTION, "replace_hbmenu", false};
|
||||
option::OptionString m_default_music{INI_SECTION, "default_music", "/config/sphaira/themes/default_music.bfstm"};
|
||||
option::OptionString m_theme_path{INI_SECTION, "theme", DEFAULT_THEME_PATH};
|
||||
option::OptionBool m_theme_music{INI_SECTION, "theme_music", true};
|
||||
option::OptionBool m_12hour_time{INI_SECTION, "12hour_time", false};
|
||||
@@ -328,13 +328,18 @@ public:
|
||||
option::OptionBool m_dump_append_folder_with_xci{"dump", "append_folder_with_xci", true};
|
||||
option::OptionBool m_dump_trim_xci{"dump", "trim_xci", false};
|
||||
option::OptionBool m_dump_label_trim_xci{"dump", "label_trim_xci", false};
|
||||
option::OptionBool m_dump_usb_transfer_stream{"dump", "usb_transfer_stream", true, false};
|
||||
option::OptionBool m_dump_convert_to_common_ticket{"dump", "convert_to_common_ticket", true};
|
||||
option::OptionLong m_nsz_compress_level{"dump", "nsz_compress_level", 3};
|
||||
option::OptionLong m_nsz_compress_threads{"dump", "nsz_compress_threads", 3};
|
||||
option::OptionBool m_nsz_compress_ldm{"dump", "nsz_compress_ldm", true};
|
||||
option::OptionBool m_nsz_compress_block{"dump", "nsz_compress_block", false};
|
||||
option::OptionLong m_nsz_compress_block_exponent{"dump", "nsz_compress_block_exponent", 6};
|
||||
|
||||
// todo: move this into it's own menu
|
||||
option::OptionLong m_text_scroll_speed{"accessibility", "text_scroll_speed", 1}; // normal
|
||||
|
||||
PLSR_PlayerSoundId m_sound_ids[SoundEffect_MAX]{};
|
||||
std::shared_ptr<fs::FsNativeSd> m_fs{};
|
||||
audio::SongID m_background_music{};
|
||||
|
||||
#ifdef USE_NVJPG
|
||||
nj::Decoder m_decoder;
|
||||
|
||||
@@ -577,6 +577,7 @@ enum class SphairaResult : Result {
|
||||
|
||||
UsbDsBadDeviceSpeed,
|
||||
|
||||
NcaBadMagic,
|
||||
NspBadMagic,
|
||||
XciBadMagic,
|
||||
XciSecurePartitionNotFound,
|
||||
@@ -648,6 +649,17 @@ enum class SphairaResult : Result {
|
||||
YatiNcmDbCorruptHeader,
|
||||
// unable to total infos from ncm database.
|
||||
YatiNcmDbCorruptInfos,
|
||||
|
||||
NszFailedCreateCctx,
|
||||
NszFailedSetCompressionLevel,
|
||||
NszFailedSetThreadCount,
|
||||
NszFailedSetLongDistanceMode,
|
||||
NszFailedResetCctx,
|
||||
NszFailedCompress2,
|
||||
NszFailedCompressStream2,
|
||||
NszTooManyBlocks,
|
||||
// set when nca finished but not all blocks were handled.
|
||||
NszMissingBlocks,
|
||||
};
|
||||
|
||||
#define MAKE_SPHAIRA_RESULT_ENUM(x) Result_##x = MAKERESULT(Module_Sphaira, (Result)SphairaResult::x)
|
||||
@@ -717,8 +729,11 @@ enum : Result {
|
||||
MAKE_SPHAIRA_RESULT_ENUM(ThemezerFailedToDownloadTheme),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(MainFailedToDownloadUpdate),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UsbDsBadDeviceSpeed),
|
||||
|
||||
MAKE_SPHAIRA_RESULT_ENUM(NspBadMagic),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(XciBadMagic),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(NcaBadMagic),
|
||||
|
||||
MAKE_SPHAIRA_RESULT_ENUM(XciSecurePartitionNotFound),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(EsBadTitleKeyType),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(EsPersonalisedTicketDeviceIdMissmatch),
|
||||
@@ -729,7 +744,9 @@ enum : Result {
|
||||
MAKE_SPHAIRA_RESULT_ENUM(EsInvalidTicketFromatVersion),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(EsInvalidTicketKeyType),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(EsInvalidTicketKeyRevision),
|
||||
|
||||
MAKE_SPHAIRA_RESULT_ENUM(OwoBadArgs),
|
||||
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UsbCancelled),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UsbBadMagic),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UsbBadVersion),
|
||||
@@ -744,6 +761,7 @@ enum : Result {
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UsbUploadBadTransferSize),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UsbUploadBadTotalSize),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UsbUploadBadCommand),
|
||||
|
||||
MAKE_SPHAIRA_RESULT_ENUM(YatiContainerNotFound),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(YatiNcaNotFound),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(YatiInvalidNcaReadSize),
|
||||
@@ -765,6 +783,16 @@ enum : Result {
|
||||
MAKE_SPHAIRA_RESULT_ENUM(YatiCertNotFound),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(YatiNcmDbCorruptHeader),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(YatiNcmDbCorruptInfos),
|
||||
|
||||
MAKE_SPHAIRA_RESULT_ENUM(NszFailedCreateCctx),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(NszFailedSetCompressionLevel),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(NszFailedSetThreadCount),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(NszFailedSetLongDistanceMode),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(NszFailedResetCctx),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(NszFailedCompress2),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(NszFailedCompressStream2),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(NszTooManyBlocks),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(NszMissingBlocks),
|
||||
};
|
||||
|
||||
#undef MAKE_SPHAIRA_RESULT_ENUM
|
||||
@@ -799,16 +827,6 @@ enum : Result {
|
||||
// #define ON_SCOPE_FAIL(_f) std::experimental::scope_exit ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){[&] { if (R_FAILED(rc)) { _f; } }};
|
||||
// #define ON_SCOPE_SUCCESS(_f) std::experimental::scope_exit ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){[&] { if (R_SUCCEEDED(rc)) { _f; } }};
|
||||
|
||||
// threading helpers.
|
||||
#define PRIO_PREEMPTIVE 0x3B
|
||||
|
||||
// threading affinity, use with svcSetThreadCoreMask().
|
||||
#define THREAD_AFFINITY_CORE0 BIT(0)
|
||||
#define THREAD_AFFINITY_CORE1 BIT(1)
|
||||
#define THREAD_AFFINITY_CORE2 BIT(2)
|
||||
#define THREAD_AFFINITY_DEFAULT(core) (BIT(core)|THREAD_AFFINITY_CORE1|THREAD_AFFINITY_CORE2)
|
||||
#define THREAD_AFFINITY_ALL (THREAD_AFFINITY_CORE0|THREAD_AFFINITY_CORE1|THREAD_AFFINITY_CORE2)
|
||||
|
||||
// mutex helpers.
|
||||
#define SCOPED_MUTEX(mutex) \
|
||||
mutexLock(mutex); \
|
||||
|
||||
@@ -142,6 +142,7 @@ struct DownloadEventData {
|
||||
|
||||
auto Init() -> bool;
|
||||
void Exit();
|
||||
void ExitSignal();
|
||||
|
||||
// sync functions
|
||||
auto ToMemory(const Api& e) -> ApiResult;
|
||||
|
||||
@@ -2,16 +2,21 @@
|
||||
|
||||
#include "fs.hpp"
|
||||
#include "location.hpp"
|
||||
#include "ui/progress_box.hpp"
|
||||
|
||||
#include <switch.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <minizip/ioapi.h>
|
||||
|
||||
namespace sphaira::dump {
|
||||
|
||||
enum DumpLocationType {
|
||||
// dump using native fs.
|
||||
DumpLocationType_SdCard,
|
||||
// dump to usb pc.
|
||||
DumpLocationType_Usb,
|
||||
// dump to usb using tinfoil protocol.
|
||||
DumpLocationType_UsbS2S,
|
||||
// speed test, only reads the data, doesn't write anything.
|
||||
@@ -24,11 +29,12 @@ enum DumpLocationType {
|
||||
|
||||
enum DumpLocationFlag {
|
||||
DumpLocationFlag_SdCard = 1 << DumpLocationType_SdCard,
|
||||
DumpLocationFlag_Usb = 1 << DumpLocationType_Usb,
|
||||
DumpLocationFlag_UsbS2S = 1 << DumpLocationType_UsbS2S,
|
||||
DumpLocationFlag_DevNull = 1 << DumpLocationType_DevNull,
|
||||
DumpLocationFlag_Stdio = 1 << DumpLocationType_Stdio,
|
||||
DumpLocationFlag_Network = 1 << DumpLocationType_Network,
|
||||
DumpLocationFlag_All = DumpLocationFlag_SdCard | DumpLocationFlag_UsbS2S | DumpLocationFlag_DevNull | DumpLocationFlag_Stdio | DumpLocationFlag_Network,
|
||||
DumpLocationFlag_All = DumpLocationFlag_SdCard | DumpLocationFlag_Usb | DumpLocationFlag_UsbS2S | DumpLocationFlag_DevNull | DumpLocationFlag_Stdio | DumpLocationFlag_Network,
|
||||
};
|
||||
|
||||
struct DumpEntry {
|
||||
@@ -48,17 +54,36 @@ struct BaseSource {
|
||||
virtual auto GetName(const std::string& path) const -> std::string = 0;
|
||||
virtual auto GetSize(const std::string& path) const -> s64 = 0;
|
||||
virtual auto GetIcon(const std::string& path) const -> int { return 0; }
|
||||
|
||||
Result Read(const std::string& path, void* buf, s64 off, s64 size) {
|
||||
u64 bytes_read;
|
||||
return Read(path, buf, off, size, &bytes_read);
|
||||
}
|
||||
};
|
||||
|
||||
struct WriteSource {
|
||||
virtual ~WriteSource() = default;
|
||||
virtual Result Write(const void* buf, s64 off, s64 size) = 0;
|
||||
virtual Result SetSize(s64 size) = 0;
|
||||
};
|
||||
|
||||
// called after dump has finished.
|
||||
using OnExit = std::function<void(Result rc)>;
|
||||
using OnLocation = std::function<void(const DumpLocation& loc)>;
|
||||
|
||||
using CustomTransfer = std::function<Result(ui::ProgressBox* pbox, BaseSource* source, WriteSource* writer, const fs::FsPath& path)>;
|
||||
|
||||
// prompts the user to select dump location, calls on_loc on success with the selected location.
|
||||
void DumpGetLocation(const std::string& title, u32 location_flags, const OnLocation& on_loc);
|
||||
void DumpGetLocation(const std::string& title, u32 location_flags, const OnLocation& on_loc, const CustomTransfer& custom_transfer = nullptr);
|
||||
|
||||
Result Dump(ui::ProgressBox* pbox, const std::shared_ptr<BaseSource>& source, const DumpLocation& location, const std::vector<fs::FsPath>& paths, const CustomTransfer& custom_transfer = nullptr);
|
||||
|
||||
// dumps to a fetched location using DumpGetLocation().
|
||||
void Dump(const std::shared_ptr<BaseSource>& source, const DumpLocation& location, const std::vector<fs::FsPath>& paths, const OnExit& on_exit);
|
||||
void Dump(const std::shared_ptr<BaseSource>& source, const DumpLocation& location, const std::vector<fs::FsPath>& paths, const OnExit& on_exit, const CustomTransfer& custom_transfer = nullptr);
|
||||
// DumpGetLocation() + Dump() all in one.
|
||||
void Dump(const std::shared_ptr<BaseSource>& source, const std::vector<fs::FsPath>& paths, const OnExit& on_exit = [](Result){}, u32 location_flags = DumpLocationFlag_All);
|
||||
void Dump(const std::shared_ptr<BaseSource>& source, const std::vector<fs::FsPath>& paths, const OnExit& on_exit = nullptr, u32 location_flags = DumpLocationFlag_All);
|
||||
void Dump(const std::shared_ptr<BaseSource>& source, const std::vector<fs::FsPath>& paths, const CustomTransfer& custom_transfer, const OnExit& on_exit = nullptr, u32 location_flags = DumpLocationFlag_All);
|
||||
|
||||
void FileFuncWriter(WriteSource* writer, zlib_filefunc64_def* funcs);
|
||||
|
||||
} // namespace sphaira::dump
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace sphaira::fatfs {
|
||||
|
||||
|
||||
@@ -322,6 +322,7 @@ struct Fs {
|
||||
virtual bool FileExists(const FsPath& path) = 0;
|
||||
virtual bool DirExists(const FsPath& path) = 0;
|
||||
virtual bool IsNative() const = 0;
|
||||
virtual bool IsSd() const { return false; }
|
||||
virtual FsPath Root() const { return "/"; }
|
||||
|
||||
Result OpenFile(const fs::FsPath& path, u32 mode, File* f) {
|
||||
@@ -510,6 +511,8 @@ struct FsNativeSd final : FsNative {
|
||||
FsNativeSd(bool ignore_read_only = true) : FsNative{fsdevGetDeviceFileSystem("sdmc:"), false, ignore_read_only} {
|
||||
m_open_result = 0;
|
||||
}
|
||||
|
||||
bool IsSd() const override { return true; }
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
@@ -14,6 +14,9 @@ enum class Type {
|
||||
Md5,
|
||||
Sha1,
|
||||
Sha256,
|
||||
Null,
|
||||
Deflate,
|
||||
Zstd,
|
||||
};
|
||||
|
||||
struct BaseSource {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
namespace sphaira::haze {
|
||||
|
||||
bool Init();
|
||||
bool IsInit();
|
||||
void Exit();
|
||||
|
||||
using OnInstallStart = std::function<bool(const char* path)>;
|
||||
|
||||
@@ -9,6 +9,11 @@
|
||||
|
||||
namespace sphaira {
|
||||
|
||||
struct NroData {
|
||||
NroStart start;
|
||||
NroHeader header;
|
||||
};
|
||||
|
||||
struct Hbini {
|
||||
u64 timestamp{}; // timestamp of last launch
|
||||
bool hidden{};
|
||||
@@ -27,9 +32,6 @@ struct NroEntry {
|
||||
u64 icon_size{};
|
||||
u64 icon_offset{};
|
||||
|
||||
u64 romfs_size{};
|
||||
u64 romfs_offset{};
|
||||
|
||||
FsTimeStampRaw timestamp{};
|
||||
Hbini hbini{};
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ private:
|
||||
|
||||
using OptionBool = OptionBase<bool>;
|
||||
using OptionLong = OptionBase<long>;
|
||||
using OptionFloat = OptionBase<float>;
|
||||
using OptionString = OptionBase<std::string>;
|
||||
|
||||
} // namespace sphaira::option
|
||||
|
||||
@@ -15,7 +15,10 @@ enum class Mode {
|
||||
SingleThreadedIfSmaller,
|
||||
};
|
||||
|
||||
using DecompressWriteCallback = std::function<Result(const void* data, s64 size)>;
|
||||
|
||||
using ReadCallback = std::function<Result(void* data, s64 off, s64 size, u64* bytes_read)>;
|
||||
using DecompressCallback = std::function<Result(void* data, s64 off, s64 size, const DecompressWriteCallback& callback)>;
|
||||
using WriteCallback = std::function<Result(const void* data, s64 off, s64 size)>;
|
||||
|
||||
// used for pull api
|
||||
@@ -33,6 +36,7 @@ using StartCallback2 = std::function<Result(StartThreadCallback start, PullCallb
|
||||
|
||||
// reads data from rfunc into wfunc.
|
||||
Result Transfer(ui::ProgressBox* pbox, s64 size, const ReadCallback& rfunc, const WriteCallback& wfunc, Mode mode = Mode::MultiThreaded);
|
||||
Result Transfer(ui::ProgressBox* pbox, s64 size, const ReadCallback& rfunc, const DecompressCallback& dfunc, const WriteCallback& wfunc, Mode mode = Mode::MultiThreaded);
|
||||
|
||||
// reads data from rfunc, pull data from provided pull() callback.
|
||||
Result TransferPull(ui::ProgressBox* pbox, s64 size, const ReadCallback& rfunc, const StartCallback& sfunc, Mode mode = Mode::MultiThreaded);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
namespace sphaira::ui::menu::fileview {
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
Menu(const fs::FsPath& path);
|
||||
Menu(fs::Fs* fs, const fs::FsPath& path);
|
||||
|
||||
auto GetShortTitle() const -> const char* override { return "File"; };
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
@@ -15,8 +15,8 @@ struct Menu final : MenuBase {
|
||||
void OnFocusGained() override;
|
||||
|
||||
private:
|
||||
fs::Fs* const m_fs;
|
||||
const fs::FsPath m_path;
|
||||
fs::FsNativeSd m_fs{};
|
||||
fs::File m_file{};
|
||||
s64 m_file_size{};
|
||||
s64 m_file_offset{};
|
||||
|
||||
@@ -106,6 +106,7 @@ struct FileEntry final : FsDirectoryEntry {
|
||||
bool checked_extension{}; // did we already search for an ext?
|
||||
bool checked_internal_extension{}; // did we already search for an ext?
|
||||
bool selected{}; // is this file selected?
|
||||
bool done_stat{}; // have we checked file_size / count.
|
||||
|
||||
auto IsFile() const -> bool {
|
||||
return type == FsDirEntryType_File;
|
||||
@@ -305,9 +306,11 @@ struct FsView final : Widget {
|
||||
void DisplayOptions();
|
||||
void DisplayAdvancedOptions();
|
||||
|
||||
void MountNspFs();
|
||||
void MountXciFs();
|
||||
void MountZipFs();
|
||||
using MountFsFunc = Result(*)(fs::Fs *fs, const fs::FsPath &path, fs::FsPath &out_path);
|
||||
// using MountFsFunc = std::function<Result(fs::Fs *fs, const fs::FsPath &path, fs::FsPath &out_path)>;
|
||||
using UmountFsFunc = std::function<void(const fs::FsPath &mount)>;
|
||||
|
||||
void MountFileFs(const MountFsFunc& mount_func, const UmountFsFunc& umount_func);
|
||||
|
||||
// private:
|
||||
Base* m_menu{};
|
||||
@@ -437,6 +440,9 @@ protected:
|
||||
|
||||
auto CreateFs(const FsEntry& fs_entry) -> std::shared_ptr<fs::Fs>;
|
||||
|
||||
private:
|
||||
void Init(const std::shared_ptr<fs::Fs>& fs, const FsEntry& fs_entry, const fs::FsPath& path, bool is_custom);
|
||||
|
||||
protected:
|
||||
static constexpr inline const char* INI_SECTION = "filebrowser";
|
||||
|
||||
@@ -480,7 +486,6 @@ auto IsSamePath(std::string_view a, std::string_view b) -> bool;
|
||||
auto IsExtension(std::string_view ext1, std::string_view ext2) -> bool;
|
||||
auto IsExtension(std::string_view ext, std::span<const std::string_view> list) -> bool;
|
||||
|
||||
|
||||
struct FsStdioWrapper final : fs::FsStdio {
|
||||
using OnExit = std::function<void(void)>;
|
||||
FsStdioWrapper(const fs::FsPath& root, const OnExit& on_exit) : fs::FsStdio{true, root}, m_on_exit{on_exit} {
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace sphaira::ui::menu::game {
|
||||
|
||||
struct Entry {
|
||||
u64 app_id{};
|
||||
u8 type{};
|
||||
u8 last_event{};
|
||||
NacpLanguageEntry lang{};
|
||||
int image{};
|
||||
bool selected{};
|
||||
@@ -84,7 +84,8 @@ private:
|
||||
}
|
||||
|
||||
void DeleteGames();
|
||||
void DumpGames(u32 flags);
|
||||
void ExportOptions(bool to_nsz);
|
||||
void DumpGames(u32 flags, bool to_nsz);
|
||||
void CreateSaves(AccountUid uid);
|
||||
|
||||
private:
|
||||
@@ -98,6 +99,10 @@ private:
|
||||
bool m_is_reversed{};
|
||||
bool m_dirty{};
|
||||
|
||||
// use for detection game card removal to force a refresh.
|
||||
Event m_gc_event{};
|
||||
FsEventNotifier m_gc_event_notifier{};
|
||||
|
||||
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Updated};
|
||||
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
|
||||
option::OptionLong m_layout{INI_SECTION, "layout", LayoutType::LayoutType_Grid};
|
||||
@@ -160,13 +165,13 @@ struct ContentInfoEntry {
|
||||
std::vector<NcmRightsId> ncm_rights_id{};
|
||||
};
|
||||
|
||||
auto BuildNspPath(const Entry& e, const NsApplicationContentMetaStatus& status) -> fs::FsPath;
|
||||
Result BuildContentEntry(const NsApplicationContentMetaStatus& status, ContentInfoEntry& out);
|
||||
Result BuildNspEntry(const Entry& e, const ContentInfoEntry& info, const keys::Keys& keys, NspEntry& out);
|
||||
Result BuildNspEntries(Entry& e, const title::MetaEntries& meta_entries, std::vector<NspEntry>& out);
|
||||
Result BuildNspEntries(Entry& e, u32 flags, std::vector<NspEntry>& out);
|
||||
auto BuildNspPath(const Entry& e, const NsApplicationContentMetaStatus& status, bool to_nsz = false) -> fs::FsPath;
|
||||
Result BuildContentEntry(const NsApplicationContentMetaStatus& status, ContentInfoEntry& out, bool to_nsz = false);
|
||||
Result BuildNspEntry(const Entry& e, const ContentInfoEntry& info, const keys::Keys& keys, NspEntry& out, bool to_nsz = false);
|
||||
Result BuildNspEntries(Entry& e, const title::MetaEntries& meta_entries, std::vector<NspEntry>& out, bool to_nsz = false);
|
||||
Result BuildNspEntries(Entry& e, u32 flags, std::vector<NspEntry>& out, bool to_nsz = false);
|
||||
|
||||
// dumps the array of nsp entries.
|
||||
void DumpNsp(const std::vector<NspEntry>& entries);
|
||||
void DumpNsp(const std::vector<NspEntry>& entries, bool to_nsz);
|
||||
|
||||
} // namespace sphaira::ui::menu::game
|
||||
|
||||
@@ -91,7 +91,7 @@ private:
|
||||
return GetEntry(m_index);
|
||||
}
|
||||
|
||||
void DumpGames();
|
||||
void DumpGames(bool to_nsz);
|
||||
void DeleteGames();
|
||||
Result ResetRequiredSystemVersion(MetaEntry& entry) const;
|
||||
Result GetNcmSizeOfMetaStatus(MetaEntry& entry) const;
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
#include <span>
|
||||
#include <memory>
|
||||
|
||||
namespace sphaira::ui::menu::gc {
|
||||
// todo: pr to libnx
|
||||
extern "C" {
|
||||
|
||||
typedef enum {
|
||||
FsGameCardPartitionRaw_None = -1,
|
||||
@@ -15,6 +16,13 @@ typedef enum {
|
||||
FsGameCardPartitionRaw_Secure = 1,
|
||||
} FsGameCardPartitionRaw;
|
||||
|
||||
Result fsOpenGameCardStorage(FsStorage* out, const FsGameCardHandle* handle, FsGameCardPartitionRaw partition);
|
||||
Result fsOpenGameCardDetectionEventNotifier(FsEventNotifier* out);
|
||||
|
||||
}
|
||||
|
||||
namespace sphaira::ui::menu::gc {
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// The below structs are taken from nxdumptool./
|
||||
////////////////////////////////////////////////
|
||||
@@ -88,6 +96,33 @@ typedef struct {
|
||||
|
||||
static_assert(sizeof(GameCardInitialData) == 0x200);
|
||||
|
||||
/// Encrypted using AES-128-CTR with the key and IV/counter from the `GameCardTitleKeyAreaEncryption` section. Assumed to be all zeroes in retail gamecards.
|
||||
typedef struct {
|
||||
u8 titlekey[0x10]; ///< Decrypted titlekey from the `GameCardInitialData` section.
|
||||
u8 reserved[0xCF0];
|
||||
} GameCardTitleKeyArea;
|
||||
|
||||
static_assert(sizeof(GameCardTitleKeyArea) == 0xD00);
|
||||
|
||||
/// Encrypted using RSA-2048-OAEP and a private OAEP key from AuthoringTool. Assumed to be all zeroes in retail gamecards.
|
||||
typedef struct {
|
||||
u8 titlekey_encryption_key[0x10]; ///< Used as the AES-128-CTR key for the `GameCardTitleKeyArea` section. Randomly generated during XCI creation by AuthoringTool.
|
||||
u8 titlekey_encryption_iv[0x10]; ///< Used as the AES-128-CTR IV/counter for the `GameCardTitleKeyArea` section. Randomly generated during XCI creation by AuthoringTool.
|
||||
u8 reserved[0xE0];
|
||||
} GameCardTitleKeyAreaEncryption;
|
||||
|
||||
static_assert(sizeof(GameCardTitleKeyAreaEncryption) == 0x100);
|
||||
|
||||
/// Used to secure communications between the Lotus and the inserted gamecard.
|
||||
/// Supposedly precedes the gamecard header.
|
||||
typedef struct {
|
||||
GameCardInitialData initial_data;
|
||||
GameCardTitleKeyArea titlekey_area;
|
||||
GameCardTitleKeyAreaEncryption titlekey_area_encryption;
|
||||
} GameCardKeyArea;
|
||||
|
||||
static_assert(sizeof(GameCardKeyArea) == 0x1000);
|
||||
|
||||
typedef struct {
|
||||
u8 maker_code; ///< GameCardUidMakerCode.
|
||||
u8 version; ///< TODO: determine whether this matches GameCardVersion or not.
|
||||
@@ -198,6 +233,7 @@ private:
|
||||
void FreeImage();
|
||||
void OnChangeIndex(s64 new_index);
|
||||
Result DumpGames(u32 flags);
|
||||
Result DumpXcz(u32 flags);
|
||||
|
||||
Result MountGcFs();
|
||||
|
||||
@@ -222,12 +258,12 @@ private:
|
||||
|
||||
FsStorage m_storage{};
|
||||
// size of normal partition.
|
||||
s64 m_parition_normal_size{};
|
||||
s64 m_partition_normal_size{};
|
||||
// size of secure partition.
|
||||
s64 m_parition_secure_size{};
|
||||
s64 m_partition_secure_size{};
|
||||
// used size reported in the xci header.
|
||||
s64 m_storage_trimmed_size{};
|
||||
// total size of m_parition_normal_size + m_parition_secure_size.
|
||||
// total size of m_partition_normal_size + m_partition_secure_size.
|
||||
s64 m_storage_total_size{};
|
||||
// reported size via rom_size in the xci header.
|
||||
s64 m_storage_full_size{};
|
||||
|
||||
@@ -71,7 +71,7 @@ private:
|
||||
return m_sort.Get() >= SortType_UpdatedStar;
|
||||
}
|
||||
|
||||
Result MountRomfsFs();
|
||||
Result MountNroFs();
|
||||
|
||||
private:
|
||||
static constexpr inline const char* INI_SECTION = "homebrew";
|
||||
|
||||
36
sphaira/include/ui/menus/image_viewer.hpp
Normal file
36
sphaira/include/ui/menus/image_viewer.hpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/widget.hpp"
|
||||
#include "fs.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace sphaira::ui::menu::imageview {
|
||||
|
||||
struct Menu final : Widget {
|
||||
Menu(fs::Fs* fs, const fs::FsPath& path);
|
||||
~Menu();
|
||||
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
|
||||
auto IsMenu() const -> bool override {
|
||||
return true;
|
||||
}
|
||||
|
||||
void UpdateSize();
|
||||
|
||||
private:
|
||||
const fs::FsPath m_path;
|
||||
int m_image{};
|
||||
float m_image_width{};
|
||||
float m_image_height{};
|
||||
|
||||
// for zoom, 0.1 - 1.0
|
||||
float m_zoom{1};
|
||||
|
||||
// for pan.
|
||||
float m_xoff{};
|
||||
float m_yoff{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::imageview
|
||||
@@ -87,7 +87,8 @@ private:
|
||||
|
||||
auto BuildSavePath(const Entry& e, bool is_auto) const -> fs::FsPath;
|
||||
Result RestoreSaveInternal(ProgressBox* pbox, const Entry& e, const fs::FsPath& path) const;
|
||||
Result BackupSaveInternal(ProgressBox* pbox, const dump::DumpLocation& location, const Entry& e, bool compressed, bool is_auto = false) const;
|
||||
Result BackupSaveInternal(ProgressBox* pbox, const dump::DumpLocation& location, Entry& e, bool compressed, bool is_auto = false) const;
|
||||
Result BackupSaveInternal(ProgressBox* pbox, const dump::DumpLocation& location, std::span<const std::reference_wrapper<Entry>> entries, bool compressed, bool is_auto = false) const;
|
||||
|
||||
Result MountSaveFs();
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#if 0
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
@@ -175,3 +176,4 @@ private:
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::themezer
|
||||
#endif
|
||||
|
||||
45
sphaira/include/ui/music_player.hpp
Normal file
45
sphaira/include/ui/music_player.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/widget.hpp"
|
||||
#include "ui/scrolling_text.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "utils/audio.hpp"
|
||||
#include <memory>
|
||||
|
||||
namespace sphaira::ui::music {
|
||||
|
||||
struct Menu final : Widget {
|
||||
Menu(fs::Fs* fs, const fs::FsPath& path);
|
||||
~Menu();
|
||||
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
|
||||
private:
|
||||
void PauseToggle();
|
||||
void SeekForward();
|
||||
void SeekBack();
|
||||
|
||||
void IncreaseVolume();
|
||||
void DecreaseVolume();
|
||||
|
||||
private:
|
||||
audio::SongID m_song{};
|
||||
audio::Info m_info{};
|
||||
audio::Meta m_meta{};
|
||||
// only set if metadata was loaded.
|
||||
int m_icon{};
|
||||
|
||||
ScrollingText m_scroll_title{};
|
||||
ScrollingText m_scroll_artist{};
|
||||
ScrollingText m_scroll_album{};
|
||||
|
||||
// from movienx
|
||||
static constexpr Vec4 osd_progress_bar{400.f, 550, 1280.f - (400.f * 2.f), 10.f};
|
||||
// static constexpr Vec4 osd_progress_bar{300.f, SCREEN_HEIGHT / 2 - 15 / 2, 1280.f - (300.f * 2.f), 10.f};
|
||||
static constexpr Vec2 osd_time_text_left{osd_progress_bar.x - 12.f, osd_progress_bar.y - 2.f};
|
||||
static constexpr Vec2 osd_time_text_right{osd_progress_bar.x + osd_progress_bar.w + 12.f, osd_progress_bar.y - 2.f};
|
||||
static constexpr Vec4 osd_bar_outline{osd_time_text_left.x - 80, osd_progress_bar.y - 30, osd_progress_bar.w + 80 * 2 + 30, osd_progress_bar.h + 30 + 30};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::music
|
||||
@@ -17,8 +17,7 @@ struct ProgressBox final : Widget {
|
||||
int image,
|
||||
const std::string& action,
|
||||
const std::string& title,
|
||||
const ProgressBoxCallback& callback, const ProgressBoxDoneCallback& done = [](Result rc){},
|
||||
int cpuid = 1, int prio = PRIO_PREEMPTIVE, int stack_size = 1024*128
|
||||
const ProgressBoxCallback& callback, const ProgressBoxDoneCallback& done = nullptr
|
||||
);
|
||||
~ProgressBox();
|
||||
|
||||
@@ -28,6 +27,8 @@ struct ProgressBox final : Widget {
|
||||
auto SetActionName(const std::string& action) -> ProgressBox&;
|
||||
auto SetTitle(const std::string& title) -> ProgressBox&;
|
||||
auto NewTransfer(const std::string& transfer) -> ProgressBox&;
|
||||
// zeros the saved offset.
|
||||
auto ResetTranfser() -> ProgressBox&;
|
||||
auto UpdateTransfer(s64 offset, s64 size) -> ProgressBox&;
|
||||
// not const in order to avoid copy by using std::swap
|
||||
auto SetImage(int image) -> ProgressBox&;
|
||||
@@ -44,10 +45,6 @@ struct ProgressBox final : Widget {
|
||||
auto CopyFile(const fs::FsPath& src, const fs::FsPath& dst, bool single_threaded = false) -> Result;
|
||||
void Yield();
|
||||
|
||||
auto GetCpuId() const {
|
||||
return m_cpuid;
|
||||
}
|
||||
|
||||
auto OnDownloadProgressCallback() {
|
||||
return [this](s64 dltotal, s64 dlnow, s64 ultotal, s64 ulnow){
|
||||
if (this->ShouldExit()) {
|
||||
@@ -103,7 +100,6 @@ private:
|
||||
ScrollingText m_scroll_title{};
|
||||
ScrollingText m_scroll_transfer{};
|
||||
|
||||
int m_cpuid{};
|
||||
int m_image{};
|
||||
bool m_own_image{};
|
||||
};
|
||||
|
||||
@@ -91,6 +91,25 @@ private:
|
||||
std::string m_false_str;
|
||||
};
|
||||
|
||||
class SidebarEntrySlider final : public SidebarEntryBase {
|
||||
public:
|
||||
using Callback = std::function<void(float&)>;
|
||||
|
||||
public:
|
||||
explicit SidebarEntrySlider(const std::string& title, float value, float min, float max, int steps, const Callback& cb, const std::string& info = "");
|
||||
void Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left) override;
|
||||
|
||||
private:
|
||||
float m_value;
|
||||
float m_min;
|
||||
float m_max;
|
||||
int m_steps;
|
||||
Callback m_callback;
|
||||
|
||||
float m_duration;
|
||||
float m_inc;
|
||||
};
|
||||
|
||||
class SidebarEntryCallback final : public SidebarEntryBase {
|
||||
public:
|
||||
using Callback = std::function<void()>;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "nanovg.h"
|
||||
#include "pulsar.h"
|
||||
#include "fs.hpp"
|
||||
|
||||
#include <switch.h>
|
||||
@@ -229,6 +228,7 @@ struct ThemeMeta {
|
||||
struct Theme {
|
||||
ThemeMeta meta;
|
||||
ElementEntry elements[ThemeEntryID_MAX];
|
||||
fs::FsPath music_path;
|
||||
|
||||
auto GetColour(ThemeEntryID id) const {
|
||||
return elements[id].colour;
|
||||
@@ -291,11 +291,13 @@ enum class Button : u64 {
|
||||
LS_RIGHT = static_cast<u64>(HidNpadButton_StickLRight),
|
||||
LS_UP = static_cast<u64>(HidNpadButton_StickLUp),
|
||||
LS_DOWN = static_cast<u64>(HidNpadButton_StickLDown),
|
||||
LS_ANY = LS_LEFT | LS_RIGHT | LS_UP | LS_DOWN,
|
||||
|
||||
RS_LEFT = static_cast<u64>(HidNpadButton_StickRLeft),
|
||||
RS_RIGHT = static_cast<u64>(HidNpadButton_StickRRight),
|
||||
RS_UP = static_cast<u64>(HidNpadButton_StickRUp),
|
||||
RS_DOWN = static_cast<u64>(HidNpadButton_StickRDown),
|
||||
RS_ANY = RS_LEFT | RS_RIGHT | RS_UP | RS_DOWN,
|
||||
|
||||
ANY_LEFT = static_cast<u64>(HidNpadButton_AnyLeft),
|
||||
ANY_RIGHT = static_cast<u64>(HidNpadButton_AnyRight),
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::usb::tinfoil {
|
||||
|
||||
enum Magic : u32 {
|
||||
Magic_List0 = 0x304C5554, // TUL0 (Tinfoil Usb List 0)
|
||||
Magic_Command0 = 0x30435554, // TUC0 (Tinfoil USB Command 0)
|
||||
};
|
||||
|
||||
enum USBCmdType : u8 {
|
||||
REQUEST = 0,
|
||||
RESPONSE = 1
|
||||
};
|
||||
|
||||
enum USBCmdId : u32 {
|
||||
EXIT = 0,
|
||||
FILE_RANGE = 1
|
||||
};
|
||||
|
||||
// extension flags for sphaira.
|
||||
enum USBFlag : u8 {
|
||||
USBFlag_NONE = 0,
|
||||
// stream install, does not allow for random access.
|
||||
// allows the upload to be multi threaded., do not modify!
|
||||
// the order of the file list must be kept as-is.
|
||||
USBFlag_STREAM = 1 << 0,
|
||||
};
|
||||
|
||||
struct TUSHeader {
|
||||
u32 magic; // TUL0 (Tinfoil Usb List 0)
|
||||
u32 nspListSize;
|
||||
u8 flags;
|
||||
u8 padding[0x7];
|
||||
};
|
||||
|
||||
struct NX_PACKED USBCmdHeader {
|
||||
u32 magic; // TUC0 (Tinfoil USB Command 0)
|
||||
USBCmdType type;
|
||||
u8 padding[0x3];
|
||||
u32 cmdId;
|
||||
u64 dataSize;
|
||||
u8 reserved[0xC];
|
||||
};
|
||||
|
||||
struct FileRangeCmdHeader {
|
||||
u64 size;
|
||||
u64 offset;
|
||||
u64 nspNameLen;
|
||||
u64 padding;
|
||||
};
|
||||
|
||||
static_assert(sizeof(TUSHeader) == 0x10, "TUSHeader must be 0x10!");
|
||||
static_assert(sizeof(USBCmdHeader) == 0x20, "USBCmdHeader must be 0x20!");
|
||||
|
||||
} // namespace sphaira::usb::tinfoil
|
||||
64
sphaira/include/usb/usb_api.hpp
Normal file
64
sphaira/include/usb/usb_api.hpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
#include "defines.hpp"
|
||||
|
||||
namespace sphaira::usb::api {
|
||||
|
||||
enum : u32 {
|
||||
MAGIC = 0x53504830,
|
||||
PACKET_SIZE = 16,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
CMD_QUIT = 0,
|
||||
CMD_OPEN = 1,
|
||||
CMD_EXPORT = 1,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
RESULT_OK = 0,
|
||||
RESULT_ERROR = 1,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
FLAG_NONE = 0,
|
||||
FLAG_STREAM = 1 << 0,
|
||||
};
|
||||
|
||||
struct SendHeader {
|
||||
u32 magic;
|
||||
u32 cmd;
|
||||
u32 arg3;
|
||||
u32 arg4;
|
||||
|
||||
Result Verify() const {
|
||||
R_UNLESS(magic == MAGIC, Result_UsbBadMagic);
|
||||
R_SUCCEED();
|
||||
}
|
||||
};
|
||||
|
||||
struct ResultHeader {
|
||||
u32 magic;
|
||||
u32 result;
|
||||
u32 arg3;
|
||||
u32 arg4;
|
||||
|
||||
Result Verify() const {
|
||||
R_UNLESS(magic == MAGIC, Result_UsbBadMagic);
|
||||
R_UNLESS(result == RESULT_OK, 1); // todo: create error code.
|
||||
R_SUCCEED();
|
||||
}
|
||||
};
|
||||
|
||||
struct SendDataHeader {
|
||||
u64 offset;
|
||||
u32 size;
|
||||
u32 crc32c;
|
||||
};
|
||||
|
||||
static_assert(sizeof(SendHeader) == PACKET_SIZE);
|
||||
static_assert(sizeof(ResultHeader) == PACKET_SIZE);
|
||||
static_assert(sizeof(SendDataHeader) == PACKET_SIZE);
|
||||
|
||||
} // namespace sphaira::usb::api
|
||||
37
sphaira/include/usb/usb_dumper.hpp
Normal file
37
sphaira/include/usb/usb_dumper.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "usb/usbds.hpp"
|
||||
#include "usb/usb_api.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::usb::dump {
|
||||
|
||||
struct Usb {
|
||||
Usb(u64 transfer_timeout);
|
||||
~Usb();
|
||||
|
||||
Result Write(const void* buf, u64 off, u32 size);
|
||||
void SignalCancel();
|
||||
|
||||
// waits for connection and then sends file list.
|
||||
Result IsUsbConnected(u64 timeout);
|
||||
Result WaitForConnection(std::string_view path, u64 timeout);
|
||||
|
||||
// Result OpenFile(u32 index, s64& file_size);
|
||||
Result CloseFile();
|
||||
|
||||
private:
|
||||
Result SendAndVerify(const void* data, u32 size, u64 timeout, api::ResultHeader* out = nullptr);
|
||||
Result SendAndVerify(const void* data, u32 size, api::ResultHeader* out = nullptr);
|
||||
|
||||
private:
|
||||
std::unique_ptr<usb::UsbDs> m_usb{};
|
||||
Result m_open_result{};
|
||||
bool m_was_connected{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::usb::dumpl
|
||||
39
sphaira/include/usb/usb_installer.hpp
Normal file
39
sphaira/include/usb/usb_installer.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "usb/usbds.hpp"
|
||||
#include "usb/usb_api.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::usb::install {
|
||||
|
||||
struct Usb {
|
||||
Usb(u64 transfer_timeout);
|
||||
~Usb();
|
||||
|
||||
Result Read(void* buf, u64 off, u32 size, u64* bytes_read);
|
||||
u32 GetFlags() const;
|
||||
void SignalCancel();
|
||||
|
||||
// waits for connection and then sends file list.
|
||||
Result IsUsbConnected(u64 timeout);
|
||||
Result WaitForConnection(u64 timeout, std::vector<std::string>& names);
|
||||
|
||||
Result OpenFile(u32 index, s64& file_size);
|
||||
Result CloseFile();
|
||||
|
||||
private:
|
||||
Result SendAndVerify(const void* data, u32 size, u64 timeout, api::ResultHeader* out = nullptr);
|
||||
Result SendAndVerify(const void* data, u32 size, api::ResultHeader* out = nullptr);
|
||||
|
||||
private:
|
||||
std::unique_ptr<usb::UsbDs> m_usb{};
|
||||
Result m_open_result{};
|
||||
bool m_was_connected{};
|
||||
u32 m_flags{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::usb::install
|
||||
@@ -13,25 +13,30 @@ struct Usb {
|
||||
Usb(u64 transfer_timeout);
|
||||
virtual ~Usb();
|
||||
|
||||
virtual Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) = 0;
|
||||
virtual Result Read(void* buf, u64 off, u32 size, u64* bytes_read) = 0;
|
||||
virtual Result Open(u32 index, s64& out_size, u16& out_flags) = 0;
|
||||
|
||||
Result IsUsbConnected(u64 timeout) {
|
||||
return m_usb->IsUsbConnected(timeout);
|
||||
}
|
||||
|
||||
// waits for connection and then sends file list.
|
||||
Result WaitForConnection(u64 timeout, u8 flags, std::span<const std::string> names);
|
||||
Result WaitForConnection(u64 timeout, std::span<const std::string> names);
|
||||
|
||||
// polls for command, executes transfer if possible.
|
||||
// will return Result_Exit if exit command is recieved.
|
||||
Result PollCommands();
|
||||
|
||||
private:
|
||||
Result FileRangeCmd(u64 data_size);
|
||||
Result file_transfer_loop();
|
||||
|
||||
private:
|
||||
std::unique_ptr<usb::UsbHs> m_usb;
|
||||
std::vector<u8> m_buf;
|
||||
Result SendResult(u32 result, u32 arg3 = 0, u32 arg4 = 0);
|
||||
|
||||
private:
|
||||
std::unique_ptr<usb::UsbHs> m_usb{};
|
||||
std::vector<u8> m_buf{};
|
||||
Result m_open_result{};
|
||||
bool m_was_connected{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::usb::upload
|
||||
|
||||
@@ -2,11 +2,6 @@
|
||||
|
||||
#include "base.hpp"
|
||||
|
||||
// TODO: remove these when libnx pr is merged.
|
||||
enum { UsbDeviceSpeed_None = 0x0 };
|
||||
enum { UsbDeviceSpeed_Low = 0x1 };
|
||||
Result usbDsGetSpeed(UsbDeviceSpeed *out);
|
||||
|
||||
auto GetUsbDsStateStr(UsbState state) -> const char*;
|
||||
auto GetUsbDsSpeedStr(UsbDeviceSpeed speed) -> const char*;
|
||||
|
||||
|
||||
13
sphaira/include/usbdvd.hpp
Normal file
13
sphaira/include/usbdvd.hpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
#include "location.hpp"
|
||||
|
||||
namespace sphaira::usbdvd {
|
||||
|
||||
Result MountAll();
|
||||
void UnmountAll();
|
||||
|
||||
bool GetMountPoint(location::StdioEntry& out);
|
||||
|
||||
} // namespace sphaira::usbdvd
|
||||
81
sphaira/include/utils/audio.hpp
Normal file
81
sphaira/include/utils/audio.hpp
Normal file
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#include "fs.hpp"
|
||||
#include "image.hpp"
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace sphaira::audio {
|
||||
|
||||
enum class State {
|
||||
Free, // private use.
|
||||
Playing, // song is playing.
|
||||
Paused, // song has paused.
|
||||
Finished, // song has finished.
|
||||
Error, // error in playback.
|
||||
};
|
||||
|
||||
struct Progress {
|
||||
u64 played;
|
||||
};
|
||||
|
||||
struct Info {
|
||||
u64 sample_count;
|
||||
u32 sample_rate;
|
||||
u32 channels;
|
||||
u32 loop_start;
|
||||
bool looping;
|
||||
};
|
||||
|
||||
struct Meta {
|
||||
std::string title{};
|
||||
std::string album{};
|
||||
std::string artist{};
|
||||
std::vector<u8> image{};
|
||||
};
|
||||
|
||||
enum class SoundEffect {
|
||||
Focus,
|
||||
Scroll,
|
||||
Limit,
|
||||
Startup,
|
||||
Install,
|
||||
Error,
|
||||
MAX,
|
||||
};
|
||||
|
||||
enum Flag {
|
||||
Flag_None = 0,
|
||||
// plays the song for ever.
|
||||
Flag_Loop = 1 << 0,
|
||||
};
|
||||
|
||||
using SongID = void*;
|
||||
|
||||
Result Init();
|
||||
void ExitSignal();
|
||||
void Exit();
|
||||
|
||||
Result PlaySoundEffect(SoundEffect effect);
|
||||
|
||||
Result OpenSong(fs::Fs* fs, const fs::FsPath& path, u32 flags, SongID* id);
|
||||
Result CloseSong(SongID* id);
|
||||
|
||||
Result PlaySong(SongID id);
|
||||
Result PauseSong(SongID id);
|
||||
Result SeekSong(SongID id, u64 target);
|
||||
|
||||
// todo:
|
||||
// 0.0 -> 2.0.
|
||||
Result GetVolumeSong(SongID id, float* out);
|
||||
Result SetVolumeSong(SongID id, float in);
|
||||
|
||||
// todo:
|
||||
Result GetPitchSong(SongID id, float* out);
|
||||
Result SetPitchSong(SongID id, float in);
|
||||
|
||||
Result GetInfo(SongID id, Info* out);
|
||||
Result GetMeta(SongID id, Meta* out);
|
||||
Result GetProgress(SongID id, Progress* out_progress, State* out_state);
|
||||
|
||||
} // namespace sphaira::audio
|
||||
@@ -1,15 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
#include "fs.hpp"
|
||||
#include "yati/source/base.hpp"
|
||||
|
||||
#include <switch.h>
|
||||
#include <memory>
|
||||
|
||||
namespace sphaira::devoptab {
|
||||
|
||||
// mounts to "lower_case_hex_id:/"
|
||||
Result MountFromSavePath(u64 id, fs::FsPath& out_path);
|
||||
Result MountSaveSystem(u64 id, fs::FsPath& out_path);
|
||||
void UnmountSave(u64 id);
|
||||
|
||||
// todo:
|
||||
Result MountZip(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path);
|
||||
void UmountZip(const fs::FsPath& mount);
|
||||
|
||||
@@ -17,6 +19,17 @@ Result MountNsp(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path);
|
||||
void UmountNsp(const fs::FsPath& mount);
|
||||
|
||||
Result MountXci(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path);
|
||||
Result MountXciSource(const std::shared_ptr<sphaira::yati::source::Base>& source, s64 size, const fs::FsPath& path, fs::FsPath& out_path);
|
||||
void UmountXci(const fs::FsPath& mount);
|
||||
|
||||
Result MountNca(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path);
|
||||
Result MountNcaNcm(NcmContentStorage* cs, const NcmContentId* id, fs::FsPath& out_path);
|
||||
void UmountNca(const fs::FsPath& mount);
|
||||
|
||||
Result MountBfsar(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path);
|
||||
void UmountBfsar(const fs::FsPath& mount);
|
||||
|
||||
Result MountNro(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path);
|
||||
void UmountNro(const fs::FsPath& mount);
|
||||
|
||||
} // namespace sphaira::devoptab
|
||||
|
||||
@@ -1,35 +1,84 @@
|
||||
#pragma once
|
||||
|
||||
#include "yati/source/file.hpp"
|
||||
#include "utils/lru.hpp"
|
||||
#include <memory>
|
||||
#include <span>
|
||||
|
||||
namespace sphaira::devoptab::common {
|
||||
|
||||
// max entries per devoptab, should be enough.
|
||||
enum { MAX_ENTRIES = 4 };
|
||||
|
||||
// buffers data in 512k chunks to maximise throughput.
|
||||
// not suitable if random access >= 512k is common.
|
||||
// if that is needed, see the LRU cache varient used for fatfs.
|
||||
struct BufferedData final : yati::source::Base {
|
||||
static constexpr inline u64 CHUNK_SIZE = 1024 * 512;
|
||||
|
||||
BufferedData(std::unique_ptr<yati::source::Base>&& _source, u64 _size)
|
||||
: source{std::forward<decltype(_source)>(_source)}
|
||||
struct BufferedDataBase : yati::source::Base {
|
||||
BufferedDataBase(const std::shared_ptr<yati::source::Base>& _source, u64 _size)
|
||||
: source{_source}
|
||||
, capacity{_size} {
|
||||
|
||||
}
|
||||
|
||||
Result Read(void *buf, s64 off, s64 size);
|
||||
Result Read(void* buf, s64 off, s64 size, u64* bytes_read) override;
|
||||
virtual Result Read(void* buf, s64 off, s64 size, u64* bytes_read) override {
|
||||
return source->Read(buf, off, size, bytes_read);
|
||||
}
|
||||
|
||||
protected:
|
||||
std::shared_ptr<yati::source::Base> source;
|
||||
const u64 capacity;
|
||||
};
|
||||
|
||||
// buffers data in 512k chunks to maximise throughput.
|
||||
// not suitable if random access >= 512k is common.
|
||||
// if that is needed, see the LRU cache varient used for fatfs.
|
||||
struct BufferedData : BufferedDataBase {
|
||||
BufferedData(const std::shared_ptr<yati::source::Base>& _source, u64 _size, u64 _alloc = 1024 * 512)
|
||||
: BufferedDataBase{_source, _size} {
|
||||
m_data.resize(_alloc);
|
||||
}
|
||||
|
||||
virtual Result Read(void* buf, s64 off, s64 size, u64* bytes_read) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<yati::source::Base> source;
|
||||
const u64 capacity;
|
||||
|
||||
u64 m_off{};
|
||||
u64 m_size{};
|
||||
u8 m_data[CHUNK_SIZE]{};
|
||||
std::vector<u8> m_data{};
|
||||
};
|
||||
|
||||
struct BufferedFileData {
|
||||
u8* data{};
|
||||
u64 off{};
|
||||
u64 size{};
|
||||
|
||||
~BufferedFileData() {
|
||||
if (data) {
|
||||
free(data);
|
||||
}
|
||||
}
|
||||
|
||||
void Allocate(u64 new_size) {
|
||||
data = (u8*)realloc(data, new_size * sizeof(*data));
|
||||
off = 0;
|
||||
size = 0;
|
||||
}
|
||||
};
|
||||
|
||||
constexpr u64 CACHE_LARGE_ALLOC_SIZE = 1024 * 512;
|
||||
constexpr u64 CACHE_LARGE_SIZE = 1024 * 16;
|
||||
|
||||
struct LruBufferedData : BufferedDataBase {
|
||||
LruBufferedData(const std::shared_ptr<yati::source::Base>& _source, u64 _size, u32 small = 1024, u32 large = 2)
|
||||
: BufferedDataBase{_source, _size} {
|
||||
buffered_small.resize(small);
|
||||
buffered_large.resize(large);
|
||||
lru_cache[0].Init(buffered_small);
|
||||
lru_cache[1].Init(buffered_large);
|
||||
}
|
||||
|
||||
virtual Result Read(void* buf, s64 off, s64 size, u64* bytes_read) override;
|
||||
|
||||
private:
|
||||
utils::Lru<BufferedFileData> lru_cache[2]{};
|
||||
std::vector<BufferedFileData> buffered_small{}; // 1MiB (usually).
|
||||
std::vector<BufferedFileData> buffered_large{}; // 1MiB
|
||||
};
|
||||
|
||||
bool fix_path(const char* str, char* out);
|
||||
|
||||
41
sphaira/include/utils/devoptab_romfs.hpp
Normal file
41
sphaira/include/utils/devoptab_romfs.hpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "yati/source/base.hpp"
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
#include <string_view>
|
||||
#include <sys/stat.h>
|
||||
|
||||
namespace sphaira::devoptab::romfs {
|
||||
|
||||
struct RomfsCollection {
|
||||
romfs_header header;
|
||||
std::vector<u8> dir_table;
|
||||
std::vector<u8> file_table;
|
||||
u64 offset;
|
||||
};
|
||||
|
||||
struct FileEntry {
|
||||
const romfs_file* romfs;
|
||||
u64 offset;
|
||||
u64 size;
|
||||
};
|
||||
|
||||
struct DirEntry {
|
||||
const RomfsCollection* romfs_collection;
|
||||
const romfs_dir* romfs_root; // start of the dir.
|
||||
u32 romfs_childDir;
|
||||
u32 romfs_childFile;
|
||||
};
|
||||
|
||||
bool find_file(const RomfsCollection& romfs, std::string_view path, FileEntry& out);
|
||||
bool find_dir(const RomfsCollection& romfs, std::string_view path, DirEntry& out);
|
||||
|
||||
// helper
|
||||
void dirreset(DirEntry& entry);
|
||||
bool dirnext(DirEntry& entry, char* filename, struct stat *filestat);
|
||||
|
||||
Result LoadRomfsCollection(yati::source::Base* source, u64 offset, RomfsCollection& out);
|
||||
|
||||
} // namespace sphaira::devoptab::romfs
|
||||
76
sphaira/include/utils/lru.hpp
Normal file
76
sphaira/include/utils/lru.hpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <span>
|
||||
|
||||
namespace sphaira::utils {
|
||||
|
||||
template<typename T>
|
||||
struct LinkedList {
|
||||
T* data;
|
||||
LinkedList* next;
|
||||
LinkedList* prev;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct Lru {
|
||||
using ListEntry = LinkedList<T>;
|
||||
|
||||
// pass span of the data.
|
||||
void Init(std::span<T> data) {
|
||||
list_flat_array.clear();
|
||||
list_flat_array.resize(data.size());
|
||||
|
||||
auto list_entry = list_head = list_flat_array.data();
|
||||
|
||||
for (size_t i = 0; i < data.size(); i++) {
|
||||
list_entry = list_flat_array.data() + i;
|
||||
list_entry->data = data.data() + i;
|
||||
|
||||
if (i + 1 < data.size()) {
|
||||
list_entry->next = &list_flat_array[i + 1];
|
||||
}
|
||||
if (i) {
|
||||
list_entry->prev = &list_flat_array[i - 1];
|
||||
}
|
||||
}
|
||||
|
||||
list_tail = list_entry->prev->next;
|
||||
}
|
||||
|
||||
// moves entry to the front of the list.
|
||||
void Update(ListEntry* entry) {
|
||||
// only update position if we are not the head.
|
||||
if (list_head != entry) {
|
||||
entry->prev->next = entry->next;
|
||||
if (entry->next) {
|
||||
entry->next->prev = entry->prev;
|
||||
} else {
|
||||
list_tail = entry->prev;
|
||||
}
|
||||
|
||||
// update head.
|
||||
auto head_temp = list_head;
|
||||
list_head = entry;
|
||||
list_head->prev = nullptr;
|
||||
list_head->next = head_temp;
|
||||
head_temp->prev = list_head;
|
||||
}
|
||||
}
|
||||
|
||||
// moves last entry (tail) to the front of the list.
|
||||
auto GetNextFree() {
|
||||
Update(list_tail);
|
||||
return list_head->data;
|
||||
}
|
||||
|
||||
auto begin() const { return list_head; }
|
||||
auto end() const { return list_tail; }
|
||||
|
||||
private:
|
||||
ListEntry* list_head{};
|
||||
ListEntry* list_tail{};
|
||||
std::vector<ListEntry> list_flat_array{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::utils
|
||||
23
sphaira/include/utils/nsz_dumper.hpp
Normal file
23
sphaira/include/utils/nsz_dumper.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "fs.hpp"
|
||||
#include "defines.hpp"
|
||||
#include "dumper.hpp"
|
||||
|
||||
#include "ui/progress_box.hpp"
|
||||
#include "yati/nx/keys.hpp"
|
||||
#include "yati/nx/nca.hpp"
|
||||
#include "yati/container/base.hpp"
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace sphaira::utils::nsz {
|
||||
|
||||
using Collection = yati::container::CollectionEntry;
|
||||
using Collections = yati::container::Collections;
|
||||
|
||||
using NcaReaderCreator = std::function<std::unique_ptr<nca::NcaReader>(const nca::Header& header, const keys::KeyEntry& title_key, const Collection& collection)>;
|
||||
|
||||
Result NszExport(ui::ProgressBox* pbox, const NcaReaderCreator& nca_creator, s64& read_offset, s64& write_offset, Collections& collections, const keys::Keys& keys, dump::BaseSource* source, dump::WriteSource* writer, const fs::FsPath& path);
|
||||
|
||||
} // namespace sphaira::utils::nsz
|
||||
@@ -5,12 +5,16 @@
|
||||
|
||||
namespace sphaira::utils {
|
||||
|
||||
struct ScopedTimestampProfile {
|
||||
struct ScopedTimestampProfile final {
|
||||
ScopedTimestampProfile(const std::string& name) : m_name{name} {
|
||||
|
||||
}
|
||||
|
||||
~ScopedTimestampProfile() {
|
||||
Log();
|
||||
}
|
||||
|
||||
void Log() {
|
||||
log_write("\t[%s] time taken: %.2fs %.2fms\n", m_name.c_str(), m_ts.GetSecondsD(), m_ts.GetMsD());
|
||||
}
|
||||
|
||||
|
||||
58
sphaira/include/utils/thread.hpp
Normal file
58
sphaira/include/utils/thread.hpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include "defines.hpp"
|
||||
#include <functional>
|
||||
#include <atomic>
|
||||
|
||||
namespace sphaira::utils {
|
||||
|
||||
static inline Result CreateThread(Thread *t, ThreadFunc entry, void *arg, size_t stack_sz = 1024*128, int prio = 0x3B) {
|
||||
u64 core_mask = 0;
|
||||
R_TRY(svcGetInfo(&core_mask, InfoType_CoreMask, CUR_PROCESS_HANDLE, 0));
|
||||
R_TRY(threadCreate(t, entry, arg, nullptr, stack_sz, prio, -2));
|
||||
R_TRY(svcSetThreadCoreMask(t->handle, -1, core_mask));
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
struct Async final {
|
||||
using Callback = std::function<void(void)>;
|
||||
|
||||
// core0=main, core1=audio, core2=servers (ftp,mtp,nxlink)
|
||||
Async(Callback&& callback) : m_callback{std::forward<Callback>(callback)} {
|
||||
m_running = true;
|
||||
|
||||
if (R_FAILED(CreateThread(&m_thread, thread_func, &m_callback))) {
|
||||
m_running = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (R_FAILED(threadStart(&m_thread))) {
|
||||
threadClose(&m_thread);
|
||||
m_running = false;
|
||||
}
|
||||
}
|
||||
|
||||
~Async() {
|
||||
WaitForExit();
|
||||
}
|
||||
|
||||
void WaitForExit() {
|
||||
if (m_running) {
|
||||
threadWaitForExit(&m_thread);
|
||||
threadClose(&m_thread);
|
||||
m_running = false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static void thread_func(void* arg) {
|
||||
(*static_cast<Callback*>(arg))();
|
||||
}
|
||||
|
||||
private:
|
||||
Callback m_callback;
|
||||
Thread m_thread{};
|
||||
std::atomic_bool m_running{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::utils
|
||||
25
sphaira/include/utils/utils.hpp
Normal file
25
sphaira/include/utils/utils.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/types.hpp"
|
||||
|
||||
namespace sphaira::utils {
|
||||
|
||||
struct HashStr {
|
||||
char str[0x21];
|
||||
};
|
||||
|
||||
HashStr hexIdToStr(FsRightsId id);
|
||||
HashStr hexIdToStr(NcmRightsId id);
|
||||
HashStr hexIdToStr(NcmContentId id);
|
||||
|
||||
template<typename T>
|
||||
constexpr inline T AlignUp(T value, T align) {
|
||||
return (value + (align - 1)) &~ (align - 1);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
constexpr inline T AlignDown(T value, T align) {
|
||||
return value &~ (align - 1);
|
||||
}
|
||||
|
||||
} // namespace sphaira::utils
|
||||
@@ -9,9 +9,10 @@ namespace sphaira::yati::container {
|
||||
struct Nsp final : Base {
|
||||
using Base::Base;
|
||||
Result GetCollections(Collections& out) override;
|
||||
Result GetCollections(Collections& out, s64 off);
|
||||
|
||||
// builds nsp meta data and the size of the entier nsp.
|
||||
static auto Build(std::span<CollectionEntry> collections, s64& size) -> std::vector<u8>;
|
||||
static auto Build(std::span<const CollectionEntry> collections, s64& size) -> std::vector<u8>;
|
||||
};
|
||||
|
||||
} // namespace sphaira::yati::container
|
||||
|
||||
@@ -9,18 +9,62 @@ namespace sphaira::yati::container {
|
||||
|
||||
struct Xci final : Base {
|
||||
|
||||
struct Hfs0Header {
|
||||
u32 magic;
|
||||
u32 total_files;
|
||||
u32 string_table_size;
|
||||
u32 padding;
|
||||
};
|
||||
|
||||
struct Hfs0FileTableEntry {
|
||||
u64 data_offset;
|
||||
u64 data_size;
|
||||
u32 name_offset;
|
||||
u32 hash_size;
|
||||
u64 padding;
|
||||
u8 hash[0x20];
|
||||
};
|
||||
|
||||
struct Hfs0 {
|
||||
Hfs0Header header{};
|
||||
std::vector<Hfs0FileTableEntry> file_table{};
|
||||
std::vector<std::string> string_table{};
|
||||
s64 data_offset{};
|
||||
|
||||
auto GetHfs0Size() const {
|
||||
return sizeof(header) + file_table.size() * sizeof(Hfs0FileTableEntry) + header.string_table_size;
|
||||
}
|
||||
|
||||
auto GetHfs0Data() const -> std::vector<u8>;
|
||||
};
|
||||
|
||||
struct Partition {
|
||||
// name of the partition.
|
||||
std::string name;
|
||||
// offset of this hfs0.
|
||||
s64 hfs0_offset;
|
||||
s64 hfs0_size;
|
||||
Hfs0 hfs0;
|
||||
// all the collections for this partition, may be empty.
|
||||
Collections collections;
|
||||
};
|
||||
|
||||
struct Root {
|
||||
// offset of this hfs0.
|
||||
s64 hfs0_offset;
|
||||
Hfs0 hfs0;
|
||||
std::vector<Partition> partitions;
|
||||
};
|
||||
|
||||
using Partitions = std::vector<Partition>;
|
||||
|
||||
using Base::Base;
|
||||
Result GetCollections(Collections& out) override;
|
||||
Result GetPartitions(Partitions& out);
|
||||
Result GetRoot(Root& out);
|
||||
|
||||
private:
|
||||
Result Hfs0GetPartition(source::Base* source, s64 off, Hfs0& out);
|
||||
Result ReadPartitionFromHfs0(source::Base* source, const Hfs0& root, u32 index, Partition& out);
|
||||
};
|
||||
|
||||
} // namespace sphaira::yati::container
|
||||
|
||||
@@ -54,4 +54,15 @@ static inline void cryptoAes128Xts(const void* in, void* out, const u8* key, u64
|
||||
Aes128Xts(key, is_encryptor).Run(out, in, sector, sector_size, data_size);
|
||||
}
|
||||
|
||||
static inline void UpdateCtr(u8* counter, u64 offset) {
|
||||
const u64 swp = __bswap64(offset >> 4);
|
||||
std::memcpy(&counter[0x8], &swp, 0x8);
|
||||
}
|
||||
|
||||
static inline void SetCtr(u8* counter, u64 ctr, u64 offset = 0) {
|
||||
const u64 swp = __bswap64(ctr);
|
||||
std::memcpy(&counter[0x0], &swp, 0x8);
|
||||
UpdateCtr(counter, offset);
|
||||
}
|
||||
|
||||
} // namespace sphaira::crypto
|
||||
|
||||
@@ -201,7 +201,8 @@ Result EncryptTitleKey(keys::KeyEntry& out, u8 key_gen, const keys::Keys& keys);
|
||||
|
||||
Result ShouldPatchTicket(const TicketData& data, std::span<const u8> ticket, std::span<const u8> cert_chain, bool patch_personalised, bool& should_patch);
|
||||
Result ShouldPatchTicket(std::span<const u8> ticket, std::span<const u8> cert_chain, bool patch_personalised, bool& should_patch);
|
||||
Result PatchTicket(std::vector<u8>& ticket, std::span<const u8> cert_chain, u8 key_gen, const keys::Keys& keys, bool patch_personalised);
|
||||
// cert chain may be modified if the ticket is converted to common ticket.
|
||||
Result PatchTicket(std::vector<u8>& ticket, std::vector<u8>& cert_chain, u8 key_gen, const keys::Keys& keys, bool patch_personalised);
|
||||
|
||||
// fills out with the list of common / personalised rights ids.
|
||||
Result GetCommonTickets(std::vector<FsRightsId>& out);
|
||||
@@ -218,6 +219,11 @@ bool IsRightsIdFound(const FsRightsId& id, std::span<const FsRightsId> ids);
|
||||
// wrapper around ipc.
|
||||
Result GetCommonTicketAndCertificate(const FsRightsId& rights_id, std::vector<u8>& tik_out, std::vector<u8>& cert_out);
|
||||
// fetches data from system es save.
|
||||
Result GetPersonalisedTicketData(u64 *size_out, void *tik_data, u64 tik_size, const FsRightsId* rightsId);
|
||||
Result GetPersonalisedTicketAndCertificate(const FsRightsId& rights_id, std::vector<u8>& tik_out, std::vector<u8>& cert_out);
|
||||
|
||||
// fills out with the decrypted title key.
|
||||
Result GetTitleKeyDecrypted(const FsRightsId& rights_id, u8 key_gen, const keys::Keys& keys, keys::KeyEntry& out);
|
||||
Result GetTitleKeyDecrypted(std::span<const u8> ticket, const FsRightsId& rights_id, u8 key_gen, const keys::Keys& keys, keys::KeyEntry& out);
|
||||
|
||||
} // namespace sphaira::es
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
#include "fs.hpp"
|
||||
#include "keys.hpp"
|
||||
#include "ncm.hpp"
|
||||
#include "yati/source/base.hpp"
|
||||
|
||||
#include <switch.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace sphaira::nca {
|
||||
|
||||
@@ -15,7 +18,7 @@ namespace sphaira::nca {
|
||||
#define NCA_SECTOR_SIZE 0x200
|
||||
#define NCA_XTS_SECTION_SIZE 0xC00
|
||||
#define NCA_SECTION_TOTAL 0x4
|
||||
#define NCA_MEDIA_REAL(x)((x * 0x200))
|
||||
#define NCA_MEDIA_REAL(x)((u64(x) * 0x200))
|
||||
|
||||
#define NCA_PROGRAM_LOGO_OFFSET 0x8000
|
||||
#define NCA_META_CNMT_OFFSET 0xC20
|
||||
@@ -94,6 +97,22 @@ struct SectionTableEntry {
|
||||
u32 media_end_offset; // divided by 0x200.
|
||||
u8 _0x8[0x4]; // unknown.
|
||||
u8 _0xC[0x4]; // unknown.
|
||||
|
||||
auto IsValid() const -> bool {
|
||||
return media_start_offset && media_end_offset;
|
||||
}
|
||||
|
||||
auto GetOffset() const -> u64 {
|
||||
return NCA_MEDIA_REAL(media_start_offset);
|
||||
}
|
||||
|
||||
auto GetOffsetEnd() const -> u64 {
|
||||
return NCA_MEDIA_REAL(media_end_offset);
|
||||
}
|
||||
|
||||
auto GetSize() const -> u64 {
|
||||
return GetOffsetEnd() - GetOffset();
|
||||
}
|
||||
};
|
||||
|
||||
struct LayerRegion {
|
||||
@@ -139,6 +158,55 @@ static_assert(sizeof(HierarchicalSha256Data) == 0xF8);
|
||||
static_assert(sizeof(IntegrityMetaInfo) == 0xF8);
|
||||
static_assert(sizeof(HierarchicalSha256Data) == sizeof(IntegrityMetaInfo));
|
||||
|
||||
struct BucketTreeHeader {
|
||||
u32 magic; // BKTR
|
||||
u32 version;
|
||||
u32 count;
|
||||
u8 _0xC[0x4];
|
||||
};
|
||||
|
||||
struct PatchInfo {
|
||||
u64 indirect_offset;
|
||||
u64 indirect_size;
|
||||
BucketTreeHeader indirect_header;
|
||||
u64 aes_ctr_offset;
|
||||
u64 aes_ctr_size;
|
||||
BucketTreeHeader aes_ctr_header;
|
||||
};
|
||||
static_assert(sizeof(PatchInfo) == 0x40);
|
||||
|
||||
struct CompressionInfo {
|
||||
u64 table_offset;
|
||||
u64 table_size;
|
||||
BucketTreeHeader table_header;
|
||||
u8 _0x20[0x8];
|
||||
};
|
||||
static_assert(sizeof(CompressionInfo) == 0x28);
|
||||
|
||||
struct BktrEntry {
|
||||
u8 _0x0[0x4];
|
||||
u32 count;
|
||||
u64 size;
|
||||
u64 offsets[0x3FF0 / sizeof(u64)];
|
||||
};
|
||||
static_assert(sizeof(BktrEntry) == 0x4000);
|
||||
|
||||
struct NX_PACKED BktrRelocationEntry {
|
||||
u64 patched_addr;
|
||||
u64 source_addr;
|
||||
u32 flag;
|
||||
};
|
||||
static_assert(sizeof(BktrRelocationEntry) == 0x14);
|
||||
|
||||
struct BktrRelocationBucket {
|
||||
u8 _0x0[0x4];
|
||||
u32 count;
|
||||
u64 end_offset;
|
||||
BktrRelocationEntry entries[0x3FF0 / sizeof(BktrRelocationEntry)];
|
||||
u8 _[0x3FF0 % sizeof(BktrRelocationEntry)];
|
||||
};
|
||||
static_assert(sizeof(BktrRelocationBucket) == 0x4000);
|
||||
|
||||
struct FsHeader {
|
||||
u16 version; // always 2.
|
||||
u8 fs_type; // see FileSystemType.
|
||||
@@ -152,12 +220,16 @@ struct FsHeader {
|
||||
IntegrityMetaInfo integrity_meta_info; // used for romfs
|
||||
} hash_data;
|
||||
|
||||
u8 patch_info[0x40];
|
||||
PatchInfo patch_info;
|
||||
u64 section_ctr;
|
||||
u8 spares_info[0x30];
|
||||
u8 compression_info[0x28];
|
||||
CompressionInfo compression_info;
|
||||
u8 meta_data_hash_data_info[0x30];
|
||||
u8 reserved[0x30];
|
||||
|
||||
auto IsValid() const -> bool {
|
||||
return version == 2;
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(FsHeader) == 0x200);
|
||||
static_assert(sizeof(FsHeader::hash_data) == 0xF8);
|
||||
@@ -203,6 +275,10 @@ struct Header {
|
||||
|
||||
FsHeader fs_header[NCA_SECTION_TOTAL];
|
||||
|
||||
auto IsValid() const -> bool {
|
||||
return magic == NCA3_MAGIC;
|
||||
}
|
||||
|
||||
auto GetKeyGeneration() const -> u8 {
|
||||
if (old_key_gen < key_gen) {
|
||||
return key_gen;
|
||||
@@ -220,12 +296,25 @@ struct Header {
|
||||
key_gen = key_generation;
|
||||
}
|
||||
}
|
||||
|
||||
auto GetSectionCount() const -> u8 {
|
||||
u8 count = 0;
|
||||
for (u32 i = 0; i < NCA_SECTION_TOTAL; i++) {
|
||||
if (!fs_header[i].IsValid() || !fs_table[i].IsValid()) {
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(Header) == 0xC00);
|
||||
|
||||
auto GetContentTypeStr(u8 content_type) -> const char*;
|
||||
auto GetDistributionTypeStr(u8 distribution_type) -> const char*;
|
||||
|
||||
Result DecryptHeader(const void* in, const keys::Keys& keys, Header& out);
|
||||
|
||||
Result DecryptKeak(const keys::Keys& keys, Header& header);
|
||||
Result EncryptKeak(const keys::Keys& keys, Header& header, u8 key_generation);
|
||||
Result VerifyFixedKey(const Header& header);
|
||||
@@ -236,4 +325,52 @@ Result ParseControl(const fs::FsPath& path, u64 program_id, void* nacp_out = nul
|
||||
|
||||
auto GetKeyGenStr(u8 key_gen) -> const char*;
|
||||
|
||||
// finds and decrypts the title key, also decrypts header key area if needed.
|
||||
Result GetDecryptedTitleKey(Header& header, const keys::Keys& keys, keys::KeyEntry& out);
|
||||
// same as above but also checks the path for ticket.
|
||||
Result GetDecryptedTitleKey(fs::Fs* fs, const fs::FsPath& path, Header& header, const keys::Keys& keys, keys::KeyEntry& out);
|
||||
|
||||
// helpers.
|
||||
struct DecyptedData : yati::source::Base {
|
||||
DecyptedData(u64 align, const std::shared_ptr<yati::source::Base>& source);
|
||||
Result Read(void *_buf, s64 _off, s64 _size, u64* _bytes_read) override;
|
||||
virtual Result SetCtr(u64 ctr) = 0;
|
||||
|
||||
private:
|
||||
virtual Result Decrypt(void* buf, s64 off, s64 size) = 0;
|
||||
|
||||
private:
|
||||
std::shared_ptr<yati::source::Base> m_source;
|
||||
const u64 m_align;
|
||||
};
|
||||
|
||||
// todo: add support for xts sections.
|
||||
struct DecyptedDataCtr final : DecyptedData {
|
||||
DecyptedDataCtr(const void* key, u64 ctr, const std::shared_ptr<yati::source::Base>& source);
|
||||
Result SetCtr(u64 ctr) override;
|
||||
|
||||
private:
|
||||
Result Decrypt(void* buf, s64 off, s64 size) override;
|
||||
|
||||
private:
|
||||
Aes128CtrContext m_ctx{};
|
||||
u8 m_ctr[AES_BLOCK_SIZE]{};
|
||||
};
|
||||
|
||||
struct NcaReader final : yati::source::Base {
|
||||
NcaReader(const nca::Header& decrypted_header, const void* key, u64 size, const std::shared_ptr<yati::source::Base>& source);
|
||||
Result Read(void *_buf, s64 off, s64 size, u64* bytes_read) override;
|
||||
Result ReadEncrypted(void *_buf, s64 off, s64 size, u64* bytes_read);
|
||||
|
||||
private:
|
||||
Result ReadInternal(void *_buf, s64 off, s64 size, u64* bytes_read, bool decrypt);
|
||||
|
||||
private:
|
||||
const nca::Header m_header;
|
||||
const u64 m_capacity;
|
||||
std::shared_ptr<yati::source::Base> m_source;
|
||||
std::unique_ptr<DecyptedData> m_decryptor{};
|
||||
u8 m_key[0x10]{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::nca
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "fs.hpp"
|
||||
#include "yati/source/base.hpp"
|
||||
|
||||
#include <switch.h>
|
||||
#include <vector>
|
||||
@@ -82,4 +83,16 @@ static constexpr inline bool HasRequiredSystemVersion(const NcmContentMetaKey *k
|
||||
// fills program id and out path of the control nca.
|
||||
Result GetFsPathFromContentId(NcmContentStorage* cs, const NcmContentMetaKey& key, const NcmContentId& id, u64* out_program_id, fs::FsPath* out_path);
|
||||
|
||||
// helper for reading nca from ncm.
|
||||
struct NcmSource final : yati::source::Base {
|
||||
NcmSource(NcmContentStorage* cs, const NcmContentId* id);
|
||||
Result Read(void* buf, s64 off, s64 size, u64* bytes_read) override;
|
||||
Result GetSize(s64* size);
|
||||
|
||||
private:
|
||||
NcmContentStorage m_cs;
|
||||
NcmContentId m_id;
|
||||
s64 m_size{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ncm
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "yati/source/base.hpp"
|
||||
#include "utils/lru.hpp"
|
||||
#include "defines.hpp"
|
||||
|
||||
#include <switch.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <zstd.h>
|
||||
|
||||
namespace sphaira::ncz {
|
||||
|
||||
@@ -8,7 +15,11 @@ namespace sphaira::ncz {
|
||||
// todo: byteswap this
|
||||
#define NCZ_BLOCK_MAGIC std::byteswap(0x4E435A424C4F434BUL)
|
||||
|
||||
#define NCZ_SECTION_OFFSET (0x4000 + sizeof(ncz::Header))
|
||||
#define NCZ_BLOCK_VERSION (2)
|
||||
#define NCZ_BLOCK_TYPE (1)
|
||||
|
||||
#define NCZ_NORMAL_SIZE (0x4000)
|
||||
#define NCZ_SECTION_OFFSET (NCZ_NORMAL_SIZE + sizeof(ncz::Header))
|
||||
|
||||
struct Header {
|
||||
u64 magic; // NCZ_SECTION_MAGIC
|
||||
@@ -23,11 +34,21 @@ struct BlockHeader {
|
||||
u8 block_size_exponent;
|
||||
u32 total_blocks;
|
||||
u64 decompressed_size;
|
||||
|
||||
Result IsValid() const {
|
||||
R_UNLESS(magic == NCZ_BLOCK_MAGIC, 9);
|
||||
R_UNLESS(version == NCZ_BLOCK_VERSION, Result_YatiInvalidNczBlockVersion);
|
||||
R_UNLESS(type == NCZ_BLOCK_TYPE, Result_YatiInvalidNczBlockType);
|
||||
R_UNLESS(total_blocks, Result_YatiInvalidNczBlockTotal);
|
||||
R_UNLESS(block_size_exponent >= 14 && block_size_exponent <= 32, Result_YatiInvalidNczBlockSizeExponent);
|
||||
R_SUCCEED();
|
||||
}
|
||||
};
|
||||
|
||||
struct Block {
|
||||
u32 size;
|
||||
};
|
||||
using Blocks = std::vector<Block>;
|
||||
|
||||
struct BlockInfo {
|
||||
u64 offset; // compressed offset.
|
||||
@@ -50,5 +71,39 @@ struct Section {
|
||||
return off < offset + size && off >= offset;
|
||||
}
|
||||
};
|
||||
using Sections = std::vector<Section>;
|
||||
|
||||
struct NczBlockReader final : yati::source::Base {
|
||||
explicit NczBlockReader(const Header& header, const Sections& sections, const BlockHeader& block_header, const Blocks& blocks, u64 offset, const std::shared_ptr<yati::source::Base>& source);
|
||||
Result Read(void *_buf, s64 off, s64 size, u64* bytes_read) override;
|
||||
|
||||
private:
|
||||
struct LruData {
|
||||
s64 offset{};
|
||||
std::vector<u8> data{};
|
||||
|
||||
auto InRange(u64 off) const -> bool {
|
||||
return off < offset + data.size() && off >= offset;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
Result ReadInternal(void *_buf, s64 off, s64 size, u64* bytes_read, bool decrypt);
|
||||
|
||||
private:
|
||||
const Header m_header;
|
||||
const Sections m_sections;
|
||||
const BlockHeader m_block_header;
|
||||
const Blocks m_blocks;
|
||||
const u64 m_block_offset;
|
||||
std::shared_ptr<yati::source::Base> m_source;
|
||||
|
||||
u32 m_block_size{};
|
||||
std::vector<BlockInfo> m_block_infos{};
|
||||
|
||||
// lru cache of blocks
|
||||
std::vector<LruData> m_lru_data{};
|
||||
utils::Lru<LruData> m_lru{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ncz
|
||||
|
||||
@@ -10,6 +10,11 @@ struct Base {
|
||||
// virtual Result Read(void* buf, s64 off, s64 size, u64* bytes_read) = 0;
|
||||
virtual Result Read(void* buf, s64 off, s64 size, u64* bytes_read) = 0;
|
||||
|
||||
Result Read2(void* buf, s64 off, s64 size) {
|
||||
u64 bytes_read;
|
||||
return Read(buf, off, size, &bytes_read);
|
||||
}
|
||||
|
||||
virtual bool IsStream() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,42 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "usb/usbds.hpp"
|
||||
#include "usb/usb_installer.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::yati::source {
|
||||
|
||||
struct Usb final : Base {
|
||||
Usb(u64 transfer_timeout);
|
||||
~Usb();
|
||||
Usb(u64 transfer_timeout) {
|
||||
m_usb = std::make_unique<usb::install::Usb>(transfer_timeout);
|
||||
}
|
||||
|
||||
bool IsStream() const override;
|
||||
Result Read(void* buf, s64 off, s64 size, u64* bytes_read) override;
|
||||
Result Finished(u64 timeout);
|
||||
void SignalCancel() override {
|
||||
m_usb->SignalCancel();
|
||||
}
|
||||
|
||||
bool IsStream() const override {
|
||||
return m_usb->GetFlags() & usb::api::FLAG_STREAM;
|
||||
}
|
||||
|
||||
Result Read(void* buf, s64 off, s64 size, u64* bytes_read) override {
|
||||
return m_usb->Read(buf, off, size, bytes_read);
|
||||
}
|
||||
|
||||
Result IsUsbConnected(u64 timeout) {
|
||||
return m_usb->IsUsbConnected(timeout);
|
||||
}
|
||||
|
||||
Result WaitForConnection(u64 timeout, std::vector<std::string>& out_names);
|
||||
void SetFileNameForTranfser(const std::string& name);
|
||||
Result WaitForConnection(u64 timeout, std::vector<std::string>& names) {
|
||||
return m_usb->WaitForConnection(timeout, names);
|
||||
}
|
||||
|
||||
void SignalCancel() override {
|
||||
m_usb->Cancel();
|
||||
Result OpenFile(u32 index, s64& file_size) {
|
||||
return m_usb->OpenFile(index, file_size);
|
||||
}
|
||||
|
||||
Result CloseFile() {
|
||||
return m_usb->CloseFile();
|
||||
}
|
||||
|
||||
private:
|
||||
Result SendCmdHeader(u32 cmdId, size_t dataSize, u64 timeout);
|
||||
Result SendFileRangeCmd(u64 offset, u64 size, u64 timeout);
|
||||
|
||||
private:
|
||||
std::unique_ptr<usb::UsbDs> m_usb;
|
||||
std::string m_transfer_file_name{};
|
||||
u8 m_flags{};
|
||||
std::unique_ptr<usb::install::Usb> m_usb{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::yati::source
|
||||
|
||||
Reference in New Issue
Block a user