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.
libra3assets
A dependency-free C++26 library that reads and writes the resource files Red Alert 3 ships, so tooling (map editors in particular) can work with the retail assets directly. No engine, no game install required to build - only to feed it data.
It is a sub-project of this repository, a sibling of
libra3replay, and follows the same build
conventions (C++26 modules, import std;, GCC 16, CMake 4 + Ninja).
What it handles
| Module | Format | Read | Write |
|---|---|---|---|
ra3.assets:big |
BIG4 archives (Data\*.big) |
yes | yes |
ra3.assets:refpack |
EA RefPack codec (10 FB) |
yes | yes |
ra3.assets:binary |
compiled BinaryAsset streams (.bin + .manifest, cdata) |
yes | - |
ra3.assets:csf |
SAGE .csf string tables (gamestrings.csf) |
yes | yes |
ra3.assets:map |
SAGE .map containers (CkMp) incl. HeightMapData/objects |
yes | yes |
ra3.assets:bytes |
bounds-checked little-/big-endian readers and writers | yes | yes |
The map module is the centrepiece for a map editor. A .map is modelled as
an ordered list of named, versioned CkMp chunks; chunks the library does not
type (e.g. BlendTileData, SidesList) are preserved byte-for-byte and the
asset-name table keeps its original indices, so an edit/ serialise cycle is
lossless. Typed accessors cover the terrain grid (HeightMapData), every placed
object (ObjectsList, including the *Waypoints/Waypoint objects that carry
Player_N_Start), MPPositionList, WorldInfo and WaypointsList.
The binary module parses the compiled BinaryAsset streams that
BinaryAssetBuilder produces (data\static.bin, data\global.bin, ...): the
manifest index, each asset's instance slice, its relocation/import sidecars and
its cdata blob, plus the hash used to name assets.
Build
cmake -S libra3assets -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-16
cmake --build build
ctest --test-dir build --output-on-failure
The library is a set of C++20/26 modules (ra3.assets plus the :error,
:bytes, :refpack, :big, :binary, :csf and :map partitions) that
imports the standard library (import std;). That needs CMake 4.0+ with the
Ninja generator and a compiler whose standard library ships a std module -
GCC 16 in practice. The tests need nothing but the library itself.
Building with the distro GCC 16: pass
-DCMAKE_CXX_COMPILER=g++-16.
Use
import std;
import ra3.assets;
using namespace ra3::assets;
// --- a map, for a map editor -------------------------------------------------
auto document = map_document::open("map_mp_2_black1b.map");
if (const auto height = document.height_map()) {
std::println("terrain {}x{}", height->width, height->height);
auto grid = *height; // copy, then edit
grid.set(10, 10, grid.at(10, 10) + 1); // raise a cell
document.set_height_map(std::move(grid)); // re-encode the chunk
}
for (const auto &start: document.player_starts())
std::println("Player {} at ({}, {})", start.index, start.position.x, start.position.y);
for (const auto &object: document.objects())
if (object.type_name == "*Waypoints/Waypoint")
std::println("{} -> {}", object.property("waypointName")->as_ascii(), object.position.y);
// Lossless: unknown chunks and the name table survive the round-trip.
write_file("edited.map", document.serialize(/* compress = */ true));
// --- a BIG4 archive ----------------------------------------------------------
const auto archive = big_archive::open("Data/GlobalStream.big");
std::println("{} entries", archive.size());
for (const auto *entry: archive.find("audio"))
std::println("{} ({} bytes)", entry->name, entry->size);
// --- a compiled BinaryAsset stream ------------------------------------------
const auto stream = binary_stream_from_big(archive, binary_stream::global, /* need_data = */ false);
for (const auto &[type, count]: stream.type_counts())
std::println("{:6} {}", count, type);
The API deliberately avoids integer IDs for anything selectable: chunk kinds are
chunk_kind (e.g. chunk_kind::height_map_data), streams are binary_stream
(binary_stream::global), property types are property_type, and an
asset_property carries a std::variant<bool, std::int32_t, float, std::string, std::u16string>.
Command-line tool
ra3assets-cli is built alongside the library:
ra3assets-cli big list <archive.big> [match]
ra3assets-cli big extract <archive.big> <out-dir> [match]
ra3assets-cli binary list <path> [type]
ra3assets-cli binary types <path> [static|global|locale|static_l|static_m]
ra3assets-cli binary cat <path> <Type:Instance|#index> <out-file>
ra3assets-cli map info <map-file>
ra3assets-cli map starts <map-file>
ra3assets-cli map repack <map-file> <out-file> [--compress]
ra3assets-cli csf <csf-or-game-dir-or-big> [label]
ra3assets-cli hash <text>...
Notes and limits
- A
BIG4archive opened from disk keeps only its index in memory and reads payloads on demand, so enumerating the ~700 MB retailStaticStream.bigcosts a few megabytes; passneed_data = falsetobinary_stream_from_bigwhen you only need the manifest. A parsed.mapdocument is held in full - that is what its typed accessors edit. set_height_mapkeeps the on-diskHeightMapDataversion (RA3 uses 6, i.e. 16-bit elevations). Older 8-bit maps serialise back as 8-bit.- RefPack compression is a plain greedy LZ77 matcher. It is lossless against the bundled decoder and produces streams the game's decoder accepts, but it is slightly less dense than the retail compressor (≈3% on a typical map).
BlendTileData(terrain texture blending) is preserved but not yet type-modelled; a map editor should treat the chunk as opaque for now. The planned follow-up coversBlendTileDataandSidesList.- Only
BIG4is supported (RA3's format); olderBIGFarchives are not.
The on-disk layouts were reverse engineered from the shipped files and
cross-checked against ra3tools, OpenRA3's ra3.fs/ra3.map
modules and the OpenSAGE re-implementation (reference/OpenSAGE).