fsmitm_romfsbuild.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include <cstring>
  4. #include <span>
  5. #include <string_view>
  6. #include "common/alignment.h"
  7. #include "common/assert.h"
  8. #include "core/file_sys/fsmitm_romfsbuild.h"
  9. #include "core/file_sys/ips_layer.h"
  10. #include "core/file_sys/vfs.h"
  11. #include "core/file_sys/vfs_vector.h"
  12. namespace FileSys {
  13. constexpr u64 FS_MAX_PATH = 0x301;
  14. constexpr u32 ROMFS_ENTRY_EMPTY = 0xFFFFFFFF;
  15. constexpr u32 ROMFS_FILEPARTITION_OFS = 0x200;
  16. // Types for building a RomFS.
  17. struct RomFSHeader {
  18. u64 header_size;
  19. u64 dir_hash_table_ofs;
  20. u64 dir_hash_table_size;
  21. u64 dir_table_ofs;
  22. u64 dir_table_size;
  23. u64 file_hash_table_ofs;
  24. u64 file_hash_table_size;
  25. u64 file_table_ofs;
  26. u64 file_table_size;
  27. u64 file_partition_ofs;
  28. };
  29. static_assert(sizeof(RomFSHeader) == 0x50, "RomFSHeader has incorrect size.");
  30. struct RomFSDirectoryEntry {
  31. u32 parent;
  32. u32 sibling;
  33. u32 child;
  34. u32 file;
  35. u32 hash;
  36. u32 name_size;
  37. };
  38. static_assert(sizeof(RomFSDirectoryEntry) == 0x18, "RomFSDirectoryEntry has incorrect size.");
  39. struct RomFSFileEntry {
  40. u32 parent;
  41. u32 sibling;
  42. u64 offset;
  43. u64 size;
  44. u32 hash;
  45. u32 name_size;
  46. };
  47. static_assert(sizeof(RomFSFileEntry) == 0x20, "RomFSFileEntry has incorrect size.");
  48. struct RomFSBuildFileContext;
  49. struct RomFSBuildDirectoryContext {
  50. std::string path;
  51. u32 cur_path_ofs = 0;
  52. u32 path_len = 0;
  53. u32 entry_offset = 0;
  54. std::shared_ptr<RomFSBuildDirectoryContext> parent;
  55. std::shared_ptr<RomFSBuildDirectoryContext> child;
  56. std::shared_ptr<RomFSBuildDirectoryContext> sibling;
  57. std::shared_ptr<RomFSBuildFileContext> file;
  58. };
  59. struct RomFSBuildFileContext {
  60. std::string path;
  61. u32 cur_path_ofs = 0;
  62. u32 path_len = 0;
  63. u32 entry_offset = 0;
  64. u64 offset = 0;
  65. u64 size = 0;
  66. std::shared_ptr<RomFSBuildDirectoryContext> parent;
  67. std::shared_ptr<RomFSBuildFileContext> sibling;
  68. VirtualFile source;
  69. };
  70. static u32 romfs_calc_path_hash(u32 parent, std::string_view path, u32 start,
  71. std::size_t path_len) {
  72. u32 hash = parent ^ 123456789;
  73. for (u32 i = 0; i < path_len; i++) {
  74. hash = (hash >> 5) | (hash << 27);
  75. hash ^= path[start + i];
  76. }
  77. return hash;
  78. }
  79. static u64 romfs_get_hash_table_count(u64 num_entries) {
  80. if (num_entries < 3) {
  81. return 3;
  82. }
  83. if (num_entries < 19) {
  84. return num_entries | 1;
  85. }
  86. u64 count = num_entries;
  87. while (count % 2 == 0 || count % 3 == 0 || count % 5 == 0 || count % 7 == 0 ||
  88. count % 11 == 0 || count % 13 == 0 || count % 17 == 0) {
  89. count++;
  90. }
  91. return count;
  92. }
  93. void RomFSBuildContext::VisitDirectory(VirtualDir romfs_dir, VirtualDir ext_dir,
  94. std::shared_ptr<RomFSBuildDirectoryContext> parent) {
  95. for (auto& child_romfs_file : romfs_dir->GetFiles()) {
  96. const auto name = child_romfs_file->GetName();
  97. const auto child = std::make_shared<RomFSBuildFileContext>();
  98. // Set child's path.
  99. child->cur_path_ofs = parent->path_len + 1;
  100. child->path_len = child->cur_path_ofs + static_cast<u32>(name.size());
  101. child->path = parent->path + "/" + name;
  102. if (ext_dir != nullptr && ext_dir->GetFile(name + ".stub") != nullptr) {
  103. continue;
  104. }
  105. // Sanity check on path_len
  106. ASSERT(child->path_len < FS_MAX_PATH);
  107. child->source = std::move(child_romfs_file);
  108. if (ext_dir != nullptr) {
  109. if (const auto ips = ext_dir->GetFile(name + ".ips")) {
  110. if (auto patched = PatchIPS(child->source, ips)) {
  111. child->source = std::move(patched);
  112. }
  113. }
  114. }
  115. child->size = child->source->GetSize();
  116. AddFile(parent, std::move(child));
  117. }
  118. for (auto& child_romfs_dir : romfs_dir->GetSubdirectories()) {
  119. const auto name = child_romfs_dir->GetName();
  120. const auto child = std::make_shared<RomFSBuildDirectoryContext>();
  121. // Set child's path.
  122. child->cur_path_ofs = parent->path_len + 1;
  123. child->path_len = child->cur_path_ofs + static_cast<u32>(name.size());
  124. child->path = parent->path + "/" + name;
  125. if (ext_dir != nullptr && ext_dir->GetFile(name + ".stub") != nullptr) {
  126. continue;
  127. }
  128. // Sanity check on path_len
  129. ASSERT(child->path_len < FS_MAX_PATH);
  130. if (!AddDirectory(parent, child)) {
  131. continue;
  132. }
  133. auto child_ext_dir = ext_dir != nullptr ? ext_dir->GetSubdirectory(name) : nullptr;
  134. this->VisitDirectory(child_romfs_dir, child_ext_dir, child);
  135. }
  136. }
  137. bool RomFSBuildContext::AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
  138. std::shared_ptr<RomFSBuildDirectoryContext> dir_ctx) {
  139. // Add a new directory.
  140. num_dirs++;
  141. dir_table_size +=
  142. sizeof(RomFSDirectoryEntry) + Common::AlignUp(dir_ctx->path_len - dir_ctx->cur_path_ofs, 4);
  143. dir_ctx->parent = std::move(parent_dir_ctx);
  144. directories.emplace_back(std::move(dir_ctx));
  145. return true;
  146. }
  147. bool RomFSBuildContext::AddFile(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
  148. std::shared_ptr<RomFSBuildFileContext> file_ctx) {
  149. // Add a new file.
  150. num_files++;
  151. file_table_size +=
  152. sizeof(RomFSFileEntry) + Common::AlignUp(file_ctx->path_len - file_ctx->cur_path_ofs, 4);
  153. file_ctx->parent = std::move(parent_dir_ctx);
  154. files.emplace_back(std::move(file_ctx));
  155. return true;
  156. }
  157. RomFSBuildContext::RomFSBuildContext(VirtualDir base_, VirtualDir ext_)
  158. : base(std::move(base_)), ext(std::move(ext_)) {
  159. root = std::make_shared<RomFSBuildDirectoryContext>();
  160. root->path = "\0";
  161. directories.emplace_back(root);
  162. num_dirs = 1;
  163. dir_table_size = 0x18;
  164. VisitDirectory(base, ext, root);
  165. }
  166. RomFSBuildContext::~RomFSBuildContext() = default;
  167. std::vector<std::pair<u64, VirtualFile>> RomFSBuildContext::Build() {
  168. const u64 dir_hash_table_entry_count = romfs_get_hash_table_count(num_dirs);
  169. const u64 file_hash_table_entry_count = romfs_get_hash_table_count(num_files);
  170. dir_hash_table_size = 4 * dir_hash_table_entry_count;
  171. file_hash_table_size = 4 * file_hash_table_entry_count;
  172. // Assign metadata pointers.
  173. RomFSHeader header{};
  174. std::vector<u8> metadata(file_hash_table_size + file_table_size + dir_hash_table_size +
  175. dir_table_size);
  176. u32* const dir_hash_table_pointer = reinterpret_cast<u32*>(metadata.data());
  177. u8* const dir_table_pointer = metadata.data() + dir_hash_table_size;
  178. u32* const file_hash_table_pointer =
  179. reinterpret_cast<u32*>(metadata.data() + dir_hash_table_size + dir_table_size);
  180. u8* const file_table_pointer =
  181. metadata.data() + dir_hash_table_size + dir_table_size + file_hash_table_size;
  182. std::span<u32> dir_hash_table(dir_hash_table_pointer, dir_hash_table_entry_count);
  183. std::span<u32> file_hash_table(file_hash_table_pointer, file_hash_table_entry_count);
  184. std::span<u8> dir_table(dir_table_pointer, dir_table_size);
  185. std::span<u8> file_table(file_table_pointer, file_table_size);
  186. // Initialize hash tables.
  187. std::memset(dir_hash_table.data(), 0xFF, dir_hash_table.size_bytes());
  188. std::memset(file_hash_table.data(), 0xFF, file_hash_table.size_bytes());
  189. // Sort tables by name.
  190. std::sort(files.begin(), files.end(),
  191. [](const auto& a, const auto& b) { return a->path < b->path; });
  192. std::sort(directories.begin(), directories.end(),
  193. [](const auto& a, const auto& b) { return a->path < b->path; });
  194. // Determine file offsets.
  195. u32 entry_offset = 0;
  196. std::shared_ptr<RomFSBuildFileContext> prev_file = nullptr;
  197. for (const auto& cur_file : files) {
  198. file_partition_size = Common::AlignUp(file_partition_size, 16);
  199. cur_file->offset = file_partition_size;
  200. file_partition_size += cur_file->size;
  201. cur_file->entry_offset = entry_offset;
  202. entry_offset +=
  203. static_cast<u32>(sizeof(RomFSFileEntry) +
  204. Common::AlignUp(cur_file->path_len - cur_file->cur_path_ofs, 4));
  205. prev_file = cur_file;
  206. }
  207. // Assign deferred parent/sibling ownership.
  208. for (auto it = files.rbegin(); it != files.rend(); ++it) {
  209. auto& cur_file = *it;
  210. cur_file->sibling = cur_file->parent->file;
  211. cur_file->parent->file = cur_file;
  212. }
  213. // Determine directory offsets.
  214. entry_offset = 0;
  215. for (const auto& cur_dir : directories) {
  216. cur_dir->entry_offset = entry_offset;
  217. entry_offset +=
  218. static_cast<u32>(sizeof(RomFSDirectoryEntry) +
  219. Common::AlignUp(cur_dir->path_len - cur_dir->cur_path_ofs, 4));
  220. }
  221. // Assign deferred parent/sibling ownership.
  222. for (auto it = directories.rbegin(); (*it) != root; ++it) {
  223. auto& cur_dir = *it;
  224. cur_dir->sibling = cur_dir->parent->child;
  225. cur_dir->parent->child = cur_dir;
  226. }
  227. // Create output map.
  228. std::vector<std::pair<u64, VirtualFile>> out;
  229. out.reserve(num_files + 2);
  230. // Set header fields.
  231. header.header_size = sizeof(RomFSHeader);
  232. header.file_hash_table_size = file_hash_table_size;
  233. header.file_table_size = file_table_size;
  234. header.dir_hash_table_size = dir_hash_table_size;
  235. header.dir_table_size = dir_table_size;
  236. header.file_partition_ofs = ROMFS_FILEPARTITION_OFS;
  237. header.dir_hash_table_ofs = Common::AlignUp(header.file_partition_ofs + file_partition_size, 4);
  238. header.dir_table_ofs = header.dir_hash_table_ofs + header.dir_hash_table_size;
  239. header.file_hash_table_ofs = header.dir_table_ofs + header.dir_table_size;
  240. header.file_table_ofs = header.file_hash_table_ofs + header.file_hash_table_size;
  241. std::vector<u8> header_data(sizeof(RomFSHeader));
  242. std::memcpy(header_data.data(), &header, header_data.size());
  243. out.emplace_back(0, std::make_shared<VectorVfsFile>(std::move(header_data)));
  244. // Populate file tables.
  245. for (const auto& cur_file : files) {
  246. RomFSFileEntry cur_entry{};
  247. cur_entry.parent = cur_file->parent->entry_offset;
  248. cur_entry.sibling =
  249. cur_file->sibling == nullptr ? ROMFS_ENTRY_EMPTY : cur_file->sibling->entry_offset;
  250. cur_entry.offset = cur_file->offset;
  251. cur_entry.size = cur_file->size;
  252. const auto name_size = cur_file->path_len - cur_file->cur_path_ofs;
  253. const auto hash = romfs_calc_path_hash(cur_file->parent->entry_offset, cur_file->path,
  254. cur_file->cur_path_ofs, name_size);
  255. cur_entry.hash = file_hash_table[hash % file_hash_table_entry_count];
  256. file_hash_table[hash % file_hash_table_entry_count] = cur_file->entry_offset;
  257. cur_entry.name_size = name_size;
  258. out.emplace_back(cur_file->offset + ROMFS_FILEPARTITION_OFS, std::move(cur_file->source));
  259. std::memcpy(file_table.data() + cur_file->entry_offset, &cur_entry, sizeof(RomFSFileEntry));
  260. std::memset(file_table.data() + cur_file->entry_offset + sizeof(RomFSFileEntry), 0,
  261. Common::AlignUp(cur_entry.name_size, 4));
  262. std::memcpy(file_table.data() + cur_file->entry_offset + sizeof(RomFSFileEntry),
  263. cur_file->path.data() + cur_file->cur_path_ofs, name_size);
  264. }
  265. // Populate dir tables.
  266. for (const auto& cur_dir : directories) {
  267. RomFSDirectoryEntry cur_entry{};
  268. cur_entry.parent = cur_dir == root ? 0 : cur_dir->parent->entry_offset;
  269. cur_entry.sibling =
  270. cur_dir->sibling == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->sibling->entry_offset;
  271. cur_entry.child =
  272. cur_dir->child == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->child->entry_offset;
  273. cur_entry.file = cur_dir->file == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->file->entry_offset;
  274. const auto name_size = cur_dir->path_len - cur_dir->cur_path_ofs;
  275. const auto hash = romfs_calc_path_hash(cur_dir == root ? 0 : cur_dir->parent->entry_offset,
  276. cur_dir->path, cur_dir->cur_path_ofs, name_size);
  277. cur_entry.hash = dir_hash_table[hash % dir_hash_table_entry_count];
  278. dir_hash_table[hash % dir_hash_table_entry_count] = cur_dir->entry_offset;
  279. cur_entry.name_size = name_size;
  280. std::memcpy(dir_table.data() + cur_dir->entry_offset, &cur_entry,
  281. sizeof(RomFSDirectoryEntry));
  282. std::memset(dir_table.data() + cur_dir->entry_offset + sizeof(RomFSDirectoryEntry), 0,
  283. Common::AlignUp(cur_entry.name_size, 4));
  284. std::memcpy(dir_table.data() + cur_dir->entry_offset + sizeof(RomFSDirectoryEntry),
  285. cur_dir->path.data() + cur_dir->cur_path_ofs, name_size);
  286. }
  287. // Write metadata.
  288. out.emplace_back(header.dir_hash_table_ofs,
  289. std::make_shared<VectorVfsFile>(std::move(metadata)));
  290. // Sort the output.
  291. std::sort(out.begin(), out.end(),
  292. [](const auto& a, const auto& b) { return a.first < b.first; });
  293. return out;
  294. }
  295. } // namespace FileSys