///|
priv struct DMXBinaryReader {
input : ValveBytes
strings : Array[String]
elements : Array[DMXElement]
options : BinaryOptions
width : Int32
mut nodes : Int32
}
///|
fn DMXBinaryReader::string(self : DMXBinaryReader) -> String raise {
let index = self.input.uint(self.width)
if index >= self.strings.length().to_uint64() {
raise InvalidBinary(self.input.pos, "DMX string table index out of range")
}
self.strings[index.to_int()]
}
///|
fn DMXBinaryReader::value(
self : DMXBinaryReader,
tag : Int32,
array : Bool,
) -> DMXValue raise {
self.nodes += 1
if self.nodes > self.options.max_entries {
raise InvalidBinary(self.input.pos, "DMX value budget exceeded")
}
match tag {
1 => {
let index = self.input.uint(4)
if index == 0xffffffffUL {
DElement(None)
} else if index == 0xfffffffeUL {
DElement(Some(dmx_uuid(self.input.cstring(36))))
} else {
if index >= self.elements.length().to_uint64() {
raise InvalidBinary(self.input.pos, "DMX element index out of range")
}
DElement(Some(self.elements[index.to_int()].id))
}
}
2 => DInt(self.input.uint(4).to_uint().reinterpret_as_int())
3 => DFloatBits(self.input.uint(4).to_uint())
4 => {
let n = self.input.uint(1)
if n > 1UL {
raise InvalidBinary(self.input.pos, "Invalid DMX bool")
}
DBool(n != 0UL)
}
5 =>
DString(
if array {
self.input.cstring(self.options.max_string_bytes)
} else {
self.string()
},
)
6 => DBinary(self.input.take(self.input.count(self.options.max_bytes)))
7 => DTime(self.input.uint(4).to_uint().reinterpret_as_int())
8 => DColor(self.input.uint(4).to_uint())
9..<=14 =>
DVector(
tag,
Array::makei([2, 3, 4, 3, 4, 16][tag - 9], _ => {
self.input.uint(4).to_uint()
}),
)
_ => raise InvalidBinary(self.input.pos, "Unsupported DMX type")
}
}
///|
fn dmx_read_binary(
input : ValveBytes,
version : Int32,
format_name : String,
format_version : Int32,
options : BinaryOptions,
) -> DMXDocument raise {
if version != 4 && version != 5 {
raise InvalidBinary(input.pos, "Supported DMX binary versions are 4 and 5")
}
if input.take(2) != b"\n\x00" {
raise InvalidBinary(input.pos, "Expected DMX binary newline and NUL")
}
let count = input.count(options.max_entries)
let strings : Array[String] = []
for _ in 0.. 14 {
let kind = tag - 14
if kind > 14 {
raise InvalidBinary(input.pos, "Unsupported DMX array type")
}
let length = input.count(options.max_entries - context.nodes)
let values = Array::makei(length, _ => context.value(kind, true))
DArray(kind, values)
} else {
context.value(tag, false)
}
attributes.push({ name, value, })
}
context.elements[i] = { ..context.elements[i], attributes, }
}
if input.remaining() != 0 || context.elements.is_empty() {
raise InvalidBinary(input.pos, "Trailing or empty DMX binary data")
}
let document = {
format_name,
format_version,
root: context.elements[0].id,
elements: context.elements,
}
dmx_validate(document, options)
document
}
///|
fn dmx_binary_value(
output : @buffer.Buffer,
value : DMXValue,
elements : Map[String, Int32],
strings : Map[String, Int32],
width : Int32,
array : Bool,
) -> Unit raise {
match value {
DElement(None) => valve_uint(output, 0xffffffffUL, 4)
DElement(Some(id)) =>
if elements.get(id) is Some(index) {
valve_uint(output, index.to_uint64(), 4)
} else {
valve_uint(output, 0xfffffffeUL, 4)
vpk_cstring(output, id)
}
DInt(n) | DTime(n) =>
valve_uint(output, n.reinterpret_as_uint().to_uint64(), 4)
DFloatBits(n) | DColor(n) => valve_uint(output, n.to_uint64(), 4)
DBool(b) => valve_uint(output, if b { 1UL } else { 0UL }, 1)
DString(s) =>
if array {
vpk_cstring(output, s)
} else {
valve_uint(output, strings[s].to_uint64(), width)
}
DBinary(bytes) => {
valve_uint(output, bytes.length().to_uint64(), 4)
output.write_bytes(bytes[:])
}
DVector(_, bits) =>
for n in bits {
valve_uint(output, n.to_uint64(), 4)
}
DArray(_, _) => raise InvalidBinary(0, "Nested DMX arrays are invalid")
}
}
///|
pub fn encode_dmx_binary(
document : DMXDocument,
version~ : Int32 = 5,
options~ : BinaryOptions = BinaryOptions::default(),
) -> Bytes raise {
dmx_validate(document, options)
if version != 4 && version != 5 {
raise InvalidBinary(0, "DMX binary writer supports versions 4 and 5")
}
let names : Map[String, Int32] = Map([])
names["name"] = 0
let elements = [document.element(document.root).unwrap()] +
document.elements.filter(e => e.id != document.root)
let ids : Map[String, Int32] = Map([])
for i in 0.. a.lexical_compare(b))
let width = if version == 4 { 2 } else { 4 }
if strings.length() > (if version == 4 { 32767 } else { options.max_entries }) {
raise InvalidBinary(0, "DMX string dictionary exceeds version limits")
}
for i in 0..\n\u0000"
output.write_bytes(@utf8.encode(header[:])[:])
valve_uint(output, strings.length().to_uint64(), 4)
for s in strings {
vpk_cstring(output, s)
}
valve_uint(output, elements.length().to_uint64(), 4)
for element in elements {
valve_uint(output, names[element.kind].to_uint64(), width)
valve_uint(output, names[element.name].to_uint64(), width)
output.write_bytes(kv3_guid_bytes(element.id)[:])
}
for element in elements {
valve_uint(output, element.attributes.length().to_uint64(), 4)
for attribute in element.attributes {
valve_uint(output, names[attribute.name].to_uint64(), width)
let tag = dmx_type(attribute.value)
valve_uint(
output,
(tag + (if attribute.value is DArray(_, _) { 14 } else { 0 })).to_uint64(),
1,
)
if attribute.value is DArray(_, values) {
valve_uint(output, values.length().to_uint64(), 4)
for value in values {
dmx_binary_value(output, value, ids, names, width, true)
}
} else {
dmx_binary_value(output, attribute.value, ids, names, width, false)
}
if output.length() > options.max_bytes {
raise InvalidBinary(0, "DMX output exceeds byte budget")
}
}
}
if output.length() > options.max_bytes {
raise InvalidBinary(0, "DMX output exceeds byte budget")
}
output.to_bytes()
}