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)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
# enable LTO (only in release builds)
|
# enable LTO (only in release builds)
|
||||||
|
if (LTO)
|
||||||
if (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
|
if (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
|
||||||
include(CheckIPOSupported)
|
include(CheckIPOSupported)
|
||||||
check_ipo_supported(RESULT ipo_supported OUTPUT ipo_error)
|
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()
|
else()
|
||||||
message(STATUS "IPO / LTO not enabled in debug build")
|
message(STATUS "IPO / LTO not enabled in debug build")
|
||||||
endif()
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
function(dkp_fatal_if_not_found var package)
|
function(dkp_fatal_if_not_found var package)
|
||||||
if (DEFINED ${var}_NOT_FOUND OR DEFINED ${var}-NOTFOUND)
|
if (DEFINED ${var}_NOT_FOUND OR DEFINED ${var}-NOTFOUND)
|
||||||
|
|||||||
@@ -18,7 +18,8 @@
|
|||||||
"inherits":["core"],
|
"inherits":["core"],
|
||||||
"cacheVariables": {
|
"cacheVariables": {
|
||||||
"CMAKE_BUILD_TYPE": "MinSizeRel",
|
"CMAKE_BUILD_TYPE": "MinSizeRel",
|
||||||
"ENABLE_NETWORK_INSTALL": false
|
"ENABLE_NETWORK_INSTALL": false,
|
||||||
|
"LTO": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -27,14 +28,19 @@
|
|||||||
"inherits":["core"],
|
"inherits":["core"],
|
||||||
"cacheVariables": {
|
"cacheVariables": {
|
||||||
"CMAKE_BUILD_TYPE": "MinSizeRel",
|
"CMAKE_BUILD_TYPE": "MinSizeRel",
|
||||||
"ENABLE_NETWORK_INSTALL": true
|
"ENABLE_NETWORK_INSTALL": true,
|
||||||
|
"LTO": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Debug",
|
"name": "Dev",
|
||||||
"displayName": "Debug",
|
"displayName": "Dev",
|
||||||
"inherits":["core"],
|
"inherits":["core"],
|
||||||
"cacheVariables": { "CMAKE_BUILD_TYPE":"Debug" }
|
"cacheVariables": {
|
||||||
|
"CMAKE_BUILD_TYPE": "MinSizeRel",
|
||||||
|
"ENABLE_NETWORK_INSTALL": true,
|
||||||
|
"LTO": false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"buildPresets": [
|
"buildPresets": [
|
||||||
@@ -49,8 +55,8 @@
|
|||||||
"jobs": 16
|
"jobs": 16
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Debug",
|
"name": "Dev",
|
||||||
"configurePreset": "Debug",
|
"configurePreset": "Dev",
|
||||||
"jobs": 16
|
"jobs": 16
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -91,6 +91,8 @@ add_executable(sphaira
|
|||||||
source/fatfs.cpp
|
source/fatfs.cpp
|
||||||
|
|
||||||
source/utils/devoptab_save.cpp
|
source/utils/devoptab_save.cpp
|
||||||
|
source/utils/devoptab_nsp.cpp
|
||||||
|
source/utils/devoptab_xci.cpp
|
||||||
# todo:
|
# todo:
|
||||||
# source/utils/devoptab_zip.cpp
|
# 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
|
# disabled as it warns for strcat 2 paths together, but it will never
|
||||||
# overflow due to fs enforcing a max path len anyway.
|
# overflow due to fs enforcing a max path len anyway.
|
||||||
-Wno-format-truncation
|
-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 ;)
|
# the below are taken from my gba emulator, they've served me well ;)
|
||||||
-Wformat-overflow=2
|
-Wformat-overflow=2
|
||||||
@@ -159,9 +163,7 @@ target_compile_options(sphaira PRIVATE
|
|||||||
-Wcast-qual
|
-Wcast-qual
|
||||||
-Wcast-align
|
-Wcast-align
|
||||||
-Wimplicit-fallthrough=5
|
-Wimplicit-fallthrough=5
|
||||||
-Wsuggest-final-types
|
|
||||||
-Wuninitialized
|
-Wuninitialized
|
||||||
-fdiagnostics-all-candidates
|
|
||||||
)
|
)
|
||||||
|
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
|
|||||||
@@ -305,6 +305,10 @@ struct FsView final : Widget {
|
|||||||
void DisplayOptions();
|
void DisplayOptions();
|
||||||
void DisplayAdvancedOptions();
|
void DisplayAdvancedOptions();
|
||||||
|
|
||||||
|
void MountNspFs();
|
||||||
|
void MountXciFs();
|
||||||
|
void MountZipFs();
|
||||||
|
|
||||||
// private:
|
// private:
|
||||||
Base* m_menu{};
|
Base* m_menu{};
|
||||||
ViewSide m_side{};
|
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 ext1, std::string_view ext2) -> bool;
|
||||||
auto IsExtension(std::string_view ext, std::span<const std::string_view> list) -> 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
|
} // namespace sphaira::ui::menu::filebrowser
|
||||||
|
|||||||
@@ -10,15 +10,15 @@ Result MountFromSavePath(u64 id, fs::FsPath& out_path);
|
|||||||
void UnmountSave(u64 id);
|
void UnmountSave(u64 id);
|
||||||
|
|
||||||
// todo:
|
// 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);
|
void UmountZip(const fs::FsPath& mount);
|
||||||
|
|
||||||
// todo:
|
Result MountNsp(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path);
|
||||||
void MountNsp(fs::Fs* fs, const fs::FsPath& mount, fs::FsPath& out_path);
|
|
||||||
void UmountNsp(const fs::FsPath& mount);
|
void UmountNsp(const fs::FsPath& mount);
|
||||||
|
|
||||||
// todo:
|
Result MountXci(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path);
|
||||||
void MountXci(fs::Fs* fs, const fs::FsPath& mount, fs::FsPath& out_path);
|
|
||||||
void UmountXci(const fs::FsPath& mount);
|
void UmountXci(const fs::FsPath& mount);
|
||||||
|
|
||||||
|
bool fix_path(const char* str, char* out);
|
||||||
|
|
||||||
} // namespace sphaira::devoptab
|
} // namespace sphaira::devoptab
|
||||||
|
|||||||
@@ -8,8 +8,19 @@
|
|||||||
namespace sphaira::yati::container {
|
namespace sphaira::yati::container {
|
||||||
|
|
||||||
struct Xci final : Base {
|
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;
|
using Base::Base;
|
||||||
Result GetCollections(Collections& out) override;
|
Result GetCollections(Collections& out) override;
|
||||||
|
Result GetPartitions(Partitions& out);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace sphaira::yati::container
|
} // namespace sphaira::yati::container
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
#include "ui/error_box.hpp"
|
#include "ui/error_box.hpp"
|
||||||
#include "ui/menus/file_viewer.hpp"
|
#include "ui/menus/file_viewer.hpp"
|
||||||
|
|
||||||
|
#include "utils/devoptab.hpp"
|
||||||
|
|
||||||
#include "log.hpp"
|
#include "log.hpp"
|
||||||
#include "app.hpp"
|
#include "app.hpp"
|
||||||
#include "ui/nvg_util.hpp"
|
#include "ui/nvg_util.hpp"
|
||||||
@@ -90,6 +92,12 @@ constexpr std::string_view IMAGE_EXTENSIONS[] = {
|
|||||||
constexpr std::string_view INSTALL_EXTENSIONS[] = {
|
constexpr std::string_view INSTALL_EXTENSIONS[] = {
|
||||||
"nsp", "xci", "nsz", "xcz",
|
"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
|
// these are files that are already compressed or encrypted and should
|
||||||
// be stored raw in a zip file.
|
// be stored raw in a zip file.
|
||||||
constexpr std::string_view COMPRESSED_EXTENSIONS[] = {
|
constexpr std::string_view COMPRESSED_EXTENSIONS[] = {
|
||||||
@@ -678,6 +686,10 @@ void FsView::OnClick() {
|
|||||||
nro_launch(GetNewPathCurrent());
|
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)) {
|
} else if (IsExtension(entry.GetExtension(), INSTALL_EXTENSIONS)) {
|
||||||
InstallFiles();
|
InstallFiles();
|
||||||
} else if (IsSd()) {
|
} 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::Base(u32 flags, u32 options)
|
||||||
: Base{CreateFs(FS_ENTRY_DEFAULT), FS_ENTRY_DEFAULT, {}, false, flags, 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();
|
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
|
} // namespace sphaira::ui::menu::filebrowser
|
||||||
|
|||||||
@@ -411,14 +411,7 @@ Result Menu::MountNcaFs() {
|
|||||||
auto fs = std::make_shared<fs::FsNativeId>(program_id, type, path);
|
auto fs = std::make_shared<fs::FsNativeId>(program_id, type, path);
|
||||||
R_TRY(fs->GetFsOpenResult());
|
R_TRY(fs->GetFsOpenResult());
|
||||||
|
|
||||||
const filebrowser::FsEntry fs_entry{
|
filebrowser::MountFsHelper(fs, hexIdToStr(e.content_id).str);
|
||||||
.name = "NCA",
|
|
||||||
.root = "/",
|
|
||||||
.type = filebrowser::FsType::Custom,
|
|
||||||
.flags = filebrowser::FsEntryFlag_ReadOnly,
|
|
||||||
};
|
|
||||||
|
|
||||||
App::Push<filebrowser::Menu>(fs, fs_entry, "/");
|
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -523,8 +523,8 @@ struct NroRomFS final : fs::FsStdio {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Result Menu::MountRomfsFs() {
|
Result Menu::MountRomfsFs() {
|
||||||
const char* name = "nro_romfs";
|
static const char* name = "nro_romfs";
|
||||||
const char* root = "nro_romfs:/";
|
static const char* root = "nro_romfs:/";
|
||||||
const auto& e = GetEntry();
|
const auto& e = GetEntry();
|
||||||
|
|
||||||
// todo: add errors for when nro doesn't have romfs.
|
// todo: add errors for when nro doesn't have romfs.
|
||||||
@@ -540,16 +540,11 @@ Result Menu::MountRomfsFs() {
|
|||||||
R_THROW(rc);
|
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{
|
filebrowser::MountFsHelper(fs, e.GetName());
|
||||||
.name = e.GetName(),
|
|
||||||
.root = root,
|
|
||||||
.type = filebrowser::FsType::Custom,
|
|
||||||
.flags = filebrowser::FsEntryFlag_ReadOnly,
|
|
||||||
};
|
|
||||||
|
|
||||||
App::Push<filebrowser::Menu>(fs, fs_entry, root);
|
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,18 +42,6 @@ constexpr const char* NX_SAVE_META_NAME = ".nx_save_meta.bin";
|
|||||||
|
|
||||||
constinit UEvent g_change_uevent;
|
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
|
// https://github.com/J-D-K/JKSV/issues/264#issuecomment-2618962807
|
||||||
struct NXSaveMeta {
|
struct NXSaveMeta {
|
||||||
u32 magic{}; // NX_SAVE_META_MAGIC
|
u32 magic{}; // NX_SAVE_META_MAGIC
|
||||||
@@ -1118,21 +1106,16 @@ Result Menu::BackupSaveInternal(ProgressBox* pbox, const dump::DumpLocation& loc
|
|||||||
|
|
||||||
Result Menu::MountSaveFs() {
|
Result Menu::MountSaveFs() {
|
||||||
const auto& e = m_entries[m_index];
|
const auto& e = m_entries[m_index];
|
||||||
fs::FsPath root;
|
|
||||||
|
|
||||||
if (e.system_save_data_id) {
|
if (e.system_save_data_id) {
|
||||||
|
fs::FsPath root;
|
||||||
R_TRY(devoptab::MountFromSavePath(e.system_save_data_id, 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{
|
filebrowser::MountFsHelper(fs, e.GetName());
|
||||||
.name = e.GetName(),
|
|
||||||
.root = root,
|
|
||||||
.type = filebrowser::FsType::Custom,
|
|
||||||
.flags = filebrowser::FsEntryFlag_ReadOnly,
|
|
||||||
};
|
|
||||||
|
|
||||||
App::Push<filebrowser::Menu>(fs, fs_entry, root);
|
|
||||||
} else {
|
} else {
|
||||||
const auto save_data_space_id = (FsSaveDataSpaceId)e.save_data_space_id;
|
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);
|
auto fs = std::make_shared<fs::FsNativeSave>((FsSaveDataType)e.save_data_type, save_data_space_id, &attr, true);
|
||||||
R_TRY(fs->GetFsOpenResult());
|
R_TRY(fs->GetFsOpenResult());
|
||||||
|
filebrowser::MountFsHelper(fs, e.GetName());
|
||||||
const filebrowser::FsEntry fs_entry{
|
|
||||||
.name = e.GetName(),
|
|
||||||
.root = "/",
|
|
||||||
.type = filebrowser::FsType::Custom,
|
|
||||||
.flags = filebrowser::FsEntryFlag_ReadOnly,
|
|
||||||
};
|
|
||||||
|
|
||||||
App::Push<filebrowser::Menu>(fs, fs_entry, "/");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
R_SUCCEED();
|
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;
|
u32 next_file;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool fix_path(const char* str, char* out) {
|
int set_errno(struct _reent *r, int err) {
|
||||||
// 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) {
|
|
||||||
r->_errno = err;
|
r->_errno = err;
|
||||||
return -1;
|
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) {
|
int devoptab_lstat(struct _reent *r, const char *_path, struct stat *st) {
|
||||||
auto device = (Device*)r->deviceData;
|
auto device = (Device*)r->deviceData;
|
||||||
|
|
||||||
log_write("[\t\tDEV] lstat\n");
|
|
||||||
|
|
||||||
char path[FS_MAX_PATH];
|
char path[FS_MAX_PATH];
|
||||||
if (!fix_path(_path, path)) {
|
if (!fix_path(_path, path)) {
|
||||||
return set_errno(r, ENOENT);
|
return set_errno(r, ENOENT);
|
||||||
@@ -300,6 +258,47 @@ void MakeMountPath(u64 id, fs::FsPath& out_path) {
|
|||||||
|
|
||||||
} // namespace
|
} // 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) {
|
Result MountFromSavePath(u64 id, fs::FsPath& out_path) {
|
||||||
SCOPED_MUTEX(&g_mutex);
|
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();
|
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
|
} // 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) {
|
Result Xci::GetCollections(Collections& out) {
|
||||||
Hfs0 root{};
|
Hfs0 root{};
|
||||||
R_TRY(Hfs0GetPartition(m_source, HFS0_HEADER_OFFSET, root));
|
R_TRY(Hfs0GetPartition(m_source, HFS0_HEADER_OFFSET, root));
|
||||||
@@ -68,20 +100,7 @@ Result Xci::GetCollections(Collections& out) {
|
|||||||
for (u32 i = 0; i < root.header.total_files; i++) {
|
for (u32 i = 0; i < root.header.total_files; i++) {
|
||||||
if (root.string_table[i] == "secure") {
|
if (root.string_table[i] == "secure") {
|
||||||
log_write("[XCI] found secure partition\n");
|
log_write("[XCI] found secure partition\n");
|
||||||
|
return ReadPartitionIntoCollection(m_source, root, i, out);
|
||||||
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();
|
|
||||||
} else {
|
} else {
|
||||||
log_write("[XCI] skipping partition %u | %s\n", i, root.string_table[i].c_str());
|
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) {
|
if ((config.convert_to_standard_crypto && ticket) || config.lower_master_key) {
|
||||||
t->nca->modified = true;
|
t->nca->modified = true;
|
||||||
u8 keak_generation;
|
u8 keak_generation = 0;
|
||||||
|
|
||||||
if (ticket) {
|
if (ticket) {
|
||||||
const auto key_gen = header.key_gen;
|
const auto key_gen = header.key_gen;
|
||||||
@@ -673,10 +673,6 @@ Result Yati::decompressFuncInternal(ThreadData* t) {
|
|||||||
R_TRY(nca::DecryptKeak(keys, header));
|
R_TRY(nca::DecryptKeak(keys, header));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.lower_master_key) {
|
|
||||||
keak_generation = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
R_TRY(nca::EncryptKeak(keys, header, keak_generation));
|
R_TRY(nca::EncryptKeak(keys, header, keak_generation));
|
||||||
std::memset(&header.rights_id, 0, sizeof(header.rights_id));
|
std::memset(&header.rights_id, 0, sizeof(header.rights_id));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user