560 lines
No EOL
15 KiB
Lua
560 lines
No EOL
15 KiB
Lua
--[[
|
||
Title: Adv. Dupe 2 Codec Legacy Support
|
||
|
||
Desc: Facilitates opening of dupes from AD1 and earlier AD2 versions.
|
||
|
||
Author: emspike
|
||
|
||
Version: 2.0
|
||
]]
|
||
|
||
local pairs = pairs
|
||
local type = type
|
||
local tonumber = tonumber
|
||
local error = error
|
||
local Vector = Vector
|
||
local Angle = Angle
|
||
local unpack = unpack
|
||
local format = string.format
|
||
local char = string.char
|
||
local byte = string.byte
|
||
local sub = string.sub
|
||
local gsub = string.gsub
|
||
local find = string.find
|
||
local gmatch = string.gmatch
|
||
local match = string.match
|
||
local concat = table.concat
|
||
|
||
--[[
|
||
Name: GenerateDupeStamp
|
||
Desc: Generates an info table.
|
||
Params: <player> ply
|
||
Return: <table> stamp
|
||
]]
|
||
function AdvDupe2.GenerateDupeStamp(ply)
|
||
local stamp = {}
|
||
stamp.name = ply:GetName()
|
||
stamp.time = os.date("%I:%M %p")
|
||
stamp.date = os.date("%d %B %Y")
|
||
stamp.timezone = os.date("%z")
|
||
hook.Call("AdvDupe2_StampGenerated",GAMEMODE,stamp)
|
||
return stamp
|
||
end
|
||
|
||
local AD2FF = "AD2F%s\n%s\n%s"
|
||
|
||
local decode_types_v1, decode_types_v2
|
||
local tables = 0
|
||
local str,pos
|
||
local a,b,c,m,n,w,tblref
|
||
|
||
local function read_v2()
|
||
local t = byte(str, pos+1)
|
||
if t then
|
||
local dt = decode_types_v2[t]
|
||
if dt then
|
||
pos = pos + 1
|
||
return dt()
|
||
else
|
||
error(format("encountered invalid data type (%u)\n",t))
|
||
end
|
||
else
|
||
error("expected value, got EOF\n")
|
||
end
|
||
end
|
||
|
||
decode_types_v2 = {
|
||
[1 ] = function()
|
||
error("expected value, got terminator\n")
|
||
end,
|
||
[2 ] = function() -- table
|
||
|
||
m = find(str, "\1", pos)
|
||
if m then
|
||
w = sub(str, pos+1, m-1)
|
||
pos = m
|
||
else
|
||
error("expected table identifier, got EOF\n")
|
||
end
|
||
|
||
local t = {}
|
||
tables[w] = t
|
||
|
||
while true do
|
||
if byte(str, pos+1) == 1 then
|
||
pos = pos + 1
|
||
return t
|
||
else
|
||
t[read_v2()] = read_v2()
|
||
end
|
||
end
|
||
end,
|
||
[3 ] = function() -- array
|
||
|
||
m = find(str, "\1", pos)
|
||
if m then
|
||
w = sub(str, pos+1, m-1)
|
||
pos = m
|
||
else
|
||
error("expected table identifier, got EOF\n")
|
||
end
|
||
|
||
local t, i = {}, 1
|
||
|
||
tables[w] = t
|
||
|
||
while true do
|
||
if byte(str,pos+1) == 1 then
|
||
pos = pos+1
|
||
return t
|
||
else
|
||
t[i] = read_v2()
|
||
i = i + 1
|
||
end
|
||
end
|
||
end,
|
||
[4 ] = function() -- false boolean
|
||
return false
|
||
end,
|
||
[5 ] = function() -- true boolean
|
||
return true
|
||
end,
|
||
[6 ] = function() -- number
|
||
m = find(str, "\1", pos)
|
||
if m then
|
||
a = tonumber(sub(str, pos+1, m-1)) or 0
|
||
pos = m
|
||
return a
|
||
else
|
||
error("expected number, got EOF\n")
|
||
end
|
||
end,
|
||
[7 ] = function() -- string
|
||
m = find(str,"\1",pos)
|
||
if m then
|
||
w = sub(str, pos+1, m-1)
|
||
pos = m
|
||
return w
|
||
else
|
||
error("expected string, got EOF\n")
|
||
end
|
||
end,
|
||
[8 ] = function() -- Vector
|
||
m,n = find(str,".-\1.-\1.-\1", pos)
|
||
if m then
|
||
a,b,c = match(str,"^(.-)\1(.-)\1(.-)\1",pos+1)
|
||
pos = n
|
||
return Vector(tonumber(a), tonumber(b), tonumber(c))
|
||
else
|
||
error("expected vector, got EOF\n")
|
||
end
|
||
end,
|
||
[9 ] = function() -- Angle
|
||
m,n = find(str, ".-\1.-\1.-\1", pos)
|
||
if m then
|
||
a,b,c = match(str, "^(.-)\1(.-)\1(.-)\1",pos+1)
|
||
pos = n
|
||
return Angle(tonumber(a), tonumber(b), tonumber(c))
|
||
else
|
||
error("expected angle, got EOF\n")
|
||
end
|
||
end,
|
||
[10 ] = function() -- Table Reference
|
||
m = find(str,"\1",pos)
|
||
if m then
|
||
w = sub(str,pos+1,m-1)
|
||
pos = m
|
||
else
|
||
error("expected table identifier, got EOF\n")
|
||
end
|
||
tblref = tables[w]
|
||
|
||
if tblref then
|
||
return tblref
|
||
else
|
||
error(format("table identifier %s points to nil\n", w))
|
||
end
|
||
|
||
end
|
||
}
|
||
|
||
|
||
|
||
local function read_v1()
|
||
local t = byte(str,pos+1)
|
||
if t then
|
||
local dt = decode_types_v1[t]
|
||
if dt then
|
||
pos = pos + 1
|
||
return dt()
|
||
else
|
||
error(format("encountered invalid data type (%u)\n",t))
|
||
end
|
||
else
|
||
error("expected value, got EOF\n")
|
||
end
|
||
end
|
||
|
||
decode_types_v1 = {
|
||
[1 ] = function()
|
||
error("expected value, got terminator\n")
|
||
end,
|
||
[2 ] = function() -- table
|
||
local t = {}
|
||
while true do
|
||
if byte(str,pos+1) == 1 then
|
||
pos = pos+1
|
||
return t
|
||
else
|
||
t[read_v1()] = read_v1()
|
||
end
|
||
end
|
||
end,
|
||
[3 ] = function() -- array
|
||
local t, i = {}, 1
|
||
while true do
|
||
if byte(str,pos+1) == 1 then
|
||
pos = pos+1
|
||
return t
|
||
else
|
||
t[i] = read_v1()
|
||
i = i + 1
|
||
end
|
||
end
|
||
end,
|
||
[4 ] = function() -- false boolean
|
||
return false
|
||
end,
|
||
[5 ] = function() -- true boolean
|
||
return true
|
||
end,
|
||
[6 ] = function() -- number
|
||
m = find(str,"\1",pos)
|
||
if m then
|
||
a = tonumber(sub(str,pos+1,m-1)) or 0
|
||
pos = m
|
||
return a
|
||
else
|
||
error("expected number, got EOF\n")
|
||
end
|
||
end,
|
||
[7 ] = function() -- string
|
||
m = find(str,"\1",pos)
|
||
if m then
|
||
w = sub(str,pos+1,m-1)
|
||
pos = m
|
||
return w
|
||
else
|
||
error("expected string, got EOF\n")
|
||
end
|
||
end,
|
||
[8 ] = function() -- Vector
|
||
m,n = find(str,".-\1.-\1.-\1",pos)
|
||
if m then
|
||
a,b,c = match(str,"^(.-)\1(.-)\1(.-)\1",pos+1)
|
||
pos = n
|
||
return Vector(tonumber(a), tonumber(b), tonumber(c))
|
||
else
|
||
error("expected vector, got EOF\n")
|
||
end
|
||
end,
|
||
[9 ] = function() -- Angle
|
||
m,n = find(str,".-\1.-\1.-\1",pos)
|
||
if m then
|
||
a,b,c = match(str,"^(.-)\1(.-)\1(.-)\1",pos+1)
|
||
pos = n
|
||
return Angle(tonumber(a), tonumber(b), tonumber(c))
|
||
else
|
||
error("expected angle, got EOF\n")
|
||
end
|
||
end
|
||
}
|
||
|
||
local function deserialize_v1(data)
|
||
str = data
|
||
pos = 0
|
||
tables = {}
|
||
return read_v1()
|
||
end
|
||
|
||
local function deserialize_v2(data)
|
||
str = data
|
||
pos = 0
|
||
tables = {}
|
||
return read_v2()
|
||
end
|
||
|
||
local function lzwDecode(encoded)
|
||
local dictionary_length = 256
|
||
local dictionary = {}
|
||
for i = 0, 255 do
|
||
dictionary[i] = char(i)
|
||
end
|
||
|
||
local pos = 2
|
||
local decompressed = {}
|
||
local decompressed_length = 1
|
||
|
||
local index = byte(encoded)
|
||
local word = dictionary[index]
|
||
|
||
decompressed[decompressed_length] = dictionary[index]
|
||
|
||
local entry
|
||
local encoded_length = #encoded
|
||
local firstbyte --of an index
|
||
while pos <= encoded_length do
|
||
firstbyte = byte(encoded,pos)
|
||
if firstbyte > 252 then --now we know it's a length indicator for a multibyte index
|
||
index = 0
|
||
firstbyte = 256 - firstbyte
|
||
|
||
--[[if pos+firstbyte > encoded_length then --will test for performance impact
|
||
error("expected index got EOF")
|
||
end]]
|
||
|
||
for i = pos+firstbyte, pos+1, -1 do
|
||
index = bit.bor(bit.lshift(index, 8), byte(encoded,i))
|
||
end
|
||
pos = pos + firstbyte + 1
|
||
else
|
||
index = firstbyte
|
||
pos = pos + 1
|
||
end
|
||
entry = dictionary[index] or (word..sub(word,1,1))
|
||
decompressed_length = decompressed_length + 1
|
||
decompressed[decompressed_length] = entry
|
||
dictionary[dictionary_length] = word..sub(entry,1,1)
|
||
dictionary_length = dictionary_length + 1
|
||
word = entry
|
||
end
|
||
return concat(decompressed)
|
||
end
|
||
|
||
--http://en.wikipedia.org/wiki/Huffman_coding#Decompression
|
||
|
||
local invcodes = {[2]={[0]="\254"},[5]={[22]="\1",[11]="\2"},[6]={[13]="\7",[35]="\6",[37]="\5",[58]="\3",[31]="\8",[9]="\13",[51]="\9",[55]="\10",[57]="\4",[59]="\15"},[7]={[1]="\14",[15]="\16",[87]="\31",[89]="\30",[62]="\26",[17]="\27",[97]="\19",[19]="\43",[10]="\12",[39]="\33",[41]="\24",[82]="\40",[3]="\32",[46]="\41",[47]="\38",[94]="\25",[65]="\23",[50]="\39",[26]="\11",[7]="\28",[33]="\18",[61]="\17",[25]="\42"},[8]={[111]="\101",[162]="\29",[2]="\34",[133]="\21",[142]="\36",[5]="\20",[21]="\37",[170]="\44",[130]="\22",[66]="\35"},[9]={[241]="\121",[361]="\104",[365]="\184",[125]="\227",[373]="\198",[253]="\117",[381]="\57",[270]="\49",[413]="\80",[290]="\47",[294]="\115",[38]="\112",[429]="\74",[433]="\0",[437]="\48",[158]="\183",[453]="\107",[166]="\111",[469]="\182",[477]="\241",[45]="\86",[489]="\69",[366]="\100",[497]="\61",[509]="\76",[49]="\53",[390]="\78",[279]="\196",[283]="\70",[414]="\98",[53]="\55",[422]="\109",[233]="\79",[349]="\89",[369]="\52",[14]="\105",[238]="\56",[319]="\162",[323]="\83",[327]="\63",[458]="\65",[335]="\231",[339]="\225",[337]="\114",[347]="\193",[493]="\139",[23]="\209",[359]="\250",[490]="\68",[42]="\54",[63]="\91",[286]="\97",[254]="\50",[510]="\108",[109]="\73",[67]="\103",[255]="\122",[69]="\170",[70]="\110",[407]="\176",[411]="\119",[110]="\120",[83]="\146",[149]="\163",[151]="\224",[85]="\51",[155]="\177",[79]="\251",[27]="\118",[447]="\159",[451]="\228",[455]="\175",[383]="\174",[463]="\243",[467]="\157",[173]="\210",[475]="\167",[177]="\84",[90]="\45",[487]="\206",[93]="\226",[495]="\245",[207]="\64",[127]="\147",[191]="\155",[511]="\153",[195]="\208",[197]="\85",[199]="\178",[181]="\82",[102]="\116",[103]="\71",[285]="\144",[105]="\102",[211]="\199",[213]="\123",[301]="\66",[305]="\46",[219]="\137",[81]="\67",[91]="\88",[157]="\130",[325]="\95",[29]="\58",[231]="\201",[117]="\99",[341]="\222",[237]="\77",[239]="\211",[71]="\223"},[10]={[710]="\149",[245]="\60",[742]="\172",[774]="\81",[134]="\151",[917]="\145",[274]="\216",[405]="\242",[146]="\194",[838]="\246",[298]="\248",[870]="\189",[1013]="\150",[894]="\190",[326]="\244",[330]="\166",[334]="\217",[465]="\179",[346]="\59",[354]="\180",[966]="\212",[974]="\143",[370]="\148",[998]="\154",[625]="\138",[382]="\161",[194]="\141",[198]="\126",[402]="\96",[206]="\185",[586]="\129",[721]="\187",[610]="\135",[618]="\181",[626]="\72",[226]="\62",[454]="\127",[658]="\113",[462]="\164",[234]="\214",[474]="\140",[242]="\106",[714]="\188",[730]="\87",[498]="\237",[746]="\125",[754]="\229",[786]="\128",[202]="\93",[18]="\255",[810]="\173",[846]="\131",[74]="\192",[842]="\142",[977]="\252",[858]="\235",[78]="\134",[874]="\234",[882]="\90",[646]="\92",[1006]="\160",[126]="\165",[914]="\221",[718]="\94",[738]="\238",[638]="\197",[482]="\230",[34]="\220",[962]="\133",[6]="\213",[706]="\219",[986]="\171",[994]="\233",[866]="\200",[1010]="\247",[98]="\169",[518]="\236",[494]="\207",[230]="\205",[542]="\191",[501]="\202",[530]="\203",[450]="\204",[209]="\158",[106]="\186",[590]="\136",[218]="\232",[733]="\124",[309]="\168",[221]="\152",[757]="\240",[113]="\215",[114]="\156",[362]="\239",[486]="\132",[358]="\249",[262]="\75",[30]="\218",[821]="\195",[546]="\253"}}
|
||
|
||
local function huffmanDecode(encoded)
|
||
|
||
local h1,h2,h3 = byte(encoded, 1, 3)
|
||
|
||
if (not h3) or (#encoded < 4) then
|
||
error("invalid input")
|
||
end
|
||
|
||
local original_length = bit.bor(bit.lshift(h3,16), bit.lshift(h2,8), h1)
|
||
local encoded_length = #encoded+1
|
||
local decoded = {}
|
||
local decoded_length = 0
|
||
local buffer = 0
|
||
local buffer_length = 0
|
||
local code
|
||
local code_len = 2
|
||
local temp
|
||
local pos = 4
|
||
|
||
while decoded_length < original_length do
|
||
if code_len <= buffer_length then
|
||
temp = invcodes[code_len]
|
||
code = bit.band(buffer, bit.lshift(1, code_len)-1)
|
||
if temp and temp[code] then --most of the time temp is nil
|
||
decoded_length = decoded_length + 1
|
||
decoded[decoded_length] = temp[code]
|
||
buffer = bit.rshift(buffer, code_len)
|
||
buffer_length = buffer_length - code_len
|
||
code_len = 2
|
||
else
|
||
code_len = code_len + 1
|
||
if code_len > 10 then
|
||
error("malformed code")
|
||
end
|
||
end
|
||
else
|
||
buffer = bit.bor(buffer, bit.lshift(byte(encoded, pos), buffer_length))
|
||
buffer_length = buffer_length + 8
|
||
pos = pos + 1
|
||
if pos > encoded_length then
|
||
error("malformed code")
|
||
end
|
||
end
|
||
end
|
||
|
||
return concat(decoded)
|
||
end
|
||
|
||
local function invEscapeSub(str)
|
||
local escseq,body = match(str,"^(.-)\n(.-)$")
|
||
|
||
if not escseq then error("invalid input") end
|
||
|
||
return gsub(body,escseq,"\26")
|
||
end
|
||
|
||
local dictionary
|
||
local subtables
|
||
|
||
local function deserializeChunk(chunk)
|
||
|
||
local ctype,val = byte(chunk),sub(chunk,3)
|
||
|
||
if ctype == 89 then return dictionary[ val ]
|
||
elseif ctype == 86 then
|
||
local a,b,c = match(val,"^(.-),(.-),(.+)$")
|
||
return Vector( tonumber(a), tonumber(b), tonumber(c) )
|
||
elseif ctype == 65 then
|
||
local a,b,c = match(val,"^(.-),(.-),(.+)$")
|
||
return Angle( tonumber(a), tonumber(b), tonumber(c) )
|
||
elseif ctype == 84 then
|
||
local t = {}
|
||
local tv = subtables[val]
|
||
if not tv then
|
||
tv = {}
|
||
subtables[ val ] = tv
|
||
end
|
||
tv[#tv+1] = t
|
||
return t
|
||
elseif ctype == 78 then return tonumber(val)
|
||
elseif ctype == 83 then return gsub(sub(val,2,-2),"<EFBFBD>",";")
|
||
elseif ctype == 66 then return val == "t"
|
||
elseif ctype == 80 then return 1
|
||
end
|
||
|
||
error(format("AD1 deserialization failed: invalid chunk (%u:%s)",ctype,val))
|
||
|
||
end
|
||
|
||
local function deserializeAD1(dupestring)
|
||
|
||
dupestring = dupestring:Replace("\r\n", "\n")
|
||
local header, extraHeader, dupeBlock, dictBlock = dupestring:match("%[Info%]\n(.+)\n%[More Information%]\n(.+)\n%[Save%]\n(.+)\n%[Dict%]\n(.+)")
|
||
|
||
if not header then
|
||
error("unknown duplication format")
|
||
end
|
||
|
||
local info = {}
|
||
for k,v in header:gmatch("([^\n:]+):([^\n]+)") do
|
||
info[k] = v
|
||
end
|
||
|
||
local moreinfo = {}
|
||
for k,v in extraHeader:gmatch("([^\n:]+):([^\n]+)") do
|
||
moreinfo[k] = v
|
||
end
|
||
|
||
dictionary = {}
|
||
for k,v in dictBlock:gmatch("(.-):\"(.-)\"\n") do
|
||
dictionary[k] = v
|
||
end
|
||
|
||
local dupe = {}
|
||
for key,block in dupeBlock:gmatch("([^\n:]+):([^\n]+)") do
|
||
|
||
local tables = {}
|
||
subtables = {}
|
||
local head
|
||
|
||
for id,chunk in block:gmatch('(%w+){(.-)}') do
|
||
|
||
--check if this table is the trunk
|
||
if byte(id) == 72 then
|
||
id = sub(id,2)
|
||
head = id
|
||
end
|
||
|
||
tables[id] = {}
|
||
|
||
for kv in gmatch(chunk,'[^;]+') do
|
||
|
||
local k,v = match(kv,'(.-)=(.+)')
|
||
|
||
if k then
|
||
k = deserializeChunk( k )
|
||
v = deserializeChunk( v )
|
||
|
||
tables[id][k] = v
|
||
else
|
||
v = deserializeChunk( kv )
|
||
local tid = tables[id]
|
||
tid[#tid+1]=v
|
||
end
|
||
|
||
end
|
||
end
|
||
|
||
--Restore table references
|
||
for id,tbls in pairs( subtables ) do
|
||
for _,tbl in pairs( tbls ) do
|
||
if not tables[id] then error("attempt to reference a nonexistent table") end
|
||
for k,v in pairs(tables[id]) do
|
||
tbl[k] = v
|
||
end
|
||
end
|
||
end
|
||
|
||
dupe[key] = tables[ head ]
|
||
|
||
end
|
||
|
||
return dupe, info, moreinfo
|
||
|
||
end
|
||
|
||
--seperates the header and body and converts the header to a table
|
||
local function getInfo(str)
|
||
local last = str:find("\2")
|
||
if not last then
|
||
error("attempt to read AD2 file with malformed info block error 1")
|
||
end
|
||
local info = {}
|
||
local ss = str:sub(1,last-1)
|
||
for k,v in ss:gmatch("(.-)\1(.-)\1") do
|
||
info[k] = v
|
||
end
|
||
if info.check ~= "\r\n\t\n" then
|
||
if info.check == "\10\9\10" then
|
||
error("detected AD2 file corrupted in file transfer (newlines homogenized)(when using FTP, transfer AD2 files in image/binary mode, not ASCII/text mode)")
|
||
else
|
||
error("attempt to read AD2 file with malformed info block error 2")
|
||
end
|
||
end
|
||
return info, str:sub(last+2)
|
||
end
|
||
|
||
--decoders for individual versions go here
|
||
local versions = {}
|
||
|
||
versions[2] = function(encodedDupe)
|
||
encodedDupe = encodedDupe:Replace("\r\r\n\t\r\n", "\t\t\t\t")
|
||
encodedDupe = encodedDupe:Replace("\r\n\t\n", "\t\t\t\t")
|
||
encodedDupe = encodedDupe:Replace("\r\n", "\n")
|
||
encodedDupe = encodedDupe:Replace("\t\t\t\t", "\r\n\t\n")
|
||
local info, dupestring = getInfo(encodedDupe:sub(7))
|
||
return deserialize_v2(
|
||
lzwDecode(
|
||
huffmanDecode(
|
||
invEscapeSub(dupestring)
|
||
)
|
||
)
|
||
), info
|
||
end
|
||
|
||
versions[1] = function(encodedDupe)
|
||
encodedDupe = encodedDupe:Replace("\r\r\n\t\r\n", "\t\t\t\t")
|
||
encodedDupe = encodedDupe:Replace("\r\n\t\n", "\t\t\t\t")
|
||
encodedDupe = encodedDupe:Replace("\r\n", "\n")
|
||
encodedDupe = encodedDupe:Replace("\t\t\t\t", "\r\n\t\n")
|
||
local info, dupestring = getInfo(encodedDupe:sub(7))
|
||
return deserialize_v1(
|
||
lzwDecode(
|
||
huffmanDecode(
|
||
invEscapeSub(dupestring)
|
||
)
|
||
)
|
||
), info
|
||
end
|
||
|
||
versions[0] = deserializeAD1
|
||
|
||
AdvDupe2.LegacyDecoders = versions |