///|
pub(all) struct ResourceBlock {
kind : String
offset : Int32
bytes : Bytes
} derive(Eq, Debug)
///|
pub(all) struct ResourceContainer {
version : Int32
declared_size : Int32
blocks : Array[ResourceBlock]
trailing : Bytes
} derive(Eq, Debug)
///|
pub(all) struct ResourceExternalReference {
id : UInt64
name : String
} derive(Eq, Debug)
///|
pub extend ResourceBlock with Eq::{equal, not_equal}
///|
pub extend ResourceBlock with @debug.Debug::{to_repr}
///|
pub extend ResourceContainer with Eq::{equal, not_equal}
///|
pub extend ResourceContainer with @debug.Debug::{to_repr}
///|
pub extend ResourceExternalReference with Eq::{equal, not_equal}
///|
pub extend ResourceExternalReference with @debug.Debug::{to_repr}
///|
/// Source 2 resource header version 12; block offsets address their own offset
/// fields. Payloads remain bounded slices; unknown blocks are exposed unchanged.
pub fn read_resource_container(
bytes : Bytes,
options~ : BinaryOptions = BinaryOptions::default(),
) -> ResourceContainer raise {
check_binary_options(options)
if bytes.length() > options.max_bytes {
raise InvalidBinary(0, "Resource exceeds byte budget")
}
let input = valve_bytes(bytes)
let declared_size = input.count(bytes.length())
if input.uint(2) != 12UL || declared_size < 16 {
raise InvalidBinary(4, "Unsupported resource header")
}
let version = input.uint(2).to_int()
let table = input.count(declared_size - 8) + 8
let count = input.count(options.max_entries)
if table < 16 || table > declared_size || count > (declared_size - table) / 12 {
raise InvalidBinary(table, "Invalid resource block table")
}
input.pos = table
let ranges : Array[(Int32, Int32)] = [(0, table + count * 12)]
let blocks : Array[ResourceBlock] = []
for _ in 0.. c < b'\x20' || c > b'\x7e') {
raise InvalidBinary(input.pos, "Invalid resource block name")
}
let kind = @utf8.decode(kind_bytes[:])
let base = input.pos
let offset = input.count(declared_size - base) + base
let size = input.count(declared_size - offset)
for (start, end) in ranges {
if size > 0 && offset < end && start < offset + size {
raise InvalidBinary(offset, "Overlapping resource blocks")
}
}
ranges.push((offset, offset + size))
blocks.push({ kind, offset, bytes: bytes[offset:offset + size].to_owned(), })
}
{
version,
declared_size,
blocks,
trailing: bytes[declared_size:].to_owned(),
}
}
///|
pub fn ResourceContainer::data(
self : ResourceContainer,
options~ : BinaryOptions = BinaryOptions::default(),
) -> KV3BinaryDocument raise {
let found = self.blocks.filter(b => b.kind == "DATA")
if found.length() != 1 {
raise InvalidBinary(0, "Expected exactly one DATA block")
}
decode_kv3_binary(found[0].bytes, options~)
}
///|
pub fn ResourceContainer::external_references(
self : ResourceContainer,
options~ : BinaryOptions = BinaryOptions::default(),
) -> Array[ResourceExternalReference] raise {
check_binary_options(options)
if self.blocks.length() > options.max_entries {
raise InvalidBinary(0, "Resource block count exceeds budget")
}
let mut total = 0
for block in self.blocks {
if block.bytes.length() > options.max_bytes - total {
raise InvalidBinary(0, "Resource blocks exceed aggregate budget")
}
total += block.bytes.length()
}
let output : Array[ResourceExternalReference] = []
for block in self.blocks {
if block.kind != "RERL" {
continue
}
let reader = valve_bytes(block.bytes)
let table = reader.count(block.bytes.length())
let count = reader.count(options.max_entries - output.length())
if count == 0 {
continue
}
if table < 8 || count > (block.bytes.length() - table) / 16 {
raise InvalidBinary(table, "Invalid RERL table")
}
reader.pos = table
for _ in 0..= 2147483648L {
relative - 4294967296L
} else {
relative
}
let absolute = base.to_int64() + signed
reader.uint(4) |> ignore
if absolute < (table + count * 16).to_int64() ||
absolute >= block.bytes.length().to_int64() {
raise InvalidBinary(base, "Invalid RERL string offset")
}
let name_reader = valve_bytes(block.bytes)
name_reader.pos = absolute.to_int()
output.push({ id, name: name_reader.cstring(options.max_string_bytes), })
}
}
output
}
///|
/// Construct a DATA-only resource. Rebuilding arbitrary compiled resources
/// needs block-specific relocation and is deliberately a separate operation.
pub fn encode_kv3_resource(
document : KV3BinaryDocument,
resource_version~ : Int32 = 1,
version~ : Int32 = 5,
compression~ : Int32 = 0,
options~ : BinaryOptions = BinaryOptions::default(),
) -> Bytes raise {
if resource_version < 0 || resource_version > 65535 {
raise InvalidBinary(0, "Resource version exceeds uint16")
}
let data = encode_kv3_binary(document, version~, compression~, options~)
if data.length() > options.max_bytes - 28 {
raise InvalidBinary(0, "Resource exceeds byte budget")
}
let output = @buffer.Buffer()
valve_uint(output, (28 + data.length()).to_uint64(), 4)
valve_uint(output, 12UL, 2)
valve_uint(output, resource_version.to_uint64(), 2)
valve_uint(output, 8UL, 4)
valve_uint(output, 1UL, 4)
output.write_bytes(b"DATA"[:])
valve_uint(output, 8UL, 4)
valve_uint(output, data.length().to_uint64(), 4)
output.write_bytes(data[:])
output.to_bytes()
}