Last active
June 23, 2018 07:23
-
-
Save CapsAdmin/5992f9b9101004519eb79863e364830e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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