ArchiveTOCView.inl 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #pragma once
  9. #include <AzCore/std/functional.h>
  10. namespace Archive
  11. {
  12. //! ArchiveTableOfContentsView member implementation
  13. inline ArchiveTableOfContentsView::ArchiveTableOfContentsView() = default;
  14. inline auto ArchiveTableOfContentsView::CreateFromArchiveHeaderAndBuffer(const ArchiveHeader& archiveHeader,
  15. AZStd::span<AZStd::byte> tocBuffer) -> CreateTOCViewOutcome
  16. {
  17. // A valid table of contents must have at least 8 bytes to store the Magic Bytes
  18. if (tocBuffer.size() < sizeof(ArchiveTocMagicBytes))
  19. {
  20. ArchiveTocValidationResult tocValidationResult;
  21. tocValidationResult.m_errorCode = ArchiveTocErrorCode::InvalidMagicBytes;
  22. tocValidationResult.m_errorMessage = ArchiveTocValidationResult::ErrorString::format(
  23. "The Archive TOC is empty. It must be at least %zu bytes to store the Magic Bytes",
  24. sizeof(ArchiveTocMagicBytes));
  25. return CreateTOCViewOutcome(AZStd::unexpected(AZStd::move(tocValidationResult)));
  26. }
  27. ArchiveTableOfContentsView tocView;
  28. // magic bytes offset
  29. constexpr size_t MagicBytesOffset = 0;
  30. // Round up the File metadata offset as it is 32-byte aligned
  31. constexpr size_t FileMetadataTableOffset = AZ_SIZE_ALIGN_UP(
  32. MagicBytesOffset + sizeof(ArchiveTocMagicBytes),
  33. sizeof(ArchiveTocFileMetadata));
  34. // The file path metadata entries is 32 bytes aligned
  35. // so round up to the nearest multiple of alignment before reading the file path index entries
  36. const size_t FilePathIndexTableOffset = AZ_SIZE_ALIGN_UP(
  37. FileMetadataTableOffset + archiveHeader.m_tocFileMetadataTableUncompressedSize,
  38. sizeof(ArchiveTocFileMetadata));
  39. // The file path index entries are 8 bytes aligned
  40. // so round up to the nearest multiple of alignment before reading the file path blob
  41. const size_t FilePathBlobOffset = AZ_SIZE_ALIGN_UP(
  42. FilePathIndexTableOffset + archiveHeader.m_tocPathIndexTableUncompressedSize,
  43. sizeof(ArchiveTocFilePathIndex));
  44. // The block offset table starts on an address aligned at a multiple of 8 bytes
  45. const size_t BlockOffsetTableOffset = AZ_SIZE_ALIGN_UP(
  46. FilePathBlobOffset + archiveHeader.m_tocPathBlobUncompressedSize,
  47. sizeof(ArchiveBlockLineUnion));
  48. // Cast the first 8 of the TOC buffer
  49. tocView.m_magicBytes = *reinterpret_cast<decltype(tocView.m_magicBytes)*>(tocBuffer.data() + MagicBytesOffset);
  50. // create a span to the file metadata entries
  51. tocView.m_fileMetadataTable = AZStd::span(
  52. reinterpret_cast<const ArchiveTocFileMetadata*>(tocBuffer.data() + FileMetadataTableOffset),
  53. archiveHeader.m_fileCount);
  54. // create a span to the file path index entries
  55. tocView.m_filePathIndexTable = AZStd::span(
  56. reinterpret_cast<const ArchiveTocFilePathIndex*>(tocBuffer.data() + FilePathIndexTableOffset),
  57. archiveHeader.m_fileCount);
  58. // create a string view to the file path blob
  59. tocView.m_filePathBlob = AZStd::string_view(
  60. reinterpret_cast<const char*>(tocBuffer.data() + FilePathBlobOffset),
  61. archiveHeader.m_tocPathBlobUncompressedSize);
  62. // create a span to the block offset entries
  63. const auto blockOffsetTableBegin = reinterpret_cast<const ArchiveBlockLineUnion*>(tocBuffer.data() + BlockOffsetTableOffset);
  64. const auto blockOffsetTableEnd = reinterpret_cast<const ArchiveBlockLineUnion*>(reinterpret_cast<const AZStd::byte*>(blockOffsetTableBegin)
  65. + archiveHeader.m_tocBlockOffsetTableUncompressedSize);
  66. tocView.m_blockOffsetTable = AZStd::span(
  67. blockOffsetTableBegin,
  68. blockOffsetTableEnd);
  69. ArchiveTocValidationOptions validationSettings;
  70. // Skip over validating the block Offset table has that is a potentially slow operation
  71. validationSettings.m_validateBlockOffsetTable = false;
  72. ArchiveTocValidationResult validationResult = ValidateTableOfContents(tocView, archiveHeader,
  73. validationSettings);
  74. return validationResult ? CreateTOCViewOutcome{ tocView } : CreateTOCViewOutcome{ AZStd::unexpected(AZStd::move(validationResult)) };
  75. }
  76. constexpr ArchiveTocValidationResult::operator bool() const
  77. {
  78. return m_errorCode == ArchiveTocErrorCode{};
  79. }
  80. inline ArchiveTocValidationResult ValidateTableOfContents(const ArchiveTableOfContentsView& tocView,
  81. const ArchiveHeader& archiveHeader,
  82. const ArchiveTocValidationOptions& validationOptions)
  83. {
  84. if (tocView.m_magicBytes != ArchiveTocMagicBytes)
  85. {
  86. ArchiveTocValidationResult tocValidationResult;
  87. tocValidationResult.m_errorCode = ArchiveTocErrorCode::InvalidMagicBytes;
  88. tocValidationResult.m_errorMessage = ArchiveTocValidationResult::ErrorString::format(
  89. "The Archive TOC has an invalid magic byte sequence of %llx", tocView.m_magicBytes);
  90. return tocValidationResult;
  91. }
  92. if (validationOptions.m_validateFileMetadataTable
  93. && tocView.m_fileMetadataTable.size_bytes() != archiveHeader.m_tocFileMetadataTableUncompressedSize)
  94. {
  95. ArchiveTocValidationResult tocValidationResult;
  96. tocValidationResult.m_errorCode = ArchiveTocErrorCode::FileMetadataTableSizeMismatch;
  97. tocValidationResult.m_errorMessage = ArchiveTocValidationResult::ErrorString::format(
  98. "The Archive TOC File Metadata table size %zu does not match the uncompressed size %u",
  99. tocView.m_fileMetadataTable.size_bytes(),
  100. archiveHeader.m_tocFileMetadataTableUncompressedSize);
  101. return tocValidationResult;
  102. }
  103. if (validationOptions.m_validateFileIndexTable
  104. && tocView.m_filePathIndexTable.size_bytes() != archiveHeader.m_tocPathIndexTableUncompressedSize)
  105. {
  106. ArchiveTocValidationResult tocValidationResult;
  107. tocValidationResult.m_errorCode = ArchiveTocErrorCode::FileIndexTableSizeMismatch;
  108. tocValidationResult.m_errorMessage = ArchiveTocValidationResult::ErrorString::format(
  109. "The Archive TOC File Path Index table size %zu does not match the uncompressed size %u",
  110. tocView.m_filePathIndexTable.size_bytes(),
  111. archiveHeader.m_tocPathIndexTableUncompressedSize);
  112. return tocValidationResult;
  113. }
  114. if (validationOptions.m_validateBlockOffsetTable)
  115. {
  116. // Validate the number blocks line offset table entries matches the total number of block
  117. // lines used per file
  118. // that should be in the table of contents based on the size
  119. // of each file uncompressed size
  120. AZ::u64 expectedBlockLineCount{};
  121. for (const ArchiveTocFileMetadata& archiveFileMetadata : tocView.m_fileMetadataTable)
  122. {
  123. // Uncompressed files are stored in contiguous memory without any blocks
  124. if (archiveFileMetadata.m_compressionAlgoIndex == UncompressedAlgorithmIndex)
  125. {
  126. continue;
  127. }
  128. // The number of blocks a file contains is based on how many 2 MiB chunks can be extracted
  129. // using it's uncompressed size
  130. expectedBlockLineCount += GetBlockLineCountIfCompressed(archiveFileMetadata.m_uncompressedSize);
  131. }
  132. if (expectedBlockLineCount == tocView.m_blockOffsetTable.size())
  133. {
  134. ArchiveTocValidationResult tocValidationResult;
  135. tocValidationResult.m_errorCode = ArchiveTocErrorCode::BlockOffsetTableCountMismatch;
  136. tocValidationResult.m_errorMessage = ArchiveTocValidationResult::ErrorString::format(
  137. "The count of blocks lines used by each compressed file is %llu, which does not match the count"
  138. " of entries in the block offset table %zu",
  139. expectedBlockLineCount,
  140. tocView.m_blockOffsetTable.size());
  141. return tocValidationResult;
  142. }
  143. }
  144. // No failure has occurred so return a default initialized validation result instance
  145. return {};
  146. }
  147. inline size_t EnumerateFilePathIndexOffsets(FilePathIndexEntryVisitor callback,
  148. const ArchiveTableOfContentsView& tocView)
  149. {
  150. size_t filePathsVisited{};
  151. for (const auto& filePathIndexEntry : tocView.m_filePathIndexTable)
  152. {
  153. // Invoke the visitor callback if the file path size > 0
  154. if (filePathIndexEntry.m_size > 0)
  155. {
  156. callback(filePathIndexEntry.m_offset, static_cast<AZ::u16>(filePathIndexEntry.m_size));
  157. ++filePathsVisited;
  158. }
  159. }
  160. return filePathsVisited;
  161. }
  162. // Implementation of function which returns a span that encompass the block lines
  163. // for a content file provided that the first block line index for that file is supplied
  164. // along with that file uncompressed size
  165. inline FileBlockLineOutcome GetBlockLineSpanForFile(const ArchiveTableOfContentsView& tocView,
  166. size_t indexInFileMetadataTable)
  167. {
  168. if (indexInFileMetadataTable >= tocView.m_fileMetadataTable.size())
  169. {
  170. return AZStd::unexpected(ResultString::format("The index %zu into the Table of Contents file metadata table"
  171. " is is out of range", indexInFileMetadataTable));
  172. }
  173. // Query the uncompressed size and block line start index for the file from the TOC
  174. const AZ::u64 uncompressedSize = tocView.m_fileMetadataTable[indexInFileMetadataTable].m_uncompressedSize;
  175. const AZ::u64 blockLineFirstIndex = tocView.m_fileMetadataTable[indexInFileMetadataTable].m_blockLineTableFirstIndex;
  176. // Determine the number of blocks lines a file uses based on the uncompressed size
  177. const AZ::u64 fileBlockLineCount = GetBlockLineCountIfCompressed(uncompressedSize);
  178. if (blockLineFirstIndex >= tocView.m_blockOffsetTable.size()
  179. && (blockLineFirstIndex + fileBlockLineCount) >= tocView.m_blockOffsetTable.size())
  180. {
  181. return AZStd::unexpected(ResultString::format("Either the first block index for the file with value %llu"
  182. "is incorrect or the uncompressed size (%llu) for the file is incorrect", blockLineFirstIndex,
  183. uncompressedSize));
  184. }
  185. // returns a subspan that starts at the first block line for the file
  186. // and continues for the count of the block lines based on the uncompressed size
  187. return tocView.m_blockOffsetTable.subspan(blockLineFirstIndex, fileBlockLineCount);
  188. }
  189. // Retrieves the compressed size for the given block
  190. inline AZ::u64 GetCompressedSizeForBlock(AZStd::span<const ArchiveBlockLineUnion> fileBlockLineSpan,
  191. AZ::u64 blockCount, AZ::u64 blockIndex)
  192. {
  193. // Get the index of the block line corresponding to the index of the block
  194. auto blockLineResult = GetBlockLineIndexFromBlockIndex(blockCount, blockIndex);
  195. if (!blockLineResult)
  196. {
  197. return 0;
  198. }
  199. // Block line indices which are multiples of 3 all have jump entries unless they are part of the final 3 block
  200. // lines of a file
  201. const bool blockLineContainsJump = (blockLineResult.m_blockLineIndex % BlockLinesToSkipWithJumpEntry == 0)
  202. && (fileBlockLineSpan.size() - blockLineResult.m_blockLineIndex) > BlockLinesToSkipWithJumpEntry;
  203. if (blockLineContainsJump)
  204. {
  205. const ArchiveBlockLineJump& blockLineWithJump = fileBlockLineSpan[
  206. blockLineResult.m_blockLineIndex].m_blockLineWithJump;
  207. switch (blockLineResult.m_offsetInBlockLine)
  208. {
  209. case 0:
  210. return blockLineWithJump.m_block0;
  211. case 1:
  212. return blockLineWithJump.m_block1;
  213. default:
  214. return 0;
  215. }
  216. }
  217. else
  218. {
  219. const ArchiveBlockLine& blockLine = fileBlockLineSpan[
  220. blockLineResult.m_blockLineIndex].m_blockLine;
  221. switch (blockLineResult.m_offsetInBlockLine)
  222. {
  223. case 0:
  224. return blockLine.m_block0;
  225. case 1:
  226. return blockLine.m_block1;
  227. case 2:
  228. return blockLine.m_block2;
  229. default:
  230. return 0;
  231. }
  232. }
  233. }
  234. // Returns the summation of the compressed sizes of each block of the file
  235. inline GetRawFileSizeOutcome GetRawFileSize(const ArchiveTocFileMetadata& fileMetadata,
  236. AZStd::span<const ArchiveBlockLineUnion> tocBlockOffsetTable)
  237. {
  238. // Query the uncompressed size TOC
  239. const AZ::u64 uncompressedSize = fileMetadata.m_uncompressedSize;
  240. // If the uncompressed, then return the uncompressed size as the raw file size
  241. if (fileMetadata.m_compressionAlgoIndex >= UncompressedAlgorithmIndex)
  242. {
  243. return uncompressedSize;
  244. }
  245. // Determine the number of blocks lines a file uses based on the uncompressed size
  246. const AZ::u64 blockLineFirstIndex = fileMetadata.m_blockLineTableFirstIndex;
  247. const AZ::u64 fileBlockLineCount = GetBlockLineCountIfCompressed(uncompressedSize);
  248. if (blockLineFirstIndex >= tocBlockOffsetTable.size()
  249. && (blockLineFirstIndex + fileBlockLineCount) >= tocBlockOffsetTable.size())
  250. {
  251. return AZStd::unexpected(ResultString::format("The first block offset entry for the file with value %llu"
  252. "is incorrect or the uncompressed size (%llu) is incorrect", blockLineFirstIndex,
  253. uncompressedSize));
  254. }
  255. // Get a subspan of only the blocks associated with the file
  256. AZStd::span<const ArchiveBlockLineUnion> fileBlockLineSpan = tocBlockOffsetTable.subspan(blockLineFirstIndex, fileBlockLineCount);
  257. AZ::u64 compressedSize{};
  258. size_t blockCount = GetBlockCountIfCompressed(uncompressedSize);
  259. for (size_t blockLineIndex{}, blockIndex{}; blockLineIndex < fileBlockLineSpan.size();)
  260. {
  261. // Determine if the block line contains a jump entry
  262. // This can be used to determine the compressed block sizes for the next 8 blocks (3 block lines)
  263. const bool blockLineContainsJump = (blockLineIndex % BlockLinesToSkipWithJumpEntry == 0)
  264. && (fileBlockLineSpan.size() - blockLineIndex) > BlockLinesToSkipWithJumpEntry;
  265. if (blockLineContainsJump)
  266. {
  267. const ArchiveBlockLineJump& blockLineWithJump = fileBlockLineSpan[blockLineIndex].m_blockLineWithJump;
  268. // A jump entry contains the aligned compressed size for the next 8 blocks
  269. compressedSize = blockLineWithJump.m_blockJump * ArchiveDefaultBlockAlignment;
  270. blockLineIndex += BlockLinesToSkipWithJumpEntry;
  271. blockIndex += BlocksToSkipWithJumpEntry;
  272. }
  273. else
  274. {
  275. // When this logic is reached, there are no more blocks with jump entries in the file
  276. // therefore add the aligned size of compressed block EXCEPT the last block
  277. // where it's actual size is used.
  278. const ArchiveBlockLine& blockLine = fileBlockLineSpan[blockLineIndex].m_blockLine;
  279. // Check if the current blockIndex corresponds to the last block of the compressed
  280. // file section
  281. if (bool isLastBlock = ++blockIndex == blockCount;
  282. isLastBlock)
  283. {
  284. compressedSize += blockLine.m_block0;
  285. break;
  286. }
  287. compressedSize += AZ_SIZE_ALIGN_UP(blockLine.m_block0, ArchiveDefaultBlockAlignment);
  288. if (bool isLastBlock = ++blockIndex == blockCount;
  289. isLastBlock)
  290. {
  291. compressedSize += blockLine.m_block1;
  292. break;
  293. }
  294. compressedSize += AZ_SIZE_ALIGN_UP(blockLine.m_block2, ArchiveDefaultBlockAlignment);
  295. if (bool isLastBlock = ++blockIndex == blockCount;
  296. isLastBlock)
  297. {
  298. compressedSize += blockLine.m_block2;
  299. break;
  300. }
  301. compressedSize += AZ_SIZE_ALIGN_UP(blockLine.m_block2, ArchiveDefaultBlockAlignment);
  302. // Increment the block line index by 1 to move to the next block
  303. ++blockLineIndex;
  304. }
  305. }
  306. return compressedSize;
  307. }
  308. } // namespace Archive