Vendor libra3assets (C++26 modules: BIG4, RefPack, BinaryAsset, CSF, CkMp map) under third_party/ and add the ra3_assets target. ra3.fs, ra3.map and ra3.terrain become thin adapters over it: - ra3.fs delegates RefPack and the BIG4 index/payload reads (index-only, payloads read on demand). - ra3.map decodes ObjectsList and CSF via map_document/csf_table; starts come from player_starts(), replacing the off-by-one whole-buffer scan. - ra3.terrain takes the CkMp container and HeightMapData from map_document, with BlendTileData (not modelled by the library) parsed from the chunk payload. Adds an opt-in real-asset check (OPENRA3_TEST_ASSETS) plus the library's own unit suite as ra3assets_unit.
66 lines
2.5 KiB
C++
66 lines
2.5 KiB
C++
/**
|
|
* libra3assets - read (and write) Red Alert 3's built-in resource files.
|
|
*
|
|
* The primary module interface re-exports every partition:
|
|
*
|
|
* - `:error` - the `asset_error` exception family
|
|
* - `:bytes` - bounds-checked little-/big-endian readers and writers
|
|
* - `:refpack` - EA's RefPack codec (decompress and compress)
|
|
* - `:big` - `BIG4` archives (`Data\*.big`), read and write
|
|
* - `:binary` - compiled `BinaryAsset` streams (`*.bin` + `.manifest`)
|
|
* - `:csf` - SAGE `.csf` string tables
|
|
* - `:map` - SAGE `.map` containers (`CkMp`), read and write
|
|
*
|
|
* Nothing here reads game data on its own: callers point the library at their
|
|
* own installation (`RA3_GAME_DIR` / `C:\Red Alert 3`).
|
|
*/
|
|
export module ra3.assets;
|
|
|
|
export import :error;
|
|
export import :bytes;
|
|
export import :refpack;
|
|
export import :big;
|
|
export import :binary;
|
|
export import :csf;
|
|
export import :map;
|
|
|
|
import std;
|
|
|
|
export namespace ra3::assets {
|
|
|
|
/**
|
|
* Locate a Red Alert 3 installation.
|
|
*
|
|
* Resolution order: the explicit argument, then `$RA3_GAME_DIR`, then the
|
|
* default `C:\Red Alert 3`. A directory qualifies only if it has a `Data`
|
|
* subdirectory.
|
|
*/
|
|
[[nodiscard]] inline auto find_game_dir(const std::optional<std::filesystem::path> &explicit_dir = std::nullopt) -> std::optional<std::filesystem::path> {
|
|
const auto qualifies = [](const std::filesystem::path &candidate) {
|
|
std::error_code ec;
|
|
return !candidate.empty() && std::filesystem::is_directory(candidate / "Data", ec);
|
|
};
|
|
|
|
if (explicit_dir && qualifies(*explicit_dir)) return explicit_dir;
|
|
if (const auto *env = std::getenv("RA3_GAME_DIR"); env != nullptr && *env != '\0') {
|
|
const std::filesystem::path candidate{env};
|
|
if (qualifies(candidate)) return candidate;
|
|
}
|
|
const std::filesystem::path default_dir{"C:/Red Alert 3"};
|
|
if (qualifies(default_dir)) return default_dir;
|
|
return std::nullopt;
|
|
}
|
|
|
|
/** Every `Data\*.big` archive in an installation, sorted by name. */
|
|
[[nodiscard]] inline auto list_archives(const std::filesystem::path &game_dir) -> std::vector<std::filesystem::path> {
|
|
std::vector<std::filesystem::path> archives;
|
|
std::error_code ec;
|
|
for (const auto &entry: std::filesystem::directory_iterator(game_dir / "Data", ec)) {
|
|
if (entry.is_regular_file() && entry.path().extension() == ".big") archives.push_back(entry.path());
|
|
}
|
|
std::ranges::sort(archives);
|
|
return archives;
|
|
}
|
|
|
|
} // namespace ra3::assets
|