fs: add support for mounting nsp an xci files in the filebrowser.
This commit is contained in:
@@ -21,6 +21,7 @@ set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
# enable LTO (only in release builds)
|
||||
if (LTO)
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
|
||||
include(CheckIPOSupported)
|
||||
check_ipo_supported(RESULT ipo_supported OUTPUT ipo_error)
|
||||
@@ -35,6 +36,7 @@ if (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel
|
||||
else()
|
||||
message(STATUS "IPO / LTO not enabled in debug build")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
function(dkp_fatal_if_not_found var package)
|
||||
if (DEFINED ${var}_NOT_FOUND OR DEFINED ${var}-NOTFOUND)
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
"inherits":["core"],
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "MinSizeRel",
|
||||
"ENABLE_NETWORK_INSTALL": false
|
||||
"ENABLE_NETWORK_INSTALL": false,
|
||||
"LTO": true
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -27,14 +28,19 @@
|
||||
"inherits":["core"],
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "MinSizeRel",
|
||||
"ENABLE_NETWORK_INSTALL": true
|
||||
"ENABLE_NETWORK_INSTALL": true,
|
||||
"LTO": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Debug",
|
||||
"displayName": "Debug",
|
||||
"name": "Dev",
|
||||
"displayName": "Dev",
|
||||
"inherits":["core"],
|
||||
"cacheVariables": { "CMAKE_BUILD_TYPE":"Debug" }
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "MinSizeRel",
|
||||
"ENABLE_NETWORK_INSTALL": true,
|
||||
"LTO": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
@@ -49,8 +55,8 @@
|
||||
"jobs": 16
|
||||
},
|
||||
{
|
||||
"name": "Debug",
|
||||
"configurePreset": "Debug",
|
||||
"name": "Dev",
|
||||
"configurePreset": "Dev",
|
||||
"jobs": 16
|
||||
}
|
||||
]
|
||||
|
||||
@@ -91,6 +91,8 @@ add_executable(sphaira
|
||||
source/fatfs.cpp
|
||||
|
||||
source/utils/devoptab_save.cpp
|
||||
source/utils/devoptab_nsp.cpp
|
||||
source/utils/devoptab_xci.cpp
|
||||
# todo:
|
||||
# source/utils/devoptab_zip.cpp
|
||||
|
||||
@@ -143,6 +145,8 @@ target_compile_options(sphaira PRIVATE
|
||||
# disabled as it warns for strcat 2 paths together, but it will never
|
||||
# overflow due to fs enforcing a max path len anyway.
|
||||
-Wno-format-truncation
|
||||
# many false positives when LTO is not enabled.
|
||||
-Wno-suggest-final-types
|
||||
|
||||
# the below are taken from my gba emulator, they've served me well ;)
|
||||
-Wformat-overflow=2
|
||||
@@ -159,9 +163,7 @@ target_compile_options(sphaira PRIVATE
|
||||
-Wcast-qual
|
||||
-Wcast-align
|
||||
-Wimplicit-fallthrough=5
|
||||
-Wsuggest-final-types
|
||||
-Wuninitialized
|
||||
-fdiagnostics-all-candidates
|
||||
)
|
||||
|
||||
include(FetchContent)
|
||||
|
||||
@@ -305,6 +305,10 @@ struct FsView final : Widget {
|
||||
void DisplayOptions();
|
||||
void DisplayAdvancedOptions();
|
||||
|
||||
void MountNspFs();
|
||||
void MountXciFs();
|
||||
void MountZipFs();
|
||||
|
||||
// private:
|
||||
Base* m_menu{};
|
||||
ViewSide m_side{};
|
||||
@@ -476,4 +480,22 @@ 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} {
|
||||
|
||||
}
|
||||
|
||||
~FsStdioWrapper() {
|
||||
if (m_on_exit) {
|
||||
m_on_exit();
|
||||
}
|
||||
}
|
||||
|
||||
const OnExit m_on_exit;
|
||||
};
|
||||
|
||||
void MountFsHelper(const std::shared_ptr<fs::Fs>& fs, const fs::FsPath& name);
|
||||
|
||||
} // namespace sphaira::ui::menu::filebrowser
|
||||
|
||||
@@ -10,15 +10,15 @@ Result MountFromSavePath(u64 id, fs::FsPath& out_path);
|
||||
void UnmountSave(u64 id);
|
||||
|
||||
// todo:
|
||||
void MountZip(fs::Fs* fs, const fs::FsPath& mount, fs::FsPath& out_path);
|
||||
Result MountZip(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path);
|
||||
void UmountZip(const fs::FsPath& mount);
|
||||
|
||||
// todo:
|
||||
void MountNsp(fs::Fs* fs, const fs::FsPath& mount, fs::FsPath& out_path);
|
||||
Result MountNsp(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path);
|
||||
void UmountNsp(const fs::FsPath& mount);
|
||||
|
||||
// todo:
|
||||
void MountXci(fs::Fs* fs, const fs::FsPath& mount, fs::FsPath& out_path);
|
||||
Result MountXci(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path);
|
||||
void UmountXci(const fs::FsPath& mount);
|
||||
|
||||
bool fix_path(const char* str, char* out);
|
||||
|
||||
} // namespace sphaira::devoptab
|
||||
|
||||
@@ -8,8 +8,19 @@
|
||||
namespace sphaira::yati::container {
|
||||
|
||||
struct Xci final : Base {
|
||||
|
||||
struct Partition {
|
||||
// name of the partition.
|
||||
std::string name;
|
||||
// all the collections for this partition, may be empty.
|
||||
Collections collections;
|
||||
};
|
||||
|
||||
using Partitions = std::vector<Partition>;
|
||||
|
||||
using Base::Base;
|
||||
Result GetCollections(Collections& out) override;
|
||||
Result GetPartitions(Partitions& out);
|
||||
};
|
||||
|
||||
} // namespace sphaira::yati::container
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include "ui/error_box.hpp"
|
||||
#include "ui/menus/file_viewer.hpp"
|
||||
|
||||
#include "utils/devoptab.hpp"
|
||||
|
||||
#include "log.hpp"
|
||||
#include "app.hpp"
|
||||
#include "ui/nvg_util.hpp"
|
||||
@@ -90,6 +92,12 @@ constexpr std::string_view IMAGE_EXTENSIONS[] = {
|
||||
constexpr std::string_view INSTALL_EXTENSIONS[] = {
|
||||
"nsp", "xci", "nsz", "xcz",
|
||||
};
|
||||
constexpr std::string_view NSP_EXTENSIONS[] = {
|
||||
"nsp", "nsz",
|
||||
};
|
||||
constexpr std::string_view XCI_EXTENSIONS[] = {
|
||||
"xci", "xcz",
|
||||
};
|
||||
// these are files that are already compressed or encrypted and should
|
||||
// be stored raw in a zip file.
|
||||
constexpr std::string_view COMPRESSED_EXTENSIONS[] = {
|
||||
@@ -678,6 +686,10 @@ void FsView::OnClick() {
|
||||
nro_launch(GetNewPathCurrent());
|
||||
}
|
||||
});
|
||||
} else if (IsExtension(entry.GetExtension(), NSP_EXTENSIONS)) {
|
||||
MountNspFs();
|
||||
} else if (IsExtension(entry.GetExtension(), XCI_EXTENSIONS)) {
|
||||
MountXciFs();
|
||||
} else if (IsExtension(entry.GetExtension(), INSTALL_EXTENSIONS)) {
|
||||
InstallFiles();
|
||||
} else if (IsSd()) {
|
||||
@@ -1950,6 +1962,38 @@ void FsView::DisplayAdvancedOptions() {
|
||||
});
|
||||
}
|
||||
|
||||
void FsView::MountNspFs() {
|
||||
fs::FsPath mount;
|
||||
const auto rc = devoptab::MountNsp(GetFs(), GetNewPathCurrent(), mount);
|
||||
App::PushErrorBox(rc, "Failed to mount NSP."_i18n);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
auto fs = std::make_shared<FsStdioWrapper>(mount, [mount](){
|
||||
devoptab::UmountNsp(mount);
|
||||
});
|
||||
|
||||
MountFsHelper(fs, GetEntry().GetName());
|
||||
}
|
||||
}
|
||||
|
||||
void FsView::MountXciFs() {
|
||||
fs::FsPath mount;
|
||||
const auto rc = devoptab::MountXci(GetFs(), GetNewPathCurrent(), mount);
|
||||
App::PushErrorBox(rc, "Failed to mount XCI."_i18n);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
auto fs = std::make_shared<FsStdioWrapper>(mount, [mount](){
|
||||
devoptab::UmountXci(mount);
|
||||
});
|
||||
|
||||
MountFsHelper(fs, GetEntry().GetName());
|
||||
}
|
||||
}
|
||||
|
||||
void FsView::MountZipFs() {
|
||||
//todo:
|
||||
}
|
||||
|
||||
Base::Base(u32 flags, u32 options)
|
||||
: Base{CreateFs(FS_ENTRY_DEFAULT), FS_ENTRY_DEFAULT, {}, false, flags, options} {
|
||||
}
|
||||
@@ -2299,4 +2343,15 @@ auto Base::CreateFs(const FsEntry& fs_entry) -> std::shared_ptr<fs::Fs> {
|
||||
std::unreachable();
|
||||
}
|
||||
|
||||
void MountFsHelper(const std::shared_ptr<fs::Fs>& fs, const fs::FsPath& name) {
|
||||
const filebrowser::FsEntry fs_entry{
|
||||
.name = name,
|
||||
.root = fs->Root(),
|
||||
.type = filebrowser::FsType::Custom,
|
||||
.flags = filebrowser::FsEntryFlag_ReadOnly,
|
||||
};
|
||||
|
||||
App::Push<filebrowser::Menu>(fs, fs_entry, fs->Root());
|
||||
}
|
||||
|
||||
} // namespace sphaira::ui::menu::filebrowser
|
||||
|
||||
@@ -411,14 +411,7 @@ Result Menu::MountNcaFs() {
|
||||
auto fs = std::make_shared<fs::FsNativeId>(program_id, type, path);
|
||||
R_TRY(fs->GetFsOpenResult());
|
||||
|
||||
const filebrowser::FsEntry fs_entry{
|
||||
.name = "NCA",
|
||||
.root = "/",
|
||||
.type = filebrowser::FsType::Custom,
|
||||
.flags = filebrowser::FsEntryFlag_ReadOnly,
|
||||
};
|
||||
|
||||
App::Push<filebrowser::Menu>(fs, fs_entry, "/");
|
||||
filebrowser::MountFsHelper(fs, hexIdToStr(e.content_id).str);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
|
||||
@@ -523,8 +523,8 @@ struct NroRomFS final : fs::FsStdio {
|
||||
};
|
||||
|
||||
Result Menu::MountRomfsFs() {
|
||||
const char* name = "nro_romfs";
|
||||
const char* root = "nro_romfs:/";
|
||||
static const char* name = "nro_romfs";
|
||||
static const char* root = "nro_romfs:/";
|
||||
const auto& e = GetEntry();
|
||||
|
||||
// todo: add errors for when nro doesn't have romfs.
|
||||
@@ -540,16 +540,11 @@ Result Menu::MountRomfsFs() {
|
||||
R_THROW(rc);
|
||||
}
|
||||
|
||||
auto fs = std::make_shared<NroRomFS>(name, root);
|
||||
auto fs = std::make_shared<filebrowser::FsStdioWrapper>(root, [](){
|
||||
romfsUnmount(name);
|
||||
});
|
||||
|
||||
const filebrowser::FsEntry fs_entry{
|
||||
.name = e.GetName(),
|
||||
.root = root,
|
||||
.type = filebrowser::FsType::Custom,
|
||||
.flags = filebrowser::FsEntryFlag_ReadOnly,
|
||||
};
|
||||
|
||||
App::Push<filebrowser::Menu>(fs, fs_entry, root);
|
||||
filebrowser::MountFsHelper(fs, e.GetName());
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
|
||||
@@ -42,18 +42,6 @@ constexpr const char* NX_SAVE_META_NAME = ".nx_save_meta.bin";
|
||||
|
||||
constinit UEvent g_change_uevent;
|
||||
|
||||
struct SystemSaveFs final : fs::FsStdio {
|
||||
SystemSaveFs(u64 id, const fs::FsPath& root) : FsStdio{true, root}, m_id{id} {
|
||||
|
||||
}
|
||||
|
||||
~SystemSaveFs() {
|
||||
devoptab::UnmountSave(m_id);
|
||||
}
|
||||
|
||||
const u64 m_id;
|
||||
};
|
||||
|
||||
// https://github.com/J-D-K/JKSV/issues/264#issuecomment-2618962807
|
||||
struct NXSaveMeta {
|
||||
u32 magic{}; // NX_SAVE_META_MAGIC
|
||||
@@ -1118,21 +1106,16 @@ Result Menu::BackupSaveInternal(ProgressBox* pbox, const dump::DumpLocation& loc
|
||||
|
||||
Result Menu::MountSaveFs() {
|
||||
const auto& e = m_entries[m_index];
|
||||
fs::FsPath root;
|
||||
|
||||
if (e.system_save_data_id) {
|
||||
fs::FsPath root;
|
||||
R_TRY(devoptab::MountFromSavePath(e.system_save_data_id, root));
|
||||
|
||||
auto fs = std::make_shared<SystemSaveFs>(e.system_save_data_id, root);
|
||||
auto fs = std::make_shared<filebrowser::FsStdioWrapper>(root, [&e](){
|
||||
devoptab::UnmountSave(e.system_save_data_id);
|
||||
});
|
||||
|
||||
const filebrowser::FsEntry fs_entry{
|
||||
.name = e.GetName(),
|
||||
.root = root,
|
||||
.type = filebrowser::FsType::Custom,
|
||||
.flags = filebrowser::FsEntryFlag_ReadOnly,
|
||||
};
|
||||
|
||||
App::Push<filebrowser::Menu>(fs, fs_entry, root);
|
||||
filebrowser::MountFsHelper(fs, e.GetName());
|
||||
} else {
|
||||
const auto save_data_space_id = (FsSaveDataSpaceId)e.save_data_space_id;
|
||||
|
||||
@@ -1146,15 +1129,7 @@ Result Menu::MountSaveFs() {
|
||||
|
||||
auto fs = std::make_shared<fs::FsNativeSave>((FsSaveDataType)e.save_data_type, save_data_space_id, &attr, true);
|
||||
R_TRY(fs->GetFsOpenResult());
|
||||
|
||||
const filebrowser::FsEntry fs_entry{
|
||||
.name = e.GetName(),
|
||||
.root = "/",
|
||||
.type = filebrowser::FsType::Custom,
|
||||
.flags = filebrowser::FsEntryFlag_ReadOnly,
|
||||
};
|
||||
|
||||
App::Push<filebrowser::Menu>(fs, fs_entry, "/");
|
||||
filebrowser::MountFsHelper(fs, e.GetName());
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
|
||||
285
sphaira/source/utils/devoptab_nsp.cpp
Normal file
285
sphaira/source/utils/devoptab_nsp.cpp
Normal file
@@ -0,0 +1,285 @@
|
||||
|
||||
#include "utils/devoptab.hpp"
|
||||
#include "defines.hpp"
|
||||
#include "log.hpp"
|
||||
|
||||
#include "yati/container/nsp.hpp"
|
||||
#include "yati/container/xci.hpp"
|
||||
#include "yati/source/file.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <cerrno>
|
||||
#include <algorithm>
|
||||
#include <sys/iosupport.h>
|
||||
|
||||
namespace sphaira::devoptab {
|
||||
namespace {
|
||||
|
||||
struct Device {
|
||||
std::unique_ptr<yati::source::Base> source;
|
||||
yati::container::Collections collections;
|
||||
};
|
||||
|
||||
struct File {
|
||||
Device* device;
|
||||
const yati::container::CollectionEntry* collection;
|
||||
size_t off;
|
||||
};
|
||||
|
||||
struct Dir {
|
||||
Device* device;
|
||||
u32 index;
|
||||
};
|
||||
|
||||
int set_errno(struct _reent *r, int err) {
|
||||
r->_errno = err;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int devoptab_open(struct _reent *r, void *fileStruct, const char *_path, int flags, int mode) {
|
||||
auto device = (Device*)r->deviceData;
|
||||
auto file = static_cast<File*>(fileStruct);
|
||||
std::memset(file, 0, sizeof(*file));
|
||||
|
||||
char path[FS_MAX_PATH];
|
||||
if (!fix_path(_path, path)) {
|
||||
return set_errno(r, ENOENT);
|
||||
}
|
||||
|
||||
for (const auto& collection : device->collections) {
|
||||
if (path == "/" + collection.name) {
|
||||
file->device = device;
|
||||
file->collection = &collection;
|
||||
return r->_errno = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return set_errno(r, ENOENT);
|
||||
}
|
||||
|
||||
int devoptab_close(struct _reent *r, void *fd) {
|
||||
auto file = static_cast<File*>(fd);
|
||||
std::memset(file, 0, sizeof(*file));
|
||||
|
||||
return r->_errno = 0;
|
||||
}
|
||||
|
||||
ssize_t devoptab_read(struct _reent *r, void *fd, char *ptr, size_t len) {
|
||||
auto file = static_cast<File*>(fd);
|
||||
|
||||
const auto& collection = file->collection;
|
||||
len = std::min(len, collection->size - file->off);
|
||||
|
||||
u64 bytes_read;
|
||||
if (R_FAILED(file->device->source->Read(ptr, collection->offset + file->off, len, &bytes_read))) {
|
||||
return set_errno(r, ENOENT);
|
||||
}
|
||||
|
||||
file->off += bytes_read;
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
off_t devoptab_seek(struct _reent *r, void *fd, off_t pos, int dir) {
|
||||
auto file = static_cast<File*>(fd);
|
||||
const auto& collection = file->collection;
|
||||
|
||||
if (dir == SEEK_CUR) {
|
||||
pos += file->off;
|
||||
} else if (dir == SEEK_END) {
|
||||
pos = collection->size;
|
||||
}
|
||||
|
||||
r->_errno = 0;
|
||||
return file->off = std::clamp<u64>(pos, 0, collection->size);
|
||||
}
|
||||
|
||||
int devoptab_fstat(struct _reent *r, void *fd, struct stat *st) {
|
||||
auto file = static_cast<File*>(fd);
|
||||
const auto& collection = file->collection;
|
||||
|
||||
std::memset(st, 0, sizeof(*st));
|
||||
st->st_nlink = 1;
|
||||
st->st_size = collection->size;
|
||||
st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
|
||||
return r->_errno = 0;
|
||||
}
|
||||
|
||||
DIR_ITER* devoptab_diropen(struct _reent *r, DIR_ITER *dirState, const char *_path) {
|
||||
auto device = (Device*)r->deviceData;
|
||||
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||
std::memset(dir, 0, sizeof(*dir));
|
||||
|
||||
char path[FS_MAX_PATH];
|
||||
if (!fix_path(_path, path)) {
|
||||
set_errno(r, ENOENT);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!std::strcmp(path, "/")) {
|
||||
dir->device = device;
|
||||
} else {
|
||||
set_errno(r, ENOENT);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
r->_errno = 0;
|
||||
return dirState;
|
||||
}
|
||||
|
||||
int devoptab_dirreset(struct _reent *r, DIR_ITER *dirState) {
|
||||
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||
|
||||
dir->index = 0;
|
||||
|
||||
return r->_errno = 0;
|
||||
}
|
||||
|
||||
int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat) {
|
||||
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||
std::memset(filestat, 0, sizeof(*filestat));
|
||||
|
||||
if (dir->index >= dir->device->collections.size()) {
|
||||
return set_errno(r, ENOENT);
|
||||
}
|
||||
|
||||
const auto& collection = dir->device->collections[dir->index];
|
||||
filestat->st_nlink = 1;
|
||||
filestat->st_size = collection.size;
|
||||
filestat->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
|
||||
std::strcpy(filename, collection.name.c_str());
|
||||
|
||||
dir->index++;
|
||||
return r->_errno = 0;
|
||||
}
|
||||
|
||||
int devoptab_dirclose(struct _reent *r, DIR_ITER *dirState) {
|
||||
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||
std::memset(dir, 0, sizeof(*dir));
|
||||
|
||||
return r->_errno = 0;
|
||||
}
|
||||
|
||||
int devoptab_lstat(struct _reent *r, const char *_path, struct stat *st) {
|
||||
auto device = (Device*)r->deviceData;
|
||||
|
||||
log_write("[\t\tDEV] lstat\n");
|
||||
|
||||
char path[FS_MAX_PATH];
|
||||
if (!fix_path(_path, path)) {
|
||||
return set_errno(r, ENOENT);
|
||||
}
|
||||
|
||||
std::memset(st, 0, sizeof(*st));
|
||||
|
||||
if (!std::strcmp(path, "/")) {
|
||||
st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
|
||||
} else {
|
||||
const auto it = std::ranges::find_if(device->collections, [path](auto& e){
|
||||
return path == "/" + e.name;
|
||||
});
|
||||
|
||||
if (it == device->collections.end()) {
|
||||
return set_errno(r, ENOENT);
|
||||
}
|
||||
|
||||
st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
|
||||
st->st_size = it->size;
|
||||
}
|
||||
|
||||
st->st_nlink = 1;
|
||||
|
||||
return r->_errno = 0;
|
||||
}
|
||||
|
||||
constexpr devoptab_t DEVOPTAB = {
|
||||
.structSize = sizeof(File),
|
||||
.open_r = devoptab_open,
|
||||
.close_r = devoptab_close,
|
||||
.read_r = devoptab_read,
|
||||
.seek_r = devoptab_seek,
|
||||
.fstat_r = devoptab_fstat,
|
||||
.stat_r = devoptab_lstat,
|
||||
.dirStateSize = sizeof(Dir),
|
||||
.diropen_r = devoptab_diropen,
|
||||
.dirreset_r = devoptab_dirreset,
|
||||
.dirnext_r = devoptab_dirnext,
|
||||
.dirclose_r = devoptab_dirclose,
|
||||
.lstat_r = devoptab_lstat,
|
||||
};
|
||||
|
||||
struct Entry {
|
||||
Device device;
|
||||
devoptab_t devoptab;
|
||||
fs::FsPath path;
|
||||
fs::FsPath mount;
|
||||
char name[32];
|
||||
s32 ref_count;
|
||||
};
|
||||
|
||||
Mutex g_mutex;
|
||||
std::vector<Entry> g_entries;
|
||||
u32 g_mount_idx;
|
||||
|
||||
} // namespace
|
||||
|
||||
Result MountNsp(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path) {
|
||||
SCOPED_MUTEX(&g_mutex);
|
||||
|
||||
// check if we already have the save mounted.
|
||||
for (auto& e : g_entries) {
|
||||
if (e.path == path) {
|
||||
e.ref_count++;
|
||||
out_path = e.mount;
|
||||
R_SUCCEED();
|
||||
}
|
||||
}
|
||||
|
||||
// create new entry.
|
||||
auto& entry = g_entries.emplace_back();
|
||||
|
||||
auto source = std::make_unique<yati::source::File>(fs, path);
|
||||
// R_TRY(yati::container::Xci(source.get()).ReadAll());
|
||||
yati::container::Nsp nsp{source.get()};
|
||||
R_TRY(nsp.GetCollections(entry.device.collections));
|
||||
|
||||
entry.path = path;
|
||||
entry.devoptab = DEVOPTAB;
|
||||
entry.devoptab.name = entry.name;
|
||||
entry.devoptab.deviceData = &entry.device;
|
||||
entry.device.source = std::move(source);
|
||||
std::snprintf(entry.name, sizeof(entry.name), "nsp_%u", g_mount_idx);
|
||||
std::snprintf(entry.mount, sizeof(entry.mount), "nsp_%u:/", g_mount_idx);
|
||||
|
||||
R_UNLESS(AddDevice(&entry.devoptab) >= 0, 0x1);
|
||||
log_write("[NSP] DEVICE SUCCESS %s %s\n", path.s, entry.name);
|
||||
|
||||
out_path = entry.mount;
|
||||
entry.ref_count++;
|
||||
g_mount_idx++;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void UmountNsp(const fs::FsPath& mount) {
|
||||
SCOPED_MUTEX(&g_mutex);
|
||||
|
||||
auto itr = std::ranges::find_if(g_entries, [&mount](auto& e){
|
||||
return mount == e.mount;
|
||||
});
|
||||
|
||||
if (itr == g_entries.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (itr->ref_count) {
|
||||
itr->ref_count--;
|
||||
}
|
||||
|
||||
if (!itr->ref_count) {
|
||||
RemoveDevice(mount);
|
||||
g_entries.erase(itr);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sphaira::devoptab
|
||||
@@ -39,47 +39,7 @@ struct Dir {
|
||||
u32 next_file;
|
||||
};
|
||||
|
||||
bool fix_path(const char* str, char* out) {
|
||||
// log_write("[SAVE] got path: %s\n", str);
|
||||
|
||||
str = std::strrchr(str, ':');
|
||||
if (!str) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// skip over ':'
|
||||
str++;
|
||||
size_t len = 0;
|
||||
|
||||
for (size_t i = 0; str[i]; i++) {
|
||||
// skip multiple slashes.
|
||||
if (i && str[i] == '/' && str[i - 1] == '/') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// add leading slash.
|
||||
if (!i && str[i] != '/') {
|
||||
out[len++] = '/';
|
||||
}
|
||||
|
||||
// save single char.
|
||||
out[len++] = str[i];
|
||||
}
|
||||
|
||||
// root path uses ""
|
||||
if (len == 1 && out[0] == '/') {
|
||||
// out[0] = '\0';
|
||||
}
|
||||
|
||||
// null the end.
|
||||
out[len] = '\0';
|
||||
|
||||
// log_write("[SAVE] end path: %s\n", out);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int set_errno(struct _reent *r, int err) {
|
||||
int set_errno(struct _reent *r, int err) {
|
||||
r->_errno = err;
|
||||
return -1;
|
||||
}
|
||||
@@ -242,8 +202,6 @@ int devoptab_dirclose(struct _reent *r, DIR_ITER *dirState) {
|
||||
int devoptab_lstat(struct _reent *r, const char *_path, struct stat *st) {
|
||||
auto device = (Device*)r->deviceData;
|
||||
|
||||
log_write("[\t\tDEV] lstat\n");
|
||||
|
||||
char path[FS_MAX_PATH];
|
||||
if (!fix_path(_path, path)) {
|
||||
return set_errno(r, ENOENT);
|
||||
@@ -300,6 +258,47 @@ void MakeMountPath(u64 id, fs::FsPath& out_path) {
|
||||
|
||||
} // namespace
|
||||
|
||||
bool fix_path(const char* str, char* out) {
|
||||
// log_write("[SAVE] got path: %s\n", str);
|
||||
|
||||
str = std::strrchr(str, ':');
|
||||
if (!str) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// skip over ':'
|
||||
str++;
|
||||
size_t len = 0;
|
||||
|
||||
// todo: hanle utf8 paths.
|
||||
for (size_t i = 0; str[i]; i++) {
|
||||
// skip multiple slashes.
|
||||
if (i && str[i] == '/' && str[i - 1] == '/') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// add leading slash.
|
||||
if (!i && str[i] != '/') {
|
||||
out[len++] = '/';
|
||||
}
|
||||
|
||||
// save single char.
|
||||
out[len++] = str[i];
|
||||
}
|
||||
|
||||
// skip trailing slash.
|
||||
if (len > 1 && out[len - 1] == '/') {
|
||||
out[len - 1] = '\0';
|
||||
}
|
||||
|
||||
// null the end.
|
||||
out[len] = '\0';
|
||||
|
||||
// log_write("[SAVE] end path: %s\n", out);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Result MountFromSavePath(u64 id, fs::FsPath& out_path) {
|
||||
SCOPED_MUTEX(&g_mutex);
|
||||
|
||||
|
||||
304
sphaira/source/utils/devoptab_xci.cpp
Normal file
304
sphaira/source/utils/devoptab_xci.cpp
Normal file
@@ -0,0 +1,304 @@
|
||||
|
||||
#include "utils/devoptab.hpp"
|
||||
#include "defines.hpp"
|
||||
#include "log.hpp"
|
||||
|
||||
#include "yati/container/xci.hpp"
|
||||
#include "yati/source/file.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <cerrno>
|
||||
#include <algorithm>
|
||||
#include <sys/iosupport.h>
|
||||
|
||||
namespace sphaira::devoptab {
|
||||
namespace {
|
||||
|
||||
struct Device {
|
||||
std::unique_ptr<yati::source::Base> source;
|
||||
yati::container::Xci::Partitions partitions;
|
||||
};
|
||||
|
||||
struct File {
|
||||
Device* device;
|
||||
const yati::container::CollectionEntry* collection;
|
||||
size_t off;
|
||||
};
|
||||
|
||||
struct Dir {
|
||||
Device* device;
|
||||
const yati::container::Collections* collections;
|
||||
u32 index;
|
||||
};
|
||||
|
||||
int set_errno(struct _reent *r, int err) {
|
||||
r->_errno = err;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int devoptab_open(struct _reent *r, void *fileStruct, const char *_path, int flags, int mode) {
|
||||
auto device = (Device*)r->deviceData;
|
||||
auto file = static_cast<File*>(fileStruct);
|
||||
std::memset(file, 0, sizeof(*file));
|
||||
|
||||
char path[FS_MAX_PATH];
|
||||
if (!fix_path(_path, path)) {
|
||||
return set_errno(r, ENOENT);
|
||||
}
|
||||
|
||||
for (const auto& partition : device->partitions) {
|
||||
for (const auto& collection : partition.collections) {
|
||||
if (path == "/" + partition.name + "/" + collection.name) {
|
||||
file->device = device;
|
||||
file->collection = &collection;
|
||||
return r->_errno = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return set_errno(r, ENOENT);
|
||||
}
|
||||
|
||||
int devoptab_close(struct _reent *r, void *fd) {
|
||||
auto file = static_cast<File*>(fd);
|
||||
std::memset(file, 0, sizeof(*file));
|
||||
|
||||
return r->_errno = 0;
|
||||
}
|
||||
|
||||
ssize_t devoptab_read(struct _reent *r, void *fd, char *ptr, size_t len) {
|
||||
auto file = static_cast<File*>(fd);
|
||||
|
||||
const auto& collection = file->collection;
|
||||
len = std::min(len, collection->size - file->off);
|
||||
|
||||
u64 bytes_read;
|
||||
if (R_FAILED(file->device->source->Read(ptr, collection->offset + file->off, len, &bytes_read))) {
|
||||
return set_errno(r, ENOENT);
|
||||
}
|
||||
|
||||
file->off += bytes_read;
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
off_t devoptab_seek(struct _reent *r, void *fd, off_t pos, int dir) {
|
||||
auto file = static_cast<File*>(fd);
|
||||
const auto& collection = file->collection;
|
||||
|
||||
if (dir == SEEK_CUR) {
|
||||
pos += file->off;
|
||||
} else if (dir == SEEK_END) {
|
||||
pos = collection->size;
|
||||
}
|
||||
|
||||
r->_errno = 0;
|
||||
return file->off = std::clamp<u64>(pos, 0, collection->size);
|
||||
}
|
||||
|
||||
int devoptab_fstat(struct _reent *r, void *fd, struct stat *st) {
|
||||
auto file = static_cast<File*>(fd);
|
||||
const auto& collection = file->collection;
|
||||
|
||||
std::memset(st, 0, sizeof(*st));
|
||||
st->st_nlink = 1;
|
||||
st->st_size = collection->size;
|
||||
st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
|
||||
return r->_errno = 0;
|
||||
}
|
||||
|
||||
DIR_ITER* devoptab_diropen(struct _reent *r, DIR_ITER *dirState, const char *_path) {
|
||||
auto device = (Device*)r->deviceData;
|
||||
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||
std::memset(dir, 0, sizeof(*dir));
|
||||
|
||||
char path[FS_MAX_PATH];
|
||||
if (!fix_path(_path, path)) {
|
||||
set_errno(r, ENOENT);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!std::strcmp(path, "/")) {
|
||||
dir->device = device;
|
||||
r->_errno = 0;
|
||||
return dirState;
|
||||
} else {
|
||||
for (const auto& partition : device->partitions) {
|
||||
if (path == "/" + partition.name) {
|
||||
dir->collections = &partition.collections;
|
||||
r->_errno = 0;
|
||||
return dirState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set_errno(r, ENOENT);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int devoptab_dirreset(struct _reent *r, DIR_ITER *dirState) {
|
||||
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||
|
||||
dir->index = 0;
|
||||
|
||||
return r->_errno = 0;
|
||||
}
|
||||
|
||||
int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat) {
|
||||
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||
std::memset(filestat, 0, sizeof(*filestat));
|
||||
|
||||
if (!dir->collections) {
|
||||
if (dir->index >= dir->device->partitions.size()) {
|
||||
return set_errno(r, ENOENT);
|
||||
}
|
||||
|
||||
filestat->st_nlink = 1;
|
||||
filestat->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
|
||||
std::strcpy(filename, dir->device->partitions[dir->index].name.c_str());
|
||||
} else {
|
||||
if (dir->index >= dir->collections->size()) {
|
||||
return set_errno(r, ENOENT);
|
||||
}
|
||||
|
||||
const auto& collection = (*dir->collections)[dir->index];
|
||||
filestat->st_nlink = 1;
|
||||
filestat->st_size = collection.size;
|
||||
filestat->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
|
||||
std::strcpy(filename, collection.name.c_str());
|
||||
}
|
||||
|
||||
dir->index++;
|
||||
return r->_errno = 0;
|
||||
}
|
||||
|
||||
int devoptab_dirclose(struct _reent *r, DIR_ITER *dirState) {
|
||||
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||
std::memset(dir, 0, sizeof(*dir));
|
||||
|
||||
return r->_errno = 0;
|
||||
}
|
||||
|
||||
int devoptab_lstat(struct _reent *r, const char *_path, struct stat *st) {
|
||||
auto device = (Device*)r->deviceData;
|
||||
|
||||
char path[FS_MAX_PATH];
|
||||
if (!fix_path(_path, path)) {
|
||||
return set_errno(r, ENOENT);
|
||||
}
|
||||
|
||||
std::memset(st, 0, sizeof(*st));
|
||||
st->st_nlink = 1;
|
||||
|
||||
if (!std::strcmp(path, "/")) {
|
||||
st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
|
||||
} else {
|
||||
for (const auto& partition : device->partitions) {
|
||||
if (path == "/" + partition.name) {
|
||||
st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
|
||||
return r->_errno = 0;
|
||||
}
|
||||
|
||||
for (const auto& collection : partition.collections) {
|
||||
if (path == "/" + partition.name + "/" + collection.name) {
|
||||
st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
|
||||
st->st_size = collection.size;
|
||||
return r->_errno = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return set_errno(r, ENOENT);
|
||||
}
|
||||
|
||||
constexpr devoptab_t DEVOPTAB = {
|
||||
.structSize = sizeof(File),
|
||||
.open_r = devoptab_open,
|
||||
.close_r = devoptab_close,
|
||||
.read_r = devoptab_read,
|
||||
.seek_r = devoptab_seek,
|
||||
.fstat_r = devoptab_fstat,
|
||||
.stat_r = devoptab_lstat,
|
||||
.dirStateSize = sizeof(Dir),
|
||||
.diropen_r = devoptab_diropen,
|
||||
.dirreset_r = devoptab_dirreset,
|
||||
.dirnext_r = devoptab_dirnext,
|
||||
.dirclose_r = devoptab_dirclose,
|
||||
.lstat_r = devoptab_lstat,
|
||||
};
|
||||
|
||||
struct Entry {
|
||||
Device device;
|
||||
devoptab_t devoptab;
|
||||
fs::FsPath path;
|
||||
fs::FsPath mount;
|
||||
char name[32];
|
||||
s32 ref_count;
|
||||
};
|
||||
|
||||
Mutex g_mutex;
|
||||
std::vector<Entry> g_entries;
|
||||
u32 g_mount_idx;
|
||||
|
||||
} // namespace
|
||||
|
||||
Result MountXci(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path) {
|
||||
SCOPED_MUTEX(&g_mutex);
|
||||
|
||||
// check if we already have the save mounted.
|
||||
for (auto& e : g_entries) {
|
||||
if (e.path == path) {
|
||||
e.ref_count++;
|
||||
out_path = e.mount;
|
||||
R_SUCCEED();
|
||||
}
|
||||
}
|
||||
|
||||
// create new entry.
|
||||
auto& entry = g_entries.emplace_back();
|
||||
|
||||
auto source = std::make_unique<yati::source::File>(fs, path);
|
||||
yati::container::Xci xci{source.get()};
|
||||
R_TRY(xci.GetPartitions(entry.device.partitions));
|
||||
|
||||
entry.path = path;
|
||||
entry.devoptab = DEVOPTAB;
|
||||
entry.devoptab.name = entry.name;
|
||||
entry.devoptab.deviceData = &entry.device;
|
||||
entry.device.source = std::move(source);
|
||||
std::snprintf(entry.name, sizeof(entry.name), "xci_%u", g_mount_idx);
|
||||
std::snprintf(entry.mount, sizeof(entry.mount), "xci_%u:/", g_mount_idx);
|
||||
|
||||
R_UNLESS(AddDevice(&entry.devoptab) >= 0, 0x1);
|
||||
log_write("[XCI] DEVICE SUCCESS %s %s\n", path.s, entry.name);
|
||||
|
||||
out_path = entry.mount;
|
||||
entry.ref_count++;
|
||||
g_mount_idx++;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void UmountXci(const fs::FsPath& mount) {
|
||||
SCOPED_MUTEX(&g_mutex);
|
||||
|
||||
auto itr = std::ranges::find_if(g_entries, [&mount](auto& e){
|
||||
return mount == e.mount;
|
||||
});
|
||||
|
||||
if (itr == g_entries.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (itr->ref_count) {
|
||||
itr->ref_count--;
|
||||
}
|
||||
|
||||
if (!itr->ref_count) {
|
||||
RemoveDevice(mount);
|
||||
g_entries.erase(itr);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sphaira::devoptab
|
||||
@@ -58,8 +58,40 @@ Result Hfs0GetPartition(source::Base* source, s64 off, Hfs0& out) {
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ReadPartitionIntoCollection(source::Base* source, const Hfs0& root, u32 index, Collections& out) {
|
||||
log_write("[XCI] fetching %s partition\n", root.string_table[index].c_str());
|
||||
|
||||
Hfs0 hfs0{};
|
||||
R_TRY(Hfs0GetPartition(source, root.data_offset + root.file_table[index].data_offset, hfs0));
|
||||
log_write("[XCI] got %s partition\n", root.string_table[index].c_str());
|
||||
|
||||
for (u32 i = 0; i < hfs0.header.total_files; i++) {
|
||||
CollectionEntry entry;
|
||||
entry.name = hfs0.string_table[i];
|
||||
entry.offset = hfs0.data_offset + hfs0.file_table[i].data_offset;
|
||||
entry.size = hfs0.file_table[i].data_size;
|
||||
out.emplace_back(entry);
|
||||
}
|
||||
|
||||
log_write("[XCI] read %s partition count: %zu\n", root.string_table[index].c_str(), out.size());
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Result Xci::GetPartitions(Partitions& out) {
|
||||
Hfs0 root{};
|
||||
R_TRY(Hfs0GetPartition(m_source, HFS0_HEADER_OFFSET, root));
|
||||
|
||||
for (u32 i = 0; i < root.header.total_files; i++) {
|
||||
Partition partition{root.string_table[i]};
|
||||
R_TRY(ReadPartitionIntoCollection(m_source, root, i, partition.collections));
|
||||
out.emplace_back(partition);
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result Xci::GetCollections(Collections& out) {
|
||||
Hfs0 root{};
|
||||
R_TRY(Hfs0GetPartition(m_source, HFS0_HEADER_OFFSET, root));
|
||||
@@ -68,20 +100,7 @@ Result Xci::GetCollections(Collections& out) {
|
||||
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;
|
||||
entry.name = secure.string_table[i];
|
||||
entry.offset = secure.data_offset + secure.file_table[i].data_offset;
|
||||
entry.size = secure.file_table[i].data_size;
|
||||
out.emplace_back(entry);
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
return ReadPartitionIntoCollection(m_source, root, i, out);
|
||||
} else {
|
||||
log_write("[XCI] skipping partition %u | %s\n", i, root.string_table[i].c_str());
|
||||
}
|
||||
|
||||
@@ -646,7 +646,7 @@ Result Yati::decompressFuncInternal(ThreadData* t) {
|
||||
|
||||
if ((config.convert_to_standard_crypto && ticket) || config.lower_master_key) {
|
||||
t->nca->modified = true;
|
||||
u8 keak_generation;
|
||||
u8 keak_generation = 0;
|
||||
|
||||
if (ticket) {
|
||||
const auto key_gen = header.key_gen;
|
||||
@@ -673,10 +673,6 @@ Result Yati::decompressFuncInternal(ThreadData* t) {
|
||||
R_TRY(nca::DecryptKeak(keys, header));
|
||||
}
|
||||
|
||||
if (config.lower_master_key) {
|
||||
keak_generation = 0;
|
||||
}
|
||||
|
||||
R_TRY(nca::EncryptKeak(keys, header, keak_generation));
|
||||
std::memset(&header.rights_id, 0, sizeof(header.rights_id));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user