-- https://github.com/MCJack123/CC-Archive/blob/master/tar.lua
-- usage string removed
local tar = (function(...) local function a(b)return string.match(b,'^()[%s%z]*$')and''or string.match(b,'^[%s%z]*(.*[^%s%z])')end;local function c(d)return bit.band(d,0x1)*8+bit.band(d,0x2)+bit.band(d,0x4)/4+4 end;local function e(d)return bit.band(d,0x8)/8+bit.band(d,0x2)+bit.band(d,0x1)*4 end;local function f(g,h,i)return string.len(g)<h and string.sub(g,1,h)..string.rep(i or" ",h-string.len(g))or g end;local function j(g,h,i)return string.len(g)<h and string.rep(i or" ",h-string.len(g))..string.sub(g,1,h)or g end;local function k(l,m,...)if m and l[m]==nil then l[m]={}end;return m~=nil and k(l[m],...)or l end;local function n(g,o)local l={}for b in string.gmatch(g,"([^"..(o or"%s").."]+)")do table.insert(l,b)end;return l end;local p=0;local q=false;local r={}function r.unserialize(s)local t={}local u={}for v,w in pairs(s)do local x=n(w.name,"/")local y=table.remove(x,table.maxn(x))local z=k(t,table.unpack(x))if w.type==0 or w.type==7 then z[y]=w elseif w.type==1 or w.type==2 then table.insert(u,w)elseif w.type==5 then z[y]={["//"]=w}end end;for v,w in pairs(u)do local x=n(w.name,"/")local y=table.remove(x,table.maxn(x))k(t,table.unpack(x))[y]=k(t,table.unpack(n(w.link,"/")))end;return t end;function r.serialize(s)local t=s["//"]~=nil and#s["//"]>0 and{s["//"]}or{}for v,w in pairs(s)do if v~="//"then if w["//"]~=nil or w.name==nil then local l=table.maxn(t)for A,B in ipairs(r.serialize(w))do t[l+A]=B end else table.insert(t,w)end end end;return t end;function r.load(C,D,E)if not fs.exists(C)and not E then error("Path does not exist",2)end;local F;if E then local b=1;F={read=function(G)if G then b=b+G;return string.sub(C,b-G,b-1)end;b=b+1;return string.byte(string.sub(C,b-1,b-1))end,close=function()end,seek=true}else F=fs.open(C,"rb")end;local H=F.read;local I=0;local J=0;F.read=function(i)i=i or 1;if i<1 then return end;local t=nil;if F.seek then t=H(i)for K in t:gmatch(".")do I=I+K:byte()end else for m=1,i do local L=H()if L==nil then return t end;t=(t or"")..string.char(L)I=I+L;if m%1000000==0 then os.queueEvent("nosleep")os.pullEvent()end end end;J=J+i;return t end;local t={}local M=0;while true do local s={}I=0;s.name=F.read(100)assert(J%512==100)if s.name==nil then break elseif s.name==string.rep("\0",100)then F.read(412)assert(J%512==0)M=M+1;if M==2 and not q then break end else s.name=a(s.name)s.mode=tonumber(a(F.read(8)),8)s.owner=tonumber(a(F.read(8)),8)s.group=tonumber(a(F.read(8)),8)local N=tonumber(a(F.read(12)),8)s.timestamp=tonumber(a(F.read(12)),8)local O=I;local P=tonumber(a(F.read(8)),8)I=O+256;local l=F.read()s.type=tonumber(l=="\0"and"0"or l)or l;s.link=a(F.read(100))if a(F.read(6))=="ustar"then F.read(2)s.ownerName=a(F.read(32))s.groupName=a(F.read(32))s.deviceNumber={tonumber(a(F.read(8))),tonumber(a(F.read(8)))}if s.deviceNumber[1]==nil and s.deviceNumber[2]==nil then s.deviceNumber=nil end;s.name=a(F.read(155))..s.name end;F.read(512-J%512)assert(J%512==0)if I~=P then print("Warning: checksum mismatch for "..s.name)end;if N~=nil and N>0 then s.data=F.read(N)if N%512~=0 then F.read(512-J%512)end end;assert(J%512==0)table.insert(t,s)end;os.queueEvent("nosleep")os.pullEvent()end;F.close()return D and t or r.unserialize(t)end;function r.extract(s,C,Q)fs.makeDir(C)local u={}for v,w in pairs(s)do if v~="//"then local d=fs.combine(C,v)if w["//"]~=nil then local A=r.extract(w,d,kernel~=nil)if kernel then for A,B in pairs(A)do table.insert(u,B)end end elseif(w.type==1 or w.type==2)and kernel then table.insert(u,w)elseif w.type==0 or w.type==7 then local F=fs.open(d,"wb")if w.data~=nil then for b in string.gmatch(w.data,".")do F.write(string.byte(b))end end;F.close()if kernel and w.owner~=nil then fs.setPermissions(d,"*",c(bit.brshift(w.mode,6))+bit.band(w.mode,0x800)/0x80)if w.ownerName~=nil and w.ownerName~=""then fs.setPermissions(d,users.getUIDFromName(w.ownerName),c(w.mode)+bit.band(w.mode,0x800)/0x80)fs.setOwner(d,users.getUIDFromName(w.ownerName))else fs.setPermissions(d,w.owner,c(w.mode)+bit.band(w.mode,0x800)/0x80)fs.setOwner(d,w.owner)end end elseif w.type~=nil then print("Unimplemented type "..w.type)end;if p>0 then print((w["//"]and w["//"].name or w.name or"?").." => "..(d or"?"))end;os.queueEvent("nosleep")os.pullEvent()end end;if Q then return u elseif kernel then for v,w in pairs(u)do end end end;function r.read(R,d)local F=fs.open(fs.combine(R,d),"rb")local t={name=d,mode=fs.getPermissions and e(fs.getPermissions(d,fs.getOwner(d)or 0))*0x40+e(fs.getPermissions(d,"*"))+bit.band(fs.getPermissions(d,"*"),0x10)*0x80 or 0x1FF,owner=fs.getOwner and fs.getOwner(d)or 0,group=0,timestamp=os.epoch and math.floor(os.epoch("utc")/1000)or 0,type=0,link="",ownerName=fs.getOwner and users.getShortName(fs.getOwner(d))or"",groupName="",deviceNumber=nil,data=""}if F.seek then t.data=F.read(fs.getSize(fs.combine(R,d)))else local i=F.read()while i~=nil do t.data=t.data..string.char(i)i=F.read()end end;F.close()return t end;function r.pack(R,C)if not fs.isDir(R)then return r.read(R,C)end;local t={["//"]={name=C.."/",mode=fs.getPermissions and e(fs.getPermissions(C,fs.getOwner(C)or 0))*0x40+e(fs.getPermissions(C,"*"))+bit.band(fs.getPermissions(C,"*"),0x10)*0x80 or 0x1FF,owner=fs.getOwner and fs.getOwner(C)or 0,group=0,timestamp=os.epoch and math.floor(os.epoch("utc")/1000)or 0,type=5,link="",ownerName=fs.getOwner and users.getShortName(fs.getOwner(C))or"",groupName="",deviceNumber=nil,data=nil}}if string.sub(R,-1)=="/"then R=string.sub(R,1,-1)end;if C and string.sub(C,1,1)=="/"then C=string.sub(C,2)end;if C and string.sub(C,-1)=="/"then C=string.sub(C,1,-1)end;local d=C and R.."/"..C or R;for v,w in pairs(fs.list(d))do if fs.isDir(fs.combine(d,w))then t[w]=r.pack(R,C and C.."/"..w or w)else t[w]=r.read(R,C and C.."/"..w or w)end;if p>0 then print(fs.combine(d,w).." => "..(C and C.."/"..w or w))end end;return t end;function r.save(s,C,D)if not D then s=r.serialize(s)end;local S=C==nil;local F;local J=0;if not S then F=fs.open(C,"wb")local T=F.write;F.write=function(g)for i in string.gmatch(g,".")do T(string.byte(i))end;J=J+string.len(g)end else F=""end;for v,w in pairs(s)do local U=""U=U..f(string.sub(w.name,-100),100,"\0")U=U..(w.mode and string.format("%07o\0",w.mode)or string.rep("\0",8))U=U..(w.owner and string.format("%07o\0",w.owner)or string.rep("\0",8))U=U..(w.group and string.format("%07o\0",w.group)or string.rep("\0",8))U=U..(w.data and string.format("%011o\0",string.len(w.data))or string.rep("0",11).."\0")U=U..(w.timestamp and string.format("%011o\0",w.timestamp)or string.rep("\0",12))U=U..w.type;U=U..(w.link and f(w.link,100,"\0")or string.rep("\0",100))U=U.."ustar \0"U=U..(w.ownerName and f(w.ownerName,32,"\0")or string.rep("\0",32))U=U..(w.groupName and f(w.groupName,32,"\0")or string.rep("\0",32))U=U..(w.deviceNumber and w.deviceNumber[1]and string.format("%07o\0",w.deviceNumber[1])or string.rep("\0",8))U=U..(w.deviceNumber and w.deviceNumber[2]and string.format("%07o\0",w.deviceNumber[2])or string.rep("\0",8))U=U..(string.len(w.name)>100 and f(string.sub(w.name,1,-101),155,"\0")or string.rep("\0",155))if string.len(U)<504 then U=U..string.rep("\0",504-string.len(U))end;local I=256;for i in string.gmatch(U,".")do I=I+string.byte(i)end;U=string.sub(U,1,148)..string.format("%06o\0 ",I)..string.sub(U,149)if S then F=F..U else F.write(U)end;if w.data~=nil and w.data~=""then if S then F=F..f(w.data,math.ceil(string.len(w.data)/512)*512,"\0")else F.write(f(w.data,math.ceil(string.len(w.data)/512)*512,"\0"))end end end;if S then F=F..string.rep("\0",1024)else F.write(string.rep("\0",1024))end;if S then F=F..string.rep("\0",10240-string.len(F)%10240)else F.write(string.rep("\0",10240-J%10240))end;if not S then F.close()end;os.queueEvent("nosleep")os.pullEvent()if S then return F end end;local function V(G,g,i)local t=""for m=1,string.len(g)do t=t..(bit.band(G,bit.blshift(1,string.len(g)-m))==0 and i or string.sub(g,m,m))end;return t end;local function W(X)local X=math.floor(X/86400)+719468;local Y=math.floor(X/146097)local Z=math.floor(X-Y*146097)local _=math.floor((Z-Z/1460+Z/36524-Z/146096)/365)local a0=math.floor(_+Y*400)local a1=Z-math.floor(365*_+_/4-_/100)local a2=math.floor((5*a1+2)/153)local a3=math.ceil(a1-(153*a2+2)/5+1)local a4=math.floor(a2+(a2<10 and 3 or-9))return a0+(a4<=2 and 1 or 0),a4,a3 end;local function a5(a6)local a7=math.floor(a6/3600%24)local a8=math.floor(a6/60%60)local a9=math.floor(a6%60)local aa,ab,ac=W(a6)return{year=aa,month=ab,day=ac,hours=a7,minutes=a8<10 and"0"..a8 or a8,seconds=a9<10 and"0"..a9 or a9}end;local ad="usage string removed"if pcall(require,"tar")then local ae={...}local af=nil;local ag={}local ah=nil;local ai=nil;local aj=true;local ak=false;local al=false;local am=nil;local an=false;local ao=false;local ap=nil;local aq=0;local ar=false;local as=false;for v,w in pairs(ae)do if ai then if ai==0 then af=w elseif ai==1 then am=w elseif ai==2 then ap=w elseif ai==3 then aq=tonumber(w)elseif ai==4 then local F=fs.open(shell.resolve(w),"r")local at=F.readLine()while at~=nil do if ar then table.insert(ag,at)else table.insert(ae,at)end;at=F.readLine()end;F.close()end;ai=nil elseif v==1 or string.sub(w,1,1)=="-"and string.sub(w,2,2)~="-"then if string.find(w,"A")then ah=0 end;if string.find(w,"d")then ah=2 end;if string.find(w,"c")then ah=1 end;if string.find(w,"r")then ah=3 end;if string.find(w,"t")then ah=4 end;if string.find(w,"u")then ah=5 end;if string.find(w,"x")then ah=6 end;if string.find(w,"f")then ai=0 end;if string.find(w,"k")then aj=false end;if string.find(w,"U")then ak=true end;if string.find(w,"W")then al=true end;if string.find(w,"O")then am=0 end;if string.find(w,"p")and kernel then an=true end;if string.find(w,"i")then q=true end;if string.find(w,"z")then ao=true end;if string.find(w,"C")then ai=1 end;if string.find(w,"K")then ai=2 end;if string.find(w,"N")then ai=3 end;if string.find(w,"T")then ai=4 end;if string.find(w,"v")then p=1 end;if string.find(w,"?")then print(ad)return 2 end elseif string.sub(w,1,2)=="--"then if w=="--catenate"then ah=0 elseif w=="--concatenate"then ah=0 elseif w=="--create"then ah=1 elseif w=="--diff"then ah=2 elseif w=="--compare"then ah=2 elseif w=="--delete"then ah=7 elseif w=="--append"then ah=3 elseif w=="--list"then ah=4 elseif w=="--update"then ah=5 elseif w=="--extract"then ah=6 elseif w=="--get"then ah=6 elseif w=="--help"or w=="--usage"then print(ad)return 2 elseif w=="--version"then print("CraftOS tar v1.0")return 2 elseif w=="--keep-old-files"then aj=false elseif w=="--overwrite"then aj=true elseif w=="--remove-files"then ak=true elseif w=="--unlink-first"then ak=true elseif w=="--verify"then al=true elseif w=="--to-stdout"then am=0 elseif w=="--preserve-permissions"and kernel then an=true elseif w=="--same-permissions"and kernel then an=true elseif w=="--preserve"and kernel then an=true elseif string.find(w,"--file=")then af=string.sub(w,8)elseif w=="--ignore-zeros"then q=true elseif w=="--gzip"or w=="--gunzip"or w=="--ungzip"then ao=true elseif string.find(w,"--add-file=")then table.insert(ag,string.sub(w,12))elseif string.find(w,"--directory=")then am=string.sub(w,13)elseif string.find(w,"--starting-file=")then ap=string.sub(w,17)elseif w=="--no-null"then ar=false elseif w=="--null"then ar=true elseif string.find(w,"--newer=")then aq=tonumber(string.sub(w,9))elseif string.find(w,"--after-date=")then aq=tonumber(string.sub(w,14))elseif string.find(w,"--files-from=")then local F=fs.open(shell.resolve(string.sub(w,14)),"r")local at=F.readLine()while at~=nil do if ar then table.insert(ag,at)else table.insert(ae,at)end;at=F.readLine()end;F.close()elseif w=="--verbose"then p=1 elseif w=="--no-recursion"then as=true end else table.insert(ag,w)end end;if ao and LibDeflate==nil then LibDeflate=require"LibDeflate"if LibDeflate==nil then error("Compression is only supported when LibDeflate.lua is available in the PATH.")end end;local au=shell.dir()if type(am)=="string"then shell.setDir(shell.resolve(am))end;local function av(g)shell.setDir(au)error(g)end;local function aw(D)if ao then local E=""local F=fs.open(shell.resolve(af),"rb")local i=F.read()while i~=nil do E=E..string.char(i)i=F.read()if string.len(E)%10240==0 then os.queueEvent("nosleep")os.pullEvent()end end;F.close()return r.load(LibDeflate:DecompressGzip(E),D,true)else return r.load(shell.resolve(af),D)end end;local function ax(s)if not ao and af then r.save(s,shell.resolve(af))else local t=r.save(s,nil)if ao then t=LibDeflate:CompressGzip(t)end;if am==0 then write(t)elseif t then local F=fs.open(shell.resolve(af),"wb")for i in string.gmatch(t,".")do F.write(string.byte(i))end;F.close()end end end;if ah==0 then if ao==true then av("Compressed files cannot be concatenated")end;if af==nil then av("You must specify an arhive with -f <first.tar>.")end;local ay=fs.open(shell.resolve(af),"ab")for v,w in pairs(ag)do local az=fs.open(shell.resolve(w),"rb")local i=az.read()while i do ay.write(i)i=az.read()end;az.close()end;ay.close()elseif ah==1 then if af==nil and am~=0 then av("You must specify an archive with -f <output.tar> or -O.")end;local s={}for v,w in pairs(ag)do local x=n(w,"/")local a3=s;local C=nil;for v,w in pairs(x)do if v==#x then break end;C=C and fs.combine(C,w)or w;if a3[w]==nil then a3[w]={}end;a3=a3[w]end;if string.sub(w,1,1)=="/"then a3[x[#x]]=as and r.read or r.pack("/",string.sub(w,2))else a3[x[#x]]=as and r.read or r.pack(shell.dir(),w)end;if ak then fs.delete(shell.resolve(w))end end;ax(s)elseif ah==2 then av("Not implemented")elseif ah==3 then if af==nil and am~=0 then av("You must specify an archive with -f <output.tar> or -O.")end;local s=aw(true)for v,w in pairs(ag)do if string.sub(w,1,1)=="/"then table.insert(s,as and r.read or r.pack("/",string.sub(w,2)))else table.insert(s,as and r.read or r.pack(shell.dir(),w))end;if ak then fs.delete(shell.resolve(w))end end;ax(s)elseif ah==4 then if af==nil then av("You must specify an archive with -f <file.tar>.")end;local s=aw(true)if p>0 then local aA={}local aB={0,0,0,0,0}for v,w in pairs(s)do local aC=a5(w.timestamp or 0)local a3=string.format("%04d-%02d-%02d %02d:%02d",aC.year,aC.month,aC.day,aC.hours,aC.minutes)local d={V(w.mode+(w.type==5 and 0x200 or 0),"drwxrwxrwx","-"),(w.ownerName or w.owner or 0).."/"..(w.groupName or w.group or 0),string.len(w.data or""),a3,w.name..(w.link and w.link~=""and" -> "..w.link or"")}for A,B in pairs(d)do if string.len(B)+1>aB[A]then aB[A]=string.len(B)+1 end end;table.insert(aA,d)end;for v,w in pairs(aA)do for A,B in pairs(w)do write(A==3 and j or f(B,aB[A])..(A==3 and" "or""))end;print("")end else for v,w in pairs(s)do print(w.name)end end elseif ah==5 then if af==nil and am~=0 then av("You must specify an archive with -f <output.tar> or -O.")end;local s=aw()for v,w in pairs(ag)do local x=n(w,"/")local a3=s;local C=nil;for v,w in pairs(x)do if v==#x then break end;C=C and fs.combine(C,w)or w;if a3[w]==nil then a3[w]={["//"]={name=C,mode=fs.getPermissions and e(fs.getPermissions(C,fs.getOwner(C)or 0))*0x40+e(fs.getPermissions(C,"*"))+bit.band(fs.getPermissions(C,"*"),0x10)*0x80 or 0x1FF,owner=fs.getOwner and fs.getOwner(C)or 0,group=0,timestamp=os.epoch and math.floor(os.epoch("utc")/1000)or 0,type=5,link="",ownerName=fs.getOwner and users.getShortName(fs.getOwner(C))or"",groupName="",deviceNumber=nil,data=nil}}end;a3=a3[w]end;if string.sub(w,1,1)=="/"then a3[x[#x]]=as and r.read or r.pack("/",string.sub(w,2))else a3[x[#x]]=as and r.read or r.pack(shell.dir(),w)end;if ak then fs.delete(shell.resolve(w))end end;ax(s)elseif ah==6 then if af==nil then av("You must specify an archive with -f <file.tar>.")end;local s=aw()r.extract(s,shell.dir())elseif ah==7 then if af==nil then av("You must specify an archive with -f <file.tar>.")end;local s=aw(true)for v,w in pairs(ag)do for A,B in pairs(s)do if B.name==w then s[A]=nil;break end end end;ax(s)else av("You must specify one of -Acdrtux, see --help for details.")end;shell.setDir(au)end;return r end)()
local function ynprompt(prompt, default, promptColor)
while true do
local choices
if default == true then
choices = "(Y/n)"
elseif default == false then
choices = "(y/N)"
else
choices = "(y/n)"
end
if promptColor then
local oldColor = term.getTextColor()
term.setTextColor(promptColor)
io.write(prompt..choices)
term.setTextColor(oldColor)
else
io.write(prompt..choices)
end
local choice = read():lower()
if choice == "" then
if default ~= nil then
return default
end
elseif choice == "y" then
return true
elseif choice == "n" then
return false
end
print("Invalid choice!")
end
end
function tarExtractSafe(data, path)
fs.makeDir(path)
for k, v in pairs(data) do
if k ~= "//" then
local p = fs.combine(path, k)
if v["//"] ~= nil then
local l = tarExtractSafe(v, p)
print((v["//"].name or "?") .. " => " .. (p or "?"))
elseif v.type == 0 or v.type == 7 then
local continue = true
if fs.exists(p) then
continue = ynprompt(string.format("overwrite '%s'? ", p), false, colors.orange)
end
if continue then
local file = fs.open(p, "wb")
if v.data ~= nil then
file.write(v.data)
end
file.close()
print((v.name or "?") .. " => " .. (p or "?"))
end
elseif v.type ~= nil then
print("Unimplemented type " .. v.type)
end
os.queueEvent("nosleep")
os.pullEvent()
end
end
end
local h = assert(http.get({
url = "https://github.com/scmcgowen/Radon/releases/latest/download/release.tar",
binary = true,
}), "Failed to get latest release")
assert(h.getResponseCode() == 200, "Bad response code: " .. h.getResponseCode())
local data = h.readAll()
h.close()
local loaded = tar.load(data, false, true)
tarExtractSafe(loaded, "/")
print("Done!")