From 37f7afb42665765a194b8af799abf3885c34f699 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Wed, 18 Mar 2020 00:07:19 -0700 Subject: [PATCH] ams.mitm: greatly reduce memory requirements to build romfs --- .../ams_mitm/source/fs_mitm/fsmitm_romfs.cpp | 399 ++++++++++++------ .../ams_mitm/source/fs_mitm/fsmitm_romfs.hpp | 5 +- 2 files changed, 283 insertions(+), 121 deletions(-) diff --git a/stratosphere/ams_mitm/source/fs_mitm/fsmitm_romfs.cpp b/stratosphere/ams_mitm/source/fs_mitm/fsmitm_romfs.cpp index b0d037f7a..5d46b0f11 100644 --- a/stratosphere/ams_mitm/source/fs_mitm/fsmitm_romfs.cpp +++ b/stratosphere/ams_mitm/source/fs_mitm/fsmitm_romfs.cpp @@ -64,21 +64,167 @@ namespace ams::mitm::fs { }; static_assert(std::is_pod::value && sizeof(FileEntry) == 0x20); - constexpr inline DirectoryEntry *GetDirectoryEntry(void *dir_table, u32 offset) { - return reinterpret_cast(reinterpret_cast(dir_table) + offset); - } + template + class TableReader { + NON_COPYABLE(TableReader); + NON_MOVEABLE(TableReader); + private: + static constexpr size_t MaxCachedSize = (1_MB / 4); + static constexpr size_t FallbackCacheSize = 1_KB; + private: + ams::fs::IStorage *storage; + size_t offset; + size_t size; + size_t cache_idx; + void *cache; + u8 fallback_cache[FallbackCacheSize]; + private: + ALWAYS_INLINE void Read(size_t ofs, void *dst, size_t size) { + R_ABORT_UNLESS(this->storage->Read(this->offset + ofs, dst, size)); + } + ALWAYS_INLINE void ReloadCacheImpl(size_t idx) { + const size_t rel_ofs = idx * MaxCachedSize; + AMS_ABORT_UNLESS(rel_ofs < this->size); + const size_t new_cache_size = std::min(this->size - rel_ofs, MaxCachedSize); + this->Read(rel_ofs, this->cache, new_cache_size); + this->cache_idx = idx; + } - constexpr inline FileEntry *GetFileEntry(void *file_table, u32 offset) { - return reinterpret_cast(reinterpret_cast(file_table) + offset); - } + ALWAYS_INLINE void ReloadCache(size_t idx) { + if (this->cache_idx != idx) { + this->ReloadCacheImpl(idx); + } + } - constexpr inline const DirectoryEntry *GetDirectoryEntry(const void *dir_table, u32 offset) { - return reinterpret_cast(reinterpret_cast(dir_table) + offset); - } + ALWAYS_INLINE size_t GetCacheIndex(u32 ofs) { + return ofs / MaxCachedSize; + } + public: + TableReader(ams::fs::IStorage *s, size_t ofs, size_t sz) : storage(s), offset(ofs), size(sz), cache_idx(0) { + this->cache = std::malloc(std::min(sz, MaxCachedSize)); + AMS_ABORT_UNLESS(this->cache != nullptr); + this->ReloadCacheImpl(0); + } + + ~TableReader() { + std::free(this->cache); + } + + const Entry *GetEntry(u32 entry_offset) { + this->ReloadCache(this->GetCacheIndex(entry_offset)); + + const size_t ofs = entry_offset % MaxCachedSize; + + const Entry *entry = reinterpret_cast(reinterpret_cast(this->cache) + ofs); + if (AMS_UNLIKELY(this->GetCacheIndex(entry_offset) != this->GetCacheIndex(entry_offset + sizeof(Entry) + entry->name_size + sizeof(u32)))) { + this->Read(entry_offset, this->fallback_cache, std::min(this->size - entry_offset, FallbackCacheSize)); + entry = reinterpret_cast(this->fallback_cache); + } + return entry; + } + }; + + template + class TableWriter { + NON_COPYABLE(TableWriter); + NON_MOVEABLE(TableWriter); + private: + static constexpr size_t MaxCachedSize = (1_MB / 4); + static constexpr size_t FallbackCacheSize = 1_KB; + private: + ::FsFile *file; + size_t offset; + size_t size; + size_t cache_idx; + void *cache; + u8 fallback_cache[FallbackCacheSize]; + size_t fallback_cache_entry_offset; + size_t fallback_cache_entry_size; + bool cache_dirty; + bool fallback_cache_dirty; + private: + ALWAYS_INLINE void Read(size_t ofs, void *dst, size_t sz) { + u64 read_size; + R_ABORT_UNLESS(fsFileRead(this->file, this->offset + ofs, dst, sz, 0, &read_size)); + AMS_ABORT_UNLESS(read_size == sz); + } + + ALWAYS_INLINE void Write(size_t ofs, const void *src, size_t sz) { + R_ABORT_UNLESS(fsFileWrite(this->file, this->offset + ofs, src, sz, FsWriteOption_None)); + } + + ALWAYS_INLINE void Flush() { + AMS_ABORT_UNLESS(!(this->cache_dirty && this->fallback_cache_dirty)); + + if (this->cache_dirty) { + const size_t ofs = this->cache_idx * MaxCachedSize; + this->Write(ofs, this->cache, std::min(this->size - ofs, MaxCachedSize)); + this->cache_dirty = false; + } + if (this->fallback_cache_dirty) { + this->Write(this->fallback_cache_entry_offset, this->fallback_cache, this->fallback_cache_entry_size); + this->fallback_cache_dirty = false; + } + } + + ALWAYS_INLINE size_t GetCacheIndex(u32 ofs) { + return ofs / MaxCachedSize; + } + + ALWAYS_INLINE void RefreshCacheImpl() { + const size_t cur_cache = this->cache_idx * MaxCachedSize; + this->Read(cur_cache, this->cache, std::min(this->size - cur_cache, MaxCachedSize)); + } + + ALWAYS_INLINE void RefreshCache(u32 entry_offset) { + if (size_t idx = this->GetCacheIndex(entry_offset); idx != this->cache_idx || this->fallback_cache_dirty) { + this->Flush(); + this->cache_idx = idx; + this->RefreshCacheImpl(); + } + } + public: + TableWriter(::FsFile *f, size_t ofs, size_t sz) : file(f), offset(ofs), size(sz), cache_idx(0), fallback_cache_entry_offset(), fallback_cache_entry_size(), cache_dirty(), fallback_cache_dirty() { + const size_t cache_size = std::min(sz, MaxCachedSize); + this->cache = std::malloc(cache_size); + AMS_ABORT_UNLESS(this->cache != nullptr); + std::memset(this->cache, 0, cache_size); + std::memset(this->fallback_cache, 0, sizeof(this->fallback_cache)); + for (size_t cur = 0; cur < this->size; cur += MaxCachedSize) { + this->Write(cur, this->cache, std::min(this->size - cur, MaxCachedSize)); + } + } + + ~TableWriter() { + this->Flush(); + } + + Entry *GetEntry(u32 entry_offset, u32 name_len) { + this->RefreshCache(entry_offset); + + const size_t ofs = entry_offset % MaxCachedSize; + + Entry *entry = reinterpret_cast(reinterpret_cast(this->cache) + ofs); + if (ofs + sizeof(Entry) + util::AlignUp(name_len, sizeof(u32)) > MaxCachedSize) { + this->Flush(); + + this->fallback_cache_entry_offset = entry_offset; + this->fallback_cache_entry_size = sizeof(Entry) + util::AlignUp(name_len, sizeof(u32)); + this->Read(this->fallback_cache_entry_offset, this->fallback_cache, this->fallback_cache_entry_size); + + entry = reinterpret_cast(this->fallback_cache); + this->fallback_cache_dirty = true; + } else { + this->cache_dirty = true; + } + + return entry; + } + }; + + using DirectoryTableWriter = TableWriter; + using FileTableWriter = TableWriter; - constexpr inline const FileEntry *GetFileEntry(const void *file_table, u32 offset) { - return reinterpret_cast(reinterpret_cast(file_table) + offset); - } constexpr inline u32 CalculatePathHash(u32 parent, const char *_path, u32 start, size_t path_len) { const unsigned char *path = reinterpret_cast(_path); @@ -214,34 +360,45 @@ namespace ams::mitm::fs { } - void Builder::VisitDirectory(BuildDirectoryContext *parent, u32 parent_offset, const void *dir_table, size_t dir_table_size, const void *file_table, size_t file_table_size) { - const DirectoryEntry *parent_entry = GetDirectoryEntry(dir_table, parent_offset); - if (parent_entry->file != EmptyEntry) { - const FileEntry *cur_file = GetFileEntry(file_table, parent_entry->file); - while (true) { - this->AddFile(parent, std::make_unique(cur_file->name, cur_file->name_size, cur_file->size, cur_file->offset, this->cur_source_type)); - if (cur_file->sibling == EmptyEntry) { - break; - } - cur_file = GetFileEntry(file_table, cur_file->sibling); - } + class DirectoryTableReader : public TableReader { + public: + DirectoryTableReader(ams::fs::IStorage *s, size_t ofs, size_t sz) : TableReader(s, ofs, sz) { /* ... */ } + }; + + class FileTableReader : public TableReader { + public: + FileTableReader(ams::fs::IStorage *s, size_t ofs, size_t sz) : TableReader(s, ofs, sz) { /* ... */ } + }; + + void Builder::VisitDirectory(BuildDirectoryContext *parent, u32 parent_offset, DirectoryTableReader &dir_table, FileTableReader &file_table) { + const DirectoryEntry *parent_entry = dir_table.GetEntry(parent_offset); + + u32 cur_file_offset = parent_entry->file; + while (cur_file_offset != EmptyEntry) { + const FileEntry *cur_file = file_table.GetEntry(cur_file_offset); + + this->AddFile(parent, std::make_unique(cur_file->name, cur_file->name_size, cur_file->size, cur_file->offset, this->cur_source_type)); + + cur_file_offset = cur_file->sibling; } - if (parent_entry->child != EmptyEntry) { - const DirectoryEntry *cur_child = GetDirectoryEntry(dir_table, parent_entry->child); - u32 cur_child_offset = parent_entry->child; - while (true) { - BuildDirectoryContext *real_child = nullptr; + + u32 cur_child_offset = parent_entry->child; + while (cur_child_offset != EmptyEntry) { + BuildDirectoryContext *real_child = nullptr; + u32 next_child_offset = 0; + { + const DirectoryEntry *cur_child = dir_table.GetEntry(cur_child_offset); + this->AddDirectory(&real_child, parent, std::make_unique(cur_child->name, cur_child->name_size)); AMS_ABORT_UNLESS(real_child != nullptr); - this->VisitDirectory(real_child, cur_child_offset, dir_table, dir_table_size, file_table, file_table_size); - - if (cur_child->sibling == EmptyEntry) { - break; - } - cur_child_offset = cur_child->sibling; - cur_child = GetDirectoryEntry(dir_table, cur_child_offset); + next_child_offset = cur_child->sibling; + __asm__ __volatile__("" ::: "memory"); } + + this->VisitDirectory(real_child, cur_child_offset, dir_table, file_table); + + cur_child_offset = next_child_offset; } } @@ -271,15 +428,11 @@ namespace ams::mitm::fs { AMS_ABORT_UNLESS(header.header_size == sizeof(Header)); /* Read tables. */ - void *tables = std::malloc(header.dir_table_size + header.file_table_size); - ON_SCOPE_EXIT { std::free(tables); }; - void *dir_table = tables; - void *file_table = reinterpret_cast(reinterpret_cast(tables) + header.dir_table_size); - R_ABORT_UNLESS(storage->Read(header.dir_table_ofs, dir_table, size_t(header.dir_table_size))); - R_ABORT_UNLESS(storage->Read(header.file_table_ofs, file_table, size_t(header.file_table_size))); + DirectoryTableReader dir_table(storage, header.dir_table_ofs, header.dir_table_size); + FileTableReader file_table(storage, header.file_table_ofs, header.file_table_size); this->cur_source_type = source_type; - this->VisitDirectory(this->root, 0x0, dir_table, size_t(header.dir_table_size), file_table, size_t(header.file_table_size)); + this->VisitDirectory(this->root, 0x0, dir_table, file_table); } void Builder::Build(std::vector *out_infos) { @@ -372,108 +525,114 @@ namespace ams::mitm::fs { /* Populate file tables. */ { - void *ft_buf = std::malloc(this->file_table_size); + /* Allocate the hash table. */ void *fht_buf = std::malloc(this->file_hash_table_size); - ON_SCOPE_EXIT { std::free(fht_buf); std::free(ft_buf); }; - + AMS_ABORT_UNLESS(fht_buf != nullptr); u32 *file_hash_table = reinterpret_cast(fht_buf); - FileEntry *file_table = reinterpret_cast(ft_buf); std::memset(file_hash_table, 0xFF, this->file_hash_table_size); + ON_SCOPE_EXIT { + R_ABORT_UNLESS(fsFileWrite(&metadata_file, this->dir_hash_table_size + this->dir_table_size, file_hash_table, this->file_hash_table_size, FsWriteOption_None)); + std::free(fht_buf); + }; - for (const auto &it : this->files) { - BuildFileContext *cur_file = it.get(); - FileEntry *cur_entry = GetFileEntry(file_table, cur_file->entry_offset); + /* Write the file table. */ + { + FileTableWriter file_table(&metadata_file, this->dir_hash_table_size + this->dir_table_size + this->file_hash_table_size, this->file_table_size); - /* Set entry fields. */ - cur_entry->parent = cur_file->parent->entry_offset; - cur_entry->sibling = (cur_file->sibling == nullptr) ? EmptyEntry : cur_file->sibling->entry_offset; - cur_entry->offset = cur_file->offset; - cur_entry->size = cur_file->size; + for (const auto &it : this->files) { + BuildFileContext *cur_file = it.get(); + FileEntry *cur_entry = file_table.GetEntry(cur_file->entry_offset, cur_file->path_len); - /* Insert into hash table. */ - const u32 name_size = cur_file->path_len; - const size_t hash_ind = CalculatePathHash(cur_entry->parent, cur_file->path.get(), 0, name_size) % num_file_hash_table_entries; - cur_entry->hash = file_hash_table[hash_ind]; - file_hash_table[hash_ind] = cur_file->entry_offset; + /* Set entry fields. */ + cur_entry->parent = cur_file->parent->entry_offset; + cur_entry->sibling = (cur_file->sibling == nullptr) ? EmptyEntry : cur_file->sibling->entry_offset; + cur_entry->offset = cur_file->offset; + cur_entry->size = cur_file->size; - /* Set name. */ - cur_entry->name_size = name_size; - if (name_size) { - std::memcpy(cur_entry->name, cur_file->path.get(), name_size); - for (size_t i = name_size; i < util::AlignUp(name_size, 4); i++) { - cur_entry->name[i] = 0; + /* Insert into hash table. */ + const u32 name_size = cur_file->path_len; + const size_t hash_ind = CalculatePathHash(cur_entry->parent, cur_file->path.get(), 0, name_size) % num_file_hash_table_entries; + cur_entry->hash = file_hash_table[hash_ind]; + file_hash_table[hash_ind] = cur_file->entry_offset; + + /* Set name. */ + cur_entry->name_size = name_size; + if (name_size) { + std::memcpy(cur_entry->name, cur_file->path.get(), name_size); + for (size_t i = name_size; i < util::AlignUp(name_size, 4); i++) { + cur_entry->name[i] = 0; + } + } + + /* Emplace a source. */ + switch (cur_file->source_type) { + case DataSourceType::Storage: + case DataSourceType::File: + { + /* Try to compact if possible. */ + auto &back = out_infos->back(); + if (back.source_type == cur_file->source_type) { + back.size = cur_file->offset + FilePartitionOffset + cur_file->size - back.virtual_offset; + } else { + out_infos->emplace_back(cur_file->offset + FilePartitionOffset, cur_file->size, cur_file->source_type, cur_file->orig_offset + FilePartitionOffset); + } + } + break; + case DataSourceType::LooseSdFile: + { + char *new_path = new char[cur_file->GetPathLength() + 1]; + cur_file->GetPath(new_path); + out_infos->emplace_back(cur_file->offset + FilePartitionOffset, cur_file->size, cur_file->source_type, new_path); + } + break; + AMS_UNREACHABLE_DEFAULT_CASE(); } } - - /* Emplace a source. */ - switch (cur_file->source_type) { - case DataSourceType::Storage: - case DataSourceType::File: - { - /* Try to compact if possible. */ - auto &back = out_infos->back(); - if (back.source_type == cur_file->source_type) { - back.size = cur_file->offset + FilePartitionOffset + cur_file->size - back.virtual_offset; - } else { - out_infos->emplace_back(cur_file->offset + FilePartitionOffset, cur_file->size, cur_file->source_type, cur_file->orig_offset + FilePartitionOffset); - } - } - break; - case DataSourceType::LooseSdFile: - { - char *new_path = new char[cur_file->GetPathLength() + 1]; - cur_file->GetPath(new_path); - out_infos->emplace_back(cur_file->offset + FilePartitionOffset, cur_file->size, cur_file->source_type, new_path); - } - break; - AMS_UNREACHABLE_DEFAULT_CASE(); - } } - - /* Write to file. */ - R_ABORT_UNLESS(fsFileWrite(&metadata_file, this->dir_hash_table_size + this->dir_table_size, file_hash_table, this->file_hash_table_size, FsWriteOption_None)); - R_ABORT_UNLESS(fsFileWrite(&metadata_file, this->dir_hash_table_size + this->dir_table_size + this->file_hash_table_size, file_table, this->file_table_size, FsWriteOption_None)); } /* Populate directory tables. */ { - void *dt_buf = std::malloc(this->dir_table_size); + /* Allocate the hash table. */ void *dht_buf = std::malloc(this->dir_hash_table_size); - ON_SCOPE_EXIT { std::free(dht_buf); std::free(dt_buf); }; - + AMS_ABORT_UNLESS(dht_buf != nullptr); u32 *dir_hash_table = reinterpret_cast(dht_buf); - DirectoryEntry *dir_table = reinterpret_cast(dt_buf); std::memset(dir_hash_table, 0xFF, this->dir_hash_table_size); + ON_SCOPE_EXIT { + R_ABORT_UNLESS(fsFileWrite(&metadata_file, 0, dir_hash_table, this->dir_hash_table_size, FsWriteOption_None)); + std::free(dht_buf); + }; - for (const auto &it : this->directories) { - BuildDirectoryContext *cur_dir = it.get(); - DirectoryEntry *cur_entry = GetDirectoryEntry(dir_table, cur_dir->entry_offset); + /* Write the file table. */ + { + DirectoryTableWriter dir_table(&metadata_file, this->dir_hash_table_size, this->dir_table_size); - /* Set entry fields. */ - cur_entry->parent = cur_dir == this->root ? 0 : cur_dir->parent->entry_offset; - cur_entry->sibling = (cur_dir->sibling == nullptr) ? EmptyEntry : cur_dir->sibling->entry_offset; - cur_entry->child = (cur_dir->child == nullptr) ? EmptyEntry : cur_dir->child->entry_offset; - cur_entry->file = (cur_dir->file == nullptr) ? EmptyEntry : cur_dir->file->entry_offset; + for (const auto &it : this->directories) { + BuildDirectoryContext *cur_dir = it.get(); + DirectoryEntry *cur_entry = dir_table.GetEntry(cur_dir->entry_offset, cur_dir->path_len); - /* Insert into hash table. */ - const u32 name_size = cur_dir->path_len; - const size_t hash_ind = CalculatePathHash(cur_entry->parent, cur_dir->path.get(), 0, name_size) % num_dir_hash_table_entries; - cur_entry->hash = dir_hash_table[hash_ind]; - dir_hash_table[hash_ind] = cur_dir->entry_offset; + /* Set entry fields. */ + cur_entry->parent = cur_dir == this->root ? 0 : cur_dir->parent->entry_offset; + cur_entry->sibling = (cur_dir->sibling == nullptr) ? EmptyEntry : cur_dir->sibling->entry_offset; + cur_entry->child = (cur_dir->child == nullptr) ? EmptyEntry : cur_dir->child->entry_offset; + cur_entry->file = (cur_dir->file == nullptr) ? EmptyEntry : cur_dir->file->entry_offset; - /* Set name. */ - cur_entry->name_size = name_size; - if (name_size) { - std::memcpy(cur_entry->name, cur_dir->path.get(), name_size); - for (size_t i = name_size; i < util::AlignUp(name_size, 4); i++) { - cur_entry->name[i] = 0; + /* Insert into hash table. */ + const u32 name_size = cur_dir->path_len; + const size_t hash_ind = CalculatePathHash(cur_entry->parent, cur_dir->path.get(), 0, name_size) % num_dir_hash_table_entries; + cur_entry->hash = dir_hash_table[hash_ind]; + dir_hash_table[hash_ind] = cur_dir->entry_offset; + + /* Set name. */ + cur_entry->name_size = name_size; + if (name_size) { + std::memcpy(cur_entry->name, cur_dir->path.get(), name_size); + for (size_t i = name_size; i < util::AlignUp(name_size, 4); i++) { + cur_entry->name[i] = 0; + } } } } - - /* Write to file. */ - R_ABORT_UNLESS(fsFileWrite(&metadata_file, 0, dir_hash_table, this->dir_hash_table_size, FsWriteOption_None)); - R_ABORT_UNLESS(fsFileWrite(&metadata_file, this->dir_hash_table_size, dir_table, this->dir_table_size, FsWriteOption_None)); } /* Delete maps. */ diff --git a/stratosphere/ams_mitm/source/fs_mitm/fsmitm_romfs.hpp b/stratosphere/ams_mitm/source/fs_mitm/fsmitm_romfs.hpp index edb30bbf3..96b8cf699 100644 --- a/stratosphere/ams_mitm/source/fs_mitm/fsmitm_romfs.hpp +++ b/stratosphere/ams_mitm/source/fs_mitm/fsmitm_romfs.hpp @@ -199,6 +199,9 @@ namespace ams::mitm::fs::romfs { } }; + class DirectoryTableReader; + class FileTableReader; + struct Builder { NON_COPYABLE(Builder); NON_MOVEABLE(Builder); @@ -243,7 +246,7 @@ namespace ams::mitm::fs::romfs { DataSourceType cur_source_type; private: void VisitDirectory(FsFileSystem *fs, BuildDirectoryContext *parent); - void VisitDirectory(BuildDirectoryContext *parent, u32 parent_offset, const void *dir_table, size_t dir_table_size, const void *file_table, size_t file_table_size); + void VisitDirectory(BuildDirectoryContext *parent, u32 parent_offset, DirectoryTableReader &dir_table, FileTableReader &file_table); void AddDirectory(BuildDirectoryContext **out, BuildDirectoryContext *parent_ctx, std::unique_ptr file_ctx); void AddFile(BuildDirectoryContext *parent_ctx, std::unique_ptr file_ctx);