diff --git a/libraries/libstratosphere/include/stratosphere/fs.hpp b/libraries/libstratosphere/include/stratosphere/fs.hpp
index be40bb263..d19df40b3 100644
--- a/libraries/libstratosphere/include/stratosphere/fs.hpp
+++ b/libraries/libstratosphere/include/stratosphere/fs.hpp
@@ -22,6 +22,7 @@
#include "fs/fsa/fs_registrar.hpp"
#include "fs/fs_remote_filesystem.hpp"
#include "fs/fs_istorage.hpp"
+#include "fs/fs_substorage.hpp"
#include "fs/fs_remote_storage.hpp"
#include "fs/fs_file_storage.hpp"
#include "fs/fs_query_range.hpp"
@@ -29,6 +30,7 @@
#include "fs/fs_mount.hpp"
#include "fs/fs_path_tool.hpp"
#include "fs/fs_path_utils.hpp"
+#include "fs/fs_rom_path_tool.hpp"
#include "fs/fs_content_storage.hpp"
#include "fs/fs_game_card.hpp"
#include "fs/fs_sd_card.hpp"
diff --git a/libraries/libstratosphere/include/stratosphere/fs/fs_path_tool.hpp b/libraries/libstratosphere/include/stratosphere/fs/fs_path_tool.hpp
index 28c180558..8eb601cb0 100644
--- a/libraries/libstratosphere/include/stratosphere/fs/fs_path_tool.hpp
+++ b/libraries/libstratosphere/include/stratosphere/fs/fs_path_tool.hpp
@@ -26,7 +26,7 @@ namespace ams::fs {
constexpr inline char Dot = '.';
constexpr inline char NullTerminator = '\x00';
- constexpr inline char UnsupportedDirectorySeparator = '/';
+ constexpr inline char UnsupportedDirectorySeparator = '\\';
}
class PathTool {
diff --git a/libraries/libstratosphere/include/stratosphere/fs/fs_rom_path_tool.hpp b/libraries/libstratosphere/include/stratosphere/fs/fs_rom_path_tool.hpp
new file mode 100644
index 000000000..dd57aba5f
--- /dev/null
+++ b/libraries/libstratosphere/include/stratosphere/fs/fs_rom_path_tool.hpp
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2018-2020 Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+#pragma once
+#include "fs_rom_types.hpp"
+
+namespace ams::fs {
+
+ namespace RomPathTool {
+
+ constexpr inline u32 MaxPathLength = 0x300;
+
+ struct RomEntryName {
+ size_t length;
+ const RomPathChar *path;
+ };
+ static_assert(std::is_pod::value);
+
+ constexpr void InitializeRomEntryName(RomEntryName *entry) {
+ AMS_ABORT_UNLESS(entry != nullptr);
+ entry->length = 0;
+ }
+
+ constexpr inline bool IsSeparator(RomPathChar c) {
+ return c == RomStringTraits::DirectorySeparator;
+ }
+
+ constexpr inline bool IsNullTerminator(RomPathChar c) {
+ return c == RomStringTraits::NullTerminator;
+ }
+
+ constexpr inline bool IsDot(RomPathChar c) {
+ return c == RomStringTraits::Dot;
+ }
+
+ constexpr inline bool IsCurrentDirectory(const RomEntryName &name) {
+ return name.length == 1 && IsDot(name.path[0]);
+ }
+
+ constexpr inline bool IsCurrentDirectory(const RomPathChar *p, size_t length) {
+ AMS_ABORT_UNLESS(p != nullptr);
+ return length == 1 && IsDot(p[0]);
+ }
+
+ constexpr inline bool IsCurrentDirectory(const RomPathChar *p) {
+ AMS_ABORT_UNLESS(p != nullptr);
+ return IsDot(p[0]) && IsNullTerminator(p[1]);
+ }
+
+ constexpr inline bool IsParentDirectory(const RomEntryName &name) {
+ return name.length == 2 && IsDot(name.path[0]) && IsDot(name.path[1]);
+ }
+
+ constexpr inline bool IsParentDirectory(const RomPathChar *p) {
+ AMS_ABORT_UNLESS(p != nullptr);
+ return IsDot(p[0]) && IsDot(p[1]) && IsNullTerminator(p[2]);
+ }
+
+ constexpr inline bool IsParentDirectory(const RomPathChar *p, size_t length) {
+ AMS_ABORT_UNLESS(p != nullptr);
+ return length == 2 && IsDot(p[0]) && IsDot(p[1]);
+ }
+
+ constexpr inline bool IsEqualPath(const RomPathChar *lhs, const RomPathChar *rhs, size_t length) {
+ AMS_ABORT_UNLESS(lhs != nullptr);
+ AMS_ABORT_UNLESS(rhs != nullptr);
+ return std::strncmp(lhs, rhs, length) == 0;
+ }
+
+ constexpr inline bool IsEqualName(const RomEntryName &lhs, const RomPathChar *rhs) {
+ AMS_ABORT_UNLESS(rhs != nullptr);
+ if (strnlen(rhs, MaxPathLength) != lhs.length) {
+ return false;
+ }
+ return IsEqualPath(lhs.path, rhs, lhs.length);
+ }
+
+ constexpr inline bool IsEqualName(const RomEntryName &lhs, const RomEntryName &rhs) {
+ if (lhs.length != rhs.length) {
+ return false;
+ }
+ return IsEqualPath(lhs.path, rhs.path, lhs.length);
+ }
+
+ Result GetParentDirectoryName(RomEntryName *out, const RomEntryName &cur, const RomPathChar *p);
+
+ class PathParser {
+ private:
+ const RomPathChar *prev_path_start;
+ const RomPathChar *prev_path_end;
+ const RomPathChar *next_path;
+ bool finished;
+ public:
+ constexpr PathParser() : prev_path_start(), prev_path_end(), next_path(), finished() { /* ... */ }
+
+ Result Initialize(const RomPathChar *path);
+ void Finalize();
+
+ bool IsFinished() const;
+ bool IsDirectoryPath() const;
+
+ Result GetAsDirectoryName(RomEntryName *out) const;
+ Result GetAsFileName(RomEntryName *out) const;
+
+ Result GetNextDirectoryName(RomEntryName *out);
+ };
+
+ }
+
+}
diff --git a/libraries/libstratosphere/include/stratosphere/fs/fs_rom_types.hpp b/libraries/libstratosphere/include/stratosphere/fs/fs_rom_types.hpp
new file mode 100644
index 000000000..cfffd658f
--- /dev/null
+++ b/libraries/libstratosphere/include/stratosphere/fs/fs_rom_types.hpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2018-2020 Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+#pragma once
+#include "fs_common.hpp"
+
+namespace ams::fs {
+
+ using RomPathChar = char;
+ using RomFileId = s32;
+ using RomDirectoryId = s32;
+
+ struct RomFileSystemInformation {
+ s64 size;
+ s64 directory_bucket_offset;
+ s64 directory_bucket_size;
+ s64 directory_entry_offset;
+ s64 directory_entry_size;
+ s64 file_bucket_offset;
+ s64 file_bucket_size;
+ s64 file_entry_offset;
+ s64 file_entry_size;
+ s64 body_offset;
+ };
+ static_assert(std::is_pod::value);
+ static_assert(sizeof(RomFileSystemInformation) == 0x50);
+
+ struct RomDirectoryInfo {
+ /* ... */
+ };
+ static_assert(std::is_pod::value);
+
+ struct RomFileInfo {
+ s64 offset;
+ s64 size;
+ };
+ static_assert(std::is_pod::value);
+
+ namespace RomStringTraits {
+
+ constexpr inline char DirectorySeparator = '/';
+ constexpr inline char NullTerminator = '\x00';
+ constexpr inline char Dot = '.';
+
+ }
+
+}
diff --git a/libraries/libstratosphere/include/stratosphere/fs/fs_substorage.hpp b/libraries/libstratosphere/include/stratosphere/fs/fs_substorage.hpp
new file mode 100644
index 000000000..1bcebd337
--- /dev/null
+++ b/libraries/libstratosphere/include/stratosphere/fs/fs_substorage.hpp
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2018-2020 Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+#pragma once
+#include "impl/fs_newable.hpp"
+#include "fs_istorage.hpp"
+
+namespace ams::fs {
+
+ class SubStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable {
+ private:
+ std::shared_ptr shared_base_storage;
+ fs::IStorage *base_storage;
+ s64 offset;
+ s64 size;
+ bool resizable;
+ private:
+ constexpr bool IsValid() const {
+ return this->base_storage != nullptr;
+ }
+ public:
+ SubStorage() : shared_base_storage(), base_storage(nullptr), offset(0), size(0), resizable(false) { /* ... */ }
+
+ SubStorage(const SubStorage &rhs) : shared_base_storage(), base_storage(rhs.base_storage), offset(rhs.offset), size(rhs.size), resizable(rhs.resizable) { /* ... */}
+ SubStorage &operator=(const SubStorage &rhs) {
+ if (this != std::addressof(rhs)) {
+ this->base_storage = rhs.base_storage;
+ this->offset = rhs.offset;
+ this->size = rhs.size;
+ this->resizable = rhs.resizable;
+ }
+ return *this;
+ }
+
+ SubStorage(IStorage *storage, s64 o, s64 sz) : shared_base_storage(), base_storage(storage), offset(o), size(sz) {
+ AMS_ABORT_UNLESS(this->IsValid());
+ AMS_ABORT_UNLESS(this->offset >= 0);
+ AMS_ABORT_UNLESS(this->size >= 0);
+ }
+
+ SubStorage(std::shared_ptr storage, s64 o, s64 sz) : shared_base_storage(storage), base_storage(storage.get()), offset(o), size(sz) {
+ AMS_ABORT_UNLESS(this->IsValid());
+ AMS_ABORT_UNLESS(this->offset >= 0);
+ AMS_ABORT_UNLESS(this->size >= 0);
+ }
+
+ SubStorage(SubStorage *sub, s64 o, s64 sz) : shared_base_storage(), base_storage(sub->base_storage), offset(o + sub->offset), size(sz) {
+ AMS_ABORT_UNLESS(this->IsValid());
+ AMS_ABORT_UNLESS(this->offset >= 0);
+ AMS_ABORT_UNLESS(this->size >= 0);
+ AMS_ABORT_UNLESS(sub->size >= o + sz);
+ }
+
+ public:
+ void SetResizable(bool rsz) {
+ this->resizable = rsz;
+ }
+ public:
+ virtual Result Read(s64 offset, void *buffer, size_t size) override {
+ R_UNLESS(this->IsValid(), fs::ResultNotInitialized());
+ R_UNLESS(size != 0, ResultSuccess());
+ R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
+ R_UNLESS(IStorage::IsRangeValid(offset, size, this->size), fs::ResultOutOfRange());
+ return this->base_storage->Read(this->offset + offset, buffer, size);
+ }
+
+ virtual Result Write(s64 offset, const void *buffer, size_t size) override{
+ R_UNLESS(this->IsValid(), fs::ResultNotInitialized());
+ R_UNLESS(size != 0, ResultSuccess());
+ R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
+ R_UNLESS(IStorage::IsRangeValid(offset, size, this->size), fs::ResultOutOfRange());
+ return this->base_storage->Write(this->offset + offset, buffer, size);
+ }
+
+ virtual Result Flush() override {
+ R_UNLESS(this->IsValid(), fs::ResultNotInitialized());
+ return this->base_storage->Flush();
+ }
+
+ virtual Result SetSize(s64 size) override {
+ R_UNLESS(this->IsValid(), fs::ResultNotInitialized());
+ R_UNLESS(this->resizable, fs::ResultUnsupportedOperation());
+ R_UNLESS(IStorage::IsOffsetAndSizeValid(this->offset, size), fs::ResultInvalidSize());
+
+ s64 cur_size;
+ R_TRY(this->base_storage->GetSize(std::addressof(cur_size)));
+
+ R_UNLESS(cur_size == this->offset + this->size, fs::ResultUnsupportedOperation());
+
+ R_TRY(this->base_storage->SetSize(this->offset + size));
+
+ this->size = size;
+ return ResultSuccess();
+ }
+
+ virtual Result GetSize(s64 *out) override {
+ R_UNLESS(this->IsValid(), fs::ResultNotInitialized());
+ *out = this->size;
+ return ResultSuccess();
+ }
+
+ virtual Result OperateRange(void *dst, size_t dst_size, OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override {
+ R_UNLESS(this->IsValid(), fs::ResultNotInitialized());
+ R_UNLESS(size != 0, ResultSuccess());
+ R_UNLESS(IStorage::IsOffsetAndSizeValid(offset, size), fs::ResultOutOfRange());
+ return this->base_storage->OperateRange(dst, dst_size, op_id, this->offset + offset, size, src, src_size);
+ }
+
+ using IStorage::OperateRange;
+ };
+
+}
diff --git a/libraries/libstratosphere/source/fs/fs_rom_path_tool.cpp b/libraries/libstratosphere/source/fs/fs_rom_path_tool.cpp
new file mode 100644
index 000000000..c758c86dc
--- /dev/null
+++ b/libraries/libstratosphere/source/fs/fs_rom_path_tool.cpp
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2018-2020 Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+#include
+
+namespace ams::fs::RomPathTool {
+
+ Result PathParser::Initialize(const RomPathChar *path) {
+ AMS_ABORT_UNLESS(path != nullptr);
+
+ /* Require paths start with a separator, and skip repeated separators. */
+ R_UNLESS(IsSeparator(path[0]), fs::ResultDbmInvalidPathFormat());
+ while (IsSeparator(path[1])) {
+ path++;
+ }
+
+ this->prev_path_start = path;
+ this->prev_path_end = path;
+ this->next_path = path + 1;
+ while (IsSeparator(this->next_path[0])) {
+ this->next_path++;
+ }
+
+ return ResultSuccess();
+ }
+
+ void PathParser::Finalize() {
+ this->prev_path_start = nullptr;
+ this->prev_path_end = nullptr;
+ this->next_path = nullptr;
+ this->finished = false;
+ }
+
+ bool PathParser::IsFinished() const {
+ return this->finished;
+ }
+
+ bool PathParser::IsDirectoryPath() const {
+ AMS_ASSERT(this->next_path != nullptr);
+ if (IsNullTerminator(this->next_path[0]) && IsSeparator(this->next_path[-1])) {
+ return true;
+ }
+
+ if (IsCurrentDirectory(this->next_path)) {
+ return true;
+ }
+
+ if (IsParentDirectory(this->next_path)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ Result PathParser::GetAsDirectoryName(RomEntryName *out) const {
+ AMS_ABORT_UNLESS(out != nullptr);
+ AMS_ABORT_UNLESS(this->prev_path_start != nullptr);
+ AMS_ABORT_UNLESS(this->prev_path_end != nullptr);
+ AMS_ABORT_UNLESS(this->next_path != nullptr);
+
+ const size_t len = this->prev_path_end - this->prev_path_start;
+ R_UNLESS(len <= MaxPathLength, fs::ResultDbmDirectoryNameTooLong());
+
+ out->length = len;
+ out->path = this->prev_path_start;
+ return ResultSuccess();
+ }
+
+ Result PathParser::GetAsFileName(RomEntryName *out) const {
+ AMS_ABORT_UNLESS(out != nullptr);
+ AMS_ABORT_UNLESS(this->prev_path_start != nullptr);
+ AMS_ABORT_UNLESS(this->prev_path_end != nullptr);
+ AMS_ABORT_UNLESS(this->next_path != nullptr);
+
+ const size_t len = this->prev_path_end - this->prev_path_start;
+ R_UNLESS(len <= MaxPathLength, fs::ResultDbmFileNameTooLong());
+
+ out->length = len;
+ out->path = this->prev_path_start;
+ return ResultSuccess();
+ }
+
+ Result PathParser::GetNextDirectoryName(RomEntryName *out) {
+ AMS_ABORT_UNLESS(out != nullptr);
+ AMS_ABORT_UNLESS(this->prev_path_start != nullptr);
+ AMS_ABORT_UNLESS(this->prev_path_end != nullptr);
+ AMS_ABORT_UNLESS(this->next_path != nullptr);
+
+ /* Set the current path to output. */
+ out->length = this->prev_path_end - this->prev_path_start;
+ out->path = this->prev_path_start;
+
+ /* Parse the next path. */
+ this->prev_path_start = this->next_path;
+ const RomPathChar *cur = this->next_path;
+ for (size_t name_len = 0; true; name_len++) {
+ if (IsSeparator(cur[name_len])) {
+ R_UNLESS(name_len < MaxPathLength, fs::ResultDbmDirectoryNameTooLong());
+
+ this->prev_path_end = cur + name_len;
+ this->next_path = this->prev_path_end + 1;
+
+ while (IsSeparator(this->next_path[0])) {
+ this->next_path++;
+ }
+ if (IsNullTerminator(this->next_path[0])) {
+ this->finished = true;
+ }
+ break;
+ }
+
+ if (IsNullTerminator(cur[name_len])) {
+ this->finished = true;
+ this->prev_path_end = this->next_path = cur + name_len;
+ break;
+ }
+ }
+
+ return ResultSuccess();
+ }
+
+ Result GetParentDirectoryName(RomEntryName *out, const RomEntryName &cur, const RomPathChar *p) {
+ AMS_ABORT_UNLESS(out != nullptr);
+ AMS_ABORT_UNLESS(p != nullptr);
+
+ const RomPathChar *start = cur.path;
+ const RomPathChar *end = cur.path + cur.length - 1;
+
+ s32 depth = 1;
+ if (IsParentDirectory(cur)) {
+ depth++;
+ }
+
+ if (cur.path > p) {
+ size_t len = 0;
+ const RomPathChar *head = cur.path - 1;
+ while (head >= p) {
+ if (IsSeparator(*head)) {
+ if (IsCurrentDirectory(head + 1, len)) {
+ depth++;
+ }
+
+ if (IsParentDirectory(head + 1, len)) {
+ depth += 2;
+ }
+
+ if (depth == 0) {
+ start = head + 1;
+ }
+
+ while (IsSeparator(*head)) {
+ head--;
+ }
+
+ end = head;
+ len = 0;
+ depth--;
+ }
+
+ len++;
+ head--;
+ }
+
+ R_UNLESS(depth == 0, fs::ResultDirectoryUnobtainable());
+
+ if (head == p) {
+ start = p + 1;
+ }
+ }
+
+ if (end <= p) {
+ out->path = p;
+ out->length = 0;
+ } else {
+ out->path = start;
+ out->length = end - start + 1;
+ }
+
+ return ResultSuccess();
+ }
+
+}
diff --git a/libraries/libvapours/include/vapours/results/fs_results.hpp b/libraries/libvapours/include/vapours/results/fs_results.hpp
index 74ed9aafe..e7a72155a 100644
--- a/libraries/libvapours/include/vapours/results/fs_results.hpp
+++ b/libraries/libvapours/include/vapours/results/fs_results.hpp
@@ -140,6 +140,12 @@ namespace ams::fs {
R_DEFINE_ERROR_RESULT(MapFull, 6811);
R_DEFINE_ERROR_RANGE(BadState, 6900, 6999);
- R_DEFINE_ERROR_RESULT(NotMounted, 6905);
+ R_DEFINE_ERROR_RESULT(NotInitialized, 6902);
+ R_DEFINE_ERROR_RESULT(NotMounted, 6905);
+
+ R_DEFINE_ERROR_RESULT(DbmInvalidOperation, 7914);
+ R_DEFINE_ERROR_RESULT(DbmInvalidPathFormat, 7915);
+ R_DEFINE_ERROR_RESULT(DbmDirectoryNameTooLong, 7916);
+ R_DEFINE_ERROR_RESULT(DbmFileNameTooLong, 7917);
}