DiscScrubber.cpp 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. // Copyright 2009 Dolphin Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include "DiscIO/DiscScrubber.h"
  4. #include <algorithm>
  5. #include <cstddef>
  6. #include <memory>
  7. #include <optional>
  8. #include <string>
  9. #include <vector>
  10. #include "Common/Align.h"
  11. #include "Common/Assert.h"
  12. #include "Common/CommonTypes.h"
  13. #include "Common/Logging/Log.h"
  14. #include "DiscIO/DiscUtils.h"
  15. #include "DiscIO/Filesystem.h"
  16. #include "DiscIO/Volume.h"
  17. namespace DiscIO
  18. {
  19. DiscScrubber::DiscScrubber() = default;
  20. bool DiscScrubber::SetupScrub(const Volume& disc)
  21. {
  22. m_file_size = disc.GetDataSize();
  23. m_has_wii_hashes = disc.HasWiiHashes();
  24. // Round up when diving by CLUSTER_SIZE, otherwise MarkAsUsed might write out of bounds
  25. const size_t num_clusters = static_cast<size_t>((m_file_size + CLUSTER_SIZE - 1) / CLUSTER_SIZE);
  26. // Table of free blocks
  27. m_free_table.resize(num_clusters, 1);
  28. // Fill out table of free blocks
  29. const bool success = ParseDisc(disc);
  30. m_is_scrubbing = success;
  31. return success;
  32. }
  33. bool DiscScrubber::CanBlockBeScrubbed(u64 offset) const
  34. {
  35. if (!m_is_scrubbing)
  36. return false;
  37. const u64 cluster_index = offset / CLUSTER_SIZE;
  38. return cluster_index >= m_free_table.size() || m_free_table[cluster_index];
  39. }
  40. void DiscScrubber::MarkAsUsed(u64 offset, u64 size)
  41. {
  42. u64 current_offset = Common::AlignDown(offset, CLUSTER_SIZE);
  43. const u64 end_offset = offset + size;
  44. DEBUG_LOG_FMT(DISCIO, "Marking {:#018x} - {:#018x} as used", offset, end_offset);
  45. while (current_offset < end_offset && current_offset < m_file_size)
  46. {
  47. m_free_table[current_offset / CLUSTER_SIZE] = 0;
  48. current_offset += CLUSTER_SIZE;
  49. }
  50. }
  51. void DiscScrubber::MarkAsUsedE(u64 partition_data_offset, u64 offset, u64 size)
  52. {
  53. if (partition_data_offset == 0)
  54. {
  55. MarkAsUsed(offset, size);
  56. }
  57. else
  58. {
  59. u64 first_cluster_start = ToClusterOffset(offset) + partition_data_offset;
  60. u64 last_cluster_end;
  61. if (size == 0)
  62. {
  63. // Without this special case, a size of 0 can be rounded to 1 cluster instead of 0
  64. last_cluster_end = first_cluster_start;
  65. }
  66. else
  67. {
  68. last_cluster_end = ToClusterOffset(offset + size - 1) + CLUSTER_SIZE + partition_data_offset;
  69. }
  70. MarkAsUsed(first_cluster_start, last_cluster_end - first_cluster_start);
  71. }
  72. }
  73. // Compensate for 0x400 (SHA-1) per 0x8000 (cluster), and round to whole clusters
  74. u64 DiscScrubber::ToClusterOffset(u64 offset) const
  75. {
  76. if (m_has_wii_hashes)
  77. return offset / 0x7c00 * CLUSTER_SIZE;
  78. else
  79. return Common::AlignDown(offset, CLUSTER_SIZE);
  80. }
  81. // Helper functions for reading the BE volume
  82. bool DiscScrubber::ReadFromVolume(const Volume& disc, u64 offset, u32& buffer,
  83. const Partition& partition)
  84. {
  85. std::optional<u32> value = disc.ReadSwapped<u32>(offset, partition);
  86. if (value)
  87. buffer = *value;
  88. return value.has_value();
  89. }
  90. bool DiscScrubber::ReadFromVolume(const Volume& disc, u64 offset, u64& buffer,
  91. const Partition& partition)
  92. {
  93. std::optional<u64> value = disc.ReadSwappedAndShifted(offset, partition);
  94. if (value)
  95. buffer = *value;
  96. return value.has_value();
  97. }
  98. bool DiscScrubber::ParseDisc(const Volume& disc)
  99. {
  100. if (disc.GetPartitions().empty())
  101. return ParsePartitionData(disc, PARTITION_NONE);
  102. // Mark the header as used - it's mostly 0s anyways
  103. MarkAsUsed(0, 0x50000);
  104. for (const DiscIO::Partition& partition : disc.GetPartitions())
  105. {
  106. u32 tmd_size;
  107. u64 tmd_offset;
  108. u32 cert_chain_size;
  109. u64 cert_chain_offset;
  110. u64 h3_offset;
  111. // The H3 size is always 0x18000
  112. if (!ReadFromVolume(disc, partition.offset + WII_PARTITION_TMD_SIZE_ADDRESS, tmd_size,
  113. PARTITION_NONE) ||
  114. !ReadFromVolume(disc, partition.offset + WII_PARTITION_TMD_OFFSET_ADDRESS, tmd_offset,
  115. PARTITION_NONE) ||
  116. !ReadFromVolume(disc, partition.offset + WII_PARTITION_CERT_CHAIN_SIZE_ADDRESS,
  117. cert_chain_size, PARTITION_NONE) ||
  118. !ReadFromVolume(disc, partition.offset + WII_PARTITION_CERT_CHAIN_OFFSET_ADDRESS,
  119. cert_chain_offset, PARTITION_NONE) ||
  120. !ReadFromVolume(disc, partition.offset + WII_PARTITION_H3_OFFSET_ADDRESS, h3_offset,
  121. PARTITION_NONE))
  122. {
  123. return false;
  124. }
  125. MarkAsUsed(partition.offset, 0x2c0);
  126. MarkAsUsed(partition.offset + tmd_offset, tmd_size);
  127. MarkAsUsed(partition.offset + cert_chain_offset, cert_chain_size);
  128. MarkAsUsed(partition.offset + h3_offset, WII_PARTITION_H3_SIZE);
  129. // Parse Data! This is where the big gain is
  130. if (!ParsePartitionData(disc, partition))
  131. return false;
  132. }
  133. return true;
  134. }
  135. // Operations dealing with encrypted space are done here
  136. bool DiscScrubber::ParsePartitionData(const Volume& disc, const Partition& partition)
  137. {
  138. const FileSystem* filesystem = disc.GetFileSystem(partition);
  139. if (!filesystem)
  140. {
  141. ERROR_LOG_FMT(DISCIO, "Failed to read file system for the partition at {:#x}",
  142. partition.offset);
  143. return false;
  144. }
  145. u64 partition_data_offset;
  146. if (partition == PARTITION_NONE)
  147. {
  148. partition_data_offset = 0;
  149. }
  150. else
  151. {
  152. u64 data_offset;
  153. if (!ReadFromVolume(disc, partition.offset + 0x2b8, data_offset, PARTITION_NONE))
  154. return false;
  155. partition_data_offset = partition.offset + data_offset;
  156. }
  157. // Mark things as used which are not in the filesystem
  158. // Header, Header Information, Apploader
  159. u32 apploader_size;
  160. u32 apploader_trailer_size;
  161. if (!ReadFromVolume(disc, 0x2440 + 0x14, apploader_size, partition) ||
  162. !ReadFromVolume(disc, 0x2440 + 0x18, apploader_trailer_size, partition))
  163. {
  164. return false;
  165. }
  166. MarkAsUsedE(partition_data_offset, 0, 0x2440 + apploader_size + apploader_trailer_size);
  167. // DOL
  168. const std::optional<u64> dol_offset = GetBootDOLOffset(disc, partition);
  169. if (!dol_offset)
  170. return false;
  171. const std::optional<u64> dol_size = GetBootDOLSize(disc, partition, *dol_offset);
  172. if (!dol_size)
  173. return false;
  174. MarkAsUsedE(partition_data_offset, *dol_offset, *dol_size);
  175. // FST
  176. const std::optional<u64> fst_offset = GetFSTOffset(disc, partition);
  177. const std::optional<u64> fst_size = GetFSTSize(disc, partition);
  178. if (!fst_offset || !fst_size)
  179. return false;
  180. MarkAsUsedE(partition_data_offset, *fst_offset, *fst_size);
  181. // Go through the filesystem and mark entries as used
  182. ParseFileSystemData(partition_data_offset, filesystem->GetRoot());
  183. return true;
  184. }
  185. void DiscScrubber::ParseFileSystemData(u64 partition_data_offset, const FileInfo& directory)
  186. {
  187. for (const DiscIO::FileInfo& file_info : directory)
  188. {
  189. DEBUG_LOG_FMT(DISCIO, "Scrubbing {}", file_info.GetPath());
  190. if (file_info.IsDirectory())
  191. ParseFileSystemData(partition_data_offset, file_info);
  192. else
  193. MarkAsUsedE(partition_data_offset, file_info.GetOffset(), file_info.GetSize());
  194. }
  195. }
  196. } // namespace DiscIO