mirror of
https://github.com/imezx/Warp.git
synced 2026-03-18 00:44:16 +00:00
964 lines
23 KiB
Lua
964 lines
23 KiB
Lua
--!native
|
|
--!optimize 2
|
|
--@EternityDev
|
|
|
|
-- 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_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_VEC3 = 0xE0 -- f32 precision (12 bytes)
|
|
local T_VEC3_F16 = 0xE1 -- f16 precision (6 bytes)
|
|
local T_VEC2 = 0xE2 -- f32 precision (8 bytes)
|
|
local T_VEC2_F16 = 0xE3 -- f16 precision (4 bytes)
|
|
local T_CFRAME = 0xE4 -- f32 pos + f32 orient (24 bytes)
|
|
local T_CFRAME_F16 = 0xE5 -- f16 pos + f16 orient (12 bytes)
|
|
local T_COLOR3 = 0xE6 -- RGB bytes (3 bytes)
|
|
local T_COLOR3_F = 0xE7 -- RGB floats (12 bytes)
|
|
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 -- ColorSequence
|
|
local T_NUMSEQ = 0xF3 -- NumberSequence
|
|
|
|
local function f32ToF16(value: number): number
|
|
if value ~= value then return 0x7E00 end
|
|
if value == math.huge then return 0x7C00 end
|
|
if value == -math.huge then return 0xFC00 end
|
|
if value == 0 then return 0 end
|
|
|
|
local sign = 0
|
|
if value < 0 then
|
|
sign = 0x8000
|
|
value = -value
|
|
end
|
|
|
|
local mantissa, exponent = math.frexp(value)
|
|
exponent += 14
|
|
|
|
if exponent <= 0 then
|
|
if exponent < -10 then return sign end
|
|
mantissa = math.floor(mantissa * bit32.lshift(1, 10 + exponent) + 0.5)
|
|
return bit32.bor(sign, mantissa)
|
|
elseif exponent >= 31 then
|
|
return bit32.bor(sign, 0x7C00)
|
|
end
|
|
|
|
mantissa = math.floor((mantissa * 2 - 1) * 1024 + 0.5)
|
|
if mantissa == 1024 then
|
|
mantissa = 0
|
|
exponent += 1
|
|
if exponent >= 31 then return bit32.bor(sign, 0x7C00) end
|
|
end
|
|
return bit32.bor(sign, bit32.lshift(exponent, 10), mantissa)
|
|
end
|
|
|
|
local function f16ToF32(bits: number): number
|
|
local sign = bit32.band(bits, 0x8000)
|
|
local exponent = bit32.band(bit32.rshift(bits, 10), 0x1F)
|
|
local mantissa = bit32.band(bits, 0x03FF)
|
|
|
|
local value: number
|
|
if exponent == 0 then
|
|
value = mantissa == 0 and 0 or math.ldexp(mantissa / 1024, -14)
|
|
elseif exponent == 31 then
|
|
value = mantissa == 0 and math.huge or 0/0
|
|
else
|
|
value = math.ldexp(1 + mantissa / 1024, exponent - 15)
|
|
end
|
|
|
|
return sign ~= 0 and -value or value
|
|
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
|
|
|
|
export type Writer = {
|
|
buf: buffer,
|
|
cursor: number,
|
|
capacity: number,
|
|
refs: {any},
|
|
}
|
|
|
|
local DEFAULT_CAPACITY = 128
|
|
|
|
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 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.writei16(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.writei32(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 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 wF16(w: Writer, v: number)
|
|
ensureSpace(w, 2)
|
|
buffer.writei16(w.buf, w.cursor, f32ToF16(v))
|
|
w.cursor += 2
|
|
end
|
|
|
|
local function wVarUInt(w: Writer, v: number)
|
|
ensureSpace(w, varUIntSize(v))
|
|
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 function packNumber(w: Writer, n: number)
|
|
if n ~= n then
|
|
wByte(w, T_F64)
|
|
wF64(w, n)
|
|
return
|
|
end
|
|
|
|
local isInt = n == math.floor(n)
|
|
|
|
if isInt then
|
|
-- + fixint: 0-127 in single byte
|
|
if n >= 0 and n <= 127 then
|
|
wByte(w, n)
|
|
return
|
|
end
|
|
|
|
-- - fixint: -32 to -1 in single byte
|
|
if n >= -32 and n < 0 then
|
|
wByte(w, bit32.bor(0x80, n + 32))
|
|
return
|
|
end
|
|
|
|
-- larger ints
|
|
if n >= 0 then
|
|
if n <= 255 then
|
|
wByte(w, T_U8)
|
|
wByte(w, n)
|
|
elseif n <= 65535 then
|
|
wByte(w, T_U16)
|
|
wU16(w, n)
|
|
elseif n <= 4294967295 then
|
|
wByte(w, T_U32)
|
|
wU32(w, n)
|
|
else
|
|
wByte(w, T_F64)
|
|
wF64(w, n)
|
|
end
|
|
else
|
|
if n >= -128 then
|
|
wByte(w, T_I8)
|
|
ensureSpace(w, 1)
|
|
buffer.writei8(w.buf, w.cursor, n)
|
|
w.cursor += 1
|
|
elseif n >= -32768 then
|
|
wByte(w, T_I16)
|
|
wI16(w, n)
|
|
elseif n >= -2147483648 then
|
|
wByte(w, T_I32)
|
|
wI32(w, n)
|
|
else
|
|
wByte(w, T_F64)
|
|
wF64(w, n)
|
|
end
|
|
end
|
|
else
|
|
-- float: try f32 first
|
|
local testBuf = buffer.create(4)
|
|
buffer.writef32(testBuf, 0, n)
|
|
local f32Val = buffer.readf32(testBuf, 0)
|
|
|
|
if f32Val == n or math.abs(n - f32Val) < math.abs(n) * 1e-6 then
|
|
wByte(w, T_F32)
|
|
wF32(w, n)
|
|
else
|
|
wByte(w, T_F64)
|
|
wF64(w, n)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function packString(w: Writer, s: string)
|
|
local n = #s
|
|
|
|
if n <= 15 then
|
|
wByte(w, T_STR_BASE + n)
|
|
elseif n <= 255 then
|
|
wByte(w, T_STR8)
|
|
wByte(w, n)
|
|
elseif n <= 65535 then
|
|
wByte(w, T_STR16)
|
|
wU16(w, n)
|
|
else
|
|
wByte(w, T_STRVAR)
|
|
wVarUInt(w, n)
|
|
end
|
|
wString(w, s)
|
|
end
|
|
|
|
-- is the table a homogeneous number array
|
|
local function analyzeNumberArray(t: {any}): (boolean, string?, number)
|
|
local count = #t
|
|
if count == 0 then return false, nil, 0 end
|
|
|
|
local minVal, maxVal = math.huge, -math.huge
|
|
local allInt = true
|
|
|
|
for i = 1, count do
|
|
local v = t[i]
|
|
if type(v) ~= "number" then return false, 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 true, "f32", count
|
|
elseif minVal >= 0 and maxVal <= 255 then
|
|
return true, "u8", count
|
|
elseif minVal >= -128 and maxVal <= 127 then
|
|
return true, "i8", count
|
|
elseif minVal >= 0 and maxVal <= 65535 then
|
|
return true, "u16", count
|
|
elseif minVal >= -32768 and maxVal <= 32767 then
|
|
return true, "i16", count
|
|
elseif minVal >= 0 and maxVal <= 4294967295 then
|
|
return true, "u32", count
|
|
elseif minVal >= -2147483648 and maxVal <= 2147483647 then
|
|
return true, "i32", count
|
|
end
|
|
return true, "f64", count
|
|
end
|
|
|
|
local TYPED_CODES = { u8 = 1, i8 = 2, u16 = 3, i16 = 4, u32 = 5, i32 = 6, f32 = 7, f64 = 8 }
|
|
local TYPED_SIZES = { u8 = 1, i8 = 1, u16 = 2, i16 = 2, u32 = 4, i32 = 4, f32 = 4, f64 = 8 }
|
|
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,
|
|
}
|
|
|
|
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
|
|
-- typed array optimization (worth it for 4+ elements)
|
|
local isHomogeneous, dtype, arrCount = analyzeNumberArray(t)
|
|
if isHomogeneous and dtype and arrCount >= 4 then
|
|
local code = TYPED_CODES[dtype]
|
|
local size = TYPED_SIZES[dtype]
|
|
local writer = TYPED_WRITERS[dtype]
|
|
|
|
wByte(w, T_TYPED_ARR)
|
|
wByte(w, code)
|
|
wVarUInt(w, arrCount)
|
|
ensureSpace(w, arrCount * size)
|
|
|
|
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
|
|
wByte(w, T_ARR8)
|
|
wByte(w, count)
|
|
elseif count <= 65535 then
|
|
wByte(w, T_ARR16)
|
|
wU16(w, count)
|
|
else
|
|
wByte(w, T_ARRVAR)
|
|
wVarUInt(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
|
|
wByte(w, T_MAP8)
|
|
wByte(w, count)
|
|
elseif count <= 65535 then
|
|
wByte(w, T_MAP16)
|
|
wU16(w, count)
|
|
else
|
|
wByte(w, T_MAPVAR)
|
|
wVarUInt(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)
|
|
local x, y, z = v.X, v.Y, v.Z
|
|
local maxComp = math.max(math.abs(x), math.abs(y), math.abs(z))
|
|
|
|
if maxComp < 65000 and maxComp > 0.00006 then
|
|
wByte(w, T_VEC3_F16)
|
|
wF16(w, x)
|
|
wF16(w, y)
|
|
wF16(w, z)
|
|
else
|
|
wByte(w, T_VEC3)
|
|
wF32(w, x)
|
|
wF32(w, y)
|
|
wF32(w, z)
|
|
end
|
|
end
|
|
|
|
local function packVector2(w: Writer, v: Vector2)
|
|
local x, y = v.X, v.Y
|
|
local maxComp = math.max(math.abs(x), math.abs(y))
|
|
|
|
if maxComp < 65000 and maxComp > 0.00006 then
|
|
wByte(w, T_VEC2_F16)
|
|
wF16(w, x)
|
|
wF16(w, y)
|
|
else
|
|
wByte(w, T_VEC2)
|
|
wF32(w, x)
|
|
wF32(w, y)
|
|
end
|
|
end
|
|
|
|
local function packCFrame(w: Writer, cf: CFrame)
|
|
local pos = cf.Position
|
|
local rx, ry, rz = cf:ToOrientation()
|
|
|
|
local px, py, pz = pos.X, pos.Y, pos.Z
|
|
local maxPos = math.max(math.abs(px), math.abs(py), math.abs(pz))
|
|
|
|
-- f16
|
|
if maxPos < 65000 and (maxPos > 0.00006 or maxPos == 0) then
|
|
wByte(w, T_CFRAME_F16)
|
|
wF16(w, px)
|
|
wF16(w, py)
|
|
wF16(w, pz)
|
|
wF16(w, rx)
|
|
wF16(w, ry)
|
|
wF16(w, rz)
|
|
else
|
|
wByte(w, T_CFRAME)
|
|
wF32(w, px)
|
|
wF32(w, py)
|
|
wF32(w, pz)
|
|
wF32(w, rx)
|
|
wF32(w, ry)
|
|
wF32(w, rz)
|
|
end
|
|
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
|
|
wByte(w, T_COLOR3_F)
|
|
wF32(w, r)
|
|
wF32(w, g)
|
|
wF32(w, b)
|
|
else
|
|
wByte(w, T_COLOR3)
|
|
wByte(w, math.clamp(math.round(r * 255), 0, 255))
|
|
wByte(w, math.clamp(math.round(g * 255), 0, 255))
|
|
wByte(w, math.clamp(math.round(b * 255), 0, 255))
|
|
end
|
|
end
|
|
|
|
packValue = function(w: Writer, v: any): ()
|
|
local t: string = typeof(v)
|
|
|
|
if t == "nil" then
|
|
wByte(w, T_NIL)
|
|
elseif t == "boolean" then
|
|
wByte(w, v and T_TRUE or T_FALSE)
|
|
elseif t == "number" then
|
|
packNumber(w, v)
|
|
elseif t == "string" then
|
|
packString(w, v)
|
|
elseif t == "table" then
|
|
packTable(w, v)
|
|
elseif t == "Vector3" then
|
|
packVector3(w, v)
|
|
elseif t == "Vector2" then
|
|
packVector2(w, v)
|
|
elseif t == "CFrame" then
|
|
packCFrame(w, v)
|
|
elseif t == "Color3" then
|
|
packColor3(w, v)
|
|
elseif t == "Instance" then
|
|
wByte(w, T_INSTANCE)
|
|
table.insert(w.refs, v)
|
|
wVarUInt(w, #w.refs)
|
|
elseif t == "EnumItem" then
|
|
wByte(w, T_ENUMITEM)
|
|
local enumName = `{v.EnumType}`
|
|
wVarUInt(w, #enumName)
|
|
wString(w, enumName)
|
|
wU16(w, v.Value)
|
|
elseif t == "BrickColor" then
|
|
wByte(w, T_BRICKCOLOR)
|
|
wU16(w, v.Number)
|
|
elseif t == "Enum" then
|
|
wByte(w, T_ENUM)
|
|
local name = v.Name
|
|
wVarUInt(w, #name)
|
|
wString(w, name)
|
|
elseif t == "UDim2" then
|
|
local X, Y = v.X, v.Y
|
|
wByte(w, T_UDIM2)
|
|
wF32(w, X.Scale)
|
|
wI32(w, X.Offset)
|
|
wF32(w, Y.Scale)
|
|
wI32(w, Y.Offset)
|
|
elseif t == "UDim" then
|
|
wByte(w, T_UDIM)
|
|
wF32(w, v.Scale)
|
|
wI32(w, v.Offset)
|
|
elseif t == "Rect" then
|
|
local Min, Max = v.Min, v.Max
|
|
wByte(w, T_RECT)
|
|
wF32(w, Min.X)
|
|
wF32(w, Min.Y)
|
|
wF32(w, Max.X)
|
|
wF32(w, Max.Y)
|
|
elseif t == "NumberRange" then
|
|
wByte(w, T_NUMBERRANGE)
|
|
wF32(w, v.Min)
|
|
wF32(w, v.Max)
|
|
elseif t == "Ray" then
|
|
local Origin, Direction = v.Origin, v.Direction
|
|
wByte(w, T_RAY)
|
|
wF32(w, Origin.X)
|
|
wF32(w, Origin.Y)
|
|
wF32(w, Origin.Z)
|
|
wF32(w, Direction.X)
|
|
wF32(w, Direction.Y)
|
|
wF32(w, Direction.Z)
|
|
elseif t == "ColorSequence" then
|
|
local keypoints = v.Keypoints
|
|
wByte(w, T_COLSEQ)
|
|
wByte(w, #keypoints)
|
|
for _, kp in keypoints do
|
|
wF32(w, kp.Time)
|
|
local c = kp.Value
|
|
wByte(w, math.clamp(math.round(c.R * 255), 0, 255))
|
|
wByte(w, math.clamp(math.round(c.G * 255), 0, 255))
|
|
wByte(w, math.clamp(math.round(c.B * 255), 0, 255))
|
|
end
|
|
elseif t == "NumberSequence" then
|
|
local keypoints = v.Keypoints
|
|
wByte(w, T_NUMSEQ)
|
|
wByte(w, #keypoints)
|
|
for _, kp in keypoints do
|
|
wF32(w, kp.Time)
|
|
wF32(w, kp.Value)
|
|
wF32(w, kp.Envelope)
|
|
end
|
|
else
|
|
error(`Unsupported type: {t}`)
|
|
end
|
|
end
|
|
|
|
local TYPED_READERS = {
|
|
[1] = function(b, o) return buffer.readu8(b, o), o + 1 end,
|
|
[2] = function(b, o) return buffer.readi8(b, o), o + 1 end,
|
|
[3] = function(b, o) return buffer.readu16(b, o), o + 2 end,
|
|
[4] = function(b, o) return buffer.readi16(b, o), o + 2 end,
|
|
[5] = function(b, o) return buffer.readu32(b, o), o + 4 end,
|
|
[6] = function(b, o) return buffer.readi32(b, o), o + 4 end,
|
|
[7] = function(b, o) return buffer.readf32(b, o), o + 4 end,
|
|
[8] = function(b, o) return buffer.readf64(b, o), o + 8 end,
|
|
}
|
|
|
|
local function readF16(b: buffer, o: number): number
|
|
return f16ToF32(buffer.readu16(b, o))
|
|
end
|
|
|
|
local unpackValue: (buf: buffer, pos: number, refs: {any}?) -> (any, number)
|
|
|
|
unpackValue = function(buf: buffer, pos: number, refs: {any}?): (any, number)
|
|
local t = buffer.readu8(buf, pos)
|
|
pos += 1
|
|
|
|
-- + fixint (0-127)
|
|
if t <= 0x7F then
|
|
return t, pos
|
|
end
|
|
|
|
-- - fixint (-32 to -1)
|
|
if t >= 0x80 and t <= 0x9F then
|
|
return t - 0x80 - 32, pos
|
|
end
|
|
|
|
if t == T_NIL then return nil, pos end
|
|
if t == T_FALSE then return false, pos end
|
|
if t == T_TRUE then return true, pos end
|
|
|
|
if t == T_U8 then return buffer.readu8(buf, pos), pos + 1 end
|
|
if t == T_U16 then return buffer.readu16(buf, pos), pos + 2 end
|
|
if t == T_U32 then return buffer.readu32(buf, pos), pos + 4 end
|
|
if t == T_I8 then return buffer.readi8(buf, pos), pos + 1 end
|
|
if t == T_I16 then return buffer.readi16(buf, pos), pos + 2 end
|
|
if t == T_I32 then return buffer.readi32(buf, pos), pos + 4 end
|
|
if t == T_F32 then return buffer.readf32(buf, pos), pos + 4 end
|
|
if t == T_F64 then return buffer.readf64(buf, pos), pos + 8 end
|
|
|
|
-- inline str len (0-15)
|
|
if t >= T_STR_BASE and t <= T_STR_BASE + 15 then
|
|
local n = t - T_STR_BASE
|
|
return buffer.readstring(buf, pos, n), pos + n
|
|
end
|
|
|
|
if t == T_STR8 then
|
|
local n = buffer.readu8(buf, pos)
|
|
return buffer.readstring(buf, pos + 1, n), pos + 1 + n
|
|
end
|
|
if t == T_STR16 then
|
|
local n = buffer.readu16(buf, pos)
|
|
return buffer.readstring(buf, pos + 2, n), pos + 2 + n
|
|
end
|
|
if t == T_STRVAR then
|
|
local n; n, pos = readVarUInt(buf, pos)
|
|
return buffer.readstring(buf, pos, n), pos + n
|
|
end
|
|
|
|
-- inline array len (0-7)
|
|
if t >= T_ARR_BASE and t <= T_ARR_BASE + 7 then
|
|
local count = t - 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
|
|
|
|
if t == T_ARR8 then
|
|
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
|
|
if t == T_ARR16 then
|
|
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
|
|
if t == T_ARRVAR then
|
|
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
|
|
|
|
-- inline map len (0-7)
|
|
if t >= T_MAP_BASE and t <= T_MAP_BASE + 7 then
|
|
local count = t - 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
|
|
|
|
if t == T_MAP8 then
|
|
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
|
|
if t == T_MAP16 then
|
|
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
|
|
if t == T_MAPVAR then
|
|
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 array
|
|
if t == T_TYPED_ARR then
|
|
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
|
|
|
|
-- Vector3
|
|
if t == T_VEC3 then
|
|
local x = buffer.readf32(buf, pos)
|
|
local y = buffer.readf32(buf, pos + 4)
|
|
local z = buffer.readf32(buf, pos + 8)
|
|
return Vector3.new(x, y, z), pos + 12
|
|
end
|
|
if t == T_VEC3_F16 then
|
|
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
|
|
if t == T_VEC2 then
|
|
local x = buffer.readf32(buf, pos)
|
|
local y = buffer.readf32(buf, pos + 4)
|
|
return Vector2.new(x, y), pos + 8
|
|
end
|
|
if t == T_VEC2_F16 then
|
|
local x = readF16(buf, pos)
|
|
local y = readF16(buf, pos + 2)
|
|
return Vector2.new(x, y), pos + 4
|
|
end
|
|
|
|
-- CFrame
|
|
if t == T_CFRAME then
|
|
local px = buffer.readf32(buf, pos)
|
|
local py = buffer.readf32(buf, pos + 4)
|
|
local pz = buffer.readf32(buf, pos + 8)
|
|
local rx = buffer.readf32(buf, pos + 12)
|
|
local ry = buffer.readf32(buf, pos + 16)
|
|
local rz = buffer.readf32(buf, pos + 20)
|
|
return CFrame.new(px, py, pz) * CFrame.fromOrientation(rx, ry, rz), pos + 24
|
|
end
|
|
if t == T_CFRAME_F16 then
|
|
local px = readF16(buf, pos)
|
|
local py = readF16(buf, pos + 2)
|
|
local pz = readF16(buf, pos + 4)
|
|
local rx = readF16(buf, pos + 6)
|
|
local ry = readF16(buf, pos + 8)
|
|
local rz = readF16(buf, pos + 10)
|
|
return CFrame.new(px, py, pz) * CFrame.fromOrientation(rx, ry, rz), pos + 12
|
|
end
|
|
|
|
-- Color3
|
|
if t == T_COLOR3 then
|
|
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
|
|
if t == T_COLOR3_F then
|
|
local r = buffer.readf32(buf, pos)
|
|
local g = buffer.readf32(buf, pos + 4)
|
|
local b = buffer.readf32(buf, pos + 8)
|
|
return Color3.new(r, g, b), pos + 12
|
|
end
|
|
|
|
if t == T_BRICKCOLOR then
|
|
return BrickColor.new(buffer.readu16(buf, pos)), pos + 2
|
|
end
|
|
|
|
if t == T_INSTANCE then
|
|
local idx; idx, pos = readVarUInt(buf, pos)
|
|
return refs and refs[idx] or nil, pos
|
|
end
|
|
|
|
if t == T_ENUMITEM then
|
|
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
|
|
|
|
if t == T_ENUM then
|
|
local nameLen; nameLen, pos = readVarUInt(buf, pos)
|
|
local enumName = buffer.readstring(buf, pos, nameLen)
|
|
return (Enum :: any)[enumName], pos + nameLen
|
|
end
|
|
|
|
if t == T_UDIM2 then
|
|
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
|
|
|
|
if t == T_UDIM then
|
|
local s = buffer.readf32(buf, pos)
|
|
local o = buffer.readi32(buf, pos + 4)
|
|
return UDim.new(s, o), pos + 8
|
|
end
|
|
|
|
if t == T_RECT then
|
|
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
|
|
|
|
if t == T_NUMBERRANGE then
|
|
return NumberRange.new(buffer.readf32(buf, pos), buffer.readf32(buf, pos + 4)), pos + 8
|
|
end
|
|
|
|
if t == T_RAY then
|
|
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
|
|
|
|
if t == T_COLSEQ then
|
|
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
|
|
|
|
if t == T_NUMSEQ then
|
|
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
|
|
|
|
error(`(serdes) unknown type: {t}`)
|
|
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)
|
|
return result, #w.refs > 0 and table.clone(w.refs) or nil
|
|
end
|
|
|
|
local function reset(w: Writer)
|
|
w.cursor = 0
|
|
table.clear(w.refs)
|
|
end
|
|
|
|
local BufferSerdes = {}
|
|
|
|
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.varUIntSize = varUIntSize
|
|
BufferSerdes.writeVarUInt = writeVarUInt
|
|
BufferSerdes.readVarUInt = readVarUInt
|
|
BufferSerdes.f32ToF16 = f32ToF16
|
|
BufferSerdes.f16ToF32 = f16ToF32
|
|
|
|
return BufferSerdes :: typeof(BufferSerdes)
|