discoveries/AI/EVENTS/dataview_by_Gottfriedleibniz.lua
--[[
Default, and assumed, LUAI_MAXSHORTLEN is 40. To create a non internalized
string always force the buffer to be greater than that value.
--]]
local _strblob = string.blob or function(length)
return string.rep("\0", math.max(40 + 1, length))
end
--[[
API:
DataView::{Get | Set}Int8
DataView::{Get | Set}Uint8
DataView::{Get | Set}Int16
DataView::{Get | Set}Uint16
DataView::{Get | Set}Int32
DataView::{Get | Set}Uint32
DataView::{Get | Set}Int64
DataView::{Get | Set}Uint64
DataView::{Get | Set}LuaInt
DataView::{Get | Set}UluaInt
DataView::{Get | Set}LuaNum
DataView::{Get | Set}Float32
DataView::{Get | Set}Float64
DataView::{Get | Set}String
Parameters:
Get: self, offset, endian (optional)
Set: self, offset, value, endian (optional)
DataView::{GetFixed | SetFixed}::Int
DataView::{GetFixed | SetFixed}::Uint
DataView::{GetFixed | SetFixed}::String
Parameters:
Get: offset, typelen, endian (optional)
Set: offset, typelen, value, endian (optional)
NOTES:
(1) Endianness changed from JS API, defaults to little endian.
(2) {Get|Set|Next} offsets are zero-based.
EXAMPLES:
local view = DataView.ArrayBuffer(512)
if Citizen.InvokeNative(0x79923CD21BECE14E, 1, view:Buffer(), Citizen.ReturnResultAnyway()) then
local dlc = {
validCheck = view:GetInt64(0),
weaponHash = view:GetInt32(8),
val3 = view:GetInt64(16),
weaponCost = view:GetInt64(24),
ammoCost = view:GetInt64(32),
ammoType = view:GetInt64(40),
defaultClipSize = view:GetInt64(48),
nameLabel = view:GetFixedString(56, 64),
descLabel = view:GetFixedString(120, 64),
simpleDesc = view:GetFixedString(184, 64),
upperCaseName = view:GetFixedString(248, 64),
}
end
--]]
DataView = {
EndBig = ">",
EndLittle = "<",
Types = {
Int8 = { code = "i1", size = 1 },
Uint8 = { code = "I1", size = 1 },
Int16 = { code = "i2", size = 2 },
Uint16 = { code = "I2", size = 2 },
Int32 = { code = "i4", size = 4 },
Uint32 = { code = "I4", size = 4 },
Int64 = { code = "i8", size = 8 },
Uint64 = { code = "I8", size = 8 },
LuaInt = { code = "j", size = 8 }, -- a lua_Integer
UluaInt = { code = "J", size = 8 }, -- a lua_Unsigned
LuaNum = { code = "n", size = 8}, -- a lua_Number
Float32 = { code = "f", size = 4 }, -- a float (native size)
Float64 = { code = "d", size = 8 }, -- a double (native size)
String = { code = "z", size = -1, }, -- zero terminated string
},
FixedTypes = {
String = { code = "c", size = -1, }, -- a fixed-sized string with n bytes
Int = { code = "i", size = -1, }, -- a signed int with n bytes
Uint = { code = "I", size = -1, }, -- an unsigned int with n bytes
},
}
DataView.__index = DataView
--[[ Is a dataview type at a specific offset still within buffer length --]]
local function _ib(o, l, t) return ((t.size < 0 and true) or (o + (t.size - 1) <= l)) end
local function _ef(big) return (big and DataView.EndBig) or DataView.EndLittle end
--[[ Helper function for setting fixed datatypes within a buffer --]]
local SetFixed = nil
--[[ Create an ArrayBuffer with a size in bytes --]]
function DataView.ArrayBuffer(length)
return setmetatable({
offset = 1, length = length, blob = _strblob(length)
}, DataView)
end
--[[ Wrap a non-internalized string --]]
function DataView.Wrap(blob)
return setmetatable({
offset = 1, blob = blob, length = blob:len(),
}, DataView)
end
function DataView:Buffer() return self.blob end
function DataView:ByteLength() return self.length end
function DataView:ByteOffset() return self.offset end
function DataView:SubView(offset)
return setmetatable({
offset = offset, blob = self.blob, length = self.length,
}, DataView)
end
--[[ Create the API by using DataView.Types. --]]
for label,datatype in pairs(DataView.Types) do
DataView["Get" .. label] = function(self, offset, endian)
local o = self.offset + offset
if _ib(o, self.length, datatype) then
local v,_ = string.unpack(_ef(endian) .. datatype.code, self.blob, o)
return v
end
return nil -- Out of bounds
end
DataView["Set" .. label] = function(self, offset, value, endian)
local o = self.offset + offset
if _ib(o, self.length, datatype) then
return SetFixed(self, o, value, _ef(endian) .. datatype.code)
end
return self -- Out of bounds
end
-- Ensure cache is correct.
if datatype.size >= 0 and string.packsize(datatype.code) ~= datatype.size then
local msg = "Pack size of %s (%d) does not match cached length: (%d)"
error(msg:format(label, string.packsize(fmt[#fmt]), datatype.size))
return nil
end
end
for label,datatype in pairs(DataView.FixedTypes) do
DataView["GetFixed" .. label] = function(self, offset, typelen, endian)
local o = self.offset + offset
if o + (typelen - 1) <= self.length then
local code = _ef(endian) .. "c" .. tostring(typelen)
local v,_ = string.unpack(code, self.blob, o)
return v
end
return nil -- Out of bounds
end
DataView["SetFixed" .. label] = function(self, offset, typelen, value, endian)
local o = self.offset + offset
if o + (typelen - 1) <= self.length then
local code = _ef(endian) .. "c" .. tostring(typelen)
return SetFixed(self, o, value, code)
end
return self
end
end
--[[ Helper function for setting fixed datatypes within a buffer --]]
SetFixed = function(self, offset, value, code)
local fmt = { }
local values = { }
-- All bytes prior to the offset
if self.offset < offset then
local size = offset - self.offset
fmt[#fmt + 1] = "c" .. tostring(size)
values[#values + 1] = self.blob:sub(self.offset, size)
end
fmt[#fmt + 1] = code
values[#values + 1] = value
-- All bytes after the value (offset + size) to the end of the buffer
-- growing the buffer if needed.
local ps = string.packsize(fmt[#fmt])
if (offset + ps) <= self.length then
local newoff = offset + ps
local size = self.length - newoff + 1
fmt[#fmt + 1] = "c" .. tostring(size)
values[#values + 1] = self.blob:sub(newoff, self.length)
end
self.blob = string.pack(table.concat(fmt, ""), table.unpack(values))
self.length = self.blob:len()
return self
end