Skip to content

Instantly share code, notes, and snippets.

@CapsAdmin
Last active June 23, 2018 07:23
Show Gist options
  • Save CapsAdmin/5992f9b9101004519eb79863e364830e to your computer and use it in GitHub Desktop.
Save CapsAdmin/5992f9b9101004519eb79863e364830e to your computer and use it in GitHub Desktop.
local OS = jit and jit.os:lower() or "unknown"
local ARCH = jit and jit.arch:lower() or "unknown"
local start_time = os.clock()
if not os.getenv("GOLUWA_CLI") then
if os.getenv("GOLUWA_CLI_TIME") then
io.write("[runfile] ", os.getenv("GOLUWA_CLI_TIME"), " seconds spent in ./goluwa", OS == "windows" and ".cmd" or "", "\n")
end
if os.getenv("GOLUWA_BOOT_TIME") then
io.write("[runfile] ", os.getenv("GOLUWA_BOOT_TIME"), " seconds spent in core/lua/boot.lua\n")
end
end
os.setlocale("")
io.stdout:setvbuf("no")
do
local ok, opt = pcall(require, "jit.opt")
if ok then
opt.start(
"maxtrace=65535", -- 1000 1-65535: maximum number of traces in the cache
"maxrecord=20000", -- 4000: maximum number of recorded IR instructions
"maxirconst=2500", -- 500: maximum number of IR constants of a trace
"maxside=100", -- 100: maximum number of side traces of a root trace
"maxsnap=800", -- 500: maximum number of snapshots for a trace
"minstitch=0", -- 0: minimum number of IR ins for a stitched trace.
"hotloop=56", -- 56: number of iterations to detect a hot loop or hot call
"hotexit=10", -- 10: number of taken exits to start a side trace
"tryside=4", -- 4: number of attempts to compile a side trace
"instunroll=500", -- 4: maximum unroll factor for instable loops
"loopunroll=500", -- 15: maximum unroll factor for loop ops in side traces
"callunroll=500", -- 3: maximum unroll factor for pseudo-recursive calls
"recunroll=2", -- 2: minimum unroll factor for true recursion
"maxmcode=8192", -- 512: maximum total size of all machine code areas in KBytes
--jit.os == "x64" and "sizemcode=64" or "sizemcode=32", -- Size of each machine code area in KBytes (Windows: 64K)
"+fold", -- Constant Folding, Simplifications and Reassociation
"+cse", -- Common-Subexpression Elimination
"+dce", -- Dead-Code Elimination
"+narrow", -- Narrowing of numbers to integers
"+loop", -- Loop Optimizations (code hoisting)
"+fwd", -- Load Forwarding (L2L) and Store Forwarding (S2L)
"+dse", -- Dead-Store Elimination
"+abc", -- Array Bounds Check Elimination
"+sink", -- Allocation/Store Sinking
"+fuse" -- Fusion of operands into instructions
)
end
end
--loadfile("../../core/lua/modules/bytecode_cache.lua")()
local PROFILE_STARTUP = false
if PROFILE_STARTUP then
local old = io.stdout
io.stdout = {write = function(_, ...) io.write(...) end}
require("jit.p").start("rplfvi1")
io.stdout = old
end
-- put all c functions in a table so we can override them if needed
-- without doing the local oldfunc = print thing over and over again
if not _G._OLD_G then
local _OLD_G = {}
if pcall(require, "ffi") then
_G.ffi = require("ffi")
end
for k,v in pairs(_G) do
if k ~= "_G" then
local t = type(v)
if t == "function" then
_OLD_G[k] = v
elseif t == "table" then
_OLD_G[k] = {}
for k2, v2 in pairs(v) do
if type(v2) == "function" then
_OLD_G[k][k2] = v2
end
end
end
end
end
_G.ffi = nil
_G._OLD_G = _OLD_G
end
local info = assert(debug.getinfo(1), "debug.getinfo(1) returns nothing")
local init_lua_path = info.source
local relative_root, internal_addon_name = init_lua_path:match("^@(.+/)(.+)/lua/init.lua$")
if not relative_root then
relative_root = ""
internal_addon_name = ""
end
do -- constants
-- enums table
e = e or {}
e.USERNAME = _G.USERNAME or tostring(os.getenv("USERNAME") or os.getenv("USER")):gsub(" ", "_"):gsub("%p", "")
e.INTERNAL_ADDON_NAME = internal_addon_name
-- _G constants. should only contain you need to access a lot like if LINUX then
_G[e.USERNAME:upper()] = true
_G[OS:upper()] = true
_G[ARCH:upper()] = true
if not _G.PLATFORM then
if OS == "windows" then
_G.PLATFORM = "windows"
elseif OS == "linux" or OS == "osx" or OS == "bsd" then
_G.PLATFORM = "unix"
else
_G.PLATFORM = "unknown"
end
end
_G.CLI = os.getenv("GOLUWA_CLI")
end
do
-- force lookup modules in current directory rather than system
if WINDOWS then
package.cpath = "./?.dll"
elseif OSX then
package.cpath = "./?.dylib;./?.so"
else
package.cpath = "./?.so"
end
package.path = "./?.lua"
end
_G.runfile = function(path, ...) return loadfile(relative_root .. e.INTERNAL_ADDON_NAME .. "/" .. path)(...) end
do
local fs
if PLATFORM == "unix" then
fs =
(function(...) local fs = _G.fs or {}
local ffi = require("ffi")
ffi.cdef([[
void *fopen(const char *filename, const char *mode);
size_t fread(void *ptr, size_t size, size_t nmemb, void *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, void *stream);
int fseek(void *stream, long offset, int whence);
long int ftell ( void * stream );
int fclose(void *fp);
int feof(void *stream);
char *getcwd(char *buf, size_t size);
int chdir (const char *filename);
int mkdir (const char *filename, uint32_t mode);
]])
fs.open = ffi.C.fopen
fs.read = ffi.C.fread
fs.write = ffi.C.fwrite
fs.seek = ffi.C.fseek
fs.tell = ffi.C.ftell
fs.close = ffi.C.fclose
fs.eof = ffi.C.feof
local size = 4096*64
local buf = ffi.new("char[?]", size)
if jit.os == "OSX" then
ffi.cdef([[
struct dirent { // NOTE: 64bit version
uint64_t d_ino;
uint64_t d_seekoff;
uint16_t d_reclen;
uint16_t d_namlen;
uint8_t d_type;
char d_name[1024];
};
]])
else
ffi.cdef([[
struct dirent { // NOTE: 64bit version
uint64_t d_ino;
int64_t d_off;
unsigned short d_reclen;
unsigned char d_type;
char d_name[256];
};
]])
end
ffi.cdef([[
typedef struct DIR DIR;
DIR *opendir(const char *name);
struct dirent *readdir(DIR *dirp);
int closedir(DIR *dirp);
]])
function fs.find(name)
local out = {}
local ptr = ffi.C.opendir(name)
local i = 1
while true do
local dir_info = ffi.C.readdir(ptr)
if dir_info == nil then break end
local name = ffi.string(dir_info.d_name)
if name ~= "." and name ~= ".." then
out[i] = name
i = i + 1
end
end
return out
end
function fs.getcd()
local temp = ffi.new("char[1024]")
return ffi.string(ffi.C.getcwd(temp, ffi.sizeof(temp)))
end
function fs.setcd(path)
return ffi.C.chdir(path)
end
function fs.createdir(path)
return ffi.C.mkdir(path, 448) -- 0700
end
if jit.os == "OSX" then
ffi.cdef([[
struct goluwa_stat {
dev_t st_dev;
mode_t st_mode;
nlink_t st_nlink;
ino64_t st_ino;
uid_t st_uid;
gid_t st_gid;
dev_t st_rdev;
struct timespec st_atimespec;
struct timespec st_mtimespec;
struct timespec st_ctimespec;
struct timespec st_birthtimespec;
off_t st_size;
blkcnt_t st_blocks;
blksize_t st_blksize;
uint32_t st_flags;
uint32_t st_gen;
int32_t st_lspare;
int64_t st_qspare[2];
};
]])
else
ffi.cdef([[
struct goluwa_stat {
unsigned long st_dev;
unsigned long st_ino;
unsigned long st_nlink;
unsigned int st_mode;
unsigned int st_uid;
unsigned int st_gid;
unsigned int __pad0;
unsigned long st_rdev;
long st_size;
long st_blksize;
long st_blocks;
unsigned long st_atime;
unsigned long st_atime_nsec;
unsigned long st_mtime;
unsigned long st_mtime_nsec;
unsigned long st_ctime;
unsigned long st_ctime_nsec;
long __unused[3];
};
]])
end
ffi.cdef([[
long syscall(int number, ...);
]])
local DIRECTORY = 0x4000
function fs.getattributes(path)
local buff = ffi.new("struct goluwa_stat[1]")
if ffi.C.syscall(jit.arch == "x64" and 4 or 195, path, buff) == 0 then
local buff = buff[0]
return {
last_accessed = tonumber(buff.st_atime),
last_changed = tonumber(buff.st_ctime),
last_modified = tonumber(buff.st_mtime),
type = bit.band(buff.st_mode, DIRECTORY) ~= 0 and "directory" or "file",
size = tonumber(buff.st_size),
}
end
return false
end
return fs
end)();
elseif PLATFORM == "windows" then
fs =
(function(...) local fs = _G.fs or {}
local ffi = require("ffi")
ffi.cdef([[
void *fopen(const char *filename, const char *mode);
size_t fread(void *ptr, size_t size, size_t nmemb, void *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, void *stream);
int fseek(void *stream, long offset, int whence);
long int ftell ( void * stream );
int fclose(void *fp);
int feof(void *stream);
]])
fs.open = ffi.C.fopen
fs.read = ffi.C.fread
fs.write = ffi.C.fwrite
fs.seek = ffi.C.fseek
fs.tell = ffi.C.ftell
fs.close = ffi.C.fclose
fs.eof = ffi.C.feof
ffi.cdef([[
typedef struct goluwa_file_time {
unsigned long high;
unsigned long low;
} goluwa_file_time;
typedef struct goluwa_find_data {
unsigned long dwFileAttributes;
goluwa_file_time ftCreationTime;
goluwa_file_time ftLastAccessTime;
goluwa_file_time ftLastWriteTime;
unsigned long nFileSizeHigh;
unsigned long nFileSizeLow;
unsigned long dwReserved0;
unsigned long dwReserved1;
char cFileName[260];
char cAlternateFileName[14];
} goluwa_find_data;
void *FindFirstFileA(const char *lpFileName, goluwa_find_data *find_data);
bool FindNextFileA(void *handle, goluwa_find_data *find_data);
bool FindClose(void *);
unsigned long GetCurrentDirectoryA(unsigned long length, char *buffer);
bool SetCurrentDirectoryA(const char *path);
bool CreateDirectoryA(const char *path, void *lpSecurityAttributes);
typedef struct goluwa_file_attributes {
unsigned long dwFileAttributes;
goluwa_file_time ftCreationTime;
goluwa_file_time ftLastAccessTime;
goluwa_file_time ftLastWriteTime;
unsigned long nFileSizeHigh;
unsigned long nFileSizeLow;
} goluwa_file_attributes;
bool GetFileAttributesExA(
const char *lpFileName,
int fInfoLevelId,
goluwa_file_attributes *lpFileInformation
);
long GetFileAttributesA(const char *);
uint32_t GetLastError();
uint32_t FormatMessageA(
uint32_t dwFlags,
const void* lpSource,
uint32_t dwMessageId,
uint32_t dwLanguageId,
char* lpBuffer,
uint32_t nSize,
va_list *Arguments
);
]])
local error_str = ffi.new("uint8_t[?]", 1024)
local FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;
local FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
local error_flags = bit.bor(FORMAT_MESSAGE_FROM_SYSTEM, FORMAT_MESSAGE_IGNORE_INSERTS)
local function error_string()
local code = ffi.C.GetLastError()
local numout = ffi.C.FormatMessageA(error_flags, nil, code, 0, error_str, 1023, nil)
local err = numout ~= 0 and ffi.string(error_str, numout)
if err and err:sub(-2) == "\r\n" then
return err:sub(0, -3)
end
return err
end
local data = ffi.new("goluwa_find_data[1]")
function fs.find(dir)
if dir:sub(-1) ~= "/" then
dir = dir .. "/"
end
local handle = ffi.C.FindFirstFileA(dir .. "*", data)
if handle == nil then
return nil, error_string()
end
local out = {}
if ffi.cast("unsigned long", handle) ~= 0xffffffff then
local i = 1
repeat
local name = ffi.string(data[0].cFileName)
if name ~= "." and name ~= ".." then
out[i] = name
i = i + 1
end
until not ffi.C.FindNextFileA(handle, data)
ffi.C.FindClose(handle)
end
return out
end
function fs.getcd()
local buffer = ffi.new("char[260]")
local length = ffi.C.GetCurrentDirectoryA(260, buffer)
return ffi.string(buffer, length)
end
function fs.setcd(path)
if ffi.C.SetCurrentDirectoryA(path) then
return true
end
return nil, error_string()
end
function fs.createdir(path)
if ffi.C.CreateDirectoryA(path, nil) then
return true
end
return nil, error_string()
end
local flags = {
archive = 0x20, -- A file or directory that is an archive file or directory. Applications typically use this attribute to mark files for backup or removal .
compressed = 0x800, -- A file or directory that is compressed. For a file, all of the data in the file is compressed. For a directory, compression is the default for newly created files and subdirectories.
device = 0x40, -- This value is reserved for system use.
directory = 0x10, -- The handle that identifies a directory.
encrypted = 0x4000, -- A file or directory that is encrypted. For a file, all data streams in the file are encrypted. For a directory, encryption is the default for newly created files and subdirectories.
hidden = 0x2, -- The file or directory is hidden. It is not included in an ordinary directory listing.
integrity_stream = 0x8000, -- The directory or user data stream is configured with integrity (only supported on ReFS volumes). It is not included in an ordinary directory listing. The integrity setting persists with the file if it's renamed. If a file is copied the destination file will have integrity set if either the source file or destination directory have integrity set.
normal = 0x80, -- A file that does not have other attributes set. This attribute is valid only when used alone.
not_content_indexed = 0x2000, -- The file or directory is not to be indexed by the content indexing service.
no_scrub_data = 0x20000, -- The user data stream not to be read by the background data integrity scanner (AKA scrubber). When set on a directory it only provides inheritance. This flag is only supported on Storage Spaces and ReFS volumes. It is not included in an ordinary directory listing.
offline = 0x1000, -- The data of a file is not available immediately. This attribute indicates that the file data is physically moved to offline storage. This attribute is used by Remote Storage, which is the hierarchical storage management software. Applications should not arbitrarily change this attribute.
readonly = 0x1, -- A file that is read-only. Applications can read the file, but cannot write to it or delete it. This attribute is not honored on directories. For more information, see You cannot view or change the Read-only or the System attributes of folders in Windows Server 2003, in Windows XP, in Windows Vista or in Windows 7.
reparse_point = 0x400, -- A file or directory that has an associated reparse point, or a file that is a symbolic link.
sparse_file = 0x200, -- A file that is a sparse file.
system = 0x4, -- A file or directory that the operating system uses a part of, or uses exclusively.
temporary = 0x100, -- A file that is being used for temporary storage. File systems avoid writing data back to mass storage if sufficient cache memory is available, because typically, an application deletes a temporary file after the handle is closed. In that scenario, the system can entirely avoid writing the data. Otherwise, the data is written after the handle is closed.
virtual = 0x10000, -- This value is reserved for system use.
}
local function flags_to_table(bits)
local out = {}
for k,v in pairs(flags) do
out[k] = bit.bor(bits, v) == v
end
return out
end
local COMBINE = function(hi, lo) return bit.band(bit.lshift(hi, 8), lo) end
local info = ffi.new("goluwa_file_attributes[1]")
function fs.getattributes(path)
if ffi.C.GetFileAttributesExA(path, 0, info) then
local info = {
creation_time = COMBINE(info[0].ftCreationTime.high, info[0].ftCreationTime.low),
last_accessed = COMBINE(info[0].ftLastAccessTime.high, info[0].ftLastAccessTime.low),
last_modified = COMBINE(info[0].ftLastWriteTime.high, info[0].ftLastWriteTime.low),
last_changed = -1, -- last permission changes
size = info[0].nFileSizeLow,--COMBINE(info[0].nFileSizeLow, info[0].nFileSizeHigh),
type = bit.band(
info[0].dwFileAttributes, flags.directory
) == flags.directory and "directory" or "file",
}
return info
end
return nil, error_string()
end
return fs end)();
elseif PLATFORM == "gmod" then
fs =
(function(...) local file_Find = gmod.file.Find
local file_Exists = gmod.file.Exists
local file_Size = gmod.file.Size
local file_IsDir = gmod.file.IsDir
local file_CreateDir = gmod.file.CreateDir
local file_Time = gmod.file.Time
local GoluwaToGmodPath = GoluwaToGmodPath
local fs = {}
local dprint = function(...) if DEBUG then gmod.print("[goluwa] fs: ", ...) end end
fs.find_cache = {}
fs.get_attributes_cache = {}
function fs.uncache(path)
if path:sub(-1) == "/" then
path = path:sub(0, -2)
end
dprint("uncaching " .. path)
fs.find_cache[path:match("(.+/)")] = nil
fs.get_attributes_cache[path] = nil
end
function fs.find(path)
dprint("fs.find: ", path)
if path:startswith("/") then
path = path:sub(2)
end
local original_path = path
dprint("fs.find: is " .. path .. " cached?")
if fs.find_cache[path] then
dprint("yes!")
return fs.find_cache[path]
else
dprint("no")
end
if path:sub(-1) == "/" then
path = path .. "*"
end
local where = "GAME"
if path:startswith("data/") then
path = path:sub(6)
where = "DATA"
end
local out
dprint("fs.find: file.Find("..path..", "..where..")")
local files, dirs = file_Find(path, where)
dprint(files, dirs)
if files then
if where == "DATA" then
for i, name in ipairs(files) do
local new_name, count = name:gsub("%^", "%.")
if count > 0 then
files[i] = new_name:sub(0, -5)
end
end
end
table.add(files, dirs)
out = files
dprint("found " .. #out .. " files and folders")
end
fs.find_cache[original_path] = out
dprint("fs.find: caching results for dir " .. path)
return out or {}
end
function fs.getcd()
dprint("fs.getcd")
return ""
end
function fs.setcd(path)
dprint("fs.setcd: ", path)
end
function fs.createdir(path)
dprint("fs.createdir: ", path)
fs.uncache(path)
local path, where = GoluwaToGmodPath(path)
file_CreateDir(path, where)
if file_IsDir(path, where) then
return true
end
return nil, "file.IsDir returns false"
end
function fs.getattributes(path)
dprint("fs.getattributes: ", path)
local cache_key = path
if cache_key:sub(-1) == "/" then cache_key = cache_key:sub(0, -2) end
if fs.get_attributes_cache[cache_key] ~= nil then
dprint("\twas cached")
return fs.get_attributes_cache[cache_key]
end
local path, where = GoluwaToGmodPath(path)
if file_Exists(path, where) then
dprint("\tfile exists")
local size = path == "" and 0 or file_Size(path, where)
local time = file_Time(path, where)
local type = file_IsDir(path, where) and "directory" or "file"
dprint("\t", size)
dprint("\t", time)
dprint("\t", type)
local res = {
creation_time = time,
last_accessed = time,
last_modified = time,
last_changed = time,
size = size,
type = type,
}
fs.get_attributes_cache[cache_key] = res
return res
else
dprint("\t" .. path .. " " .. where .. " does not exist")
end
fs.get_attributes_cache[cache_key] = false
return false
end
return fs end)();
elseif PLATFORM == "unknown" then
fs =
(function(...) local fs = _G.fs or {}
function fs.find(name)
local out = {}
return out
end
function fs.getcd()
return "."
end
function fs.setcd(path)
end
function fs.createdir(path)
end
function fs.getattributes(path)
return false
end
return fs
end)();
end
package.loaded.fs = fs
-- create constants
e.BIN_FOLDER = fs.getcd() .. "/"
e.ROOT_FOLDER = e.BIN_FOLDER:match("(.+/)" .. (".-/"):rep(select(2, relative_root:gsub("/", ""))))
e.CORE_FOLDER = e.ROOT_FOLDER .. e.INTERNAL_ADDON_NAME .. "/"
e.DATA_FOLDER = e.ROOT_FOLDER .. "data/"
e.USERDATA_FOLDER = e.DATA_FOLDER .. "users/" .. e.USERNAME:lower() .. "/"
for k,v in pairs(e) do print(k,v) end
fs.createdir(e.DATA_FOLDER)
fs.createdir(e.DATA_FOLDER .. "users/")
fs.createdir(e.USERDATA_FOLDER)
end
-- standard library extensions
(function(...) local out = {
write = function(t, ...) log(...) end,
close = function(t) end,
flush = function(t) end,
}
pcall(function()
jit.vmdef = require("jit.vmdef")
jit.util = require("jit.util")
local old = jit.util.funcinfo
function jit.util.funcinfo(...)
local t = old(...)
if t.loc and t.source and t.source:startswith("@") then
t.loc = t.source:sub(2) .. ":" .. t.loc:match(".+:(.+)")
end
return t
end
jit.profiler = require("jit.profile")
jit.bc = require("jit.bc")
jit.v = require("jit.v")
jit.opt = require("jit.opt")
jit.dump = require("jit.dump")
jit.dis = require("jit.dis_x64")
end)
function jit.dumpinfo(cb, output)
local old = jit.getoptions().hotloop
jit.opt.start("hotloop=1")
jit.dump.on("tbimrsXaT", output)
local ok, err = pcall(function() cb()cb()cb()cb() end) -- uhhh
jit.dump.off()
jit.opt.start("hotloop="..old)
if not ok then logn(err) end
end
function jit.dumpbytecode(func)
jit.bc.dump(func, dummy_file, true)
end
function jit.debug(b)
if b then
jit.v.on()
else
jit.v.off()
end
end
do
local current = {
-- maximum number of traces in the cache
-- default = 1000
-- min = 1
-- max = 65535
maxtrace = 65535,
-- maximum number of recorded IR instructions
-- default = 4000
maxrecord = 20000,
-- maximum number of IR constants of a trace
-- default = 500
maxirconst = 2500,
-- maximum number of side traces of a root trace
-- default = 100
maxside = 100,
-- maximum number of snapshots for a trace
-- default = 500
maxsnap = 800,
-- minimum number of IR ins for a stitched trace.
-- default = 0
minstitch = 0,
-- number of iterations to detect a hot loop or hot call
-- default = 56
hotloop = 56,
-- number of taken exits to start a side trace
-- default = 10
hotexit = 10,
-- number of attempts to compile a side trace
-- default = 4
tryside = 4,
-- maximum unroll factor for instable loops
-- default = 4
instunroll = 500,
-- maximum unroll factor for loop ops in side traces
-- default = 15
loopunroll = 500,
-- maximum unroll factor for pseudo-recursive calls
-- default = 3
callunroll = 500,
-- minimum unroll factor for true recursion
-- default = 2
recunroll = 2,
-- maximum total size of all machine code areas in KBytes
-- default = 512
maxmcode = 8192,
--sizemcode = X64 and 64 or 32, -- Size of each machine code area in KBytes (Windows: 64K)
-- Constant Folding, Simplifications and Reassociation
fold = true,
-- Common-Subexpression Elimination
cse = true,
-- Dead-Code Elimination
dce = true,
-- Narrowing of numbers to integers
narrow = true,
-- Loop Optimizations (code hoisting)
loop = true,
-- Load Forwarding (L2L) and Store Forwarding (S2L)
fwd = true,
-- Dead-Store Elimination
dse = true,
-- Array Bounds Check Elimination
abc = true,
-- Allocation/Store Sinking
sink = true,
-- Fusion of operands into instructions
fuse = true,
}
if current.minstitch then
if jit.version:find("LuaJIT 2.0", nil, true) then
current.minstitch = nil
end
end
function jit.getoptions()
return current
end
local last = {}
local function update()
local options = {}
for k, v in pairs(current) do
if type(v) == "number" then
table.insert(options, k .. "=" .. v)
elseif type(v) == "boolean" then
table.insert(options, v and ("+" .. k) or ("-" .. k))
end
end
jit.opt.start(unpack(options))
jit.flush()
end
function jit.setoption(option, val)
if current[option] == nil then error("not a valid option", 2) end
current[option] = val
if last[option] ~= val then
logn("jit option ", option, " = ", val)
update()
last[option] = val
end
end
end
pcall(function()
local loom = loadfile("../../../"..e.INTERNAL_ADDON_NAME.."/lua/modules/loom.lua")()
package.preload["jit.loom"] = function() return loom end
jit.loom = require("jit.loom")
local html_template = [[<!DOCTYPE html>
<html lang="en">
{@ traces, funcs}
{%
local loom = require 'jit.loom'
local function class(t)
if not t then return '' end
o = {}
for k, v in pairs(t) do
if v then o[#o+1] = k end
end
if #o == 0 then return '' end
return 'class="'..table.concat(o, ' ')..'"'
end
local _ft_, _fndx_ = {}, 0
local function funclabel(f)
if not f then return '' end
if _ft_[f] == nil then
_fndx_ = _fndx_+1
_ft_[f] = ('fn%03d'):format(_fndx_)
end
return _ft_[f]
end
local function lines(s)
s = s or ''
local o = {}
for l in s:gmatch('[^\r\n]+') do
o[#o+1] = l
end
return o
end
local function cols(s, cwl)
local o, start = {}, 1
for i, w in ipairs(cwl) do
o[i] = s:sub(start, start+w-1):gsub('%s+$', '')
start = start+w
end
return o
end
local function is_irref(f)
if f:match('^%d%d%d%d$') then
return 'ref_'..f
end
end
local function all_refs(s)
c = {}
for ref in s:gmatch('%d+') do
c[#c+1] = is_irref(ref)
end
return table.concat(c, ' ')
end
local function table_ir(txt)
local o = lines(txt)
local cwl = {5, 6, 3, 4, 7, 6, 1000}
for i, l in ipairs(o) do
l = cols(l, cwl)
local class = {is_irref(l[1])}
if l[5] == 'SNAP' then
class[#class+1] = 'snap_'..l[6]:sub(2)
l.title = l[7]
l[7] = ('<span class="opt">%s</span>'):format(l[7])
end
l.class = next(class) and table.concat(class, ' ')
o[i] = l
end
return o
end
local function annot_mcode(txt)
if type(txt) ~= 'string' then return '' end
txt = txt:gsub('%(exit (%d+)/(%d+)%)', function (a, b)
a, b = tonumber(a), tonumber(b)
return ('(exit %d/%d [n=%d])'):format(a, b, traces[a].exits[b] or 0)
end)
txt = _e(txt)
txt = txt:gsub('Trace #(%d+)', function (tr)
return ('<span class="tr%03d">Trace #%d</span>'):format(
tr, tr)
end)
return txt
end
local cmdline = ''
do
local minarg, maxarg = 1000,-1000
for k in pairs(arg) do
if type(k) == 'number' then
minarg = math.min(k, minarg)
maxarg = math.max(k, maxarg)
end
end
local newarg = {}
for i = minarg, maxarg do
local v = tostring(arg[i])
if v:find('[^%w.,/=_-]') then
v = ('%q'):format(v)
end
newarg[i] = v
end
cmdline = table.concat(newarg, ' ', minarg, maxarg)
end
local annotated = loom.annotated(funcs, traces)
%}
<head>
<meta charset="utf-8" />
<style media="screen" type="text/css">
.code {
font-family: monospace;
white-space: pre;
tab-size: 4;
}
.opt { display: none; }
.codespan {
width: 100%;
}
.bordertop td {
border-top: thin lightgray solid;
}
.phantom {
color: #ccc;
}
.white {
background-color: white;
}
.hilight {
background-color: lightsteelblue;
}
{% for f, fi in pairs(funcs) do %}.{{funclabel(f)}} {
background-color: hsla({{math.random(360)}}, 100%, 90%, 1);
}
{% end %}
{% for i = 1, table.maxn(traces) do %}{{:'.tr%03d', i}} {
background-color: hsla({{math.random(360)}}, 80%, 75%, 1);
}
{% end %}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script>
var sameref = function (elm, parent) {
var refmatch = elm.className.match(/ref_\d+/);
return refmatch ? $(elm).closest(parent).find('.'+refmatch[0])
: $();
return $(elm).closest(parent).find('.'+refclass);
}
$(function() {
$('th.bc').click(function(e) {
$('.bc .opt').toggle();
});
$('th.ir').click(function(e) {
$('.ir .opt').toggle();
});
$('th.titlebar').click(function(e) {
$(e.target).closest('tr').siblings().toggle();
});
$('[class^="ref_"]').mouseenter(function(e) {
sameref(e.target, '.ir').addClass('hilight');
}).mouseleave(function(e){
sameref(e.target, '.ir').removeClass('hilight');
});
});
</script>
<title>{{cmdline}}</title>
</head>
<body>
<h2>{{=cmdline:gsub('\\[\r\n]+',"<br/>")}}</h2>
<table class="code" cellpadding="0" cellspacing="0">
{% for filename, filedata in pairs(annotated) do
local lastline
%}
<tr>
<th colspan="2">{{ filename }}</th>
<th colspan="3">Bytecode</th>
</th>
{% for i, l in loom.sortedpairs(filedata) do
local notsame = l.i ~= lastline
lastline = l.i
%}
<tr {{= class{bordertop=notsame and l.bc ~= ''} }}>
<td {{= class{phantom=l.back} }}> {{ notsame and l.i or '' }} </td>
<td {{= class{phantom=l.back} }}> {{ notsame and l.src or '' }} </td>
<td {{= class{[funclabel(l.func)] = l.bc ~= ''} }}> {{ l.bc }} </td>
<td>{% for i, tr in ipairs(l.tr or {}) do
local trref = ('tr%03d'):format(tr[1])
local lnref = ('tr%03d_%03d'):format(tr[1], tr[2])
%} <a href="#{{trref}}" name="{{lnref}}"><span
id="{{lnref}}"
class="{{trref}}"
>{{tr[1]}}/{{tr[2]}}</span></a> {%
end %}</td>
<td>{% for msg, n in pairs(l.evt or {}) do
%} <span>"{{msg}}" [n={{n}}]</span> {%
end %}</td>
</tr>
{% end %}
{% end %}
</table>
{% for i, tr in loom.allipairs(traces) do local prevsrc%}
<br/>
<a name="#{{:'tr%0dd', i}}"><table class="popup trace {{:'tr%03d', i}}" id="{{:'tr%03d', i}}" cellpadding="4">
<tr>
<th colspan="3" class="titlebar">Trace #{{i}}: {{tr.tracelabel}}</th>
</tr>
<tr class="white">
<th class="bc" >bytecode</th>
<th class="ir" >IR</th>
<th class="mcode" >mcode</th>
</tr>
<tr class="white" valign="top">
<td class="code bc"><table cellpadding="0" cellspacing="0">
{% for j, rec in ipairs(tr.rec) do
local f, pc, l, src = unpack(rec)
local srcline = src and ('%s:%d %s'):format(src.name, src.i, src.l or '')
local lnref = ('tr%03d_%03d'):format(i, j)
%}
<tr class="code">
<td class="{{:'tr%03d', i}}"><a href="#{{lnref}}">{{i}}/{{j}}</a> </td>
<td class="{{funclabel(f)}}"> {{l}} </td>
<td class="src opt">{{srcline ~= prevsrc and srcline or ''}}</td>
</tr>
{% prevsrc = srcline
end %}
</table></td>
<td class="code ir"><table>
{%for _, l in ipairs(table_ir(tr.ir)) do %}
<tr class="{{=l.class or ''}}" title="{{=l.title}}">{% for _, f in ipairs(l) do %}
<td class="{{=all_refs(f)}}">{{=f}}</td>
{% end %}</tr>
{% end %}
</table></td>
<td class="code mcode">{{=annot_mcode(tr.mcode)}}</td>
</tr>
</table></a>
{% end %}
</body>
</html>
]]
local dot_template = [[{@ traces, funcs}
{%
local loom = require 'jit.loom'
local funcinfo = require ('jit.util').funcinfo
local nodeshape = {
none = 'shape=none',
root = 'shape=ellipse',
loop = 'penwidth=3.0',
['tail-recursion'] = 'shape=diamond',
['up-recursion'] = 'shape=house',
['down-recursion'] = 'ishape=nvhouse',
interpreter = 'shape=egg',
['return'] = 'shape=parallelogram',
stitch = 'shape=Mdiamond',
}
%}
digraph G {
{% for i, tr in loom.allipairs(traces) do
local entryinfo = funcinfo(tr.rec[1][1], tr.rec[1][2])
local shape = nodeshape[tr.info.linktype] or 'circle' %}
T{{ i }} [{{ shape }}, label="#{{ i }}\n{{ entryinfo.loc }}", href="#tr{{ i }}"]
{% end %}
{% for i, tr in loom.allipairs(traces) do %}
{% if false and tr.info.linktype == 'loop' then %}
T{{ i }} -> T{{ i }};
{% end %}
{% if tr.parent and tr.p_exit and tr.p_exit>0 then %}
T{{ tr.parent }} -> T{{ i }} [taillabel=">{{ tr.p_exit }}"];
{% end %}
{% if tonumber(tr.info.link) then %}
T{{ i }} -> T{{ tr.info.link }} [dir=both, arrowtail=odot];
{% end %}
{% end %}
}]]
local tmpl
function loom.start2(type, clear)
_G.arg = {}
tmpl = loom.template(type == "html" and html_template or dot_template)
loom.on(clear)
--_G.arg = nil
end
function loom.stop()
_G.arg = {}
local res = loom.off(tmpl)
--_G.arg = nil
return res
end
end)
if not jit.tracebarrier then
jit.tracebarrier = function() debug.gethook() end
end end)();
(function(...) do
local rawset = rawset
local rawget = rawget
local getmetatable = getmetatable
local newproxy = newproxy
if pcall(require, "table.gcnew") then
local function gc(s)
local tbl = s.tbl
rawset(tbl, "__gc_proxy", nil)
local new_meta = getmetatable(tbl)
if new_meta then
local __gc = rawget(new_meta, "__gc")
if __gc then
__gc(tbl)
end
end
end
-- 52 compat
function setmetatable(tbl, meta)
if meta and rawget(meta, "__gc") and not rawget(tbl, "__gc_proxy") then
local proxy = _OLD_G.setmetatable(table.gcnew(), {__gc = gc})
proxy.tbl = tbl
rawset(tbl, "__gc_proxy", proxy)
end
return _OLD_G.setmetatable(tbl, meta)
end
else
local function gc(s)
local tbl = getmetatable(s).__div
rawset(tbl, "__gc_proxy", nil)
local new_meta = getmetatable(tbl)
if new_meta then
local __gc = rawget(new_meta, "__gc")
if __gc then
__gc(tbl)
end
end
end
-- 52 compat
function setmetatable(tbl, meta)
if meta and rawget(meta, "__gc") and not rawget(tbl, "__gc_proxy") then
local proxy = newproxy(true)
rawset(tbl, "__gc_proxy", proxy)
getmetatable(proxy).__div = tbl
getmetatable(proxy).__gc = gc
end
return _OLD_G.setmetatable(tbl, meta)
end
end
end
do -- logging
local pretty_prints = {}
pretty_prints.table = function(t)
local str = tostring(t) or "nil"
str = str .. " [" .. table.count(t) .. " subtables]"
-- guessing the location of a library
local sources = {}
for _, v in pairs(t) do
if type(v) == "function" then
local src = debug.getinfo(v).source
sources[src] = (sources[src] or 0) + 1
end
end
local tmp = {}
for k, v in pairs(sources) do
table.insert(tmp, {k = k, v = v})
end
table.sort(tmp, function(a,b) return a.v > b.v end)
if #tmp > 0 then
str = str .. "[" .. tmp[1].k:gsub("!/%.%./", "") .. "]"
end
return str
end
local function tostringx(val)
local t = (typex or type)(val)
return pretty_prints[t] and pretty_prints[t](val) or tostring(val)
end
local function tostring_args(...)
local copy = {}
for i = 1, select("#", ...) do
table.insert(copy, tostringx(select(i, ...)))
end
return copy
end
local function formatx(str, ...)
local copy = {}
local i = 1
for arg in str:gmatch("%%(.)") do
arg = arg:lower()
if arg == "s" then
table.insert(copy, tostringx(select(i, ...)))
else
table.insert(copy, (select(i, ...)))
end
i = i + 1
end
return string.format(str, unpack(copy))
end
local base_log_dir = e.USERDATA_FOLDER .. "logs/"
local log_files = {}
local log_file
function getlogpath(name)
name = name or "console"
return base_log_dir .. name .. "_" .. jit.os:lower() .. ".txt"
end
function setlogfile(name)
name = name or "console"
if not log_files[name] then
local file = assert(io.open(getlogpath(name), "w"))
log_files[name] = file
end
log_file = log_files[name]
end
function getlogfile(name)
name = name or "console"
return log_files[name]
end
local last_line
local count = 0
local last_count_length = 0
require("fs").createdir(base_log_dir)
local suppress_print = false
local function can_print(str)
if suppress_print then return end
if event then
suppress_print = true
if event.Call("ReplPrint", str) == false then
suppress_print = false
return false
end
suppress_print = false
end
return true
end
local silence
local function raw_log(args, sep, append)
if silence then return end
local line = type(args) == "string" and args or table.concat(args, sep)
if append then
line = line .. append
end
if vfs then
if not log_file then
setlogfile()
end
if line == last_line then
if count > 0 then
local count_str = ("[%i x] "):format(count)
log_file:seek("cur", -#line-1-last_count_length)
log_file:write(count_str, line)
last_count_length = #count_str
end
count = count + 1
else
log_file:write(line)
count = 0
last_count_length = 0
end
log_file:flush()
last_line = line
end
if log_files.console == log_file then
if repl and repl.Print and repl.curses_init then
repl.Print(line)
elseif can_print(line) then
io.write(line)
end
end
end
function silence_log(b)
silence = b
end
function log(...)
raw_log(tostring_args(...), "")
return ...
end
function logn(...)
raw_log(tostring_args(...), "", "\n")
return ...
end
function print(...)
raw_log(tostring_args(...), ",\t", "\n")
return ...
end
function logf(str, ...)
raw_log(formatx(str, ...), "")
return ...
end
function errorf(str, level, ...)
error(formatx(str, ...), level)
end
function logsection(type, b)
event.Call("LogSection", type, b)
end
do
local level = 1
function logsourcelevel(n)
if n then
level = n
end
return level
end
end
-- library log
function llog(fmt, ...)
fmt = tostringx(fmt)
local level = tonumber(select(fmt:count("%") + 1, ...) or 1) or 1
local source = debug.getprettysource(level + 1, false, true)
local main_category = source:match(".+/libraries/(.-)/")
local sub_category = source:match(".+/libraries/.-/(.-)/") or source:match(".+/(.-)%.lua")
if sub_category == "libraries" then
sub_category = source:match(".+/libraries/(.+)%.lua")
end
local str = fmt:safeformat(...)
if not main_category or not sub_category or main_category == sub_category then
return logf("[%s] %s\n", main_category or sub_category, str)
else
return logf("[%s][%s] %s\n", main_category, sub_category, str)
end
return str
end
-- warning log
function wlog(fmt, ...)
fmt = tostringx(fmt)
local level = tonumber(select(fmt:count("%") + 1, ...) or 1) or 1
local str = fmt:safeformat(...)
local source = debug.getprettysource(level + 1, true)
logn(source, ": ", str)
return fmt, ...
end
end
do
local luadata
function fromstring(str)
local num = tonumber(str)
if num then return num end
luadata = luadata or serializer.GetLibrary("luadata")
return unpack(luadata.Decode(str, true)) or str
end
end
function vprint(...)
logf("%s:\n", debug.getinfo(logsourcelevel() + 1, "n").name or "unknown")
for i = 1, select("#", ...) do
local name = debug.getlocal(logsourcelevel() + 1, i)
local arg = select(i, ...)
logf("\t%s:\n\t\ttype: %s\n\t\tprty: %s\n", name or "arg" .. i, type(arg), tostring(arg), serializer.Encode("luadata", arg))
if type(arg) == "string" then
logn("\t\tsize: ", #arg)
end
if typex(arg) ~= type(arg) then
logn("\t\ttypx: ", typex(arg))
end
end
end
function desire(name)
local ok, res = pcall(require, name)
if not ok then
wlog("unable to require %s:\n\t%s", name, res, 2)
return nil, res
end
if not res and package.loaded[name] then
return package.loaded[name]
end
return res
end
do -- nospam
local last = {}
function logf_nospam(str, ...)
local str = string.format(str, ...)
local t = system.GetElapsedTime()
if not last[str] or last[str] < t then
logn(str)
last[str] = t + 3
end
end
function logn_nospam(...)
logf_nospam(("%s "):rep(select("#", ...)), ...)
end
end
do -- wait
local temp = {}
function wait(seconds)
local time = system.GetElapsedTime()
if not temp[seconds] or (temp[seconds] + seconds) <= time then
temp[seconds] = system.GetElapsedTime()
return true
end
return false
end
end
local idx = function(var) return var.Type end
function hasindex(var)
if getmetatable(var) == getmetatable(NULL) then return false end
local T = type(var)
if T == "string" then
return false
end
if T == "table" then
return true
end
if not pcall(idx, var) then return false end
local meta = getmetatable(var)
if meta == "ffi" then return true end
T = type(meta)
return T == "table" and meta.__index ~= nil
end
function typex(var)
local t = type(var)
if
t == "nil" or
t == "boolean" or
t == "number" or
t == "string" or
t == "userdata" or
t == "function" or
t == "thread"
then
return t
end
local ok, res = pcall(idx, var)
if ok and res then
return res
end
return t
end
function istype(var, t)
if
t == "nil" or
t == "boolean" or
t == "number" or
t == "string" or
t == "userdata" or
t == "function" or
t == "thread" or
t == "table" or
t == "cdata"
then
return type(var) == t
end
return typex(var) == t
end
local pretty_prints = {}
pretty_prints.table = function(t)
local str = tostring(t)
str = str .. " [" .. table.count(t) .. " subtables]"
-- guessing the location of a library
local sources = {}
for _, v in pairs(t) do
if type(v) == "function" then
local src = debug.getinfo(v).source
sources[src] = (sources[src] or 0) + 1
end
end
local tmp = {}
for k, v in pairs(sources) do
table.insert(tmp, {k = k, v = v})
end
table.sort(tmp, function(a,b) return a.v > b.v end)
if #tmp > 0 then
str = str .. "[" .. tmp[1].k:gsub("!/%.%./", "") .. "]"
end
return str
end
pretty_prints["function"] = function(self)
return ("function[%p][%s](%s)"):format(self, debug.getprettysource(self, true), table.concat(debug.getparams(self), ", "))
end
function tostringx(val)
local t = type(val)
if t == "table" and getmetatable(val) then return tostring(val) end
return pretty_prints[t] and pretty_prints[t](val) or tostring(val)
end
function tostring_args(...)
local copy = {}
for i = 1, select("#", ...) do
table.insert(copy, tostringx(select(i, ...)))
end
return copy
end
function istype(var, ...)
for _, str in pairs({...}) do
if typex(var) == str then
return true
end
end
return false
end
do -- negative pairs
local v
local function iter(a, i)
i = i - 1
v = a[i]
if v then
return i, v
end
end
function npairs(a)
return iter, a, #a + 1
end
end
function rpairs(tbl)
local sorted = {}
for key, val in pairs(tbl) do
table.insert(sorted, {key = key, val = val, rand = math.random()})
end
table.sort(sorted, function(a,b) return a.rand > b.rand end)
local i = 0
return function()
i = i + 1
if sorted[i] then
return sorted[i].key, sorted[i].val--, sorted[i].rand
end
end
end
function spairs(tbl, desc)
local sorted = {}
for key, val in pairs(tbl) do
table.insert(sorted, {key = key, val = val})
end
if desc then
table.sort(sorted, function(a,b) return a.key > b.key end)
else
table.sort(sorted, function(a,b) return a.key < b.key end)
end
local i = 0
return function()
i = i + 1
if sorted[i] then
return sorted[i].key, sorted[i].val--, sorted[i].rand
end
end
end end)();
(function(...)
do
local file
local max_lines = 10000
function debug.loglines(b)
if b == nil then b = not file end
if b then
local path = R"data/" .. "debug_lines.lua"
file = assert(io.open(path, "wb"))
jit.off()
jit.flush()
local i = 0
debug.sethook(function()
if not file then debug.sethook() return end
local info = debug.getinfo(2)
if i > max_lines then
file:close()
file = assert(io.open(path, "wb"))
i = 0
end
file:write(info.source, ":", info.currentline, "\n")
file:flush()
i = i + 1
end, "l")
else
if file then file:close() file = nil end
jit.on()
jit.flush()
debug.sethook()
end
end
end
function debug.getsource(func)
local info = debug.getinfo(func)
local src = vfs.Read(e.ROOT_FOLDER .. "/" .. info.source:sub(2))
if not src then
src = vfs.Read(info.source:sub(2))
end
if src then
local lines = src:split("\n")
local str = {}
for i = info.linedefined, info.lastlinedefined do
table.insert(str, lines[i])
end
return table.concat(str, "\n")
end
return "source unavailble for: " .. info.source
end
function debug.getprettysource(level, append_line, full_folder)
local info = debug.getinfo(type(level) == "number" and (level + 1) or level)
local pretty_source = "debug.getinfo = nil"
if info then
if info.source:sub(1, 1) == "@" then
pretty_source = info.source:sub(2)
if not full_folder and vfs then
pretty_source = vfs.FixPathSlashes(pretty_source:replace(e.ROOT_FOLDER, ""))
end
if append_line then
local line = info.currentline
if line == -1 then
line = info.linedefined
end
pretty_source = pretty_source .. ":" .. line
end
else
pretty_source = info.source:sub(0, 25)
if pretty_source ~= info.source then
pretty_source = pretty_source .. "...(+"..#info.source - #pretty_source.." chars)"
end
end
end
return pretty_source
end
do
local started = {}
function debug.loglibrary(library, filter, post_calls_only, lib_name)
if type(library) == "string" then
lib_name = library
library = _G[library]
end
if not lib_name then
for k,v in pairs(_G) do
if v == library then
lib_name = k
end
end
if not lib_name then
for k,v in pairs(package.loaded) do
if v == library then
lib_name = k
end
end
end
lib_name = lib_name or "unknown"
end
local log_name = lib_name .. "_calls"
local arg_line
if started[library] then
for name, func in pairs(started[library]) do
library[name] = func
end
else
if type(filter) == "string" then
filter = {filter}
end
filter = filter or {}
for _, v in pairs(filter) do filter[v] = true end
started[library] = {}
local function log_return(...)
if not filter[name] then
if (post_calls_only or post_calls_only == nil) and arg_line then
local ret = {}
for i = 1, select("#", ...) do
table.insert(ret, serializer.GetLibrary("luadata").ToString((select(i, ...))):sub(0,20))
end
if #ret ~= 0 then
logf("%s = %s\n", table.concat(ret, ", "), arg_line)
else
logn(arg_line)
end
arg_line = nil
end
end
setlogfile()
return ...
end
for name, func in pairs(library) do
if type(func) == "function" or type(func) == "cdata" then
library[name] = function(...)
setlogfile(log_name)
if not post_calls_only and not filter[name] then
local args = {}
for i = 1, select("#", ...) do
table.insert(args, serializer.GetLibrary("luadata").ToString((select(i, ...))))
end
arg_line = ("%s.%s(%s) "):format(lib_name, name, table.concat(args, ", "):sub(0,100))
end
return log_return(func(...))
end
started[library][name] = func
end
end
end
end
end
function debug.trace(skip_print)
local lines = {}
for level = 1, math.huge do
local info = debug.getinfo(level, "Sln")
if info then
lines[#lines + 1] = ("%i: Line %d\t\"%s\"\t%s"):format(level, info.currentline, info.name or "unknown", info.source or "")
else
break
end
end
local str
if debug.debugging then
str = {}
-- this doesn't really be long here..
local stop = #lines
for i = 2, #lines do
if lines[i]:find("event") then
stop = i - 2
end
end
for i = 2, stop do
table.insert(str, lines[i])
end
else
str = lines
end
str = table.concat(str, "\n")
if not skip_print then
logn(str)
end
return str
end
function debug.getparams(func)
local params = {}
for i = 1, math.huge do
local key = debug.getlocal(func, i)
if key then
table.insert(params, key)
else
break
end
end
return params
end
function debug.getparamsx(func)
local params = {}
for i = 1, math.huge do
local key, val = debug.getlocal(func, i)
if key then
table.insert(params, {key = key, val = val})
else
break
end
end
return params
end
function debug.getupvalues(func)
local params = {}
for i = 1, math.huge do
local key, val = debug.getupvalue(func, i)
if key then
table.insert(params, {key = key, val = val})
else
break
end
end
return params
end
function debug.dumpcall(level, line, info_match)
level = level + 1
local info = debug.getinfo(level)
local path = e.ROOT_FOLDER .. info.source:sub(2)
local currentline = line or info.currentline
if info_match and info.func ~= info_match.func then
return
end
if info.source == "=[C]" then return end
if info.source:find("ffi_binds") then return end
if info.source:find("console%.lua") then return end
if info.source:find("string%.lua") then return end
if info.source:find("globals%.lua") then return end
if info.source:find("strung%.lua") then return end
if path == "../../../lua/init.lua" then return end
if vfs.IsFile(path) then
local script = vfs.Read(path)
local lines = script:split("\n")
for i = -20, 20 do
local line = lines[currentline + i]
if line then
line = line:gsub("\t", " ")
if i == 0 then
line = (currentline + i) .. ":==>\t" .. line
else
line = (currentline + i) .. ":\t" .. line
end
logn(line)
else
if i == 0 then
line = (">"):rep(string.len(currentline)) .. ":"
else
line = (currentline + i) .. ":"
end
logn(line, " ", "This line does not exist. It may be due to inlining so try running jit.off()")
end
end
end
logn(path)
logn("LOCALS: ")
for _, data in pairs(debug.getparamsx(level+1)) do
--if not data.key:find("(",nil,true) then
local val
if type(data.val) == "table" then
val = tostring(data.val)
elseif type(data.val) == "string" then
val = data.val:sub(0, 10)
if val ~= data.val then
val = val .. " .. " .. utility.FormatFileSize(#data.val)
end
else
val = serializer.GetLibrary("luadata").ToString(data.val)
end
logf("%s = %s\n", data.key, val)
--end
end
logn(debug.traceback())
if info_match then
print(info_match.func)
print(info.func)
end
return true
end
function debug.logcalls(b, type)
if not b then
debug.sethook()
return
end
type = type or "r"
local hook
hook = function()
debug.sethook()
setlogfile("lua_calls")
logn(debug.traceback())
setlogfile()
debug.sethook(hook, type)
end
debug.sethook(hook, type)
end
end)();
(function(...) function string.buildclass(...)
local classes = {...}
local check
if type(classes[#classes]) == "function" then
check = table.remove(classes, #classes)
end
local out = ""
for i = 0, 255 do
for _, class in ipairs(classes) do
local char = string.char(i)
if char:find(class) and (not check or check(char) ~= false) then
out = out .. char
end
end
end
return out
end
function string.iswhitespace(char)
return
char == "\32" or
char == "\9" or
char == "\10" or
char == "\11" or
char == "\12"
end
function string.haswhitespace(str)
for i = 1, #str do
local b = str:byte(i)
if b == 32 or (b >= 9 and b <= 12) then
return true
end
end
end
function string.upperchar(self, pos)
return self:sub(0, pos-1) .. self:sub(pos, pos):upper() .. self:sub(pos + 1)
end
function string.wholeword(self, what)
return self:find("%f[%a%d_]"..what.."%f[^%a%d_]") ~= nil
end
function string.slice(self, what, from, offset)
offset = offset or 0
local _, pos = self:find(what, from, true)
if pos then
return self:sub(0, pos - offset), self:sub(pos + offset)
end
end
do
local vowels = {"e", "a", "o", "i", "u", "y"}
local consonants = {"t", "n", "s", "h", "r", "d", "l", "c", "m", "w", "f", "g", "p", "b", "v", "k", "j", "x", "q", "z"}
local first_letters = {"t", "a", "s", "h", "w", "i", "o", "b", "m", "f", "c", "l", "d", "p", "n", "e", "g", "r", "y", "u", "v", "j", "k", "q", "z", "x"}
function string.randomwords(word_count, seed)
word_count = word_count or 8
seed = seed or 0
local text = {}
local last_punctation = 1
local capitalize = true
for i = 1, word_count do
math.randomseed(seed + i)
local word = ""
local consonant_start = 1
local length = math.ceil((math.random()^3)*8) + math.random(2, 3)
for i = 1, length do
if i == 1 then
word = word .. first_letters[math.floor((math.random()^3) * #first_letters) + 1]
if table.hasvalue(vowels, word[i]) then
consonant_start = 0
end
elseif i%2 == consonant_start then
word = word .. consonants[math.floor((math.random()^4) * #consonants) + 1]
else
if i ~= length or math.random() < 0.25 then
word = word .. vowels[math.floor((math.random()^3) * #vowels) + 1]
end
end
if capitalize then
word = word:upper()
capitalize = false
end
end
text[i] = word
last_punctation = last_punctation + 1
if last_punctation > math.random(4,16) then
if math.random() > 0.9 then
text[i] = text[i] .. ","
else
text[i] = text[i] .. "."
capitalize = true
end
last_punctation = 1
end
text[i] = text[i] .. " "
end
return table.concat(text)
end
end
function string.random(length, min, max)
length = length or 10
min = min or 32
max = max or 126
local tbl = {}
for i = 1, length do
tbl[i] = string.char(math.random(min, max))
end
return table.concat(tbl)
end
function string.readablehex(str)
return (str:gsub("(.)", function(str) str = ("%X"):format(str:byte()) if #str == 1 then str = "0" .. str end return str .. " " end))
end
-- gsub doesn't seem to remove \0
function string.removepadding(str, padding)
padding = padding or "\0"
local new = {}
for i = 1, #str do
local char = str:sub(i, i)
if char ~= padding then
table.insert(new, char)
end
end
return table.concat(new)
end
function string.dumphex(str)
local str = str:readablehex():lower():split(" ")
local out = {}
for i, char in pairs(str) do
table.insert(out, char)
table.insert(out, " ")
if i%16 == 0 then
table.insert(out, "\n")
end
if i%16 == 4 or i%16 == 12 then
table.insert(out, " ")
end
if i%16 == 8 then
table.insert(out, " ")
end
end
table.insert(out, "\n")
return table.concat(out)
end
string.hexdump = string.dumphex
function string.endswith(a, b)
return a:sub(-#b) == b
end
function string.endswiththese(a, b)
for _, str in ipairs(b) do
if a:sub(-#str) == str then
return true
end
end
end
function string.startswith(a, b)
return a:sub(0, #b) == b
end
function string.levenshtein(a, b)
local distance = {}
for i = 0, #a do
distance[i] = {}
distance[i][0] = i
end
for i = 0, #b do
distance[0][i] = i
end
local str1 = utf8.totable(a)
local str2 = utf8.totable(b)
for i = 1, #a do
for j = 1, #b do
distance[i][j] = math.min(
distance[i-1][j] + 1,
distance[i][j-1] + 1,
distance[i-1][j-1] + (str1[i-1] == str2[j-1] and 0 or 1)
)
end
end
return distance[#a][#b]
end
function string.lengthsplit(str, len)
if #str > len then
local tbl = {}
local max = math.floor(#str/len)
for i = 0, max do
local left = i * len + 1
local right = (i * len) + len
local res = str:sub(left, right)
if res ~= "" then
table.insert(tbl, res)
end
end
return tbl
end
return {str}
end
function string.getchartype(char)
if char:find("%p") and char ~= "_" then
return "punctation"
elseif char:find("%s") then
return "space"
elseif char:find("%d") then
return "digit"
elseif char:find("%a") or char == "_" then
return "letters"
end
return "unknown"
end
local types = {
"%a",
"%c",
"%d",
"%l",
"%p",
"%u",
"%w",
"%x",
"%z",
}
function string.charclass(char)
for _, v in ipairs(types) do
if char:find(v) then
return v
end
end
end
function string.safeformat(str, ...)
str = str:gsub("%%(%d+)", "%%s")
local count = select(2, str:gsub("(%%)", ""))
if str:find("%...", nil, true) then
local temp = {}
for i = count, select("#", ...) do
table.insert(temp, tostringx(select(i, ...)))
end
str = str:replace("%...", table.concat(temp, ", "))
count = count - 1
end
if count == 0 then
return table.concat({str, ...}, "")
end
local copy = {}
for i = 1, count do
table.insert(copy, tostringx(select(i, ...)))
end
return string.format(str, unpack(copy))
end
function string.findsimple(self, find)
return self:find(find, nil, true) ~= nil
end
function string.findsimplelower(self, find)
return self:lower():find(find:lower(), nil, true) ~= nil
end
function string.compare(self, target)
return
self == target or
self:findsimple(target) or
self:lower() == target:lower() or
self:findsimplelower(target)
end
function string.trim(self, char)
if char then
char = char:patternsafe() .. "*"
else
char = "%s*"
end
local _, start = self:find(char, 0)
local end_start, end_stop = self:reverse():find(char, 0)
if start and end_start then
return self:sub(start + 1, (end_start - end_stop) - 2)
elseif start then
return self:sub(start + 1)
elseif end_start then
return self:sub(0, (end_start - end_stop) - 2)
end
return self
end
function string.getchar(self, pos)
return string.sub(self, pos, pos)
end
function string.getbyte(self, pos)
return self:getchar(pos):byte() or 0
end
function string.totable(self)
local tbl = table.new(#self, 0)
for i = 1, #self do
tbl[i] = self:sub(i, i)
end
return tbl
end
function string.split(self, separator, plain_search)
if separator == nil or separator == "" then
return self:totable()
end
if plain_search == nil then
plain_search = true
end
local tbl = {}
local current_pos = 1
for i = 1, #self do
local start_pos, end_pos = self:find(separator, current_pos, plain_search)
if not start_pos then break end
tbl[i] = self:sub(current_pos, start_pos - 1)
current_pos = end_pos + 1
end
if current_pos > 1 then
tbl[#tbl + 1] = self:sub(current_pos)
else
tbl[1] = self
end
return tbl
end
function string.count(self, what, plain)
if plain == nil then plain = true end
local count = 0
local current_pos = 1
for _ = 1, #self do
local start_pos, end_pos = self:find(what, current_pos, plain)
if not start_pos then break end
count = count + 1
current_pos = end_pos + 1
end
return count
end
function string.containsonly(self, pattern)
return self:gsub(pattern, "") == ""
end
function string.replace(self, what, with)
local tbl = {}
local current_pos = 1
local last_i
for i = 1, #self do
local start_pos, end_pos = self:find(what, current_pos, true)
if not start_pos then last_i = i break end
tbl[i] = self:sub(current_pos, start_pos - 1)
current_pos = end_pos + 1
end
if current_pos > 1 and last_i then
tbl[last_i] = self:sub(current_pos)
return table.concat(tbl, with)
end
return self
end
local pattern_escape_replacements = {
["("] = "%(",
[")"] = "%)",
["."] = "%.",
["%"] = "%%",
["+"] = "%+",
["-"] = "%-",
["*"] = "%*",
["?"] = "%?",
["["] = "%[",
["]"] = "%]",
["^"] = "%^",
["$"] = "%$",
["\0"] = "%z"
}
function string.escapepattern(str)
return (str:gsub(".", pattern_escape_replacements))
end
function string.getchar(self, pos)
return self:sub(pos, pos)
end end)();
(function(...) table.new = table.new or desire("table.new") or function() return {} end
table.clear = table.clear or desire("table.clear") or function(t) for k in pairs(t) do t[k] = nil end end
if not table.pack then
function table.pack(...)
return {
n = select("#", ...),
...
}
end
end
if not table.unpack then
function table.unpack(tbl)
return unpack(tbl)
end
end
function table.tolist(tbl, sort)
local list = {}
for key, val in pairs(tbl) do
table.insert(list, {key = key, val = val})
end
return list
end
function table.sortedpairs(tbl, sort)
local list = table.tolist(tbl)
table.sort(list, sort)
local i = 0
return function()
i = i + 1
if list[i] then
return list[i].key, list[i].val
end
end
end
function table.slice(tbl, first, last, step)
local sliced = {}
for i = first or 1, last or #tbl, step or 1 do
sliced[#sliced+1] = tbl[i]
end
return sliced
end
function table.shuffle(a, times)
times = times or 1
local c = #a
for _ = 1, c * times do
local ndx0 = math.random(1, c)
local ndx1 = math.random(1, c)
local temp = a[ndx0]
a[ndx0] = a[ndx1]
a[ndx1] = temp
end
return a
end
function table.scroll(tbl, offset)
if offset == 0 then return end
if offset > 0 then
for _ = 1, offset do
local val = table.remove(tbl, 1)
table.insert(tbl, val)
end
else
for _ = 1, math.abs(offset) do
local val = table.remove(tbl)
table.insert(tbl, 1, val)
end
end
end
-- http://stackoverflow.com/questions/6077006/how-can-i-check-if-a-lua-table-contains-only-sequential-numeric-indices
function table.isarray(t)
local i = 0
for _ in pairs(t) do
i = i + 1
if t[i] == nil then
return false
end
end
return true
end
function table.reverse(tbl)
for i = 1, math.floor(#tbl / 2) do
tbl[i], tbl[#tbl - i + 1] = tbl[#tbl - i + 1], tbl[i]
end
return tbl
end
-- 12:34 - <mniip> http://codepad.org/cLaX7lVn
function table.multiremove(tbl, locations)
if locations[1] then
local off = 0
local idx = 1
for i = 1, #tbl do
while i + off == locations[idx] do
off = off + 1
idx = idx + 1
end
tbl[i] = tbl[i + off]
end
end
return tbl
end
function table.removevalue(tbl, val)
for i,v in ipairs(tbl) do
if v == val then
table.remove(tbl, i)
break
end
end
end
function table.fixindices(tbl)
local temp = {}
for k, v in pairs(tbl) do
table.insert(temp, {v = v, k = tonumber(k) or 0})
tbl[k] = nil
end
table.sort(temp, function(a, b) return a.k < b.k end)
for k, v in ipairs(temp) do
tbl[k] = v.v
end
return temp
end
function table.hasvalue(tbl, val)
for k,v in pairs(tbl) do
if v == val then
return k
end
end
return false
end
function table.getkey(tbl, val)
for k in pairs(tbl) do
if k == val then
return k
end
end
return nil
end
function table.getindex(tbl, val)
for i, v in ipairs(tbl) do
if i == v then
return i
end
end
return nil
end
function table.removevalues(tbl, val)
local index = table.getindex(tbl, val)
while index ~= nil do
table.removevalues(tbl, index)
index = table.getindex(tbl, val)
end
end
function table.count(tbl)
local i = 0
for _ in pairs(tbl) do
i = i + 1
end
return i
end
function table.merge(a, b, merge_aray)
for k,v in pairs(b) do
if type(v) == "table" and type(a[k]) == "table" then
if merge_aray and table.isarray(a[k]) and table.isarray(v) then
local offset = #a[k]
for i = 1, #v do
a[k][i + offset] = v[i]
end
else
table.merge(a[k], v, merge_aray)
end
else
a[k] = v
end
end
return a
end
function table.add(a, b)
for _, v in pairs(b) do
table.insert(a, v)
end
end
function table.random(tbl)
local key = math.random(1, table.count(tbl))
local i = 1
for _key, _val in pairs(tbl) do
if i == key then
return _val, _key
end
i = i + 1
end
end
function table.print(...)
local tbl = {...}
local max_level
if type(tbl[1]) == "table" and type(tbl[2]) == "number" and type(tbl[3]) == "nil" then
max_level = tbl[2]
tbl[2] = nil
end
local luadata = serializer.GetLibrary("luadata")
luadata.SetModifier("function", function(var)
return ("function(%s) --[==[ptr: %p src: %s]==] end"):format(table.concat(debug.getparams(var), ", "), var, debug.getprettysource(var))
end)
luadata.SetModifier("fallback", function(var)
return "--[==[ " .. tostringx(var) .. " ]==]"
end)
logn(luadata.ToString(tbl, {tab_limit = max_level, done = {}}))
luadata.SetModifier("function", nil)
end
do
local indent = 0
function table.print2(tbl)
for k,v in pairs(tbl) do
log(("\t"):rep(indent))
if type(v) == "table" then
logn(k, ":")
indent = indent + 1
table.print2(v)
indent = indent - 1
else
local v = v
if type(v) == "string" then
v = "\"" .. v .. "\""
end
logn(k, " = ", v)
end
end
end
end
do -- table copy
local lookup_table = {}
local type = type
local pairs = pairs
local getmetatable = getmetatable
local function copy(obj, skip_meta)
local t = type(obj)
if t == "number" or t == "string" or t == "function" or t == "boolean" then
return obj
end
if ((t == "table" or (t == "cdata" and structs.GetStructMeta(obj))) and obj.__copy) then
return obj:__copy()
elseif lookup_table[obj] then
return lookup_table[obj]
elseif t == "table" then
local new_table = {}
lookup_table[obj] = new_table
for key, val in pairs(obj) do
new_table[copy(key, skip_meta)] = copy(val, skip_meta)
end
if skip_meta then
return new_table
end
local meta = getmetatable(obj)
if meta then
setmetatable(new_table, meta)
end
return new_table
end
return obj
end
function table.copy(obj, skip_meta)
table.clear(lookup_table)
return copy(obj, skip_meta)
end
end
do
local setmetatable = setmetatable
local ipairs = ipairs
local META = {}
META.__index = META
META.concat = table.concat
META.insert = table.insert
META.remove = table.remove
META.unpack = table.unpack
META.sort = table.sort
function META:pairs()
return ipairs(self)
end
function table.list(count)
return setmetatable(table.new(count or 1, 0), META)
end
end
function table.weak(k, v)
if k and v then
mode = "kv"
elseif k then
mode = "k"
elseif v then
mode = "v"
else
mode = "kv"
end
return setmetatable({__mode = mode})
end end)();
(function(...) do
local ffi = desire("ffi")
if ffi then
if WINDOWS then
ffi.cdef([[
int _putenv_s(const char *var_name, const char *new_value);
int _putenv(const char *var_name);
]])
function os.setenv(key, val)
if not val then
ffi.C._putenv(key)
else
ffi.C._putenv_s(key, val)
end
end
else
ffi.cdef([[
int setenv(const char *var_name, const char *new_value, int change_flag);
int unsetenv(const char *name);
]])
function os.setenv(key, val)
if not val then
ffi.C.unsetenv(key)
else
ffi.C.setenv(key, val, 0)
end
end
end
else
function os.setenv(key, val)
logn("ffi.C.setenv(", key, val, ")")
end
end
end
do -- by Python1320
local dd=60*60*24
local hh=60*60
local mm=60
function os.datetable(a)
local negative=false
if a<0 then negative=true a=a*-1 end
local f,s,m,h,d
f=a - math.floor(a)
f=math.round(f*10)*0.1
a=math.floor(a)
d=math.floor(a/dd)
a=a-d*dd
h=math.floor(a/hh)
a=a-h*hh
m=math.floor(a/mm)
a=a-m*mm
s=a
return {
f=f,
sec=s,
min=m,
hour=h,
day=d,
n=negative
}
end
end
do -- by Python1320
local conjunction= " and"
local conjunction2= ","
function os.prettydate(t, just_time)
if type(t)=="number" then
t = os.datetable(t)
end
if just_time then t.n = nil end
local tbl={}
if t.day~=0 then
table.insert(tbl,t.day .." day"..(t.day==1 and "" or "s"))
end
local lastand
if t.hour~=0 then
if #tbl>0 then lastand=table.insert(tbl,conjunction)table.insert(tbl," ")end
table.insert(tbl,t.hour .." hour"..(t.hour==1 and "" or "s"))
end
if t.min~=0 then
if #tbl>0 then lastand=table.insert(tbl,conjunction)table.insert(tbl," ")end
table.insert(tbl,t.min .." minute"..(t.min==1 and "" or "s"))
end
if t.sec~=0 or #tbl==0 then
if #tbl>0 then lastand=table.insert(tbl,conjunction)table.insert(tbl," ")end
table.insert(tbl,t.sec .."."..math.round((t.f or 0)*10).." seconds")
end
if t.n then
table.insert(tbl," in the past")
end
for k,v in pairs(tbl) do
if v==conjunction and k~=lastand then
tbl[k]=conjunction2
end
end
return table.concat ( tbl , "" )
end
end
function os.executeasync(str)
if LINUX then
return os.execute([[eval ']]..str..[[' &]])
else
return os.execute(str)
end
end end)();
(function(...) --_G.ffi = require("ffi")
local ffi = require("ffi")
ffi.cdef("char *strerror(int)")
function ffi.strerror()
local num = ffi.errno()
local err = ffi.string(ffi.C.strerror(num))
return err == "" and tostring(num) or err
end
if DEBUG_GC then
local hooked = table.weak()
local real_gc = ffi.gc
local real_new = ffi.new
function ffi.gc(cdata, finalizer)
hooked[cdata] = finalizer
return cdata
end
function ffi.new(...)
local obj = real_new(...)
logn("ffi.new: ", ...)
real_gc(obj, function(...)
logn("ffi.gc: ", ...)
if hooked[obj] then
return hooked[obj](...)
end
end)
return obj
end
local old = setmetatable
function setmetatable(tbl, meta)
if meta then
local __gc = meta.__gc
if __gc then
function meta.__gc(...)
logn("META:__gc: ", ...)
local a,b,c = pcall(__gc, ...)
logn("OK")
return a,b,c
end
end
end
return old(tbl, meta)
end
end
local where = {
"bin/" .. jit.os:lower() .. "_" .. jit.arch:lower() .. "/",
"lua/modules/bin/" .. jit.os:lower() .. "_" .. jit.arch:lower() .. "/",
}
local function warn_pcall(func, ...)
local res = {pcall(func, ...)}
if not res[1] then
logn(res[2]:trim())
end
return unpack(res, 2)
end
local function handle_stupid(path, clib, err, ...)
if WINDOWS and clib then
return setmetatable({}, {
__index = function(s, k)
if k == "Type" then return "ffi" end
local ok, msg = pcall(function() return clib[k] end)
if not ok then
if msg:find("cannot resolve symbol", nil, true) then
logf("[%s] could not find function %q in shared library\n", path, msg:match("cannot resolve symbol '(.-)': "))
return nil
else
error(msg, 2)
end
end
return msg
end,
__newindex = clib,
})
end
return clib, err, ...
end
local function indent_error(str)
local last_line
str = "\n" .. str .. "\n"
str = str:gsub("(.-\n)", function(line)
line = "\t" .. line:trim() .. "\n"
if line == last_line then
return ""
end
last_line = line
return line
end)
str= str:gsub("\n\n", "\n")
return str
end
-- make ffi.load search using our file system
function ffi.load(path, ...)
local args = {pcall(_OLD_G.ffi.load, path, ...)}
if WINDOWS and not args[1] then
args = {pcall(_OLD_G.ffi.load, "lib" .. path, ...)}
end
if not args[1] then
if vfs and system and system.SetSharedLibraryPath then
for _, where in ipairs(where) do
for _, full_path in ipairs(vfs.GetFiles({path = where, filter = path, filter_plain = true, full_path = true})) do
-- look first in the vfs' bin directories
local old = system.GetSharedLibraryPath()
system.SetSharedLibraryPath(full_path:match("(.+/)"))
args = {pcall(_OLD_G.ffi.load, full_path, ...)}
system.SetSharedLibraryPath(old)
if args[1] then
return handle_stupid(path, select(2, unpack(args)))
end
args[2] = args[2] .. "\n" .. system.GetLibraryDependencies(full_path)
-- if not try the default OS specific dll directories
args = {pcall(_OLD_G.ffi.load, full_path, ...)}
if args[1] then
return handle_stupid(path, select(2, unpack(args)))
end
args[2] = args[2] .. "\n" .. system.GetLibraryDependencies(full_path)
end
end
error(indent_error(args[2]), 2)
end
end
return handle_stupid(path, args[2])
end
ffi.cdef("void* malloc(size_t size); void free(void* ptr);")
function ffi.malloc(t, size)
size = size * ffi.sizeof(t)
local ptr = ffi.gc(ffi.C.malloc(size), ffi.C.free)
return ffi.cast(ffi.typeof("$ *", t), ptr), ptr
end
local function warn_pcall(func, ...)
local res = {pcall(func, ...)}
if not res[1] then
logn(res[2]:trim())
end
return unpack(res)
end
function ffi.cdef(str, ...)
return warn_pcall(_OLD_G.ffi.cdef, str, ...)
end
local metatable_lookup = {}
function ffi.metatype(ct, meta)
metatable_lookup[tostring((ct))] = meta
return _OLD_G.ffi.metatype(ct, meta)
end
function ffi.getmetatable(ct)
return metatable_lookup[tostring((ct))]
end end)();
(function(...) math.tau = math.pi*2
function math.linear2gamma(n, gamma)
gamma = gamma or 2.4
if n <= 0.04045 then
return n / 12.92
end
return ((n + 0.055) / 1.055) ^ gamma
end
function math.gamma2linear(n, gamma)
gamma = gamma or 2.4
if n < 0.0031308 then
return n * 12.92
else
return 1.055 * (n ^ (1.0 / gamma)) - 0.055
end
end
function math.normalizeangle(a)
return (a + math.pi) % math.tau - math.pi
end
function math.map(num, in_min, in_max, out_min, out_max)
return (num - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
end
function math.normalize(num, min, max)
return (num - min) / (max - min)
end
function math.pow2ceil(n)
return 2 ^ math.ceil(math.log(n) / math.log(2))
end
function math.pow2floor(n)
return 2 ^ math.floor(math.log(n) / math.log(2))
end
function math.pow2round(n)
return 2 ^ math.round(math.log(n) / math.log(2))
end
function math.round(num, idp)
if idp and idp > 0 then
local mult = 10 ^ idp
return math.floor(num * mult + 0.5) / mult
end
return math.floor(num + 0.5)
end
function math.randomf(min, max)
min = min or -1
max = max or 1
return min + (math.random() * (max-min))
end
function math.clamp(self, min, max)
return math.min(math.max(self, min), max)
end
function math.lerp(m, a, b)
return (b - a) * m + a
end
function math.len(x)
local len = 1
while x > 9999 do
x = x / 10000
len = len + 4
end
while x > 99 do
x = x / 100
len = len + 2
end
if x > 9 then
len = len + 1
end
return len
end
function math.digit10(x, n)
while n > 0 do
x = x / 10
n = n - 1
end
return math.floor(x % 10)
end
function math.approach(cur, target, inc)
inc = math.abs(inc)
if cur < target then
return math.clamp(cur + inc, cur, target)
elseif cur > target then
return math.clamp(cur - inc, target, cur)
end
return target
end
local inf, ninf = math.huge, -math.huge
function math.isvalid(num)
return
num and
num ~= inf and
num ~= ninf and
(num >= 0 or num <= 0)
end
function math.tostring(num)
local t = {}
local len = math.len(num)
for i = 0, len - 1 do
t[len - i] = math.digit10(num, i)
end
return table.concat(t)
end end)();
utility =
(function(...) local utility = _G.utility or {}
do
function utility.StartRecordingCalls(lib, filter)
lib.old_funcs = lib.old_funcs or {}
lib.call_log = lib.call_log or {}
local i = 1
for k,v in pairs(lib) do
if (type(v) == "cdata" or type(v) == "function") and (not filter or filter(k)) then
lib.old_funcs[k] = v
lib[k] = function(...)
local ret = v(...)
lib.call_log[i] = {func_name = k, ret = ret, args = {...}}
i = i + 1
return ret
end
end
end
end
function utility.StopRecordingCalls(lib, name)
if not lib.old_funcs then return end
for k,v in pairs(lib.old_funcs) do
lib[k] = v
end
local tbl = lib.call_log
lib.call_log = nil
for i,v in ipairs(tbl) do
log(("%3i"):format(i), ": ")
if v.ret ~= nil then
log(v.ret, " ")
end
local args = {}
for k,v in pairs(v.args) do
table.insert(args, tostringx(v))
end
logn(name or "", ".", v.func_name, "(", table.concat(args, ", "), ")")
end
end
end
do
local ran = {}
function utility.RunOnNextGarbageCollection(callback, id)
if id then
ran[id] = false
getmetatable(newproxy(true)).__gc = function(...)
if not ran[id] then
callback(...)
ran[id] = true
end
end
else
getmetatable(newproxy(true)).__gc = callback
end
end
end
do
local function handle_path(path)
if vfs.IsPathAbsolute(path) then
return path
end
if path == "." then
path = ""
end
return system.GetWorkingDirectory() .. path
end
function utility.CLIPathInputToTable(str, extensions)
local paths = {}
str = str:trim()
if handle_path(str):endswith("/**") then
vfs.GetFilesRecursive(handle_path(str:sub(0, -3)), extensions, function(path)
table.insert(paths, R(path))
end)
elseif handle_path(str):endswith("/*") then
for _, path in ipairs(vfs.Find(handle_path(str:sub(0, -2)), true)) do
if not extensions or vfs.GetExtensionFromPath(path):endswiththese(extensions) then
table.insert(paths, path)
end
end
elseif str:find(",", nil, true) then
for i, path in ipairs(str:split(",")) do
path = handle_path(vfs.FixPathSlashes(path:trim()))
if vfs.IsFile(path) and (not extensions or vfs.GetExtensionFromPath(path):endswiththese(extensions)) then
table.insert(paths, R(path))
end
end
elseif LINUX and str:find("%s") then
for i, path in ipairs(str:split(" ")) do
path = handle_path(vfs.FixPathSlashes(path:trim()))
if vfs.IsFile(path) and (not extensions or vfs.GetExtensionFromPath(path):endswiththese(extensions)) then
table.insert(paths, R(path))
end
end
elseif vfs.IsFile(handle_path(str)) and (not extensions or vfs.GetExtensionFromPath(str):endswiththese(extensions)) then
table.insert(paths, R(handle_path(str)))
else
table.insert(paths, handle_path(str))
end
return paths
end
end
function utility.GenerateCheckLastFunction(func, arg_count)
local lua = ""
lua = lua .. "local func = ...\n"
for i = 1, arg_count do
lua = lua .. "local last_" .. i .. "\n"
end
lua = lua .. "return function("
for i = 1, arg_count do
lua = lua .. "_" .. i
if i ~= arg_count then
lua = lua .. ", "
end
end
lua = lua .. ")\n"
lua = lua .. "\tif\n"
for i = 1, arg_count do
lua = lua .. "\t\t_" .. i .. " ~= last_" .. i
if i ~= arg_count then
lua = lua .. " or\n"
else
lua = lua .. "\n"
end
end
lua = lua .. "\tthen\n"
lua = lua .. "\t\tfunc("
for i = 1, arg_count do
lua = lua .. "_" .. i
if i ~= arg_count then
lua = lua .. ", "
end
end
lua = lua .. ")\n"
for i = 1, arg_count do
lua = lua .. "\t\tlast_" .. i .. " = _" .. i .. "\n"
end
lua = lua .. "\tend\n"
lua = lua .. "end"
return assert(loadstring(lua))(func)
end
do
local stack = {}
function utility.PushTimeWarning()
if CLI then return end
table.insert(stack, os.clock())
end
function utility.PopTimeWarning(what, threshold, category)
if CLI then return end
threshold = threshold or 0.1
local start_time = table.remove(stack)
if not start_time then return end
local delta = os.clock() - start_time
if delta > threshold then
if category then
logf("%s %f seconds spent in %s\n", category, delta, what)
else
logf("%f seconds spent in %s\n", delta, what)
end
end
end
end
function utility.CreateDeferredLibrary(name)
return setmetatable(
{
queue = {},
Start = function(self)
_G[name] = self
end,
Stop = function()
_G[name] = nil
end,
Call = function(self, lib)
for _, v in ipairs(self.queue) do
if not lib[v.key] then error(v.key .. " was not found", 2) end
print(self, lib)
lib[v.key](unpack(v.args))
end
return lib
end,
},
{
__index = function(self, key)
return function(...)
table.insert(self.queue, {key = key, args = {...}})
end
end,
}
)
end
do
function utility.StripLuaCommentsAndStrings(code, post_process)
code = code:gsub("\\\\", "____ESCAPE_ESCAPE")
code = code:gsub("\\'", "____SINGLE_QUOTE_ESCAPE")
code = code:gsub('\\"', "____DOUBLE_QUOTE_ESCAPE")
local singleline_comments = {}
local multiline_comments = {}
local double_quote_strings = {}
local single_quote_strings = {}
local multiline_strings = {}
code = code:gsub("(%-%-%[(=*)%[.-%]%2%])", function(str) table.insert(multiline_comments, str) return "____COMMENT_MULTILINE_" .. #multiline_comments .. "____" .. " " end)
code = code:gsub("(%[(=*)%[.-%]%2%])", function(str) table.insert(multiline_strings, str) return "____STRING_MULTILINE_" .. #multiline_strings .. "____" .. " " end)
code = code:gsub("%b\"\"", function(str) table.insert(double_quote_strings, str) return "____STRING_DOUBLE_QUOTE_" .. #double_quote_strings .. "____" .. " " end)
code = code:gsub("(%-%-.-)\n", function(str) table.insert(singleline_comments, str) return "____COMMENT_SINGLELINE_" .. #singleline_comments .. "____" .. " " end)
code = code:gsub("%b''", function(str) table.insert(single_quote_strings, str) return "____STRING_SINGLE_QUOTE_" .. #single_quote_strings .. "____" .. " " end)
local res = {
singleline_comments = singleline_comments,
multiline_comments = multiline_comments,
double_quote_strings = double_quote_strings,
single_quote_strings = single_quote_strings,
multiline_strings = multiline_strings,
}
if post_process then
code = post_process(code, res) or code
end
return code, res
end
function utility.RestoreLuaCommentsAndStrings(code, data)
for i, v in ipairs(data.multiline_comments) do code = code:replace("____COMMENT_MULTILINE_" .. i .. "____", v) end
for i, v in ipairs(data.multiline_strings) do code = code:replace("____STRING_MULTILINE_" .. i .. "____", v) end
for i, v in ipairs(data.double_quote_strings) do code = code:replace("____STRING_DOUBLE_QUOTE_" .. i .. "____", v) end
for i, v in ipairs(data.singleline_comments) do code = code:replace("____COMMENT_SINGLELINE_" .. i .. "____", v .. "\n") end
for i, v in ipairs(data.single_quote_strings) do code = code:replace("____STRING_SINGLE_QUOTE_" .. i .. "____", v) end
code = code:gsub("____ESCAPE_ESCAPE", "\\\\")
code = code:gsub("____SINGLE_QUOTE_ESCAPE", "\\'")
code = code:gsub("____DOUBLE_QUOTE_ESCAPE", '\\"')
return code
end
end
function utility.CreateCallbackThing(cache)
cache = cache or {}
local self = {}
function self:check(path, callback, extra)
if cache[path] then
if cache[path].extra_callbacks then
for key, old in pairs(cache[path].extra_callbacks) do
local callback = extra[key]
if callback then
cache[path].extra_callbacks[key] = function(...)
old(...)
callback(...)
end
end
end
end
if cache[path].callback then
local old = cache[path].callback
cache[path].callback = function(...)
old(...)
callback(...)
end
return true
end
end
end
function self:start(path, callback, extra)
cache[path] = {callback = callback, extra_callbacks = extra}
end
function self:callextra(path, key, out)
if not cache[path] or not cache[path].extra_callbacks[key] then return end
return cache[path].extra_callbacks[key](out)
end
function self:stop(path, out, ...)
if not cache[path] then return end
cache[path].callback(out, ...)
cache[path] = out
end
function self:get(path)
return cache[path]
end
function self:uncache(path)
cache[path] = nil
end
return self
end
do
local ffi = desire("ffi")
local ok, lib
if ffi then
ok, lib = pcall(ffi.load, "lz4")
if ok then
ffi.cdef[[
int LZ4_compress (const char* source, char* dest, int inputSize);
int LZ4_decompress_safe (const char* source, char* dest, int inputSize, int maxOutputSize);
]]
function utility.Compress(data)
local size = #data
local buf = ffi.new("uint8_t[?]", ((size) + ((size)/255) + 16))
local res = lib.LZ4_compress(data, buf, size)
if res ~= 0 then
return ffi.string(buf, res)
end
end
function utility.Decompress(source, orig_size)
local dest = ffi.new("uint8_t[?]", orig_size)
local res = lib.LZ4_decompress_safe(source, dest, #source, orig_size)
if res > 0 then
return ffi.string(dest, res)
end
end
end
end
if not ok then
utility.Compress = function() error("lz4 is not avaible: " .. lib, 2) end
utility.Decompress = utility.Compress
end
end
function utility.MakePushPopFunction(lib, name, func_set, func_get, reset)
func_set = func_set or lib["Set" .. name]
func_get = func_get or lib["Get" .. name]
local stack = {}
local i = 1
lib["Push" .. name] = function(a,b,c,d)
stack[i] = stack[i] or {}
stack[i][1], stack[i][2], stack[i][3], stack[i][4] = func_get()
func_set(a,b,c,d)
i = i + 1
end
lib["Pop" .. name] = function()
i = i - 1
if i < 1 then
error("stack underflow", 2)
end
if i == 1 and reset then
reset()
end
func_set(stack[i][1], stack[i][2], stack[i][3], stack[i][4])
end
end
function utility.FindReferences(reference)
local done = {}
local found = {}
local found2 = {}
local revg = {}
for k,v in pairs(_G) do revg[v] = tostring(k) end
local function search(var, str)
if done[var] then return end
if revg[var] then str = revg[var] end
if rawequal(var, reference) then
local res = str .. " = " .. tostring(reference)
if not found2[res] then
table.insert(found, res)
found2[res] = true
end
end
local t = type(var)
if t == "table" then
done[var] = true
for k, v in pairs(var) do
search(k, str .. "." .. tostring(k))
search(v, str .. "." .. tostring(k))
end
elseif t == "function" then
done[var] = true
for _, v in pairs(debug.getupvalues(var)) do
if v.val then
search(v.val, str .. "^" .. v.key)
end
end
end
end
search(_G, "_G")
return table.concat(found, "\n")
end
function utility.TableToColumns(title, tbl, columns, check, sort_key)
if false and gui then
local frame = gui.CreatePanel("frame", nil, "table_to_columns_" .. title)
frame:SetSize(Vec2() + 300)
frame:SetTitle(title)
local list = frame:CreatePanel("list")
list:SetupLayout("fill")
local keys = {}
for i,v in ipairs(columns) do keys[i] = v.friendly or v.key end
list:SetupSorted(unpack(keys))
for _, data in ipairs(tbl) do
local args = {}
for i, info in ipairs(columns) do
if info.tostring then
args[i] = info.tostring(data[info.key], data, tbl)
else
args[i] = data[info.key]
end
if type(args[i]) == "string" then
args[i] = args[i]:trim()
end
end
list:AddEntry(unpack(args))
end
return
end
local top = {}
for k, v in pairs(tbl) do
if not check or check(v) then
table.insert(top, {key = k, val = v})
end
end
if type(sort_key) == "function" then
table.sort(top, function(a, b)
return sort_key(a.val, b.val)
end)
else
table.sort(top, function(a, b)
return a.val[sort_key] > b.val[sort_key]
end)
end
local max_lengths = {}
local temp = {}
for _, column in ipairs(top) do
for key, data in ipairs(columns) do
data.tostring = data.tostring or function(...) return ... end
data.friendly = data.friendly or data.key
max_lengths[data.key] = max_lengths[data.key] or 0
local str = tostring(data.tostring(column.val[data.key], column.val, top))
column.str = column.str or {}
column.str[data.key] = str
if #str > max_lengths[data.key] then
max_lengths[data.key] = #str
end
temp[key] = data
end
end
columns = temp
local width = 0
for _,v in pairs(columns) do
if max_lengths[v.key] > #v.friendly then
v.length = max_lengths[v.key]
else
v.length = #v.friendly + 1
end
width = width + #v.friendly + max_lengths[v.key] - 2
end
local out = " "
out = out .. ("_"):rep(width - 1) .. "\n"
out = out .. "|" .. (" "):rep(width / 2 - math.floor(#title / 2)) .. title .. (" "):rep(math.floor(width / 2) - #title + math.floor(#title / 2)) .. "|\n"
out = out .. "|" .. ("_"):rep(width - 1) .. "|\n"
for _,v in ipairs(columns) do
out = out .. "| " .. v.friendly .. ": " .. (" "):rep(-#v.friendly + max_lengths[v.key] - 1) -- 2 = : + |
end
out = out .. "|\n"
for _,v in ipairs(columns) do
out = out .. "|" .. ("_"):rep(v.length + 2)
end
out = out .. "|\n"
for _,v in ipairs(top) do
for _,column in ipairs(columns) do
out = out .. "| " .. v.str[column.key] .. (" "):rep(-#v.str[column.key] + column.length + 1)
end
out = out .. "|\n"
end
out = out .. "|"
out = out .. ("_"):rep(width-1) .. "|\n"
return out
end
function utility.TableToFlags(flags, valid_flags)
if type(flags) == "string" then
flags = {flags}
end
local out = 0
for k, v in pairs(flags) do
local flag = valid_flags[v] or valid_flags[k]
if not flag then
error("invalid flag", 2)
end
out = bit.band(out, tonumber(flag))
end
return out
end
function utility.FlagsToTable(flags, valid_flags)
if not flags then return valid_flags.default_valid_flag end
local out = {}
for k, v in pairs(valid_flags) do
if bit.band(flags, v) > 0 then
out[k] = true
end
end
return out
end
do -- long long
local ffi = desire("ffi")
if ffi then
local btl = ffi.typeof([[union {
char b[8];
int64_t i;
}]])
function utility.StringToLongLong(str)
return btl(str).i
end
end
end
do -- find value
local found = {}
local done = {}
local skip =
{
ffi = true,
}
local keywords =
{
AND = function(a, func, x,y) return func(a, x) and func(a, y) end
}
local function args_call(a, func, ...)
local tbl = {...}
for i = 1, #tbl do
local val = tbl[i]
if not keywords[val] then
local keyword = tbl[i+1]
if keywords[keyword] and tbl[i+2] then
local ret = keywords[keyword](a, func, val, tbl[i+2])
if ret ~= nil then
return ret
end
else
local ret = func(a, val)
if ret ~= nil then
return ret
end
end
end
end
end
local function strfind(str, ...)
return args_call(str, string.compare, ...) or args_call(str, string.find, ...)
end
local function _find(tbl, name, dot, level, ...)
if level >= 3 then return end
for key, val in pairs(tbl) do
local T = type(val)
key = tostring(key)
if name == "_M" then
if val.Type == val.ClassName then
key = val.Type
else
key = val.Type .. "." .. val.ClassName
end
end
if not skip[key] and T == "table" and not done[val] then
done[val] = true
_find(val, name .. "." .. key, dot, level + 1, ...)
else
if (T == "function" or T == "number") and strfind(name .. "." .. key, ...) then
local nice_name
if type(val) == "function" then
local params = debug.getparams(val)
if dot == ":" and params[1] == "self" then
table.remove(params, 1)
end
nice_name = ("%s%s%s(%s)"):format(name, dot, key, table.concat(params, ", "))
else
nice_name = ("%s.%s = %s"):format(name, key, val)
end
if name == "_G" or name == "_M" then
table.insert(found, {key = key, val = val, name = name, nice_name = nice_name})
else
table.insert(found, {key = ("%s%s%s"):format(name, dot, key), val = val, name = name, nice_name = nice_name})
end
end
end
end
end
local function find(tbl, ...)
found = {}
_find(...)
table.sort(found, function(a, b) return #a.key < #b.key end)
for _,v in ipairs(found) do table.insert(tbl, v) end
end
function utility.FindValue(...)
local found = {}
done =
{
[_G] = true,
[package] = true,
[_OLD_G] = true,
}
find(found, _G, "_G", ".", 1, ...)
find(found, prototype.GetAllRegistered(), "_M", ":", 1, ...)
return found
end
end
do -- find in files
function utility.FindInLoadedLuaFiles(find)
local out = {}
for path in pairs(vfs.GetLoadedLuaFiles()) do
if not path:find("modules") or (path:find("ffi", nil, true) and (not path:find("header.lua") and not path:find("enums"))) then
local str = vfs.Read(path)
if str then
for i, line in ipairs(str:split("\n")) do
local start, stop = line:find(find)
if start then
out[path] = out[path] or {}
table.insert(out[path], {str = line, line = i, start = start, stop = stop})
end
end
end
end
end
return out
end
end
do
-- http://cakesaddons.googlecode.com/svn/trunk/glib/lua/glib/stage1.lua
local size_units =
{
"B",
"KiB",
"MiB",
"GiB",
"TiB",
"PiB",
"EiB",
"ZiB",
"YiB"
}
function utility.FormatFileSize(size)
local unit_index = 1
while size >= 1024 and size_units[unit_index + 1] do
size = size / 1024
unit_index = unit_index + 1
end
return tostring(math.floor(size * 100 + 0.5) / 100) .. " " .. size_units[unit_index]
end
end
function utility.SafeRemove(obj, gc)
if hasindex(obj) then
if obj.IsValid and not obj:IsValid() then return end
if type(obj.Remove) == "function" then
obj:Remove()
elseif type(obj.Close) == "function" then
obj:Close()
end
if gc and type(obj.__gc) == "function" then
obj:__gc()
end
end
end
utility.remakes = table.weak()
function utility.RemoveOldObject(obj, id)
if hasindex(obj) and type(obj.Remove) == "function" then
id = id or (debug.getinfo(2).currentline .. debug.getinfo(2).source)
if typex(utility.remakes[id]) == typex(obj) then
utility.remakes[id]:Remove()
end
utility.remakes[id] = obj
end
return obj
end
do
local hooks = {}
function utility.SetFunctionHook(tag, tbl, func_name, type, callback)
local old = hooks[tag] or tbl[func_name]
if type == "pre" then
tbl[func_name] = function(...)
local args = {callback(old, ...)}
if args[1] == "abort_call" then return end
if #args == 0 then return old(...) end
return unpack(args)
end
elseif type == "post" then
tbl[func_name] = function(...)
local args = {old(...)}
if callback(old, unpack(args)) == false then return end
return unpack(args)
end
end
return old
end
function utility.RemoveFunctionHook(tag, tbl, func_name)
local old = hooks[tag]
if old then
tbl[func_name] = old
hooks[tag] = nil
end
end
end
function utility.NumberToBinary(num, bits)
bits = bits or 32
local bin = {}
for i = 1, bits do
if num > 0 then
rest = math.fmod(num,2)
table.insert(bin, rest)
num = (num - rest) / 2
else
table.insert(bin, 0)
end
end
return table.concat(bin)
end
function utility.BinaryToNumber(bin)
bin = string.reverse(bin)
local sum = 0
for i = 1, string.len(bin) do
num = string.sub(bin, i,i) == "1" and 1 or 0
sum = sum + num * math.pow(2, i-1)
end
return sum
end
function utility.NumberToHex(num)
return "0x" .. bit.tohex(num):upper()
end
return utility
end)();
prototype =
(function(...) local prototype = _G.prototype or {}
prototype.registered = prototype.registered or {}
prototype.prepared_metatables = prototype.prepared_metatables or {}
local template_functions = {
"GetSet",
"IsSet",
"Delegate",
"GetSetDelegate",
"DelegateProperties",
"RemoveField",
"StartStorable",
"EndStorable",
"Register",
"RegisterComponent",
"CreateObject",
}
function prototype.CreateTemplate(super_type, sub_type)
local template = type(super_type) == "table" and super_type or {}
if type(super_type) == "string" then
template.Type = super_type
template.ClassName = sub_type or super_type
end
for _, key in ipairs(template_functions) do
template[key] = prototype[key]
end
return template
end
do
local function checkfield(tbl, key, def)
tbl[key] = tbl[key] or def
if not tbl[key] then
error(string.format("The type field %q was not found!", key), 3)
end
return tbl[key]
end
local blacklist = {
prototype_variables = true,
Events = true,
Require = true,
Network = true,
write_functions = true,
read_functions = true,
Args = true,
type_ids = true,
storable_variables = true,
ProtectedFields = true,
}
function prototype.Register(meta, super_type, sub_type)
local super_type = checkfield(meta, "Type", super_type)
sub_type = sub_type or super_type
local sub_type = checkfield(meta, "ClassName", sub_type)
for _, key in ipairs(template_functions) do
if key ~= "CreateObject" and meta[key] == prototype[key] then
meta[key] = nil
end
end
prototype.registered[super_type] = prototype.registered[super_type] or {}
prototype.registered[super_type][sub_type] = meta
prototype.invalidate_meta = prototype.invalidate_meta or {}
prototype.invalidate_meta[super_type] = true
if RELOAD then
prototype.UpdateObjects(meta)
for k,v in pairs(meta) do
if type(v) ~= "function" and not blacklist[k] then
local found = false
if meta.prototype_variables then
for _,v in pairs(meta.prototype_variables) do
if v.var_name == k then
found = true
break
end
end
end
local t = type(v)
if t == "number" or t == "string" or t == "function" or t == "boolean" or typex(v) == "null" then
found = true
end
if not found then
wlog("%s: META.%s = %s is mutable", meta.ClassName, k, tostring(v), 2)
end
end
end
end
return super_type, sub_type
end
end
function prototype.RebuildMetatables(what)
for super_type, sub_types in pairs(prototype.registered) do
if what == nil or what == super_type then
prototype.invalidate_meta[what or super_type] = nil
for sub_type, meta in pairs(sub_types) do
local copy = {}
local prototype_variables = {}
-- first add all the base functions from the base object
for k, v in pairs(prototype.base_metatable) do
copy[k] = v
if k == "prototype_variables" then for k,v in pairs(v) do prototype_variables[k] = v end end
end
-- if this metatable has a type base derive from it first
if meta.TypeBase then
for k, v in pairs(sub_types[meta.TypeBase]) do
copy[k] = v
if k == "prototype_variables" then for k,v in pairs(v) do prototype_variables[k] = v end end
end
end
-- then go through the list of bases and derive from them in reversed order
local base_list = {}
if meta.Base then
table.insert(base_list, meta.Base)
local base = meta
for _ = 1, 50 do
base = sub_types[base.Base]
if not base or not base.Base then break end
table.insert(base_list, 1, base.Base)
end
for _, v in ipairs(base_list) do
local base = sub_types[v]
-- the base might not be registered yet
-- however this will be run again once it actually is
if base then
for k, v in pairs(base) do
copy[k] = v
if k == "prototype_variables" then for k,v in pairs(v) do prototype_variables[k] = v end end
end
end
end
end
-- finally the actual metatable
for k, v in pairs(meta) do
copy[k] = v
if k == "prototype_variables" then for k,v in pairs(v) do prototype_variables[k] = v end end
end
do
local tbl = {}
for _, info in pairs(prototype_variables) do
if info.copy then
table.insert(tbl, info)
end
end
copy.copy_variables = tbl[1] and tbl
end
if copy.__index2 then
copy.__index = function(s, k) return copy[k] or copy.__index2(s, k) end
else
copy.__index = copy
end
copy.BaseClass = sub_types[base_list[#base_list] or meta.TypeBase]
meta.BaseClass = copy.BaseClass
prototype.prepared_metatables[super_type] = prototype.prepared_metatables[super_type] or {}
prototype.prepared_metatables[super_type][sub_type] = copy
end
end
end
end
function prototype.GetRegistered(super_type, sub_type)
sub_type = sub_type or super_type
if prototype.registered[super_type] and prototype.registered[super_type][sub_type] then
if prototype.invalidate_meta[super_type] then
prototype.RebuildMetatables(super_type)
end
return prototype.prepared_metatables[super_type][sub_type]
end
end
function prototype.GetRegisteredSubTypes(super_type)
return prototype.registered[super_type]
end
function prototype.GetAllRegistered()
local out = {}
for _, sub_types in pairs(prototype.registered) do
for _, meta in pairs(sub_types) do
table.insert(out, meta)
end
end
return out
end
local function remove_callback(self)
if (not self.IsValid or self:IsValid()) and self.Remove then
self:Remove()
end
if prototype.created_objects then
prototype.created_objects[self] = nil
end
end
function prototype.OverrideCreateObjectTable(obj)
prototype.override_object = obj
end
do
local DEBUG = DEBUG or DEBUG_OPENGL
local setmetatable = setmetatable
local type = type
local ipairs = ipairs
prototype.created_objects = prototype.created_objects or setmetatable({}, {__mode = "kv"})
function prototype.CreateObject(meta, override, skip_gc_callback)
override = override or prototype.override_object or {}
if type(meta) == "string" then
meta = prototype.GetRegistered(meta)
end
-- this has to be done in order to ensure we have the prepared metatable with bases
meta = prototype.GetRegistered(meta.Type, meta.ClassName) or meta
if not skip_gc_callback then
meta.__gc = remove_callback
end
local self = setmetatable(override, meta)
if meta.copy_variables then
for _, info in ipairs(meta.copy_variables) do
self[info.var_name] = info.copy()
end
end
prototype.created_objects[self] = self
if DEBUG then
self:SetDebugTrace(debug.traceback())
self:SetCreationTime(system and system.GetElapsedTime and system.GetElapsedTime() or os.clock())
end
return self
end
end
do
prototype.linked_objects = prototype.linked_objects or {}
function prototype.AddPropertyLink(...)
event.AddListener("Update", "update_object_properties", function()
for i, data in ipairs(prototype.linked_objects) do
if type(data.args[1]) == "table" and type(data.args[2]) == "table" then
local obj_a = data.args[1]
local obj_b = data.args[2]
local field_a = data.args[3]
local field_b = data.args[4]
local key_a = data.args[5]
local key_b = data.args[6]
if obj_a:IsValid() and obj_b:IsValid() then
local info_a = obj_a.prototype_variables[field_a]
local info_b = obj_b.prototype_variables[field_b]
if info_a and info_b then
if key_a and key_b then
-- local val = a:GeFieldA().key_a
-- val.key_a = b:GetFieldB().key_b
-- a:SetFieldA(val)
local val = obj_a[info_a.get_name](obj_a)
val[key_a] = obj_b[info_b.get_name](obj_b)[key_b]
if data.store.last_val ~= val then
obj_a[info_a.set_name](obj_a, val)
data.store.last_val = val
end
elseif key_a and not key_b then
-- local val = a:GeFieldA()
-- val.key_a = b:GetFieldB()
-- a:SetFieldA(val)
local val = obj_a[info_a.get_name](obj_a)
val[key_a] = obj_b[info_b.get_name](obj_b)
if data.store.last_val ~= val then
obj_a[info_a.set_name](obj_a, val)
data.store.last_val = val
end
elseif key_b and not key_a then
-- local val = b:GeFieldB().key_b
-- a:SetFieldA(val)
local val = obj_b[info_b.get_name](obj_b)[key_b]
if data.store.last_val ~= val then
obj_a[info_a.set_name](obj_a, val)
data.store.last_val = val
end
else
-- local val = b:GeFieldB()
-- a:SetFieldA(val)
local val = obj_b[info_b.get_name](obj_b)
if data.store.last_val ~= val then
obj_a[info_a.set_name](obj_a, val)
data.store.last_val = val
end
end
end
if not info_b then
wlog("unable to find property info for %s (%s)", field_b, obj_b)
end
else
table.remove(prototype.linked_objects, i)
break
end
elseif type(data.args[2]) == "function" and type(data.args[3]) == "function" then
local obj = data.args[1]
local get_func = data.args[2]
local set_func = data.args[3]
if obj:IsValid() then
local val = get_func()
if data.store.last_val ~= val then
set_func(val)
data.store.last_val = val
end
end
end
end
end)
table.insert(prototype.linked_objects, {store = table.weak(), args = {...}})
end
function prototype.RemovePropertyLink(obj_a, obj_b, field_a, field_b, key_a, key_b)
for i, v in ipairs(prototype.linked_objects) do
local obj_a_, obj_b_, field_a_, field_b_, key_a_, key_b_ = unpack(v)
if
obj_a == obj_a_ and
obj_b == obj_b_ and
field_a == field_a_ and
field_b == field_b_ and
key_a == key_a_ and
key_b == key_b_
then
table.remove(prototype.linked_objects, i)
break
end
end
end
function prototype.RemovePropertyLinks(obj)
for i in pairs(prototype.linked_objects) do
if v[1] == obj then
prototype.linked_objects[i] = nil
end
end
table.fixindices(prototype.linked_objects)
end
function prototype.GetPropertyLinks(obj)
local out = {}
for _, v in ipairs(prototype.linked_objects) do
if v[1] == obj then
table.insert(out, {unpack(v)})
end
end
return out
end
end
function prototype.CreateDerivedObject(super_type, sub_type, override, skip_gc_callback)
local meta = prototype.GetRegistered(super_type, sub_type)
if not meta then
llog("tried to create unknown %s %q!", super_type or "no type", sub_type or "no class")
return
end
return prototype.CreateObject(meta, override, skip_gc_callback)
end
function prototype.SafeRemove(obj)
if hasindex(obj) and obj.IsValid and obj.Remove and obj:IsValid() then
obj:Remove()
end
end
function prototype.GetCreated(sorted, super_type, sub_type)
if sorted then
local out = {}
for _, v in pairs(prototype.created_objects) do
if (not super_type or v.Type == super_type) and (not sub_type or v.ClassName == sub_type) then
table.insert(out, v)
end
end
table.sort(out, function(a, b) return a:GetCreationTime() < b:GetCreationTime() end)
return out
end
return prototype.created_objects or {}
end
function prototype.FindObject(str)
local name, property = str:match("(.-):(.+)")
if not name then name = str end
local objects = prototype.GetCreated()
local found
local function try(compare)
for obj in pairs(objects) do
if compare(obj) then
found = obj
return true
end
end
end
local function find_property(obj)
if not property then return true end
for _, v in pairs(prototype.GetStorableVariables(obj)) do
if tostring(obj[v.get_name](obj)):compare(property) then
return true
end
end
end
if try(function(obj) return obj:GetName() == name and find_property(obj) end) then return found end
if try(function(obj) return obj:GetName():compare(name) and find_property(obj) end) then return found end
if try(function(obj) return obj:GetNiceClassName() == name and find_property(obj) end) then return found end
if try(function(obj) return obj:GetNiceClassName():compare(name) and find_property(obj) end) then return found end
end
function prototype.UpdateObjects(meta)
if type(meta) == "string" then
meta = prototype.GetRegistered(meta)
end
if not meta then return end
for _, obj in pairs(prototype.GetCreated()) do
local tbl
if obj.Type == meta.Type and obj.ClassName == meta.ClassName then
tbl = meta
elseif obj.Type == meta.Type and obj.TypeBase == meta.ClassName then
tbl = prototype.GetRegistered(obj.Type, obj.ClassName)
end
if tbl then
if RELOAD then
for k, v in pairs(tbl) do
if type(v) == "function" then
if type(obj[k]) == "function" and debug.getinfo(v).source ~= debug.getinfo(obj[k]).source and #string.dump(v) < #string.dump(obj[k]) then
llog("not overriding smaller function %s.%s:%s(%s)", tbl.Type, tbl.ClassName, k, table.concat(debug.getupvalues(v), ", "))
else
obj[k] = v
end
elseif obj[k] == nil then
obj[k] = v
end
end
else
for k, v in pairs(tbl) do
if type(v) == "function" then
obj[k] = v
end
end
end
end
end
end
function prototype.RemoveObjects(super_type, sub_type)
sub_type = sub_type or super_type
for _, obj in pairs(prototype.GetCreated()) do
if obj.Type == super_type and obj.ClassName == sub_type then
if obj:IsValid() then
obj:Remove()
end
end
end
end
function prototype.DumpObjectCount()
local found = {}
for obj in pairs(prototype.GetCreated()) do
local name = obj.ClassName
if obj.ClassName ~= obj.Type then
name = obj.Type .. "_" .. name
end
found[name] = (found[name] or 0) + 1
end
local sorted = {}
for k, v in pairs(found) do
table.insert(sorted, {k = k, v = v})
end
table.sort(sorted, function(a, b) return a.v > b.v end)
for _, v in ipairs(sorted) do
logn(v.k, " = ", v.v)
end
end
(function(...) local prototype = (...) or _G.prototype
local __store = false
local __meta
function prototype.StartStorable(meta)
__store = true
__meta = meta
end
function prototype.EndStorable()
__store = false
__meta = nil
end
function prototype.GetStorableVariables(meta)
return meta.storable_variables or {}
end
function prototype.DelegateProperties(meta, from, var_name)
meta[var_name] = NULL
for _, info in pairs(prototype.GetStorableVariables(from)) do
if not meta[info.var_name] then
prototype.SetupProperty({
meta = meta,
var_name = info.var_name,
default = info.default,
set_name = info.set_name,
get_name = info.get_name,
})
meta[info.set_name] = function(self, var)
self[info.var_name] = var
if self[var_name]:IsValid() then
self[var_name][info.set_name](self[var_name], var)
end
end
meta[info.get_name] = function(self)
if self[var_name]:IsValid() then
return self[var_name][info.get_name](self[var_name])
end
return self[info.var_name]
end
end
end
end
local function has_copy(obj)
assert(type(obj.__copy) == "function")
end
function prototype.SetupProperty(info)
local meta = info.meta or __meta
local default = info.default
local name = info.var_name
local set_name = info.set_name
local get_name = info.get_name
local callback = info.callback
if type(default) == "number" then
if callback then
meta[set_name] = meta[set_name] or function(self, var) self[name] = tonumber(var) or default self[callback](self) end
else
meta[set_name] = meta[set_name] or function(self, var) self[name] = tonumber(var) or default end
end
meta[get_name] = meta[get_name] or function(self) return self[name] or default end
elseif type(default) == "string" then
if callback then
meta[set_name] = meta[set_name] or function(self, var) self[name] = tostring(var) self[callback](self) end
else
meta[set_name] = meta[set_name] or function(self, var) self[name] = tostring(var) end
end
meta[get_name] = meta[get_name] or function(self) if self[name] ~= nil then return self[name] end return default end
else
if callback then
meta[set_name] = meta[set_name] or function(self, var) if var == nil then var = default end self[name] = var self[callback](self) end
else
meta[set_name] = meta[set_name] or function(self, var) if var == nil then var = default end self[name] = var end
end
meta[get_name] = meta[get_name] or function(self) if self[name] ~= nil then return self[name] end return default end
end
meta[name] = default
if __store then
info.type = typex(default)
meta.storable_variables = meta.storable_variables or {}
table.insert(meta.storable_variables, info)
end
do
if pcall(has_copy, info.default) then
info.copy = function()
return info.default:__copy()
end
elseif typex(info.default) == "table" then
if not next(info.default) then
info.copy = function()
return {}
end
else
info.copy = function()
return table.copy(info.default)
end
end
end
meta.prototype_variables = meta.prototype_variables or {}
meta.prototype_variables[info.var_name] = info
end
return info
end
local function add(meta, name, default, extra_info, get)
local info = {
meta = meta,
default = default,
var_name = name,
set_name = "Set" .. name,
get_name = get .. name,
}
if extra_info then
if table.isarray(extra_info) and #extra_info > 1 then
extra_info = {enums = extra_info}
end
table.merge(info, extra_info)
end
return prototype.SetupProperty(info)
end
function prototype.GetSet(meta, name, default, extra_info)
if type(meta) == "string" and __meta then
return add(__meta, meta, name, default, "Get")
else
return add(meta, name, default, extra_info, "Get")
end
end
function prototype.IsSet(meta, name, default, extra_info)
if type(meta) == "string" and __meta then
return add(__meta, meta, name, default, "Is")
else
return add(meta, name, default, extra_info, "Is")
end
end
function prototype.Delegate(meta, key, func_name, func_name2)
if not func_name2 then func_name2 = func_name end
meta[func_name] = function(self, ...)
return self[key][func_name2](self[key], ...)
end
end
function prototype.GetSetDelegate(meta, func_name, def, key)
local get = "Get" .. func_name
local set = "Set" .. func_name
local info = prototype.GetSet(meta, func_name, def)
prototype.Delegate(meta, key, get)
prototype.Delegate(meta, key, set)
return info
end
function prototype.RemoveField(meta, name)
meta["Set" .. name] = nil
meta["Get" .. name] = nil
meta["Is" .. name] = nil
meta[name] = nil
end
end)( prototype);
(function(...) local prototype = (...) or _G.prototype
local META = {}
prototype.GetSet(META, "DebugTrace", "")
prototype.GetSet(META, "CreationTime", os.clock())
prototype.GetSet(META, "PropertyIcon", "")
prototype.GetSet(META, "HideFromEditor", false)
prototype.GetSet(META, "GUID", "")
prototype.StartStorable(META)
prototype.GetSet("Name", "")
prototype.GetSet("Description", "")
prototype.EndStorable()
function META:GetGUID()
self.GUID = self.GUID or ("%p%p"):format(self, getmetatable(META))
end
function META:GetNiceClassName()
if self.ClassName ~= self.Type then
return self.Type .. "_" .. self.ClassName
end
return self.ClassName
end
function META:GetEditorName()
if self.Name == "" then
return self.EditorName or ""
end
return self.Name
end
function META:__tostring()
local additional_info = self:__tostring2()
if self.Name ~= "" then
if self.ClassName ~= self.Type then
return ("%s:%s[%s]%s"):format(self.Type, self.ClassName, self.Name, additional_info)
else
return ("%s[%s]%s"):format(self.Type, self.Name, additional_info)
end
else
if self.ClassName ~= self.Type then
return ("%s:%s[%p]%s"):format(self.Type, self.ClassName, self, additional_info)
else
return ("%s[%p]%s"):format(self.Type, self, additional_info)
end
end
end
function META:__tostring2()
return ""
end
function META:IsValid()
return true
end
do
prototype.remove_these = prototype.remove_these or {}
local event_added = false
function META:Remove(...)
if self.__removed then return end
if self.call_on_remove then
for _, v in pairs(self.call_on_remove) do
if v(self) == false then
return
end
end
end
if self.added_events then
for event in pairs(self.added_events) do
self:RemoveEvent(event)
end
end
if self.OnRemove then
self:OnRemove(...)
end
if not event_added and _G.event then
event.AddListener("Update", "prototype_remove_objects", function()
if #prototype.remove_these > 0 then
for _, obj in ipairs(prototype.remove_these) do
prototype.created_objects[obj] = nil
prototype.MakeNULL(obj)
end
table.clear(prototype.remove_these)
end
end)
event_added = true
end
table.insert(prototype.remove_these, self)
self.__removed = true
end
end
do -- serializing
local callbacks = {}
function META:SetStorableTable(tbl)
self:SetGUID(tbl.GUID)
if self.OnDeserialize then
self:OnDeserialize(tbl.__extra_data)
end
for _, info in ipairs(prototype.GetStorableVariables(self)) do
if tbl[info.var_name] ~= nil then
self[info.set_name](self, tbl[info.var_name])
end
end
if tbl.__property_links then
for _, v in ipairs(tbl.__property_links) do
self:WaitForGUID(v[1], function(obj)
v[1] = obj
self:WaitForGUID(v[2], function(obj)
v[2] = obj
prototype.AddPropertyLink(unpack(v))
end)
end)
end
end
end
function META:GetStorableTable()
local out = {}
for _, info in ipairs(prototype.GetStorableVariables(self)) do
out[info.var_name] = self[info.get_name](self)
end
out.GUID = self.GUID
local info = prototype.GetPropertyLinks(self)
if next(info) then
for _, v in ipairs(info) do
v[1] = v[1].GUID
v[2] = v[2].GUID
end
out.__property_links = info
end
if self.OnSerialize then
out.__extra_data = self:OnSerialize()
end
return table.copy(out)
end
function META:SetGUID(guid)
prototype.created_objects_guid = prototype.created_objects_guid or table.weak()
if prototype.created_objects_guid[self.GUID] then
prototype.created_objects_guid[self.GUID] = nil
end
self.GUID = guid
prototype.created_objects_guid[self.GUID] = self
if callbacks[self.GUID] then
for _, cb in ipairs(callbacks[self.GUID]) do
cb(self)
end
callbacks[self.GUID] = nil
end
end
function META:WaitForGUID(guid, callback)
local obj = prototype.GetObjectByGUID(guid)
if obj:IsValid() then
callback(obj)
else
callbacks[guid] = callbacks[guid] or {}
table.insert(callbacks[guid], callback)
print("added callback for ", guid)
end
end
function prototype.GetObjectByGUID(guid)
prototype.created_objects_guid = prototype.created_objects_guid or table.weak()
return prototype.created_objects_guid[guid] or NULL
end
end
function META:CallOnRemove(callback, id)
id = id or callback
if type(callback) == "table" and callback.Remove then
callback = function() prototype.SafeRemove(callback) end
end
self.call_on_remove = self.call_on_remove or {}
self.call_on_remove[id] = callback
end
do -- events
local events = {}
function META:AddEvent(event_type)
self.added_events = self.added_events or {}
if self.added_events[event_type] then return end
local func_name = "On" .. event_type
events[event_type] = events[event_type] or {}
table.insert(events[event_type], self)
event.AddListener(event_type, "prototype_events", function(a_, b_, c_)
--for _, self in ipairs(events[event_type]) do
for i = 1, #events[event_type] do
local self = events[event_type][i]
if self[func_name] then
self[func_name](self, a_, b_, c_)
else
wlog("%s.%s is nil", self, func_name)
self:RemoveEvent(event_type)
end
end
end, {on_error = function(str)
system.OnError(str)
self:RemoveEvent(event_type)
end})
self.added_events[event_type] = true
end
function META:RemoveEvent(event_type)
self.added_events = self.added_events or {}
if not self.added_events[event_type] then return end
events[event_type] = events[event_type] or table.weak()
for i, other in pairs(events[event_type]) do
if other == self then
events[event_type][i] = nil
break
end
end
table.fixindices(events[event_type])
self.added_events[event_type] = nil
if #events[event_type] <= 0 then
event.RemoveListener(event_type, "prototype_events")
end
end
prototype.added_events = events
end
prototype.base_metatable = META
if RELOAD then
prototype.RebuildMetatables()
end end)( prototype);
(function(...) local prototype = ... or _G.prototype
do
local NULL = {}
NULL.Type = "null"
NULL.IsNull = true
local function FALSE()
return false
end
function NULL:IsValid()
return false
end
function NULL:__tostring()
return "NULL"
end
function NULL:__copy()
return self
end
function NULL:__index2(key)
if type(key) == "string" and key:sub(0, 2) == "Is" then
return FALSE
end
--error(("tried to index %q on a NULL value"):format(key), 2)
end
prototype.Register(NULL)
end
function prototype.MakeNULL(tbl)
table.clear(tbl)
tbl.Type = "null"
setmetatable(tbl, prototype.GetRegistered("null"))
if prototype.created_objects then
prototype.created_objects[tbl] = nil
end
end
_G.NULL = setmetatable({Type = "null", ClassName = "ClassName"}, prototype.GetRegistered("null")) end)( prototype);
return prototype
end)();
vfs =
(function(...) local vfs = _G.vfs or {}
vfs.use_appdata = false
vfs.mounted_paths = vfs.mounted_paths or {}
do -- mounting/links
function vfs.Mount(where, to, userdata)
to = to or ""
if not vfs.IsDirectory(where) then
llog("attempted to mount non existing directory ", where)
return false
end
vfs.ClearCallCache()
vfs.Unmount(where, to)
local path_info_where = vfs.GetPathInfo(where, true)
local path_info_to = vfs.GetPathInfo(to, true)
if path_info_where.filesystem == "unknown" then
for context, info in pairs(vfs.DescribePath(where, true)) do
if info.is_folder then
path_info_where.filesystem = context.Name
where = context.Name .. ":" .. where
end
end
end
if to ~= "" and not path_info_to.filesystem then
error("a filesystem has to be provided when mounting /to/ somewhere")
end
--llog("mounting ", path_info_where.full_path, " -> ", path_info_to.full_path)
table.insert(vfs.mounted_paths, {
where = path_info_where,
to = path_info_to,
full_where = where,
full_to = to,
userdata = userdata
})
end
function vfs.Unmount(where, to)
to = to or ""
vfs.ClearCallCache()
for i, v in ipairs(vfs.mounted_paths) do
if
v.full_where:lower() == where:lower() and
v.full_to:lower() == to:lower()
then
table.remove(vfs.mounted_paths, i)
return true
end
end
return false
end
function vfs.GetMounts()
local out = {}
for _, v in ipairs(vfs.mounted_paths) do
out[v.full_where] = v
end
return out
end
function vfs.TranslatePath(path, is_folder)
local path_info = vfs.GetPathInfo(path, is_folder)
local out = {}
local out_i = 1
if path_info.relative then
for _, mount_info in ipairs(vfs.mounted_paths) do
local where
if path_info.full_path:sub(0, #mount_info.to.full_path) == mount_info.to.full_path then
where = vfs.GetPathInfo(mount_info.where.filesystem .. ":" .. mount_info.where.full_path .. path_info.full_path:sub(#mount_info.to.full_path+1), is_folder)
elseif path_info.full_path ~= "/" then
where = vfs.GetPathInfo(mount_info.where.filesystem .. ":" .. mount_info.where.full_path .. path_info.full_path, is_folder)
else
where = vfs.GetPathInfo(mount_info.where.filesystem .. ":" .. mount_info.to.full_path, is_folder)
end
if where then
out[out_i] = {
path_info = where,
context = vfs.filesystems2[mount_info.where.filesystem],
userdata = mount_info.userdata
}
out_i = out_i + 1
end
end
else
local filesystems = vfs.GetFileSystems()
if path_info.filesystem ~= "unknown" then
filesystems = {vfs.GetFileSystem(path_info.filesystem)}
end
for _, context in ipairs(filesystems) do
if (is_folder and context:IsFolder(path_info)) or (not is_folder and context:IsFile(path_info)) then
out[out_i] = {path_info = path_info, context = context, userdata = path_info.userdata}
out_i = out_i + 1
elseif not is_folder and context:IsFolder({full_path = vfs.GetParentFolderFromPath(path_info.full_path)}) then
out[out_i] = {path_info = path_info, context = context, userdata = path_info.userdata}
out_i = out_i + 1
end
end
end
return out
end
end
do -- env vars/path preprocessing
vfs.env_override = vfs.env_override or {}
function vfs.GetEnv(key)
local val = vfs.env_override[key]
if type(val) == "function" then
val = val()
end
return val or os.getenv(key)
end
function vfs.SetEnv(key, val)
vfs.env_override[key] = val
end
function vfs.PreprocessPath(path)
if path:find("%", nil, true) or path:find("$", nil, true) then
-- windows
path = path:gsub("%%(.-)%%", vfs.GetEnv)
path = path:gsub("%%", "")
path = path:gsub("%$%((.-)%)", vfs.GetEnv)
-- linux
path = path:gsub("%$%((.-)%)", "%1")
end
return path
end
end
do -- file systems
vfs.filesystems = vfs.filesystems or {}
vfs.filesystems2 = vfs.filesystems2 or {}
function vfs.RegisterFileSystem(META, is_base)
META.TypeBase = "base"
META.Position = META.Position or 0
prototype.Register(META, "file_system", META.Name)
if is_base then return end
local context = prototype.CreateDerivedObject("file_system", META.Name)
context.mounted_paths = {}
for k,v in ipairs(vfs.filesystems) do
if v.Name == META.Name then
table.remove(vfs.filesystems, k)
context.mounted_paths = v.mounted_paths
break
end
end
table.insert(vfs.filesystems, context)
table.sort(vfs.filesystems, function(a, b)
return a.Position < b.Position
end)
vfs.filesystems2[context.Name] = context
end
function vfs.GetFileSystems()
return vfs.filesystems
end
function vfs.GetFileSystem(name)
return vfs.filesystems2[name]
end
end
do -- translate path to useful data
function vfs.DescribePath(path, is_folder)
local path_info = vfs.GetPathInfo(path, is_folder)
local out = {}
for _, context in ipairs(vfs.GetFileSystems()) do
out[context] = {}
if is_folder then
out[context].is_folder = context:IsFolder(path_info)
else
out[context].is_folder = context:IsFolder(path_info)
out[context].is_file = context:IsFile(path_info)
end
end
return out
end
local function get_folders(self, typ)
if typ == "full" then
local folders = {}
for i = 0, 100 do
local folder = vfs.GetParentFolderFromPath(self.full_path, i)
if folder == "" then
break
end
table.insert(folders, 1, folder)
end
--table.remove(folders) -- remove the filename
return folders
else
local folders = self.full_path:split("/")
-- if the folder is something like "/foo/bar/" remove the first /
if self.full_path:sub(1,1) == "/" then
table.remove(folders, 1)
end
table.remove(folders) -- remove the filename
return folders
end
end
function vfs.IsPathAbsolute(path)
if WINDOWS then
return path:sub(2, 2) == ":" or path:sub(1, 2) == [[//]]
end
return path:sub(1, 1) == "/"
end
function vfs.GetPathInfo(path, is_folder)
local out = {}
local pos = path:find(":", 0, true)
if pos then
local filesystem = path:sub(0, pos - 1)
if vfs.GetFileSystem(filesystem) then
path = path:sub(pos + 1)
out.filesystem = filesystem
else
out.filesystem = "unknown"
end
else
out.filesystem = "unknown"
end
local relative = not vfs.IsPathAbsolute(path)
if is_folder and not path:endswith("/") then
path = path .. "/"
end
out.full_path = path
out.relative = relative
out.GetFolders = get_folders
return out
end
end
function vfs.Open(path, mode, sub_mode)
mode = mode or "read"
local errors = {}
for i, data in ipairs(vfs.TranslatePath(path)) do
local file = prototype.CreateDerivedObject("file_system", data.context.Name)
file:SetMode(mode)
local ok, err = file:Open(data.path_info)
file.path_used = data.path_info.full_path
if ok ~= false then
if mode == "write" then
vfs.ClearCallCache()
end
return file
else
file:Remove()
local err = "\t" .. data.context.Name .. ": " .. err
if errors[#errors] ~= err then
table.insert(errors, err)
end
end
end
return false, "unable to open file: \n" .. table.concat(errors, "\n")
end
(function(...) local vfs = (...) or _G.vfs
function vfs.AbsoluteToRelativePath(root, abs)
local root_info = vfs.GetPathInfo(root)
local abs_info = vfs.GetPathInfo(abs)
return abs_info.full_path:sub(#root_info.full_path + 2)
end
function vfs.GetParentFolderFromPath(str, level)
level = level or 1
for i = #str, 1, -1 do
local char = str:sub(i, i)
if char == "/" then
level = level - 1
end
if level == -1 then
return str:sub(0, i)
end
end
return ""
end
function vfs.GetFolderNameFromPath(str)
if str:sub(#str, #str) == "/" then
str = str:sub(0, #str - 1)
end
return str:match(".+/(.+)") or str:match(".+/(.+)/") or str:match(".+/(.+)") or str:match("(.+)/")
end
function vfs.GetFileNameFromPath(str)
local pos = (str):reverse():find("/", 0, true)
return pos and str:sub(-pos + 1) or str
end
function vfs.RemoveExtensionFromPath(str)
return str:match("(.+)%..+") or str
end
function vfs.GetExtensionFromPath(str)
return vfs.GetFileNameFromPath(str):match(".+%.(%w+)") or ""
end
function vfs.GetFolderFromPath(str)
return str:match("(.*)/") .. "/"
end
function vfs.GetFileFromPath(str)
return str:match(".*/(.*)")
end
function vfs.IsPathAbsolutePath(path)
if LINUX then
return path:sub(1,1) == "/"
end
if WINDOWS then
return path:sub(1, 2):find("%a:") ~= nil
end
end
function vfs.ParsePathVariables(path)
-- windows
path = path:gsub("%%(.-)%%", vfs.GetEnv)
path = path:gsub("%%", "")
path = path:gsub("%$%((.-)%)", vfs.GetEnv)
-- linux
path = path:gsub("%$%((.-)%)", "%1")
return path
end
local character_translation = {
["\\"] = "⟍",
[":"] = "⠅",
["*"] = "✱",
["?"] = "❔",
["<"] = "ᐸ",
[">"] = "𝈷",
["|"] = "ᥣ",
["~"] = "𝀈",
["#"] = "⧣",
["\""] = "‟",
["^"] = "ᣔ",
}
function vfs.ReplaceIllegalPathSymbols(path, forward_slash)
local out = path:gsub(".", character_translation)
if forward_slash then
out = out:gsub("/", "⟋")
end
return out
end
function vfs.ReplaceIllegalCharacters()
end
function vfs.FixPathSlashes(path)
return (path:gsub("\\", "/"):gsub("(/+)", "/"))
end
function vfs.CreateDirectoriesFromPath(path, force)
local path_info = vfs.GetPathInfo(path, true)
local folders = path_info:GetFolders("full")
local max = #folders
if not path:endswith("/") then
max = max - 1
end
for i = 1, max do
local folder = folders[i]
local ok, err = vfs.CreateDirectory(path_info.filesystem ..":".. folder, force)
if not ok then
return nil, err
end
end
return true
end
function vfs.GetAbsolutePath(path, is_folder)
if vfs.IsPathAbsolute(path) then
if
(is_folder == true and vfs.IsDirectory(path)) or
(is_folder == false and vfs.IsFile(path)) or
vfs.Exists(path)
then
return path
end
end
for _, data in ipairs(vfs.TranslatePath(path, is_folder)) do
if data.context:CacheCall("IsFile", data.path_info) or data.context:CacheCall("IsFolder", data.path_info) then
return data.path_info.full_path
end
end
end end)( vfs);
(function(...) local vfs = (...) or _G.vfs
local CONTEXT = {}
CONTEXT.Name = "base"
prototype.GetSet(CONTEXT, "Mode", "read")
function CONTEXT:__tostring2()
return self.path_used or ""
end
do
local cache = vfs.call_cache or {}
local last_framenumber = 0
function vfs.ClearCallCache()
table.clear(cache)
end
function CONTEXT:CacheCall(func_name, path_info)
if system then
local frame_number = system.GetFrameNumber()
if frame_number ~= last_framenumber then
vfs.ClearCallCache()
last_framenumber = frame_number
end
end
cache[func_name] = cache[func_name] or {}
cache[func_name][self.Name] = cache[func_name][self.Name] or {}
if cache[func_name][self.Name][path_info.full_path] == nil then
cache[func_name][self.Name][path_info.full_path] = self[func_name](self, path_info)
end
-- might have been cleared inbetween
cache[func_name] = cache[func_name] or {}
cache[func_name][self.Name] = cache[func_name][self.Name] or {}
return cache[func_name][self.Name][path_info.full_path]
end
vfs.call_cache = cache
end
function CONTEXT:Write(str)
return self:WriteBytes(str)
end
function CONTEXT:Read(bytes)
return self:ReadBytes(bytes)
end
function CONTEXT:Lines()
local temp = {}
return function()
while not self:TheEnd() do
local char = self:ReadChar()
if char == "\n" then
local str = table.concat(temp)
table.clear(temp)
return str
else
table.insert(temp, char)
end
end
end
end
function CONTEXT:ReadByte()
local str = self:ReadBytes(1)
if str then
return str:byte()
end
end
function CONTEXT:WriteByte(byte)
self:WriteBytes(string.char(byte))
end
function CONTEXT:GetFiles(path_info)
error(self.Name .. ": not implemented")
end
function CONTEXT:IsFile(path_info)
error(self.Name .. ": not implemented")
end
function CONTEXT:IsFolder(path_info)
error(self.Name .. ": not implemented")
end
function CONTEXT:CreateFolder(path_info)
error(self.Name .. ": not implemented")
end
function CONTEXT:Open(path, mode, ...)
error(self.Name .. ": not implemented")
end
function CONTEXT:SetPosition(pos)
error(self.Name .. ": not implemented")
end
function CONTEXT:GetPosition()
error(self.Name .. ": not implemented")
end
function CONTEXT:Close()
error(self.Name .. ": not implemented")
end
function CONTEXT:GetSize()
error(self.Name .. ": not implemented")
end
function CONTEXT:GetLastModified()
error(self.Name .. ": not implemented")
end
function CONTEXT:GetLastAccessed()
error(self.Name .. ": not implemented")
end
function CONTEXT:Flush()
error(self.Name .. ": not implemented")
end
function CONTEXT:Close()
self:Remove()
end
function CONTEXT:IsFolderValid(path_info)
return self:IsFolder(path_info)
end
function CONTEXT:IsArchive(path_info)
return false
end
(function(...) local META = ...
local ffi = desire("ffi")
-- <cmtptr> CapsAdmin, http://codepad.org/uN7qlQTm
local function swap_endian(num, size)
local result = 0
for shift = 0, size - 8, 8 do
result = bit.bor(bit.lshift(result, 8),
bit.band(bit.rshift(num, shift), 0xff))
end
return result
end
local function header_to_table(str)
local out = {}
str = str:gsub("//.-\n", "") -- remove line comments
str = str:gsub("/%*.-%s*/", "") -- remove multiline comments
str = str:gsub("%s+", " ") -- remove excessive whitespace
for field in str:gmatch("(.-);") do
local type, key
local assert
local swap_endianess = false
field = field:trim()
if field:startswith("swap") then
field = field:sub(#"swap" + 1)
swap_endianess = true
end
if field:find("=") then
type, key, assert = field:match("^(.+) (.+) = (.+)$")
assert = tonumber(assert) or assert
else
type, key = field:match("(.+) (.+)$")
end
type = type:trim()
key = key:trim()
local length
key = key:gsub("%[(.-)%]$", function(num)
length = tonumber(num) or num
return ""
end)
local qualifier, _type = type:match("(.+) (.+)")
if qualifier then
type = _type
end
if not type then
logn("somethings wrong with the above line!")
error(field, 2)
end
if qualifier == nil then
qualifier = "signed"
end
if type == "char" and not length then
type = "byte"
end
table.insert(out, {
type,
key,
signed = qualifier == "signed",
length = length,
padding = qualifier == "padding",
assert = assert,
swap_endianess = swap_endianess,
})
end
return out
end
assert(META.WriteByte, "missing META:WriteByte")
assert(META.ReadByte, "missing META:ReadByte")
do -- basic data types
if ffi then
local type_info = {
LongLong = "int64_t",
UnsignedLongLong = "uint64_t",
Long = "int32_t",
UnsignedLong = "uint32_t",
Short = "int16_t",
UnsignedShort = "uint16_t",
Double = "double",
Float = "float",
}
local ffi_cast = ffi.cast
local ffi_string = ffi.string
for name, type in pairs(type_info) do
type = ffi.typeof(type)
local size = ffi.sizeof(type)
local ctype = ffi.typeof("$*", type)
META["Read" .. name] = function(self)
return ffi_cast(ctype, self:ReadBytes(size))[0]
end
local ctype = ffi.typeof("$[1]", type)
local hmm = ffi.new(ctype, 0)
META["Write" .. name] = function(self, num)
hmm[0] = num
self:WriteBytes(ffi_string(hmm, size))
return self
end
end
function META:ReadVariableSizedInteger(byte_size)
local ret = 0
for i = 0, byte_size - 1 do
local byte = self:ReadByte()
ret = bit.bor(ret, bit.lshift(bit.band(byte, 127), 7 * i))
if bit.band(byte, 128) == 0 then
break
end
end
if byte_size == 1 then
ret = tonumber(ffi.cast("uint8_t", ret))
elseif byte_size == 2 then
ret = tonumber(ffi.cast("uint16_t", ret))
elseif byte_size >= 2 and byte_size <= 4 then
ret = tonumber(ffi.cast("uint32_t", ret))
elseif byte_size > 4 and byte_size <= 8 then
ret = tonumber(ffi.cast("uint64_t", ret))
end
return ret
end
function META:WriteSizedInteger(value, byte_size)
for i = 0, byte_size do
if value > 127 then
self:WriteByte(tonumber(bit.band(value, 7)))
value = bit.rshift(value, 7)
else
self:WriteByte(0)
end
end
end
function META:ReadSizedInteger(byte_size)
local ret = 0
for i = 0, byte_size do
ret = bit.bor(ret, bit.lshift(self:ReadByte(), 7 * i))
end
if byte_size == 1 then
ret = tonumber(ffi.cast("uint8_t", ret))
elseif byte_size == 2 then
ret = tonumber(ffi.cast("uint16_t", ret))
elseif byte_size >= 2 and byte_size <= 4 then
ret = tonumber(ffi.cast("uint32_t", ret))
elseif byte_size > 4 and byte_size <= 8 then
ret = tonumber(ffi.cast("uint64_t", ret))
end
return ret
end
else
-- short
function META:WriteShort(short)
self:WriteByte(bit.band(short, 0xFF))
self:WriteByte(bit.band(bit.rshift(short, 8), 0xFF))
return self
end
function META:ReadShort()
local b1, b2 = self:ReadByte(), self:ReadByte()
if not b1 or not b2 then return end
return b1 + bit.lshift(b2, 8)
end
-- long
function META:WriteLong(int)
self:WriteShort(bit.band(int, 0xFFFF))
self:WriteShort(bit.band(bit.rshift(int, 16), 0xFFFF))
return self
end
function META:ReadLong()
local s1, s2 = self:ReadShort(), self:ReadShort()
if not s1 or not s2 then return end
return s1 + bit.lshift(s2, 16)
end
-- half
function META:WriteHalf(value)
-- ieee 754 binary16
-- 111111
-- 54321098 76543210
-- seeeeemm mmmmmmmm
if value==0.0 then
self:WriteByte(0)
self:WriteByte(0)
return
end
local signBit=0
if value<0 then
signBit=128 -- shifted left to appropriate position
value=-value
end
local m,e=math.frexp(value)
m=m*2-1
e=e-1+15
e=math.min(math.max(0,e),31)
m=m*4
-- sign, 5 bits of exponent, 2 bits of mantissa
self:WriteByte(bit.bor(signBit,bit.band(e,31)*4,bit.band(m,3)))
-- get rid of written bits and shift for next 8
m=(m-math.floor(m))*256
self:WriteByte(bit.band(m,255))
return self
end
function META:ReadHalf()
local b=self:ReadByte()
local sign=1
if b>=128 then
sign=-1
b=b-128
end
local exponent=bit.rshift(b,2)-15
local mantissa=bit.band(b,3)/4
b=self:ReadByte()
mantissa=mantissa+b/4/256
if mantissa==0.0 and exponent==-15 then return 0.0
else return (mantissa+1.0)*math.pow(2,exponent)*sign end
end
-- float
function META:WriteFloat(value)
-- ieee 754 binary32
-- 33222222 22221111 111111
-- 10987654 32109876 54321098 76543210
-- seeeeeee emmmmmmm mmmmmmmm mmmmmmmm
if value==0.0 then
self:WriteByte(0)
self:WriteByte(0)
self:WriteByte(0)
self:WriteByte(0)
return
end
local signBit=0
if value<0 then
signBit=128 -- shifted left to appropriate position
value=-value
end
local m,e=math.frexp(value)
m=m*2-1
e=e-1+127
e=math.min(math.max(0,e),255)
-- sign and 7 bits of exponent
self:WriteByte(bit.bor(signBit,bit.band(bit.rshift(e,1),127)))
-- first 7 bits of mantissa
m=m*128
-- write last bit of exponent and first 7 of mantissa
self:WriteByte(bit.bor(bit.band(bit.lshift(e,7),255),bit.band(m,127)))
-- get rid of written bits and shift for next 8
m=(m-math.floor(m))*256
self:WriteByte(bit.band(m,255))
m=(m-math.floor(m))*256
self:WriteByte(bit.band(m,255))
return self
end
function META:ReadFloat()
local b=self:ReadByte()
local sign=1
if b>=128 then
sign=-1
b=b-128
end
local exponent=b*2
b=self:ReadByte()
exponent=exponent+bit.band(bit.rshift(b,7),1)-127
local mantissa=bit.band(b,127)/128
b=self:ReadByte()
mantissa=mantissa+b/128/256
b=self:ReadByte()
mantissa=mantissa+b/128/65536
if mantissa==0.0 and exponent==-127 then return 0.0
else return (mantissa+1.0)*math.pow(2,exponent)*sign end
end
-- double
function META:WriteDouble(value)
-- ieee 754 binary64
-- 66665555 55555544 44444444 33333333 33222222 22221111 111111
-- 32109876 54321098 76543210 98765432 10987654 32109876 54321098 76543210
-- seeeeeee eeeemmmm mmmmmmmm mmmmmmmm mmmmmmmm mmmmmmmm mmmmmmmm mmmmmmmm
if value==0.0 then
for i = 1, 8 do
self:WriteByte(0)
end
return
end
local signBit=0
if value<0 then
signBit=128 -- shifted left to appropriate position
value=-value
end
local m,e=math.frexp(value)
m=m*2-1 -- m in [0.5,1.0), multiply by 2 will get it to [1.0,2.0) giving the implicit first bit in mantissa, -1 to get rid of that
e=e-1+1023 -- adjust for the *2 on previous line and 1023 is the exponent zero offset
-- sign and 7 bits of exponent
self:WriteByte(bit.bor(signBit,bit.band(bit.rshift(e,4),127)))
-- first 4 bits of mantissa
m=m*16
-- write last 4 bits of exponent and first 4 of mantissa
self:WriteByte(bit.bor(bit.band(bit.lshift(e,4),255),bit.band(m,15)))
-- get rid of written bits and shift for next 8
m=(m-math.floor(m))*256
self:WriteByte(bit.band(m,255))
-- repeat for rest of mantissa
m=(m-math.floor(m))*256
self:WriteByte(bit.band(m,255))
m=(m-math.floor(m))*256
self:WriteByte(bit.band(m,255))
m=(m-math.floor(m))*256
self:WriteByte(bit.band(m,255))
m=(m-math.floor(m))*256
self:WriteByte(bit.band(m,255))
m=(m-math.floor(m))*256
self:WriteByte(bit.band(m,255))
return self
end
function META:ReadDouble()
local b = self:ReadByte()
if not b then return end
local sign = 1
if b >= 128 then
sign =- 1
b = b - 128
end
local exponent = b*16
b = self:ReadByte()
if not b then return end
exponent = exponent+bit.band(bit.rshift(b,4),15)-1023
local mantissa=bit.band(b,15)/16
b = self:ReadByte()
if not b then return end
mantissa = mantissa+b/16/256
b = self:ReadByte()
if not b then return end
mantissa = mantissa+b/16/65536
b = self:ReadByte()
if not b then return end
mantissa = mantissa+b/16/65536/256
b = self:ReadByte()
if not b then return end
mantissa = mantissa+b/16/65536/65536
b = self:ReadByte()
if not b then return end
mantissa = mantissa+b/16/65536/65536/256
b = self:ReadByte()
if not b then return end
mantissa = mantissa+b/16/65536/65536/65536
if mantissa==0.0 and exponent==-1023 then
return 0.0
else
return (mantissa+1.0)*math.pow(2,exponent)*sign
end
end
end
META.WriteUInt16_T = META.WriteUnsignedShort
META.WriteUInt32_T = META.WriteUnsignedLong
META.WriteUInt64_T = META.WriteUnsignedLongLong
META.ReadUInt16_T = META.ReadUnsignedShort
META.ReadUInt32_T = META.ReadUnsignedLong
META.ReadUInt64_T = META.ReadUnsignedLongLong
META.WriteInt16_T = META.WriteShort
META.WriteInt32_T = META.WriteLong
META.WriteInt64_T = META.WriteLongLong
META.ReadInt16_T = META.ReadShort
META.ReadInt32_T = META.ReadLong
META.ReadInt64_T = META.ReadLongLong
function META:WriteBytes(str, len)
for i = 1, len or #str do
self:WriteByte(str:byte(i))
end
return self
end
function META:ReadBytes(bytes)
local out = {}
for i = 1, bytes do
out[i] = string.char(self:ReadByte())
end
return table.concat(out)
end
-- null terminated string
function META:WriteString(str)
self:WriteBytes(str)
self:WriteByte(0)
return self
end
function META:ReadString(length, advance, terminator)
terminator = terminator or 0
if length and not advance then
return self:ReadBytes(length)
end
local str = {}
local pos = self:GetPosition()
for _ = 1, length or self:GetSize() do
local byte = self:ReadByte()
if not byte or byte == terminator then break end
table.insert(str, string.char(byte))
end
if advance then self:SetPosition(pos + length) end
return table.concat(str)
end
-- not null terminated string (write size of string first)
function META:WriteString2(str)
if #str > 0xFFFFFFFF then error("string is too long!", 2) end
self:WriteUnsignedLong(#str)
self:WriteBytes(str)
return self
end
function META:ReadString2()
local length = self:ReadUnsignedLong()
local str = {}
for _ = 1, length do
local byte = self:ReadByte()
if not byte then break end
table.insert(str, string.char(byte))
end
return table.concat(str)
end
end
do -- extended
function META:IterateStrings()
return function()
local value = self:ReadString()
return value ~= "" and value or nil
end
end
-- half precision (2 bytes)
function META:WriteHalf(value)
-- ieee 754 binary16
-- 111111
-- 54321098 76543210
-- seeeeemm mmmmmmmm
if value==0.0 then
self:WriteByte(0)
self:WriteByte(0)
return
end
local signBit=0
if value<0 then
signBit=128 -- shifted left to appropriate position
value=-value
end
local m,e=math.frexp(value)
m=m*2-1
e=e-1+15
e=math.min(math.max(0,e),31)
m=m*4
-- sign, 5 bits of exponent, 2 bits of mantissa
self:WriteByte(bit.bor(signBit,bit.band(e,31)*4,bit.band(m,3)))
-- get rid of written bits and shift for next 8
m=(m-math.floor(m))*256
self:WriteByte(bit.band(m,255))
return self
end
function META:ReadHalf()
local b=self:ReadByte()
local sign=1
if b>=128 then
sign=-1
b=b-128
end
local exponent=bit.rshift(b,2)-15
local mantissa=bit.band(b,3)/4
b=self:ReadByte()
mantissa=mantissa+b/4/256
if mantissa==0.0 and exponent==-15 then return 0.0
else return (mantissa+1.0)*math.pow(2,exponent)*sign end
end
function META:ReadVarInt(signed)
local res = 0
local size = 0
for shift = 0, math.huge, 7 do
local b = self:ReadByte()
if shift < 28 then
res = res + bit.lshift(bit.band(b, 0x7F), shift)
else
res = res + bit.band(b, 0x7F) * (2 ^ shift)
end
size = size + 1
if b < 0x80 then break end
end
if signed then
res = res - bit.band(res, 2^15) * 2
end
return res
end
function META:ReadAll()
return self:ReadBytes(self:GetSize())
end
-- boolean
function META:WriteBoolean(b)
self:WriteByte(b and 1 or 0)
return self
end
function META:ReadBoolean()
return self:ReadByte() >= 1
end
-- number
META.WriteNumber = META.WriteDouble
META.ReadNumber = META.ReadDouble
-- char
function META:WriteChar(b)
self:WriteByte(b:byte())
return self
end
function META:ReadChar()
return string.char(self:ReadByte())
end
-- nil
function META:WriteNil()
self:WriteByte(0)
return self
end
function META:ReadNil()
self:ReadByte()
return nil
end
-- matrix44
function META:WriteMatrix44(matrix)
for i = 1, 16 do
self:WriteFloat(matrix[i - 1])
end
return self
end
function META:ReadMatrix44()
local out = Matrix44()
for i = 1, 16 do
out.m[i - 1] = self:ReadFloat()
end
return out
end
-- matrix33
function META:WriteMatrix33(matrix)
for i = 1, 8 do
self:WriteFloat(matrix[i - 1])
end
return self
end
function META:ReadMatrix33()
local out = Matrix33()
for i = 1, 8 do
out.m[i - 1] = self:ReadFloat()
end
return out
end
-- vec3
function META:WriteVec3(v)
self:WriteFloat(v.x)
self:WriteFloat(v.y)
self:WriteFloat(v.z)
return self
end
function META:ReadVec3()
return Vec3(self:ReadFloat(), self:ReadFloat(), self:ReadFloat())
end
-- vec2
function META:WriteVec2(v)
self:WriteFloat(v.x)
self:WriteFloat(v.y)
return self
end
function META:ReadVec2()
return Vec2(self:ReadFloat(), self:ReadFloat())
end
-- vec2
function META:WriteVec2Short(v)
self:WriteShort(v.x)
self:WriteShort(v.y)
return self
end
function META:ReadVec2Short()
return Vec2(self:ReadShort(), self:ReadShort())
end
-- ang3
function META:WriteAng3(v)
self:WriteFloat(v.x)
self:WriteFloat(v.y)
self:WriteFloat(v.z)
return self
end
function META:ReadAng3()
return Ang3(self:ReadFloat(), self:ReadFloat(), self:ReadFloat())
end
-- quat
function META:WriteQuat(quat)
self:WriteFloat(quat.x)
self:WriteFloat(quat.y)
self:WriteFloat(quat.z)
self:WriteFloat(quat.w)
return self
end
function META:ReadQuat()
return Quat(self:ReadFloat(), self:ReadFloat(), self:ReadFloat(), self:ReadFloat())
end
-- color
function META:WriteColor(color)
self:WriteFloat(color.r)
self:WriteFloat(color.g)
self:WriteFloat(color.b)
self:WriteFloat(color.a)
return self
end
function META:ReadColor()
return Color(self:ReadFloat(), self:ReadFloat(), self:ReadFloat(), self:ReadFloat())
end
-- integer/long
META.WriteInt = META.WriteLong
META.WriteUnsignedInt = META.WriteUnsignedLong
META.ReadInt = META.ReadLong
META.ReadUnsignedInt = META.ReadUnsignedLong
function META:WriteVariableSizedInteger(value)
local output_size = 1
while value > 127 do
self:WriteByte(tonumber(bit.bor(bit.band(value, 127), 128)))
value = bit.rshift(value, 7)
output_size = output_size + 1
end
self:WriteByte(tonumber(bit.band(value, 127)))
return output_size
end
-- consistency
META.ReadUnsignedByte = META.ReadByte
META.WriteUnsignedByte = META.WriteByte
function META:WriteTable(tbl, type_func)
type_func = type_func or _G.type
for k, v in pairs(tbl) do
local t = type_func(k)
local id = self:GetTypeID(t)
if not id then error("tried to write unknown type " .. t, 2) end
self:WriteByte(id)
self:WriteType(k, t, type_func)
t = type_func(v)
id = self:GetTypeID(t)
if not id then error("tried to write unknown type " .. t, 2) end
self:WriteByte(id)
self:WriteType(v, t, type_func)
end
end
function META:ReadTable()
local tbl = {}
while true do
local b = self:ReadByte()
local t = self:GetTypeFromID(b)
if not t then error("typeid " .. b .. " is unknown!", 2) end
local k = self:ReadType(t)
b = self:ReadByte()
t = self:GetTypeFromID(b)
if not t then error("typeid " .. b .. " is unknown!", 2) end
tbl[k] = self:ReadType(t)
if self:TheEnd() then return tbl end
end
end
function META:ReadULEB()
local result, shift = 0, 0
while not self:TheEnd() do
local b = self:ReadByte()
result = bit.bor( result, bit.lshift( bit.band( b, 0x7f ), shift ) )
if bit.band( b, 0x80 ) == 0 then break end
shift = shift + 7
end
return result
end
end
do -- structures
function META:WriteStructure(structure, values)
for _, data in ipairs(structure) do
if type(data) == "number" then
self:WriteByte(data)
else
if data.get then
if type(data.get) == "function" then
self:WriteType(data.get(values), data[1])
else
if not values or values[data.get] == nil then
errorf("expected %s %s got nil", 2, data[1], data.get)
end
self:WriteType(values[data.get], data[1])
end
else
self:WriteType(data[2], data[1])
end
end
end
end
local cache = table.weak()
function META:ReadStructure(structure, ordered)
if cache[structure] then
return self:ReadStructure(cache[structure], ordered)
end
if type(structure) == "string" then
-- if the string is something like "vec3" just call ReadType
if META.read_functions[structure] then
return self:ReadType(structure)
end
local data = header_to_table(structure)
cache[structure] = data
return self:ReadStructure(data, ordered)
end
if self:GetSize() == 0 then return end
local out = {}
for i, data in ipairs(structure) do
if data.match then
local key, val = next(data.match)
if (type(val) == "function" and not val(out[key])) or out[key] ~= val then
goto continue_
end
end
local read_type = data.signed and data[1] or "unsigned " .. data[1]
local val
if data.length then
local length = data.length
if type(length) == "string" then
if out[length] then
length = out[length]
else
error(length .. " is not defined!")
end
end
if data[1] == "char" or data[1] == "string" then
val = self:ReadString(length)
else
local values = {}
for i = 1, length do
values[i] = self:ReadType(read_type)
end
val = values
end
else
if data[1] == "bufferpos" then
val = self:GetPosition()
else
val = self:ReadType(read_type)
if data.swap_endianess then
local size = 16
if read_type:find("32", nil, true) or read_type:find("long", nil, true) then
size = 32 -- asdasdasd
end
val = swap_endian(val, size)
end
end
end
if data.assert then
if val ~= data.assert then
errorf("error in header: %s %s expected %s got %s", 2, data[1], data[2], data.assert, (type(val) == "number" and ("%X"):format(val) or val))
end
end
if data.translate then
val = data.translate[val] or val
end
if not data.padding then
if val == nil then val = "nil" end
local key = data[2]
if ordered then
table.insert(out, {key = key, val = val})
else
if out[key] then
key = key .. i
end
out[key] = val
end
end
if type(data[3]) == "table" then
local tbl = {}
if ordered then
table.insert(out, {key = data[2], val = tbl})
else
out[data[2]] = tbl
end
for _ = 1, val do
table.insert(tbl, self:ReadStructure(data[3], ordered))
end
end
if data.switch then
for k, v in pairs(self:ReadStructure(data.switch[val], ordered)) do
if ordered then
table.insert(out, {key = k, val = v})
else
out[k] = v
end
end
end
::continue_::
end
return out
end
function META:GetStructureSize(structure)
if type(structure) == "string" then
return self:GetStructureSize(header_to_table(structure))
end
local size = 0
for _, v in ipairs(structure) do
local t = v[1]
if t == "longlong" then t = "long long" end
if t == "byte" then t = "uint8_t" end
if structs.GetStructMeta(t) then
size = size + structs.GetStructMeta(t).byte_size
elseif ffi then
size = size + ffi.sizeof(t)
end
end
return size
end
end
do -- automatic
function META:GenerateTypes()
local read_functions = {}
local write_functions = {}
for k, v in pairs(META) do
if type(k) == "string" then
local key = k:match("Read(.+)")
if key then
read_functions[key:lower()] = v
if key:find("Unsigned") then
key = key:gsub("(Unsigned)(.+)", "%1 %2")
read_functions[key:lower()] = v
end
end
key = k:match("Write(.+)")
if key then
write_functions[key:lower()] = v
if key:find("Unsigned") then
key = key:gsub("(Unsigned)(.+)", "%1 %2")
write_functions[key:lower()] = v
end
end
end
end
META.read_functions = read_functions
META.write_functions = write_functions
local ids = {}
for k in pairs(read_functions) do
table.insert(ids, k)
end
table.sort(ids, function(a, b) return a > b end)
META.type_ids = ids
end
META:GenerateTypes()
function META:WriteType(val, t, type_func)
t = t or type(val)
if META.write_functions[t] then
if t == "table" then
return META.write_functions[t](self, val, type_func)
else
return META.write_functions[t](self, val)
end
end
error("tried to write unknown type " .. t, 2)
end
function META:ReadType(t, signed)
if META.read_functions[t] then
return META.read_functions[t](self, signed)
end
error("tried to read unknown type " .. t, 2)
end
function META:GetTypeID(val)
for k,v in ipairs(META.type_ids) do
if v == val then
return k
end
end
end
function META:GetTypeFromID(id)
return META.type_ids[id]
end
end
do -- push pop position
function META:PushPosition(pos)
if pos >= self:GetSize() then error("position pushed is larger than reported size of buffer", 2) end
self.push_pop_pos_stack = self.push_pop_pos_stack or {}
table.insert(self.push_pop_pos_stack, self:GetPosition())
self:SetPosition(pos)
end
function META:PopPosition()
self:SetPosition(table.remove(self.push_pop_pos_stack))
end
end
function META:TheEnd()
return self:GetPosition() >= self:GetSize()
end
function META:PeakByte()
return self:ReadByte(), self:Advance(-1)
end
function META:PeakBytes(len)
return self:ReadBytes(len), self:Advance(-len)
end
function META:Advance(i)
i = i or 1
local pos = self:GetPosition() + i
self:SetPosition(pos)
return pos
end
META.__len = META.GetSize
function META:GetDebugString()
self:PushPosition(1)
local str = self:GetString():readablehex()
self:PopPosition()
return str
end
do -- read bits
function META:RestartReadBits()
self.buf_byte = 0
self.buf_nbit = 0
end
function META:BitsLeftInByte()
return self.buf_nbit
end
function META:ReadBits(nbits)
if nbits == 0 then return 0 end
for i = 0, nbits, 8 do
if self.buf_nbit >= nbits then break end
self.buf_byte = self.buf_byte + bit.lshift(self:ReadByte(), self.buf_nbit)
self.buf_nbit = self.buf_nbit + 8
end
self.buf_nbit = self.buf_nbit - nbits
local bits
if nbits == 32 then
bits = self.buf_byte
self.buf_byte = 0
else
bits = bit.band(self.buf_byte, bit.rshift(0xffffffff, 32 - nbits))
self.buf_byte = bit.rshift(self.buf_byte, nbits)
end
return bits
end
end end)( CONTEXT);
prototype.Register(CONTEXT, "file_system", CONTEXT.Name)
end)( vfs);
(function(...) local vfs = (...) or _G.vfs
function vfs.GetFiles(info)
local out = {}
if info.verbose then
local i = 1
for _, data in ipairs(vfs.TranslatePath(info.path, true)) do
local found = data.context:CacheCall("GetFiles", data.path_info)
if found then
local prefix = data.context.Name .. ":" .. data.path_info.full_path
for _, name in ipairs(found) do
if not info.filter or name:find(info.filter, info.filter_pos, info.filter_plain) then
out[i] = {
name = name,
filesystem = data.context.Name,
full_path = prefix .. name,
userdata = data.userdata,
}
i = i + 1
end
end
end
end
if not info.no_sort then
if info.reverse_sort then
table.sort(out, function(a, b) return a.full_path:lower() > b.full_path:lower() end)
else
table.sort(out, function(a, b) return a.full_path:lower() < b.full_path:lower() end)
end
end
else
local done = {}
local i = 1
for _, data in ipairs(vfs.TranslatePath(info.path, true)) do
local found = data.context:CacheCall("GetFiles", data.path_info)
if found then
for _, name in ipairs(found) do
if not done[name] then
done[name] = true
if info.full_path then
name = data.path_info.full_path .. name
end
if not info.filter or name:find(info.filter, info.filter_pos, info.filter_plain) then
out[i] = name
i = i + 1
end
end
end
end
end
done = nil
if not info.no_sort then
if info.reverse_sort then
table.sort(out, function(a, b) return a:lower() > b:lower() end)
else
table.sort(out, function(a, b) return a:lower() < b:lower() end)
end
end
end
return out
end
function vfs.Find(path, full_path, reverse_sort, start, plain, verbose)
local path_, filter = path:match("(.+)/(.*)")
if filter then path = path_ end
if filter == "" then filter = nil end
return vfs.GetFiles({
path = path,
filter = filter,
filter_pos = start,
filter_plain = plain,
verbose = verbose,
full_path = full_path,
reverse_sort = reverse_sort,
no_filter = reverse_sort == nil,
})
end
function vfs.Iterate(path, ...)
local tbl = vfs.Find(path, ...)
local i = 1
return function()
local val = tbl[i]
i = i + 1
if val then
return val
end
end
end
do
local out
local function search(path, ext, callback, dir_blacklist, include_directories, userdata)
for _, v in ipairs(vfs.GetFiles({path = path, verbose = true, no_sort = true})) do
local is_dir = vfs.IsDirectory(v.full_path)
if (not ext or v.name:endswiththese(ext)) and (not is_dir or include_directories) then
if callback then
if callback(v.full_path, v.userdata or userdata, v) ~= nil then
return
end
else
table.insert(out, v.full_path)
end
end
if is_dir then
local okay = true
if dir_blacklist then
for i,v in ipairs(dir_blacklist) do
if v.full_path:find(v) then
okay = false
break
end
end
end
if okay then
search(v.full_path .. "/", ext, callback, dir_blacklist, include_directories, v.userdata or userdata)
end
end
end
end
function vfs.GetFilesRecursive(path, ext, callback, dir_blacklist)
out = {}
search(path, ext, callback, dir_blacklist, include_directories)
return out
end
end end)( vfs);
(function(...) local vfs = (...) or _G.vfs
function vfs.FindMixedCasePath(path)
-- try all lower case first just in case
if vfs.IsFile(path:lower()) then
return path:lower()
end
local dir = ""
for _, str in ipairs(path:split("/")) do
for _, found in ipairs(vfs.Find(dir)) do
if found:lower() == str:lower() then
str = found
dir = dir .. str .. "/"
break
end
end
end
dir = dir:sub(0,-2)
if #dir == #path then
wlog("found mixed case path for %s: found %s", dir, path)
return dir
end
wlog("tried to find mixed case path for %s but nothing was found", path, 2)
end
local fs = require("fs")
vfs.OSCreateDirectory = fs.createdir
vfs.OSGetAttributes = fs.getattributes
do
vfs.SetWorkingDirectory = fs.setcd
vfs.GetWorkingDirectory = fs.getcd
if utility.MakePushPopFunction then
utility.MakePushPopFunction(vfs, "WorkingDirectory")
end
end
function vfs.Delete(path, ...)
local abs_path = vfs.GetAbsolutePath(path, ...)
if abs_path then
local ok, err = os.remove(abs_path)
return ok, err
end
local err = ("No such file or directory %q"):format(path)
if CLI then
error(err, 2)
end
return nil, err
end
function vfs.Rename(path, name, ...)
local abs_path = vfs.GetAbsolutePath(path, ...)
if abs_path then
local dst = abs_path:match("(.+/)") .. name
if WINDOWS then
if vfs.IsFile(dst) then
vfs.Delete(dst)
end
end
local ok, err = os.rename(abs_path, dst)
if not ok then
if CLI then
error(err, 2)
else
wlog(err)
end
end
return ok, err
end
local err = ("No such file or directory %q"):format(path)
if CLI then
error(err, 2)
end
return nil, err
end
local function add_helper(name, func, mode, cb)
vfs[name] = function(path, ...)
if cb then cb(path, ...) end
local file, err = vfs.Open(path, mode)
if file then
local args = {...}
if event then
local ret = {event.Call("VFSPre" .. name, path, ...)}
if ret[1] ~= nil then
for i,v in ipairs(args) do
if ret[i] ~= nil then
args[i] = ret[i]
end
end
end
end
local res, err = file[func](file, unpack(args))
file:Close()
if res and event then
local res, err = event.Call("VFSPost" .. name, path, res)
if res ~= nil or err then
if CLI then
debug.trace()
error(err, 2)
end
return res, err
end
end
return res, err
end
if CLI then
logn(path)
error(err, 2)
end
return nil, err
end
end
add_helper("Read", "ReadAll", "read")
add_helper("Write", "WriteBytes", "write", function(path, content, on_change)
path = path:gsub("(.+/)(.+)", function(folder, file_name)
for _, char in ipairs({--[['\\', '/', ]]':', '%*', '%?', '"', '<', '>', '|'}) do
file_name = file_name:gsub(char, "_il" .. char:byte() .. "_")
end
return folder .. file_name
end)
if type(on_change) == "function" then
vfs.MonitorFile(path, function(file_path)
on_change(vfs.Read(file_path), file_path)
end)
on_change(content)
end
if path:startswith("data/") or path:sub(4):startswith("data/") then
if path:startswith("os:") then
path = path:sub(4)
end
path = path:sub(#"data/" + 1)
local fs = vfs.GetFileSystem("os")
if fs then
local base = e.USERDATA_FOLDER
local dir = ""
for folder in path:gmatch("(.-/)") do
dir = dir .. folder
fs:CreateFolder({full_path = base .. dir})
end
end
elseif CLI then
vfs.CreateDirectoriesFromPath(path, true)
end
end)
add_helper("GetLastModified", "GetLastModified", "read")
add_helper("GetLastAccessed", "GetLastAccessed", "read")
add_helper("GetSize", "GetSize", "read")
function vfs.CreateDirectory(path, force)
if vfs.IsDirectory(path) then return true end
local path_info = vfs.GetPathInfo(path, true)
local dir_name = vfs.GetFolderNameFromPath(path_info.full_path) or path_info.full_path
local parent_dir = vfs.GetParentFolderFromPath(path_info.full_path)
local full_path = vfs.GetAbsolutePath(parent_dir, true)
if not full_path then return nil, "directory " .. parent_dir .. " does not exist" end
local path_info = vfs.GetPathInfo(path_info.filesystem .. ":" .. full_path)
if path_info.filesystem == "unknown" then
return nil, "filesystem must be explicit when creating directories"
end
path_info.full_path = path_info.full_path .. dir_name .. "/"
return vfs.GetFileSystem(path_info.filesystem):CreateFolder(path_info, force)
end
function vfs.IsDirectory(path)
if path == "" then return false end
for _, data in ipairs(vfs.TranslatePath(path, true)) do
if data.context:CacheCall("IsFolder", data.path_info) then
return true
end
end
return false
end
function vfs.IsFile(path)
if path == "" then return false end
for _, data in ipairs(vfs.TranslatePath(path)) do
if data.context:CacheCall("IsFile", data.path_info) then
return true
end
end
return false
end
function vfs.IsFolderValid(path)
if path == "" then return false, "path is nothing" end
local path, err = vfs.GetAbsolutePath(path)
if not path then
return false, err
end
local path_info = vfs.GetPathInfo(path, true)
local errors = ""
for _, context in ipairs(vfs.GetFileSystems()) do
if context:IsArchive(path_info) then
local ok, err = context:IsFolderValid(path_info)
if ok then
return true
end
if err then
errors = errors .. err .. "\n"
end
end
end
return false, errors
end
function vfs.Exists(path)
return vfs.IsDirectory(path) or vfs.IsFile(path)
end end)( vfs);
(function(...) local vfs = (...) or _G.vfs
vfs.loaded_addons = vfs.loaded_addons or {}
vfs.disabled_addons = vfs.disabled_addons or {}
function vfs.MountAddons(dir)
for info in vfs.Iterate(dir, true, nil, nil, nil, true) do
if info.name ~= e.INTERNAL_ADDON_NAME then
if
vfs.IsDirectory(info.full_path:sub(#info.filesystem + 2)) and
not info.name:startswith(".") and
not info.name:startswith("__") and
(info.name ~= "data" and info.filesystem == "os")
then
vfs.MountAddon(info.full_path:sub(#info.filesystem + 2) .. "/")
end
end
end
end
function vfs.SortAddonsAfterPriority()
local vfs_loaded_addons = copy
local found = {}
local not_found = {}
local done = {}
local function sort_dependencies(info)
if done[info] then return end
done[info] = true
local found_addon = false
if info.dependencies then
for _, name in ipairs(info.dependencies) do
for _, info in ipairs(vfs.loaded_addons) do
if info.name == name and info.dependencies then
sort_dependencies(info)
found_addon = true
break
end
end
end
end
if found_addon then
table.insert(found, info)
else
table.insert(not_found, info)
end
end
for _, info in ipairs(vfs.loaded_addons) do
sort_dependencies(info)
end
table.sort(not_found, function(a,b) return a.priority > b.priority end)
table.add(found, not_found)
vfs.loaded_addons = found
end
function vfs.GetAddonInfo(addon)
for _, info in pairs(vfs.loaded_addons) do
if info.name == addon then
return info
end
end
return {}
end
local function check_dependencies(info, what)
if info.dependencies then
for i, name in ipairs(info.dependencies) do
local found = false
for i,v in ipairs(vfs.loaded_addons) do
if v.name == name then
found = true
break
end
end
if not found then
if what then llog(info.name, ": could not ", what ," because it depends on ", name) end
return false
end
end
end
return true
end
function vfs.InitAddons()
for _, info in pairs(vfs.GetMountedAddons()) do
if info.startup and check_dependencies(info, "init") then
runfile(info.startup)
end
end
end
function vfs.AutorunAddon(addon, folder, force)
local info = type(addon) == "table" and addon or vfs.GetAddonInfo(addon)
if force or info.load ~= false and not info.core then
_G.INFO = info
local function run()
if not check_dependencies(info, "run autorun " .. folder .. "*") then
return
end
-- autorun folders
for path in vfs.Iterate(info.path .. "lua/autorun/" .. folder, true) do
if path:find("%.lua") then
local ok, err = system.pcall(vfs.RunFile, path)
if not ok then
wlog(err)
end
end
end
end
if info.event then
event.AddListener(info.event, "addon_" .. folder, function()
run()
return e.EVENT_DESTROY
end)
else
run()
end
_G.INFO = nil
else
--logf("the addon %q does not want to be loaded\n", info.name)
end
end
function vfs.GetMountedAddons()
return vfs.loaded_addons
end
function vfs.AutorunAddons(folder, force)
folder = folder or ""
if not CLI then
utility.PushTimeWarning()
end
for _, info in pairs(vfs.GetMountedAddons()) do
vfs.AutorunAddon(info, folder, force)
end
if not CLI then
utility.PopTimeWarning("autorun " .. folder .. "*", 0.1)
end
end
function vfs.MountAddon(path, force)
local info = {}
if vfs.IsFile(path .. "config.lua") then
local func, err = vfs.LoadFile(path .. "config.lua")
if func then
info = func() or info
else
wlog(err)
end
end
if vfs.IsFile(path .. "addon.json") then
info.load = false
info.gmod_addon = true
end
local folder = path:match(".+/(.+)/")
info.path = path
info.file_info = folder
info.name = info.name or folder
info.folder = folder
info.priority = info.priority or -1
if not info.startup and vfs.IsFile(path .. "lua/init.lua") then
info.startup = path .. "lua/init.lua"
end
if info.dependencies and type(info.dependencies) == "string" then
info.dependencies = {info.dependencies}
end
table.insert(vfs.loaded_addons, info)
e["ADDON_" .. info.name:upper()] = info
vfs.SortAddonsAfterPriority()
if info.load == false and not force then
table.insert(vfs.disabled_addons, info)
return false
end
vfs.Mount(path)
if vfs.IsDirectory(path .. "addons") then
vfs.MountAddons(path .. "addons/")
end
return true
end
return vfs
end)( vfs);
(function(...) local vfs = (...) or _G.vfs
vfs.files_ran_ = vfs.files_ran_ or {}
local function store_run_file_path(path)
vfs.files_ran = nil
table.insert(vfs.files_ran_, path)
end
function loadfile(path, ...)
store_run_file_path(path)
return _OLD_G.loadfile(path, ...)
end
function dofile(path, ...)
store_run_file_path(path)
return _OLD_G.dofile(path, ...)
end
do
local first = true
local resolved = {}
function vfs.GetLoadedLuaFiles()
if not vfs.files_ran then
vfs.files_ran = {}
for _, path in ipairs(vfs.files_ran_) do
local full_path = vfs.GetAbsolutePath(path, false) or path
vfs.files_ran[full_path] = vfs.OSGetAttributes(full_path)
end
end
return vfs.files_ran
end
end
function vfs.LoadFile(path, chunkname)
local full_path = vfs.GetAbsolutePath(path, false)
if full_path then
if event then full_path = event.Call("PreLoadFile", full_path) or full_path end
local res, err = vfs.Read(full_path)
if not res and not err then
res = ""
end
if not res then
return res, err, full_path
end
if event then res = event.Call("PreLoadString", res, full_path) or res end
-- prepend "@" in front of the path so it will be treated as a lua file and not a string by lua internally
-- for nicer error messages and debug
if vfs.modify_chunkname then
chunkname = vfs.modify_chunkname(full_path)
end
res, err = loadstring(res, chunkname or "@" .. full_path:replace(e.ROOT_FOLDER, ""))
if event and res then res = event.Call("PostLoadString", res, full_path) or res end
store_run_file_path(full_path)
return res, err, full_path
end
return nil, path .. ": No such file or directory"
end
function vfs.DoFile(path, ...)
return assert(vfs.LoadFile(path))(...)
end
do -- runfile
local filerun_stack = vfs.filerun_stack or {}
vfs.filerun_stack = filerun_stack
function vfs.PushToFileRunStack(path)
table.insert(filerun_stack, path)
end
function vfs.PopFromFileRunStack()
table.remove(filerun_stack)
end
function vfs.GetFileRunStack()
return filerun_stack
end
local function not_found(err)
return
err and
(
err:find("No such file or directory", nil, true) or
err:find("Invalid argument", nil, true)
)
end
local system_pcall = true
function vfs.RunFile(source, ...)
if type(source) == "table" then
system_pcall = false
local ok, err
local errors = {}
for _, path in ipairs(source) do
ok, err = vfs.RunFile(path)
if ok == false then
table.insert(errors, err .. ": " .. path)
else
return ok
end
end
system_pcall = true
if ok == false then
err = table.concat(errors, "\n")
else
ok = true
err = nil
end
return ok, err
end
if source:startswith("!") then
source = source:sub(2)
if filerun_stack[#filerun_stack] then
source = filerun_stack[#filerun_stack]:match("(.+/).-/") .. source
end
end
local dir, file = source:match("(.+/)(.+)")
if not dir then
dir = ""
file = source
end
if file == "*" then
local previous_dir = filerun_stack[#filerun_stack]
local original_dir = dir
if previous_dir then
dir = previous_dir .. dir
end
if not vfs.IsDirectory(dir) then
dir = original_dir
end
for script in vfs.Iterate(dir .. ".+%.lua", true) do
local func, err, full_path = vfs.LoadFile(script)
if func then
vfs.PushToFileRunStack(full_path:match("(.+/)") or dir)
_G.FILE_PATH = full_path
_G.FILE_NAME = full_path:match(".*/(.+)%.") or full_path
_G.FILE_EXTENSION = full_path:match(".*/.+%.(.+)")
if not CLI and utility and utility.PushTimeWarning then
utility.PushTimeWarning()
end
local ok, err
if system_pcall and system and system.pcall then
ok, err = system.pcall(func, ...)
else
ok, err = pcall(func, ...)
end
if not CLI and utility and utility.PushTimeWarning then
utility.PopTimeWarning(full_path, 0.01)
end
_G.FILE_NAME = nil
_G.FILE_PATH = nil
_G.FILE_EXTENSION = nil
if not ok then logn(err) end
vfs.PopFromFileRunStack()
end
if not func then
logn(err)
end
end
return
end
local previous_dir = filerun_stack[#filerun_stack]
if previous_dir then
dir = previous_dir .. dir
end
local full_path
local err
local func
local path = source
if vfs.IsPathAbsolute(path) then
func, err, full_path = vfs.LoadFile(path)
else
if path:startswith("lua/") then
func, err, full_path = vfs.LoadFile(path)
end
if not func then
-- try first with the last directory
-- once with lua prepended
path = dir .. file
func, err, full_path = vfs.LoadFile(path)
if not_found(err) then
if path ~= dir .. file then
path = dir .. file
func, err, full_path = vfs.LoadFile(path)
end
-- and without the last directory
-- once with lua prepended
if not_found(err) then
path = source
func, err, full_path = vfs.LoadFile(path)
end
end
end
end
if func then
dir = path:match("(.+/)(.+)")
if not full_path:startswith(e.ROOT_FOLDER) then
vfs.PushWorkingDirectory(dir)
end
vfs.PushToFileRunStack(dir)
_G.FILE_PATH = full_path
_G.FILE_NAME = full_path:match(".*/(.+)%.") or full_path
_G.FILE_EXTENSION = full_path:match(".*/.+%.(.+)")
if full_path:find(e.ROOT_FOLDER, nil, true) then
utility.PushTimeWarning()
end
local res
if system_pcall and system and system.pcall then
res = {system.pcall(func, ...)}
else
res = {pcall(func, ...)}
end
if full_path:find(e.ROOT_FOLDER, nil, true) then
utility.PopTimeWarning(full_path:gsub(e.ROOT_FOLDER, ""), 0.025, "[runfile]")
end
_G.FILE_PATH = nil
_G.FILE_NAME = nil
_G.FILE_EXTENSION = nil
if not res[1] then
logn(res[2])
end
vfs.PopFromFileRunStack()
if not full_path:startswith(e.ROOT_FOLDER) then
vfs.PopWorkingDirectory(dir)
end
return select(2, unpack(res))
end
if system_pcall and full_path then
err = err or "no error"
logn(source:sub(1) .. " " .. err)
debug.openscript(full_path, err:match(":(%d+)"))
end
return false, err
end
end
-- although vfs will add a loader for each mount, the module folder has to be an exception for modules only
-- this loader should support more ways of loading than just adding ".lua"
function vfs.AddPackageLoader(func, loaders)
loaders = loaders or package.loaders
for i, v in ipairs(loaders) do
if v == func then
table.remove(loaders, i)
break
end
end
table.insert(loaders, func)
end
local function handle_dir(dir, path)
if path:startswith("..") then
local last_dir = vfs.GetFileRunStack()[#vfs.GetFileRunStack()]
local dir = vfs.FixPathSlashes(vfs.GetParentFolderFromPath(last_dir, 1) .. path:sub(3))
return dir
elseif path:startswith(".") then
return vfs.FixPathSlashes(vfs.GetFileRunStack()[#vfs.GetFileRunStack()] .. path:sub(2))
end
return dir .. path
end
function vfs.AddModuleDirectory(dir, loaders)
loaders = loaders or package.loaders
do -- relative path
vfs.AddPackageLoader(function(path)
return vfs.LoadFile(handle_dir(dir, path) .. ".lua")
end, loaders)
vfs.AddPackageLoader(function(path)
local path, count = path:gsub("(.)%.(.)", "%1/%2")
if count == 0 then return end
return vfs.LoadFile(handle_dir(dir, path) .. ".lua")
end, loaders)
vfs.AddPackageLoader(function(path)
return vfs.LoadFile(handle_dir(dir, path))
end, loaders)
end
vfs.AddPackageLoader(function(path)
return vfs.LoadFile(handle_dir(dir, path) .. "/"..path..".lua")
end, loaders)
vfs.AddPackageLoader(function(path)
return vfs.LoadFile(handle_dir(dir, path) .. "/init.lua")
end, loaders)
-- again but with . replaced with /
vfs.AddPackageLoader(function(path)
path = path:gsub("\\", "/"):gsub("(%a)%.(%a)", "%1/%2")
return vfs.LoadFile(handle_dir(dir, path) .. ".lua")
end, loaders)
vfs.AddPackageLoader(function(path)
path = path:gsub("\\", "/"):gsub("(%a)%.(%a)", "%1/%2")
return vfs.LoadFile(handle_dir(dir, path) .. "/init.lua")
end, loaders)
vfs.AddPackageLoader(function(path)
path = path:gsub("\\", "/"):gsub("(%a)%.(%a)", "%1/%2")
return vfs.LoadFile(handle_dir(dir, path) .. "/" .. path .. ".lua")
end, loaders)
end end)( vfs);
(function(...) local vfs = (...) or _G.vfs
local CONTEXT = {}
CONTEXT.Name = "generic_archive"
function CONTEXT:AddEntry(entry)
self.tree.done_directories = self.tree.done_directories or {}
local directory = entry.full_path:match("(.+)/")
entry.file_name = entry.full_path:match(".+/(.+)")
entry.size = tonumber(entry.size) or 0
--entry.crc = entry.crc or 0
entry.offset = tonumber(entry.offset) or 0
entry.is_file = true
local full_path = entry.full_path
entry.full_path = nil
self.tree:SetEntry(full_path, entry)
self.tree:SetEntry(directory, {path = directory, is_dir = true, file_name = directory:match(".+/(.+)") or directory})
for i = #directory, 1, -1 do
local char = directory:sub(i, i)
if char == "/" then
local dir = directory:sub(0, i)
if dir == "" or self.tree.done_directories[dir] then break end
local file_name = dir:match(".+/(.+)") or dir
if file_name:sub(-1) == "/" then
file_name = file_name:sub(0, -2)
end
self.tree:SetEntry(dir, {path = dir, is_dir = true, file_name = file_name})
self.tree.done_directories[dir] = true
end
end
end
--self:ParseArchive(vfs.Open("os:G:/SteamLibrary/SteamApps/common/Skyrim/Data/Skyrim - Sounds.gma"), "os:G:/SteamLibrary/SteamApps/common/Skyrim/Data/Skyrim - Sounds.gma")
local cache = table.weak()
local never
local modified_cache = {}
function CONTEXT:GetFileTree(path_info)
if never then return false, "recursive call to GetFileTree" end
local archive_path, relative = path_info.full_path:slice((self.NameEndsWith or "") .. "." .. self.Extension .. "/", 0, 1)
if not archive_path then
return false, "not a valid archive path"
end
if not modified_cache[archive_path] then
never = true
modified_cache[archive_path] = vfs.GetLastModified(archive_path) or ""
never = false
end
local cache_key = archive_path .. modified_cache[archive_path]
if cache[cache_key] then
return cache[cache_key], relative, archive_path
end
if not vfs.IsFile(archive_path) then
return false, "not a valid archive path"
end
local cache_path = "os:data/archive_cache/" .. crypto.CRC32(cache_key)
if vfs.IsFile(cache_path) then
never = true
local tree_data, err, what = serializer.ReadFile("msgpack", cache_path)
never = false
if tree_data then
local tree = utility.CreateTree("/", tree_data)
cache[cache_key] = tree
return cache[cache_key], relative, archive_path
end
end
never = true
local file, err = vfs.Open("os:" .. archive_path)
never = false
if not file then
return false, err
end
if not CLI then
llog("generating tree data cache for ", archive_path)
end
local tree = utility.CreateTree("/")
self.tree = tree
local ok, err = self:OnParseArchive(file, archive_path)
self.tree.done_directories = nil
file:Close()
if not ok then
return false, err
end
cache[cache_key] = tree
utility.RunOnNextGarbageCollection(function()
serializer.WriteFile("msgpack", cache_path, tree.tree)
end)
return tree, relative, archive_path
end
function CONTEXT:IsFile(path_info)
local tree, relative, archive_path = self:GetFileTree(path_info)
if not tree then return tree, relative end
local entry, err = tree:GetEntry(relative)
if entry and entry.is_file then
return true
end
end
function CONTEXT:IsFolder(path_info)
local tree, relative, archive_path = self:GetFileTree(path_info)
if relative == "" then return true end
if not tree then return tree, relative end
local entry = tree:GetEntry(relative)
if entry and entry.is_dir then
return true
end
end
function CONTEXT:GetFiles(path_info)
local tree, relative, archive_path = self:GetFileTree(path_info)
if not tree then return tree, relative end
local children, err = tree:GetChildren(relative:match("(.*)/") or relative)
if not children then return false, err end
local out = {}
for _, v in pairs(children) do
if type(v) == "table" and v.v then -- fix me!!
table.insert(out, v.v.file_name)
end
end
return out
end
function CONTEXT:TranslateArchivePath(file_info)
return file_info.archive_path
end
local cache = table.weak()
function CONTEXT:Open(path_info, mode, ...)
if self:GetMode() == "read" then
local tree, relative, archive_path = self:GetFileTree(path_info)
if not tree then
return false, relative
end
local file_info = tree:GetEntry(relative)
if not file_info then
return false, "file not found in archive"
end
if file_info.is_dir then
return false, "file is a directory"
end
local archive_path = self:TranslateArchivePath(file_info, archive_path)
local file, err = cache[archive_path] or vfs.Open(archive_path)
cache[archive_path] = file
if not file then
return false, err
end
file:SetPosition(file_info.offset)
self.position = 0
self.file_info = file_info
if file_info.preload_data then
if file_info.size == #file_info.preload_data then
self.data = file_info.preload_data
else
self.data = file_info.preload_data .. file:ReadBytes(file_info.size-#file_info.preload_data)
end
self.file = nil
else
self.file = file
end
return true
elseif self:GetMode() == "write" then
return false, "write mode not implemented"
end
return false, "read mode " .. self:GetMode() .. " not supported"
end
function CONTEXT:ReadByte()
if self.file_info.preload_data then
local char = self.data:sub(self.position+1, self.position+1)
self.position = math.clamp(self.position + 1, 0, self.file_info.size)
return char:byte()
else
self.file:SetPosition(self.file_info.offset + self.position)
local char = self.file:ReadByte(1)
self.position = math.clamp(self.position + 1, 0, self.file_info.size)
return char
end
end
function CONTEXT:ReadBytes(bytes)
if bytes == math.huge then bytes = self:GetSize() end
if self.file_info.preload_data then
local str = {}
for i = 1, bytes do
local byte = self:ReadByte()
if not byte then return table.concat(str, "") end
str[i] = string.char(byte)
end
return table.concat(str, "")
else
bytes = math.min(bytes, self.file_info.size - self.position)
self.file:SetPosition(self.file_info.offset + self.position)
local str = self.file:ReadBytes(bytes)
self.position = math.clamp(self.position + bytes, 0, self.file_info.size)
if str == "" then str = nil end
return str
end
end
function CONTEXT:SetPosition(pos)
self.position = math.clamp(pos, 0, self.file_info.size)
end
function CONTEXT:GetPosition()
return self.position
end
function CONTEXT:OnRemove()
if self.file and self.file:IsValid() then
self.file = nil -- just unref
end
end
function CONTEXT:GetSize()
return self.file_info.size
end
vfs.RegisterFileSystem(CONTEXT, true) end)( vfs);
(function(...) local vfs = (...) or _G.vfs
local fs = require("fs")
local ffi = require("ffi")
local CONTEXT = {}
CONTEXT.Name = "os"
CONTEXT.Position = 0
function CONTEXT:CreateFolder(path_info, force)
if force or path_info.full_path:startswith(e.DATA_FOLDER) or path_info.full_path:startswith(e.USERDATA_FOLDER) or path_info.full_path:startswith(e.ROOT_FOLDER) then
if self:IsFolder(path_info) then return true end
if force then
if not CLI then
llog("creating directory: ", path_info.full_path)
end
end
local path = path_info.full_path
--if path:endswith("/") then path = path:sub(0, -2) end
local ok, err = fs.createdir(path)
vfs.ClearCallCache()
return ok, err
end
return false, "directory does not start from goluwa"
end
function CONTEXT:GetFiles(path_info)
if not self:IsFolder(path_info) then
return false, "not a directory"
end
return fs.find(path_info.full_path)
end
function CONTEXT:IsFile(path_info)
local info = fs.getattributes(path_info.full_path)
return info and info.type ~= "directory"
end
function CONTEXT:IsFolder(path_info)
if path_info.full_path:endswith("/") then
local info = fs.getattributes(path_info.full_path:sub(0, -2))
return info and info.type == "directory"
end
end
function CONTEXT:ReadAll()
return self:ReadBytes(math.huge)
end
if fs.open then
-- if CONTEXT:Open errors the virtual file system will assume
-- the file doesn't exist and will go to the next mounted context
local translate_mode = {
read = "r",
write = "w",
}
function CONTEXT:Open(path_info, ...)
local mode = translate_mode[self:GetMode()]
if not mode then return false, "mode not supported" end
self.file = fs.open(path_info.full_path, mode .. "b")
if self.file == nil then
return false, "unable to open file: " .. ffi.strerror()
end
self.attributes = fs.getattributes(path_info.full_path)
end
function CONTEXT:WriteBytes(str)
return fs.write(str, 1, #str, self.file)
end
local ctype = ffi.typeof("uint8_t[?]")
local ffi_string = ffi.string
local math_min = math.min
-- without this cache thing loading gm_construct takes 30 sec opposed to 15
local cache = {}
for i = 1, 32 do
cache[i] = ctype(i)
end
function CONTEXT:ReadBytes(bytes)
bytes = math_min(bytes, self.attributes.size)
local buff = bytes > 32 and ctype(bytes) or cache[bytes]
if self.memory then
local mem_pos_start = math_min(tonumber(self.mem_pos), self.attributes.size)
local mem_pos_stop = math_min(tonumber(mem_pos_start + bytes), self.attributes.size)
local i = 0
for mem_i = mem_pos_start, mem_pos_stop-1 do
buff[i] = self.memory[mem_i]
i = i + 1
end
self.mem_pos = self.mem_pos + bytes
return ffi.string(buff, bytes)
else
local len = fs.read(buff, bytes, 1, self.file)
if len > 0 or fs.eof(self.file) == 1 then
return ffi_string(buff, bytes)
end
end
end
function CONTEXT:LoadToMemory()
local bytes = self:GetSize()
local buffer = ctype(bytes)
local len = fs.read(buffer, bytes, 1, self.file)
self.memory = buffer
self:SetPosition(ffi.new("uint64_t", 0))
self:OnRemove()
end
function CONTEXT:SetPosition(pos)
if self.memory then
self.mem_pos = pos
else
fs.seek(self.file, pos, 0)
end
end
function CONTEXT:GetPosition()
if self.memory then
return self.mem_pos
else
return fs.tell(self.file)
end
end
function CONTEXT:OnRemove()
if self.file ~= nil then
fs.close(self.file)
self.file = nil
end
end
else
local translate_mode = {
read = "r",
write = "w",
}
function CONTEXT:Open(path_info, ...)
local mode = translate_mode[self:GetMode()]
if not mode then return false, "mode not supported" end
local f, err = io.open(path_info.full_path, mode .. "b")
self.file = f
if self.file == nil then
return false, "unable to open file: " .. err
end
self.attributes = fs.getattributes(path_info.full_path)
end
function CONTEXT:WriteBytes(str)
return self.file:write(str)
end
function CONTEXT:ReadBytes(bytes)
bytes = math.min(bytes, self.attributes.size)
return self.file:read(bytes)
end
function CONTEXT:SetPosition(pos)
self.file:seek("set", pos)
end
function CONTEXT:GetPosition()
return self.file:seek("cur")
end
function CONTEXT:OnRemove()
if self.file ~= nil then
self.file:close()
self.file = nil
end
end
end
function CONTEXT:GetSize()
return self.attributes.size
end
function CONTEXT:GetLastModified()
return self.attributes.last_modified
end
function CONTEXT:GetLastAccessed()
return self.attributes.last_accessed
end
function CONTEXT:Flush()
--self.file:flush()
end
vfs.RegisterFileSystem(CONTEXT) end)( vfs);
for _, context in ipairs(vfs.GetFileSystems()) do
if context.VFSOpened then
context:VFSOpened()
end
end
return vfs
end)();
vfs.Mount("os:" .. e.USERDATA_FOLDER, "os:data") -- mount "ROOT/data/users/*username*/" to "/data/"
vfs.MountAddon("os:" .. e.CORE_FOLDER) -- mount "ROOT/"..e.INTERNAL_ADDON_NAME to "/"
vfs.GetAddonInfo(e.INTERNAL_ADDON_NAME).dependencies = {e.INTERNAL_ADDON_NAME} -- prevent init.lua from running later on again
vfs.GetAddonInfo(e.INTERNAL_ADDON_NAME).startup = nil -- prevent init.lua from running later on again
vfs.AddModuleDirectory("lua/modules/")
_G.runfile = vfs.RunFile
_G.R = vfs.GetAbsolutePath -- a nice global for loading resources externally from current dir
-- libraries
crypto =
(function(...) local crypto = _G.crypto or {}
do
-- https://github.com/lancelijade/qqwry.lua/blob/master/crc32.lua#L133
local CRC32 = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
}
local xor = bit.bxor
local lshift = bit.lshift
local rshift = bit.rshift
local band = bit.band
local cache = table.weak()
function crypto.CRC32(val)
if cache[val] then
return cache[val]
end
local str = tostring(val)
local count = string.len(str)
local crc = 2 ^ 32 - 1
local i = 1
while count > 0 do
local byte = string.byte(str, i)
crc = xor(rshift(crc, 8), CRC32[xor(band(crc, 0xFF), byte) + 1])
i = i + 1
count = count - 1
end
crc = xor(crc, 0xFFFFFFFF)
-- dirty hack for bitop return number < 0
if crc < 0 then crc = crc + 2 ^ 32 end
cache[val] = tostring(crc)
return cache[val]
end
end
-- Lua 5.1+ base64 v3.0 (c) 2009 by Alex Kloss <[email protected]>
-- licensed under the terms of the LGPL2
-- character table string
local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
-- encoding
function crypto.Base64Encode(data)
return ((data:gsub('.', function(x)
local r,b='',x:byte()
for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end
return r;
end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
if (#x < 6) then return '' end
local c=0
for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end
return b:sub(c+1,c+1)
end)..({ '', '==', '=' })[#data%3+1])
end
-- decoding
function crypto.Base64Decode(data)
data = string.gsub(data, '[^'..b..'=]', '')
return (data:gsub('.', function(x)
if (x == '=') then return '' end
local r,f='',(b:find(x)-1)
for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end
return r;
end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
if (#x ~= 8) then return '' end
local c=0
for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end
return string.char(c)
end))
end
return crypto end)();
-- base64 and other hash functions
serializer =
(function(...) local serializer = _G.serializer or {}
serializer.libraries = {}
function serializer.AddLibrary(id, encode, decode, lib)
serializer.libraries[id] = {encode = encode, decode = decode, lib = lib}
end
function serializer.GetAvailible()
return serializer.libraries
end
function serializer.GetLibrary(name)
local lib = serializer.libraries[name] and serializer.libraries[name].lib
if type(lib) == "string" then
lib = require(lib)
serializer.libraries[name].lib = lib
end
return lib
end
function serializer.Encode(lib, ...)
lib = lib or "luadata"
local data = serializer.libraries[lib]
if not data then
error("serializer " .. lib .. " not found", 2)
end
if data.encode then
return data.encode(serializer.GetLibrary(lib), ...)
end
error("encoding not supported", 2)
end
function serializer.Decode(lib, ...)
lib = lib or "luadata"
local data = serializer.libraries[lib]
if not data then
error("serializer " .. lib .. " not found", 2)
end
if data.decode then
return data.decode(serializer.GetLibrary(lib), ...)
end
error("decoding not supported", 2)
end
do -- vfs extension
function serializer.WriteFile(lib, path, ...)
return vfs.Write(path, serializer.Encode(lib, ...))
end
function serializer.ReadFile(lib, path, ...)
local str = vfs.Read(path)
if str then
return serializer.Decode(lib, str)
end
return false, "no such file"
end
function serializer.SetKeyValueInFile(lib, path, key, value)
local tbl = serializer.ReadFile(lib, path) or {}
tbl[key] = value
serializer.WriteFile(lib, path, tbl)
end
function serializer.GetKeyFromFile(lib, path, key, def)
local tbl = serializer.ReadFile(lib, path)
if tbl then
local val = serializer.ReadFile(lib, path)[key]
if val == nil then
return def
end
return val
end
return def
end
function serializer.AppendToFile(lib, path, value)
local tbl = serializer.ReadFile(lib, path) or {}
table.insert(tbl, value)
serializer.WriteFile(lib, path, tbl)
end
end
return serializer end)();
-- for serializing lua data in different formats
system =
(function(...) local system = _G.system or {}
if PLATFORM == "gmod" then
(function(...) local system = ... or _G.system
local ffi = require("ffi")
function system.OpenURL(url)
gmod.gui.OpenURL(url)
end
function system.Sleep(ms)
end
local SysTime = gmod.SysTime
function system.GetTime()
return SysTime()
end
function system.SetConsoleTitleRaw(str)
end
function system.FindFirstTextEditor(os_execute, with_args)
end
function system.SetSharedLibraryPath(path)
end
function system.GetSharedLibraryPath()
return ""
end
function system._OSCommandExists(cmd)
return false
end end)( system);
elseif PLATFORM == "unix" then
(function(...) local system = ... or _G.system
local ffi = require("ffi")
do
local attempts = {
"sensible-browser",
"xdg-open",
"kde-open",
"gnome-open",
}
function system.OpenURL(url)
for _, cmd in ipairs(attempts) do
if os.execute(cmd .. " " .. url) then
return
end
end
wlog("don't know how to open an url (tried: %s)", table.concat(attempts, ", "), 2)
end
end
do
ffi.cdef("void usleep(unsigned int ns);")
function system.Sleep(ms)
ffi.C.usleep(ms*1000)
end
end
do
ffi.cdef([[
struct timespec {
long int tv_sec;
long tv_nsec;
};
int clock_gettime(int clock_id, struct timespec *tp);
]])
local ts = ffi.new("struct timespec")
local enum = 1
local func = ffi.C.clock_gettime
function system.GetTime()
func(enum, ts)
return tonumber(ts.tv_sec) + tonumber(ts.tv_nsec) * 0.000000001
end
end
do
if CURSES then
local iowrite = _OLD_G.io.write
function system.SetConsoleTitleRaw(str)
return iowrite and iowrite('\27]0;', str, '\7') or nil
end
elseif CLI then
local last
function system.SetConsoleTitleRaw(str)
if str ~= last then
for i, v in ipairs(str:split("|")) do
local s = v:trim()
if s ~= "" then
logn(s)
end
end
last = str
end
end
else
function system.SetConsoleTitleRaw(str)
end
end
end
do
local text_editors = {
{
name = "atom",
args = "%PATH%:%LINE%",
},
{
name = "scite",
args = "%PATH% -goto:%LINE%",
},
{
name = "emacs",
args = "+%LINE% %PATH%",
terminal = true,
},
{
name = "vim",
args = "%PATH%:%LINE%",
terminal = true,
},
{
name = "kate",
args = "-l %LINE% %PATH%",
},
{
name = "gedit",
args = "+%LINE% %PATH%",
},
{
name = "nano",
args = "+%LINE% %PATH%",
terminal = true,
},
}
function system.FindFirstTextEditor(os_execute, with_args)
for _, v in pairs(text_editors) do
if io.popen("command -v " .. v.name):read() then
local cmd = v.name
if v.terminal then
cmd = "x-terminal-emulator -e " .. cmd
end
if with_args then
cmd = cmd .. " " .. v.args
end
if os_execute then
cmd = cmd .. " &"
end
return cmd
end
end
end
end
do
function system.SetSharedLibraryPath(path)
os.setenv("LD_LIBRARY_PATH", path)
end
function system.GetSharedLibraryPath()
return os.getenv("LD_LIBRARY_PATH") or ""
end
end
function system._OSCommandExists(cmd)
if io.popen("command -v " .. cmd):read("*all") ~= "" then
return true
end
end end)( system);
elseif PLATFORM == "windows" then
(function(...) local system = ... or _G.system
local ffi = require("ffi")
function system.OpenURL(url)
os.execute(([[explorer "%s"]]):format(url))
end
ffi.cdef("void Sleep(uint32_t);")
function system.Sleep(ms)
ffi.C.Sleep(ms)
end
do
require("winapi.time")
local winapi = require("winapi")
local freq = tonumber(winapi.QueryPerformanceFrequency().QuadPart)
local start_time = winapi.QueryPerformanceCounter()
function system.GetTime()
local time = winapi.QueryPerformanceCounter()
time.QuadPart = time.QuadPart - start_time.QuadPart
return tonumber(time.QuadPart) / freq
end
end
do
ffi.cdef("int SetConsoleTitleA(const char* blah);")
function system.SetConsoleTitleRaw(str)
return ffi.C.SetConsoleTitleA(str)
end
end
do
local text_editors = {
["ZeroBrane.Studio"] = "%PATH%:%LINE%",
["notepad++.exe"] = "\"%PATH%\" -n%LINE%",
["notepad2.exe"] = "/g %LINE% %PATH%",
["sublime_text.exe"] = "%PATH%:%LINE%",
["notepad.exe"] = "/A %PATH%",
}
function system.FindFirstTextEditor(os_execute, with_args)
local app = system.GetRegistryValue("ClassesRoot/.lua/default")
if app then
local path = system.GetRegistryValue("ClassesRoot/" .. app .. "/shell/edit/command/default")
if path then
path = path and path:match("(.-) %%") or path:match("(.-) \"%%")
if path then
if os_execute then
path = "start \"\" " .. path
end
if with_args and text_editors[app] then
path = path .. " " .. text_editors[app]
end
return path
end
end
end
end
end
do
ffi.cdef[[
BOOL SetDllDirectoryA(LPCTSTR lpPathName);
DWORD GetDllDirectoryA(DWORD nBufferLength, LPTSTR lpBuffer);
]]
function system.SetSharedLibraryPath(path)
ffi.C.SetDllDirectoryA(path or "")
end
local str = ffi.new("char[1024]")
function system.GetSharedLibraryPath()
ffi.C.GetDllDirectoryA(1024, str)
return ffi.string(str)
end
end
do
ffi.cdef([[
typedef unsigned goluwa_hkey;
LONG RegGetValueA(goluwa_hkey, LPCTSTR, LPCTSTR, DWORD, LPDWORD, PVOID, LPDWORD);
]])
local advapi = ffi.load("advapi32")
local ERROR_SUCCESS = 0
local HKEY_CLASSES_ROOT = 0x80000000
local HKEY_CURRENT_USER = 0x80000001
local HKEY_LOCAL_MACHINE = 0x80000002
local HKEY_CURRENT_CONFIG = 0x80000005
local RRF_RT_REG_SZ = 0x00000002
local translate = {
HKEY_CLASSES_ROOT = 0x80000000,
HKEY_CURRENT_USER = 0x80000001,
HKEY_LOCAL_MACHINE = 0x80000002,
HKEY_CURRENT_CONFIG = 0x80000005,
ClassesRoot = 0x80000000,
CurrentUser = 0x80000001,
LocalMachine = 0x80000002,
CurrentConfig = 0x80000005,
}
function system.GetRegistryValue(str)
local where, key1, key2 = str:match("(.-)/(.+)/(.*)")
if where then
where, key1 = str:match("(.-)/(.+)/")
end
where = translate[where] or where
key1 = key1:gsub("/", "\\")
key2 = key2 or ""
if key2 == "default" then key2 = nil end
local value = ffi.new("char[4096]")
local value_size = ffi.new("unsigned[1]")
value_size[0] = 4096
local err = advapi.RegGetValueA(where, key1, key2, RRF_RT_REG_SZ, nil, value, value_size)
if err ~= ERROR_SUCCESS then
return
end
return ffi.string(value)
end
end
function system._OSCommandExists(cmd)
return false, "NYI"
end end)( system);
end
function system.ForceMainLoop()
system.force_main_loop = true
end
function system.GetWorkingDirectory()
if CLI then
local dir = os.getenv("GOLUWA_WORKING_DIRECTORY")
if dir then
return vfs.FixPathSlashes("os:" .. dir .. "/")
end
end
return "os:" .. e.USERDATA_FOLDER
end
function system.OSCommandExists(...)
if select("#", ...) > 1 then
for _, cmd in ipairs({...}) do
local ok, err = system.OSCommandExists(cmd)
if not ok then
return false, err
end
end
end
return system._OSCommandExists(...)
end
function system.GetLibraryDependencies(path)
if system.OSCommandExists("ldd", "otool") then
local cmd = system.OSCommandExists("ldd") and "ldd" or "otool -L"
local f = io.popen(cmd .. " " .. path .. " 2>&1")
if f then
local str = f:read("*all")
f:close()
str = str:gsub("(.-\n)", function(line) if not line:find("not found") then return "" end end)
return str
end
end
return "unable to find library dependencies for " .. path .. " because ldd is not an os command"
end
do -- console title
local titles = {}
local titlesi = {}
local str = ""
local last_title
local lasttbl = {}
function system.SetConsoleTitle(title, id)
local time = system.GetElapsedTime()
if not lasttbl[id] or lasttbl[id] < time then
if id then
if title then
if not titles[id] then
titles[id] = {title = title}
table.insert(titlesi, titles[id])
end
titles[id].title = title
else
for _, v in ipairs(titlesi) do
if v == titles[id] then
table.remove(titlesi, i)
break
end
end
end
str = "| "
for _, v in ipairs(titlesi) do
str = str .. v.title .. " | "
end
if str ~= last_title then
system.SetConsoleTitleRaw(str)
end
else
str = title
if str ~= last_title then
system.SetConsoleTitleRaw(title)
end
end
last_title = str
lasttbl[id] = system.GetElapsedTime() + 0.05
end
end
function system.GetConsoleTitle()
return str
end
end
do
system.run = true
function system.ShutDown(code)
code = code or 0
if not CLI then
logn("shutting down with code ", code)
end
system.run = code
end
local old = os.exit
function os.exit(code)
wlog("os.exit() called with code %i", code or 0, 2)
--system.ShutDown(code)
end
function os.realexit(code)
old(code)
end
end
local function not_implemented() debug.trace() logn("this function is not yet implemented!") end
do -- frame time
local frame_time = 0.1
function system.GetFrameTime()
return frame_time
end
-- used internally in main_loop.lua
function system.SetFrameTime(dt)
frame_time = dt
end
end
do -- frame time
local frame_time = 0.1
function system.GetInternalFrameTime()
return frame_time
end
-- used internally in main_loop.lua
function system.SetInternalFrameTime(dt)
frame_time = dt
end
end
do -- frame number
local frame_number = 0
function system.GetFrameNumber()
return frame_number
end
-- used internally in main_loop.lua
function system.SetFrameNumber(num)
frame_number = num
end
end
do -- elapsed time (avanved from frame time)
local elapsed_time = 0
function system.GetElapsedTime()
return elapsed_time
end
-- used internally in main_loop.lua
function system.SetElapsedTime(num)
elapsed_time = num
end
end
do -- server time (synchronized across client and server)
local server_time = 0
function system.SetServerTime(time)
server_time = time
end
function system.GetServerTime()
return server_time
end
end
do -- arg is made from luajit.exe
local arg = _G.arg or {}
_G.arg = nil
arg[0] = nil
arg[-1] = nil
table.remove(arg, 1)
function system.GetStartupArguments()
return arg
end
end
do
-- this should be used for xpcall
local suppress = false
function system.OnError(msg, ...)
logsection("lua error", true)
if msg then logn(msg) end
msg = msg or "no error"
if suppress then logn("error in system.OnError: ", msg, ...) logn(debug.traceback()) return end
suppress = true
if event.Call("LuaError", msg) == false then return end
if msg:find("stack overflow") then
logn(msg)
table.print(debug.getinfo(3))
elseif msg:find("\n") then
-- if the message contains a newline it's
-- probably not a good idea to do anything fancy
logn(msg)
else
logn("STACK TRACE:")
logn("{")
local data = {}
for level = 3, 100 do
local info = debug.getinfo(level)
if info then
info.source = debug.getprettysource(level)
local args = {}
for arg = 1, info.nparams do
local key, val = debug.getlocal(level, arg)
if type(val) == "table" then
val = tostring(val)
else
val = serializer.GetLibrary("luadata").ToString(val)
if val and #val > 200 then
val = val:sub(0, 200) .. "...."
end
end
table.insert(args, ("%s = %s"):format(key, val))
end
info.arg_line = table.concat(args, ", ")
info.name = info.name or "unknown"
table.insert(data, info)
else
break
end
end
local function resize_field(tbl, field)
local length = 0
for _, info in pairs(tbl) do
local str = tostring(info[field])
if str then
if #str > length then
length = #str
end
info[field] = str
end
end
for _, info in pairs(tbl) do
local str = info[field]
if str then
local diff = length - #str:split("\n")[1]
if diff > 0 then
info[field] = str .. (" "):rep(diff)
end
end
end
end
table.insert(data, {currentline = "LINE:", source = "SOURCE:", name = "FUNCTION:", arg_line = " ARGUMENTS "})
resize_field(data, "currentline")
resize_field(data, "source")
resize_field(data, "name")
for _, info in npairs(data) do
logf(" %s %s %s (%s)\n", info.currentline, info.source, info.name, info.arg_line)
end
table.clear(data)
logn("}")
logn("LOCALS: ")
logn("{")
for _, param in pairs(debug.getparamsx(4)) do
--if not param.key:find("(",nil,true) then
local val
if type(param.val) == "table" then
val = tostring(param.val)
elseif type(param.val) == "string" then
val = param.val:sub(0, 10)
if val ~= param.val then
val = val .. " .. " .. utility.FormatFileSize(#param.val)
end
else
val = serializer.GetLibrary("luadata").ToString(param.val)
end
table.insert(data, {key = param.key, value = val})
--end
end
table.insert(data, {key = "KEY:", value = "VALUE:"})
resize_field(data, "key")
resize_field(data, "value")
for _, info in npairs(data) do
logf(" %s %s\n", info.key, info.value)
end
logn("}")
logn("ERROR:")
logn("{")
local source, _msg = msg:match("(.+): (.+)")
if source then
source = source:trim()
local info = debug.getinfo(2)
logn(" ", info.currentline, " ", info.source)
logn(" ", _msg:trim())
else
logn(msg)
end
logn("}")
logn("")
end
logsection("lua error", false)
suppress = false
end
function system.pcall(func, ...)
return xpcall(func, system.OnError, ...)
end
end
return system
end)();
-- os and luajit related functions like creating windows or changing jit options
event =
(function(...) local event = _G.event or {}
event.active = event.active or {}
event.destroy_tag = {}
e.EVENT_DESTROY = event.destroy_tag
local function sort_events()
for key, tbl in pairs(event.active) do
local new = {}
for _, v in pairs(tbl) do table.insert(new, v) end
table.sort(new, function(a, b) return a.priority > b.priority end)
event.active[key] = new
end
end
function event.AddListener(event_type, id, callback, config)
if type(event_type) == "table" then
config = event_type
end
if not callback and type(id) == "function" then
callback = id
id = nil
end
config = config or {}
config.event_type = config.event_type or event_type
config.id = config.id or id
config.callback = config.callback or callback
config.priority = config.priority or 0
-- useful for initialize events
if config.id == nil then
config.id = {}
config.remove_after_one_call = true
end
config.print_str = config.event_type .. "->" .. tostring(config.id)
event.RemoveListener(config.event_type, config.id)
event.active[config.event_type] = event.active[config.event_type] or {}
table.insert(event.active[config.event_type], config)
sort_events()
if event_type ~= "EventAdded" then
event.Call("EventAdded", config)
end
end
event.fix_indices = {}
function event.RemoveListener(event_type, id)
if type(event_type) == "table" then
id = id or event_type.id
event_type = event_type or event_type.event_type
end
if id ~= nil and event.active[event_type] then
if event_type ~= "EventRemoved" then
event.Call("EventRemoved", event.active[event_type])
end
for index, val in pairs(event.active[event_type]) do
if id == val.id then
event.active[event_type][index] = nil
event.fix_indices[event_type] = true
break
end
end
else
--logn(("Tried to remove non existing event '%s:%s'"):format(event, tostring(unique)))
end
end
function event.Call(event_type, a_, b_, c_, d_, e_)
local status, a,b,c,d,e
if event.active[event_type] then
for index = 1, #event.active[event_type] do
local data = event.active[event_type][index]
if not data then break end
if data.self_arg then
if data.self_arg:IsValid() then
if data.self_arg_with_callback then
status, a,b,c,d,e = xpcall(data.callback, data.on_error or system.OnError, a_, b_, c_, d_, e_)
else
status, a,b,c,d,e = xpcall(data.callback, data.on_error or system.OnError, data.self_arg, a_, b_, c_, d_, e_)
end
else
event.RemoveListener(event_type, data.id)
event.active[event_type][index] = nil
sort_events()
llog("[%q][%q] removed because self is invalid", event_type, data.unique)
return
end
else
status, a,b,c,d,e = xpcall(data.callback, data.on_error or system.OnError, a_, b_, c_, d_, e_)
end
if a == event.destroy_tag or data.remove_after_one_call then
event.RemoveListener(event_type, data.id)
else
if status == false then
if type(data.on_error) == "function" then
data.on_error(a, event_type, data.id)
else
event.RemoveListener(event_type, data.id)
llog("[%q][%q] removed", event_type, data.id)
end
end
if a ~= nil then
return a,b,c,d,e
end
end
end
end
if event.fix_indices[event_type] then
table.fixindices(event.active[event_type])
event.fix_indices[event_type] = nil
sort_events()
end
end
do -- helpers
function event.CreateRealm(config)
if type(config) == "string" then
config = {id = config}
end
return setmetatable({}, {
__index = function(_, key, val)
for i, data in ipairs(event.active[key]) do
if data.id == config.id then
return config.callback
end
end
end,
__newindex = function(_, key, val)
if type(val) == "function" then
config = table.copy(config)
config.event_type = key
config.callback = val
event.AddListener(config)
elseif val == nil then
config = table.copy(config)
config.event_type = key
event.RemoveListener(config)
end
end,
})
end
end
return event
end)();
-- event handler
utf8 =
(function(...) local utf8 = _G.utf8 or {}
function utf8.midsplit(str)
local half = math.round(str:ulength()/2+1)
return str:usub(1, half-1), str:usub(half)
end
local math_floor = math.floor
function utf8.byte(char, offset)
if char == "" then return -1 end
offset = offset or 1
local byte = char:byte(offset)
if byte and byte >= 128 then
if byte >= 240 then
if #char < 4 then return -1 end
byte = (byte % 8) * 262144
byte = byte + (char:byte(offset + 1) % 64) * 4096
byte = byte + (char:byte(offset + 2) % 64) * 64
byte = byte + (char:byte(offset + 3) % 64)
elseif byte >= 224 then
if #char < 3 then return -1 end
byte = (byte % 16) * 4096
byte = byte + (char:byte(offset + 1) % 64) * 64
byte = byte + (char:byte(offset + 2) % 64)
elseif byte >= 192 then
if #char < 2 then return -1 end
byte = (byte % 32) * 64
byte = byte + (char:byte(offset + 1) % 64)
else
byte = -1
end
end
return byte
end
function utf8.bytelength(char, offset)
local byte = char:byte(offset or 1)
local length = 1
if byte and byte >= 128 then
if byte >= 240 then
length = 4
elseif byte >= 224 then
length = 3
elseif byte >= 192 then
length = 2
end
end
return length
end
function utf8.char(byte)
local utf8 = ""
if byte <= 127 then
utf8 = string.char(byte)
elseif byte < 2048 then
utf8 = ("%c%c"):format(
192 + math_floor(byte / 64),
128 + (byte % 64)
)
elseif byte < 65536 then
utf8 = ("%c%c%c"):format(
224 + math_floor(byte / 4096),
128 + (math_floor(byte / 64) % 64),
128 + (byte % 64)
)
elseif byte < 2097152 then
utf8 = ("%c%c%c%c"):format(
240 + math_floor(byte / 262144),
128 + (math_floor(byte / 4096) % 64),
128 + (math_floor(byte / 64) % 64),
128 + (byte % 64)
)
end
return utf8
end
function utf8.sub(str, i, j)
j = j or -1
local length = 0
-- only set l if i or j is negative
local l = (i >= 0 and j >= 0) or utf8.length(str)
local start_char = (i >= 0) and i or l + i + 1
local end_char = (j >= 0) and j or l + j + 1
-- can't have start before end!
if start_char > end_char then
return ""
end
local pos = 1
local bytes = #str
local start_byte = 1
local end_byte = bytes
for _ = 1, bytes do
length = length + 1
if length == start_char then
start_byte = pos
end
pos = pos + utf8.bytelength(str, pos)
if length == end_char then
end_byte = pos - 1
break
end
end
return str:sub(start_byte, end_byte)
end
local function utf8replace(str, mapping)
local out = {}
for i, char in ipairs(utf8.totable(str)) do
table.insert(out, mapping[char] or char)
end
return table.concat(out)
end
do
local upper = {}
local lower = {}
local function init()
local case =
"Ζ;ζ;Ⓣ;ⓣ;Ḟ;ḟ;Ȗ;ȗ;Ա;ա;U;u;Ė;ė;Ἓ;ἓ;Ś;ś;Ⱋ;ⱋ;Ʊ;ʊ;Ϛ;ϛ;Ⴆ;ⴆ;Ú;ú;О;о;Ṙ;ṙ;Ξ;ξ;Ȟ;ȟ;Թ;թ;Ğ;ğ;"..
"Ḑ;ḑ;LJ;lj;Ⲉ;ⲉ;Ⴡ;ⴡ;Ç;ç;Ⴌ;ⴌ;Ѓ;ѓ;T;t;Ⓓ;ⓓ;Ĺ;ĺ;Ả;ả;Ṛ;ṛ;Ň;ň;Ӈ;ӈ;Ⳉ;ⳉ;Ữ;ữ;Ž;ž;Ͻ;ͻ;Ḓ;ḓ;Ţ;ţ;"..
"Ⲏ;ⲏ;𐐞;𐑆;Ƌ;ƌ;Ʀ;ʀ;Ⴊ;ⴊ;Ⴣ;ⴣ;Ӣ;ӣ;Ǐ;ǐ;Ấ;ấ;Φ;φ;Ḕ;ḕ;Ï;ï;R;r;Ћ;ћ;Ȧ;ȧ;Ħ;ħ;Ⱒ;ⱒ;Ū;ū;Г;г;Ⓩ;ⓩ;"..
"Տ;տ;Ϫ;ϫ;Ῡ;ῡ;Ⲱ;ⲱ;Ⴥ;ⴥ;Ἇ;ἇ;Ǫ;ǫ;P;p;Ỡ;ỡ;Ȯ;ȯ;Ὑ;ὑ;Ʈ;ʈ;Į;į;Ӫ;ӫ;Ύ;ύ;Γ;γ;Ӳ;ӳ;Ḗ;ḗ;Ѳ;ѳ;N;n;"..
"Ү;ү;Л;л;Ὴ;ὴ;Ā;ā;Ҷ;ҷ;Ȁ;ȁ;Λ;λ;Dz;dz;Ἦ;ἦ;V;v;Ѐ;ѐ;DŽ;dž;Ҁ;ҁ;Ԁ;ԁ;Ä;ä;Ḉ;ḉ;Ⱖ;ⱖ;Ʉ;ʉ;𐐕;𐐽;𐐢;𐑊;"..
"Ĉ;ĉ;Ⴔ;ⴔ;Ӻ;ӻ;L;l;Ȉ;ȉ;Ἤ;ἤ;Ķ;ķ;Ḥ;ḥ;Έ;έ;Ϻ;ϻ;Ⱔ;ⱔ;Ԉ;ԉ;Ҿ;ҿ;𐐎;𐐶;Ɍ;ɍ;Ḋ;ḋ;Ō;ō;Ⱦ;ⱦ;G;g;Ⴒ;ⴒ;"..
"Ⰵ;ⰵ;Σ;σ;А;а;Ґ;ґ;Ռ;ռ;Ᾰ;ᾰ;Ἢ;ἢ;Ṋ;ṋ;𐐆;𐐮;Ⱚ;ⱚ;𐐧;𐑏;Đ;đ;𐐣;𐑋;Ɛ;ɛ;Ⲹ;ⲹ;Ϋ;ϋ;𐐥;𐑍;𐐤;𐑌;𐐦;𐑎;O;o;"..
"H;h;Ք;ք;Ἠ;ἠ;𐐡;𐑉;𐐠;𐑈;𐐟;𐑇;Ḍ;ḍ;Ⴘ;ⴘ;Ὰ;ὰ;𐐝;𐑅;𐐜;𐑄;Ṗ;ṗ;Ⱘ;ⱘ;И;и;𐐛;𐑃;Ҙ;ҙ;𐐚;𐑂;Ŕ;ŕ;𐐙;𐑁;𐐘;𐑀;"..
"𐐗;𐐿;Ș;ș;Ḏ;ḏ;Ὤ;ὤ;𐐖;𐐾;𐐔;𐐼;Ⲙ;ⲙ;Θ;θ;𐐒;𐐺;𐐑;𐐹;Ⲍ;ⲍ;Գ;գ;𐐏;𐐷;W;w;𐐍;𐐵;Ƙ;ƙ;Ǥ;ǥ;Ϝ;ϝ;Ⴖ;ⴖ;𐐋;𐐳;"..
"Ł;ł;𐐊;𐐲;𐐉;𐐱;Ŝ;ŝ;Ɂ;ɂ;Ƴ;ƴ;Ⱡ;ⱡ;𐐇;𐐯;𐐅;𐐭;Ü;ü;Ⲽ;ⲽ;𐐄;𐐬;𐐃;𐐫;Ƿ;ƿ;Ḁ;ḁ;𐐂;𐐪;Ձ;ձ;Ի;ի;ᾈ;ᾀ;𐐀;𐐨;"..
"Z;z;Ϸ;ϸ;Ⱐ;ⱐ;Ⴜ;ⴜ;D;d;X;x;W;w;Ⅷ;ⅷ;Ẓ;ẓ;V;v;Ѕ;ѕ;U;u;S;s;Ļ;ļ;Å;å;Ġ;ġ;Ẑ;ẑ;Ơ;ơ;Ὅ;ὅ;Ƞ;ƞ;"..
"É;é;Ộ;ộ;Չ;չ;Ⅳ;ⅳ;O;o;Π;π;Ḃ;ḃ;Р;р;Մ;մ;Ҡ;ҡ;Ᾱ;ᾱ;K;k;J;j;Ὸ;ὸ;I;i;Ⴚ;ⴚ;Ӊ;ӊ;G;g;F;f;H;h;"..
"E;e;Ť;ť;Ṡ;ṡ;A;a;B;b;Ȩ;ȩ;Ⱓ;ⱓ;Ӥ;ӥ;Ց;ց;Ⓧ;ⓧ;Ⳡ;ⳡ;Ψ;ψ;Ѝ;ѝ;Ở;ở;Ⳟ;ⳟ;Э;э;Ⳛ;ⳛ;Њ;њ;Ⳗ;ⳗ;Ⳕ;ⳕ;"..
"Ⳓ;ⳓ;Ϭ;ϭ;Ñ;ñ;Ц;ц;Ⳏ;ⳏ;Ὼ;ὼ;Ǒ;ǒ;Ŭ;ŭ;Ⳍ;ⳍ;Ǭ;ǭ;Ⓜ;ⓜ;Ҩ;ҩ;Ⳋ;ⳋ;P;p;Ḅ;ḅ;Ⳇ;ⳇ;Ⳅ;ⳅ;Ⳃ;ⳃ;Ҟ;ҟ;Ⲿ;ⲿ;"..
"Ⴃ;ⴃ;Ұ;ұ;Ⲻ;ⲻ;Ⲷ;ⲷ;Ε;ε;Ⲵ;ⲵ;Ⲳ;ⲳ;Ѭ;ѭ;Ḻ;ḻ;Ӭ;ӭ;Ḇ;ḇ;Ⴞ;ⴞ;Ⲯ;ⲯ;Ờ;ờ;Ⲭ;ⲭ;Ṍ;ṍ;Ⲫ;ⲫ;Ѵ;ѵ;Ⰽ;ⰽ;Ӵ;ӵ;"..
"Ᵽ;ᵽ;Ⲥ;ⲥ;Ⲣ;ⲣ;Ȱ;ȱ;Ù;ù;Ⲡ;ⲡ;Z;z;X;x;Ⴁ;ⴁ;Ը;ը;Ν;ν;Ǵ;ǵ;Ω;ω;Ṁ;ṁ;Ⓞ;ⓞ;𐐓;𐐻;ᾬ;ᾤ;Ἵ;ἵ;Ɲ;ɲ;ϴ;θ;"..
"Ḹ;ḹ;Ⲕ;ⲕ;Ȃ;ȃ;Ҹ;ҹ;Ῑ;ῑ;Ŵ;ŵ;Ы;ы;Ὥ;ὥ;𐐐;𐐸;Ⲋ;ⲋ;Ђ;ђ;Ⲇ;ⲇ;Ɇ;ɇ;Ⲅ;ⲅ;Ԃ;ԃ;ᾉ;ᾁ;A;a;Ⲃ;ⲃ;П;п;Ѽ;ѽ;"..
"Ↄ;ↄ;Ӽ;ӽ;Ί;ί;Ƹ;ƹ;Ċ;ċ;Ầ;ầ;Ⱬ;ⱬ;Ӌ;ӌ;Ȋ;ȋ;Ⱨ;ⱨ;Ɗ;ɗ;Ɽ;ɽ;Ⴇ;ⴇ;Ǽ;ǽ;Ⲧ;ⲧ;Ɫ;ɫ;𐐈;𐐰;Ⱞ;ⱞ;Ɏ;ɏ;Ἳ;ἳ;"..
"Ԋ;ԋ;Ⱝ;ⱝ;Ҋ;ҋ;ᾏ;ᾇ;Ⱜ;ⱜ;Ὢ;ὢ;Ἣ;ἣ;Ⅻ;ⅻ;Υ;υ;Î;î;I;i;Ⱕ;ⱕ;Ⴅ;ⴅ;Ⳣ;ⳣ;Ⱑ;ⱑ;Ⅽ;ⅽ;Ẁ;ẁ;Y;y;Վ;վ;Ἱ;ἱ;"..
"Ⱏ;ⱏ;Ỉ;ỉ;В;в;Ⅱ;ⅱ;Ⱍ;ⱍ;Ⓑ;ⓑ;Ԓ;ԓ;Ⱊ;ⱊ;Ⱆ;ⱆ;N;n;Ⲩ;ⲩ;Ⱇ;ⱇ;Ẏ;ẏ;Ⱅ;ⱅ;Ⱄ;ⱄ;Ⱃ;ⱃ;Ⱂ;ⱂ;Ⱁ;ⱁ;Ӗ;ӗ;Ⱀ;ⱀ;"..
"Β;β;Ῥ;ῥ;Ē;ē;Ⰿ;ⰿ;Ֆ;ֆ;Ṻ;ṻ;Ȓ;ȓ;Ⰾ;ⰾ;Ⴋ;ⴋ;Ⱈ;ⱈ;Ⰼ;ⰼ;Ⰻ;ⰻ;Ⰺ;ⰺ;Ⰹ;ⰹ;Ö;ö;Ọ;ọ;Ḳ;ḳ;Ⰷ;ⰷ;Ự;ự;ᾋ;ᾃ;"..
"Ŗ;ŗ;Ổ;ổ;Ṉ;ṉ;Ṅ;ṅ;К;к;Ⰳ;ⰳ;Κ;κ;Ⰲ;ⰲ;Ě;ě;Ỗ;ỗ;Ⰱ;ⰱ;Ⰰ;ⰰ;Ț;ț;DZ;dz;Ṽ;ṽ;Ⓦ;ⓦ;Ã;ã;Ṹ;ṹ;Y;y;Ⅹ;ⅹ;"..
"Ӟ;ӟ;Ե;ե;Ⓥ;ⓥ;Ƶ;ƶ;Ϟ;ϟ;Ⓤ;ⓤ;Ş;ş;Ⓢ;ⓢ;Ô;ô;Ḱ;ḱ;Ń;ń;Ⓠ;ⓠ;Ǟ;ǟ;Ȼ;ȼ;Ƀ;ƀ;Є;є;Þ;þ;Ź;ź;Ƈ;ƈ;Ƅ;ƅ;"..
"Ⓚ;ⓚ;Ⓙ;ⓙ;Ⓨ;ⓨ;Ⓗ;ⓗ;Ẃ;ẃ;Ӕ;ӕ;Ճ;ճ;Ϲ;ϲ;Ⓕ;ⓕ;ᾊ;ᾂ;Ṿ;ṿ;Ⓒ;ⓒ;Ї;ї;Ⱌ;ⱌ;B;b;K;k;Ƣ;ƣ;Ⱶ;ⱶ;Ⴢ;ⴢ;Ƚ;ƚ;"..
"Ⅿ;ⅿ;Ḷ;ḷ;Nj;nj;Ľ;ľ;Ⅾ;ⅾ;Ị;ị;Ģ;ģ;Ն;ն;Ң;ң;Ⅺ;ⅺ;Ȣ;ȣ;Ⅸ;ⅸ;Ώ;ώ;Ⅶ;ⅶ;Ǧ;ǧ;Ⅵ;ⅵ;Ծ;ծ;Ḵ;ḵ;Т;т;E;e;"..
"Ə;ə;Ⱎ;ⱎ;Ϧ;ϧ;Ր;ր;Ŧ;ŧ;Ҍ;ҍ;Q;q;Ⓐ;ⓐ;J;j;ῌ;ῃ;Ⴤ;ⴤ;ῼ;ῳ;Ī;ī;Ώ;ώ;Џ;џ;C;c;Ȫ;ȫ;Ὺ;ὺ;Փ;փ;Ῠ;ῠ;"..
"Ó;ó;Ί;ί;Ⱗ;ⱗ;Ῐ;ῐ;Ǔ;ǔ;Ω;ω;Ề;ề;Ή;ή;Ϯ;ϯ;Ὦ;ὦ;Ů;ů;Ὲ;ὲ;Ⴑ;ⴑ;ᾼ;ᾳ;Ṳ;ṳ;Ⓖ;ⓖ;Ǯ;ǯ;Ẇ;ẇ;M;m;Қ;қ;"..
"Ъ;ъ;ᾮ;ᾦ;R;r;Ӧ;ӧ;Ȥ;ȥ;Ḫ;ḫ;ᾪ;ᾢ;Ⲓ;ⲓ;Ἥ;ἥ;ᾨ;ᾠ;Η;η;Ẹ;ẹ;ᾟ;ᾗ;Ὀ;ὀ;Բ;բ;ᾝ;ᾕ;Ӯ;ӯ;À;à;Ἁ;ἁ;ᾛ;ᾓ;"..
"Ⴏ;ⴏ;Ẅ;ẅ;Û;û;ᾚ;ᾒ;ᾙ;ᾑ;Ẉ;ẉ;Ǜ;ǜ;Ҫ;ҫ;ᾍ;ᾅ;ᾌ;ᾄ;IJ;ij;Ⓔ;ⓔ;𐐁;𐐩;Ӏ;ӏ;Ȳ;ȳ;Հ;հ;Ο;ο;Ḩ;ḩ;Ƕ;ƕ;Ṣ;ṣ;"..
"Ą;ą;Έ;έ;Һ;һ;Ⲑ;ⲑ;Ɵ;ɵ;K;k;Ⱛ;ⱛ;NJ;nj;Ŷ;ŷ;Ὠ;ὠ;Ὗ;ὗ;Ẻ;ẻ;Ὕ;ὕ;Ḭ;ḭ;Ɉ;ɉ;Ὓ;ὓ;Ⴕ;ⴕ;Н;н;Ṷ;ṷ;Ὃ;ὃ;"..
"Ⱥ;ⱥ;Ⰴ;ⰴ;Ȍ;ȍ;Ὁ;ὁ;Ἡ;ἡ;ᾞ;ᾖ;Č;č;Ἷ;ἷ;Ό;ό;Ḯ;ḯ;Ἶ;ἶ;Ⲗ;ⲗ;Ͼ;ͼ;Ύ;ύ;Ṵ;ṵ;Ӷ;ӷ;Ո;ո;Ἧ;ἧ;Ⱙ;ⱙ;Ἕ;ἕ;"..
"Ǿ;ǿ;Ἔ;ἔ;Ӑ;ӑ;Ẽ;ẽ;Ἒ;ἒ;Ἑ;ἑ;Ƨ;ƨ;Ἐ;ἐ;Ἄ;ἄ;Ἅ;ἅ;Ṫ;ṫ;Ἆ;ἆ;Ⴓ;ⴓ;Ἃ;ἃ;Χ;χ;Ⓘ;ⓘ;Д;д;ᾜ;ᾔ;Ð;ð;Ớ;ớ;"..
"Ч;ч;Ỹ;ỹ;Ά;ά;Ỵ;ỵ;З;з;Ⰶ;ⰶ;Ҕ;ҕ;Ế;ế;Ő;ő;Ử;ử;Ừ;ừ;Ԑ;ԑ;Ủ;ủ;Ụ;ụ;Ợ;ợ;Ἀ;ἀ;Ồ;ồ;Ố;ố;Ɣ;ɣ;Ỏ;ỏ;"..
"Ȕ;ȕ;Ⴙ;ⴙ;Ṩ;ṩ;Ằ;ằ;Ĕ;ĕ;Ⰸ;ⰸ;Δ;δ;Ệ;ệ;S;s;Ễ;ễ;Ể;ể;Ë;ë;Ẵ;ẵ;Ḣ;ḣ;Ř;ř;Ⲛ;ⲛ;Ϙ;ϙ;Ⅴ;ⅴ;М;м;Ӛ;ӛ;"..
"Ø;ø;Ẫ;ẫ;Я;я;Ϡ;ϡ;Á;á;Ẕ;ẕ;Ȝ;ȝ;Ẍ;ẍ;Ҝ;ҝ;Ẋ;ẋ;Ĝ;ĝ;Ɓ;ɓ;Μ;μ;ᾘ;ᾐ;Ṯ;ṯ;Ἂ;ἂ;Ṱ;ṱ;Ṭ;ṭ;Ⳑ;ⳑ;Ṥ;ṥ;"..
"Ɯ;ɯ;Ὧ;ὧ;C;c;Ṟ;ṟ;Ⴗ;ⴗ;Ḡ;ḡ;Ṝ;ṝ;Dž;dž;Ṕ;ṕ;Ё;ё;Ʒ;ʒ;Å;å;Ṓ;ṓ;Ņ;ņ;Ṑ;ṑ;Ṏ;ṏ;Ὂ;ὂ;Ṇ;ṇ;Ṃ;ṃ;Ẳ;ẳ;"..
"Ḿ;ḿ;Ḽ;ḽ;Ϊ;ϊ;Ⴎ;ⴎ;Q;q;Ⴝ;ⴝ;Ǡ;ǡ;Ӆ;ӆ;Ż;ż;Յ;յ;Կ;կ;Ǘ;ǘ;Š;š;Ɖ;ɖ;D;d;Ƃ;ƃ;Ջ;ջ;Ḧ;ḧ;Ӡ;ӡ;Ⲟ;ⲟ;"..
"Ὄ;ὄ;Ⅰ;ⅰ;Ƥ;ƥ;Lj;lj;Ѡ;ѡ;Ò;ò;Ŀ;ŀ;Ɨ;ɨ;Ĥ;ĥ;Ǎ;ǎ;Τ;τ;Љ;љ;Ф;ф;Í;í;Æ;æ;Ս;ս;Ɠ;ɠ;Ȑ;ȑ;Ũ;ũ;Ặ;ặ;"..
"Ϩ;ϩ;Ⴛ;ⴛ;Ҥ;ҥ;Ժ;ժ;Զ;զ;Ὣ;ὣ;Ҏ;ҏ;Ⲝ;ⲝ;Ǩ;ǩ;Ӎ;ӎ;Ȭ;ȭ;Ό;ό;Ă;ă;Х;х;Ĭ;ĭ;Ƒ;ƒ;Ư;ư;Ⅲ;ⅲ;Ὶ;ὶ;Ȅ;ȅ;"..
"Ө;ө;Օ;օ;ᾭ;ᾥ;Α;α;Ƭ;ƭ;Ὡ;ὡ;Ѩ;ѩ;L;l;Ď;ď;Ή;ή;Ӱ;ӱ;Ǖ;ǖ;Ⓝ;ⓝ;Ŏ;ŏ;Ь;ь;Õ;õ;Ѱ;ѱ;Ⱪ;ⱪ;Ű;ű;Ќ;ќ;"..
"Ⓛ;ⓛ;Ρ;ρ;T;t;Й;й;Ҭ;ҭ;Ⅼ;ⅼ;Ϥ;ϥ;Ẩ;ẩ;Ϣ;ϣ;Ι;ι;ᾯ;ᾧ;Ѻ;ѻ;Ѷ;ѷ;Ѧ;ѧ;Ҵ;ҵ;Ͽ;ͽ;Դ;դ;Ⴟ;ⴟ;Ⳝ;ⳝ;İ;i;"..
"Ÿ;ÿ;Â;â;Ṧ;ṧ;Ғ;ғ;Ј;ј;Ǚ;ǚ;Ⳙ;ⳙ;Ɔ;ɔ;Ĵ;ĵ;Ų;ų;Ì;ì;Е;е;Ӹ;ӹ;Ḙ;ḙ;Ⴀ;ⴀ;Ǻ;ǻ;Ỳ;ỳ;Ý;ý;Ѹ;ѹ;Ⲁ;ⲁ;"..
"Ĩ;ĩ;Ά;ά;Ⴄ;ⴄ;Ղ;ղ;Ш;ш;Ἴ;ἴ;ᾩ;ᾡ;Ȇ;ȇ;Ю;ю;Ѥ;ѥ;Ѫ;ѫ;Ć;ć;Ǹ;ǹ;Ŋ;ŋ;Ⅎ;ⅎ;І;і;Ҽ;ҽ;Ɋ;ɋ;Ⳁ;ⳁ;Ҧ;ҧ;"..
"Б;б;ᾎ;ᾆ;Ѯ;ѯ;Ê;ê;Ҳ;ҳ;Ḛ;ḛ;ᾫ;ᾣ;Ԇ;ԇ;Ƽ;ƽ;Պ;պ;𐐌;𐐴;Ӄ;ӄ;Ⓟ;ⓟ;Ȏ;ȏ;Ⴂ;ⴂ;Ǝ;ǝ;Ỷ;ỷ;С;с;Ә;ә;Ậ;ậ;"..
"Ӝ;ӝ;Ḝ;ḝ;Ʌ;ʌ;Ӂ;ӂ;Է;է;Ἲ;ἲ;Ӿ;ӿ;Ạ;ạ;Ԅ;ԅ;Ӓ;ӓ;Ԍ;ԍ;Ę;ę;Ứ;ứ;Ԏ;ԏ;F;f;Ւ;ւ;Ⓡ;ⓡ;Ў;ў;Ѣ;ѣ;M;m;"..
"Լ;լ;Ʃ;ʃ;Խ;խ;Ắ;ắ;Ѿ;ѿ;Җ;җ;Ⱉ;ⱉ;Ж;ж;Շ;շ;Ἰ;ἰ;У;у;Œ;œ;È;è;Ⴉ;ⴉ;Ⴈ;ⴈ;Ʋ;ʋ;Ⴍ;ⴍ;Ɩ;ɩ;Ⴐ;ⴐ;Щ;щ;"..
"Ⴠ;ⴠ;Ǣ;ǣ"
local tbl = case:split(";")
for i = 1, #tbl, 2 do
local key, val = tbl[i + 0], tbl[i + 1]
lower[key] = val
upper[val] = key
end
end
function utf8.upper(str)
if not upper[1] then init() end
return utf8replace(str, upper)
end
function utf8.lower(str)
if not upper[1] then init() end
return utf8replace(str, lower)
end
end
do
local translate = {}
local function init()
local translate_data = [=[0,O;ᔡ,s;く,<;ᖱ,d;⋓,u;б,b,6;ฝ,w,u;Ա,u,U;ݒ,u;ᘬ,s;ꇘ,S;≻,>;‵,';ᘉ,n;ฑ,n;Ҟ,K;ı,i;Ξ,E;₩,W;ᑙ,n;ʞ,k;]=]..
[=[ڹ,U;ƃ,b;☊,n;ƞ,n;ʃ,s,f;ҹ,u,h;Ꮽ,9;˵,";ʹ,';ս,u;ι,i,l,L;ڃ,c,z;ѽ,w,o;ɇ,e;ͽ,c,E;━,-;ƹ,E,z,3;╤,T;ɽ,r,l;ч,ch,u,y,h;]=]..
[=[‘,';Ᏺ,h;⓱,17;┮,T;ه,o,a;ᑢ,c;Շ,o;ԋ,H;ˏ,COMMA;‹,<;Ѣ,b;ᑝ,c;բ,f,p;R,R;Ꮜ,a,u;٢,2,r;Ħ,H;≿,>;ғ,F;˪,L,i;]=]..
[=[ᵾ,Au;Ϫ,Y,a,v;ⅴ,V;ᒶ,L;ê,E;Ꮹ,G,c;╙,L;⓬,12;ᴦ,G,L,R;☣,o;〡,i,l;ʮ,h,u;⊂,<;Ɠ,G;Ʈ,T;ʓ,E,z,3;®,R;₱,D;ת,n;ᶈ,A,p,o;]=]..
[=[ԛ,q;ᐏ,A,n,v;Ѳ,f,th,o;ᑦ,c;Ү,U,Y;⓵,1;Л,L,N;∪,u;ٲ,i;≳,>;♈,v;‽,!?,?!,?,!;ㄘ,c,s;ж,x,k;{,{;ɲ,n;丨,i,l;ᙄ,e,c;☂,j;˟,x;]=]..
[=[ᄇ,A;τ,t,T;J,J;ᶕ,e;๑,a;ß,ss,B;ศ,a;╅,t,+;Ӻ,F;ᴢ,Z;ᗷ,E,B;ʈ,t;⊆,<;˺,';ᴩ,R,P;ઠ,s;ۄ,a,g;⏋,L;ƈ,c;†,+,t;]=]..
[=[Ɍ,R;⓹,5;£,L,E;১,1;ʾ,';ᑪ,c;ƾ,s;ʣ,dz;Σ,S,E;А,A;ң,H;ㄤ,ang;Ռ,n;ь,b;ɧ,h;w,W;☺,o;ŧ,t;ฬ,n,w,m;Đ,D;]=]..
[=[ᶑ,d;╁,t,+;٧,7,v;o,/,O;է,t;⊚,o;ژ,j;ث,u;ᒎ,j,i;⓽,9;Ҙ,E;☧,t;⏇,T;ʘ,O;ᑮ,p;Θ,TH,O;ᕫ,w,u;ڳ,S;ᐐ,A,n,v;Ƙ,K;]=]..
[=[ㄠ,au;ɜ,e,3;Ł,L;☾,c;Ɂ,?;Ƴ,Y;≨,<;ᴍ,M;ᒢ,i;с,s,c,C;γ,g,y,b,Y;ᄏ,F;շ,2;Ձ,2;Ի,r,R;!,i;ᖆ,A;ᴪ,W;ᗿ,B;ɷ,w,o;]=]..
[=[л,l,n;ᇫ,A,n,v;ᒇ,d;ˉ,';ᔓ,s;⋧,>;ω,aw,w;Ȼ,C;义,X;Ƞ,n;É,E;ӿ,x;ㄍ,g;ᵮ,f;৯,9;Р,R,P;ۉ,g;ᶅ,l,I;ʍ,w,m;ä,A;]=]..
[=[ƍ,o;Ϥ,h,b,p,q;Ӊ,H;♄,h;ᅅ,ON,OA;B,B;ʨ,tc,ts,t;Ց,s,/;▍,|;Ψ,PS,U,W;ҍ,b;✚,t;ƨ,s;ᶙ,u;Е,E;ɬ,l,I;╉,t,+;₠,C;ɑ,a,A;¡,!,i;]=]..
[=[ڙ,j;⋁,u;⊒,c;ᖂ,d;ᴉ,i,!;Ꮾ,6,G;_,_;Ƀ,B;а,a,A;ᴙ,R;2,z;ᕲ,D;լ,L;₵,C;ᑷ,p;Γ,G,L,r,T;ذ,s;ݑ,u;χ,x,X;ĕ,E;]=]..
[=[ฅ,n,a;ᑐ,c;ᶁ,d;╼,-;ᗋ,A;3,/;∞,oo;◎,O;∺,H;ϙ,o;V,V;—,-;☡,Z,2;Ꮇ,M;❿,10;ҝ,x,k;۴,r;Ƃ,b;➋,2;Ν,N;]=]..
[=[¦,|;ʂ,s;Ʀ,R;ʝ,j,i;ᑱ,d;ᚺ,H,N;⁎,*;Ɲ,N;ᒪ,L;⋜,<;Ꭼ,E;║,ll,||;Ҹ,u,y,h;➇,8;Ɫ,L,t;แ,ii;エ,I;ڂ,c,z;ᚤ,n;ɭ,l;]=]..
[=[✖,X;Ɇ,E;❻,6;♯,#;θ,th,o;੫,5;฿,B;Ʃ,E;Ѽ,w,u;ц,ts,u;ұ,u,y,v;ᔛ,s;Ƹ,E,z,3;Ꮃ,W;­,-;ᕾ,p;ɼ,r,l;ن,u;は,t;ᑔ,c;]=]..
[=[ᗬ,D;ɡ,g,G;ⱳ,w;◌,o;Ꮤ,W;>,>;ᶍ,x;ݢ,S;ⅼ,L;ѡ,W,v;ђ,dj,h;ϩ,s;ݙ,S;ˎ,COMMA;ઝ,K;ȶ,t;ᴧ,L,A,n,v;١,1;ێ,s;ȥ,z;]=]..
[=[ټ,u;Ғ,F;–,-;c,C;Ⅽ,C;ァ,P;➑,8;Х,H,X;➓,10;g,G;❷,2;å,A;☪,c;ผ,w,u;﹞,);ٽ,u;ᑵ,q;é,E;N,N;ㄑ,q,k;]=]..
[=[Ǝ,E;і,i,I;Ӕ,AE,E;F,F;╱,/;➃,4;╌,-;ʭ,n;ʎ,A,Y,l;Β,B;੯,9;ƭ,E;ˮ,";Ֆ,s;ᒐ,l,i;ๅ,r;ᗣ,A;ヨ,E;ꇙ,S;ᕺ,b;]=]..
[=[ѱ,ps,w;Ԛ,Q;ف,g;Ꮠ,i;ձ,do;ɖ,d;Z,Z;卩,P,N;ٱ,i;:,:;W,W;ᶉ,r;Ъ,b;К,K;ɥ,h,u;Ê,E;ミ,E;Ě,E;е,e,E;ҭ,t;]=]..
[=[ᵵ,t;۞,O;乙,z;ᚽ,I;ɱ,m;Ã,A;р,r,p,P;〈,<;Ⅹ,X;ǃ,!;Ե,t;ᔢ,s;ᑹ,p;ݖ,u;ⱷ,w;ᴻ,N;۹,q;σ,s,o;Ꭴ,u;Ρ,R,P;]=]..
[=[《,<<;ᑜ,n;ȵ,n;І,I;⋟,>;Þ,b,p;ฐ,s;Ƈ,C;╽,|;ۃ,s,o,j;‛,';ղ,n;˹,';〕,);ڽ,o,u;ᶄ,k;Ϲ,C;ઙ,s;~,~;ʇ,t,f;]=]..
[=[▎,|;¢,c;ҽ,e;z,Z;乚,L;Ƣ,a;ν,n,v,V;S,S;♑,N;ʢ,?,c;ʽ,';ق,g;€,E;м,m,M;ƽ,s;ڇ,c;ᗫ,D;Ң,H;ы,bl;〝,";]=]..
[=[。,.;╕,F;տ,s;ď,D;ᐄ,A,n;凢,N;ด,n,a;ᚱ,R;▐,|;ɦ,h;⋉,K;丫,Y;ᵱ,p;Ŧ,T;⓭,13;丅,T;▁,_;Ѧ,A;ᴎ,N;ԏ,T;]=]..
[=[☢,o;ㄝ,eh;ʁ,R;Џ,U,Y;﹝,(;٦,6;─,-;M,M;х,h,x,X;զ,q;δ,d,o;り,n;₰,I;Ꭳ,o;ᴚ,R;Ԋ,H;☒,X;Ϯ,I,t,l;੧,1;җ,x,k;]=]..
[=[ᑍ,n,h;ت,u;‚,COMMA;),);ઈ,d;∑,S,E;Â,A;ڗ,j;=,=;Ҳ,X;₡,C;Ɨ,I;܄,:;⺇,n;٠,.;Ր,r;Ꮟ,b;ۮ,j;ᒘ,j;Η,H;]=]..
[=[״,";!,;Ꭰ,a,d,D;ʗ,c;ョ,E;ћ,ts,h;〃,";؟,?;ᵽ,p;Ʋ,u;➎,5;ㄈ,f,c;ɀ,z;ɛ,e;2,/;П,P,N;Ꮋ,H;β,b,B;ᘂ,i;˩,l,i;]=]..
[=[ᑡ,c;ն,u,G;モ,E,T;K,K;Հ,R;ᗶ,R;ϯ,i,t;┈,-;凣,N;Ժ,o,a;*,*;Ⱥ,A;ҧ,n;غ,E;ㄆ,p,r;ӏ,l;╥,T;ˈ,';ᴆ,D;✗,X;]=]..
[=[ㄉ,d;ψ,ps,u,w;ฤ,n,a;Ꮯ,c,C;∶,:;Ԅ,R;Ⅼ,L;ㄋ,n;ݕ,u;∕,/;h,H;≼,<;Ꮛ,e;ã,A;ᗻ,R;ᴯ,B,D;♉,u;Ӿ,X;三,3,E;サ,H;]=]..
[=[۳,r;ӈ,H,A;ㄐ,j,u;Ⱪ,K;ᵹ,d;ϣ,w;ƒ,f;v,V;ㄛ,o;ݫ,j;‧,.;⓶,2;ᴕ,o;þ,b,p;ᗑ,A;ร,s;Ɯ,w;ڌ,j;ᒀ,b;Ꮴ,v;]=]..
[=[ⅽ,C;Ⴓ,Q;ᘺ,w;Χ,X;ᶒ,e;Ҍ,b;Ᵽ,p;ʧ,ts,a;Ө,th,O;ٻ,u;ᖺ,n,h;⊙,o;ᑥ,c;匸,C;∈,E;G,G;☦,t;ɫ,l,I;s,S;と,y;]=]..
[=[Ѩ,IA,A;ɐ,a;ᴂ,ae;ᙂ,c;l,L;ㄕ,sh,p,r;⊺,T;ᚩ,F;ꆿ,H;ի,h;ㄙ,s,A;➈,9;ㄡ,ou,A,r;Ĕ,E;ㇶ,t;n,N;Ɉ,J;ט,u;丷,'';Ꮰ,j;]=]..
[=[♍,m;ݐ,u;ᑘ,u;ѕ,s,S;Ė,E;ᐑ,A,n,v;ᴈ,3;ᵰ,n;ᒏ,j,i;Ø,O;ㄨ,u,X;r,R;▄,_;丄,T;Կ,u;》,>>;ᅂ,OC;Ҝ,K;f,F;٥,5,o;]=]..
[=[Ɓ,B;৬,6;�,?,-,/,=,C,I,O,S;Ä,A;ⅹ,X;ᗺ,E,B;➒,9;ҷ,u,y,h;ᑩ,c;ϳ,j,J;Ⱨ,H;C,C;ҁ,k,c;ᗾ,B;`,`;Ꮄ,S;ց,g;ᑼ,d;Ʌ,^,N;Ʒ,E,z,3;]=]..
[=[ځ,c,z;ɨ,i;╚,L;·,';╭,r;☹,o;一,-;η,e,n;╒,F,r;ͻ,c;ة,o;م,p;ᑭ,p;ѻ,o;ヒ,t;⓺,6;ʔ,?;ռ,n;Յ,3,d;j,J;]=]..
[=[ㄌ,l;ɻ,r,l;ᅄ,OU,OR,OA;п,p,n;⁃,-;✝,t;ㄩ,iu,U;ᵎ,i,!;੬,6;ム,A;њ,nj,Hb,H,b;ȿ,s;ᆼ,o;Ѡ,w;ˍ,COMMA;ᕷ,d;Ꮓ,Z;․,.;∂,d,a;⓾,10;]=]..
[=[Љ,N;Ф,F,O;Њ,H;ɢ,G;ґ,r;˨,l,i;&,&;ǂ,|;נ,i,c,j;Ϩ,s;ۍ,s;Ⅰ,I;ڑ,j;┽,t,+;Ⱬ,Z;匚,C;┾,t,+;Ϟ,N,X;Ӎ,M;-,-;]=]..
[=[⊑,c;ᛖ,M;┰,T;∀,A;Ƒ,F;ɍ,r;ʙ,B;Ꮀ,E;ʑ,z;ʬ,w;Օ,O,/;″,";Α,A;Ƭ,T;ค,n,a;ⱥ,a;۔,.;¬,-,!;ۈ,g;?,?;]=]..
[=[ઘ,u;┭,T;ɕ,c;∣,l;╈,t,+;Ѱ,PS,W;╲,\;₴,S;♅,H;հ,h;〞,";Ⴝ,S;┌,r;Ҭ,T;ᑰ,d;ᒈ,b;イ,T;à,A;܁,.;ㄊ,t,g;]=]..
[=[么,G;▌,|;ᅀ,A;⋝,>;ᵭ,d;ᆺ,A,n,v;ם,a,o;۝,O;ᶆ,m;ɰ,w;",";Ͼ,C,c,E;$,";ๆ,r;➊,1;ㄞ,ai;┍,r;ᅡ,t;˂,<;⓼,8;]=]..
[=[◊,o;ᵼ,i;Ⅾ,D;Å,A;Պ,n;ϝ,F;Φ,PH,F,O;┼,t,+;Ď,D;ᕵ,p;┇,|;b,B;\,\;ϸ,b,p;ʆ,l;〓,=;ס,o;ڼ,i;ֆ,S,$;ป,u;]=]..
[=[ڄ,c,z;ȴ,l;Ŋ,N;ᑗ,u;જ,v;Ҽ,e;Ɋ,q,a;[,[;ɪ,I;μ,m,u,U;چ,c;ᒌ,i;ʡ,?;ʼ,';ᖁ,d;‟,";❼,7;Ƽ,s;ᑴ,q;ᑤ,c;]=]..
[=[੨,2;➆,7;ي,s;ㄖ,r,o;ě,E;‖,||,ll;☩,t;٪,%;Ɔ,C;ท,n;ъ,b;❸,3;Ѕ,S;מ,a;ѥ,ie,E;⋑,c;ۏ,g;〨,E;ᙀ,u;ᘪ,s;]=]..
[=[ᶂ,f;⓮,14;Ԏ,T;ӄ,b,h,k;ᴃ,B,D;ϗ,h,k,x;ե,t,b;ڷ,J;ᗸ,E;⫽,/;ᐁ,v;ɘ,e;ᒑ,l,i;ᄔ,LL;Җ,X,K;˃,>;X,X;Ю,IO,O;ϭ,d,o;※,X;]=]..
[=[。,.;6,/;อ,d,o;ฟ,w,m;ᑽ,d;╏,|;Ɩ,I;ᑛ,n;Щ,SH,W;ᵴ,s;≽,>;ㄒ,x,T;〤,X;╎,|;Ζ,Z;;,SEMICOLON;Ᏸ,B;╍,-;ʖ,?;ڱ,S;]=]..
[=[า,r;Λ,L,A,N;ᑸ,p;E,E;╊,t,+;ѵ,v,V;⏆,l;±,+,-;ᑨ,n;⊌,u;ɚ,e;Ʊ,u;二,2;╆,t,+;ᗐ,V;Д,D,A;Ꭹ,y,v,u;յ,j,i,J;О,O,/;α,a,A;]=]..
[=[ᔣ,s;ᄐ,E;╂,t,+;➂,3;ม,u,w;ฮ,a;マ,R;ᴟ,m,E;┃,|;ɵ,o;В,V,B;ع,E;┱,T;ȣ,d,o;ᑠ,c;২,2;ᒕ,r;Ȥ,Z;ل,j;┗,L;]=]..
[=[ᗽ,B;T,T;╺,-;⊝,o;},};⏌,L;ˇ,';ȹ,qp,cp;ë,E;└,L;る,z;ᑟ,c;܃,:;ㄣ,en;Ӈ,H,A;凡,N;ʋ,u,v;ӽ,x;â,A;┏,r;]=]..
[=[ᴇ,E;Ͻ,C;ۇ,g;p,P;ᑌ,u;♌,n;๛,c;7,/;Ƌ,d;丶,';乛,-;⊛,o;⊥,T;ᅙ,o;ɏ,y;ᴫ,N;˫,l,i;➐,7;ע,v,u;ᗵ,R;]=]..
[=[Ꭵ,v,i,I;ᖅ,b;ʦ,ts,s;┉,-;下,T;ᄜ,OA,OU,OR;ᗪ,D;ᘢ,u;У,U,Y;ᖳ,q;Ҧ,n;Ϻ,M;Г,G,L;ᕹ,b;Տ,s,S;╇,t,+;ԓ,N;ٺ,u;я,y,R;∐,U;]=]..
[=[ᒙ,j;ᚫ,F;ח,n;Є,E;ᘃ,i;ᶊ,s;ժ,d,D;⺆,n;ҫ,s,c;y,Y;♊,o;ӌ,u,y,h;ē,E;⊎,u;Ꮞ,4;ᴋ,K;/,/;┖,L;⏊,T;Ԍ,G;]=]..
[=[⏄,A;ƣ,a;×,x;Գ,q;从,M;ᚾ,I;ⅾ,D;Ƨ,S;上,T;⋂,n;⏀,O;ᗹ,E,B;қ,k;个,T;۲,r;⊕,o;⎿,L;ӷ,L;☥,t;⋩,>;]=]..
[=[ʛ,G;ƀ,b;Ҷ,u,y,h;ᗱ,E;Ꭱ,e,R;ʀ,R;ɾ,r;ك,j;ᅊ,OE;⓳,19;ڶ,J;⏉,T;ƛ,A;Ҁ,k,c;ƶ,z;P,P;+,+;ր,n;¶,P;人,N;]=]..
[=[⋎,v;ڀ,u;Ʉ,U;ᛙ,I;;,SEMICOLON;⋗,>;ᗖ,V;ㄦ,er;⋒,n;L,L;Ꮚ,w;I,I;∆,D,A;u,U;╀,t,+;ԃ,d;Ꮍ,Y;վ,u;ɺ,r,l;⋀,A,n;]=]..
[=[∋,R;օ,o;Ծ,o;ㄢ,an;ᶔ,3,e;⓸,4;о,o,/,O;ᔙ,s;ᴏ,O;♩,l,I;ˌ,COMMA,.;D,D;♏,m;厂,R;Ⱦ,T;๓,n,m;ᒁ,b;˶,";ᶓ,e;,,COMMA;]=]..
[=[Ⱡ,L;λ,l,A;ݟ,E;Ꮒ,h;ʄ,s,f;»,>>,>;ی,s;╃,t,+;ฃ,u;ᴠ,V;ϧ,3,c;☸,o;七,t;≪,<<;˧,l,i;ф,f,o;ᴨ,P,N;⁁,l,/;ק,p;⋥,c;]=]..
[=[⊔,u;⓷,3;є,e;⏅,A;ʫ,lz;t,T;ᆷ,o;∨,v;',';ẞ,ss,B;ʐ,z;ӡ,3,z;«,<<,<;H,H;Ք,p;ɗ,d;ƫ,t;q,Q;Ꮵ,h;≲,<;]=]..
[=[ګ,S;ᒚ,j;Ꮖ,C;ヲ,E;ㄧ,i;ɯ,w;И,I,N;⊇,>;凵,U;₫,d;Ę,E;⊍,u;┳,T;♭,b;Ϭ,d,o;₤,E,L;➍,4;ย,u;ˑ,.;⎾,L;]=]..
[=[ᒅ,q;ᛘ,Y;կ,u;ᖄ,b;Ɽ,R;⊜,o;ѯ,E;ᒋ,i;⊦,t;ᒗ,r;Ϝ,F;Á,A;Ꮁ,T,G,F;∩,n;ݔ,u;ǁ,II;ز,j;À,A;ᵲ,r;▏,|;]=]..
[=[ᒫ,L;ㄏ,h;۷,v;ρ,r,p,P;˴,';ƅ,b;Ƿ,P;৭,7;ᖃ,b;ᒰ,r;÷,-;Ѯ,E;ڻ,o,u;⓻,7;ݤ,S;⏁,O,T;ᶚ,3;ʅ,s;@,a;☓,X;]=]..
[=[һ,h,H;m,M;℧,u;ɉ,j;Ꮅ,P;๏,o;ᶗ,c;ᑏ,n;ʻ,';∃,E;ٿ,u;ᖯ,b;➉,10;څ,c,z;ʠ,q;Չ,o;┿,t,+;∇,A;Π,P,N;ى,s;]=]..
[=[ƻ,2;ฦ,n,a;Ҡ,K;ⅿ,M;ᒉ,i;ᚪ,F;ɿ,r;щ,sh,w;ⱨ,h;Ꮳ,c;ɤ,v;〜,~;₯,Dp,D;Ը,c,l;▃,_;ᕴ,q;ᗥ,D;۸,A;Ѥ,IE,E;ᶋ,s;]=]..
[=[ȝ,3,E;ԍ,G;ᔑ,s;⋨,<;ڲ,S;੦,0;٤,4,E;ۑ,s;],];₥,m;դ,n;ʪ,ls;ᑒ,c;Ҫ,s,C;ب,u;ᛕ,K;∠,<;ҕ,b,a;ᵬ,b;৩,3;]=]..
[=[₣,F;@,?;Ш,SH,W;⌡,j;и,i,N;i,I;È,E;ᚿ,I;ڸ,U;ƕ,h;Æ,AE;ィ,T;ᅃ,OO;”,";Ұ,U,Y,V;☼,o;“,";Ε,E;ェ,I;љ,lj,nb,n,b;]=]..
[=[Ⅿ,M;ʕ,?;ڰ,S;➏,6;➅,6;ᴔ,oe;‒,-;ə,e;Փ,o;ᒍ,j,i;°,',o;˼,COMMA;ⱬ,z;ԝ,w;ۆ,g;✘,X;∏,P,N;Н,N,H;ᖇ,R;⋞,<;]=]..
[=[╳,X;ᅇ,OO;մ,u;Ꮮ,l,L;ᄘ,2L;╔,r;ظ,b;Ҋ,I,N;ᵯ,m;ᚨ,F;̸,/;Ꮪ,s,S;Q,Q;ᶎ,z;ɴ,N;╘,L;ᶌ,v;ᑖ,c;ĸ,K;≾,<;]=]..
[=[ᛉ,Y;ᶇ,n;ȸ,db,cb;Ђ,h;ㄚ,a,Y;<,<;◇,o;Ԃ,d;〷,XX;e,E;Ⴍ,Q;ˆ,^;Ԁ,d;Т,T;Ӽ,X;܂,.;ᑻ,d;ɩ,i,I;ϼ,p;á,A;]=]..
[=[凹,U;ㄗ,z,p,n;ᔤ,s;Ɗ,D;›,>;ڒ,j;9,g;▋,|;ᗏ,A;บ,u;ᵺ,th;Ɏ,Y;₢,G;ƥ,b;ᚢ,n;乃,B;ᵷ,g,o,b;¥,Y;⊃,>;⦿,o;]=]..
[=[ז,i;Υ,U,Y;੩,3;ᘭ,s;╿,|;ʥ,dz;❽,8;ø,o;ݞ,E;═,-;Y,Y;Վ,u;ᐂ,A,n;ҥ,H;ᴊ,J;ю,io,o;⺁,r;ѩ,IA,A;ᴡ,W;Ԓ,N;]=]..
[=[8,B;ᑚ,n;ᘨ,u;թ,p;ต,n,a;∟,L;ᶃ,g;٩,9,q;г,g,r,L;8,/;ә,e;┋,|;ᆠ,T;a,A;〔,(;Ē,E;ᘻ,m;ƌ,d;┆,|;੭,7;]=]..
[=[ᑿ,b;ᑣ,c;ˋ,';ᖰ,p;พ,w,m;ح,c,z;ⱱ,v;Қ,K;ᒸ,L;⓰,16;ױ,ii;ᴁ,AE;╻,i;Э,E;۱,i,l;ʚ,a,d;₦,N;Κ,K;ʊ,u;乇,T;]=]..
[=[ᛇ,S;Ᏻ,G;―,-;4,/;ҵ,u;Մ,u;ᛒ,B;ᕸ,d;ᗩ,A;❹,4;ᴜ,U;ᛊ,E;ڵ,J;U,U;չ,Z;ɞ,e,B;Ƶ,Z;ᚼ,I;⓴,20;≺,<;]=]..
[=[ᚹ,P;|,|;ᶏ,a;⊏,c;ε,e;ᑞ,c;ѹ,oy;7,t;ᛁ,I;⁅,E;⓫,11;ᚗ,G;△,A;ᘯ,n;ɹ,r;у,u,y,Y;Ҏ,p;ƚ,l;ᙁ,n;Ճ,bo;]=]..
[=[ᵳ,r;็,r;ㄓ,zh,z,w;ᚻ,H,N;н,n,H;ᛗ,M;Ꭺ,A,n;ᛞ,M;☨,t;ㄟ,ei;ᘵ,n;Ë,E;Ƚ,L,t;ᗴ,E;十,T;ㄥ,eng,L,C;ζ,z,s,c;⋖,<;ᴘ,P;ۋ,g;]=]..
[=[㉿,K;☋,u;│,|;Ȣ,d,o;৫,5;O,/,O;’,';Ӌ,u,y,h;ᴛ,T;ʏ,Y;պ,w;æ,ae;ɋ,q,a;Ə,e;⊋,>;Ϧ,h,b;ҡ,k;∓,T;ᴣ,3;˦,l,i;]=]..
[=[ۯ,j;ᘫ,s;⊤,T;➀,1;≦,<;┯,T;⓯,15;x,X;╋,t,+;ઇ,d;ᒖ,r;ƪ,l;ᄕ,LC;ҏ,p;6,b;ᖀ,p;φ,ph,o,w;З,E;‡,t,i;ɮ,B,k,z,3;]=]..
[=[ટ,s;几,N;ө,th,o;ᔍ,c;ᵿ,A,u;ᔜ,s;○,O;œ,oe,ce;ᑧ,u;╄,t,+;ᔕ,s;ɓ,b;ϡ,l;ᶖ,i;Ѵ,V;ٮ,u;れ,n;♆,W;┎,r;ծ,d,o;]=]..
[=[ᒺ,L;ɶ,OE,CE;ถ,n,a;כ,c;₮,T;ė,E;ᒮ,r;Բ,f;ᒛ,j;„,,,,COMMA;ᙅ,c;ϛ,s;⁄,/;Ꮣ,l,c;ǀ,I,|;꞉,:;ᖸ,u;ડ,s;ษ,u;Ӷ,L;]=]..
[=[Ц,TS,U,Li;₧,Pts,P;π,p,n;₭,K;⊐,c;ҟ,k;Ӏ,I;Ƅ,b;♋,69;Ο,O,/;Ы,bl;Ƕ,H;ᑬ,p;ʟ,L;ᴄ,C;Һ,h;Ꮙ,v;Ɵ,O;ᑳ,b;϶,e;]=]..
[=[₳,A;5,S;、,COMMA;ք,p;ᄆ,O;০,0;₸,T;κ,k,x,K;ᵻ,i;Ϸ,b,p;к,k;$,S;ᑫ,q;∫,s;ᘀ,B;Ѿ,w;∊,R;ᕶ,p;ᅌ,O;Ո,n;]=]..
[=[∝,oo;ᗼ,R;в,v,B;و,g;ݓ,u;ก,n,a;ϟ,S;≤,<;≩,>;Ꮷ,J;Ꭲ,I,G,L,T;ш,shw;ݬ,j;☻,o;ɔ,c;ɣ,Y,v;Ћ,h;๐,o;ں,i;א,X,N;]=]..
[=[Ɛ,E;ݣ,S;ᴀ,A;ѣ,b;⊖,o;⊘,o;❍,o;գ,q;Ґ,L,r;ħ,h,n;⋃,u,U;Ð,D;ѿ,W;Ч,CH,u,h;Ͽ,C,c,E;ϫ,a,v;Ј,J,I,l;⋦,<;ᗗ,A;Ҕ,b,h;]=]..
[=[ᐎ,A,n,v;ا,i,l,/,I,L,|;Ǥ,G;پ,u;ᶐ,q;થ,u;(,(;Ꮶ,K;4,A,R;Ѻ,O;⋐,c;Ɣ,Y;ƺ,E,z,3;ӎ,m;Μ,M;∔,i;ᑯ,d;ʯ,h,u;ɟ,j,f;Δ,D,A;]=]..
[=[☤,t;ϲ,C;ㄇ,m,n;ј,j,J;џ,dz,u;⋤,c;Ꮎ,E;Ԝ,W;ᔖ,s;ᑎ,n;Ⅴ,V;М,M;1,I,l;گ,S;┲,T;ѳ,f,th,o,e;➁,2;≫,>>;Ӄ,R,b,h,K;Ȝ,3,E;]=]..
[=[⏃,A;ү,u,Y,v;⏈,T;ٳ,i;ら,s;з,z,e,3;ᴌ,L;┕,L;ᙃ,e,c;˄,^;ŋ,n;இ,I,A,O;⓲,18;ط,b;ᒂ,b;ې,s;♃,u;Է,t;ƴ,y;ɳ,n;]=]..
[=[ҋ,i,N;ᑲ,b;ᅆ,OA;٣,3,r;Ꮑ,N;Ϣ,W;ԁ,d,D;ӻ,f;υ,u,U;ȷ,j,i;ᗟ,D;〉,>;˅,v;৮,8;ʉ,u,A;˻,COMMA;ː,i,¦;≥,>;➌,3;ξ,x,E;]=]..
[=[┬,T;⏂,O,T;Ꮊ,N;A,A;ᔔ,s;ᛂ,I;Ꮢ,R;Ꮲ,p,P;ۅ,g;ᘮ,u;д,d,A;Ӡ,3,z;ᔒ,s;ҿ,e;ข,u;Ƥ,P;ן,i;ο,o,/,O;丂,S;¤,o,x;]=]..
[=[⊓,n;ʿ,';։,:;Τ,T;Ь,b,B;ƿ,D;ᒿ,2;ʤ,dz;Ս,u,U;¿,?;ᗦ,D;Ꮐ,G;э,e,3;✛,t;Б,b;Ҥ,H;ચ,u;ᛔ,B;ԑ,E;❾,9;]=]..
[=[ห,n;ᗤ,D;ճ,d,o;ǝ,e;♇,B;§,S;ᑶ,p;ը,o,n;ו,i;Ꮭ,c;❶,1;٨,8,n;ӕ,ae;ɠ,g;đ,d,D;⁓,~;▂,_;Ᏼ,B;╾,-;ج,c,z;]=]..
[=[ە,o;ᛈ,C;3,E;ⅰ,I;k,K;K,K;ς,s,c;װ,ii;ᶘ,s;Ϙ,O;ҙ,E;d,D;ݘ,c;⺋,e;ʒ,E,z,3;Ꮆ,G;^,^;5,/;ฆ,u;ð,d,o;]=]..
[=[ʌ,v,n,^;৪,4;Ι,I;ᑓ,c;ᶀ,b;Ɖ,D;☽,c;Ҵ,u;Ҿ,e;〇,o;ƙ,k;น,u;ɝ,e,3,s;て,t;Ә,e;ڴ,S;❺,5;%,%;ᄙ,22;ᑑ,c;]=]..
[=[ł,l;٫,COMMA;.,.;ᒊ,i;ɂ,?;ㄎ,k,s,e;ᒔ,r;੪,4;Я,Y,R;Ԑ,E;ᑺ,d;Ѹ,Oy;т,t;Ꮩ,V,N;ʜ,H;Լ,L;Ղ,n;Ն,i;ᒆ,p;ɸ,o,ph,f;]=]..
[=[Ꭿ,A;ᄖ,LA,LR,LU;ノ,J;ա,w;ᵫ,ue;ㄜ,e;➄,5;ȼ,c;ˊ,';ո,n;丁,T;ʺ,";ה,n;เ,i;‐,-;خ,c,z;ݝ,E;ㄅ,b,s;ᐃ,A,n;ᴑ,O;]=]..
[=[⊗,o;ᔚ,s;ȡ,d;☖,o;ۊ,g;💀,oo;Ꮸ,c;√,v;ǥ,g;ᕿ,p;С,S,C;Ⱳ,W;ӊ,H;Ϛ,S;ڈ,s,j;ㄔ,ch;ϥ,h,b,p,q;ᴅ,D;ノ,/;⫻,/;]=]..
[=[˥,l,i;ݗ,c;ʩ,fn;9,/;ץ,Y;≧,>;Ω,aw,O,n;┊,|;Ւ,t,r;ᵶ,z;©,c;ᑕ,c;੮,8;ⱪ,k;ᑾ,b;ⱦ,t,l;Ꭻ,j,J;Ꮥ,s;ک,S;ᘴ,u;]=]..
[=[Ж,X,K;ภ,n,a;ͼ,c,E;ւ,L;Œ,OE,CE;⊊,,<;ᄋ,O;1,/;ɒ,a;✞,t;ҳ,x;ѧ,A;٭,*;◯,O;▕,|;]=]
for _, chunk in ipairs(translate_data:split(";")) do
local args = chunk:split(",")
local key = table.remove(args, 1)
for i,v in ipairs(args) do
if v == "COMMA" then v = "," end
if v == "SEMICOLON" then v = ";" end
end
translate[key] = args
end
end
function utf8.getsimilarity(a, b)
if not translate[1] then init() end
b = b:upper()
local score = 0
for i, char in ipairs(utf8.totable(a)) do
if translate[char] then
local test = b:usub(i, i)
if table.hasvalue(translate[char], test) then
score = score + 1
end
end
end
return score / #b
end
end
function utf8.length(str)
local len = 0
for i = 1, #str do
local b = str:byte(i)
if b < 128 or b > 191 then
len = len + 1
end
end
return len
end
utf8.len = utf8.length
function utf8.totable(str)
local tbl = {}
local i = 1
for tbl_i = 1, #str do
local byte = str:byte(i)
if not byte then break end
local length = 1
if byte >= 128 then
if byte >= 240 then
length = 4
elseif byte >= 224 then
length = 3
elseif byte >= 192 then
length = 2
end
end
tbl[tbl_i] = str:sub(i, i + length - 1)
i = i + length
end
return tbl
end
for name, func in pairs(utf8) do
string["u" .. name] = func
end
return utf8 end)();
-- utf8 string library, also extends to string as utf8.len > string.ulen
profiler =
(function(...) local profiler = _G.profiler or {}
profiler.data = profiler.data or {sections = {}, statistical = {}, trace_aborts = {}}
profiler.raw_data = profiler.raw_data or {sections = {}, statistical = {}, trace_aborts = {}}
local blacklist = {
["leaving loop in root trace"] = true,
["error thrown or hook fed during recording"] = true,
["too many spill slots"] = true,
}
local function trace_dump_callback(what, trace_id, func, pc, trace_error_id, trace_error_arg)
if what == "abort" then
local info = jit.util.funcinfo(func, pc)
table.insert(profiler.raw_data.trace_aborts, {info, trace_error_id, trace_error_arg})
end
end
local function parse_raw_trace_abort_data()
local data = profiler.data.trace_aborts
for _ = 1, #profiler.raw_data.trace_aborts do
local args = table.remove(profiler.raw_data.trace_aborts)
local info = args[1]
local trace_error_id = args[2]
local trace_error_arg = args[3]
local reason = jit.vmdef.traceerr[trace_error_id]
if not blacklist[reason] then
if type(trace_error_arg) == "number" and reason:find("bytecode") then
trace_error_arg = string.sub(jit.vmdef.bcnames, trace_error_arg*6+1, trace_error_arg*6+6)
reason = reason:gsub("(%%d)", "%%s")
end
reason = reason:format(trace_error_arg)
local path = info.source
local line = info.currentline or info.linedefined
data[path] = data[path] or {}
data[path][line] = data[path][line] or {}
data[path][line][reason] = (data[path][line][reason] or 0) + 1
end
end
end
function profiler.EnableTraceAbortLogging(b)
if b then
jit.attach(function(...)
local ok, err = xpcall(type(b) == "function" and b or trace_dump_callback, system.OnError, ...)
if not ok then
logn(err)
profiler.EnableTraceAbortLogging(false)
end
end, "trace")
else
jit.attach(trace_dump_callback)
end
end
local function parse_raw_statistical_data()
local data = profiler.data.statistical
for _ = 1, #profiler.raw_data.statistical do
local args = table.remove(profiler.raw_data.statistical)
local str, samples, vmstate = args[1], args[2], args[3]
local children = {}
for line in str:gmatch("(.-)\n") do
local path, line_number = line:match("(.+):(%d+)")
if not path and not line_number then
line = line:gsub("%[builtin#(%d+)%]", function(x) return jit.vmdef.ffnames[tonumber(x)] end)
table.insert(children, {name = line or -1, external_function = true})
else
table.insert(children, {path = path, line = tonumber(line_number) or -1, external_function = false})
end
end
local info = children[#children]
table.remove(children, #children)
local path = info.path or info.name
local line = tonumber(info.line) or -1
data[path] = data[path] or {}
data[path][line] = data[path][line] or {total_time = 0, samples = 0, children = {}, parents = {}, ready = false, func_name = path, vmstate = vmstate}
data[path][line].samples = data[path][line].samples + samples
data[path][line].start_time = data[path][line].start_time or system.GetTime()
local parent = data[path][line]
for _, info in ipairs(children) do
local path = info.path or info.name
local line = tonumber(info.line) or -1
data[path] = data[path] or {}
data[path][line] = data[path][line] or {total_time = 0, samples = 0, children = {}, parents = {}, ready = false, func_name = path, vmstate = vmstate}
data[path][line].samples = data[path][line].samples + samples
data[path][line].start_time = data[path][line].start_time or system.GetTime()
data[path][line].parents[tostring(parent)] = parent
parent.children[tostring(data[path][line])] = data[path][line]
--table.insert(data[path][line].parents, parent)
--table.insert(parent.children, data[path][line])
end
end
end
local function statistical_callback(thread, samples, vmstate)
local str = jit.profiler.dumpstack(thread, "pl\n", 1000)
table.insert(profiler.raw_data.statistical, {str, samples, vmstate})
end
function profiler.EnableStatisticalProfiling(b)
profiler.busy = b
if b then
jit.profiler.start("li0", function(...)
local ok, err = pcall(statistical_callback, ...)
if not ok then
logn(err)
profiler.EnableStatisticalProfiling(false)
end
end)
else
jit.profiler.stop()
end
end
do
local started = false
function profiler.ToggleStatistical()
if not started then
profiler.EnableStatisticalProfiling(true)
started = true
else
profiler.EnableStatisticalProfiling(false)
profiler.PrintStatistical(0)
started = false
profiler.Restart()
end
end
end
function profiler.Restart()
profiler.data = {sections = {}, statistical = {}, trace_aborts = {}}
profiler.raw_data = {sections = {}, statistical = {}, trace_aborts = {}}
end
do
local stack = {}
local enabled = false
local i = 0
function profiler.PushSection(section_name)
if not enabled then return end
local info = debug.getinfo(3)
local start_time = system.GetTime()
table.insert(stack, {
section_name = section_name,
start_time = start_time,
info = info,
level = #stack,
})
end
function profiler.PopSection()
if not enabled then return end
local res = table.remove(stack)
if res then
local time = system.GetTime() - res.start_time
local path, line = res.info.source, res.info.currentline
if type(res.section_name) == "string" then line = res.section_name end
local data = profiler.data.sections
data[path] = data[path] or {}
data[path][line] = data[path][line] or {total_time = 0, samples = 0, name = res.section_name, section_name = res.section_name, instrumental = true, section = true}
data[path][line].total_time = data[path][line].total_time + time
data[path][line].samples = data[path][line].samples + 1
data[path][line].level = res.level
data[path][line].start_time = res.start_time
data[path][line].i = i
i = i + 1
return time
end
end
function profiler.RemoveSection(name)
profiler.data.sections[name] = nil
end
function profiler.EnableSectionProfiling(b, reset)
enabled = b
if reset then table.clear(profiler.data.sections) end
table.clear(stack)
end
profiler.PushSection()
profiler.PopSection()
end
do -- timer
local stack = {}
function profiler.StartTimer(str, ...)
table.insert(stack, {str = str and str:format(...), level = #stack})
local last = stack[#stack]
last.time = system.GetTime() -- just to make sure there's overhead with table.insert and whatnot
end
function profiler.StopTimer(no_print)
local time = system.GetTime()
local data = table.remove(stack)
local delta = time - data.time
if not no_print then
logf("%s%s: %1.22f\n", (" "):rep(data.level-1), data.str, math.round(delta, 5))
end
return delta
end
function profiler.ToggleTimer(val)
if started then
started = false
return profiler.StopTimer(val == true)
else
started = true
return profiler.StartTimer(val)
end
end
end
function profiler.GetBenchmark(type, file, dump_line)
local benchmark_time
if profiler.start_time and profiler.stop_time then
benchmark_time = profiler.stop_time - profiler.start_time
end
if type == "statistical" then
parse_raw_statistical_data()
end
local out = {}
for path, lines in pairs(profiler.data[type]) do
if path:startswith("@") then
path = path:sub(2)
end
if not file or path:find(file) then
for line, data in pairs(lines) do
line = tonumber(line) or line
local name = "unknown(file not found)"
local debug_info
if data.func then
debug_info = debug.getinfo(data.func)
-- remove some useless fields
debug_info.source = nil
debug_info.short_src = nil
debug_info.currentline = nil
debug_info.func = nil
end
if dump_line then
local content = vfs.Read(path)
if content then
name = content:split("\n")[line]
if name then
name = name:gsub("function ", "")
name = name:trim()
end
end
elseif data.func then
name = ("%s(%s)"):format(data.func_name, table.concat(debug.getparams(data.func), ", "))
else
local full_path = R(path) or path
name = full_path .. ":" .. line
end
if data.section_name then
data.section_name = data.section_name:match(".+lua/(.+)") or data.section_name
end
if name:find("\n", 1, true) then
name = name:gsub("\n", "")
name = name:sub(0, 50)
end
name = name:trim()
data.path = path
data.file_name = path:match(".+/(.+)%.") or path
data.line = line
data.name = name
data.debug_info = debug_info
data.ready = true
if data.total_time then
data.average_time = data.total_time / data.samples
--data.total_time = data.average_time * data.samples
end
if benchmark_time then
data.fraction_time = data.total_time / benchmark_time
end
data.start_time = data.start_time or 0
data.samples = data.samples or 0
data.sample_duration = system.GetTime() - data.start_time
data.times_called = data.samples
table.insert(out, data)
end
end
end
return out
end
function profiler.PrintTraceAborts(min_samples)
min_samples = min_samples or 500
parse_raw_statistical_data()
parse_raw_trace_abort_data()
logn("trace abort reasons for functions that were sampled by the profiler more than ", min_samples, " times:")
local blacklist = {
["NYI: return to lower frame"] = true,
["inner loop in root trace"] = true,
["blacklisted"] = true,
}
for path, lines in pairs(profiler.data.trace_aborts) do
path = path:sub(2)
local s = profiler.data.statistical
if s[path] or not next(s) then
local full_path = R(path) or path
full_path = full_path:replace("../../../", e.CORE_FOLDER)
full_path = full_path:lower():replace(e.ROOT_FOLDER:lower(), "")
local temp = {}
for line, reasons in pairs(lines) do
if not next(s) or s[path][line] and s[path][line].samples > min_samples then
local str = "unknown line"
local content, err = vfs.Read(e.ROOT_FOLDER .. path)
if content then
local lines = content:split("\n")
str = lines[line]
str = "\"" .. str:trim() .. "\""
else
str = err
end
for reason, count in pairs(reasons) do
if not blacklist[reason] then
table.insert(temp, "\t\t" .. reason:trim() .. " (x" .. count .. ")")
table.insert(temp, "\t\t\t" .. line .. ": " .. str)
end
end
end
end
if #temp > 0 then
logn("\t", full_path)
logn(table.concat(temp, "\n"))
end
end
end
end
function profiler.PrintSections()
log(utility.TableToColumns(
"sections",
profiler.GetBenchmark("sections"),
{
{key = "times_called", friendly = "calls"},
{key = "name", tostring = function(val, column) return (" "):rep(column.level - 1) .. tostring(val) end},
{key = "average_time", friendly = "time", tostring = function(val) return math.round(val * 100 * 100, 3) end},
},
function(a) return a.times_called > 50 end,
"i"
))
end
function profiler.PrintStatistical(min_samples)
min_samples = min_samples or 100
local tr = {
N = "native",
I = "interpreted",
G = "garbage collector",
J = "JIT compiler",
C = "C",
}
log(utility.TableToColumns(
"statistical",
profiler.GetBenchmark("statistical"),
{
{key = "name"},
{key = "times_called", friendly = "percent", tostring = function(val, column, columns) return math.round((val / columns[#columns].val.times_called) * 100, 2) end},
{key = "vmstate", tostring = function(str)
return tr[str]
end},
},
function(a) return a.name and a.times_called > min_samples end,
function(a, b) return a.times_called < b.times_called end
))
end
function profiler.StartInstrumental(file_filter, method)
method = method or "cr"
profiler.EnableSectionProfiling(true, true)
profiler.busy = true
local last_info
debug.sethook(function(what, line)
local info = debug.getinfo(2)
if not file_filter or not info.source:find(file_filter, nil, true) then
if what == "call" then
if last_info and last_info.what == "C" then
profiler.PopSection()
end
local name
if info.what == "C" then
name = info.name
if not name then
name = ""
end
local info = debug.getinfo(3)
name = name .. " " .. info.source .. ":" .. info.currentline
end
profiler.PushSection(name)
elseif what == "return" then
profiler.PopSection()
end
end
last_info = info
end, method)
profiler.start_time = system.GetTime()
end
function profiler.StopInstrumental(file_filter, show_everything)
profiler.EnableSectionProfiling(false)
profiler.stop_time = system.GetTime()
profiler.busy = false
debug.sethook()
profiler.PopSection()
log(utility.TableToColumns(
"instrumental",
profiler.GetBenchmark("sections"),
{
{key = "times_called", friendly = "calls"},
{key = "name"},
{key = "average_time", friendly = "time", tostring = function(val) return ("%f"):format(val) end},
{key = "total_time", friendly = "total time", tostring = function(val) return ("%f"):format(val) end},
{key = "fraction_time", friendly = "percent", tostring = function(val) return math.round(val * 100, 2) end},
},
function(a) return show_everything or a.average_time > 0.5 or (file_filter or a.times_called > 100) end,
function(a, b) return a.total_time < b.total_time end
))
end
do
local started = false
function profiler.ToggleInstrumental(file_filter, method)
if file_filter == "" then file_filter = nil end
if not started then
profiler.StartInstrumental(file_filter, method)
started = true
else
profiler.StopInstrumental(file_filter, true)
started = false
end
end
end
function profiler.MeasureInstrumental(time, file_filter, show_everything)
profiler.StartInstrumental(file_filter)
event.Delay(time, function()
profiler.StopInstrumental(file_filter, show_everything)
end)
end
function profiler.DumpZerobraneProfileTree(min, filter)
min = min or 1
local huh = serializer.ReadFile("msgpack", "zerobrane_statistical.msgpack")
local most_samples
local path, root
for k,v in pairs(huh) do
most_samples = most_samples or v
if v.samples >= most_samples.samples then
most_samples = v
path = k
root = v
end
end
local level = 0
local function dump(path, node)
local percent = math.round((node.samples / root.samples) * 100, 3)
if percent > min then
if not filter or path:find(filter) then
logf("%s%s (%s) %s\n", ("\t"):rep(level), percent, node.samples, path)
else
logf("%s%s\n", ("\t"):rep(level), "...")
end
for path, child in pairs(node.children) do
level = level + 1
dump(path, child)
level = level - 1
end
end
end
dump(path, root)
end
function profiler.IsBusy()
return profiler.busy
end
local blacklist = {
["NYI: return to lower frame"] = true,
["inner loop in root trace"] = true,
["leaving loop in root trace"] = true,
["blacklisted"] = true,
["too many spill slots"] = true,
["down-recursion, restarting"] = true,
}
function profiler.EnableRealTimeTraceAbortLogging(b)
if b then
local last_log
jit.attach(function(what, trace_id, func, pc, trace_error_id, trace_error_arg)
if what == "abort" then
local info = jit.util.funcinfo(func, pc)
local reason = jit.vmdef.traceerr[trace_error_id]
if not blacklist[reason] then
if type(trace_error_arg) == "number" and reason:find("bytecode") then
trace_error_arg = string.sub(jit.vmdef.bcnames, trace_error_arg*6+1, trace_error_arg*6+6)
reason = reason:gsub("(%%d)", "%%s")
end
reason = reason:format(trace_error_arg)
local path = info.source
local line = info.currentline or info.linedefined
local content = vfs.Read(e.ROOT_FOLDER .. path:sub(2)) or vfs.Read(path:sub(2))
local str
if content then
str = string.format("%s:%s\n%s:--\t%s\n\n", path:sub(2):replace(e.ROOT_FOLDER, ""), line, content:split("\n")[line]:trim(), reason)
else
str = string.format("%s:%s:\n\t%s\n\n", path, line, reason)
end
if str ~= last_log then
log(str)
last_log = str
end
end
end
end, "trace")
else
jit.attach(function() end)
end
end
local system_GetTime = system.GetTime
function profiler.MeasureFunction(func, count, name, no_print)
count = count or 1
name = name or "measure result"
local total_time = 0
for _ = 1, count do
local time = system_GetTime()
jit.tracebarrier()
func()
jit.tracebarrier()
total_time = total_time + system_GetTime() - time
end
if not no_print then
logf("%s: average: %1.22f total: %f\n", name, total_time / count, total_time)
end
return total_time, func
end
function profiler.MeasureFunctions(tbl, count)
local res = {}
for name, func in pairs(tbl) do
table.insert(res, {time = profiler.MeasureFunction(func, count, name, true), name = name})
end
table.sort(res, function(a, b) return a.time < b.time end)
for i,v in ipairs(res) do
logf("%s: average: %1.22f total: %f\n", v.name, v.time / count, v.time)
end
end
function profiler.Compare(old, new, count)
profiler.MeasureFunction(old, count, "OLD")
profiler.MeasureFunction(new, count, "NEW")
end
profiler.Restart()
return profiler
end)();
-- for profiling
if THREAD then return end
-- tries to load all addons
-- some might not load depending on its info.lua file.
-- for instance: "load = CAPSADMIN ~= nil," will make it load
-- only if the CAPSADMIN constant is not nil.
-- this will skip the src folder though
vfs.MountAddons(e.ROOT_FOLDER)
if not CLI then
logn("[runfile] ", os.clock() - start_time," seconds spent in core/lua/init.lua")
end
do -- autorun
-- call goluwa/*/lua/init.lua if it exists
vfs.InitAddons()
-- load everything in goluwa/*/lua/autorun/*
vfs.AutorunAddons()
-- load everything in goluwa/*/lua/autorun/*USERNAME*/*
vfs.AutorunAddons(e.USERNAME .. "/")
end
e.CLI_TIME = tonumber(os.getenv("GOLUWA_CLI_TIME")) or -1
e.BOOT_TIME = tonumber(os.getenv("GOLUWA_BOOT_TIME")) or -1
e.INIT_TIME = os.clock() - start_time
e.BOOTIME = os.clock()
event.Call("Initialize")
if not CLI then
logn("[runfile] total init time took ", os.clock() - start_time, " seconds to execute")
end
if system.MainLoop then
system.MainLoop()
end
event.Call("ShutDown")
collectgarbage()
collectgarbage() -- https://stackoverflow.com/questions/28320213/why-do-we-need-to-call-luas-collectgarbage-twice
os.realexit(os.exitcode)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment