从 Godot 解码 Variant 字节到 javascript 对象

Decoding Variant bytes from Godot to javascript object

我正在尝试将我认为是来自 Godot 的 Uint8 数组的字节数组反序列化为节点进程中的 javascript 对象。

我当前的实现是:

const message = Buffer.from(buffer.data, 'ascii');
console.log('message', message.toString('utf8'))

这是输出类似的东西:�a@

如何在节点进程中不使用 Godot 的 bytes2var 反序列化此对象?

var2bytes 使用值对类型信息进行编码。如果 PoolByteArray 中的数据表示 utf8 字符串,请使用 String 的方法 to_utf8() 而不是 var2bytes.

字符串上还有 to_ascii() 方法。

var string = "hello"
var bytes = string.to_utf8()

如果您无权访问 Godot 中的序列化过程并且缓冲区有效负载旨在包含 ascii 或 utf8:

首先var2bytes字符串编码很简单。前 4 个字节表示变体类型和编码标志。

后4个字节表示utf8编码字符串的长度

剩下的字节是utf8编码的字符串。可以在 4 字节偏移处填充缓冲区。

const lengthOffset = 4
const messageOffset = lengthOffset * 2
const messageLength = buffer.readUInt32LE(lengthOffset)
const messageEndOffset = messageLength + messageOffset
const message = buffer.slice(messageOffset, messageEndOffset)
console.log(message.toString('utf8'))

解码多种类型的变体数组

解码一系列变体更加复杂。您将必须为每种支持的类型重新实现 decode_variant。我将带您开始解码任意大小的整数、字符串和 Vector3 数组。

资源:

  1. Marshalls decode_variant
  2. Variant Encode Values
  3. Variant Type Values

以下代码假定缓冲区有效。

// These values are from resource 2.
const variantEncodeMask = 0xFF
const variantEncodeFlag64 = 1 << 16

// These values are from resource 3.
const variantTypeInt = 2
const variantTypeString = 4
const variantTypeVector3 = 7
const variantTypeArray = 19

// Transcribed from resource 1.
function decodeVariant(buffer, _track = {offset: 0}) {
    const typeAndFlags = buffer.readUInt32LE(_track.offset)
    const type = typeAndFlags & variantEncodeMask
    _track.offset += 4

    // Buffer.slice does not copy memory. This makes the code a little easier to
    // read since every read* doesn't need the _track.offset parameter.
    const bufferSlice = buffer.slice(_track.offset)


    let result

    // The following switches on each type case. It's almost a one-to-one
    // implementation of the decode_variant function defined in marshalls.cpp.
    //
    // The main difference here is that we use _track to track the buffer offset
    // rather than using pointer arithmetic.
    //
    // Extend this by adding a case for the desired variant type. Follow godot's
    // implementation and make sure to increment _track.offset by the correct
    // byte count for every read* operation.
    switch (type) {
        case variantTypeInt:
            if (typeAndFlags & variantEncodeFlag64) {
                result = bufferSlice.readBigUInt64()
                _track.offset += 8
            } else {
                result = bufferSlice.readUInt32LE()
                _track.offset += 4
            }
            break

        case variantTypeVector3:
            result = {
                x: bufferSlice.readFloatLE(),
                y: bufferSlice.readFloatLE(4),
                z: bufferSlice.readFloatLE(8)
            }
            _track.offset += 4 * 3
            break

        case variantTypeString:
            const length = bufferSlice.readUInt32LE()
            const offset = 4
            const endOffset = length + offset
            const payload = bufferSlice.slice(offset, endOffset)
            let pad = 0
            if (length % 4) {
                pad = 4 - length % 4
            }
            result = payload.toString('utf8')

            _track.offset += endOffset + pad
            break

        case variantTypeArray:
            let count = bufferSlice.readUInt32LE()
            count &= 0x7FFFFFFF // Don't know why array needs this.
            _track.offset += 4

            result = new Array(count)
            for (let i = 0; i < count; i++) {
                result[i] = decodeVariant(buffer, _track)
            }
            break

    }

    return result
}