Files
juan 7fe51ecf26 Merge upstream/main (v1.0.5/v1.0.6): best-GPU launcher, per-archive mod index, UTF-8 paths
- Conflict in launcher_support.h: kept Linux guards, took upstream's
  validated_remote_load_library(note) signature + comment.
- Upstream's path_utf8() helper is portable; merged cleanly into the
  Linux closure. Build + 8/8 portable ctest green.
2026-10-03 21:37:35 -03:00

86 lines
3.6 KiB
C++

#include "game_bundles.h"
#include "Engine/Resource/cas_codec.h"
#include "Engine/Core/Platform/path_text.h"
#include <fstream>
#include <stdexcept>
namespace dingosdk::vfs {
namespace fs = std::filesystem;
namespace fb = frostbite;
namespace {
std::size_t first_index(const fb::BinaryBundle& manifest, fb::AssetKind kind) {
switch (kind) {
case fb::AssetKind::ebx: return 0;
case fb::AssetKind::resource: return manifest.ebx.size();
default: return manifest.ebx.size() + manifest.resources.size();
}
}
const std::vector<fb::BundleAsset>& list(const fb::BinaryBundle& manifest, fb::AssetKind kind) {
return kind == fb::AssetKind::ebx ? manifest.ebx : kind == fb::AssetKind::resource ? manifest.resources : manifest.chunks;
}
}
const fb::BundleFileInfo* GameBundle::payload(fb::AssetKind kind, std::size_t index) const noexcept {
if (index >= list(manifest, kind).size()) return nullptr;
const auto at = first_index(manifest, kind) + index;
return at < payloads.size() ? &payloads[at] : nullptr;
}
const fb::BundleAsset* GameBundle::find(fb::AssetKind kind, std::string_view name, std::size_t* index) const noexcept {
const auto& assets = list(manifest, kind);
for (std::size_t i = 0; i < assets.size(); ++i) {
if (assets[i].name != name) continue;
if (index) *index = i;
return &assets[i];
}
return nullptr;
}
const fb::BundleAsset* GameBundle::find_chunk(const fb::Guid& id, std::size_t* index) const noexcept {
for (std::size_t i = 0; i < manifest.chunks.size(); ++i) {
if (manifest.chunks[i].guid != id) continue;
if (index) *index = i;
return &manifest.chunks[i];
}
return nullptr;
}
GameData::GameData(fs::path gameRoot)
: gameRoot_(std::move(gameRoot)), archives_(gameRoot_ / "Data", read_layout(gameRoot_ / "Data" / "layout.toc").root) {}
fb::TocDocument GameData::read_toc(std::string_view relative) const {
const auto path = gameRoot_ / "Data" / fs::path(relative);
std::ifstream input(path, std::ios::binary);
if (!input) throw std::runtime_error("Cannot open " + path_utf8(path));
std::vector<std::byte> bytes(static_cast<std::size_t>(fs::file_size(path)));
if (!input.read(reinterpret_cast<char*>(bytes.data()), static_cast<std::streamsize>(bytes.size())))
throw std::runtime_error("Cannot read " + path_utf8(path));
return fb::read_toc(bytes);
}
std::optional<GameBundle> GameData::read_bundle(const fb::TocDocument& toc, std::string_view name) const {
for (const auto& bundle : toc.bundles) {
if (bundle.name != name) continue;
auto region = fb::read_bundle_region(bundle.region);
GameBundle result;
if (!region.inlineManifest.empty()) {
result.manifest = fb::read_binary_bundle(region.inlineManifest);
result.payloads = std::move(region.files);
} else {
// The manifest is the region's first file, in cas like any payload.
if (region.files.empty()) throw std::runtime_error("Bundle " + std::string(name) + " has no manifest");
const auto& first = region.files.front();
result.manifest = archives_.read_manifest(archives_.root(), first.location, first.offset, first.size, gameRoot_);
result.payloads.assign(region.files.begin() + 1, region.files.end());
}
return result;
}
return std::nullopt;
}
std::vector<std::byte> GameData::read(const fb::BundleFileInfo& payload) const {
const auto raw = archives_.read(archives_.root(), payload.location, payload.offset, payload.size);
return fb::decode_cas(raw, {gameRoot_});
}
}