mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-17 13:57:05 +02:00
- 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.
86 lines
3.6 KiB
C++
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_});
|
|
}
|
|
}
|