26 std::shared_ptr<RawDataPool>
pool;
36 std::weak_ptr<const DataBytes> lease;
38 std::uint64_t identity{};
41 using Entries = std::list<Entry>;
46 const std::scoped_lock lock(mutex_);
47 const auto data = cached.lock();
49 const auto item = find(
data.get());
50 if (item == entries_.end() || !item->cached)
return {};
51 entries_.splice(entries_.begin(), entries_, item);
52 if (
auto lease = item->lease.lock())
return lease;
53 return makeLease(*item);
58 const std::scoped_lock lock(mutex_);
62 auto item = entries_.end();
64 item != entries_.begin())
67 if (!item->lease.expired())
continue;
68 residentBytes_ -= item->data->size();
69 item = entries_.erase(item);
71 reservedBytes_ += size;
76 const std::scoped_lock lock(mutex_);
77 reservedBytes_ -= size;
82 const std::scoped_lock lock(mutex_);
83 entries_.push_front({std::move(
data), {},
true, ++lastIdentity_});
85 try { lease = makeLease(entries_.front()); }
86 catch (...) { entries_.pop_front();
throw; }
87 residentBytes_ += entries_.front().data->size();
88 reservedBytes_ -= reserved;
92 bool remove(
const std::weak_ptr<const DataBytes>& cached)
noexcept
94 const std::scoped_lock lock(mutex_);
95 const auto data = cached.lock();
96 if (!
data)
return false;
97 const auto item = find(
data.get());
98 if (item == entries_.end() || !item->cached)
return false;
100 if (item->lease.expired()) erase(item);
106 const std::scoped_lock lock(mutex_);
107 const auto item = std::find_if(entries_.begin(), entries_.end(),
108 [identity](
const Entry&
entry) { return entry.identity == identity; });
109 if (item == entries_.end())
return;
112 if (!item->lease.expired())
return;
113 if (!item->cached) erase(item);
114 else entries_.splice(entries_.begin(), entries_, item);
119 const std::scoped_lock lock(mutex_);
121 for (
const auto& item : entries_)
123 result.cachedItems += item.cached;
124 result.leasedItems += !item.lease.expired();
130 Entries::iterator find(
const DataBytes* data)
noexcept
132 return std::find_if(entries_.begin(), entries_.end(),
133 [data](
const Entry& item) { return item.data.get() == data; });
138 auto holder = std::make_shared<RawDataLease>();
141 holder->identity = item.identity;
147 void erase(Entries::iterator item)
noexcept
149 residentBytes_ -= item->data->size();
150 entries_.erase(item);
153 mutable std::mutex mutex_;
155 std::size_t residentBytes_{};
156 std::size_t reservedBytes_{};
157 std::uint64_t lastIdentity_{};
170 static const auto pool = std::make_shared<RawDataPool>();
178 : pool_(std::move(pool)), size_(size) { pool_->reserve(size_); }
184 auto result = pool_->publish(std::move(
data), size_);
189 std::shared_ptr<RawDataPool> pool_;
198 {{
'A',
'r',
't',
'e',
'c',
'h',
'\0' }};
202 const std::byte*
bytes)
noexcept
205 static_cast<std::uint16_t
>(
206 std::to_integer<std::uint8_t>(
bytes[0])) |
207 static_cast<std::uint16_t
>(
208 std::to_integer<std::uint8_t>(
bytes[1]) << 8U);
213 const std::byte*
bytes)
noexcept
216 static_cast<std::uint32_t
>(
217 std::to_integer<std::uint8_t>(
bytes[0])) |
218 (
static_cast<std::uint32_t
>(
219 std::to_integer<std::uint8_t>(
bytes[1])) << 8U) |
220 (
static_cast<std::uint32_t
>(
221 std::to_integer<std::uint8_t>(
bytes[2])) << 16U) |
222 (
static_cast<std::uint32_t
>(
223 std::to_integer<std::uint8_t>(
bytes[3])) << 24U);
228 std::ifstream& stream,
250 std::ostringstream
output;
316 return "DecompressedSizeMismatch";
321 return "UnsortedIndexTable";
325 return "IndexedItemNotFound";
335 return "InvalidDataErrorCode";
339 DataError LegacyDataArchive::makeError(
341 const std::filesystem::path&
path,
343 std::optional<DataTag> tag)
345 return { code,
path, tag, std::move(detail) };
355 std::expected<std::shared_ptr<LegacyDataArchive>,
DataError>
357 const std::filesystem::path&
path,
363 return std::unexpected(makeError(
366 "legacy group zero is reserved for EmptyDataId"));
369 auto archive = std::shared_ptr<LegacyDataArchive>(
373 archive->path_ =
path;
374 archive->group_ =
group;
375 archive->maximumUncompressedItemSize_ =
377 archive->stream_.open(
path, std::ios::binary);
379 if (!archive->stream_)
381 return std::unexpected(makeError(
384 "unable to open the legacy DAT bank"));
388 archive->fileSize_ = std::filesystem::file_size(
path,
sizeError);
392 return std::unexpected(makeError(
398 std::array<std::byte, PhysicalHeaderSize>
header{};
402 return std::unexpected(makeError(
405 "DAT header must contain exactly 28 bytes"));
413 return std::unexpected(makeError(
416 "missing Artech\\0 signature"));
431 return std::unexpected(makeError(
434 "only the source-defined DAT version 2.3 is supported"));
444 return std::unexpected(makeError(
447 "DAT index must contain at least one item"));
453 return std::unexpected(makeError(
456 "DAT index cannot exceed the 16-bit DataTag space"));
461 const std::uint64_t indexBytes =
466 if (dataStart > archive->fileSize_)
468 return std::unexpected(makeError(
471 "DAT index extends past end of file"));
476 std::array<std::byte, PhysicalIndexRecordSize> record{};
478 for (std::uint32_t item = 0; item <
rawItemCount; ++item)
480 if (!
readExact(archive->stream_, record))
482 return std::unexpected(makeError(
485 itemDetail(
"unable to read complete DAT index record", item),
490 const auto uncompressedSize =
readU32Le(record.data() + 4);
492 const auto compressedSize =
readU32Le(record.data() + 12);
496 return std::unexpected(makeError(
499 itemDetail(
"data type is outside LE_DATA_DataType", item),
514 archive->items_.push_back(
metadata);
519 offset >
static_cast<std::uint32_t
>(
520 std::numeric_limits<std::int32_t>::max()) ||
521 uncompressedSize >
static_cast<std::uint32_t
>(
522 std::numeric_limits<std::int32_t>::max()) ||
523 compressedSize >
static_cast<std::uint32_t
>(
524 std::numeric_limits<std::int32_t>::max());
527 static_cast<std::uint64_t
>(
offset) + compressedSize;
531 uncompressedSize == 0 ||
532 compressedSize == 0 ||
537 return std::unexpected(makeError(
541 "DAT item offset or size is outside validated bounds",
546 archive->items_.push_back(
metadata);
551 archive->stream_.clear();
552 archive->stream_.seekg(
557 std::array<std::byte, 64U * 1024U>
block{};
563 const auto amount =
static_cast<std::size_t
>(std::min<
569 return std::unexpected(makeError(
572 "unable to read DAT bytes for CRC-32 validation"));
578 static_cast<uInt>(amount));
582 if (
static_cast<std::uint32_t
>(
checksum) != archive->checksum_)
584 return std::unexpected(makeError(
587 "stored CRC-32 does not match bytes after the header"));
591 archive->stream_.clear();
592 archive->cache_.resize(archive->items_.size());
593 archive->open_ =
true;
600 std::scoped_lock lock(mutex_);
601 for (
auto& item : cache_) rawDataPool_->remove(item);
604 if (stream_.is_open())
615 std::scoped_lock lock(mutex_);
646 return items_.size();
650 std::expected<ArchiveItemMetadata, DataError>
653 if (
static_cast<std::size_t
>(tag) >= items_.size())
655 return std::unexpected(makeError(
658 "DataTag is outside this archive index",
666 std::expected<LegacyDataType, DataError>
670 if (!item)
return std::unexpected(item.error());
675 std::expected<std::uint32_t, DataError>
679 if (!item)
return std::unexpected(item.error());
680 return item->uncompressedSize;
684 std::expected<std::uint32_t, DataError>
688 if (!
bytes)
return std::unexpected(
bytes.error());
690 if ((*bytes)->size() > std::numeric_limits<std::uint32_t>::max())
692 return std::unexpected(makeError(
695 "loaded DAT item size exceeds the legacy 32-bit range",
699 return static_cast<std::uint32_t
>((*bytes)->size());
703 std::expected<SharedDataBytes, DataError>
706 std::scoped_lock lock(mutex_);
710 return std::unexpected(makeError(
713 "cannot load an item from a closed archive",
717 if (
static_cast<std::size_t
>(tag) >= items_.size())
719 return std::unexpected(makeError(
722 "DataTag is outside this archive index",
726 if (
auto cached = rawDataPool_->acquire(cache_[tag]))
731 const auto& item = items_[tag];
735 return std::unexpected(makeError(
738 "DataTag identifies an empty DAT slot",
743 auto compressed = std::vector<Bytef>(item.compressedSize);
746 static_cast<std::streamoff
>(item.offset),
752 std::as_writable_bytes(std::span(
compressed))))
754 return std::unexpected(makeError(
757 "unable to read the complete compressed DAT item",
761 auto output = std::make_shared<DataBytes>(item.uncompressedSize);
773 std::ostringstream detail;
775 <<
"zlib inflate failed with code "
778 return std::unexpected(makeError(
788 return std::unexpected(makeError(
791 "inflated size or consumed compressed size differs from index",
805 std::scoped_lock lock(mutex_);
809 return std::unexpected(makeError(
812 "cannot read raw bytes from a closed archive",
816 if (
static_cast<std::size_t
>(tag) >= items_.size())
818 return std::unexpected(makeError(
821 "DataTag is outside this archive index",
825 const auto& item = items_[tag];
828 return std::unexpected(makeError(
831 "DataTag identifies an empty DAT slot",
836 return std::size_t{0};
838 const auto amount = std::min<std::size_t>(
840 static_cast<std::size_t
>(item.uncompressedSize -
startOffset));
842 std::vector<Bytef>
compressed(item.compressedSize);
845 static_cast<std::streamoff
>(item.offset),
851 std::as_writable_bytes(std::span(
compressed))))
853 return std::unexpected(makeError(
856 "unable to read the complete compressed DAT item",
865 return std::unexpected(makeError(
868 "zlib inflate initialization failed",
881 std::array<std::byte, 64U * 1024U>
block{};
897 return std::unexpected(makeError(
900 "zlib inflate failed during raw slice read",
933 return std::unexpected(makeError(
936 "compressed DAT item ended before the requested raw slice",
943 return std::unexpected(makeError(
946 "decompressed DAT item ended before the requested raw slice",
956 std::scoped_lock lock(mutex_);
960 return std::unexpected(makeError(
963 "cannot query loaded state from a closed archive",
967 if (
static_cast<std::size_t
>(tag) >= items_.size())
969 return std::unexpected(makeError(
972 "DataTag is outside this archive index",
976 if (!items_[tag].present())
979 return !cache_[tag].expired();
985 std::scoped_lock lock(mutex_);
989 return std::unexpected(makeError(
992 "cannot unload an item from a closed archive",
996 if (
static_cast<std::size_t
>(tag) >= items_.size())
998 return std::unexpected(makeError(
1001 "DataTag is outside this archive index",
1005 const bool wasCached = rawDataPool_->remove(cache_[tag]);
1006 cache_[tag].reset();
1013 std::scoped_lock lock(mutex_);
1015 for (
auto& item : cache_)
1017 rawDataPool_->remove(item);
1025 std::scoped_lock lock(mutex_);
1026 return static_cast<std::size_t
>(std::count_if(
1029 [](
const std::weak_ptr<const DataBytes>& item)
1031 return !item.expired();
1036 std::expected<std::shared_ptr<LegacyDataArchive>,
DataError>
1038 const std::filesystem::path&
path,
1039 std::uint16_t
group,
1043 std::scoped_lock lock(mutex_);
1045 if (archives_.contains(
group))
1052 "a DAT archive is already mounted for this group"
1061 return std::unexpected(
opened.error());
1064 std::scoped_lock lock(mutex_);
1076 "a DAT archive was mounted concurrently for this group"
1084 std::expected<std::shared_ptr<LegacyDataArchive>,
DataError>
1087 std::scoped_lock lock(mutex_);
1090 if (
found == archives_.end())
1097 "no DAT archive is mounted for this group"
1101 return found->second;
1105 std::expected<ArchiveItemMetadata, DataError>
1112 return std::unexpected(
mounted.error());
1115 return (*mounted)->metadata(
dataTag(
id));
1119 std::expected<LegacyDataType, DataError>
1127 return std::unexpected(
mounted.error());
1129 return (*mounted)->initialDataType(
dataTag(
id));
1133 std::expected<std::uint32_t, DataError>
1137 return std::uint32_t{0};
1141 return std::unexpected(
mounted.error());
1143 return (*mounted)->initialSize(
dataTag(
id));
1147 std::expected<std::uint32_t, DataError>
1151 return std::uint32_t{0};
1155 return std::unexpected(
mounted.error());
1157 return (*mounted)->loadedRawSize(
dataTag(
id));
1161 std::expected<SharedDataBytes, DataError>
1168 return std::unexpected(
mounted.error());
1171 return (*mounted)->load(
dataTag(
id));
1175 std::expected<std::size_t, DataError>
1182 return std::size_t{0};
1186 return std::unexpected(
mounted.error());
1188 return (*mounted)->readRaw(
1193 std::expected<bool, DataError>
1201 return std::unexpected(
mounted.error());
1203 return (*mounted)->isLoaded(
dataTag(
id));
1207 std::expected<void, DataError>
1215 return std::unexpected(
mounted.error());
1219 return std::unexpected(
removed.error());
1230 std::vector<std::shared_ptr<LegacyDataArchive>>
archives;
1232 std::scoped_lock lock(mutex_);
1233 archives.reserve(archives_.size());
1236 static_cast<void>(
group);
1248 std::vector<std::shared_ptr<LegacyDataArchive>>
archives;
1250 std::scoped_lock lock(mutex_);
1251 archives.reserve(archives_.size());
1254 static_cast<void>(
group);
1259 std::size_t
total{};
1266 std::expected<void, DataError>
1269 std::shared_ptr<LegacyDataArchive>
removed;
1272 std::scoped_lock lock(mutex_);
1275 if (
found == archives_.end())
1282 "no DAT archive is mounted for this group"
1287 archives_.erase(
found);
1299 std::shared_ptr<LegacyDataArchive>>
removed;
1302 std::scoped_lock lock(mutex_);
1308 static_cast<void>(
group);
1316 std::scoped_lock lock(mutex_);
1317 return archives_.size();
1321 std::expected<DataIndexTable, DataError>
1331 "index table size must be a non-zero multiple of 6 bytes"
1338 for (std::size_t
offset = 0;
1348 if (!
table.entries_.empty())
1359 "index entries must be sorted for legacy bsearch"
1370 "duplicate keys make legacy bsearch nondeterministic"
1383 std::uint32_t indexValue)
const noexcept
1385 const auto found = std::lower_bound(
1391 return entry.indexValue < key;
1394 if (
found == entries_.end() ||
found->indexValue != indexValue)
1396 return std::nullopt;
1399 return found->dataTag;
1412 std::uint32_t indexValue)
1418 return std::unexpected(
metadata.error());
1428 "indexed lookup requires a LE_DATA_DataIndexTable item"
1436 return std::unexpected(
bytes.error());
1443 return std::unexpected(
table.error());
1446 const auto tag =
table->find(indexValue);
1455 "requested key does not exist in the index table"
1465 std::uint32_t indexValue)
1469 if (result)
return *result;
1475 return std::unexpected(result.error());
std::vector< std::uint8_t > data
LegacyBitmapMetadata metadata
std::vector< std::byte > bytes
std::shared_ptr< const data::HmdTextureImage > source
std::deque< Bytes > output
std::expected< LegacyDataType, DataError > initialDataType(DataId id) const override
std::expected< std::uint32_t, DataError > loadedRawSize(DataId id) const override
void clearCaches() noexcept
std::expected< void, DataError > unmount(std::uint16_t group)
std::size_t cachedItemCount() const noexcept
std::expected< SharedDataBytes, DataError > load(DataId id) const override
std::expected< void, DataError > unload(DataId id) const
std::expected< std::uint32_t, DataError > initialSize(DataId id) const override
std::expected< std::shared_ptr< LegacyDataArchive >, DataError > mount(const std::filesystem::path &path, std::uint16_t group, ArchiveOpenOptions options={})
std::size_t mountedCount() const noexcept
std::expected< bool, DataError > isLoaded(DataId id) const override
std::expected< std::shared_ptr< LegacyDataArchive >, DataError > archive(std::uint16_t group) const
std::expected< std::size_t, DataError > readRaw(DataId id, std::span< std::byte > destination, std::uint32_t startOffset=0) const override
std::expected< ArchiveItemMetadata, DataError > metadata(DataId id) const override
std::optional< DataTag > find(std::uint32_t indexValue) const noexcept
std::span< const DataIndexEntry > entries() const noexcept
static std::expected< DataIndexTable, DataError > parse(std::span< const std::byte > bytes)
static constexpr std::size_t PhysicalEntrySize
ArchiveVersion version() const noexcept
std::size_t itemCount() const noexcept
std::expected< std::uint32_t, DataError > loadedRawSize(DataTag tag)
static constexpr std::size_t PhysicalHeaderSize
const std::filesystem::path & path() const noexcept
bool isOpen() const noexcept
static std::expected< std::shared_ptr< LegacyDataArchive >, DataError > open(const std::filesystem::path &path, std::uint16_t group, ArchiveOpenOptions options={})
std::expected< std::uint32_t, DataError > initialSize(DataTag tag) const
void clearCache() noexcept
std::expected< LegacyDataType, DataError > initialDataType(DataTag tag) const
std::uint32_t storedChecksum() const noexcept
std::expected< bool, DataError > isLoaded(DataTag tag) const
static constexpr std::size_t PhysicalIndexRecordSize
std::expected< ArchiveItemMetadata, DataError > metadata(DataTag tag) const
std::expected< bool, DataError > unload(DataTag tag)
std::expected< SharedDataBytes, DataError > load(DataTag tag)
static constexpr std::uint16_t SupportedVersionMajor
static constexpr std::uint16_t SupportedVersionMinor
static constexpr std::size_t MaximumItemCount
std::size_t cachedItemCount() const noexcept
std::uint16_t group() const noexcept
std::expected< std::size_t, DataError > readRaw(DataTag tag, std::span< std::byte > destination, std::uint32_t startOffset=0)
SharedDataBytes publish(SharedDataBytes data, std::size_t reserved)
SharedDataBytes acquire(const std::weak_ptr< const DataBytes > &cached)
RawDataCacheStats stats() const noexcept
bool remove(const std::weak_ptr< const DataBytes > &cached) noexcept
void released(std::uint64_t identity) noexcept
void reserve(std::size_t size)
void cancelReservation(std::size_t size) noexcept
SharedDataBytes publish(SharedDataBytes data)
RawDataReservation(const RawDataReservation &)=delete
RawDataReservation(std::shared_ptr< RawDataPool > pool, std::size_t size)
RawDataReservation & operator=(const RawDataReservation &)=delete
const std::shared_ptr< RawDataPool > & rawDataPool()
constexpr DataId EmptyDataId
std::string_view legacyDataTypeName(LegacyDataType type) noexcept
std::vector< std::byte > DataBytes
constexpr DataTag dataTag(DataId id) noexcept
RawDataCacheStats rawDataCacheStats() noexcept
std::expected< DataId, DataError > lookupIndexedDataIdLegacy(const DataSource &source, DataId indexTableId, std::uint32_t indexValue)
std::shared_ptr< const DataBytes > SharedDataBytes
@ DecompressedSizeMismatch
constexpr bool isEmptyDataId(DataId id) noexcept
std::expected< DataId, DataError > lookupIndexedDataId(const DataSource &source, DataId indexTableId, std::uint32_t indexValue)
std::string_view dataErrorCodeName(DataErrorCode code) noexcept
constexpr DataId packDataId(std::uint16_t group, DataTag tag) noexcept
constexpr std::size_t LegacyRawDataCacheBudget
constexpr std::uint16_t dataGroup(DataId id) noexcept
std::uint32_t maximumUncompressedItemSize
ChecksumPolicy checksumPolicy
std::shared_ptr< RawDataPool > pool