Aimware Libraries local M = {}
local ffi = require "ffi" local vtable = require "vtable" local csgo_weapons = require "csgo_weapons"
local string_gsub = string.gsub local math_floor = math.floor local cast = ffi.cast
local png_ihdr_t = ffi.typeof( [[ struct { char type[4]; uint32_t width; uint32_t height; char bitDepth; char colorType; char compression; char filter; char interlace; } * ]] )
local jpg_segment_t = ffi.typeof([[ struct { char type[2]; uint16_t size; } * ]])
local jpg_segment_sof0_t = ffi.typeof([[ struct { uint16_t size; char precision; uint16_t height; uint16_t width; } attribute((packed)) * ]])
local uint16_t_ptr = ffi.typeof("uint16_t*") local charbuffer = ffi.typeof("char[?]") local uintbuffer = ffi.typeof("unsigned int[?]")
local INVALID_TEXTURE = -1 local PNG_MAGIC = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A"
local JPG_MAGIC_1 = "\xFF\xD8\xFF\xDB" local JPG_MAGIC_2 = "\xFF\xD8\xFF\xE0\x00\x10\x4A\x46\x49\x46\x00\x01"
local JPG_SEGMENT_SOI = "\xFF\xD8" local JPG_SEGMENT_SOF0 = "\xFF\xC0" local JPG_SEGMENT_SOS = "\xFF\xDA" local JPG_SEGMENT_EOI = "\xFF\xD9"
local RENDERER_LOAD_FUNCS = { png = function(contents, width, height) local rgba, _width, _height = common.DecodePNG(contents) return draw.CreateTexture(rgba, _width, _height) end, svg = function(contents, width, height) local rgba, _width, _height = common.RasterizeSVG(contents) return draw.CreateTexture(rgba, _width, _height) end, jpg = function(contents, width, height) local rgba, _width, _height = common.DecodeJPEG(contents) return draw.CreateTexture(rgba, _width, _height) end, rgba = draw.CreateTexture }
local function bswap_16(x) return bit.rshift(bit.bswap(x), 16) end
local function hexdump(str) local out = {} str:gsub( ".", function(chr) table.insert(out, string.format("%02x", string.byte(chr))) end ) return table.concat(out, " ") end
local native_ReadFile = vtable.bind("filesystem_stdio.dll", "VBaseFileSystem011", 0, "int(__thiscall*)(void*, void*, int, void*)") local native_OpenFile = vtable.bind("filesystem_stdio.dll", "VBaseFileSystem011", 2, "void*(__thiscall*)(void*, const char*, const char*, const char*)") local native_CloseFile = vtable.bind("filesystem_stdio.dll", "VBaseFileSystem011", 3, "void(__thiscall*)(void*, void*)") local native_GetFileSize = vtable.bind("filesystem_stdio.dll", "VBaseFileSystem011", 7, "unsigned int(__thiscall*)(void*, void*)")
local function engine_read_file(filename) local handle = native_OpenFile(filename, "r", "MOD") if handle == nil then return end
local filesize = native_GetFileSize(handle)
if filesize == nil or filesize < 0 then
return
end
local buffer = charbuffer(filesize + 1)
if buffer == nil then
return
end
local read_success = native_ReadFile(buffer, filesize, handle)
if not read_success then
return
end
return ffi.string(buffer, filesize)
end
-- That shit now use ingame context of steamapi instead of connecting to global user -- enjoy, by w7rus
ffi.cdef( [[ typedef struct { void* steam_client; void* steam_user; void* steam_friends; void* steam_utils; void* steam_matchmaking; void* steam_user_stats; void* steam_apps; void* steam_matchmakingservers; void* steam_networking; void* steam_remotestorage; void* steam_screenshots; void* steam_http; void* steam_unidentifiedmessages; void* steam_controller; void* steam_ugc; void* steam_applist; void* steam_music; void* steam_musicremote; void* steam_htmlsurface; void* steam_inventory; void* steam_video; } S_steamApiCtx_t; ]] )
local pS_SteamApiCtx = ffi.cast("S_steamApiCtx_t**", ffi.cast("char*", mem.FindPattern("client.dll", "FF 15 ?? ?? ?? ?? B9 ?? ?? ?? ?? E8 ?? ?? ?? ?? 6A")) + 7)[0] or error("invalid interface", 2)
local native_ISteamFriends = ffi.cast("void***", pS_SteamApiCtx.steam_friends) local native_ISteamUtils = ffi.cast("void***", pS_SteamApiCtx.steam_utils)
local native_ISteamFriends_GetSmallFriendAvatar = vtable.thunk(34, "int(__thiscall*)(void*, uint64_t)") local native_ISteamFriends_GetMediumFriendAvatar = vtable.thunk(35, "int(__thiscall*)(void*, uint64_t)") local native_ISteamFriends_GetLargeFriendAvatar = vtable.thunk(36, "int(__thiscall*)(void*, uint64_t)")
local native_ISteamUtils_GetImageSize = vtable.thunk(5, "bool(__thiscall*)(void*, int, uint32_t*, uint32_t*)") local native_ISteamUtils_GetImageRGBA = vtable.thunk(6, "bool(__thiscall*)(void*, int, unsigned char*, int)")
local function image_measure(self, width, height) if width ~= nil and height ~= nil then return math_floor(width), math_floor(height) else if self.width == nil or self.height == nil then error("Image dimensions not known, full size is required") elseif width == nil then height = height or self.height local width = math_floor(self.width * (height / self.height)) return width, height elseif height == nil then width = width or self.width local height = math_floor(self.height * (width / self.width)) return width, height else return math_floor(self.width), math_floor(self.height) end end end
local function image_draw(self, x, y, width, height, r, g, b, a, force_same_res_render) width, height = image_measure(self, width, height)
local id = string.format("%f_%f", width, height)
local texture = self.textures[id]
-- no texture with same width and height has been loaded
if texture == nil then
if ({next(self.textures)})[2] == nil or force_same_res_render or force_same_res_render == nil then
-- try and load the texture
local func = RENDERER_LOAD_FUNCS[self.type]
if func then
if self.type == "rgba" then
width, height = self.width, self.height
end
texture = func(self.contents, width, height)
end
if texture == nil then
self.textures[id] = INVALID_TEXTURE
error("failed to load texture for " .. width .. "x" .. height, 2)
else
-- client.log("loaded svg ", self.name, " for ", width, "x", height)
self.textures[id] = texture
end
else
--right now we just choose a random texture (determined by the pairs order aka unordered)
--todo: select the texture with the highest or closest resolution?
texture = ({next(self.textures)})[2]
end
end
if texture == nil or texture == INVALID_TEXTURE then
return
elseif a == nil or a > 0 then
draw.SetTexture(texture)
draw.Color(r or 255, g or 255, b or 255, a or 255)
draw.FilledRect(x, y, x + width, y + height)
draw.SetTexture(nil)
end
return width, height
end
local image_mt = { __index = { measure = image_measure, draw = image_draw } }
local function load_png(contents) if contents:sub(1, 8) ~= PNG_MAGIC then error("Invalid magic", 2) return end
local ihdr_raw = contents:sub(13, 30)
if ihdr_raw:len() < 17 then
error("Incomplete data", 2)
return
end
local ihdr = cast(png_ihdr_t, cast("const uint8_t *", cast("const char*", ihdr_raw)))
if ffi.string(ihdr.type, 4) ~= "IHDR" then
error("Invalid chunk type, expected IHDR", 2)
return
end
local width = bit.bswap(ihdr.width)
local height = bit.bswap(ihdr.height)
if width <= 0 or height <= 0 then
error("Invalid width or height", 2)
return
end
return setmetatable(
{
type = "png",
width = width,
height = height,
contents = contents,
textures = {}
},
image_mt
)
end
local function load_jpg(contents) local buffer = ffi.cast("const uint8_t *", ffi.cast("const char *", contents)) local len_remaining = contents:len()
local width, height
if contents:sub(1, 4) == JPG_MAGIC_1 or contents:sub(1, 12) == JPG_MAGIC_2 then
local got_soi, got_sos = false, false
-- read segments until we find a SOF0 header (containing width/height)
while len_remaining > 0 do
local segment = ffi.cast(jpg_segment_t, buffer)
local typ = ffi.string(segment.type, 2)
buffer = buffer + 2
len_remaining = len_remaining - 2
if typ == JPG_SEGMENT_SOI then
got_soi = true
elseif not got_soi then
error("expected SOI segment", 2)
elseif typ == JPG_SEGMENT_SOS or typ == JPG_SEGMENT_EOI then
if typ == JPG_SEGMENT_SOS then
got_sos = true
end
break
else
-- endian convert of the size (be -> le)
local size = bswap_16(segment.size)
if typ == JPG_SEGMENT_SOF0 then
local sof0 = cast(jpg_segment_sof0_t, buffer)
height = bswap_16(sof0.height)
width = bswap_16(sof0.width)
if width <= 0 or height <= 0 then
error("Invalid width or height")
return
end
end
buffer = buffer + size
len_remaining = len_remaining - size
end
end
if not got_soi then
error("Incomplete image, missing SOI segment", 2)
return
elseif not got_sos then
error("Incomplete image, missing SOS segment", 2)
return
elseif width == nil then
error("Incomplete image, missing SOF0 segment", 2)
return
end
else
error("Invalid magic", 2)
return
end
return setmetatable(
{
type = "jpg",
width = width,
height = height,
contents = contents,
textures = {}
},
image_mt
)
end
local function load_svg(contents) -- try and find tag
local match = contents:match("<svg(.*)>.*</svg>")
if match == nil then
error("Invalid svg, missing <svg> tag", 2)
return
end
match = match:gsub("\r\n", ""):gsub("\n", "")
-- parse tag contents
local in_quote = false
local key, value = "", ""
local attributes = {}
local offset = 1
while true do
local chr = match:sub(offset, offset)
if chr == "" then
break
end
if in_quote then
-- text inside quotation marks
if chr == '"' then
in_quote = false
attributes[key:gsub("\t", ""):lower()] = value
key, value = "", ""
else
value = value .. chr
end
else
-- normal text, not inside quotes
if chr == ">" then
break
elseif chr == "=" then
if match:sub(offset, offset + 1) == '="' then
in_quote = true
offset = offset + 1
end
elseif chr == " " then
key = ""
else
key = key .. chr
end
end
offset = offset + 1
end
-- heuristics to find valid image width and height
local width, height
if attributes["width"] ~= nil then
width = tonumber((attributes["width"]:gsub("px$", ""):gsub("pt$", ""):gsub("mm$", "")))
if width ~= nil and 0 >= width then
width = nil
end
end
if attributes["height"] ~= nil then
height = tonumber((attributes["height"]:gsub("px$", ""):gsub("pt$", ""):gsub("mm$", "")))
if height ~= nil and 0 >= height then
height = nil
end
end
if width == nil or height == nil and attributes["viewbox"] ~= nil then
local x, y, w, h = attributes["viewbox"]:match("^%s*([%d.]*) ([%d.]*) ([%d.]*) ([%d.]*)%s*$")
width, height = tonumber(width), tonumber(height)
if width ~= nil and height ~= nil and (0 >= width or 0 >= height) then
width, height = nil, nil
end
end
local self =
setmetatable(
{
type = "svg",
contents = contents,
textures = {}
},
image_mt
)
if width ~= nil and height ~= nil and width > 0 and height > 0 then
self.width, self.height = width, height
end
return self
end
local function load_rgba(contents, width, height) if width == nil or height == nil or width <= 0 or height <= 0 then error("Invalid size: width and height are required and have to be greater than zero.") return end
local size = width * height * 4
if contents:len() ~= size then
error("invalid buffer length, expected width*height*4", 2)
return
end
-- load texture
local texture = draw.CreateTexture(contents, width, height)
if texture == nil then
return
end
return setmetatable(
{
type = "rgba",
width = width,
height = height,
contents = contents,
textures = {[string.format("%f_%f", width, height)] = texture}
},
image_mt
)
end
local function load_image(contents) if type(contents) == "table" then if getmetatable(contents) == image_mt then return error("trying to load an existing image") else local result = {} for key, value in pairs(contents) do result[key] = load_image(value) end return result end else -- try and determine type etc by looking for magic value if type(contents) == "string" then if contents:sub(1, 8) == PNG_MAGIC then return load_png(contents) elseif contents:sub(1, 4) == JPG_MAGIC_1 or contents:sub(1, 12) == JPG_MAGIC_2 then return load_jpg(contents) elseif contents:match("^%s*%<%?xml") ~= nil then return load_svg(contents) else return error("Failed to determine image type") end end end end
local panorama_images = setmetatable({}, {__mode = "k"}) local function get_panorama_image(path) if panorama_images[path] == nil then local path_cleaned = string_gsub(string_gsub(string_gsub(string_gsub(string_gsub(path, "%z", ""), "%c", ""), "\", "/"), "%.%./", ""), "^/+", "") local contents = engine_read_file("materials/panorama/images/" .. path_cleaned)
if contents then
local image = load_image(contents)
panorama_images[path] = image
else
panorama_images[path] = false
end
end
if panorama_images[path] then
return panorama_images[path]
end
end
local weapon_icons = setmetatable({}, {__mode = "k"}) local function get_weapon_icon(weapon_name) if weapon_icons[weapon_name] == nil then local weapon_name_cleaned local typ = type(weapon_name)
if typ == "table" and weapon_name.console_name ~= nil then
weapon_name_cleaned = weapon_name.console_name
elseif typ == "number" then
local weapon = csgo_weapons[weapon_name]
if weapon == nil then
weapon_icons[weapon_name] = false
return
end
weapon_name_cleaned = weapon.console_name
elseif typ == "string" then
weapon_name_cleaned = tostring(weapon_name)
elseif weapon_name ~= nil then
weapon_icons[weapon_name] = nil
return
else
return
end
weapon_name_cleaned = string_gsub(string_gsub(weapon_name_cleaned, "^weapon_", ""), "^item_", "")
local image = get_panorama_image("icons/equipment/" .. weapon_name_cleaned .. ".svg")
weapon_icons[weapon_name] = image or false
end
if weapon_icons[weapon_name] then
return weapon_icons[weapon_name]
end
end
local steam_avatars = {} local function get_steam_avatar(steamid3_or_steamid64, size) local cache_key = string.format("%s_%d", steamid3_or_steamid64, size or 32)
if steam_avatars[cache_key] == nil then
local func
if size == nil then
func = native_ISteamFriends_GetSmallFriendAvatar
elseif size > 64 then
func = native_ISteamFriends_GetLargeFriendAvatar
elseif size > 32 then
func = native_ISteamFriends_GetMediumFriendAvatar
else
func = native_ISteamFriends_GetSmallFriendAvatar
end
local steamid
if type(steamid3_or_steamid64) == "string" then
steamid = 76500000000000000ULL + tonumber(steamid3_or_steamid64:sub(4, -1))
elseif type(steamid3_or_steamid64) == "number" then
steamid = 76561197960265728ULL + steamid3_or_steamid64
else
return
end
local handle = func(native_ISteamFriends, steamid)
if handle > 0 then
local width = uintbuffer(1)
local height = uintbuffer(1)
if native_ISteamUtils_GetImageSize(native_ISteamUtils, handle, width, height) then
if width[0] > 0 and height[0] > 0 then
local rgba_buffer_size = width[0] * height[0] * 4
local rgba_buffer = charbuffer(rgba_buffer_size)
if native_ISteamUtils_GetImageRGBA(native_ISteamUtils, handle, rgba_buffer, rgba_buffer_size) then
steam_avatars[cache_key] = load_rgba(ffi.string(rgba_buffer, rgba_buffer_size), width[0], height[0])
end
end
end
elseif handle ~= -1 then
steam_avatars[cache_key] = false
end
end
if steam_avatars[cache_key] then
return steam_avatars[cache_key]
end
end
return { load = load_image, load_png = load_png, load_jpg = load_jpg, load_svg = load_svg, load_rgba = load_rgba, get_weapon_icon = get_weapon_icon, get_panorama_image = get_panorama_image, get_steam_avatar = get_steam_avatar }
--
-- dependencies
--
local ffi = require "ffi"
local vtable = require "vtable"
local csgo_weapons = require "csgo_weapons"
local string_gsub = string.gsub
local math_floor = math.floor
local cast = ffi.cast
--
-- ffi structs
-- (mostly for image parsing)
--
local png_ihdr_t =
ffi.typeof(
[[
struct {
char type[4];
uint32_t width;
uint32_t height;
char bitDepth;
char colorType;
char compression;
char filter;
char interlace;
} *
]]
)
local jpg_segment_t = ffi.typeof([[
struct {
char type[2];
uint16_t size;
} *
]])
local jpg_segment_sof0_t =
ffi.typeof([[
struct {
uint16_t size;
char precision;
uint16_t height;
uint16_t width;
} __attribute__((packed)) *
]])
local uint16_t_ptr = ffi.typeof("uint16_t*")
local charbuffer = ffi.typeof("char[?]")
local uintbuffer = ffi.typeof("unsigned int[?]")
--
-- constants
--
local INVALID_TEXTURE = -1
local PNG_MAGIC = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A"
local JPG_MAGIC_1 = "\xFF\xD8\xFF\xDB"
local JPG_MAGIC_2 = "\xFF\xD8\xFF\xE0\x00\x10\x4A\x46\x49\x46\x00\x01"
local JPG_SEGMENT_SOI = "\xFF\xD8"
local JPG_SEGMENT_SOF0 = "\xFF\xC0"
local JPG_SEGMENT_SOS = "\xFF\xDA"
local JPG_SEGMENT_EOI = "\xFF\xD9"
local RENDERER_LOAD_FUNCS = {
png = function(contents, width, height)
local rgba, _width, _height = common.DecodePNG(contents)
return draw.CreateTexture(rgba, _width, _height)
end,
svg = function(contents, width, height)
local rgba, _width, _height = common.RasterizeSVG(contents)
return draw.CreateTexture(rgba, _width, _height)
end,
jpg = function(contents, width, height)
local rgba, _width, _height = common.DecodeJPEG(contents)
return draw.CreateTexture(rgba, _width, _height)
end,
rgba = draw.CreateTexture
}
--
-- utility functions
--
local function bswap_16(x)
return bit.rshift(bit.bswap(x), 16)
end
local function hexdump(str)
local out = {}
str:gsub(
".",
function(chr)
table.insert(out, string.format("%02x", string.byte(chr)))
end
)
return table.concat(out, " ")
end
--
-- small filesystem implementation
--
local native_ReadFile = vtable.bind("filesystem_stdio.dll", "VBaseFileSystem011", 0, "int(__thiscall*)(void*, void*, int, void*)")
local native_OpenFile =
vtable.bind("filesystem_stdio.dll", "VBaseFileSystem011", 2, "void*(__thiscall*)(void*, const char*, const char*, const char*)")
local native_CloseFile = vtable.bind("filesystem_stdio.dll", "VBaseFileSystem011", 3, "void(__thiscall*)(void*, void*)")
local native_GetFileSize = vtable.bind("filesystem_stdio.dll", "VBaseFileSystem011", 7, "unsigned int(__thiscall*)(void*, void*)")
local function engine_read_file(filename)
local handle = native_OpenFile(filename, "r", "MOD")
if handle == nil then
return
end
local filesize = native_GetFileSize(handle)
if filesize == nil or filesize < 0 then
return
end
local buffer = charbuffer(filesize + 1)
if buffer == nil then
return
end
local read_success = native_ReadFile(buffer, filesize, handle)
if not read_success then
return
end
return ffi.string(buffer, filesize)
end
--
-- ISteamFriends / ISteamUtils
--
-- That shit now use ingame context of steamapi instead of connecting to global user
-- enjoy, by w7rus
ffi.cdef(
[[
typedef struct
{
void* steam_client;
void* steam_user;
void* steam_friends;
void* steam_utils;
void* steam_matchmaking;
void* steam_user_stats;
void* steam_apps;
void* steam_matchmakingservers;
void* steam_networking;
void* steam_remotestorage;
void* steam_screenshots;
void* steam_http;
void* steam_unidentifiedmessages;
void* steam_controller;
void* steam_ugc;
void* steam_applist;
void* steam_music;
void* steam_musicremote;
void* steam_htmlsurface;
void* steam_inventory;
void* steam_video;
} S_steamApiCtx_t;
]]
)
local pS_SteamApiCtx =
ffi.cast("S_steamApiCtx_t**", ffi.cast("char*", mem.FindPattern("client.dll", "FF 15 ?? ?? ?? ?? B9 ?? ?? ?? ?? E8 ?? ?? ?? ?? 6A")) + 7)[0] or
error("invalid interface", 2)
local native_ISteamFriends = ffi.cast("void***", pS_SteamApiCtx.steam_friends)
local native_ISteamUtils = ffi.cast("void***", pS_SteamApiCtx.steam_utils)
local native_ISteamFriends_GetSmallFriendAvatar = vtable.thunk(34, "int(__thiscall*)(void*, uint64_t)")
local native_ISteamFriends_GetMediumFriendAvatar = vtable.thunk(35, "int(__thiscall*)(void*, uint64_t)")
local native_ISteamFriends_GetLargeFriendAvatar = vtable.thunk(36, "int(__thiscall*)(void*, uint64_t)")
local native_ISteamUtils_GetImageSize = vtable.thunk(5, "bool(__thiscall*)(void*, int, uint32_t*, uint32_t*)")
local native_ISteamUtils_GetImageRGBA = vtable.thunk(6, "bool(__thiscall*)(void*, int, unsigned char*, int)")
--
-- image object implementation
--
local function image_measure(self, width, height)
if width ~= nil and height ~= nil then
return math_floor(width), math_floor(height)
else
if self.width == nil or self.height == nil then
error("Image dimensions not known, full size is required")
elseif width == nil then
height = height or self.height
local width = math_floor(self.width * (height / self.height))
return width, height
elseif height == nil then
width = width or self.width
local height = math_floor(self.height * (width / self.width))
return width, height
else
return math_floor(self.width), math_floor(self.height)
end
end
end
local function image_draw(self, x, y, width, height, r, g, b, a, force_same_res_render)
width, height = image_measure(self, width, height)
local id = string.format("%f_%f", width, height)
local texture = self.textures[id]
-- no texture with same width and height has been loaded
if texture == nil then
if ({next(self.textures)})[2] == nil or force_same_res_render or force_same_res_render == nil then
-- try and load the texture
local func = RENDERER_LOAD_FUNCS[self.type]
if func then
if self.type == "rgba" then
width, height = self.width, self.height
end
texture = func(self.contents, width, height)
end
if texture == nil then
self.textures[id] = INVALID_TEXTURE
error("failed to load texture for " .. width .. "x" .. height, 2)
else
-- client.log("loaded svg ", self.name, " for ", width, "x", height)
self.textures[id] = texture
end
else
--right now we just choose a random texture (determined by the pairs order aka unordered)
--todo: select the texture with the highest or closest resolution?
texture = ({next(self.textures)})[2]
end
end
if texture == nil or texture == INVALID_TEXTURE then
return
elseif a == nil or a > 0 then
draw.SetTexture(texture)
draw.Color(r or 255, g or 255, b or 255, a or 255)
draw.FilledRect(x, y, x + width, y + height)
draw.SetTexture(nil)
end
return width, height
end
local image_mt = {
__index = {
measure = image_measure,
draw = image_draw
}
}
--
-- functions for loading images
--
local function load_png(contents)
if contents:sub(1, 8) ~= PNG_MAGIC then
error("Invalid magic", 2)
return
end
local ihdr_raw = contents:sub(13, 30)
if ihdr_raw:len() < 17 then
error("Incomplete data", 2)
return
end
local ihdr = cast(png_ihdr_t, cast("const uint8_t *", cast("const char*", ihdr_raw)))
if ffi.string(ihdr.type, 4) ~= "IHDR" then
error("Invalid chunk type, expected IHDR", 2)
return
end
local width = bit.bswap(ihdr.width)
local height = bit.bswap(ihdr.height)
if width <= 0 or height <= 0 then
error("Invalid width or height", 2)
return
end
return setmetatable(
{
type = "png",
width = width,
height = height,
contents = contents,
textures = {}
},
image_mt
)
end
local function load_jpg(contents)
local buffer = ffi.cast("const uint8_t *", ffi.cast("const char *", contents))
local len_remaining = contents:len()
local width, height
if contents:sub(1, 4) == JPG_MAGIC_1 or contents:sub(1, 12) == JPG_MAGIC_2 then
local got_soi, got_sos = false, false
-- read segments until we find a SOF0 header (containing width/height)
while len_remaining > 0 do
local segment = ffi.cast(jpg_segment_t, buffer)
local typ = ffi.string(segment.type, 2)
buffer = buffer + 2
len_remaining = len_remaining - 2
if typ == JPG_SEGMENT_SOI then
got_soi = true
elseif not got_soi then
error("expected SOI segment", 2)
elseif typ == JPG_SEGMENT_SOS or typ == JPG_SEGMENT_EOI then
if typ == JPG_SEGMENT_SOS then
got_sos = true
end
break
else
-- endian convert of the size (be -> le)
local size = bswap_16(segment.size)
if typ == JPG_SEGMENT_SOF0 then
local sof0 = cast(jpg_segment_sof0_t, buffer)
height = bswap_16(sof0.height)
width = bswap_16(sof0.width)
if width <= 0 or height <= 0 then
error("Invalid width or height")
return
end
end
buffer = buffer + size
len_remaining = len_remaining - size
end
end
if not got_soi then
error("Incomplete image, missing SOI segment", 2)
return
elseif not got_sos then
error("Incomplete image, missing SOS segment", 2)
return
elseif width == nil then
error("Incomplete image, missing SOF0 segment", 2)
return
end
else
error("Invalid magic", 2)
return
end
return setmetatable(
{
type = "jpg",
width = width,
height = height,
contents = contents,
textures = {}
},
image_mt
)
end
local function load_svg(contents)
-- try and find <svg> tag
local match = contents:match("<svg(.*)>.*</svg>")
if match == nil then
error("Invalid svg, missing <svg> tag", 2)
return
end
match = match:gsub("\r\n", ""):gsub("\n", "")
-- parse tag contents
local in_quote = false
local key, value = "", ""
local attributes = {}
local offset = 1
while true do
local chr = match:sub(offset, offset)
if chr == "" then
break
end
if in_quote then
-- text inside quotation marks
if chr == '"' then
in_quote = false
attributes[key:gsub("\t", ""):lower()] = value
key, value = "", ""
else
value = value .. chr
end
else
-- normal text, not inside quotes
if chr == ">" then
break
elseif chr == "=" then
if match:sub(offset, offset + 1) == '="' then
in_quote = true
offset = offset + 1
end
elseif chr == " " then
key = ""
else
key = key .. chr
end
end
offset = offset + 1
end
-- heuristics to find valid image width and height
local width, height
if attributes["width"] ~= nil then
width = tonumber((attributes["width"]:gsub("px$", ""):gsub("pt$", ""):gsub("mm$", "")))
if width ~= nil and 0 >= width then
width = nil
end
end
if attributes["height"] ~= nil then
height = tonumber((attributes["height"]:gsub("px$", ""):gsub("pt$", ""):gsub("mm$", "")))
if height ~= nil and 0 >= height then
height = nil
end
end
if width == nil or height == nil and attributes["viewbox"] ~= nil then
local x, y, w, h = attributes["viewbox"]:match("^%s*([%d.]*) ([%d.]*) ([%d.]*) ([%d.]*)%s*$")
width, height = tonumber(width), tonumber(height)
if width ~= nil and height ~= nil and (0 >= width or 0 >= height) then
width, height = nil, nil
end
end
local self =
setmetatable(
{
type = "svg",
contents = contents,
textures = {}
},
image_mt
)
if width ~= nil and height ~= nil and width > 0 and height > 0 then
self.width, self.height = width, height
end
return self
end
local function load_rgba(contents, width, height)
if width == nil or height == nil or width <= 0 or height <= 0 then
error("Invalid size: width and height are required and have to be greater than zero.")
return
end
local size = width * height * 4
if contents:len() ~= size then
error("invalid buffer length, expected width*height*4", 2)
return
end
-- load texture
local texture = draw.CreateTexture(contents, width, height)
if texture == nil then
return
end
return setmetatable(
{
type = "rgba",
width = width,
height = height,
contents = contents,
textures = {[string.format("%f_%f", width, height)] = texture}
},
image_mt
)
end
local function load_image(contents)
if type(contents) == "table" then
if getmetatable(contents) == image_mt then
return error("trying to load an existing image")
else
local result = {}
for key, value in pairs(contents) do
result[key] = load_image(value)
end
return result
end
else
-- try and determine type etc by looking for magic value
if type(contents) == "string" then
if contents:sub(1, 8) == PNG_MAGIC then
return load_png(contents)
elseif contents:sub(1, 4) == JPG_MAGIC_1 or contents:sub(1, 12) == JPG_MAGIC_2 then
return load_jpg(contents)
elseif contents:match("^%s*%<%?xml") ~= nil then
return load_svg(contents)
else
return error("Failed to determine image type")
end
end
end
end
local panorama_images = setmetatable({}, {__mode = "k"})
local function get_panorama_image(path)
if panorama_images[path] == nil then
local path_cleaned = string_gsub(string_gsub(string_gsub(string_gsub(string_gsub(path, "%z", ""), "%c", ""), "\\", "/"), "%.%./", ""), "^/+", "")
local contents = engine_read_file("materials/panorama/images/" .. path_cleaned)
if contents then
local image = load_image(contents)
panorama_images[path] = image
else
panorama_images[path] = false
end
end
if panorama_images[path] then
return panorama_images[path]
end
end
local weapon_icons = setmetatable({}, {__mode = "k"})
local function get_weapon_icon(weapon_name)
if weapon_icons[weapon_name] == nil then
local weapon_name_cleaned
local typ = type(weapon_name)
if typ == "table" and weapon_name.console_name ~= nil then
weapon_name_cleaned = weapon_name.console_name
elseif typ == "number" then
local weapon = csgo_weapons[weapon_name]
if weapon == nil then
weapon_icons[weapon_name] = false
return
end
weapon_name_cleaned = weapon.console_name
elseif typ == "string" then
weapon_name_cleaned = tostring(weapon_name)
elseif weapon_name ~= nil then
weapon_icons[weapon_name] = nil
return
else
return
end
weapon_name_cleaned = string_gsub(string_gsub(weapon_name_cleaned, "^weapon_", ""), "^item_", "")
local image = get_panorama_image("icons/equipment/" .. weapon_name_cleaned .. ".svg")
weapon_icons[weapon_name] = image or false
end
if weapon_icons[weapon_name] then
return weapon_icons[weapon_name]
end
end
local steam_avatars = {}
local function get_steam_avatar(steamid3_or_steamid64, size)
local cache_key = string.format("%s_%d", steamid3_or_steamid64, size or 32)
if steam_avatars[cache_key] == nil then
local func
if size == nil then
func = native_ISteamFriends_GetSmallFriendAvatar
elseif size > 64 then
func = native_ISteamFriends_GetLargeFriendAvatar
elseif size > 32 then
func = native_ISteamFriends_GetMediumFriendAvatar
else
func = native_ISteamFriends_GetSmallFriendAvatar
end
local steamid
if type(steamid3_or_steamid64) == "string" then
steamid = 76500000000000000ULL + tonumber(steamid3_or_steamid64:sub(4, -1))
elseif type(steamid3_or_steamid64) == "number" then
steamid = 76561197960265728ULL + steamid3_or_steamid64
else
return
end
local handle = func(native_ISteamFriends, steamid)
if handle > 0 then
local width = uintbuffer(1)
local height = uintbuffer(1)
if native_ISteamUtils_GetImageSize(native_ISteamUtils, handle, width, height) then
if width[0] > 0 and height[0] > 0 then
local rgba_buffer_size = width[0] * height[0] * 4
local rgba_buffer = charbuffer(rgba_buffer_size)
if native_ISteamUtils_GetImageRGBA(native_ISteamUtils, handle, rgba_buffer, rgba_buffer_size) then
steam_avatars[cache_key] = load_rgba(ffi.string(rgba_buffer, rgba_buffer_size), width[0], height[0])
end
end
end
elseif handle ~= -1 then
steam_avatars[cache_key] = false
end
end
if steam_avatars[cache_key] then
return steam_avatars[cache_key]
end
end
return {
load = load_image,
load_png = load_png,
load_jpg = load_jpg,
load_svg = load_svg,
load_rgba = load_rgba,
get_weapon_icon = get_weapon_icon,
get_panorama_image = get_panorama_image,
get_steam_avatar = get_steam_avatar
} ```
= math.floor local cast = ffi.cast -- -- ffi structs -- (mostly for image parsing) -- local png_ihdr_t = ffi.typeof( [[ struct { char type[4]; uint32_t width; uint32_t height; char bitDepth; char colorType; char compression;
char filter; char interlace; } * ]] ) local jpg_segment_t = ffi.typeof([[ struct { char type[2]; uint16_t size; } * ]]) local jpg_segment_sof0_t = ffi.typeof([[ struct { uint16_t size; char precision; uint16_t height;
uint16_t width; } __attribute__((packed)) * ]]) local uint16_t_ptr = ffi.typeof("uint16_t*") local charbuffer = ffi.typeof("char[?]") local uintbuffer = ffi.typeof("unsigned int[?]") --
-- constants -- local INVALID_TEXTURE = -1 local PNG_MAGIC = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A" local JPG_MAGIC_1 = "\xFF\xD8\xFF\xDB" local JPG_MAGIC_2 = "\xFF\xD8\xFF\xE0\x00\x10\x4A\x46\x49\x46\x00\x01"
local JPG_SEGMENT_SOI = "\xFF\xD8" local JPG_SEGMENT_SOF0 = "\xFF\xC0" local JPG_SEGMENT_SOS = "\xFF\xDA" local JPG_SEGMENT_EOI = "\xFF\xD9" local RENDERER_LOAD_FUNCS = { png
= function(contents, width, height) local rgba, _width, _height = common.DecodePNG(contents) return draw.CreateTexture(rgba, _width, _height) end, svg = function(contents, width, height) local rgba, _width, _height
= common.RasterizeSVG(contents) return draw.CreateTexture(rgba, _width, _height) end, jpg = function(contents, width, height) local rgba, _width, _height = common.DecodeJPEG(contents) return draw.CreateTexture(rgba,
_width, _height) end, rgba = draw.CreateTexture } -- -- utility functions -- local function bswap_16(x) return bit.rshift(bit.bswap(x), 16) end local function hexdump(str) local out = {} str:gsub( ".", function(chr)
table.insert(out, string.format("%02x", string.byte(chr))) end ) return table.concat(out, " ") end -- -- small filesystem implementation -- local native_ReadFile = vtable.bind("filesystem_stdio.dll",
"VBaseFileSystem011", 0, "int(__thiscall*)(void*, void*, int, void*)") local native_OpenFile = vtable.bind("filesystem_stdio.dll", "VBaseFileSystem011", 2, "void*(__thiscall*)(void*,
const char*, const char*, const char*)") local native_CloseFile = vtable.bind("filesystem_stdio.dll", "VBaseFileSystem011", 3, "void(__thiscall*)(void*, void*)") local native_GetFileSize
= vtable.bind("filesystem_stdio.dll", "VBaseFileSystem011", 7, "unsigned int(__thiscall*)(void*, void*)") local function engine_read_file(filename) local handle = native_OpenFile(filename,
"r", "MOD") if handle == nil then return end local filesize = native_GetFileSize(handle) if filesize == nil or filesize < 0 then return end local buffer = charbuffer(filesize + 1) if buffer ==
nil then return end local read_success = native_ReadFile(buffer, filesize, handle) if not read_success then return end return ffi.string(buffer, filesize) end -- -- ISteamFriends / ISteamUtils -- -- That shit now use
ingame context of steamapi instead of connecting to global user -- enjoy, by w7rus ffi.cdef( [[ typedef struct { void* steam_client; void* steam_user; void* steam_friends; void* steam_utils; void* steam_matchmaking;
void* steam_user_stats; void* steam_apps; void* steam_matchmakingservers; void* steam_networking; void* steam_remotestorage; void* steam_screenshots; void* steam_http; void* steam_unidentifiedmessages; void* steam_controller;
void* steam_ugc; void* steam_applist; void* steam_music; void* steam_musicremote; void* steam_htmlsurface; void* steam_inventory; void* steam_video; } S_steamApiCtx_t; ]] ) local pS_SteamApiCtx = ffi.cast("S_steamApiCtx_t**",
ffi.cast("char*", mem.FindPattern("client.dll", "FF 15 ?? ?? ?? ?? B9 ?? ?? ?? ?? E8 ?? ?? ?? ?? 6A")) + 7)[0] or error("invalid interface", 2) local native_ISteamFriends = ffi.cast("void***",
pS_SteamApiCtx.steam_friends) local native_ISteamUtils = ffi.cast("void***", pS_SteamApiCtx.steam_utils) local native_ISteamFriends_GetSmallFriendAvatar = vtable.thunk(34, "int(__thiscall*)(void*, uint64_t)")
local native_ISteamFriends_GetMediumFriendAvatar = vtable.thunk(35, "int(__thiscall*)(void*, uint64_t)") local native_ISteamFriends_GetLargeFriendAvatar = vtable.thunk(36, "int(__thiscall*)(void*, uint64_t)")
local native_ISteamUtils_GetImageSize = vtable.thunk(5, "bool(__thiscall*)(void*, int, uint32_t*, uint32_t*)") local native_ISteamUtils_GetImageRGBA = vtable.thunk(6, "bool(__thiscall*)(void*, int, unsigned
char*, int)") -- -- image object implementation -- local function image_measure(self, width, height) if width ~= nil and height ~= nil then return math_floor(width), math_floor(height) else if self.width == nil
or self.height == nil then error("Image dimensions not known, full size is required") elseif width == nil then height = height or self.height local width = math_floor(self.width * (height / self.height)) return
width, height elseif height == nil then width = width or self.width local height = math_floor(self.height * (width / self.width)) return width, height else return math_floor(self.width), math_floor(self.height) end
end end local function image_draw(self, x, y, width, height, r, g, b, a, force_same_res_render) width, height = image_measure(self, width, height) local id = string.format("%f_%f", width, height) local texture
= self.textures[id] -- no texture with same width and height has been loaded if texture == nil then if ({next(self.textures)})[2] == nil or force_same_res_render or force_same_res_render == nil then -- try and load
the texture local func = RENDERER_LOAD_FUNCS[self.type] if func then if self.type == "rgba" then width, height = self.width, self.height end texture = func(self.contents, width, height) end if texture == nil
then self.textures[id] = INVALID_TEXTURE error("failed to load texture for " .. width .. "x" .. height, 2) else -- client.log("loaded svg ", self.name, " for ", width, "x",
height) self.textures[id] = texture end else --right now we just choose a random texture (determined by the pairs order aka unordered) --todo: select the texture with the highest or closest resolution? texture = ({next(self.textures)})[2]
end end if texture == nil or texture == INVALID_TEXTURE then return elseif a == nil or a > 0 then draw.SetTexture(texture) draw.Color(r or 255, g or 255, b or 255, a or 255) draw.FilledRect(x, y, x + width, y + height)
draw.SetTexture(nil) end return width, height end local image_mt = { __index = { measure = image_measure, draw = image_draw } } -- -- functions for loading images -- local function load_png(contents) if contents:sub(1,
8) ~= PNG_MAGIC then error("Invalid magic", 2) return end local ihdr_raw = contents:sub(13, 30) if ihdr_raw:len() < 17 then error("Incomplete data", 2) return end local ihdr = cast(png_ihdr_t,
cast("const uint8_t *", cast("const char*", ihdr_raw))) if ffi.string(ihdr.type, 4) ~= "IHDR" then error("Invalid chunk type, expected IHDR", 2) return end local width = bit.bswap(ihdr.width)
local height = bit.bswap(ihdr.height) if width <= 0 or height <= 0 then error("Invalid width or height", 2) return end return setmetatable( { type = "png", width = width, height = height, contents
= contents, textures = {} }, image_mt ) end local function load_jpg(contents) local buffer = ffi.cast("const uint8_t *", ffi.cast("const char *", contents)) local len_remaining = contents:len() local
width, height if contents:sub(1, 4) == JPG_MAGIC_1 or contents:sub(1, 12) == JPG_MAGIC_2 then local got_soi, got_sos = false, false -- read segments until we find a SOF0 header (containing width/height) while len_remaining
> 0 do local segment = ffi.cast(jpg_segment_t, buffer) local typ = ffi.string(segment.type, 2) buffer = buffer + 2 len_remaining = len_remaining - 2 if typ == JPG_SEGMENT_SOI then got_soi = true elseif not got_soi
then error("expected SOI segment", 2) elseif typ == JPG_SEGMENT_SOS or typ == JPG_SEGMENT_EOI then if typ == JPG_SEGMENT_SOS then got_sos = true end break else -- endian convert of the size (be -> le) local
size = bswap_16(segment.size) if typ == JPG_SEGMENT_SOF0 then local sof0 = cast(jpg_segment_sof0_t, buffer) height = bswap_16(sof0.height) width = bswap_16(sof0.width) if width <= 0 or height <= 0 then error("Invalid
width or height") return end end buffer = buffer + size len_remaining = len_remaining - size end end if not got_soi then error("Incomplete image, missing SOI segment", 2) return elseif not got_sos then
error("Incomplete image, missing SOS segment", 2) return elseif width == nil then error("Incomplete image, missing SOF0 segment", 2) return end else error("Invalid magic", 2) return end
return setmetatable( { type = "jpg", width = width, height = height, contents = contents, textures = {} }, image_mt ) end local function load_svg(contents) -- try and find <svg> tag local match = contents:match("<svg(.*)>.*</svg>")
if match == nil then error("Invalid svg, missing <svg> tag", 2) return end match = match:gsub("\r\n", ""):gsub("\n", "") -- parse tag contents local in_quote =
false local key, value = "", "" local attributes = {} local offset = 1 while true do local chr = match:sub(offset, offset) if chr == "" then break end if in_quote then -- text inside quotation
marks if chr == '"' then in_quote = false attributes[key:gsub("\t", ""):lower()] = value key, value = "", "" else value = value .. chr end else -- normal text,
not inside quotes if chr == ">" then break elseif chr == "=" then if match:sub(offset, offset + 1) == '="' then in_quote = true offset = offset + 1 end elseif chr == " "
then key = "" else key = key .. chr end end offset = offset + 1 end -- heuristics to find valid image width and height local width, height if attributes["width"] ~= nil then width = tonumber((attributes["width"]:gsub("px$",
""):gsub("pt$", ""):gsub("mm$", ""))) if width ~= nil and 0 >= width then width = nil end end if attributes["height"] ~= nil then height = tonumber((attributes["height"]:gsub("px$",
""):gsub("pt$", ""):gsub("mm$", ""))) if height ~= nil and 0 >= height then height = nil end end if width == nil or height == nil and attributes["viewbox"]
~= nil then local x, y, w, h = attributes["viewbox"]:match("^%s*([%d.]*) ([%d.]*) ([%d.]*) ([%d.]*)%s*$") width, height = tonumber(width), tonumber(height) if width ~= nil and height ~= nil and (0
>= width or 0 >= height) then width, height = nil, nil end end local self = setmetatable( { type = "svg", contents = contents, textures = {} }, image_mt ) if width ~= nil and height ~= nil and width
> 0 and height > 0 then self.width, self.height = width, height end return self end local function load_rgba(contents, width, height) if width == nil or height == nil or width <= 0 or height <= 0 then error("Invalid
size: width and height are required and have to be greater than zero.") return end local size = width * height * 4 if contents:len() ~= size then error("invalid buffer length, expected width*height*4",
2) return end -- load texture local texture = draw.CreateTexture(contents, width, height) if texture == nil then return end return setmetatable( { type = "rgba", width = width, height = height, contents =
contents, textures = {[string.format("%f_%f", width, height)] = texture} }, image_mt ) end local function load_image(contents) if type(contents) == "table" then if getmetatable(contents) == image_mt
then return error("trying to load an existing image") else local result = {} for key, value in pairs(contents) do result[key] = load_image(value) end return result end else -- try and determine type etc by
looking for magic value if type(contents) == "string" then if contents:sub(1, 8) == PNG_MAGIC then return load_png(contents) elseif contents:sub(1, 4) == JPG_MAGIC_1 or contents:sub(1, 12) == JPG_MAGIC_2 then
return load_jpg(contents) elseif contents:match("^%s*%<%?xml") ~= nil then return load_svg(contents) else return error("Failed to determine image type") end end end end local panorama_images =
setmetatable({}, {__mode = "k"}) local function get_panorama_image(path) if panorama_images[path] == nil then local path_cleaned = string_gsub(string_gsub(string_gsub(string_gsub(string_gsub(path, "%z",
""), "%c", ""), "\\", "/"), "%.%./", ""), "^/+", "") local contents = engine_read_file("materials/panorama/images/"
.. path_cleaned) if contents then local image = load_image(contents) panorama_images[path] = image else panorama_images[path] = false end end if panorama_images[path] then return panorama_images[path] end end local
weapon_icons = setmetatable({}, {__mode = "k"}) local function get_weapon_icon(weapon_name) if weapon_icons[weapon_name] == nil then local weapon_name_cleaned local typ = type(weapon_name) if typ == "table"
and weapon_name.console_name ~= nil then weapon_name_cleaned = weapon_name.console_name elseif typ == "number" then local weapon = csgo_weapons[weapon_name] if weapon == nil then weapon_icons[weapon_name]
= false return end weapon_name_cleaned = weapon.console_name elseif typ == "string" then weapon_name_cleaned = tostring(weapon_name) elseif weapon_name ~= nil then weapon_icons[weapon_name] = nil return else
return end weapon_name_cleaned = string_gsub(string_gsub(weapon_name_cleaned, "^weapon_", ""), "^item_", "") local image = get_panorama_image("icons/equipment/" .. weapon_name_cleaned
.. ".svg") weapon_icons[weapon_name] = image or false end if weapon_icons[weapon_name] then return weapon_icons[weapon_name] end end local steam_avatars = {} local function get_steam_avatar(steamid3_or_steamid64,
size) local cache_key = string.format("%s_%d", steamid3_or_steamid64, size or 32) if steam_avatars[cache_key] == nil then local func if size == nil then func = native_ISteamFriends_GetSmallFriendAvatar elseif
size > 64 then func = native_ISteamFriends_GetLargeFriendAvatar elseif size > 32 then func = native_ISteamFriends_GetMediumFriendAvatar else func = native_ISteamFriends_GetSmallFriendAvatar end local steamid if
type(steamid3_or_steamid64) == "string" then steamid = 76500000000000000ULL + tonumber(steamid3_or_steamid64:sub(4, -1)) elseif type(steamid3_or_steamid64) == "number" then steamid = 76561197960265728ULL
+ steamid3_or_steamid64 else return end local handle = func(native_ISteamFriends, steamid) if handle > 0 then local width = uintbuffer(1) local height = uintbuffer(1) if native_ISteamUtils_GetImageSize(native_ISteamUtils,
handle, width, height) then if width[0] > 0 and height[0] > 0 then local rgba_buffer_size = width[0] * height[0] * 4 local rgba_buffer = charbuffer(rgba_buffer_size) if native_ISteamUtils_GetImageRGBA(native_ISteamUtils,
handle, rgba_buffer, rgba_buffer_size) then steam_avatars[cache_key] = load_rgba(ffi.string(rgba_buffer, rgba_buffer_size), width[0], height[0]) end end end elseif handle ~= -1 then steam_avatars[cache_key] = false
end end if steam_avatars[cache_key] then return steam_avatars[cache_key] end end return { load = load_image, load_png = load_png, load_jpg = load_jpg, load_svg = load_svg, load_rgba = load_rgba, get_weapon_icon = get_weapon_icon,
get_panorama_image = get_panorama_image, get_steam_avatar = get_steam_avatar } ```