Files
Questie-X/Modules/QuestieStream.lua
T
2026-06-04 21:54:36 -05:00

491 lines
14 KiB
Lua

-- small binary stream library with "base 89" decoder (credit to Aero for the algorithm)
---@class QuestieStreamLib
local QuestieStreamLib = QuestieLoader:CreateModule("QuestieStreamLib");
local tinsert = table.insert
local unpack_limit = 4096 -- wow api limits unpack to somewhere between 7000-8000
-- reduce cpu time by less global space lookups
local band = bit.band
local lshift = bit.lshift
local rshift = bit.rshift
local mod = math.mod
local stringchar = string.char
local stringbyte = string.byte
local stringsub = string.sub
-- shift level table
local QSL_dltab = {};
QSL_dltab[stringbyte("x")] = 0;
QSL_dltab[stringbyte("y")] = 1;
QSL_dltab[stringbyte("z")] = 2;
-- translation table
local QSL_dttab = {};
QSL_dttab[123] = 1;
QSL_dttab[124] = 6;
QSL_dttab[125] = 59;
local QSL_ltab = {};
local QSL_ttab = {};
QSL_ltab[0] = string.byte("x");
QSL_ltab[1] = string.byte("y");
QSL_ltab[2] = string.byte("z");
QSL_ttab[34] = 123;
QSL_ttab[39] = 124;
QSL_ttab[92] = 125;
local StreamPool = {}
local function _StreamReset(self)
self._pointer = 1
self._bin = {{}}
self._level = 0
end
local function _StreamFinished(self)
self:reset()
tinsert(StreamPool, self)
end
function QuestieStreamLib:GetStream(mode) -- returns a new stream
if not mode then
Questie:Error("QuestieStreamLib: Stream encoding mode is not defined.")
error("Stream encoding mode is not defined.")
end
local stream = tremove(StreamPool)
if stream and stream._mode == mode then
return stream
else
stream = {}
end
for k,v in pairs(QuestieStreamLib) do -- copy functions to new object
stream[k] = v
end
stream.reset = _StreamReset
stream.finished = _StreamFinished -- its best to call this (not required) when done using the stream
stream:reset()
stream._mode = mode
if mode == "raw" or mode == "raw_assert" then
stream.ReadByte = QuestieStreamLib._ReadByte_raw
stream.ReadShort = QuestieStreamLib._ReadShort_raw
stream.ReadInt24 = QuestieStreamLib._ReadInt24_raw
stream.ReadInt = QuestieStreamLib._ReadInt_raw
stream.ReadInt12Pair = QuestieStreamLib._ReadInt12Pair_raw
stream.ReadTinyString = QuestieStreamLib._ReadTinyString_raw
stream.ReadShortString = QuestieStreamLib._ReadShortString_raw
stream.ReadTinyStringNil = QuestieStreamLib._ReadTinyStringNil_raw
if mode == "raw" then
stream.WriteByte = QuestieStreamLib._writeByte
stream.WriteShort = QuestieStreamLib._WriteShort
stream.WriteInt = QuestieStreamLib._WriteInt
stream.WriteInt24 = QuestieStreamLib._WriteInt24
stream.WriteInt12Pair = QuestieStreamLib._WriteInt12Pair
stream.WriteLong = QuestieStreamLib._WriteLong
else
stream.WriteByte = QuestieStreamLib._writeByte_assert
stream.WriteShort = QuestieStreamLib._WriteShort_assert
stream.WriteInt = QuestieStreamLib._WriteInt_assert
stream.WriteInt24 = QuestieStreamLib._WriteInt24_assert
stream.WriteInt12Pair = QuestieStreamLib._WriteInt12Pair_assert
stream.WriteLong = QuestieStreamLib._WriteLong_assert
end
stream.WriteTinyString = QuestieStreamLib._WriteTinyString
else
if mode == "1short" then
stream.ReadByte = QuestieStreamLib._ReadByte_1short
stream.ReadTinyString = QuestieStreamLib._ReadTinyString
stream.WriteByte = QuestieStreamLib._WriteByte_1short
stream.WriteTinyString = QuestieStreamLib._WriteTinyString
else
stream.ReadByte = QuestieStreamLib._ReadByte_b89
stream.ReadTinyString = QuestieStreamLib._ReadTinyString_b89
stream.WriteByte = QuestieStreamLib._WriteByte_b89
stream.WriteTinyString = QuestieStreamLib._WriteTinyString_b89
end
stream.ReadShort = QuestieStreamLib._ReadShort
stream.ReadInt24 = QuestieStreamLib._ReadInt24
stream.ReadInt = QuestieStreamLib._ReadInt
stream.ReadInt12Pair = QuestieStreamLib._ReadInt12Pair
stream.ReadTinyString = QuestieStreamLib._ReadTinyString
stream.ReadShortString = QuestieStreamLib._ReadShortString
stream.ReadTinyStringNil = QuestieStreamLib._ReadTinyStringNil
stream.WriteShort = QuestieStreamLib._WriteShort
stream.WriteInt = QuestieStreamLib._WriteInt
stream.WriteInt24 = QuestieStreamLib._WriteInt24
stream.WriteInt12Pair = QuestieStreamLib._WriteInt12Pair
stream.WriteLong = QuestieStreamLib._WriteLong
end
return stream
end
function QuestieStreamLib:_writeByte(val)
local p = self._pointer
local chunkId = math.floor((p - 1) / 100000) + 1
local localIdx = mod(p - 1, 100000) + 1
if not self._bin[chunkId] then
self._bin[chunkId] = {}
end
self._bin[chunkId][localIdx] = stringchar(val)
self._pointer = p + 1
end
function QuestieStreamLib:_writeByte_assert(val)
assert(type(val) == "number", "Not a number")
assert(val >= 0, "Underflow")
assert(val <= 255, "Overflow") -- 8 bits
self:_writeByte(val)
end
function QuestieStreamLib:_readByte()
local p = self._pointer
self._pointer = p + 1
if type(self._bin) == "string" then
return stringbyte(self._bin, p)
else
local chunkId = math.floor((p - 1) / 100000) + 1
local localIdx = mod(p - 1, 100000) + 1
if self._bin[chunkId] then
return stringbyte(self._bin[chunkId][localIdx] or "\0")
end
return 0
end
end
function QuestieStreamLib:SetPointer(pointer)
self._pointer = pointer;
end
function QuestieStreamLib:_ReadByte_b89()
local v = self:_readByte()
if QSL_dltab[v] then
self._level = QSL_dltab[v];
v = self:_readByte()
end
if QSL_dttab[v] then
return self._level * 86 + QSL_dttab[v];
else
return self._level * 86 + v - 33;
end
end
function QuestieStreamLib:_WriteByte_b89(e)
if not e then return end
local level = math.floor(e / 86);
if self._level ~= level then
self._level = level
self:_writeByte(QSL_ltab[level])
end
local chr = mod(e, 86) + 33
if QSL_ttab[chr] then
self:_writeByte(QSL_ttab[chr])
else
self:_writeByte(chr)
end
end
local _1short_control = 237
local _1short_zero = 238
local _1short_control_table = {
_1short_control, _1short_zero
}
local _1short_control_table_reversed = {};
for k,v in pairs(_1short_control_table) do _1short_control_table_reversed[v] = k end
function QuestieStreamLib:_WriteByte_1short(e)
if _1short_control_table_reversed[e] then
self:_writeByte(_1short_control)
self:_writeByte(_1short_control_table_reversed[e])
elseif e == 0 then
self:_writeByte(_1short_zero)
else
self:_writeByte(e)
end
end
function QuestieStreamLib:_ReadByte_1short()
local v = self:_readByte()
if v == _1short_zero then
return 0
elseif v == _1short_control then
v = self:_readByte()
return _1short_control_table[v]
end
return v
end
function QuestieStreamLib:_ReadByte_raw()
local p = self._pointer
self._pointer = p + 1
return stringbyte(self._bin, p)
end
function QuestieStreamLib:ReadBytes(count)
local ret = {};
for i=1, count do
tinsert(ret, self:ReadByte());
end
return unpack(ret)
end
function QuestieStreamLib:ReadShorts(count)
local ret = {};
for i=1, count do
tinsert(ret, self:ReadShort());
end
return unpack(ret)
end
function QuestieStreamLib:_ReadShort()
return lshift(self:ReadByte(), 8) + self:ReadByte();
end
function QuestieStreamLib:_ReadShort_raw()
local p = self._pointer
self._pointer = p + 2
local a,b = stringbyte(self._bin, p, p+1)
return a*256 + b
end
function QuestieStreamLib:_ReadInt12Pair()
local a = self:ReadByte()
return self:ReadByte() + lshift(band(a, 15), 8), self:ReadByte() + lshift(band(a, 240), 4)
end
function QuestieStreamLib:_ReadInt12Pair_raw()
local p = self._pointer
self._pointer = p + 3
local a,b,c = stringbyte(self._bin, p, p+2)
local low4bit = mod(a, 16)
return low4bit * 256 + b, (a - low4bit) * 16 + c
end
function QuestieStreamLib:_ReadInt24()
return lshift(self:ReadByte(), 16) + lshift(self:ReadByte(), 8) + self:ReadByte();
end
function QuestieStreamLib:_ReadInt24_raw()
local p = self._pointer
self._pointer = p + 3
local a,b,c = stringbyte(self._bin, p, p+2)
return a*65536 + b*256 + c
end
function QuestieStreamLib:_ReadInt()
return lshift(self:ReadByte(), 24) + lshift(self:ReadByte(), 16) + lshift(self:ReadByte(), 8) + self:ReadByte();
end
function QuestieStreamLib:_ReadInt_raw()
local p = self._pointer
self._pointer = p + 4
local a,b,c,d = stringbyte(self._bin, p, p+3)
return a*16777216 + b*65536 + c*256 + d
end
function QuestieStreamLib:ReadLong()
return lshift(self:ReadByte(), 56) +lshift(self:ReadByte(), 48) +lshift(self:ReadByte(), 40) +lshift(self:ReadByte(), 32) +lshift(self:ReadByte(), 24) + lshift(self:ReadByte(), 16) + lshift(self:ReadByte(), 8) + self:ReadByte();
end
function QuestieStreamLib:_ReadTinyString()
local length = self:ReadByte()
local ret = {};
for i = 1, length do
tinsert(ret, self:ReadByte()) -- slightly better lua code is slightly better
end
return stringchar(unpack(ret))
end
function QuestieStreamLib:_ReadTinyString_b89()
local length = self:ReadByte()
local ret = {};
for i = 1, length do
tinsert(ret, self:_readByte()) -- slightly better lua code is slightly better
end
return stringchar(unpack(ret))
end
function QuestieStreamLib:_ReadTinyString_raw()
local p = self._pointer
local length = stringbyte(self._bin, p)
p = p + 1
self._pointer = p + length
return stringsub(self._bin, p, p+length-1)
end
function QuestieStreamLib:_ReadTinyStringNil_raw()
local p = self._pointer
local length = stringbyte(self._bin, p)
p = p + 1
if length == 0 then
self._pointer = p
return nil
end
self._pointer = p + length
return stringsub(self._bin, p, p+length-1)
end
function QuestieStreamLib:_ReadTinyStringNil()
local length = self:ReadByte()
if length == 0 then return nil end
local ret = {};
for i = 1, length do
tinsert(ret, self:ReadByte()) -- slightly better lua code is slightly better
end
return stringchar(unpack(ret))
end
function QuestieStreamLib:_ReadShortString()
local length = self:ReadShort()
local ret = {};
if length > unpack_limit then
for i = 1, length do
tinsert(ret, stringchar(self:ReadByte()))
end
return table.concat(ret)
else
for i = 1, length do
tinsert(ret, self:ReadByte()) -- slightly better lua code is slightly better
end
return stringchar(unpack(ret))
end
end
function QuestieStreamLib:_ReadShortString_raw()
local p = self._pointer
local a,b = stringbyte(self._bin, p, p+1)
local length = a*256 + b
p = p + 2
self._pointer = p + length
return stringsub(self._bin, p, p+length-1)
end
function QuestieStreamLib:_WriteShort(val)
--print("wshort: " .. val);
self:WriteByte(mod(rshift(val, 8), 256));
self:WriteByte(mod(val, 256));
end
function QuestieStreamLib:_WriteShort_assert(val)
assert(type(val) == "number", "Not a number")
assert(val >= 0, "Underflow")
assert(val <= 65535, "Overflow") -- 16 bits
self:_WriteShort(val)
end
function QuestieStreamLib:_WriteInt(val)
self:WriteByte(mod(rshift(val, 24), 256));
self:WriteByte(mod(rshift(val, 16), 256));
self:WriteByte(mod(rshift(val, 8), 256));
self:WriteByte(mod(val, 256));
end
function QuestieStreamLib:_WriteInt_assert(val)
assert(type(val) == "number", "Not a number")
assert(val >= 0, "Underflow")
assert(val <= 4294967295, "Overflow") -- 32 bits
self:_WriteInt(val)
end
function QuestieStreamLib:_WriteInt24(val)
--print("wi24: " .. val);
self:WriteByte(mod(rshift(val, 16), 256));
self:WriteByte(mod(rshift(val, 8), 256));
self:WriteByte(mod(val, 256));
end
function QuestieStreamLib:_WriteInt24_assert(val)
assert(type(val) == "number", "Not a number")
assert(val >= 0, "Underflow")
assert(val <= 99999999, "Overflow") -- 24 bits
self:_WriteInt24(val)
end
function QuestieStreamLib:_WriteInt12Pair(val1, val2)
self:WriteByte(band(rshift(val1, 8), 15) + lshift(band(rshift(val2, 8), 15), 4))
self:WriteByte(mod(val1, 256))
self:WriteByte(mod(val2, 256))
end
function QuestieStreamLib:_WriteInt12Pair_assert(val1, val2)
assert(type(val1) == "number", "Not a number")
assert(val1 >= 0, "Underflow")
assert(val1 <= 4095, "Overflow") -- 12 bits
assert(type(val2) == "number", "Not a number")
assert(val2 >= 0, "Underflow")
assert(val2 <= 4095, "Overflow") -- 12 bits
self:_WriteInt12Pair(val1, val2)
end
function QuestieStreamLib:_WriteLong(val)
self:WriteByte(mod(rshift(val, 56), 256));
self:WriteByte(mod(rshift(val, 48), 256));
self:WriteByte(mod(rshift(val, 40), 256));
self:WriteByte(mod(rshift(val, 32), 256));
self:WriteByte(mod(rshift(val, 24), 256));
self:WriteByte(mod(rshift(val, 16), 256));
self:WriteByte(mod(rshift(val, 8), 256));
self:WriteByte(mod(val, 256));
end
function QuestieStreamLib:_WriteLong_assert(val)
assert(type(val) == "number", "Not a number")
assert(val >= 0, "Underflow")
assert(val <= 18446744073709551615, "Overflow") -- 64 bits
self:_WriteLong(val)
end
function QuestieStreamLib:_WriteTinyString(val)
local length = string.len(val)
self:WriteByte(length);
for i=1, length do
self:WriteByte(stringbyte(val, i));
end
end
function QuestieStreamLib:_WriteTinyString_b89(val)
local length = string.len(val)
self:WriteByte(length);
for i=1, length do
self:_writeByte(stringbyte(val, i));
end
end
function QuestieStreamLib:WriteShortString(val)
local length = string.len(val)
self:WriteShort(length);
for i=1, length do
self:WriteByte(stringbyte(val, i));
end
end
function QuestieStreamLib:Save()
local chunks = {}
for i=1, table.getn(self._bin) do
table.insert(chunks, table.concat(self._bin[i]))
end
return table.concat(chunks)
end
function QuestieStreamLib:Load(bin)
self._pointer = 1
self._bin = bin
self._level = 0
end