377 lines
11 KiB
Lua
377 lines
11 KiB
Lua
--[[
|
|
|
|
The MIT License (MIT)
|
|
|
|
Copyright (c) 2016-2021 Eric Mauser (Shagu)
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
|
|
]]
|
|
|
|
-- I'm too lazy to implement this myself, for now..
|
|
|
|
PTUtil.SetEnvironment(PTUtil)
|
|
local _G = getfenv(0)
|
|
|
|
|
|
local gfind = string.gmatch or string.gfind
|
|
|
|
-- [ SanitizePattern ]
|
|
-- Sanitizes and convert patterns into gfind compatible ones.
|
|
-- 'pattern' [string] unformatted pattern
|
|
-- returns: [string] simplified gfind compatible pattern
|
|
local sanitize_cache = {}
|
|
function SanitizePattern(pattern)
|
|
if not sanitize_cache[pattern] then
|
|
local ret = pattern
|
|
-- escape magic characters
|
|
ret = gsub(ret, "([%+%-%*%(%)%?%[%]%^])", "%%%1")
|
|
-- remove capture indexes
|
|
ret = gsub(ret, "%d%$","")
|
|
-- catch all characters
|
|
ret = gsub(ret, "(%%%a)","%(%1+%)")
|
|
-- convert all %s to .+
|
|
ret = gsub(ret, "%%s%+",".+")
|
|
-- set priority to numbers over strings
|
|
ret = gsub(ret, "%(.%+%)%(%%d%+%)","%(.-%)%(%%d%+%)")
|
|
-- cache it
|
|
sanitize_cache[pattern] = ret
|
|
end
|
|
|
|
return sanitize_cache[pattern]
|
|
end
|
|
|
|
-- [ GetCaptures ]
|
|
-- Returns the indexes of a given regex pattern
|
|
-- 'pat' [string] unformatted pattern
|
|
-- returns: [numbers] capture indexes
|
|
local capture_cache = {}
|
|
function GetCaptures(pat)
|
|
local r = capture_cache
|
|
if not r[pat] then
|
|
for a, b, c, d, e in gfind(gsub(pat, "%((.+)%)", "%1"), gsub(pat, "%d%$", "%%(.-)$")) do
|
|
r[pat] = { a, b, c, d, e}
|
|
end
|
|
|
|
r[pat] = r[pat] or {}
|
|
end
|
|
|
|
return r[pat][1], r[pat][2], r[pat][3], r[pat][4], r[pat][5]
|
|
end
|
|
|
|
-- [ cmatch ]
|
|
-- Same as string.match but aware of capture indexes (up to 5)
|
|
-- 'str' [string] input string that should be matched
|
|
-- 'pat' [string] unformatted pattern
|
|
-- returns: [strings] matched string in capture order
|
|
local a, b, c, d, e
|
|
local _, va, vb, vc, vd, ve
|
|
local ra, rb, rc, rd, re
|
|
function cmatch(str, pat)
|
|
-- read capture indexes
|
|
a, b, c, d, e = GetCaptures(pat)
|
|
_, _, va, vb, vc, vd, ve = string.find(str, SanitizePattern(pat))
|
|
|
|
-- put entries into the proper return values
|
|
ra = e == 1 and ve or d == 1 and vd or c == 1 and vc or b == 1 and vb or va
|
|
rb = e == 2 and ve or d == 2 and vd or c == 2 and vc or a == 2 and va or vb
|
|
rc = e == 3 and ve or d == 3 and vd or a == 3 and va or b == 3 and vb or vc
|
|
rd = e == 4 and ve or a == 4 and va or c == 4 and vc or b == 4 and vb or vd
|
|
re = a == 5 and va or d == 5 and vd or c == 5 and vc or b == 5 and vb or ve
|
|
|
|
return ra, rb, rc, rd, re
|
|
end
|
|
|
|
-- pfQuest
|
|
function modulo(val, by)
|
|
return val - math.floor(val/by)*by;
|
|
end
|
|
|
|
|
|
-- Modified serialization and compression functions from pfUI/modules/share.lua
|
|
|
|
local indentMap = {[0] = ""}
|
|
for i = 1, 8 do
|
|
indentMap[i] = indentMap[i - 1].." "
|
|
end
|
|
-- Parameters:
|
|
-- tbl: The table to serialize
|
|
-- name: Name of the table, or nil for a nameless table
|
|
-- compare: A table to compare equality to, reducing the output if values are equal, can be nil
|
|
-- ignored: A set of ignored keys at all depths, can be nil
|
|
-- indentation: The number of spaces to indent in each nest, 0-8, default 4
|
|
-- spacing (Internal): String of current indentation
|
|
function SerializeTable(tbl, name, compare, ignored, indentation, spacing)
|
|
indentation = indentation or 4
|
|
spacing = spacing or ""
|
|
local match = nil
|
|
local str = spacing .. (name and (( spacing == "" and "" or "[\"" ) .. name .. ( spacing == "" and "" or "\"]" ) .. " = {\n")
|
|
or "{\n")
|
|
|
|
local isArray = GetTableSize(tbl) == table.getn(tbl)
|
|
for k, v in (isArray and ipairs or pairs)(tbl) do
|
|
local serializedK = type(k) == "number" and k or ("\""..k.."\"")
|
|
local start = str .. spacing .. indentMap[indentation] .. (not isArray and "["..serializedK.."] = " or "")
|
|
local vType = type(v)
|
|
if not ( ignored ~= nil and (ignored[k] and spacing == "") ) and ( compare == nil or compare[k] == nil or compare[k] ~= tbl[k] ) then
|
|
if vType == "table" then
|
|
local result = SerializeTable(tbl[k], k, compare and compare[k], ignored, indentation, spacing .. indentMap[indentation])
|
|
if result then
|
|
match = true
|
|
str = str .. result
|
|
end
|
|
elseif vType == "string" then
|
|
match = true
|
|
str = start.."\""..string.gsub(v, "\\", "\\\\").."\",\n"
|
|
elseif vType == "number" or vType == "boolean" then
|
|
match = true
|
|
str = start..tostring(v)..",\n"
|
|
end
|
|
end
|
|
end
|
|
|
|
if not match then
|
|
return nil
|
|
end
|
|
|
|
str = str .. spacing .. "}" .. ( spacing == "" and "" or "," ) .. "\n"
|
|
return str
|
|
end
|
|
|
|
function DeserializeTable(str)
|
|
return loadstring("return "..str)()
|
|
end
|
|
|
|
function Compress(input)
|
|
-- based on Rochet2's lzw compression
|
|
if type(input) ~= "string" then
|
|
return nil
|
|
end
|
|
local len = strlen(input)
|
|
if len <= 1 then
|
|
return "u"..input
|
|
end
|
|
|
|
local dict = {}
|
|
for i = 0, 255 do
|
|
local ic, iic = strchar(i), strchar(i, 0)
|
|
dict[ic] = iic
|
|
end
|
|
local a, b = 0, 1
|
|
|
|
local result = {"c"}
|
|
local resultlen = 1
|
|
local n = 2
|
|
local word = ""
|
|
for i = 1, len do
|
|
local c = strsub(input, i, i)
|
|
local wc = word..c
|
|
if not dict[wc] then
|
|
local write = dict[word]
|
|
if not write then
|
|
return nil
|
|
end
|
|
result[n] = write
|
|
resultlen = resultlen + strlen(write)
|
|
n = n+1
|
|
if len <= resultlen then
|
|
return "u"..input
|
|
end
|
|
local str = wc
|
|
if a >= 256 then
|
|
a, b = 0, b+1
|
|
if b >= 256 then
|
|
dict = {}
|
|
b = 1
|
|
end
|
|
end
|
|
dict[str] = strchar(a,b)
|
|
a = a+1
|
|
word = c
|
|
else
|
|
word = wc
|
|
end
|
|
end
|
|
result[n] = dict[word]
|
|
resultlen = resultlen+strlen(result[n])
|
|
n = n+1
|
|
if len <= resultlen then
|
|
return "u"..input
|
|
end
|
|
return table.concat(result)
|
|
end
|
|
|
|
function Decompress(input)
|
|
-- based on Rochet2's lzw compression
|
|
if type(input) ~= "string" or strlen(input) < 1 then
|
|
return nil
|
|
end
|
|
|
|
local control = strsub(input, 1, 1)
|
|
if control == "u" then
|
|
return strsub(input, 2)
|
|
elseif control ~= "c" then
|
|
return nil
|
|
end
|
|
input = strsub(input, 2)
|
|
local len = strlen(input)
|
|
|
|
if len < 2 then
|
|
return nil
|
|
end
|
|
|
|
local dict = {}
|
|
for i = 0, 255 do
|
|
local ic, iic = strchar(i), strchar(i, 0)
|
|
dict[iic] = ic
|
|
end
|
|
|
|
local a, b = 0, 1
|
|
|
|
local result = {}
|
|
local n = 1
|
|
local last = strsub(input, 1, 2)
|
|
result[n] = dict[last]
|
|
n = n+1
|
|
for i = 3, len, 2 do
|
|
local code = strsub(input, i, i+1)
|
|
local lastStr = dict[last]
|
|
if not lastStr then
|
|
return nil
|
|
end
|
|
local toAdd = dict[code]
|
|
if toAdd then
|
|
result[n] = toAdd
|
|
n = n+1
|
|
local str = lastStr..strsub(toAdd, 1, 1)
|
|
if a >= 256 then
|
|
a, b = 0, b+1
|
|
if b >= 256 then
|
|
dict = {}
|
|
b = 1
|
|
end
|
|
end
|
|
dict[strchar(a,b)] = str
|
|
a = a+1
|
|
else
|
|
local str = lastStr..strsub(lastStr, 1, 1)
|
|
result[n] = str
|
|
n = n+1
|
|
if a >= 256 then
|
|
a, b = 0, b+1
|
|
if b >= 256 then
|
|
dict = {}
|
|
b = 1
|
|
end
|
|
end
|
|
dict[strchar(a,b)] = str
|
|
a = a+1
|
|
end
|
|
last = code
|
|
end
|
|
return table.concat(result)
|
|
end
|
|
|
|
function Encode(to_encode)
|
|
local index_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
|
local bit_pattern = ''
|
|
local encoded = ''
|
|
local trailing = ''
|
|
|
|
for i = 1, string.len(to_encode) do
|
|
local remaining = tonumber(string.byte(string.sub(to_encode, i, i)))
|
|
local bin_bits = ''
|
|
for i = 7, 0, -1 do
|
|
local current_power = math.pow(2, i)
|
|
if remaining >= current_power then
|
|
bin_bits = bin_bits .. '1'
|
|
remaining = remaining - current_power
|
|
else
|
|
bin_bits = bin_bits .. '0'
|
|
end
|
|
end
|
|
bit_pattern = bit_pattern .. bin_bits
|
|
end
|
|
|
|
if mod(string.len(bit_pattern), 3) == 2 then
|
|
trailing = '=='
|
|
bit_pattern = bit_pattern .. '0000000000000000'
|
|
elseif mod(string.len(bit_pattern), 3) == 1 then
|
|
trailing = '='
|
|
bit_pattern = bit_pattern .. '00000000'
|
|
end
|
|
|
|
local count = 0
|
|
for i = 1, string.len(bit_pattern), 6 do
|
|
local byte = string.sub(bit_pattern, i, i+5)
|
|
local offset = tonumber(tonumber(byte, 2))
|
|
encoded = encoded .. string.sub(index_table, offset+1, offset+1)
|
|
count = count + 1
|
|
if count >= 92 then
|
|
encoded = encoded .. "\n"
|
|
count = 0
|
|
end
|
|
end
|
|
|
|
return string.sub(encoded, 1, -1 - string.len(trailing)) .. trailing
|
|
end
|
|
|
|
function Decode(to_decode)
|
|
local index_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
|
local padded = gsub(to_decode,"%s", "")
|
|
local unpadded = gsub(padded,"=", "")
|
|
local bit_pattern = ''
|
|
local decoded = ''
|
|
|
|
to_decode = gsub(to_decode,"\n", "")
|
|
to_decode = gsub(to_decode," ", "")
|
|
|
|
for i = 1, string.len(unpadded) do
|
|
local char = string.sub(to_decode, i, i)
|
|
local offset, _ = string.find(index_table, char)
|
|
if offset == nil then return nil end
|
|
|
|
local remaining = tonumber(offset-1)
|
|
local bin_bits = ''
|
|
for i = 7, 0, -1 do
|
|
local current_power = math.pow(2, i)
|
|
if remaining >= current_power then
|
|
bin_bits = bin_bits .. '1'
|
|
remaining = remaining - current_power
|
|
else
|
|
bin_bits = bin_bits .. '0'
|
|
end
|
|
end
|
|
|
|
bit_pattern = bit_pattern .. string.sub(bin_bits, 3)
|
|
end
|
|
|
|
for i = 1, string.len(bit_pattern), 8 do
|
|
local byte = string.sub(bit_pattern, i, i+7)
|
|
decoded = decoded .. strchar(tonumber(byte, 2))
|
|
end
|
|
|
|
local padding_length = string.len(padded)-string.len(unpadded)
|
|
|
|
if (padding_length == 1 or padding_length == 2) then
|
|
decoded = string.sub(decoded,1,-2)
|
|
end
|
|
|
|
return decoded
|
|
end |