mirror of
https://github.com/imezx/Warp.git
synced 2026-06-02 12:18:32 +00:00
2556 lines
61 KiB
Text
2556 lines
61 KiB
Text
--!native
|
|
--!optimize 2
|
|
--@EternityDev
|
|
export type Writer = {
|
|
buf: buffer,
|
|
cursor: number,
|
|
capacity: number,
|
|
refs: { any },
|
|
}
|
|
|
|
local DEFAULT_CAPACITY: number = 64
|
|
local NaN_VALUE: number = 0 / 0
|
|
|
|
-- 0x00-0x7F: + fixint (0-127) — single byte
|
|
-- 0x80-0x9F: - fixint (-32 to -1) — single byte
|
|
local T_NIL = 0xA0
|
|
local T_FALSE = 0xA1
|
|
local T_TRUE = 0xA2
|
|
local T_U8 = 0xA3
|
|
local T_U16 = 0xA4
|
|
local T_U32 = 0xA5
|
|
local T_I8 = 0xA6
|
|
local T_I16 = 0xA7
|
|
local T_I32 = 0xA8
|
|
local T_F16 = 0xAB
|
|
local T_F32 = 0xA9
|
|
local T_F64 = 0xAA
|
|
|
|
-- str: 0xB0-0xBF = inline len 0-15
|
|
local T_STR_BASE = 0xB0
|
|
local T_STR8 = 0xC0
|
|
local T_STR16 = 0xC1
|
|
local T_STRVAR = 0xC2
|
|
|
|
-- arrays: 0xC4-0xCB = inline len 0-7
|
|
local T_ARR_BASE = 0xC4
|
|
local T_ARR8 = 0xCC
|
|
local T_ARR16 = 0xCD
|
|
local T_ARRVAR = 0xCE
|
|
|
|
-- maps: 0xD0-0xD7 = inline len 0-7
|
|
local T_MAP_BASE = 0xD0
|
|
local T_MAP8 = 0xD8
|
|
local T_MAP16 = 0xD9
|
|
local T_MAPVAR = 0xDA
|
|
|
|
-- typed arrays (homogeneous, no per-element tags)
|
|
local T_TYPED_ARR = 0xDB
|
|
local T_BUFFER = 0xDC
|
|
local T_BOOL_ARR = 0xDD
|
|
|
|
local T_VEC3 = 0xE0
|
|
local T_VEC2 = 0xE2
|
|
local T_CFRAME = 0xE4
|
|
local T_COLOR3 = 0xE6
|
|
local T_COLOR3_F = 0xE7
|
|
local T_BRICKCOLOR = 0xE8
|
|
local T_INSTANCE = 0xE9
|
|
local T_ENUMITEM = 0xEA
|
|
local T_ENUM = 0xEB
|
|
local T_UDIM2 = 0xEC
|
|
local T_UDIM = 0xED
|
|
local T_RECT = 0xEE
|
|
local T_NUMBERRANGE = 0xEF
|
|
local T_RAY = 0xF0
|
|
local T_COLSEQ = 0xF2
|
|
local T_NUMSEQ = 0xF3
|
|
local T_VEC3I16 = 0xF1
|
|
local T_VEC2I16 = 0xF4
|
|
local T_TWEENINFO = 0xF5
|
|
local T_PHYSPROP = 0xF6
|
|
local T_FONT = 0xF7
|
|
local T_DATETIME = 0xF8
|
|
|
|
local F16_LOOKUP: { number } = table.create(65536, 0)
|
|
do
|
|
for raw = 0, 65535 do
|
|
local sign: number = bit32.btest(raw, 0x8000) and -1 or 1
|
|
local exponent: number = bit32.extract(raw, 10, 5)
|
|
local mantissa: number = bit32.band(raw, 0x03FF)
|
|
local value: number
|
|
if exponent == 0 then
|
|
if mantissa == 0 then
|
|
value = 0 * sign
|
|
else
|
|
value = sign * (mantissa / 1024) * 6.103515625e-05
|
|
end
|
|
elseif exponent == 31 then
|
|
if mantissa == 0 then
|
|
value = sign * math.huge
|
|
else
|
|
value = NaN_VALUE
|
|
end
|
|
else
|
|
value = sign * (1 + mantissa / 1024) * math.ldexp(1, exponent - 15)
|
|
end
|
|
F16_LOOKUP[raw + 1] = value
|
|
-- if raw % 8192 == 0 then
|
|
-- task.wait()
|
|
-- end
|
|
end
|
|
end
|
|
|
|
local function encodeF16(value: number): number
|
|
if value ~= value then
|
|
return 0x7E00
|
|
end
|
|
if value == 0 then
|
|
return if 1 / value < 0 then 0x8000 else 0
|
|
end
|
|
local sign = 0
|
|
if value < 0 then
|
|
sign = 0x8000
|
|
value = -value
|
|
end
|
|
if value >= 65504 then
|
|
return sign + 0x7C00
|
|
end
|
|
if value < 6.103515625e-05 then
|
|
local f = math.floor(value * 16777216 + 0.5)
|
|
return sign + (if f >= 1024 then 0x0400 else f)
|
|
end
|
|
local m, e = math.frexp(value)
|
|
local biasedExp = e + 14
|
|
local f = math.floor(m * 2048 - 1024 + 0.5)
|
|
if f >= 1024 then
|
|
f = 0
|
|
biasedExp += 1
|
|
end
|
|
if biasedExp >= 31 then
|
|
return sign + 0x7C00
|
|
end
|
|
return sign + biasedExp * 1024 + f
|
|
end
|
|
|
|
local function readF16(b: buffer, pos: number): number
|
|
return F16_LOOKUP[buffer.readu16(b, pos) + 1]
|
|
end
|
|
|
|
local function varUIntSize(value: number): number
|
|
if value < 0x80 then
|
|
return 1
|
|
end
|
|
if value < 0x4000 then
|
|
return 2
|
|
end
|
|
if value < 0x200000 then
|
|
return 3
|
|
end
|
|
if value < 0x10000000 then
|
|
return 4
|
|
end
|
|
return 5
|
|
end
|
|
|
|
local function writeVarUInt(buf: buffer, offset: number, value: number): number
|
|
while value >= 0x80 do
|
|
buffer.writeu8(buf, offset, bit32.bor(bit32.band(value, 0x7F), 0x80))
|
|
offset += 1
|
|
value = bit32.rshift(value, 7)
|
|
end
|
|
buffer.writeu8(buf, offset, value)
|
|
return offset + 1
|
|
end
|
|
|
|
local function readVarUInt(buf: buffer, offset: number): (number, number)
|
|
local value = 0
|
|
local shift = 0
|
|
repeat
|
|
local byte = buffer.readu8(buf, offset)
|
|
offset += 1
|
|
value = bit32.bor(value, bit32.lshift(bit32.band(byte, 0x7F), shift))
|
|
shift += 7
|
|
until byte < 0x80
|
|
return value, offset
|
|
end
|
|
|
|
local function createWriter(capacity: number?): Writer
|
|
local cap = capacity or DEFAULT_CAPACITY
|
|
return {
|
|
buf = buffer.create(cap),
|
|
cursor = 0,
|
|
capacity = cap,
|
|
refs = {},
|
|
}
|
|
end
|
|
|
|
local function ensureSpace(w: Writer, bytes: number)
|
|
local needed = w.cursor + bytes
|
|
if needed <= w.capacity then
|
|
return
|
|
end
|
|
local newCap = w.capacity
|
|
while newCap < needed do
|
|
newCap *= 2
|
|
end
|
|
local newBuf = buffer.create(newCap)
|
|
buffer.copy(newBuf, 0, w.buf, 0, w.cursor)
|
|
w.buf = newBuf
|
|
w.capacity = newCap
|
|
end
|
|
|
|
local function rawU8(w: Writer, v: number)
|
|
buffer.writeu8(w.buf, w.cursor, v)
|
|
w.cursor += 1
|
|
end
|
|
|
|
local function rawU16(w: Writer, v: number)
|
|
buffer.writeu16(w.buf, w.cursor, v)
|
|
w.cursor += 2
|
|
end
|
|
|
|
local function rawI16(w: Writer, v: number)
|
|
buffer.writei16(w.buf, w.cursor, v)
|
|
w.cursor += 2
|
|
end
|
|
|
|
local function rawU32(w: Writer, v: number)
|
|
buffer.writeu32(w.buf, w.cursor, v)
|
|
w.cursor += 4
|
|
end
|
|
|
|
local function rawI32(w: Writer, v: number)
|
|
buffer.writei32(w.buf, w.cursor, v)
|
|
w.cursor += 4
|
|
end
|
|
|
|
local function rawF32(w: Writer, v: number)
|
|
buffer.writef32(w.buf, w.cursor, v)
|
|
w.cursor += 4
|
|
end
|
|
|
|
local function rawF64(w: Writer, v: number)
|
|
buffer.writef64(w.buf, w.cursor, v)
|
|
w.cursor += 8
|
|
end
|
|
|
|
local function rawF16(w: Writer, value: number)
|
|
buffer.writeu16(w.buf, w.cursor, encodeF16(value))
|
|
w.cursor += 2
|
|
end
|
|
|
|
local function rawVarUInt(w: Writer, v: number)
|
|
w.cursor = writeVarUInt(w.buf, w.cursor, v)
|
|
end
|
|
|
|
local function rawString(w: Writer, s: string, n: number)
|
|
buffer.writestring(w.buf, w.cursor, s)
|
|
w.cursor += n
|
|
end
|
|
|
|
local function wByte(w: Writer, v: number)
|
|
ensureSpace(w, 1)
|
|
buffer.writeu8(w.buf, w.cursor, v)
|
|
w.cursor += 1
|
|
end
|
|
|
|
local function wU16(w: Writer, v: number)
|
|
ensureSpace(w, 2)
|
|
buffer.writeu16(w.buf, w.cursor, v)
|
|
w.cursor += 2
|
|
end
|
|
|
|
local function wI16(w: Writer, v: number)
|
|
ensureSpace(w, 2)
|
|
buffer.writei16(w.buf, w.cursor, v)
|
|
w.cursor += 2
|
|
end
|
|
|
|
local function wU32(w: Writer, v: number)
|
|
ensureSpace(w, 4)
|
|
buffer.writeu32(w.buf, w.cursor, v)
|
|
w.cursor += 4
|
|
end
|
|
|
|
local function wI32(w: Writer, v: number)
|
|
ensureSpace(w, 4)
|
|
buffer.writei32(w.buf, w.cursor, v)
|
|
w.cursor += 4
|
|
end
|
|
|
|
local function wF16(w: Writer, value: number)
|
|
ensureSpace(w, 2)
|
|
buffer.writeu16(w.buf, w.cursor, encodeF16(value))
|
|
w.cursor += 2
|
|
end
|
|
|
|
local function wF32(w: Writer, v: number)
|
|
ensureSpace(w, 4)
|
|
buffer.writef32(w.buf, w.cursor, v)
|
|
w.cursor += 4
|
|
end
|
|
|
|
local function wF64(w: Writer, v: number)
|
|
ensureSpace(w, 8)
|
|
buffer.writef64(w.buf, w.cursor, v)
|
|
w.cursor += 8
|
|
end
|
|
|
|
local function wVarUInt(w: Writer, v: number)
|
|
ensureSpace(w, 5) -- max varuint = 5 bytes; avoids double-analysis via varUIntSize
|
|
w.cursor = writeVarUInt(w.buf, w.cursor, v)
|
|
end
|
|
|
|
local function wString(w: Writer, s: string)
|
|
local n = #s
|
|
ensureSpace(w, n)
|
|
buffer.writestring(w.buf, w.cursor, s)
|
|
w.cursor += n
|
|
end
|
|
|
|
local packValue: (w: Writer, v: any) -> ()
|
|
local unpackValue: (buf: buffer, pos: number, refs: { any }?) -> (any, number)
|
|
|
|
local F32_TEST_BUF = buffer.create(4)
|
|
|
|
local TYPED_CODES = { u8 = 1, i8 = 2, u16 = 3, i16 = 4, u32 = 5, i32 = 6, f32 = 7, f64 = 8, f16 = 9 }
|
|
local TYPED_SIZES = { u8 = 1, i8 = 1, u16 = 2, i16 = 2, u32 = 4, i32 = 4, f32 = 4, f64 = 8, f16 = 2 }
|
|
|
|
local TYPED_WRITERS = {
|
|
u8 = buffer.writeu8,
|
|
i8 = buffer.writei8,
|
|
u16 = buffer.writeu16,
|
|
i16 = buffer.writei16,
|
|
u32 = buffer.writeu32,
|
|
i32 = buffer.writei32,
|
|
f32 = buffer.writef32,
|
|
f64 = buffer.writef64,
|
|
f16 = function(b: buffer, offset: number, value: number)
|
|
buffer.writeu16(b, offset, encodeF16(value))
|
|
end,
|
|
}
|
|
|
|
local TYPED_READERS = {
|
|
[1] = function(b: buffer, o: number)
|
|
return buffer.readu8(b, o), o + 1
|
|
end,
|
|
[2] = function(b: buffer, o: number)
|
|
return buffer.readi8(b, o), o + 1
|
|
end,
|
|
[3] = function(b: buffer, o: number)
|
|
return buffer.readu16(b, o), o + 2
|
|
end,
|
|
[4] = function(b: buffer, o: number)
|
|
return buffer.readi16(b, o), o + 2
|
|
end,
|
|
[5] = function(b: buffer, o: number)
|
|
return buffer.readu32(b, o), o + 4
|
|
end,
|
|
[6] = function(b: buffer, o: number)
|
|
return buffer.readi32(b, o), o + 4
|
|
end,
|
|
[7] = function(b: buffer, o: number)
|
|
return buffer.readf32(b, o), o + 4
|
|
end,
|
|
[8] = function(b: buffer, o: number)
|
|
return buffer.readf64(b, o), o + 8
|
|
end,
|
|
[9] = function(b: buffer, o: number)
|
|
return readF16(b, o), o + 2
|
|
end,
|
|
}
|
|
|
|
-- CFrame quaternion compression (Shepperd's method + smallest-three encoding).
|
|
-- Extracts a unit quaternion directly from the rotation matrix - no trig needed.
|
|
-- The three stored components are guaranteed in [-1/√2, 1/√2], so we rescale
|
|
-- by √2 to fill the full i16 range, giving ~41% better precision for free.
|
|
local CF_I16_SCALE: number = 32767 * math.sqrt(2)
|
|
local CF_I16_INV_SCALE: number = 1 / CF_I16_SCALE
|
|
|
|
local function compressCFrameRotation(cf: CFrame): (number, number, number, number)
|
|
local _, _, _, r00, r01, r02, r10, r11, r12, r20, r21, r22 = cf:GetComponents()
|
|
local trace: number = r00 + r11 + r22
|
|
local qx: number, qy: number, qz: number, qw: number
|
|
if trace > 0 then
|
|
local sq: number = math.sqrt(trace + 1)
|
|
local s: number = 0.5 / sq
|
|
qw = sq * 0.5
|
|
qx = (r21 - r12) * s
|
|
qy = (r02 - r20) * s
|
|
qz = (r10 - r01) * s
|
|
elseif r00 > r11 and r00 > r22 then
|
|
local sq: number = math.sqrt(1 + r00 - r11 - r22)
|
|
local s: number = 0.5 / sq
|
|
qx = sq * 0.5
|
|
qw = (r21 - r12) * s
|
|
qy = (r01 + r10) * s
|
|
qz = (r02 + r20) * s
|
|
elseif r11 > r22 then
|
|
local sq: number = math.sqrt(1 + r11 - r00 - r22)
|
|
local s: number = 0.5 / sq
|
|
qy = sq * 0.5
|
|
qw = (r02 - r20) * s
|
|
qx = (r01 + r10) * s
|
|
qz = (r12 + r21) * s
|
|
else
|
|
local sq: number = math.sqrt(1 + r22 - r00 - r11)
|
|
local s: number = 0.5 / sq
|
|
qz = sq * 0.5
|
|
qw = (r10 - r01) * s
|
|
qx = (r02 + r20) * s
|
|
qy = (r12 + r21) * s
|
|
end
|
|
|
|
local index, value, sign = 1, math.abs(qx), qx >= 0 and 1 or -1
|
|
local abs
|
|
abs = math.abs(qy)
|
|
if abs > value then
|
|
index = 2
|
|
value = abs
|
|
sign = qy >= 0 and 1 or -1
|
|
end
|
|
abs = math.abs(qz)
|
|
if abs > value then
|
|
index = 3
|
|
value = abs
|
|
sign = qz >= 0 and 1 or -1
|
|
end
|
|
abs = math.abs(qw)
|
|
if abs > value then
|
|
index = 4
|
|
sign = qw >= 0 and 1 or -1
|
|
end
|
|
|
|
local scale = CF_I16_SCALE * sign
|
|
local v0: number, v1: number, v2: number
|
|
if index == 1 then
|
|
v0 = math.round(qy * scale)
|
|
v1 = math.round(qz * scale)
|
|
v2 = math.round(qw * scale)
|
|
elseif index == 2 then
|
|
v0 = math.round(qx * scale)
|
|
v1 = math.round(qz * scale)
|
|
v2 = math.round(qw * scale)
|
|
elseif index == 3 then
|
|
v0 = math.round(qx * scale)
|
|
v1 = math.round(qy * scale)
|
|
v2 = math.round(qw * scale)
|
|
else
|
|
v0 = math.round(qx * scale)
|
|
v1 = math.round(qy * scale)
|
|
v2 = math.round(qz * scale)
|
|
end
|
|
return index, v0, v1, v2
|
|
end
|
|
|
|
local function decompressCFrameRotation(
|
|
index: number,
|
|
v0: number,
|
|
v1: number,
|
|
v2: number
|
|
): (number, number, number, number)
|
|
v0 *= CF_I16_INV_SCALE
|
|
v1 *= CF_I16_INV_SCALE
|
|
v2 *= CF_I16_INV_SCALE
|
|
local sq = 1 - (v0 * v0 + v1 * v1 + v2 * v2)
|
|
local d = sq > 0 and math.sqrt(sq) or 0
|
|
if index == 1 then
|
|
return d, v0, v1, v2
|
|
elseif index == 2 then
|
|
return v0, d, v1, v2
|
|
elseif index == 3 then
|
|
return v0, v1, d, v2
|
|
end
|
|
return v0, v1, v2, d
|
|
end
|
|
|
|
local function packNumber(w: Writer, n: number)
|
|
if n ~= n then
|
|
ensureSpace(w, 9)
|
|
rawU8(w, T_F64)
|
|
rawF64(w, n)
|
|
return
|
|
end
|
|
|
|
local isInt = n == math.floor(n)
|
|
|
|
if isInt then
|
|
if n >= 0 then
|
|
if n <= 127 then
|
|
ensureSpace(w, 1)
|
|
rawU8(w, n)
|
|
return
|
|
end
|
|
if n <= 255 then
|
|
ensureSpace(w, 2)
|
|
rawU8(w, T_U8)
|
|
rawU8(w, n)
|
|
elseif n <= 65535 then
|
|
ensureSpace(w, 3)
|
|
rawU8(w, T_U16)
|
|
rawU16(w, n)
|
|
elseif n <= 4294967295 then
|
|
ensureSpace(w, 5)
|
|
rawU8(w, T_U32)
|
|
rawU32(w, n)
|
|
else
|
|
ensureSpace(w, 9)
|
|
rawU8(w, T_F64)
|
|
rawF64(w, n)
|
|
end
|
|
else
|
|
if n >= -32 then
|
|
ensureSpace(w, 1)
|
|
rawU8(w, bit32.bor(0x80, n + 32))
|
|
return
|
|
end
|
|
if n >= -128 then
|
|
ensureSpace(w, 2)
|
|
rawU8(w, T_I8)
|
|
buffer.writei8(w.buf, w.cursor, n)
|
|
w.cursor += 1
|
|
elseif n >= -32768 then
|
|
ensureSpace(w, 3)
|
|
rawU8(w, T_I16)
|
|
rawI16(w, n)
|
|
elseif n >= -2147483648 then
|
|
ensureSpace(w, 5)
|
|
rawU8(w, T_I32)
|
|
rawI32(w, n)
|
|
else
|
|
ensureSpace(w, 9)
|
|
rawU8(w, T_F64)
|
|
rawF64(w, n)
|
|
end
|
|
end
|
|
else
|
|
buffer.writef32(F32_TEST_BUF, 0, n)
|
|
local f32Val = buffer.readf32(F32_TEST_BUF, 0)
|
|
|
|
if f32Val == n then
|
|
ensureSpace(w, 5)
|
|
rawU8(w, T_F32)
|
|
rawF32(w, n)
|
|
else
|
|
ensureSpace(w, 9)
|
|
rawU8(w, T_F64)
|
|
rawF64(w, n)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function packString(w: Writer, s: string)
|
|
local n = #s
|
|
if n <= 15 then
|
|
ensureSpace(w, 1 + n)
|
|
rawU8(w, T_STR_BASE + n)
|
|
elseif n <= 255 then
|
|
ensureSpace(w, 2 + n)
|
|
rawU8(w, T_STR8)
|
|
rawU8(w, n)
|
|
elseif n <= 65535 then
|
|
ensureSpace(w, 3 + n)
|
|
rawU8(w, T_STR16)
|
|
rawU16(w, n)
|
|
else
|
|
ensureSpace(w, 6 + n) -- tag(1) + varuint(max5) + data
|
|
rawU8(w, T_STRVAR)
|
|
rawVarUInt(w, n)
|
|
end
|
|
rawString(w, s, n)
|
|
end
|
|
|
|
local function analyzeArray(t: { any }, count: number): (string?, string?, number)
|
|
if count < 2 then
|
|
return nil, nil, count
|
|
end
|
|
|
|
local first = t[1]
|
|
local firstType = type(first)
|
|
|
|
if firstType == "boolean" then
|
|
for i = 2, count do
|
|
if type(t[i]) ~= "boolean" then
|
|
return nil, nil, count
|
|
end
|
|
end
|
|
return "boolean", nil, count
|
|
end
|
|
|
|
if firstType ~= "number" then
|
|
return nil, nil, count
|
|
end
|
|
|
|
local minVal, maxVal = first, first
|
|
local allInt = first == math.floor(first)
|
|
|
|
for i = 2, count do
|
|
local v = t[i]
|
|
if type(v) ~= "number" then
|
|
return nil, nil, count
|
|
end
|
|
if v ~= math.floor(v) then
|
|
allInt = false
|
|
end
|
|
if v < minVal then
|
|
minVal = v
|
|
end
|
|
if v > maxVal then
|
|
maxVal = v
|
|
end
|
|
end
|
|
|
|
if not allInt then
|
|
return "number", "f32", count
|
|
elseif minVal >= 0 and maxVal <= 255 then
|
|
return "number", "u8", count
|
|
elseif minVal >= -128 and maxVal <= 127 then
|
|
return "number", "i8", count
|
|
elseif minVal >= 0 and maxVal <= 65535 then
|
|
return "number", "u16", count
|
|
elseif minVal >= -32768 and maxVal <= 32767 then
|
|
return "number", "i16", count
|
|
elseif minVal >= 0 and maxVal <= 4294967295 then
|
|
return "number", "u32", count
|
|
elseif minVal >= -2147483648 and maxVal <= 2147483647 then
|
|
return "number", "i32", count
|
|
end
|
|
return "number", "f64", count
|
|
end
|
|
|
|
local function packTable(w: Writer, t: { [any]: any })
|
|
local count = 0
|
|
local maxIdx = 0
|
|
local isArray = true
|
|
|
|
for k in t do
|
|
count += 1
|
|
if type(k) == "number" and k > 0 and k == math.floor(k) then
|
|
if k > maxIdx then
|
|
maxIdx = k
|
|
end
|
|
else
|
|
isArray = false
|
|
end
|
|
end
|
|
|
|
isArray = isArray and maxIdx == count
|
|
|
|
if isArray then
|
|
local category, dtype, arrCount = analyzeArray(t, count)
|
|
|
|
if category == "boolean" then
|
|
local numBytes = math.ceil(arrCount / 8)
|
|
ensureSpace(w, 1 + 5 + numBytes)
|
|
rawU8(w, T_BOOL_ARR)
|
|
rawVarUInt(w, arrCount)
|
|
for i = 0, numBytes - 1 do
|
|
local byte = 0
|
|
for bit = 0, 7 do
|
|
local idx = i * 8 + bit + 1
|
|
if idx > arrCount then
|
|
break
|
|
end
|
|
if t[idx] then
|
|
byte = bit32.bor(byte, bit32.lshift(1, bit))
|
|
end
|
|
end
|
|
buffer.writeu8(w.buf, w.cursor, byte)
|
|
w.cursor += 1
|
|
end
|
|
return
|
|
end
|
|
|
|
-- typed num arrays
|
|
if category == "number" and dtype and arrCount >= 4 then
|
|
local code = TYPED_CODES[dtype]
|
|
local size = TYPED_SIZES[dtype]
|
|
local writer = TYPED_WRITERS[dtype]
|
|
|
|
ensureSpace(w, 2 + 5 + arrCount * size)
|
|
rawU8(w, T_TYPED_ARR)
|
|
rawU8(w, code)
|
|
rawVarUInt(w, arrCount)
|
|
for i = 1, arrCount do
|
|
writer(w.buf, w.cursor, t[i])
|
|
w.cursor += size
|
|
end
|
|
return
|
|
end
|
|
|
|
-- regular array
|
|
if count <= 7 then
|
|
wByte(w, T_ARR_BASE + count)
|
|
elseif count <= 255 then
|
|
ensureSpace(w, 2)
|
|
rawU8(w, T_ARR8)
|
|
rawU8(w, count)
|
|
elseif count <= 65535 then
|
|
ensureSpace(w, 3)
|
|
rawU8(w, T_ARR16)
|
|
rawU16(w, count)
|
|
else
|
|
ensureSpace(w, 6)
|
|
rawU8(w, T_ARRVAR)
|
|
rawVarUInt(w, count)
|
|
end
|
|
|
|
for i = 1, count do
|
|
packValue(w, t[i])
|
|
end
|
|
else
|
|
-- map
|
|
if count <= 7 then
|
|
wByte(w, T_MAP_BASE + count)
|
|
elseif count <= 255 then
|
|
ensureSpace(w, 2)
|
|
rawU8(w, T_MAP8)
|
|
rawU8(w, count)
|
|
elseif count <= 65535 then
|
|
ensureSpace(w, 3)
|
|
rawU8(w, T_MAP16)
|
|
rawU16(w, count)
|
|
else
|
|
ensureSpace(w, 6)
|
|
rawU8(w, T_MAPVAR)
|
|
rawVarUInt(w, count)
|
|
end
|
|
|
|
for k, v in t do
|
|
packValue(w, k)
|
|
packValue(w, v)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function packVector3(w: Writer, v: Vector3)
|
|
ensureSpace(w, 7)
|
|
rawU8(w, T_VEC3)
|
|
rawF16(w, v.X)
|
|
rawF16(w, v.Y)
|
|
rawF16(w, v.Z)
|
|
end
|
|
|
|
local function packVector2(w: Writer, v: Vector2)
|
|
ensureSpace(w, 5)
|
|
rawU8(w, T_VEC2)
|
|
rawF16(w, v.X)
|
|
rawF16(w, v.Y)
|
|
end
|
|
|
|
local function packCFrame(w: Writer, cf: CFrame)
|
|
local pos = cf.Position
|
|
local qi, q0, q1, q2 = compressCFrameRotation(cf)
|
|
ensureSpace(w, 20) -- tag(1) + 3xf32(12) + u8(1) + 3xi16(6)
|
|
rawU8(w, T_CFRAME)
|
|
rawF32(w, pos.X)
|
|
rawF32(w, pos.Y)
|
|
rawF32(w, pos.Z)
|
|
rawU8(w, qi)
|
|
rawI16(w, q0)
|
|
rawI16(w, q1)
|
|
rawI16(w, q2)
|
|
end
|
|
|
|
local function packColor3(w: Writer, c: Color3)
|
|
local r, g, b = c.R, c.G, c.B
|
|
if r > 1 or g > 1 or b > 1 then
|
|
ensureSpace(w, 7)
|
|
rawU8(w, T_COLOR3_F)
|
|
rawF16(w, r)
|
|
rawF16(w, g)
|
|
rawF16(w, b)
|
|
else
|
|
ensureSpace(w, 4)
|
|
rawU8(w, T_COLOR3)
|
|
rawU8(w, math.clamp(math.round(r * 255), 0, 255))
|
|
rawU8(w, math.clamp(math.round(g * 255), 0, 255))
|
|
rawU8(w, math.clamp(math.round(b * 255), 0, 255))
|
|
end
|
|
end
|
|
|
|
local PACK_DISPATCH: { [string]: (Writer, any) -> () } = {}
|
|
|
|
PACK_DISPATCH["nil"] = function(w: Writer, _: any)
|
|
wByte(w, T_NIL)
|
|
end
|
|
|
|
PACK_DISPATCH["boolean"] = function(w: Writer, v: any)
|
|
wByte(w, v and T_TRUE or T_FALSE)
|
|
end
|
|
|
|
PACK_DISPATCH["number"] = function(w: Writer, v: any)
|
|
packNumber(w, v)
|
|
end
|
|
|
|
PACK_DISPATCH["string"] = function(w: Writer, v: any)
|
|
packString(w, v)
|
|
end
|
|
|
|
PACK_DISPATCH["table"] = function(w: Writer, v: any)
|
|
packTable(w, v)
|
|
end
|
|
|
|
PACK_DISPATCH["Vector3"] = function(w: Writer, v: any)
|
|
packVector3(w, v)
|
|
end
|
|
|
|
PACK_DISPATCH["Vector2"] = function(w: Writer, v: any)
|
|
packVector2(w, v)
|
|
end
|
|
|
|
PACK_DISPATCH["CFrame"] = function(w: Writer, v: any)
|
|
packCFrame(w, v)
|
|
end
|
|
|
|
PACK_DISPATCH["Color3"] = function(w: Writer, v: any)
|
|
packColor3(w, v)
|
|
end
|
|
|
|
PACK_DISPATCH["Instance"] = function(w: Writer, v: any)
|
|
table.insert(w.refs, v)
|
|
local refIdx = #w.refs
|
|
ensureSpace(w, 6)
|
|
rawU8(w, T_INSTANCE)
|
|
rawVarUInt(w, refIdx)
|
|
end
|
|
|
|
PACK_DISPATCH["EnumItem"] = function(w: Writer, v: any)
|
|
local enumName = `{v.EnumType}`
|
|
local nameLen = #enumName
|
|
ensureSpace(w, 1 + 5 + nameLen + 2)
|
|
rawU8(w, T_ENUMITEM)
|
|
rawVarUInt(w, nameLen)
|
|
rawString(w, enumName, nameLen)
|
|
rawU16(w, v.Value)
|
|
end
|
|
|
|
PACK_DISPATCH["BrickColor"] = function(w: Writer, v: any)
|
|
ensureSpace(w, 3)
|
|
rawU8(w, T_BRICKCOLOR)
|
|
rawU16(w, v.Number)
|
|
end
|
|
|
|
PACK_DISPATCH["Enum"] = function(w: Writer, v: any)
|
|
local name = v.Name
|
|
local nameLen = #name
|
|
ensureSpace(w, 1 + 5 + nameLen)
|
|
rawU8(w, T_ENUM)
|
|
rawVarUInt(w, nameLen)
|
|
rawString(w, name, nameLen)
|
|
end
|
|
|
|
PACK_DISPATCH["UDim2"] = function(w: Writer, v: any)
|
|
local X, Y = v.X, v.Y
|
|
ensureSpace(w, 17)
|
|
rawU8(w, T_UDIM2)
|
|
rawF32(w, X.Scale)
|
|
rawI32(w, X.Offset)
|
|
rawF32(w, Y.Scale)
|
|
rawI32(w, Y.Offset)
|
|
end
|
|
|
|
PACK_DISPATCH["UDim"] = function(w: Writer, v: any)
|
|
ensureSpace(w, 9)
|
|
rawU8(w, T_UDIM)
|
|
rawF32(w, v.Scale)
|
|
rawI32(w, v.Offset)
|
|
end
|
|
|
|
PACK_DISPATCH["Rect"] = function(w: Writer, v: any)
|
|
local Min, Max = v.Min, v.Max
|
|
ensureSpace(w, 17)
|
|
rawU8(w, T_RECT)
|
|
rawF32(w, Min.X)
|
|
rawF32(w, Min.Y)
|
|
rawF32(w, Max.X)
|
|
rawF32(w, Max.Y)
|
|
end
|
|
|
|
PACK_DISPATCH["NumberRange"] = function(w: Writer, v: any)
|
|
ensureSpace(w, 9)
|
|
rawU8(w, T_NUMBERRANGE)
|
|
rawF32(w, v.Min)
|
|
rawF32(w, v.Max)
|
|
end
|
|
|
|
PACK_DISPATCH["Ray"] = function(w: Writer, v: any)
|
|
local Origin, Direction = v.Origin, v.Direction
|
|
ensureSpace(w, 25)
|
|
rawU8(w, T_RAY)
|
|
rawF32(w, Origin.X)
|
|
rawF32(w, Origin.Y)
|
|
rawF32(w, Origin.Z)
|
|
rawF32(w, Direction.X)
|
|
rawF32(w, Direction.Y)
|
|
rawF32(w, Direction.Z)
|
|
end
|
|
|
|
PACK_DISPATCH["ColorSequence"] = function(w: Writer, v: any)
|
|
local keypoints = v.Keypoints
|
|
local kpCount = #keypoints
|
|
ensureSpace(w, 2 + kpCount * 7)
|
|
rawU8(w, T_COLSEQ)
|
|
rawU8(w, kpCount)
|
|
for _, kp in keypoints do
|
|
rawF32(w, kp.Time)
|
|
local c = kp.Value
|
|
rawU8(w, math.clamp(math.round(c.R * 255), 0, 255))
|
|
rawU8(w, math.clamp(math.round(c.G * 255), 0, 255))
|
|
rawU8(w, math.clamp(math.round(c.B * 255), 0, 255))
|
|
end
|
|
end
|
|
|
|
PACK_DISPATCH["NumberSequence"] = function(w: Writer, v: any)
|
|
local keypoints = v.Keypoints
|
|
local kpCount = #keypoints
|
|
ensureSpace(w, 2 + kpCount * 12)
|
|
rawU8(w, T_NUMSEQ)
|
|
rawU8(w, kpCount)
|
|
for _, kp in keypoints do
|
|
rawF32(w, kp.Time)
|
|
rawF32(w, kp.Value)
|
|
rawF32(w, kp.Envelope)
|
|
end
|
|
end
|
|
|
|
PACK_DISPATCH["buffer"] = function(w: Writer, v: any)
|
|
local len = buffer.len(v)
|
|
ensureSpace(w, 1 + 5 + len)
|
|
rawU8(w, T_BUFFER)
|
|
rawVarUInt(w, len)
|
|
buffer.copy(w.buf, w.cursor, v, 0, len)
|
|
w.cursor += len
|
|
end
|
|
|
|
PACK_DISPATCH["Vector3int16"] = function(w: Writer, v: any)
|
|
ensureSpace(w, 7)
|
|
rawU8(w, T_VEC3I16)
|
|
rawI16(w, v.X)
|
|
rawI16(w, v.Y)
|
|
rawI16(w, v.Z)
|
|
end
|
|
|
|
PACK_DISPATCH["Vector2int16"] = function(w: Writer, v: any)
|
|
ensureSpace(w, 5)
|
|
rawU8(w, T_VEC2I16)
|
|
rawI16(w, v.X)
|
|
rawI16(w, v.Y)
|
|
end
|
|
|
|
PACK_DISPATCH["TweenInfo"] = function(w: Writer, v: any)
|
|
ensureSpace(w, 16) -- tag(1) + f32(4) + u8(1) + u8(1) + i32(4) + u8(1) + f32(4)
|
|
rawU8(w, T_TWEENINFO)
|
|
rawF32(w, v.Time)
|
|
rawU8(w, v.EasingStyle.Value)
|
|
rawU8(w, v.EasingDirection.Value)
|
|
rawI32(w, v.RepeatCount)
|
|
rawU8(w, v.Reverses and 1 or 0)
|
|
rawF32(w, v.DelayTime)
|
|
end
|
|
|
|
PACK_DISPATCH["PhysicalProperties"] = function(w: Writer, v: any)
|
|
ensureSpace(w, 21) -- tag(1) + 5xf32(20)
|
|
rawU8(w, T_PHYSPROP)
|
|
rawF32(w, v.Density)
|
|
rawF32(w, v.Friction)
|
|
rawF32(w, v.Elasticity)
|
|
rawF32(w, v.FrictionWeight)
|
|
rawF32(w, v.ElasticityWeight)
|
|
end
|
|
|
|
PACK_DISPATCH["Font"] = function(w: Writer, v: any)
|
|
local family = v.Family
|
|
local familyLen = #family
|
|
ensureSpace(w, 1 + 5 + familyLen + 3) -- tag + varuint + str + u16 + u8
|
|
rawU8(w, T_FONT)
|
|
rawVarUInt(w, familyLen)
|
|
rawString(w, family, familyLen)
|
|
rawU16(w, v.Weight.Value)
|
|
rawU8(w, v.Style.Value)
|
|
end
|
|
|
|
PACK_DISPATCH["DateTime"] = function(w: Writer, v: any)
|
|
ensureSpace(w, 9) -- tag(1) + f64(8)
|
|
rawU8(w, T_DATETIME)
|
|
rawF64(w, v.UnixTimestampMillis)
|
|
end
|
|
|
|
packValue = function(w: Writer, v: any)
|
|
local handler = PACK_DISPATCH[typeof(v)]
|
|
if handler then
|
|
handler(w, v)
|
|
else
|
|
error(`Unsupported type: {typeof(v)}`)
|
|
end
|
|
end
|
|
|
|
local UNPACK: { (buffer, number, { any }?, number) -> (any, number) } = table.create(256)
|
|
|
|
do
|
|
local function unpackError(_: buffer, _: number, _: { any }?, tag: number): (any, number)
|
|
error(`(serdes) unknown type: {tag}`)
|
|
return nil :: any, 0
|
|
end
|
|
for i = 1, 256 do
|
|
UNPACK[i] = unpackError
|
|
end
|
|
end
|
|
|
|
do
|
|
local function handler(_: buffer, pos: number, _: { any }?, tag: number): (any, number)
|
|
return tag, pos
|
|
end
|
|
for i = 0x00, 0x7F do
|
|
UNPACK[i + 1] = handler
|
|
end
|
|
end
|
|
|
|
do
|
|
local function handler(_: buffer, pos: number, _: { any }?, tag: number): (any, number)
|
|
return tag - 0x80 - 32, pos
|
|
end
|
|
for i = 0x80, 0x9F do
|
|
UNPACK[i + 1] = handler
|
|
end
|
|
end
|
|
|
|
-- scalar types
|
|
UNPACK[T_NIL + 1] = function(_: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
return nil, pos
|
|
end
|
|
|
|
UNPACK[T_FALSE + 1] = function(_: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
return false, pos
|
|
end
|
|
|
|
UNPACK[T_TRUE + 1] = function(_: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
return true, pos
|
|
end
|
|
|
|
-- numeric types
|
|
UNPACK[T_U8 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
return buffer.readu8(buf, pos), pos + 1
|
|
end
|
|
|
|
UNPACK[T_U16 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
return buffer.readu16(buf, pos), pos + 2
|
|
end
|
|
|
|
UNPACK[T_U32 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
return buffer.readu32(buf, pos), pos + 4
|
|
end
|
|
|
|
UNPACK[T_I8 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
return buffer.readi8(buf, pos), pos + 1
|
|
end
|
|
|
|
UNPACK[T_I16 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
return buffer.readi16(buf, pos), pos + 2
|
|
end
|
|
|
|
UNPACK[T_I32 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
return buffer.readi32(buf, pos), pos + 4
|
|
end
|
|
|
|
UNPACK[T_F16 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
return readF16(buf, pos), pos + 2
|
|
end
|
|
|
|
UNPACK[T_F32 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
return buffer.readf32(buf, pos), pos + 4
|
|
end
|
|
|
|
UNPACK[T_F64 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
return buffer.readf64(buf, pos), pos + 8
|
|
end
|
|
|
|
UNPACK[T_VEC3I16 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
return Vector3int16.new(buffer.readi16(buf, pos), buffer.readi16(buf, pos + 2), buffer.readi16(buf, pos + 4)),
|
|
pos + 6
|
|
end
|
|
|
|
UNPACK[T_VEC2I16 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
return Vector2int16.new(buffer.readi16(buf, pos), buffer.readi16(buf, pos + 2)), pos + 4
|
|
end
|
|
|
|
UNPACK[T_TWEENINFO + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
return TweenInfo.new(
|
|
buffer.readf32(buf, pos),
|
|
Enum.EasingStyle:FromValue(buffer.readu8(buf, pos + 4)),
|
|
Enum.EasingDirection:FromValue(buffer.readu8(buf, pos + 5)),
|
|
buffer.readi32(buf, pos + 6),
|
|
buffer.readu8(buf, pos + 10) ~= 0,
|
|
buffer.readf32(buf, pos + 11)
|
|
),
|
|
pos + 15
|
|
end
|
|
|
|
UNPACK[T_PHYSPROP + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
return PhysicalProperties.new(
|
|
buffer.readf32(buf, pos),
|
|
buffer.readf32(buf, pos + 4),
|
|
buffer.readf32(buf, pos + 8),
|
|
buffer.readf32(buf, pos + 12),
|
|
buffer.readf32(buf, pos + 16)
|
|
),
|
|
pos + 20
|
|
end
|
|
|
|
UNPACK[T_FONT + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
local familyLen
|
|
familyLen, pos = readVarUInt(buf, pos)
|
|
local family = buffer.readstring(buf, pos, familyLen)
|
|
pos += familyLen
|
|
local weight = buffer.readu16(buf, pos)
|
|
local style = buffer.readu8(buf, pos + 2)
|
|
return Font.new(family, Enum.FontWeight:FromValue(weight), Enum.FontStyle:FromValue(style)), pos + 3
|
|
end
|
|
|
|
UNPACK[T_DATETIME + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
return DateTime.fromUnixTimestampMillis(buffer.readf64(buf, pos)), pos + 8
|
|
end
|
|
|
|
do
|
|
local function handler(buf: buffer, pos: number, _: { any }?, tag: number): (any, number)
|
|
local n = tag - T_STR_BASE
|
|
return buffer.readstring(buf, pos, n), pos + n
|
|
end
|
|
for i = T_STR_BASE, T_STR_BASE + 15 do
|
|
UNPACK[i + 1] = handler
|
|
end
|
|
end
|
|
|
|
UNPACK[T_STR8 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
local n = buffer.readu8(buf, pos)
|
|
return buffer.readstring(buf, pos + 1, n), pos + 1 + n
|
|
end
|
|
|
|
UNPACK[T_STR16 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
local n = buffer.readu16(buf, pos)
|
|
return buffer.readstring(buf, pos + 2, n), pos + 2 + n
|
|
end
|
|
|
|
UNPACK[T_STRVAR + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
local n
|
|
n, pos = readVarUInt(buf, pos)
|
|
return buffer.readstring(buf, pos, n), pos + n
|
|
end
|
|
|
|
do
|
|
local function handler(buf: buffer, pos: number, refs: { any }?, tag: number): (any, number)
|
|
local count = tag - T_ARR_BASE
|
|
local arr = table.create(count)
|
|
for i = 1, count do
|
|
arr[i], pos = unpackValue(buf, pos, refs)
|
|
end
|
|
return arr, pos
|
|
end
|
|
for i = T_ARR_BASE, T_ARR_BASE + 7 do
|
|
UNPACK[i + 1] = handler
|
|
end
|
|
end
|
|
|
|
UNPACK[T_ARR8 + 1] = function(buf: buffer, pos: number, refs: { any }?, _: number): (any, number)
|
|
local count = buffer.readu8(buf, pos)
|
|
pos += 1
|
|
local arr = table.create(count)
|
|
for i = 1, count do
|
|
arr[i], pos = unpackValue(buf, pos, refs)
|
|
end
|
|
return arr, pos
|
|
end
|
|
|
|
UNPACK[T_ARR16 + 1] = function(buf: buffer, pos: number, refs: { any }?, _: number): (any, number)
|
|
local count = buffer.readu16(buf, pos)
|
|
pos += 2
|
|
local arr = table.create(count)
|
|
for i = 1, count do
|
|
arr[i], pos = unpackValue(buf, pos, refs)
|
|
end
|
|
return arr, pos
|
|
end
|
|
|
|
UNPACK[T_ARRVAR + 1] = function(buf: buffer, pos: number, refs: { any }?, _: number): (any, number)
|
|
local count
|
|
count, pos = readVarUInt(buf, pos)
|
|
local arr = table.create(count)
|
|
for i = 1, count do
|
|
arr[i], pos = unpackValue(buf, pos, refs)
|
|
end
|
|
return arr, pos
|
|
end
|
|
|
|
do
|
|
local function handler(buf: buffer, pos: number, refs: { any }?, tag: number): (any, number)
|
|
local count = tag - T_MAP_BASE
|
|
local map = {}
|
|
for _ = 1, count do
|
|
local k, v
|
|
k, pos = unpackValue(buf, pos, refs)
|
|
v, pos = unpackValue(buf, pos, refs)
|
|
map[k] = v
|
|
end
|
|
return map, pos
|
|
end
|
|
for i = T_MAP_BASE, T_MAP_BASE + 7 do
|
|
UNPACK[i + 1] = handler
|
|
end
|
|
end
|
|
|
|
UNPACK[T_MAP8 + 1] = function(buf: buffer, pos: number, refs: { any }?, _: number): (any, number)
|
|
local count = buffer.readu8(buf, pos)
|
|
pos += 1
|
|
local map = {}
|
|
for _ = 1, count do
|
|
local k, v
|
|
k, pos = unpackValue(buf, pos, refs)
|
|
v, pos = unpackValue(buf, pos, refs)
|
|
map[k] = v
|
|
end
|
|
return map, pos
|
|
end
|
|
|
|
UNPACK[T_MAP16 + 1] = function(buf: buffer, pos: number, refs: { any }?, _: number): (any, number)
|
|
local count = buffer.readu16(buf, pos)
|
|
pos += 2
|
|
local map = {}
|
|
for _ = 1, count do
|
|
local k, v
|
|
k, pos = unpackValue(buf, pos, refs)
|
|
v, pos = unpackValue(buf, pos, refs)
|
|
map[k] = v
|
|
end
|
|
return map, pos
|
|
end
|
|
|
|
UNPACK[T_MAPVAR + 1] = function(buf: buffer, pos: number, refs: { any }?, _: number): (any, number)
|
|
local count
|
|
count, pos = readVarUInt(buf, pos)
|
|
local map = {}
|
|
for _ = 1, count do
|
|
local k, v
|
|
k, pos = unpackValue(buf, pos, refs)
|
|
v, pos = unpackValue(buf, pos, refs)
|
|
map[k] = v
|
|
end
|
|
return map, pos
|
|
end
|
|
|
|
-- typed number array
|
|
UNPACK[T_TYPED_ARR + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
local subtype = buffer.readu8(buf, pos)
|
|
pos += 1
|
|
local count
|
|
count, pos = readVarUInt(buf, pos)
|
|
local reader = TYPED_READERS[subtype]
|
|
local arr = table.create(count)
|
|
for i = 1, count do
|
|
arr[i], pos = reader(buf, pos)
|
|
end
|
|
return arr, pos
|
|
end
|
|
|
|
-- boolean bitpacked array
|
|
UNPACK[T_BOOL_ARR + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
local count
|
|
count, pos = readVarUInt(buf, pos)
|
|
local arr = table.create(count)
|
|
local numBytes = math.ceil(count / 8)
|
|
for i = 0, numBytes - 1 do
|
|
local byte = buffer.readu8(buf, pos + i)
|
|
for bit = 0, 7 do
|
|
local idx = i * 8 + bit + 1
|
|
if idx > count then
|
|
break
|
|
end
|
|
arr[idx] = bit32.band(byte, bit32.lshift(1, bit)) ~= 0
|
|
end
|
|
end
|
|
return arr, pos + numBytes
|
|
end
|
|
|
|
-- buffer
|
|
UNPACK[T_BUFFER + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
local len
|
|
len, pos = readVarUInt(buf, pos)
|
|
local b = buffer.create(len)
|
|
buffer.copy(b, 0, buf, pos, len)
|
|
return b, pos + len
|
|
end
|
|
|
|
-- Vector3
|
|
UNPACK[T_VEC3 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
local x = readF16(buf, pos)
|
|
local y = readF16(buf, pos + 2)
|
|
local z = readF16(buf, pos + 4)
|
|
return Vector3.new(x, y, z), pos + 6
|
|
end
|
|
|
|
-- Vector2
|
|
UNPACK[T_VEC2 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
local x = readF16(buf, pos)
|
|
local y = readF16(buf, pos + 2)
|
|
return Vector2.new(x, y), pos + 4
|
|
end
|
|
|
|
-- CFrame
|
|
UNPACK[T_CFRAME + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
local px = buffer.readf32(buf, pos)
|
|
local py = buffer.readf32(buf, pos + 4)
|
|
local pz = buffer.readf32(buf, pos + 8)
|
|
local qi = buffer.readu8(buf, pos + 12)
|
|
local q0 = buffer.readi16(buf, pos + 13)
|
|
local q1 = buffer.readi16(buf, pos + 15)
|
|
local q2 = buffer.readi16(buf, pos + 17)
|
|
local qx, qy, qz, qw = decompressCFrameRotation(qi, q0, q1, q2)
|
|
return CFrame.new(px, py, pz, qx, qy, qz, qw), pos + 19
|
|
end
|
|
|
|
-- Color3 (RGB bytes)
|
|
UNPACK[T_COLOR3 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
local r = buffer.readu8(buf, pos)
|
|
local g = buffer.readu8(buf, pos + 1)
|
|
local b = buffer.readu8(buf, pos + 2)
|
|
return Color3.fromRGB(r, g, b), pos + 3
|
|
end
|
|
|
|
-- Color3 (f16 floats)
|
|
UNPACK[T_COLOR3_F + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
local r = readF16(buf, pos)
|
|
local g = readF16(buf, pos + 2)
|
|
local b = readF16(buf, pos + 4)
|
|
return Color3.new(r, g, b), pos + 6
|
|
end
|
|
|
|
-- BrickColor
|
|
UNPACK[T_BRICKCOLOR + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
return BrickColor.new(buffer.readu16(buf, pos)), pos + 2
|
|
end
|
|
|
|
-- Instance
|
|
UNPACK[T_INSTANCE + 1] = function(buf: buffer, pos: number, refs: { any }?, _: number): (any, number)
|
|
local idx
|
|
idx, pos = readVarUInt(buf, pos)
|
|
return refs and refs[idx] or nil, pos
|
|
end
|
|
|
|
-- EnumItem
|
|
UNPACK[T_ENUMITEM + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
local nameLen
|
|
nameLen, pos = readVarUInt(buf, pos)
|
|
local enumName = buffer.readstring(buf, pos, nameLen)
|
|
pos += nameLen
|
|
local val = buffer.readu16(buf, pos)
|
|
return (Enum :: any)[enumName]:FromValue(val), pos + 2
|
|
end
|
|
|
|
-- Enum
|
|
UNPACK[T_ENUM + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
local nameLen
|
|
nameLen, pos = readVarUInt(buf, pos)
|
|
local enumName = buffer.readstring(buf, pos, nameLen)
|
|
return (Enum :: any)[enumName], pos + nameLen
|
|
end
|
|
|
|
-- UDim2
|
|
UNPACK[T_UDIM2 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
local xs = buffer.readf32(buf, pos)
|
|
local xo = buffer.readi32(buf, pos + 4)
|
|
local ys = buffer.readf32(buf, pos + 8)
|
|
local yo = buffer.readi32(buf, pos + 12)
|
|
return UDim2.new(xs, xo, ys, yo), pos + 16
|
|
end
|
|
|
|
-- UDim
|
|
UNPACK[T_UDIM + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
local s = buffer.readf32(buf, pos)
|
|
local o = buffer.readi32(buf, pos + 4)
|
|
return UDim.new(s, o), pos + 8
|
|
end
|
|
|
|
-- Rect
|
|
UNPACK[T_RECT + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
return Rect.new(
|
|
buffer.readf32(buf, pos),
|
|
buffer.readf32(buf, pos + 4),
|
|
buffer.readf32(buf, pos + 8),
|
|
buffer.readf32(buf, pos + 12)
|
|
),
|
|
pos + 16
|
|
end
|
|
|
|
-- NumberRange
|
|
UNPACK[T_NUMBERRANGE + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
return NumberRange.new(buffer.readf32(buf, pos), buffer.readf32(buf, pos + 4)), pos + 8
|
|
end
|
|
|
|
-- Ray
|
|
UNPACK[T_RAY + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
return Ray.new(
|
|
Vector3.new(buffer.readf32(buf, pos), buffer.readf32(buf, pos + 4), buffer.readf32(buf, pos + 8)),
|
|
Vector3.new(buffer.readf32(buf, pos + 12), buffer.readf32(buf, pos + 16), buffer.readf32(buf, pos + 20))
|
|
),
|
|
pos + 24
|
|
end
|
|
|
|
-- ColorSequence
|
|
UNPACK[T_COLSEQ + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
local count = buffer.readu8(buf, pos)
|
|
pos += 1
|
|
local keypoints = table.create(count)
|
|
for i = 1, count do
|
|
local time = buffer.readf32(buf, pos)
|
|
local r = buffer.readu8(buf, pos + 4)
|
|
local g = buffer.readu8(buf, pos + 5)
|
|
local b = buffer.readu8(buf, pos + 6)
|
|
keypoints[i] = ColorSequenceKeypoint.new(time, Color3.fromRGB(r, g, b))
|
|
pos += 7
|
|
end
|
|
return ColorSequence.new(keypoints), pos
|
|
end
|
|
|
|
-- NumberSequence
|
|
UNPACK[T_NUMSEQ + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
|
|
local count = buffer.readu8(buf, pos)
|
|
pos += 1
|
|
local keypoints = table.create(count)
|
|
for i = 1, count do
|
|
local time = buffer.readf32(buf, pos)
|
|
local value = buffer.readf32(buf, pos + 4)
|
|
local envelope = buffer.readf32(buf, pos + 8)
|
|
keypoints[i] = NumberSequenceKeypoint.new(time, value, envelope)
|
|
pos += 12
|
|
end
|
|
return NumberSequence.new(keypoints), pos
|
|
end
|
|
|
|
unpackValue = function(buf: buffer, pos: number, refs: { any }?): (any, number)
|
|
local tag = buffer.readu8(buf, pos)
|
|
return UNPACK[tag + 1](buf, pos + 1, refs, tag)
|
|
end
|
|
|
|
local function build(w: Writer): buffer
|
|
local result = buffer.create(w.cursor)
|
|
buffer.copy(result, 0, w.buf, 0, w.cursor)
|
|
return result
|
|
end
|
|
|
|
local function buildWithRefs(w: Writer): (buffer, { any }?)
|
|
local result = buffer.create(w.cursor)
|
|
buffer.copy(result, 0, w.buf, 0, w.cursor)
|
|
local refs = w.refs
|
|
if #refs > 0 then
|
|
w.refs = {}
|
|
return result, refs
|
|
end
|
|
return result, nil
|
|
end
|
|
|
|
local function reset(w: Writer)
|
|
w.cursor = 0
|
|
table.clear(w.refs)
|
|
end
|
|
|
|
local Schema = {}
|
|
Schema.__custom_datatypes = {}
|
|
|
|
export type SchemaType = { type: string, [any]: any }
|
|
|
|
Schema.u8 = { type = "u8" }
|
|
Schema.i8 = { type = "i8" }
|
|
Schema.u16 = { type = "u16" }
|
|
Schema.i16 = { type = "i16" }
|
|
Schema.u32 = { type = "u32" }
|
|
Schema.i32 = { type = "i32" }
|
|
Schema.f16 = { type = "f16" }
|
|
Schema.f32 = { type = "f32" }
|
|
Schema.f64 = { type = "f64" }
|
|
Schema.boolean = { type = "boolean" }
|
|
Schema.vector3 = { type = "vector3" }
|
|
Schema.vector2 = { type = "vector2" }
|
|
Schema.cframe = { type = "cframe" }
|
|
Schema.color3 = { type = "color3" }
|
|
Schema.color3f16 = { type = "color3f16" }
|
|
Schema.instance = { type = "instance" }
|
|
Schema.string = { type = "string" }
|
|
Schema.udim2 = { type = "udim2" }
|
|
Schema.udim = { type = "udim" }
|
|
Schema.rect = { type = "rect" }
|
|
Schema.numberrange = { type = "numberrange" }
|
|
Schema.ray = { type = "ray" }
|
|
Schema.colorsequence = { type = "colorsequence" }
|
|
Schema.numbersequence = { type = "numbersequence" }
|
|
Schema.brickcolor = { type = "brickcolor" }
|
|
Schema.buffer = { type = "buffer" }
|
|
Schema.vector3int16 = { type = "vector3int16" }
|
|
Schema.vector2int16 = { type = "vector2int16" }
|
|
Schema.tweeninfo = { type = "tweeninfo" }
|
|
Schema.physicalproperties = { type = "physicalproperties" }
|
|
Schema.font = { type = "font" }
|
|
Schema.datetime = { type = "datetime" }
|
|
|
|
function Schema.optional(item: SchemaType): SchemaType
|
|
return { type = "optional", item = item }
|
|
end
|
|
|
|
function Schema.array(item: SchemaType): SchemaType
|
|
return { type = "array", item = item }
|
|
end
|
|
|
|
function Schema.map(key: SchemaType, value: SchemaType): SchemaType
|
|
return { type = "map", key = key, value = value }
|
|
end
|
|
|
|
function Schema.struct(fields: { [string]: SchemaType }): SchemaType
|
|
local orderedFields = {}
|
|
for k, v in fields do
|
|
table.insert(orderedFields, { key = k, schema = v })
|
|
end
|
|
table.sort(orderedFields, function(a, b)
|
|
return a.key < b.key
|
|
end)
|
|
return { type = "struct", fields = orderedFields }
|
|
end
|
|
|
|
function Schema.custom_datatype(
|
|
name: string,
|
|
object: { any },
|
|
writer: (w: Writer, v: any) -> (),
|
|
reader: (b: buffer, c: number, refs: { Instance }?) -> (any, number)
|
|
)
|
|
if Schema[name] then
|
|
warn(`[Warp.Schema]: this 'custom_datatype': "{name}" is already exists, try changing the name.`)
|
|
return
|
|
end
|
|
(object :: any).type = name
|
|
Schema[name] = object
|
|
Schema.__custom_datatypes[name] = {
|
|
writer = writer,
|
|
reader = reader,
|
|
}
|
|
end
|
|
|
|
local SCHEMA_FIXED_SIZES: { [string]: number } = {
|
|
u8 = 1,
|
|
i8 = 1,
|
|
u16 = 2,
|
|
i16 = 2,
|
|
u32 = 4,
|
|
i32 = 4,
|
|
f16 = 2,
|
|
f32 = 4,
|
|
f64 = 8,
|
|
boolean = 1,
|
|
vector3 = 6,
|
|
vector2 = 4,
|
|
cframe = 19,
|
|
color3 = 3,
|
|
color3f16 = 6,
|
|
udim2 = 16,
|
|
udim = 8,
|
|
rect = 16,
|
|
numberrange = 8,
|
|
ray = 24,
|
|
brickcolor = 2,
|
|
vector3int16 = 6,
|
|
vector2int16 = 4,
|
|
tweeninfo = 15,
|
|
physicalproperties = 20,
|
|
datetime = 8,
|
|
}
|
|
|
|
local function compilePacker(s: SchemaType): (Writer, any) -> ()
|
|
local schema_type = s.type
|
|
|
|
if Schema.__custom_datatypes[schema_type] then
|
|
return Schema.__custom_datatypes[schema_type].writer
|
|
end
|
|
|
|
if schema_type == "u8" then
|
|
return function(w: Writer, v: any)
|
|
ensureSpace(w, 1)
|
|
rawU8(w, v)
|
|
end
|
|
end
|
|
if schema_type == "i8" then
|
|
return function(w: Writer, v: any)
|
|
ensureSpace(w, 1)
|
|
buffer.writei8(w.buf, w.cursor, v)
|
|
w.cursor += 1
|
|
end
|
|
end
|
|
if schema_type == "u16" then
|
|
return function(w: Writer, v: any)
|
|
ensureSpace(w, 2)
|
|
rawU16(w, v)
|
|
end
|
|
end
|
|
if schema_type == "i16" then
|
|
return function(w: Writer, v: any)
|
|
ensureSpace(w, 2)
|
|
rawI16(w, v)
|
|
end
|
|
end
|
|
if schema_type == "u32" then
|
|
return function(w: Writer, v: any)
|
|
ensureSpace(w, 4)
|
|
rawU32(w, v)
|
|
end
|
|
end
|
|
if schema_type == "i32" then
|
|
return function(w: Writer, v: any)
|
|
ensureSpace(w, 4)
|
|
rawI32(w, v)
|
|
end
|
|
end
|
|
if schema_type == "f16" then
|
|
return function(w: Writer, v: any)
|
|
ensureSpace(w, 2)
|
|
rawF16(w, v)
|
|
end
|
|
end
|
|
if schema_type == "f32" then
|
|
return function(w: Writer, v: any)
|
|
ensureSpace(w, 4)
|
|
rawF32(w, v)
|
|
end
|
|
end
|
|
if schema_type == "f64" then
|
|
return function(w: Writer, v: any)
|
|
ensureSpace(w, 8)
|
|
rawF64(w, v)
|
|
end
|
|
end
|
|
if schema_type == "boolean" then
|
|
return function(w: Writer, v: any)
|
|
ensureSpace(w, 1)
|
|
rawU8(w, v and 1 or 0)
|
|
end
|
|
end
|
|
if schema_type == "string" then
|
|
return function(w: Writer, v: any)
|
|
local len = #v
|
|
ensureSpace(w, 5 + len)
|
|
rawVarUInt(w, len)
|
|
rawString(w, v, len)
|
|
end
|
|
end
|
|
|
|
if schema_type == "vector3" then
|
|
return function(w: Writer, v: any)
|
|
ensureSpace(w, 6)
|
|
rawF16(w, v.X)
|
|
rawF16(w, v.Y)
|
|
rawF16(w, v.Z)
|
|
end
|
|
end
|
|
if schema_type == "vector2" then
|
|
return function(w: Writer, v: any)
|
|
ensureSpace(w, 4)
|
|
rawF16(w, v.X)
|
|
rawF16(w, v.Y)
|
|
end
|
|
end
|
|
|
|
if schema_type == "cframe" then
|
|
return function(w: Writer, v: any)
|
|
ensureSpace(w, 19) -- 3xf32(12) + u8(1) + 3xi16(6)
|
|
local pos = v.Position
|
|
local qi, q0, q1, q2 = compressCFrameRotation(v)
|
|
rawF32(w, pos.X)
|
|
rawF32(w, pos.Y)
|
|
rawF32(w, pos.Z)
|
|
rawU8(w, qi)
|
|
rawI16(w, q0)
|
|
rawI16(w, q1)
|
|
rawI16(w, q2)
|
|
end
|
|
end
|
|
|
|
if schema_type == "color3" then
|
|
return function(w: Writer, v: any)
|
|
ensureSpace(w, 3)
|
|
rawU8(w, math.clamp(math.round(v.R * 255), 0, 255))
|
|
rawU8(w, math.clamp(math.round(v.G * 255), 0, 255))
|
|
rawU8(w, math.clamp(math.round(v.B * 255), 0, 255))
|
|
end
|
|
end
|
|
|
|
if schema_type == "color3f16" then
|
|
return function(w: Writer, v: any)
|
|
ensureSpace(w, 6)
|
|
rawF16(w, math.clamp(v.R * 255, 0, 255))
|
|
rawF16(w, math.clamp(v.G * 255, 0, 255))
|
|
rawF16(w, math.clamp(v.B * 255, 0, 255))
|
|
end
|
|
end
|
|
|
|
if schema_type == "udim2" then
|
|
return function(w: Writer, v: any)
|
|
ensureSpace(w, 16)
|
|
rawF32(w, v.X.Scale)
|
|
rawI32(w, v.X.Offset)
|
|
rawF32(w, v.Y.Scale)
|
|
rawI32(w, v.Y.Offset)
|
|
end
|
|
end
|
|
if schema_type == "udim" then
|
|
return function(w: Writer, v: any)
|
|
ensureSpace(w, 8)
|
|
rawF32(w, v.Scale)
|
|
rawI32(w, v.Offset)
|
|
end
|
|
end
|
|
if schema_type == "rect" then
|
|
return function(w: Writer, v: any)
|
|
ensureSpace(w, 16)
|
|
rawF32(w, v.Min.X)
|
|
rawF32(w, v.Min.Y)
|
|
rawF32(w, v.Max.X)
|
|
rawF32(w, v.Max.Y)
|
|
end
|
|
end
|
|
if schema_type == "numberrange" then
|
|
return function(w: Writer, v: any)
|
|
ensureSpace(w, 8)
|
|
rawF32(w, v.Min)
|
|
rawF32(w, v.Max)
|
|
end
|
|
end
|
|
if schema_type == "ray" then
|
|
return function(w: Writer, v: any)
|
|
ensureSpace(w, 24)
|
|
rawF32(w, v.Origin.X)
|
|
rawF32(w, v.Origin.Y)
|
|
rawF32(w, v.Origin.Z)
|
|
rawF32(w, v.Direction.X)
|
|
rawF32(w, v.Direction.Y)
|
|
rawF32(w, v.Direction.Z)
|
|
end
|
|
end
|
|
if schema_type == "colorsequence" then
|
|
return function(w: Writer, v: any)
|
|
local kps = v.Keypoints
|
|
local n = #kps
|
|
ensureSpace(w, 1 + n * 7)
|
|
rawU8(w, n)
|
|
for _, kp in kps do
|
|
rawF32(w, kp.Time)
|
|
local c = kp.Value
|
|
rawU8(w, math.clamp(math.round(c.R * 255), 0, 255))
|
|
rawU8(w, math.clamp(math.round(c.G * 255), 0, 255))
|
|
rawU8(w, math.clamp(math.round(c.B * 255), 0, 255))
|
|
end
|
|
end
|
|
end
|
|
if schema_type == "numbersequence" then
|
|
return function(w: Writer, v: any)
|
|
local kps = v.Keypoints
|
|
local n = #kps
|
|
ensureSpace(w, 1 + n * 12)
|
|
rawU8(w, n)
|
|
for _, kp in kps do
|
|
rawF32(w, kp.Time)
|
|
rawF32(w, kp.Value)
|
|
rawF32(w, kp.Envelope)
|
|
end
|
|
end
|
|
end
|
|
if schema_type == "brickcolor" then
|
|
return function(w: Writer, v: any)
|
|
ensureSpace(w, 2)
|
|
rawU16(w, v.Number)
|
|
end
|
|
end
|
|
if schema_type == "buffer" then
|
|
return function(w: Writer, v: any)
|
|
local len = buffer.len(v)
|
|
ensureSpace(w, 5 + len)
|
|
rawVarUInt(w, len)
|
|
buffer.copy(w.buf, w.cursor, v, 0, len)
|
|
w.cursor += len
|
|
end
|
|
end
|
|
if schema_type == "vector3int16" then
|
|
return function(w: Writer, v: any)
|
|
ensureSpace(w, 6)
|
|
rawI16(w, v.X)
|
|
rawI16(w, v.Y)
|
|
rawI16(w, v.Z)
|
|
end
|
|
end
|
|
if schema_type == "vector2int16" then
|
|
return function(w: Writer, v: any)
|
|
ensureSpace(w, 4)
|
|
rawI16(w, v.X)
|
|
rawI16(w, v.Y)
|
|
end
|
|
end
|
|
if schema_type == "tweeninfo" then
|
|
return function(w: Writer, v: any)
|
|
ensureSpace(w, 15)
|
|
rawF32(w, v.Time)
|
|
rawU8(w, v.EasingStyle.Value)
|
|
rawU8(w, v.EasingDirection.Value)
|
|
rawI32(w, v.RepeatCount)
|
|
rawU8(w, v.Reverses and 1 or 0)
|
|
rawF32(w, v.DelayTime)
|
|
end
|
|
end
|
|
if schema_type == "physicalproperties" then
|
|
return function(w: Writer, v: any)
|
|
ensureSpace(w, 20)
|
|
rawF32(w, v.Density)
|
|
rawF32(w, v.Friction)
|
|
rawF32(w, v.Elasticity)
|
|
rawF32(w, v.FrictionWeight)
|
|
rawF32(w, v.ElasticityWeight)
|
|
end
|
|
end
|
|
if schema_type == "font" then
|
|
return function(w: Writer, v: any)
|
|
local family = v.Family
|
|
local familyLen = #family
|
|
ensureSpace(w, 5 + familyLen + 3)
|
|
rawVarUInt(w, familyLen)
|
|
rawString(w, family, familyLen)
|
|
rawU16(w, v.Weight.Value)
|
|
rawU8(w, v.Style.Value)
|
|
end
|
|
end
|
|
if schema_type == "datetime" then
|
|
return function(w: Writer, v: any)
|
|
ensureSpace(w, 8)
|
|
rawF64(w, v.UnixTimestampMillis)
|
|
end
|
|
end
|
|
|
|
if schema_type == "instance" then
|
|
return function(w: Writer, v: any)
|
|
table.insert(w.refs, v)
|
|
ensureSpace(w, 5)
|
|
rawVarUInt(w, #w.refs)
|
|
end
|
|
end
|
|
|
|
if schema_type == "struct" then
|
|
local fields = {}
|
|
local optionalIndices = {}
|
|
|
|
for idx, field in ipairs(s.fields) do
|
|
local isOpt = field.schema.type == "optional"
|
|
table.insert(fields, {
|
|
key = field.key,
|
|
packer = compilePacker(isOpt and field.schema.item or field.schema),
|
|
optional = isOpt,
|
|
})
|
|
if isOpt then
|
|
table.insert(optionalIndices, idx)
|
|
end
|
|
end
|
|
|
|
local numOpt = #optionalIndices
|
|
|
|
if numOpt == 0 then
|
|
-- Pre-calculate total fixed size for all-fixed structs
|
|
local totalFixed = 0
|
|
local allFixed = true
|
|
for _, field in ipairs(s.fields) do
|
|
local size = SCHEMA_FIXED_SIZES[field.schema.type]
|
|
if size then
|
|
totalFixed += size
|
|
else
|
|
allFixed = false
|
|
break
|
|
end
|
|
end
|
|
|
|
if allFixed and totalFixed > 0 then
|
|
return function(w: Writer, v: any)
|
|
if type(v) ~= "table" then
|
|
error(`Expected table for struct, got {typeof(v)}`)
|
|
end
|
|
ensureSpace(w, totalFixed)
|
|
for _, f in fields do
|
|
local val = v[f.key]
|
|
if val == nil then
|
|
error(`Schema Error: Missing required field '{f.key}'`)
|
|
end
|
|
f.packer(w, val)
|
|
end
|
|
end
|
|
end
|
|
|
|
return function(w: Writer, v: any)
|
|
if type(v) ~= "table" then
|
|
error(`Expected table for struct, got {typeof(v)}`)
|
|
end
|
|
for _, f in fields do
|
|
local val = v[f.key]
|
|
if val == nil then
|
|
error(`Schema Error: Missing required field '{f.key}'`)
|
|
end
|
|
f.packer(w, val)
|
|
end
|
|
end
|
|
end
|
|
|
|
local maskBytes = math.ceil(numOpt / 8)
|
|
|
|
return function(w: Writer, v: any)
|
|
if type(v) ~= "table" then
|
|
error(`Expected table for struct, got {typeof(v)}`)
|
|
end
|
|
|
|
ensureSpace(w, maskBytes)
|
|
for i = 0, maskBytes - 1 do
|
|
local byte = 0
|
|
for b = 0, 7 do
|
|
local optIdx = i * 8 + b + 1
|
|
if optIdx > numOpt then
|
|
break
|
|
end
|
|
local fieldIdx = optionalIndices[optIdx]
|
|
if v[fields[fieldIdx].key] ~= nil then
|
|
byte = bit32.bor(byte, bit32.lshift(1, b))
|
|
end
|
|
end
|
|
buffer.writeu8(w.buf, w.cursor, byte)
|
|
w.cursor += 1
|
|
end
|
|
|
|
for _, f in fields do
|
|
local val = v[f.key]
|
|
if f.optional then
|
|
if val ~= nil then
|
|
f.packer(w, val)
|
|
end
|
|
else
|
|
if val == nil then
|
|
error(`Schema Error: Missing required field '{f.key}'`)
|
|
end
|
|
f.packer(w, val)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if schema_type == "array" then
|
|
local itemSchema = s.item
|
|
|
|
if itemSchema.type == "boolean" then
|
|
return function(w: Writer, v: any)
|
|
local len = #v
|
|
local numBytes = math.ceil(len / 8)
|
|
ensureSpace(w, 5 + numBytes)
|
|
rawVarUInt(w, len)
|
|
for i = 0, numBytes - 1 do
|
|
local byte = 0
|
|
for bit = 0, 7 do
|
|
local idx = i * 8 + bit + 1
|
|
if idx > len then
|
|
break
|
|
end
|
|
if v[idx] then
|
|
byte = bit32.bor(byte, bit32.lshift(1, bit))
|
|
end
|
|
end
|
|
buffer.writeu8(w.buf, w.cursor, byte)
|
|
w.cursor += 1
|
|
end
|
|
end
|
|
end
|
|
|
|
local itemPacker = compilePacker(itemSchema)
|
|
return function(w: Writer, v: any)
|
|
if type(v) ~= "table" then
|
|
error(`Expected table for array, got {typeof(v)}`)
|
|
end
|
|
local len = #v
|
|
wVarUInt(w, len)
|
|
for i = 1, len do
|
|
if v[i] == nil then
|
|
error(`Schema Error: Array item at index {i} is nil`)
|
|
end
|
|
itemPacker(w, v[i])
|
|
end
|
|
end
|
|
end
|
|
|
|
if schema_type == "map" then
|
|
local keyPacker = compilePacker(s.key)
|
|
local valPacker = compilePacker(s.value)
|
|
return function(w: Writer, v: any)
|
|
local count = 0
|
|
for _ in v do
|
|
count += 1
|
|
end
|
|
wVarUInt(w, count)
|
|
for k, val in v do
|
|
keyPacker(w, k)
|
|
valPacker(w, val)
|
|
end
|
|
end
|
|
end
|
|
|
|
if schema_type == "optional" then
|
|
local itemPacker = compilePacker(s.item)
|
|
return function(w: Writer, v: any)
|
|
if v == nil then
|
|
wByte(w, 0)
|
|
else
|
|
wByte(w, 1)
|
|
itemPacker(w, v)
|
|
end
|
|
end
|
|
end
|
|
|
|
return function()
|
|
return
|
|
end
|
|
end
|
|
|
|
local function compileReader(s: SchemaType): (buffer, number, { any }?) -> (any, number)
|
|
local schema_type = s.type
|
|
|
|
if Schema.__custom_datatypes[schema_type] then
|
|
return Schema.__custom_datatypes[schema_type].reader
|
|
end
|
|
|
|
if schema_type == "u8" then
|
|
return function(b: buffer, c: number)
|
|
return buffer.readu8(b, c), c + 1
|
|
end
|
|
end
|
|
if schema_type == "i8" then
|
|
return function(b: buffer, c: number)
|
|
return buffer.readi8(b, c), c + 1
|
|
end
|
|
end
|
|
if schema_type == "u16" then
|
|
return function(b: buffer, c: number)
|
|
return buffer.readu16(b, c), c + 2
|
|
end
|
|
end
|
|
if schema_type == "i16" then
|
|
return function(b: buffer, c: number)
|
|
return buffer.readi16(b, c), c + 2
|
|
end
|
|
end
|
|
if schema_type == "u32" then
|
|
return function(b: buffer, c: number)
|
|
return buffer.readu32(b, c), c + 4
|
|
end
|
|
end
|
|
if schema_type == "i32" then
|
|
return function(b: buffer, c: number)
|
|
return buffer.readi32(b, c), c + 4
|
|
end
|
|
end
|
|
if schema_type == "f16" then
|
|
return function(b: buffer, c: number)
|
|
return readF16(b, c), c + 2
|
|
end
|
|
end
|
|
if schema_type == "f32" then
|
|
return function(b: buffer, c: number)
|
|
return buffer.readf32(b, c), c + 4
|
|
end
|
|
end
|
|
if schema_type == "f64" then
|
|
return function(b: buffer, c: number)
|
|
return buffer.readf64(b, c), c + 8
|
|
end
|
|
end
|
|
if schema_type == "boolean" then
|
|
return function(b: buffer, c: number)
|
|
return buffer.readu8(b, c) ~= 0, c + 1
|
|
end
|
|
end
|
|
if schema_type == "string" then
|
|
return function(b: buffer, c: number)
|
|
local len
|
|
len, c = readVarUInt(b, c)
|
|
return buffer.readstring(b, c, len), c + len
|
|
end
|
|
end
|
|
if schema_type == "vector3" then
|
|
return function(b: buffer, c: number)
|
|
local x = readF16(b, c)
|
|
local y = readF16(b, c + 2)
|
|
local z = readF16(b, c + 4)
|
|
return Vector3.new(x, y, z), c + 6
|
|
end
|
|
end
|
|
if schema_type == "vector2" then
|
|
return function(b: buffer, c: number)
|
|
local x = readF16(b, c)
|
|
local y = readF16(b, c + 2)
|
|
return Vector2.new(x, y), c + 4
|
|
end
|
|
end
|
|
if schema_type == "color3" then
|
|
return function(b: buffer, c: number)
|
|
local r = buffer.readu8(b, c)
|
|
local g = buffer.readu8(b, c + 1)
|
|
local bVal = buffer.readu8(b, c + 2)
|
|
return Color3.fromRGB(r, g, bVal), c + 3
|
|
end
|
|
end
|
|
if schema_type == "color3f16" then
|
|
return function(b: buffer, c: number)
|
|
local r = readF16(b, c)
|
|
local g = readF16(b, c + 2)
|
|
local bVal = readF16(b, c + 4)
|
|
return Color3.fromRGB(r, g, bVal), c + 6
|
|
end
|
|
end
|
|
if schema_type == "cframe" then
|
|
return function(b: buffer, c: number)
|
|
local px = buffer.readf32(b, c)
|
|
local py = buffer.readf32(b, c + 4)
|
|
local pz = buffer.readf32(b, c + 8)
|
|
local qi = buffer.readu8(b, c + 12)
|
|
local q0 = buffer.readi16(b, c + 13)
|
|
local q1 = buffer.readi16(b, c + 15)
|
|
local q2 = buffer.readi16(b, c + 17)
|
|
local qx, qy, qz, qw = decompressCFrameRotation(qi, q0, q1, q2)
|
|
return CFrame.new(px, py, pz, qx, qy, qz, qw), c + 19
|
|
end
|
|
end
|
|
|
|
if schema_type == "udim2" then
|
|
return function(b: buffer, c: number)
|
|
return UDim2.new(
|
|
buffer.readf32(b, c),
|
|
buffer.readi32(b, c + 4),
|
|
buffer.readf32(b, c + 8),
|
|
buffer.readi32(b, c + 12)
|
|
),
|
|
c + 16
|
|
end
|
|
end
|
|
if schema_type == "udim" then
|
|
return function(b: buffer, c: number)
|
|
return UDim.new(buffer.readf32(b, c), buffer.readi32(b, c + 4)), c + 8
|
|
end
|
|
end
|
|
if schema_type == "rect" then
|
|
return function(b: buffer, c: number)
|
|
return Rect.new(
|
|
buffer.readf32(b, c),
|
|
buffer.readf32(b, c + 4),
|
|
buffer.readf32(b, c + 8),
|
|
buffer.readf32(b, c + 12)
|
|
),
|
|
c + 16
|
|
end
|
|
end
|
|
if schema_type == "numberrange" then
|
|
return function(b: buffer, c: number)
|
|
return NumberRange.new(buffer.readf32(b, c), buffer.readf32(b, c + 4)), c + 8
|
|
end
|
|
end
|
|
if schema_type == "ray" then
|
|
return function(b: buffer, c: number)
|
|
return Ray.new(
|
|
Vector3.new(buffer.readf32(b, c), buffer.readf32(b, c + 4), buffer.readf32(b, c + 8)),
|
|
Vector3.new(buffer.readf32(b, c + 12), buffer.readf32(b, c + 16), buffer.readf32(b, c + 20))
|
|
),
|
|
c + 24
|
|
end
|
|
end
|
|
if schema_type == "colorsequence" then
|
|
return function(b: buffer, c: number)
|
|
local n = buffer.readu8(b, c)
|
|
c += 1
|
|
local kps = table.create(n)
|
|
for i = 1, n do
|
|
local t = buffer.readf32(b, c)
|
|
kps[i] = ColorSequenceKeypoint.new(
|
|
t,
|
|
Color3.fromRGB(buffer.readu8(b, c + 4), buffer.readu8(b, c + 5), buffer.readu8(b, c + 6))
|
|
)
|
|
c += 7
|
|
end
|
|
return ColorSequence.new(kps), c
|
|
end
|
|
end
|
|
if schema_type == "numbersequence" then
|
|
return function(b: buffer, c: number)
|
|
local n = buffer.readu8(b, c)
|
|
c += 1
|
|
local kps = table.create(n)
|
|
for i = 1, n do
|
|
kps[i] =
|
|
NumberSequenceKeypoint.new(buffer.readf32(b, c), buffer.readf32(b, c + 4), buffer.readf32(b, c + 8))
|
|
c += 12
|
|
end
|
|
return NumberSequence.new(kps), c
|
|
end
|
|
end
|
|
if schema_type == "brickcolor" then
|
|
return function(b: buffer, c: number)
|
|
return BrickColor.new(buffer.readu16(b, c)), c + 2
|
|
end
|
|
end
|
|
if schema_type == "buffer" then
|
|
return function(b: buffer, c: number)
|
|
local len
|
|
len, c = readVarUInt(b, c)
|
|
local out = buffer.create(len)
|
|
buffer.copy(out, 0, b, c, len)
|
|
return out, c + len
|
|
end
|
|
end
|
|
if schema_type == "vector3int16" then
|
|
return function(b: buffer, c: number)
|
|
return Vector3int16.new(buffer.readi16(b, c), buffer.readi16(b, c + 2), buffer.readi16(b, c + 4)), c + 6
|
|
end
|
|
end
|
|
if schema_type == "vector2int16" then
|
|
return function(b: buffer, c: number)
|
|
return Vector2int16.new(buffer.readi16(b, c), buffer.readi16(b, c + 2)), c + 4
|
|
end
|
|
end
|
|
if schema_type == "tweeninfo" then
|
|
return function(b: buffer, c: number)
|
|
return TweenInfo.new(
|
|
buffer.readf32(b, c),
|
|
Enum.EasingStyle:FromValue(buffer.readu8(b, c + 4)),
|
|
Enum.EasingDirection:FromValue(buffer.readu8(b, c + 5)),
|
|
buffer.readi32(b, c + 6),
|
|
buffer.readu8(b, c + 10) ~= 0,
|
|
buffer.readf32(b, c + 11)
|
|
),
|
|
c + 15
|
|
end
|
|
end
|
|
if schema_type == "physicalproperties" then
|
|
return function(b: buffer, c: number)
|
|
return PhysicalProperties.new(
|
|
buffer.readf32(b, c),
|
|
buffer.readf32(b, c + 4),
|
|
buffer.readf32(b, c + 8),
|
|
buffer.readf32(b, c + 12),
|
|
buffer.readf32(b, c + 16)
|
|
),
|
|
c + 20
|
|
end
|
|
end
|
|
if schema_type == "font" then
|
|
return function(b: buffer, c: number)
|
|
local familyLen
|
|
familyLen, c = readVarUInt(b, c)
|
|
local family = buffer.readstring(b, c, familyLen)
|
|
c += familyLen
|
|
return Font.new(
|
|
family,
|
|
Enum.FontWeight:FromValue(buffer.readu16(b, c)),
|
|
Enum.FontStyle:FromValue(buffer.readu8(b, c + 2))
|
|
),
|
|
c + 3
|
|
end
|
|
end
|
|
if schema_type == "datetime" then
|
|
return function(b: buffer, c: number)
|
|
return DateTime.fromUnixTimestampMillis(buffer.readf64(b, c)), c + 8
|
|
end
|
|
end
|
|
if schema_type == "instance" then
|
|
return function(b: buffer, c: number, refs: { any }?)
|
|
local idx
|
|
idx, c = readVarUInt(b, c)
|
|
return refs and refs[idx] or nil, c
|
|
end
|
|
end
|
|
|
|
if schema_type == "struct" then
|
|
local fields = {}
|
|
local optionalIndices = {}
|
|
|
|
for idx, field in ipairs(s.fields) do
|
|
local isOpt = field.schema.type == "optional"
|
|
table.insert(fields, {
|
|
key = field.key,
|
|
reader = compileReader(isOpt and field.schema.item or field.schema),
|
|
optional = isOpt,
|
|
})
|
|
if isOpt then
|
|
table.insert(optionalIndices, idx)
|
|
end
|
|
end
|
|
|
|
local numOpt = #optionalIndices
|
|
|
|
if numOpt == 0 then
|
|
return function(b: buffer, c: number, refs: { any }?)
|
|
local obj = {}
|
|
for _, f in fields do
|
|
obj[f.key], c = f.reader(b, c, refs)
|
|
end
|
|
return obj, c
|
|
end
|
|
end
|
|
|
|
local maskBytes = math.ceil(numOpt / 8)
|
|
|
|
return function(b: buffer, c: number, refs: { any }?)
|
|
local present = {}
|
|
for i = 0, maskBytes - 1 do
|
|
local byte = buffer.readu8(b, c + i)
|
|
for bit = 0, 7 do
|
|
local optIdx = i * 8 + bit + 1
|
|
if optIdx > numOpt then
|
|
break
|
|
end
|
|
present[optIdx] = bit32.band(byte, bit32.lshift(1, bit)) ~= 0
|
|
end
|
|
end
|
|
c += maskBytes
|
|
|
|
local obj = {}
|
|
local optIdx = 0
|
|
for _, f in fields do
|
|
if f.optional then
|
|
optIdx += 1
|
|
if present[optIdx] then
|
|
obj[f.key], c = f.reader(b, c, refs)
|
|
end
|
|
else
|
|
obj[f.key], c = f.reader(b, c, refs)
|
|
end
|
|
end
|
|
|
|
return obj, c
|
|
end
|
|
end
|
|
|
|
if schema_type == "array" then
|
|
local itemSchema = s.item
|
|
|
|
if itemSchema.type == "boolean" then
|
|
return function(b: buffer, c: number, _: { any }?)
|
|
local len
|
|
len, c = readVarUInt(b, c)
|
|
local arr = table.create(len)
|
|
local numBytes = math.ceil(len / 8)
|
|
for i = 0, numBytes - 1 do
|
|
local byte = buffer.readu8(b, c + i)
|
|
for bit = 0, 7 do
|
|
local idx = i * 8 + bit + 1
|
|
if idx > len then
|
|
break
|
|
end
|
|
arr[idx] = bit32.band(byte, bit32.lshift(1, bit)) ~= 0
|
|
end
|
|
end
|
|
return arr, c + numBytes
|
|
end
|
|
end
|
|
|
|
local itemReader = compileReader(itemSchema)
|
|
return function(b: buffer, c: number, refs: { any }?)
|
|
local len
|
|
len, c = readVarUInt(b, c)
|
|
local arr = table.create(len)
|
|
for i = 1, len do
|
|
arr[i], c = itemReader(b, c, refs)
|
|
end
|
|
return arr, c
|
|
end
|
|
end
|
|
|
|
if schema_type == "map" then
|
|
local keyReader = compileReader(s.key)
|
|
local valReader = compileReader(s.value)
|
|
return function(b: buffer, c: number, refs: { any }?)
|
|
local count
|
|
count, c = readVarUInt(b, c)
|
|
local map = {}
|
|
for _ = 1, count do
|
|
local k, val
|
|
k, c = keyReader(b, c, refs)
|
|
val, c = valReader(b, c, refs)
|
|
map[k] = val
|
|
end
|
|
return map, c
|
|
end
|
|
end
|
|
|
|
if schema_type == "optional" then
|
|
local itemReader = compileReader(s.item)
|
|
return function(b: buffer, c: number, refs: { any }?)
|
|
local exists = buffer.readu8(b, c) ~= 0
|
|
c += 1
|
|
if exists then
|
|
return itemReader(b, c, refs)
|
|
else
|
|
return nil, c
|
|
end
|
|
end
|
|
end
|
|
|
|
return function(_: buffer, c: number)
|
|
return nil, c
|
|
end
|
|
end
|
|
|
|
local packerCache = setmetatable({}, { __mode = "k" })
|
|
local readerCache = setmetatable({}, { __mode = "k" })
|
|
|
|
local function packStrict(w: Writer, s: SchemaType, v: any)
|
|
local packer = packerCache[s]
|
|
if not packer then
|
|
packer = compilePacker(s)
|
|
packerCache[s] = packer
|
|
end
|
|
packer(w, v)
|
|
end
|
|
|
|
local function readStrict(buf: buffer, cursor: number, s: SchemaType, refs: { any }?): (any, number)
|
|
local reader = readerCache[s]
|
|
if not reader then
|
|
reader = compileReader(s)
|
|
readerCache[s] = reader
|
|
end
|
|
return reader(buf, cursor, refs)
|
|
end
|
|
|
|
local function writeEvents(w: Writer, events: { { any } }, schemas: { [number]: SchemaType })
|
|
local count = #events
|
|
wVarUInt(w, count)
|
|
for _, event in events do
|
|
local id = event[1]
|
|
local args = event[2]
|
|
wByte(w, id)
|
|
local schema = schemas[id]
|
|
if schema then
|
|
packStrict(w, schema, args[1])
|
|
else
|
|
packValue(w, args)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function readEvents(buf: buffer, refs: { any }?, schemas: { [number]: SchemaType }): { { any } }
|
|
local pos, count = 0, 0
|
|
count, pos = readVarUInt(buf, pos)
|
|
local events = table.create(count)
|
|
for i = 1, count do
|
|
local id: number = buffer.readu8(buf, pos)
|
|
pos += 1
|
|
local args: any
|
|
local schema = schemas[id]
|
|
if schema then
|
|
local val
|
|
val, pos = readStrict(buf, pos, schema, refs)
|
|
args = { val }
|
|
else
|
|
args, pos = unpackValue(buf, pos, refs)
|
|
end
|
|
events[i] = { id, args }
|
|
end
|
|
return events
|
|
end
|
|
|
|
local function writeRepl(w: Writer, content: { [string]: number }, count: number, schema: SchemaType)
|
|
wVarUInt(w, count)
|
|
for name, id in content do
|
|
wByte(w, id)
|
|
if schema then
|
|
packStrict(w, schema, name)
|
|
else
|
|
packValue(w, name)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function readRepl(buf: buffer, schema: SchemaType): { { any } }
|
|
local pos, count = 0, 0
|
|
count, pos = readVarUInt(buf, pos)
|
|
local events = table.create(count)
|
|
for i = 1, count do
|
|
local id: number = buffer.readu8(buf, pos)
|
|
pos += 1
|
|
local args: any
|
|
if schema then
|
|
local val
|
|
val, pos = readStrict(buf, pos, schema)
|
|
args = val
|
|
else
|
|
args, pos = unpackValue(buf, pos)
|
|
end
|
|
events[i] = { id, args }
|
|
end
|
|
return events
|
|
end
|
|
|
|
local BufferSerdes = {}
|
|
|
|
BufferSerdes.Schema = Schema
|
|
|
|
BufferSerdes.writeRepl = writeRepl
|
|
BufferSerdes.readRepl = readRepl
|
|
|
|
BufferSerdes.writeEvents = writeEvents
|
|
BufferSerdes.readEvents = readEvents
|
|
|
|
BufferSerdes.compilePacker = compilePacker
|
|
BufferSerdes.compileReader = compileReader
|
|
|
|
BufferSerdes.packStrict = packStrict
|
|
BufferSerdes.readStrict = readStrict
|
|
|
|
function BufferSerdes.write(data: any): (buffer, { any }?)
|
|
local w = createWriter()
|
|
packValue(w, data)
|
|
return buildWithRefs(w)
|
|
end
|
|
|
|
function BufferSerdes.read(buf: buffer, refs: { any }?): any
|
|
return (unpackValue(buf, 0, refs))
|
|
end
|
|
|
|
function BufferSerdes.readAll(buf: buffer, refs: { any }?): { any }
|
|
local bufLen = buffer.len(buf)
|
|
local pos = 0
|
|
local results = {}
|
|
while pos < bufLen do
|
|
local value
|
|
value, pos = unpackValue(buf, pos, refs)
|
|
table.insert(results, value)
|
|
end
|
|
return results
|
|
end
|
|
|
|
BufferSerdes.createWriter = createWriter
|
|
BufferSerdes.pack = packValue
|
|
BufferSerdes.build = build
|
|
BufferSerdes.buildWithRefs = buildWithRefs
|
|
BufferSerdes.reset = reset
|
|
|
|
BufferSerdes.readTagged = unpackValue
|
|
BufferSerdes.packTagged = packValue
|
|
BufferSerdes.unpack = unpackValue
|
|
|
|
-- for internal test
|
|
BufferSerdes.writeVarUInt = writeVarUInt
|
|
BufferSerdes.readVarUInt = readVarUInt
|
|
BufferSerdes.varUIntSize = varUIntSize
|
|
|
|
-- for custom datatype usage
|
|
BufferSerdes.ensureSpace = ensureSpace
|
|
BufferSerdes.writeu8 = wByte
|
|
BufferSerdes.writeu16 = wU16
|
|
BufferSerdes.writeu32 = wU32
|
|
BufferSerdes.writei16 = wI16
|
|
BufferSerdes.writei32 = wI32
|
|
BufferSerdes.writef16 = wF16
|
|
BufferSerdes.writef32 = wF32
|
|
BufferSerdes.writef64 = wF64
|
|
BufferSerdes.writestring = wString
|
|
|
|
return BufferSerdes :: typeof(BufferSerdes)
|