DirectoryBlob.cpp 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270
  1. // Copyright 2008 Dolphin Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include "DiscIO/DirectoryBlob.h"
  4. #include <algorithm>
  5. #include <array>
  6. #include <cstring>
  7. #include <locale>
  8. #include <map>
  9. #include <memory>
  10. #include <set>
  11. #include <string>
  12. #include <utility>
  13. #include <variant>
  14. #include <vector>
  15. #include "Common/Align.h"
  16. #include "Common/Assert.h"
  17. #include "Common/CommonPaths.h"
  18. #include "Common/CommonTypes.h"
  19. #include "Common/FileUtil.h"
  20. #include "Common/IOFile.h"
  21. #include "Common/Logging/Log.h"
  22. #include "Common/StringUtil.h"
  23. #include "Common/Swap.h"
  24. #include "Core/Boot/DolReader.h"
  25. #include "Core/IOS/ES/Formats.h"
  26. #include "DiscIO/Blob.h"
  27. #include "DiscIO/DiscUtils.h"
  28. #include "DiscIO/VolumeDisc.h"
  29. #include "DiscIO/VolumeWii.h"
  30. #include "DiscIO/WiiEncryptionCache.h"
  31. namespace DiscIO
  32. {
  33. // Reads as many bytes as the vector fits (or less, if the file is smaller).
  34. // Returns the number of bytes read.
  35. static size_t ReadFileToVector(const std::string& path, std::vector<u8>* vector);
  36. static void PadToAddress(u64 start_address, u64* address, u64* length, u8** buffer);
  37. static void Write32(u32 data, u32 offset, std::vector<u8>* buffer);
  38. enum class PartitionType : u32
  39. {
  40. Game = 0,
  41. Update = 1,
  42. Channel = 2,
  43. // There are more types used by Super Smash Bros. Brawl, but they don't have special names
  44. };
  45. // 0xFF is an arbitrarily picked value. Note that we can't use 0x00, because that means NTSC-J
  46. constexpr u32 INVALID_REGION = 0xFF;
  47. constexpr u32 PARTITION_DATA_OFFSET = 0x20000;
  48. constexpr u8 ENTRY_SIZE = 0x0c;
  49. constexpr u8 FILE_ENTRY = 0;
  50. constexpr u8 DIRECTORY_ENTRY = 1;
  51. DiscContent::DiscContent(u64 offset, u64 size, ContentSource source)
  52. : m_offset(offset), m_size(size), m_content_source(std::move(source))
  53. {
  54. }
  55. DiscContent::DiscContent(u64 offset) : m_offset(offset)
  56. {
  57. }
  58. u64 DiscContent::GetOffset() const
  59. {
  60. return m_offset;
  61. }
  62. u64 DiscContent::GetEndOffset() const
  63. {
  64. return m_offset + m_size;
  65. }
  66. u64 DiscContent::GetSize() const
  67. {
  68. return m_size;
  69. }
  70. bool DiscContent::Read(u64* offset, u64* length, u8** buffer, DirectoryBlobReader* blob) const
  71. {
  72. if (m_size == 0)
  73. return true;
  74. DEBUG_ASSERT(*offset >= m_offset);
  75. const u64 offset_in_content = *offset - m_offset;
  76. if (offset_in_content < m_size)
  77. {
  78. const u64 bytes_to_read = std::min(m_size - offset_in_content, *length);
  79. if (std::holds_alternative<ContentFile>(m_content_source))
  80. {
  81. const auto& content = std::get<ContentFile>(m_content_source);
  82. File::IOFile file(content.m_filename, "rb");
  83. if (!file.Seek(content.m_offset + offset_in_content, File::SeekOrigin::Begin) ||
  84. !file.ReadBytes(*buffer, bytes_to_read))
  85. {
  86. return false;
  87. }
  88. }
  89. else if (std::holds_alternative<ContentMemory>(m_content_source))
  90. {
  91. const auto& content = std::get<ContentMemory>(m_content_source);
  92. std::copy_n(content->begin() + offset_in_content, bytes_to_read, *buffer);
  93. }
  94. else if (std::holds_alternative<ContentPartition>(m_content_source))
  95. {
  96. const auto& content = std::get<ContentPartition>(m_content_source);
  97. const u64 decrypted_size = m_size * VolumeWii::BLOCK_DATA_SIZE / VolumeWii::BLOCK_TOTAL_SIZE;
  98. if (!blob->EncryptPartitionData(content.m_offset + offset_in_content, bytes_to_read, *buffer,
  99. content.m_partition_data_offset, decrypted_size))
  100. {
  101. return false;
  102. }
  103. }
  104. else if (std::holds_alternative<ContentVolume>(m_content_source))
  105. {
  106. const auto& source = std::get<ContentVolume>(m_content_source);
  107. if (!blob->GetWrappedVolume()->Read(source.m_offset + offset_in_content, bytes_to_read,
  108. *buffer, source.m_partition))
  109. {
  110. return false;
  111. }
  112. }
  113. else if (std::holds_alternative<ContentFixedByte>(m_content_source))
  114. {
  115. const ContentFixedByte& source = std::get<ContentFixedByte>(m_content_source);
  116. std::fill_n(*buffer, bytes_to_read, source.m_byte);
  117. }
  118. else
  119. {
  120. PanicAlertFmt("DirectoryBlob: Invalid content source in DiscContent.");
  121. return false;
  122. }
  123. *length -= bytes_to_read;
  124. *buffer += bytes_to_read;
  125. *offset += bytes_to_read;
  126. }
  127. return true;
  128. }
  129. void DiscContentContainer::Add(u64 offset, u64 size, ContentSource source)
  130. {
  131. if (size != 0)
  132. m_contents.emplace(offset, size, std::move(source));
  133. }
  134. u64 DiscContentContainer::CheckSizeAndAdd(u64 offset, const std::string& path)
  135. {
  136. const u64 size = File::GetSize(path);
  137. Add(offset, size, ContentFile{path, 0});
  138. return size;
  139. }
  140. u64 DiscContentContainer::CheckSizeAndAdd(u64 offset, u64 max_size, const std::string& path)
  141. {
  142. const u64 size = std::min(File::GetSize(path), max_size);
  143. Add(offset, size, ContentFile{path, 0});
  144. return size;
  145. }
  146. bool DiscContentContainer::Read(u64 offset, u64 length, u8* buffer, DirectoryBlobReader* blob) const
  147. {
  148. // Determine which DiscContent the offset refers to
  149. std::set<DiscContent>::const_iterator it = m_contents.upper_bound(DiscContent(offset));
  150. while (it != m_contents.end() && length > 0)
  151. {
  152. // Zero fill to start of DiscContent data
  153. PadToAddress(it->GetOffset(), &offset, &length, &buffer);
  154. if (length == 0)
  155. return true;
  156. if (!it->Read(&offset, &length, &buffer, blob))
  157. return false;
  158. ++it;
  159. DEBUG_ASSERT(it == m_contents.end() || it->GetOffset() >= offset);
  160. }
  161. // Zero fill if we went beyond the last DiscContent
  162. std::fill_n(buffer, static_cast<size_t>(length), 0);
  163. return true;
  164. }
  165. static std::optional<PartitionType> ParsePartitionDirectoryName(const std::string& name)
  166. {
  167. if (name.size() < 2)
  168. return {};
  169. if (!strcasecmp(name.c_str(), "DATA"))
  170. return PartitionType::Game;
  171. if (!strcasecmp(name.c_str(), "UPDATE"))
  172. return PartitionType::Update;
  173. if (!strcasecmp(name.c_str(), "CHANNEL"))
  174. return PartitionType::Channel;
  175. if (name[0] == 'P' || name[0] == 'p')
  176. {
  177. // e.g. "P-HA8E" (normally only used for Super Smash Bros. Brawl's VC partitions)
  178. if (name[1] == '-' && name.size() == 6)
  179. {
  180. const u32 result = Common::swap32(reinterpret_cast<const u8*>(name.data() + 2));
  181. return static_cast<PartitionType>(result);
  182. }
  183. // e.g. "P0"
  184. if (std::all_of(name.cbegin() + 1, name.cend(), [](char c) { return c >= '0' && c <= '9'; }))
  185. {
  186. u32 result;
  187. if (TryParse(name.substr(1), &result))
  188. return static_cast<PartitionType>(result);
  189. }
  190. }
  191. return {};
  192. }
  193. static bool IsDirectorySeparator(char c)
  194. {
  195. return c == '/'
  196. #ifdef _WIN32
  197. || c == '\\'
  198. #endif
  199. ;
  200. }
  201. static bool PathCharactersEqual(char a, char b)
  202. {
  203. return a == b || (IsDirectorySeparator(a) && IsDirectorySeparator(b));
  204. }
  205. static bool PathEndsWith(const std::string& path, const std::string& suffix)
  206. {
  207. if (suffix.size() > path.size())
  208. return false;
  209. std::string::const_iterator path_iterator = path.cend() - suffix.size();
  210. std::string::const_iterator suffix_iterator = suffix.cbegin();
  211. while (path_iterator != path.cend())
  212. {
  213. if (!PathCharactersEqual(*path_iterator, *suffix_iterator))
  214. return false;
  215. path_iterator++;
  216. suffix_iterator++;
  217. }
  218. return true;
  219. }
  220. static bool IsValidDirectoryBlob(const std::string& dol_path, std::string* partition_root,
  221. std::string* true_root = nullptr)
  222. {
  223. if (!PathEndsWith(dol_path, "/sys/main.dol"))
  224. return false;
  225. const size_t chars_to_remove = std::string("sys/main.dol").size();
  226. *partition_root = dol_path.substr(0, dol_path.size() - chars_to_remove);
  227. if (File::GetSize(*partition_root + "sys/boot.bin") < 0x20)
  228. return false;
  229. #ifdef _WIN32
  230. constexpr const char* dir_separator = "/\\";
  231. #else
  232. constexpr char dir_separator = '/';
  233. #endif
  234. if (true_root)
  235. {
  236. *true_root =
  237. dol_path.substr(0, dol_path.find_last_of(dir_separator, partition_root->size() - 2) + 1);
  238. }
  239. return true;
  240. }
  241. static bool ExistsAndIsValidDirectoryBlob(const std::string& dol_path)
  242. {
  243. std::string partition_root;
  244. return File::Exists(dol_path) && IsValidDirectoryBlob(dol_path, &partition_root);
  245. }
  246. static bool IsInFilesDirectory(const std::string& path)
  247. {
  248. size_t files_pos = std::string::npos;
  249. while (true)
  250. {
  251. files_pos = path.rfind("files", files_pos);
  252. if (files_pos == std::string::npos)
  253. return false;
  254. const size_t slash_before_pos = files_pos - 1;
  255. const size_t slash_after_pos = files_pos + 5;
  256. if ((files_pos == 0 || IsDirectorySeparator(path[slash_before_pos])) &&
  257. (slash_after_pos == path.size() || (IsDirectorySeparator(path[slash_after_pos]))) &&
  258. ExistsAndIsValidDirectoryBlob(path.substr(0, files_pos) + "sys/main.dol"))
  259. {
  260. return true;
  261. }
  262. --files_pos;
  263. }
  264. }
  265. static bool IsMainDolForNonGamePartition(const std::string& path)
  266. {
  267. std::string partition_root, true_root;
  268. if (!IsValidDirectoryBlob(path, &partition_root, &true_root))
  269. return false; // This is not a /sys/main.dol
  270. std::string partition_directory_name = partition_root.substr(true_root.size());
  271. partition_directory_name.pop_back(); // Remove trailing slash
  272. const std::optional<PartitionType> partition_type =
  273. ParsePartitionDirectoryName(partition_directory_name);
  274. if (!partition_type || *partition_type == PartitionType::Game)
  275. return false; // volume_path is the game partition's /sys/main.dol
  276. const File::FSTEntry true_root_entry = File::ScanDirectoryTree(true_root, false);
  277. for (const File::FSTEntry& entry : true_root_entry.children)
  278. {
  279. if (entry.isDirectory &&
  280. ParsePartitionDirectoryName(entry.virtualName) == PartitionType::Game &&
  281. ExistsAndIsValidDirectoryBlob(entry.physicalName + "/sys/main.dol"))
  282. {
  283. return true; // volume_path is the /sys/main.dol for a non-game partition
  284. }
  285. }
  286. return false; // volume_path is the game partition's /sys/main.dol
  287. }
  288. bool ShouldHideFromGameList(const std::string& volume_path)
  289. {
  290. return IsInFilesDirectory(volume_path) || IsMainDolForNonGamePartition(volume_path);
  291. }
  292. std::unique_ptr<DirectoryBlobReader> DirectoryBlobReader::Create(const std::string& dol_path)
  293. {
  294. std::string partition_root, true_root;
  295. if (!IsValidDirectoryBlob(dol_path, &partition_root, &true_root))
  296. return nullptr;
  297. return std::unique_ptr<DirectoryBlobReader>(new DirectoryBlobReader(partition_root, true_root));
  298. }
  299. std::unique_ptr<DirectoryBlobReader> DirectoryBlobReader::Create(
  300. std::unique_ptr<DiscIO::VolumeDisc> volume,
  301. const std::function<void(std::vector<FSTBuilderNode>* fst_nodes)>& sys_callback,
  302. const std::function<void(std::vector<FSTBuilderNode>* fst_nodes, FSTBuilderNode* dol_node)>&
  303. fst_callback)
  304. {
  305. if (!volume)
  306. return nullptr;
  307. return std::unique_ptr<DirectoryBlobReader>(
  308. new DirectoryBlobReader(std::move(volume), sys_callback, fst_callback));
  309. }
  310. DirectoryBlobReader::DirectoryBlobReader(const std::string& game_partition_root,
  311. const std::string& true_root)
  312. : m_encryption_cache(this)
  313. {
  314. DirectoryBlobPartition game_partition(game_partition_root, {});
  315. m_is_wii = game_partition.IsWii();
  316. if (!m_is_wii)
  317. {
  318. m_gamecube_pseudopartition = std::move(game_partition);
  319. m_data_size = m_gamecube_pseudopartition.GetDataSize();
  320. m_encrypted = false;
  321. }
  322. else
  323. {
  324. std::vector<u8> disc_header(DISCHEADER_SIZE);
  325. game_partition.GetContents().Read(DISCHEADER_ADDRESS, DISCHEADER_SIZE, disc_header.data(),
  326. this);
  327. SetNonpartitionDiscHeaderFromFile(disc_header, game_partition_root);
  328. SetWiiRegionDataFromFile(game_partition_root);
  329. std::vector<PartitionWithType> partitions;
  330. partitions.emplace_back(std::move(game_partition), PartitionType::Game);
  331. std::string game_partition_directory_name = game_partition_root.substr(true_root.size());
  332. game_partition_directory_name.pop_back(); // Remove trailing slash
  333. if (ParsePartitionDirectoryName(game_partition_directory_name) == PartitionType::Game)
  334. {
  335. const File::FSTEntry true_root_entry = File::ScanDirectoryTree(true_root, false);
  336. for (const File::FSTEntry& entry : true_root_entry.children)
  337. {
  338. if (entry.isDirectory)
  339. {
  340. const std::optional<PartitionType> type = ParsePartitionDirectoryName(entry.virtualName);
  341. if (type && *type != PartitionType::Game)
  342. {
  343. partitions.emplace_back(DirectoryBlobPartition(entry.physicalName + "/", m_is_wii),
  344. *type);
  345. }
  346. }
  347. }
  348. }
  349. SetPartitions(std::move(partitions));
  350. }
  351. }
  352. DirectoryBlobReader::DirectoryBlobReader(
  353. std::unique_ptr<DiscIO::VolumeDisc> volume,
  354. const std::function<void(std::vector<FSTBuilderNode>* fst_nodes)>& sys_callback,
  355. const std::function<void(std::vector<FSTBuilderNode>* fst_nodes, FSTBuilderNode* dol_node)>&
  356. fst_callback)
  357. : m_encryption_cache(this), m_wrapped_volume(std::move(volume))
  358. {
  359. DirectoryBlobPartition game_partition(m_wrapped_volume.get(),
  360. m_wrapped_volume->GetGamePartition(), std::nullopt,
  361. sys_callback, fst_callback, this);
  362. m_is_wii = game_partition.IsWii();
  363. if (!m_is_wii)
  364. {
  365. m_gamecube_pseudopartition = std::move(game_partition);
  366. m_data_size = m_gamecube_pseudopartition.GetDataSize();
  367. m_encrypted = false;
  368. }
  369. else
  370. {
  371. std::vector<u8> header_bin(WII_NONPARTITION_DISCHEADER_SIZE);
  372. if (!m_wrapped_volume->Read(WII_NONPARTITION_DISCHEADER_ADDRESS,
  373. WII_NONPARTITION_DISCHEADER_SIZE, header_bin.data(),
  374. PARTITION_NONE))
  375. {
  376. header_bin.clear();
  377. }
  378. std::vector<u8> disc_header(DISCHEADER_SIZE);
  379. game_partition.GetContents().Read(DISCHEADER_ADDRESS, DISCHEADER_SIZE, disc_header.data(),
  380. this);
  381. SetNonpartitionDiscHeader(disc_header, std::move(header_bin));
  382. std::vector<u8> wii_region_data(WII_REGION_DATA_SIZE);
  383. if (!m_wrapped_volume->Read(WII_REGION_DATA_ADDRESS, WII_REGION_DATA_SIZE,
  384. wii_region_data.data(), PARTITION_NONE))
  385. {
  386. wii_region_data.clear();
  387. }
  388. SetWiiRegionData(wii_region_data, "volume");
  389. std::vector<PartitionWithType> partitions;
  390. partitions.emplace_back(std::move(game_partition), PartitionType::Game);
  391. for (Partition partition : m_wrapped_volume->GetPartitions())
  392. {
  393. if (partition == m_wrapped_volume->GetGamePartition())
  394. continue;
  395. auto type = m_wrapped_volume->GetPartitionType(partition);
  396. if (type)
  397. {
  398. partitions.emplace_back(DirectoryBlobPartition(m_wrapped_volume.get(), partition, m_is_wii,
  399. nullptr, nullptr, this),
  400. static_cast<PartitionType>(*type));
  401. }
  402. }
  403. SetPartitions(std::move(partitions));
  404. }
  405. }
  406. DirectoryBlobReader::DirectoryBlobReader(const DirectoryBlobReader& rhs)
  407. : m_gamecube_pseudopartition(rhs.m_gamecube_pseudopartition),
  408. m_nonpartition_contents(rhs.m_nonpartition_contents), m_partitions(rhs.m_partitions),
  409. m_encryption_cache(this), m_is_wii(rhs.m_is_wii), m_encrypted(rhs.m_encrypted),
  410. m_data_size(rhs.m_data_size),
  411. m_wrapped_volume(rhs.m_wrapped_volume ?
  412. CreateDisc(rhs.m_wrapped_volume->GetBlobReader().CopyReader()) :
  413. nullptr)
  414. {
  415. }
  416. bool DirectoryBlobReader::Read(u64 offset, u64 length, u8* buffer)
  417. {
  418. if (offset + length > m_data_size)
  419. return false;
  420. return (m_is_wii ? m_nonpartition_contents : m_gamecube_pseudopartition.GetContents())
  421. .Read(offset, length, buffer, this);
  422. }
  423. const DirectoryBlobPartition* DirectoryBlobReader::GetPartition(u64 offset, u64 size,
  424. u64 partition_data_offset) const
  425. {
  426. const auto it = m_partitions.find(partition_data_offset);
  427. if (it == m_partitions.end())
  428. return nullptr;
  429. if (offset + size > it->second.GetDataSize())
  430. return nullptr;
  431. return &it->second;
  432. }
  433. bool DirectoryBlobReader::SupportsReadWiiDecrypted(u64 offset, u64 size,
  434. u64 partition_data_offset) const
  435. {
  436. return static_cast<bool>(GetPartition(offset, size, partition_data_offset));
  437. }
  438. bool DirectoryBlobReader::ReadWiiDecrypted(u64 offset, u64 size, u8* buffer,
  439. u64 partition_data_offset)
  440. {
  441. const DirectoryBlobPartition* partition = GetPartition(offset, size, partition_data_offset);
  442. if (!partition)
  443. return false;
  444. return partition->GetContents().Read(offset, size, buffer, this);
  445. }
  446. bool DirectoryBlobReader::EncryptPartitionData(u64 offset, u64 size, u8* buffer,
  447. u64 partition_data_offset,
  448. u64 partition_data_decrypted_size)
  449. {
  450. auto it = m_partitions.find(partition_data_offset);
  451. if (it == m_partitions.end())
  452. return false;
  453. if (!m_encrypted)
  454. return it->second.GetContents().Read(offset, size, buffer, this);
  455. return m_encryption_cache.EncryptGroups(offset, size, buffer, partition_data_offset,
  456. partition_data_decrypted_size, it->second.GetKey());
  457. }
  458. BlobType DirectoryBlobReader::GetBlobType() const
  459. {
  460. return BlobType::DIRECTORY;
  461. }
  462. std::unique_ptr<BlobReader> DirectoryBlobReader::CopyReader() const
  463. {
  464. return std::unique_ptr<DirectoryBlobReader>(new DirectoryBlobReader(*this));
  465. }
  466. u64 DirectoryBlobReader::GetRawSize() const
  467. {
  468. // Not implemented
  469. return 0;
  470. }
  471. u64 DirectoryBlobReader::GetDataSize() const
  472. {
  473. return m_data_size;
  474. }
  475. void DirectoryBlobReader::SetNonpartitionDiscHeaderFromFile(const std::vector<u8>& partition_header,
  476. const std::string& game_partition_root)
  477. {
  478. std::vector<u8> header_bin(WII_NONPARTITION_DISCHEADER_SIZE);
  479. const size_t header_bin_bytes_read =
  480. ReadFileToVector(game_partition_root + "disc/header.bin", &header_bin);
  481. header_bin.resize(header_bin_bytes_read);
  482. SetNonpartitionDiscHeader(partition_header, std::move(header_bin));
  483. }
  484. void DirectoryBlobReader::SetNonpartitionDiscHeader(const std::vector<u8>& partition_header,
  485. std::vector<u8> header_bin)
  486. {
  487. const size_t header_bin_size = header_bin.size();
  488. header_bin.resize(WII_NONPARTITION_DISCHEADER_SIZE);
  489. // If header.bin is missing or smaller than expected, use the content of sys/boot.bin instead
  490. if (header_bin_size < header_bin.size())
  491. {
  492. std::copy(partition_header.data() + header_bin_size,
  493. partition_header.data() + header_bin.size(), header_bin.data() + header_bin_size);
  494. }
  495. // 0x60 and 0x61 are the only differences between the partition and non-partition headers
  496. if (header_bin_size < 0x60)
  497. header_bin[0x60] = 0;
  498. if (header_bin_size < 0x61)
  499. header_bin[0x61] = 0;
  500. m_encrypted =
  501. std::all_of(header_bin.data() + 0x60, header_bin.data() + 0x64, [](u8 x) { return x == 0; });
  502. m_nonpartition_contents.Add(WII_NONPARTITION_DISCHEADER_ADDRESS, std::move(header_bin));
  503. }
  504. void DirectoryBlobReader::SetWiiRegionDataFromFile(const std::string& game_partition_root)
  505. {
  506. std::vector<u8> wii_region_data(WII_REGION_DATA_SIZE);
  507. const std::string region_bin_path = game_partition_root + "disc/region.bin";
  508. const size_t bytes_read = ReadFileToVector(region_bin_path, &wii_region_data);
  509. wii_region_data.resize(bytes_read);
  510. SetWiiRegionData(wii_region_data, region_bin_path);
  511. }
  512. void DirectoryBlobReader::SetWiiRegionData(const std::vector<u8>& wii_region_data,
  513. const std::string& log_path)
  514. {
  515. std::vector<u8> region_data(0x10, 0x00);
  516. region_data.resize(WII_REGION_DATA_SIZE, 0x80);
  517. Write32(INVALID_REGION, 0, &region_data);
  518. std::copy_n(wii_region_data.begin(),
  519. std::min<size_t>(wii_region_data.size(), WII_REGION_DATA_SIZE), region_data.begin());
  520. if (wii_region_data.size() < 0x4)
  521. ERROR_LOG_FMT(DISCIO, "Couldn't read region from {}", log_path);
  522. else if (wii_region_data.size() < 0x20)
  523. ERROR_LOG_FMT(DISCIO, "Couldn't read age ratings from {}", log_path);
  524. m_nonpartition_contents.Add(WII_REGION_DATA_ADDRESS, std::move(region_data));
  525. }
  526. void DirectoryBlobReader::SetPartitions(std::vector<PartitionWithType>&& partitions)
  527. {
  528. std::ranges::sort(partitions, [](const PartitionWithType& lhs, const PartitionWithType& rhs) {
  529. if (lhs.type == rhs.type)
  530. return lhs.partition.GetRootDirectory() < rhs.partition.GetRootDirectory();
  531. // Ascending sort by partition type, except Update (1) comes before before Game (0)
  532. return (lhs.type > PartitionType::Update || rhs.type > PartitionType::Update) ?
  533. lhs.type < rhs.type :
  534. lhs.type > rhs.type;
  535. });
  536. u32 subtable_1_size = 0;
  537. while (subtable_1_size < partitions.size() && subtable_1_size < 3 &&
  538. partitions[subtable_1_size].type <= PartitionType::Channel)
  539. {
  540. ++subtable_1_size;
  541. }
  542. const u32 subtable_2_size = static_cast<u32>(partitions.size() - subtable_1_size);
  543. constexpr u32 PARTITION_TABLE_ADDRESS = 0x40000;
  544. constexpr u32 PARTITION_SUBTABLE1_OFFSET = 0x20;
  545. constexpr u32 PARTITION_SUBTABLE2_OFFSET = 0x40;
  546. std::vector<u8> partition_table(PARTITION_SUBTABLE2_OFFSET + subtable_2_size * 8);
  547. Write32(subtable_1_size, 0x0, &partition_table);
  548. Write32((PARTITION_TABLE_ADDRESS + PARTITION_SUBTABLE1_OFFSET) >> 2, 0x4, &partition_table);
  549. if (subtable_2_size != 0)
  550. {
  551. Write32(subtable_2_size, 0x8, &partition_table);
  552. Write32((PARTITION_TABLE_ADDRESS + PARTITION_SUBTABLE2_OFFSET) >> 2, 0xC, &partition_table);
  553. }
  554. constexpr u64 STANDARD_UPDATE_PARTITION_ADDRESS = 0x50000;
  555. constexpr u64 STANDARD_GAME_PARTITION_ADDRESS = 0xF800000;
  556. u64 partition_address = STANDARD_UPDATE_PARTITION_ADDRESS;
  557. u64 offset_in_table = PARTITION_SUBTABLE1_OFFSET;
  558. for (size_t i = 0; i < partitions.size(); ++i)
  559. {
  560. if (i == subtable_1_size)
  561. offset_in_table = PARTITION_SUBTABLE2_OFFSET;
  562. if (partitions[i].type == PartitionType::Game)
  563. partition_address = std::max(partition_address, STANDARD_GAME_PARTITION_ADDRESS);
  564. Write32(static_cast<u32>(partition_address >> 2), offset_in_table, &partition_table);
  565. offset_in_table += 4;
  566. Write32(static_cast<u32>(partitions[i].type), offset_in_table, &partition_table);
  567. offset_in_table += 4;
  568. SetPartitionHeader(&partitions[i].partition, partition_address);
  569. const u64 data_size =
  570. Common::AlignUp(partitions[i].partition.GetDataSize(), VolumeWii::BLOCK_DATA_SIZE);
  571. partitions[i].partition.SetDataSize(data_size);
  572. const u64 encrypted_data_size =
  573. (data_size / VolumeWii::BLOCK_DATA_SIZE) * VolumeWii::BLOCK_TOTAL_SIZE;
  574. const u64 partition_data_offset = partition_address + PARTITION_DATA_OFFSET;
  575. m_partitions.emplace(partition_data_offset, std::move(partitions[i].partition));
  576. m_nonpartition_contents.Add(partition_data_offset, encrypted_data_size,
  577. ContentPartition{0, partition_data_offset});
  578. const u64 unaligned_next_partition_address = VolumeWii::OffsetInHashedPartitionToRawOffset(
  579. data_size, Partition(partition_address), PARTITION_DATA_OFFSET);
  580. partition_address = Common::AlignUp(unaligned_next_partition_address, 0x10000ull);
  581. }
  582. m_data_size = partition_address;
  583. m_nonpartition_contents.Add(PARTITION_TABLE_ADDRESS, std::move(partition_table));
  584. }
  585. // This function sets the header that's shortly before the start of the encrypted
  586. // area, not the header that's right at the beginning of the encrypted area
  587. void DirectoryBlobReader::SetPartitionHeader(DirectoryBlobPartition* partition,
  588. u64 partition_address)
  589. {
  590. constexpr u32 TMD_OFFSET = 0x2c0;
  591. constexpr u32 H3_OFFSET = 0x4000;
  592. const std::optional<DiscIO::Partition>& wrapped_partition = partition->GetWrappedPartition();
  593. const std::string& partition_root = partition->GetRootDirectory();
  594. u64 ticket_size;
  595. if (wrapped_partition)
  596. {
  597. std::vector<u8> new_ticket = m_wrapped_volume->GetTicket(*wrapped_partition).GetBytes();
  598. if (new_ticket.size() > WII_PARTITION_TICKET_SIZE)
  599. new_ticket.resize(WII_PARTITION_TICKET_SIZE);
  600. ticket_size = new_ticket.size();
  601. m_nonpartition_contents.Add(partition_address + WII_PARTITION_TICKET_ADDRESS,
  602. std::move(new_ticket));
  603. }
  604. else
  605. {
  606. ticket_size = m_nonpartition_contents.CheckSizeAndAdd(
  607. partition_address + WII_PARTITION_TICKET_ADDRESS, WII_PARTITION_TICKET_SIZE,
  608. partition_root + "ticket.bin");
  609. }
  610. u64 tmd_size;
  611. if (wrapped_partition)
  612. {
  613. std::vector<u8> new_tmd = m_wrapped_volume->GetTMD(*wrapped_partition).GetBytes();
  614. if (new_tmd.size() > IOS::ES::MAX_TMD_SIZE)
  615. new_tmd.resize(IOS::ES::MAX_TMD_SIZE);
  616. tmd_size = new_tmd.size();
  617. m_nonpartition_contents.Add(partition_address + TMD_OFFSET, std::move(new_tmd));
  618. }
  619. else
  620. {
  621. tmd_size = m_nonpartition_contents.CheckSizeAndAdd(
  622. partition_address + TMD_OFFSET, IOS::ES::MAX_TMD_SIZE, partition_root + "tmd.bin");
  623. }
  624. const u64 cert_offset = Common::AlignUp(TMD_OFFSET + tmd_size, 0x20ull);
  625. const u64 max_cert_size = H3_OFFSET - cert_offset;
  626. u64 cert_size;
  627. if (wrapped_partition)
  628. {
  629. std::vector<u8> new_cert = m_wrapped_volume->GetCertificateChain(*wrapped_partition);
  630. if (new_cert.size() > max_cert_size)
  631. new_cert.resize(max_cert_size);
  632. cert_size = new_cert.size();
  633. m_nonpartition_contents.Add(partition_address + cert_offset, std::move(new_cert));
  634. }
  635. else
  636. {
  637. cert_size = m_nonpartition_contents.CheckSizeAndAdd(partition_address + cert_offset,
  638. max_cert_size, partition_root + "cert.bin");
  639. }
  640. if (wrapped_partition)
  641. {
  642. if (m_wrapped_volume->HasWiiHashes())
  643. {
  644. const std::optional<u64> offset = m_wrapped_volume->ReadSwappedAndShifted(
  645. wrapped_partition->offset + WII_PARTITION_H3_OFFSET_ADDRESS, PARTITION_NONE);
  646. if (offset)
  647. {
  648. std::vector<u8> new_h3(WII_PARTITION_H3_SIZE);
  649. if (m_wrapped_volume->Read(wrapped_partition->offset + *offset, new_h3.size(),
  650. new_h3.data(), PARTITION_NONE))
  651. {
  652. m_nonpartition_contents.Add(partition_address + H3_OFFSET, std::move(new_h3));
  653. }
  654. }
  655. }
  656. }
  657. else
  658. {
  659. m_nonpartition_contents.CheckSizeAndAdd(partition_address + H3_OFFSET, WII_PARTITION_H3_SIZE,
  660. partition_root + "h3.bin");
  661. }
  662. constexpr u32 PARTITION_HEADER_SIZE = 0x1c;
  663. const u64 data_size = Common::AlignUp(partition->GetDataSize(), 0x7c00) / 0x7c00 * 0x8000;
  664. std::vector<u8> partition_header(PARTITION_HEADER_SIZE);
  665. Write32(static_cast<u32>(tmd_size), 0x0, &partition_header);
  666. Write32(TMD_OFFSET >> 2, 0x4, &partition_header);
  667. Write32(static_cast<u32>(cert_size), 0x8, &partition_header);
  668. Write32(static_cast<u32>(cert_offset >> 2), 0x0C, &partition_header);
  669. Write32(H3_OFFSET >> 2, 0x10, &partition_header);
  670. Write32(PARTITION_DATA_OFFSET >> 2, 0x14, &partition_header);
  671. Write32(static_cast<u32>(data_size >> 2), 0x18, &partition_header);
  672. m_nonpartition_contents.Add(partition_address + WII_PARTITION_TICKET_SIZE,
  673. std::move(partition_header));
  674. std::vector<u8> ticket_buffer(ticket_size);
  675. m_nonpartition_contents.Read(partition_address + WII_PARTITION_TICKET_ADDRESS, ticket_size,
  676. ticket_buffer.data(), this);
  677. IOS::ES::TicketReader ticket(std::move(ticket_buffer));
  678. if (ticket.IsValid())
  679. partition->SetKey(ticket.GetTitleKey());
  680. }
  681. static void GenerateBuilderNodesFromFileSystem(const DiscIO::VolumeDisc& volume,
  682. const DiscIO::Partition& partition,
  683. std::vector<FSTBuilderNode>* nodes,
  684. const FileInfo& parent_info)
  685. {
  686. for (const FileInfo& file_info : parent_info)
  687. {
  688. if (file_info.IsDirectory())
  689. {
  690. std::vector<FSTBuilderNode> child_nodes;
  691. GenerateBuilderNodesFromFileSystem(volume, partition, &child_nodes, file_info);
  692. nodes->emplace_back(FSTBuilderNode{file_info.GetName(), file_info.GetTotalChildren(),
  693. std::move(child_nodes)});
  694. }
  695. else
  696. {
  697. std::vector<BuilderContentSource> source;
  698. source.emplace_back(BuilderContentSource{0, file_info.GetSize(),
  699. ContentVolume{file_info.GetOffset(), partition}});
  700. nodes->emplace_back(
  701. FSTBuilderNode{file_info.GetName(), file_info.GetSize(), std::move(source)});
  702. }
  703. }
  704. }
  705. DirectoryBlobPartition::DirectoryBlobPartition(const std::string& root_directory,
  706. std::optional<bool> is_wii)
  707. : m_root_directory(root_directory)
  708. {
  709. std::vector<u8> disc_header(DISCHEADER_SIZE);
  710. if (ReadFileToVector(m_root_directory + "sys/boot.bin", &disc_header) < 0x20)
  711. ERROR_LOG_FMT(DISCIO, "{} doesn't exist or is too small", m_root_directory + "sys/boot.bin");
  712. SetDiscType(is_wii, disc_header);
  713. SetBI2FromFile(m_root_directory + "sys/bi2.bin");
  714. const u64 dol_address = SetApploaderFromFile(m_root_directory + "sys/apploader.img");
  715. const u64 fst_address =
  716. SetDOLFromFile(m_root_directory + "sys/main.dol", dol_address, &disc_header);
  717. BuildFSTFromFolder(m_root_directory + "files/", fst_address, &disc_header);
  718. m_contents.Add(DISCHEADER_ADDRESS, disc_header);
  719. }
  720. static void FillSingleFileNode(FSTBuilderNode* node, std::vector<u8> data)
  721. {
  722. std::vector<BuilderContentSource> contents;
  723. const size_t size = data.size();
  724. contents.emplace_back(
  725. BuilderContentSource{0, size, std::make_shared<std::vector<u8>>(std::move(data))});
  726. node->m_size = size;
  727. node->m_content = std::move(contents);
  728. }
  729. static FSTBuilderNode BuildSingleFileNode(std::string filename, std::vector<u8> data,
  730. void* userdata)
  731. {
  732. FSTBuilderNode node{std::move(filename), 0, {}, userdata};
  733. FillSingleFileNode(&node, std::move(data));
  734. return node;
  735. }
  736. static std::vector<u8> ExtractNodeToVector(std::vector<FSTBuilderNode>* nodes, void* userdata,
  737. DirectoryBlobReader* blob)
  738. {
  739. std::vector<u8> data;
  740. const auto it =
  741. std::find_if(nodes->begin(), nodes->end(), [&userdata](const FSTBuilderNode& node) {
  742. return node.m_user_data == userdata;
  743. });
  744. if (it == nodes->end() || !it->IsFile())
  745. return data;
  746. DiscContentContainer tmp;
  747. for (auto& content : it->GetFileContent())
  748. tmp.Add(content.m_offset, content.m_size, std::move(content.m_source));
  749. data.resize(it->m_size);
  750. tmp.Read(0, it->m_size, data.data(), blob);
  751. return data;
  752. }
  753. DirectoryBlobPartition::DirectoryBlobPartition(
  754. DiscIO::VolumeDisc* volume, const DiscIO::Partition& partition, std::optional<bool> is_wii,
  755. const std::function<void(std::vector<FSTBuilderNode>* fst_nodes)>& sys_callback,
  756. const std::function<void(std::vector<FSTBuilderNode>* fst_nodes, FSTBuilderNode* dol_node)>&
  757. fst_callback,
  758. DirectoryBlobReader* blob)
  759. : m_wrapped_partition(partition)
  760. {
  761. std::vector<FSTBuilderNode> sys_nodes;
  762. std::vector<u8> disc_header(DISCHEADER_SIZE);
  763. if (!volume->Read(DISCHEADER_ADDRESS, DISCHEADER_SIZE, disc_header.data(), partition))
  764. disc_header.clear();
  765. sys_nodes.emplace_back(BuildSingleFileNode("boot.bin", std::move(disc_header), &disc_header));
  766. std::vector<u8> bi2(BI2_SIZE);
  767. if (!volume->Read(BI2_ADDRESS, BI2_SIZE, bi2.data(), partition))
  768. bi2.clear();
  769. sys_nodes.emplace_back(BuildSingleFileNode("bi2.bin", std::move(bi2), &bi2));
  770. std::vector<u8> apploader;
  771. const auto apploader_size = GetApploaderSize(*volume, partition);
  772. auto& apploader_node = sys_nodes.emplace_back(FSTBuilderNode{"apploader.img", 0, {}, &apploader});
  773. if (apploader_size)
  774. {
  775. apploader.resize(*apploader_size);
  776. if (!volume->Read(APPLOADER_ADDRESS, *apploader_size, apploader.data(), partition))
  777. apploader.clear();
  778. FillSingleFileNode(&apploader_node, std::move(apploader));
  779. }
  780. if (sys_callback)
  781. sys_callback(&sys_nodes);
  782. disc_header = ExtractNodeToVector(&sys_nodes, &disc_header, blob);
  783. disc_header.resize(DISCHEADER_SIZE);
  784. SetDiscType(is_wii, disc_header);
  785. SetBI2(ExtractNodeToVector(&sys_nodes, &bi2, blob));
  786. const u64 new_dol_address =
  787. SetApploader(ExtractNodeToVector(&sys_nodes, &apploader, blob), "apploader");
  788. FSTBuilderNode dol_node{"main.dol", 0, {}};
  789. const auto dol_offset = GetBootDOLOffset(*volume, partition);
  790. if (dol_offset)
  791. {
  792. const auto dol_size = GetBootDOLSize(*volume, partition, *dol_offset);
  793. if (dol_size)
  794. {
  795. std::vector<BuilderContentSource> dol_contents;
  796. dol_contents.emplace_back(
  797. BuilderContentSource{0, *dol_size, ContentVolume{*dol_offset, partition}});
  798. dol_node.m_size = *dol_size;
  799. dol_node.m_content = std::move(dol_contents);
  800. }
  801. }
  802. std::vector<FSTBuilderNode> nodes;
  803. const FileSystem* fs = volume->GetFileSystem(partition);
  804. if (fs && fs->IsValid())
  805. GenerateBuilderNodesFromFileSystem(*volume, partition, &nodes, fs->GetRoot());
  806. if (fst_callback)
  807. fst_callback(&nodes, &dol_node);
  808. const u64 new_fst_address = SetDOL(std::move(dol_node), new_dol_address, &disc_header);
  809. BuildFST(std::move(nodes), new_fst_address, &disc_header);
  810. m_contents.Add(DISCHEADER_ADDRESS, disc_header);
  811. }
  812. void DirectoryBlobPartition::SetDiscType(std::optional<bool> is_wii,
  813. const std::vector<u8>& disc_header)
  814. {
  815. if (is_wii.has_value())
  816. {
  817. m_is_wii = *is_wii;
  818. }
  819. else
  820. {
  821. m_is_wii = Common::swap32(&disc_header[0x18]) == WII_DISC_MAGIC;
  822. const bool is_gc = Common::swap32(&disc_header[0x1c]) == GAMECUBE_DISC_MAGIC;
  823. if (m_is_wii == is_gc)
  824. {
  825. ERROR_LOG_FMT(DISCIO, "Couldn't detect disc type based on disc header; assuming {}",
  826. m_is_wii ? "Wii" : "GameCube");
  827. }
  828. }
  829. m_address_shift = m_is_wii ? 2 : 0;
  830. }
  831. void DirectoryBlobPartition::SetBI2FromFile(const std::string& bi2_path)
  832. {
  833. std::vector<u8> bi2(BI2_SIZE);
  834. if (!m_is_wii)
  835. Write32(INVALID_REGION, 0x18, &bi2);
  836. const size_t bytes_read = ReadFileToVector(bi2_path, &bi2);
  837. if (!m_is_wii && bytes_read < 0x1C)
  838. ERROR_LOG_FMT(DISCIO, "Couldn't read region from {}", bi2_path);
  839. m_contents.Add(BI2_ADDRESS, std::move(bi2));
  840. }
  841. void DirectoryBlobPartition::SetBI2(std::vector<u8> bi2)
  842. {
  843. const size_t bi2_size = bi2.size();
  844. bi2.resize(BI2_SIZE);
  845. if (!m_is_wii && bi2_size < 0x1C)
  846. Write32(INVALID_REGION, 0x18, &bi2);
  847. m_contents.Add(BI2_ADDRESS, std::move(bi2));
  848. }
  849. u64 DirectoryBlobPartition::SetApploaderFromFile(const std::string& path)
  850. {
  851. File::IOFile file(path, "rb");
  852. std::vector<u8> apploader(file.GetSize());
  853. file.ReadBytes(apploader.data(), apploader.size());
  854. return SetApploader(std::move(apploader), path);
  855. }
  856. u64 DirectoryBlobPartition::SetApploader(std::vector<u8> apploader, const std::string& log_path)
  857. {
  858. bool success = false;
  859. if (apploader.size() < 0x20)
  860. {
  861. ERROR_LOG_FMT(DISCIO, "{} couldn't be accessed or is too small", log_path);
  862. }
  863. else
  864. {
  865. const size_t apploader_size =
  866. 0x20 + Common::swap32(*(u32*)&apploader[0x14]) + Common::swap32(*(u32*)&apploader[0x18]);
  867. if (apploader_size != apploader.size())
  868. ERROR_LOG_FMT(DISCIO, "{} is the wrong size... Is it really an apploader?", log_path);
  869. else
  870. success = true;
  871. }
  872. if (!success)
  873. {
  874. apploader.resize(0x20);
  875. // Make sure BS2 HLE doesn't try to run the apploader
  876. Write32(static_cast<u32>(-1), 0x10, &apploader);
  877. }
  878. size_t apploader_size = apploader.size();
  879. m_contents.Add(APPLOADER_ADDRESS, std::move(apploader));
  880. // Return DOL address, 32 byte aligned (plus 32 byte padding)
  881. return Common::AlignUp(APPLOADER_ADDRESS + apploader_size + 0x20, 0x20ull);
  882. }
  883. u64 DirectoryBlobPartition::SetDOLFromFile(const std::string& path, u64 dol_address,
  884. std::vector<u8>* disc_header)
  885. {
  886. const u64 dol_size = m_contents.CheckSizeAndAdd(dol_address, path);
  887. Write32(static_cast<u32>(dol_address >> m_address_shift), 0x0420, disc_header);
  888. // Return FST address, 32 byte aligned (plus 32 byte padding)
  889. return Common::AlignUp(dol_address + dol_size + 0x20, 0x20ull);
  890. }
  891. u64 DirectoryBlobPartition::SetDOL(FSTBuilderNode dol_node, u64 dol_address,
  892. std::vector<u8>* disc_header)
  893. {
  894. for (auto& content : dol_node.GetFileContent())
  895. m_contents.Add(dol_address + content.m_offset, content.m_size, std::move(content.m_source));
  896. Write32(static_cast<u32>(dol_address >> m_address_shift), 0x0420, disc_header);
  897. // Return FST address, 32 byte aligned (plus 32 byte padding)
  898. return Common::AlignUp(dol_address + dol_node.m_size + 0x20, 0x20ull);
  899. }
  900. static std::vector<FSTBuilderNode> ConvertFSTEntriesToBuilderNodes(const File::FSTEntry& parent)
  901. {
  902. std::vector<FSTBuilderNode> nodes;
  903. nodes.reserve(parent.children.size());
  904. for (const File::FSTEntry& entry : parent.children)
  905. {
  906. std::variant<std::vector<BuilderContentSource>, std::vector<FSTBuilderNode>> content;
  907. if (entry.isDirectory)
  908. {
  909. content = ConvertFSTEntriesToBuilderNodes(entry);
  910. }
  911. else
  912. {
  913. content =
  914. std::vector<BuilderContentSource>{{0, entry.size, ContentFile{entry.physicalName, 0}}};
  915. }
  916. nodes.emplace_back(FSTBuilderNode{entry.virtualName, entry.size, std::move(content)});
  917. }
  918. return nodes;
  919. }
  920. void DirectoryBlobPartition::BuildFSTFromFolder(const std::string& fst_root_path, u64 fst_address,
  921. std::vector<u8>* disc_header)
  922. {
  923. auto nodes = ConvertFSTEntriesToBuilderNodes(File::ScanDirectoryTree(fst_root_path, true));
  924. BuildFST(std::move(nodes), fst_address, disc_header);
  925. }
  926. static void ConvertUTF8NamesToSHIFTJIS(std::vector<FSTBuilderNode>* fst)
  927. {
  928. for (FSTBuilderNode& entry : *fst)
  929. {
  930. if (entry.IsFolder())
  931. ConvertUTF8NamesToSHIFTJIS(&entry.GetFolderContent());
  932. entry.m_filename = UTF8ToSHIFTJIS(entry.m_filename);
  933. }
  934. }
  935. static u32 ComputeNameSize(const std::vector<FSTBuilderNode>& files)
  936. {
  937. u32 name_size = 0;
  938. for (const FSTBuilderNode& entry : files)
  939. {
  940. if (entry.IsFolder())
  941. name_size += ComputeNameSize(entry.GetFolderContent());
  942. name_size += static_cast<u32>(entry.m_filename.length() + 1);
  943. }
  944. return name_size;
  945. }
  946. static size_t RecalculateFolderSizes(std::vector<FSTBuilderNode>* fst)
  947. {
  948. size_t size = 0;
  949. for (FSTBuilderNode& entry : *fst)
  950. {
  951. ++size;
  952. if (entry.IsFile())
  953. continue;
  954. entry.m_size = RecalculateFolderSizes(&entry.GetFolderContent());
  955. size += entry.m_size;
  956. }
  957. return size;
  958. }
  959. void DirectoryBlobPartition::BuildFST(std::vector<FSTBuilderNode> root_nodes, u64 fst_address,
  960. std::vector<u8>* disc_header)
  961. {
  962. ConvertUTF8NamesToSHIFTJIS(&root_nodes);
  963. u32 name_table_size = Common::AlignUp(ComputeNameSize(root_nodes), 1ull << m_address_shift);
  964. // 1 extra for the root entry
  965. u64 total_entries = RecalculateFolderSizes(&root_nodes) + 1;
  966. const u64 name_table_offset = total_entries * ENTRY_SIZE;
  967. std::vector<u8> fst_data(name_table_offset + name_table_size);
  968. // 32 KiB aligned start of data on disc
  969. u64 current_data_address = Common::AlignUp(fst_address + fst_data.size(), 0x8000ull);
  970. u32 fst_offset = 0; // Offset within FST data
  971. u32 name_offset = 0; // Offset within name table
  972. u32 root_offset = 0; // Offset of root of FST
  973. // write root entry
  974. WriteEntryData(&fst_data, &fst_offset, DIRECTORY_ENTRY, 0, 0, total_entries, m_address_shift);
  975. WriteDirectory(&fst_data, &root_nodes, &fst_offset, &name_offset, &current_data_address,
  976. root_offset, name_table_offset);
  977. // overflow check, compare the aligned name offset with the aligned name table size
  978. ASSERT(Common::AlignUp(name_offset, 1ull << m_address_shift) == name_table_size);
  979. // write FST size and location
  980. Write32((u32)(fst_address >> m_address_shift), 0x0424, disc_header);
  981. Write32((u32)(fst_data.size() >> m_address_shift), 0x0428, disc_header);
  982. Write32((u32)(fst_data.size() >> m_address_shift), 0x042c, disc_header);
  983. m_contents.Add(fst_address, std::move(fst_data));
  984. m_data_size = current_data_address;
  985. }
  986. void DirectoryBlobPartition::WriteEntryData(std::vector<u8>* fst_data, u32* entry_offset, u8 type,
  987. u32 name_offset, u64 data_offset, u64 length,
  988. u32 address_shift)
  989. {
  990. (*fst_data)[(*entry_offset)++] = type;
  991. (*fst_data)[(*entry_offset)++] = (name_offset >> 16) & 0xff;
  992. (*fst_data)[(*entry_offset)++] = (name_offset >> 8) & 0xff;
  993. (*fst_data)[(*entry_offset)++] = (name_offset)&0xff;
  994. Write32((u32)(data_offset >> address_shift), *entry_offset, fst_data);
  995. *entry_offset += 4;
  996. Write32((u32)length, *entry_offset, fst_data);
  997. *entry_offset += 4;
  998. }
  999. void DirectoryBlobPartition::WriteEntryName(std::vector<u8>* fst_data, u32* name_offset,
  1000. const std::string& name, u64 name_table_offset)
  1001. {
  1002. strncpy((char*)&(*fst_data)[*name_offset + name_table_offset], name.c_str(), name.length() + 1);
  1003. *name_offset += (u32)(name.length() + 1);
  1004. }
  1005. void DirectoryBlobPartition::WriteDirectory(std::vector<u8>* fst_data,
  1006. std::vector<FSTBuilderNode>* parent_entries,
  1007. u32* fst_offset, u32* name_offset, u64* data_offset,
  1008. u32 parent_entry_index, u64 name_table_offset)
  1009. {
  1010. std::vector<FSTBuilderNode>& sorted_entries = *parent_entries;
  1011. // Sort for determinism
  1012. std::sort(sorted_entries.begin(), sorted_entries.end(),
  1013. [](const FSTBuilderNode& one, const FSTBuilderNode& two) {
  1014. std::string one_upper = one.m_filename;
  1015. std::string two_upper = two.m_filename;
  1016. Common::ToUpper(&one_upper);
  1017. Common::ToUpper(&two_upper);
  1018. return one_upper == two_upper ? one.m_filename < two.m_filename :
  1019. one_upper < two_upper;
  1020. });
  1021. for (FSTBuilderNode& entry : sorted_entries)
  1022. {
  1023. if (entry.IsFolder())
  1024. {
  1025. u32 entry_index = *fst_offset / ENTRY_SIZE;
  1026. WriteEntryData(fst_data, fst_offset, DIRECTORY_ENTRY, *name_offset, parent_entry_index,
  1027. entry_index + entry.m_size + 1, 0);
  1028. WriteEntryName(fst_data, name_offset, entry.m_filename, name_table_offset);
  1029. auto& child_nodes = entry.GetFolderContent();
  1030. WriteDirectory(fst_data, &child_nodes, fst_offset, name_offset, data_offset, entry_index,
  1031. name_table_offset);
  1032. }
  1033. else
  1034. {
  1035. // put entry in FST
  1036. WriteEntryData(fst_data, fst_offset, FILE_ENTRY, *name_offset, *data_offset, entry.m_size,
  1037. m_address_shift);
  1038. WriteEntryName(fst_data, name_offset, entry.m_filename, name_table_offset);
  1039. // write entry to virtual disc
  1040. auto& contents = entry.GetFileContent();
  1041. for (BuilderContentSource& content : contents)
  1042. {
  1043. m_contents.Add(*data_offset + content.m_offset, content.m_size,
  1044. std::move(content.m_source));
  1045. }
  1046. // 32 KiB aligned - many games are fine with less alignment, but not all
  1047. *data_offset = Common::AlignUp(*data_offset + entry.m_size, 0x8000ull);
  1048. }
  1049. }
  1050. }
  1051. static size_t ReadFileToVector(const std::string& path, std::vector<u8>* vector)
  1052. {
  1053. File::IOFile file(path, "rb");
  1054. size_t bytes_read;
  1055. file.ReadArray<u8>(vector->data(), std::min<u64>(file.GetSize(), vector->size()), &bytes_read);
  1056. return bytes_read;
  1057. }
  1058. static void PadToAddress(u64 start_address, u64* address, u64* length, u8** buffer)
  1059. {
  1060. if (start_address > *address && *length > 0)
  1061. {
  1062. u64 padBytes = std::min(start_address - *address, *length);
  1063. memset(*buffer, 0, (size_t)padBytes);
  1064. *length -= padBytes;
  1065. *buffer += padBytes;
  1066. *address += padBytes;
  1067. }
  1068. }
  1069. static void Write32(u32 data, u32 offset, std::vector<u8>* buffer)
  1070. {
  1071. (*buffer)[offset++] = (data >> 24);
  1072. (*buffer)[offset++] = (data >> 16) & 0xff;
  1073. (*buffer)[offset++] = (data >> 8) & 0xff;
  1074. (*buffer)[offset] = data & 0xff;
  1075. }
  1076. } // namespace DiscIO