SHARE
TWEET

BBPack (ComputerCraft)

BombBloke Jan 29th, 2015 (edited) 5,030 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. -- +---------------------+------------+---------------------+
  2. -- |                     |            |                     |
  3. -- |                     |   BBPack   |                     |
  4. -- |                     |            |                     |
  5. -- +---------------------+------------+---------------------+
  6.  
  7. local version = "Version 1.6.1"
  8.  
  9. -- Pastebin uploader/downloader for ComputerCraft, by Jeffrey Alexander (aka Bomb Bloke).
  10. -- Handles multiple files in a single paste, as well as non-ASCII symbols within files.
  11. -- Used to be called "package".
  12. -- http://www.computercraft.info/forums2/index.php?/topic/21801-
  13. -- pastebin get cUYTGbpb bbpack
  14.  
  15. ---------------------------------------------
  16. ------------Variable Declarations------------
  17. ---------------------------------------------
  18.  
  19. local band, brshift, blshift = bit.band, bit.brshift, bit.blshift
  20.  
  21. local b64 = {}
  22. for i = 1, 64 do
  23.     b64[i - 1] = ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"):byte(i)
  24.     b64[("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"):sub(i, i)] = i - 1
  25. end
  26.  
  27. ---------------------------------------------
  28. ------------Function Declarations------------
  29. ---------------------------------------------
  30.  
  31. local unpack = unpack or table.unpack
  32.  
  33. local function snooze()
  34.     local myEvent = tostring({})
  35.     os.queueEvent(myEvent)
  36.     os.pullEvent(myEvent)
  37. end
  38.  
  39. local function toBase64Internal(inputlist)
  40.     if type(inputlist) ~= "table" then error("bbpack.toBase64: Expected: table or file handle", 2) end
  41.  
  42.     if inputlist.read then
  43.         local templist, len = {}, 1
  44.  
  45.         for byte in inputlist.read do
  46.             templist[len] = byte
  47.             len = len + 1
  48.         end
  49.  
  50.         inputlist.close()
  51.         inputlist = templist
  52.     elseif inputlist.readLine then
  53.         inputlist.close()
  54.         error("bbpack.toBase64: Use a binary-mode file handle", 2)
  55.     end
  56.  
  57.     if #inputlist == 0 then return "" end
  58.  
  59.     local curbit, curbyte, outputlist, len = 32, 0, {}, 1
  60.  
  61.     for i = 1, #inputlist do
  62.         local inByte, mask = inputlist[i], 128
  63.  
  64.         for j = 1, 8 do
  65.             if band(inByte, mask) == mask then curbyte = curbyte + curbit end
  66.             curbit, mask = curbit / 2, mask / 2
  67.  
  68.             if curbit < 1 then
  69.                 outputlist[len] = b64[curbyte]
  70.                 curbit, curbyte, len = 32, 0, len + 1
  71.             end
  72.         end
  73.     end
  74.  
  75.     if curbit > 1 then outputlist[len] = b64[curbyte] end
  76.  
  77.     return string.char(unpack(outputlist))
  78. end
  79.  
  80. local function fromBase64Internal(inData)
  81.     if type(inData) ~= "string" and type(inData) ~= "table" then error("bbpack.fromBase64: Expected: string or file handle", 2) end
  82.  
  83.     if type(inData) == "table" then
  84.         if inData.readLine then
  85.             local temp = inData.readAll()
  86.             inData.close()
  87.             inData = temp
  88.         else
  89.             if inData.close then inData.close() end
  90.             error("bbpack.fromBase64: Use text-mode file handles", 2)
  91.         end
  92.     end
  93.  
  94.     if #inData == 0 then return {} end
  95.  
  96.     local curbyte, curbit, outputlist, len = 0, 128, {}, 1
  97.  
  98.     for i = 1, #inData do
  99.         local mask, curchar = 32, b64[inData:sub(i, i)]
  100.  
  101.         for j = 1, 6 do
  102.             if band(curchar, mask) == mask then curbyte = curbyte + curbit end
  103.             curbit, mask = curbit / 2, mask / 2
  104.  
  105.             if curbit < 1 then
  106.                 outputlist[len] = curbyte
  107.                 curbit, curbyte, len = 128, 0, len + 1
  108.             end
  109.         end
  110.     end
  111.  
  112.     if curbit > 1 and curbyte > 0 then outputlist[len] = curbyte end
  113.  
  114.     return outputlist
  115. end
  116.  
  117. local function compressIterator(ClearCode)
  118.     local startCodeSize = 1
  119.     while math.pow(2, startCodeSize) < ClearCode do startCodeSize = startCodeSize + 1 end
  120.  
  121.     local EOI, ClearCode = math.pow(2, startCodeSize) + 1, math.pow(2, startCodeSize)
  122.     startCodeSize = startCodeSize + 1
  123.  
  124.     local curstring, len, curbit, curbyte, outputlist, codes, CodeSize, MaxCode, nextcode, curcode = "", 2, 1, 0, {0}, {}, startCodeSize, math.pow(2, startCodeSize) - 1, EOI + 1
  125.  
  126.     local function packByte(num)
  127.         local mask = 1
  128.  
  129.         for i = 1, CodeSize do
  130.             if band(num, mask) == mask then curbyte = curbyte + curbit end
  131.             curbit, mask = curbit * 2, mask * 2
  132.  
  133.             if curbit > 128 or (i == CodeSize and num == EOI) then
  134.                 local counter = blshift(brshift(#outputlist - 1, 8), 8) + 1
  135.                 outputlist[counter] = outputlist[counter] + 1
  136.  
  137.                 if outputlist[counter] > 255 then
  138.                     outputlist[counter], outputlist[counter + 256], len = 255, 1, len + 1
  139.                     snooze()
  140.                 end
  141.  
  142.                 outputlist[len] = curbyte
  143.                 curbit, curbyte, len = 1, 0, len + 1
  144.             end
  145.         end
  146.     end
  147.  
  148.     packByte(ClearCode)
  149.  
  150.     return function(incode)
  151.         if not incode then
  152.             if curcode then packByte(curcode) end
  153.             packByte(EOI)
  154.             outputlist[#outputlist + 1] = 0
  155.             return outputlist
  156.         end
  157.  
  158.         if not curcode then
  159.             curcode = incode
  160.             return
  161.         end
  162.  
  163.         curstring = curstring .. string.char(incode)
  164.         local thisCode = codes[curstring]
  165.  
  166.         if thisCode then
  167.             curcode = thisCode
  168.         else
  169.             codes[curstring] = nextcode
  170.             nextcode = nextcode + 1
  171.  
  172.             packByte(curcode)
  173.  
  174.             if nextcode == MaxCode + 2 then
  175.                 CodeSize = CodeSize + 1
  176.                 MaxCode = math.pow(2, CodeSize) - 1
  177.             end
  178.  
  179.             if nextcode == 4095 then
  180.                 packByte(ClearCode)
  181.                 CodeSize, MaxCode, nextcode, codes = startCodeSize, math.pow(2, startCodeSize) - 1, EOI + 1, {}
  182.             end
  183.  
  184.             curcode, curstring = incode, string.char(incode)
  185.         end
  186.     end
  187. end
  188.  
  189. local function compressInternal(inputlist, valRange)
  190.     if type(inputlist) ~= "table" and type(inputlist) ~= "string" then error("bbpack.compress: Expected: table, string or file handle", 2) end
  191.  
  192.     if not valRange then valRange = 256 end
  193.     if type(valRange) ~= "number" or valRange < 2 or valRange > 256 then error("bbpack.compress: Value range must be a number between 2 - 256.", 2) end
  194.  
  195.     if type(inputlist) == "table" and inputlist.close then
  196.         local templist
  197.         if inputlist.readAll then
  198.             templist = inputlist.readAll()
  199.         else
  200.             local len = 1
  201.             templist = {}
  202.             for thisByte in inputlist.read do
  203.                 templist[len] = thisByte
  204.                 len = len + 1
  205.             end
  206.         end
  207.         inputlist.close()
  208.         inputlist = templist
  209.     end
  210.  
  211.     if type(inputlist) == "string" then inputlist = {inputlist:byte(1, #inputlist)} end
  212.  
  213.     if #inputlist == 0 then return {} end
  214.  
  215.     local compressIt = compressIterator(valRange)
  216.  
  217.     local sleepCounter = 0
  218.     for i = 1, #inputlist do
  219.         compressIt(inputlist[i])
  220.  
  221.         sleepCounter = sleepCounter + 1
  222.         if sleepCounter > 1023 then
  223.             sleepCounter = 0
  224.             snooze()
  225.         end
  226.     end
  227.  
  228.     return compressIt(false)
  229. end
  230.  
  231. local function decompressIterator(ClearCode, codelist)
  232.     local startCodeSize = 1
  233.     while math.pow(2, startCodeSize) < ClearCode do startCodeSize = startCodeSize + 1 end
  234.  
  235.     local EOI, ClearCode = math.pow(2, startCodeSize) + 1, math.pow(2, startCodeSize)
  236.     startCodeSize = startCodeSize + 1
  237.  
  238.     local lastcounter, curbyte, spot, CodeSize, MaxCode, maskbit, nextcode, codes, gotbytes = codelist[1], codelist[2], 3, startCodeSize, math.pow(2, startCodeSize) - 1, 1, EOI + 1, {}, 1
  239.     for i = 0, ClearCode - 1 do codes[i] = string.char(i) end
  240.  
  241.     return function()
  242.         while true do
  243.             local curcode, curbit = 0, 1
  244.  
  245.             for i = 1, CodeSize do
  246.                 if band(curbyte, maskbit) == maskbit then curcode = curcode + curbit end
  247.                 curbit, maskbit = curbit * 2, maskbit * 2
  248.  
  249.                 if maskbit > 128 and not (i == CodeSize and curcode == EOI) then
  250.                     maskbit, curbyte, gotbytes = 1, codelist[spot], gotbytes + 1
  251.                     spot = spot + 1
  252.  
  253.                     if gotbytes > lastcounter then
  254.                         if curbyte == 0 then break end
  255.                         lastcounter, gotbytes = curbyte, 1
  256.                         curbyte = codelist[spot]
  257.                         spot = spot + 1
  258.                         snooze()
  259.                     end
  260.                 end
  261.             end
  262.  
  263.             if curcode == ClearCode then
  264.                 CodeSize, MaxCode, nextcode, codes = startCodeSize, math.pow(2, startCodeSize) - 1, EOI + 1, {}
  265.                 for i = 0, ClearCode - 1 do codes[i] = string.char(i) end
  266.             elseif curcode ~= EOI then
  267.                 if codes[nextcode - 1] then codes[nextcode - 1] = codes[nextcode - 1] .. codes[curcode]:sub(1, 1) else codes[nextcode - 1] = codes[curcode]:sub(1, 1) end
  268.  
  269.                 if nextcode < 4096 then
  270.                     codes[nextcode] = codes[curcode]
  271.                     nextcode = nextcode + 1
  272.                 end
  273.  
  274.                 if nextcode - 2 == MaxCode then
  275.                     CodeSize = CodeSize + 1
  276.                     MaxCode = math.pow(2, CodeSize) - 1
  277.                 end
  278.  
  279.                 return codes[curcode]
  280.             else return end
  281.         end
  282.     end
  283. end
  284.  
  285. local function decompressInternal(codelist, outputText, valRange)
  286.     if type(codelist) ~= "table" then error("bbpack.decompress: Expected: table or file handle", 2) end
  287.  
  288.     if not valRange then valRange = 256 end
  289.     if type(valRange) ~= "number" or valRange < 2 or valRange > 256 then error("bbpack.decompress: Value range must be a number between 2 - 256.", 2) end
  290.  
  291.     if codelist.readLine then
  292.         codelist.close()
  293.         error("bbpack.decompress: Use binary-mode file handles", 2)
  294.     elseif codelist.readAll then
  295.         codelist = codelist.readAll()
  296.         codelist = {codelist:byte(1, #codelist)}
  297.     elseif codelist.read then
  298.         local data, len = {}, 1
  299.         while true do
  300.             local amount = codelist.read()
  301.             data[len] = amount
  302.             len = len + 1
  303.  
  304.             if amount == 0 then break end
  305.  
  306.             for i = 1, amount do
  307.                 data[len] = codelist.read()
  308.                 len = len + 1
  309.             end
  310.  
  311.             snooze()
  312.         end
  313.         codelist = data
  314.     elseif #codelist == 0 then return outputText and "" or {} end
  315.  
  316.     local outputlist, decompressIt, len = {}, decompressIterator(valRange, codelist), 1
  317.  
  318.     local sleepCounter = 0
  319.     while true do
  320.         local output = decompressIt()
  321.  
  322.         if output then
  323.             outputlist[len] = output
  324.             len = len + 1
  325.         else break end
  326.     end
  327.  
  328.     outputlist = table.concat(outputlist)
  329.  
  330.     return outputText and outputlist or {outputlist:byte(1, #outputlist)}
  331. end
  332.  
  333. local function uploadPasteInternal(name, content)
  334.     if type(name) ~= "string" or (type(content) ~= "string" and type(content) ~= "table") then error("bbpack.uploadPaste: Expected: (string) paste name, (string or file handle) paste content", 2) end
  335.  
  336.     if type(content) == "table" then
  337.         if content.readLine then
  338.             local temp = content.readAll()
  339.             content.close()
  340.             content = temp
  341.         else
  342.             if content.close then content.close() end
  343.             error("bbpack.uploadPaste: Use text-mode file handles", 2)
  344.         end
  345.     end
  346.  
  347.     local webHandle = http.post(
  348.         "https://pastebin.com/api/api_post.php",
  349.         "api_option=paste&" ..
  350.         "api_dev_key=147764e5c6ac900a3015d77811334df1&" ..
  351.         "api_paste_format=lua&" ..
  352.         "api_paste_name=" .. textutils.urlEncode(name) .. "&" ..
  353.         "api_paste_code=" .. textutils.urlEncode(content))
  354.  
  355.     if webHandle then
  356.         local response = webHandle.readAll()
  357.         webHandle.close()
  358.         return string.match(response, "[^/]+$")
  359.     else error("Connection to pastebin failed. http API config in ComputerCraft.cfg is enabled, but may be set to block pastebin - or pastebin servers may be busy.") end
  360. end
  361.  
  362. local function downloadPasteInternal(pasteID)
  363.     if type(pasteID) ~= "string" then error("bbpack.downloadPaste: Expected: (string) paste ID", 2) end
  364.  
  365.     local webHandle = http.get("https://pastebin.com/raw/" .. textutils.urlEncode(pasteID))
  366.  
  367.     if webHandle then
  368.         local incoming = webHandle.readAll()
  369.         webHandle.close()
  370.         return incoming
  371.     else error("Connection to pastebin failed. http API config in ComputerCraft.cfg is enabled, but may be set to block pastebin - or pastebin servers may be busy.") end
  372. end
  373.  
  374. if shell then
  375.     ---------------------------------------------
  376.     ------------     Main Program    ------------
  377.     ---------------------------------------------
  378.  
  379.     if not bbpack then os.loadAPI("bbpack") end
  380.  
  381.     local args = {...}
  382.     if #args > 0 then args[1] = args[1]:lower() end
  383.  
  384.     if #args < 1 or not (args[1] == "put" or args[1] == "get" or args[1] == "fileput" or args[1] == "fileget" or args[1] == "mount" or args[1] == "compress" or args[1] == "decompress" or args[1] == "cluster" or args[1] == "update") then
  385.         textutils.pagedPrint("Usage:\n")
  386.  
  387.         textutils.pagedPrint("Uploads specified file or directory:")
  388.         textutils.pagedPrint("bbpack put [file/directory name]\n")
  389.  
  390.         textutils.pagedPrint("Dumps paste into specified file or directory:")
  391.         textutils.pagedPrint("bbpack get <pasteID> [file/directory name]\n")
  392.  
  393.         textutils.pagedPrint("Writes specified file or directory to archive file:")
  394.         textutils.pagedPrint("bbpack fileput [file/directory name] <target file>\n")
  395.  
  396.         textutils.pagedPrint("Unpacks archive file to specified file or directory:")
  397.         textutils.pagedPrint("bbpack fileget <source file> [file/directory name]\n")
  398.  
  399.         print("For the above options, if [file/directory name] is omitted the root of the drive will be used instead.\n")
  400.  
  401.         textutils.pagedPrint("Enables automatic compression on hdd and compresses all existing files:")
  402.         textutils.pagedPrint("bbpack compress\n")
  403.  
  404.         textutils.pagedPrint("Disables automatic compression on hdd and decompresses all existing files:")
  405.         textutils.pagedPrint("bbpack decompress\n")
  406.  
  407.         textutils.pagedPrint("Mounts a given URL as the specified local file:")
  408.         textutils.pagedPrint("bbpack mount <URL> <fileName>\n")
  409.  
  410.         textutils.pagedPrint("Turns the system into a server for the specified cluster:")
  411.         textutils.pagedPrint("bbpack cluster <clusterName>\n")
  412.  
  413.         textutils.pagedPrint("Mounts the specified cluster as a drive:")
  414.         textutils.pagedPrint("bbpack mount <clusterName>\n")
  415.  
  416.         textutils.pagedPrint("Updates package, reboots, and instructs all mounted cluster servers to do the same:")
  417.         textutils.pagedPrint("bbpack update\n")
  418.  
  419.         return
  420.     end
  421.  
  422.     if (args[1] == "put" or args[1] == "get") and not http then
  423.         print("BBPack's pastebin functionality requires that the http API be enabled.")
  424.         print("Shut down MineCraft game/server, set http_enable to true in ComputerCraft.cfg, then restart.")
  425.         error()
  426.     end
  427.  
  428.     ---------------------------------------------
  429.     ------------      Uploading      ------------
  430.     ---------------------------------------------
  431.  
  432.     if args[1] == "put" or args[1] == "fileput" then
  433.         local toFile
  434.         if args[1] == "fileput" then toFile = table.remove(args, #args) end
  435.  
  436.         local uploadName, parent
  437.  
  438.         if not args[2] then
  439.             print("Full system upload - are you sure? (y/n)")
  440.             if read():sub(1, 1):lower() ~= "y" then
  441.                 print("Aborted.")
  442.                 error()
  443.             end
  444.  
  445.             uploadName = os.getComputerLabel()
  446.             if not uploadName then
  447.                 print("Enter paste title:")
  448.                 uploadName = read()
  449.             end
  450.  
  451.             args[2] = ""
  452.         end
  453.  
  454.         local target, output = shell.resolve(args[2]), {}
  455.         uploadName = uploadName or target
  456.  
  457.         if not fs.exists(target) then
  458.             print("Invalid target.")
  459.             error()
  460.         end
  461.  
  462.         if fs.isDir(target) then
  463.             local fileList = fs.list(target)
  464.             parent = target
  465.             while #fileList > 0 do
  466.                 if fs.isDir(shell.resolve(fs.combine(parent, fileList[#fileList]))) then
  467.                     local thisDir = table.remove(fileList, #fileList)
  468.                     local newList = fs.list(shell.resolve(fs.combine(parent, thisDir)))
  469.                     for i = 1, #newList do fileList[#fileList + 1] = fs.combine(thisDir, newList[i]) end
  470.                     if #newList == 0 then output[#output + 1] = thisDir end
  471.                 else output[#output + 1] = table.remove(fileList, #fileList) end
  472.             end
  473.             target = output
  474.             output = {}
  475.         else parent, target = "", {target} end
  476.  
  477.         snooze()
  478.  
  479.         for i = #target, 1, -1 do
  480.             if fs.combine(parent, target[i]) ~= shell.getRunningProgram() and not (parent == "" and fs.getDrive(target[i]) ~= "hdd") then
  481.                 if fs.isDir(fs.combine(parent, target[i])) then
  482.                     print(target[i])
  483.                     output[#output + 1] = {target[i], true}
  484.                 else
  485.                     print(target[i])
  486.                     output[#output + 1] = {target[i], toBase64Internal(compressInternal(fs.open(fs.combine(parent, target[i]), "rb")))}
  487.                     snooze()
  488.                 end
  489.             end
  490.         end
  491.  
  492.         if toFile then
  493.             output = textutils.serialize(output)
  494.             if fs.getFreeSpace(shell.dir()) < #output then error("Output "..#output.." bytes, disk space available "..fs.getFreeSpace(shell.dir()).." bytes: file not written.") end
  495.  
  496.             write("Writing to file \"" .. toFile .. "\"... ")
  497.  
  498.             toFile = fs.open(shell.resolve(toFile), "w")
  499.             toFile.write(output)
  500.             toFile.close()
  501.             print("Success.")
  502.         else
  503.             write("Connecting to pastebin.com... ")
  504.             local response = uploadPasteInternal(uploadName, textutils.serialize(output))
  505.             print("Success.")
  506.  
  507.             print("Uploaded to paste ID: " .. response)
  508.             print("Run \"bbpack get " .. response .. (parent == "" and "" or " " .. parent) .. "\" to download.")
  509.         end
  510.  
  511.     ---------------------------------------------
  512.     ------------     Downloading     ------------
  513.     ---------------------------------------------
  514.  
  515.     elseif args[1] == "get" or args[1] == "fileget" then
  516.         local incoming
  517.         if args[1] == "fileget" then
  518.             write("Attempting to read from archive... ")
  519.             if not fs.exists(shell.resolve(args[2])) then error("Can't find \"" .. shell.resolve(args[2]) .. "\".") end
  520.             local inFile = fs.open(shell.resolve(args[2]), "r")
  521.             incoming = textutils.unserialize(inFile.readAll())
  522.             inFile.close()
  523.             print("Success.")
  524.         else
  525.             write("Connecting to pastebin.com... ")
  526.             incoming = textutils.unserialize(downloadPasteInternal(args[2]))
  527.             print("Downloaded.")
  528.         end
  529.  
  530.         local function getParent(path)
  531.             local pos = #path
  532.             if path:sub(pos,pos) == "/" then pos = pos - 1 end
  533.             while pos > 0 and path:sub(pos,pos) ~= "/" do pos = pos - 1 end
  534.             return pos > 0 and path:sub(1, pos - 1) or ""
  535.         end
  536.  
  537.         local function writeFile(filePath, fileContent)
  538.             local path = fs.combine(shell.resolve("."), filePath)
  539.             if not fs.exists(getParent(path)) then fs.makeDir(getParent(path)) end
  540.             if fs.getFreeSpace(shell.dir()) < #fileContent then error(path.." "..#fileContent.." bytes, disk space available "..fs.getFreeSpace(shell.dir()).." bytes: file not written.") end
  541.  
  542.             snooze()
  543.  
  544.             local myFile = fs.open(path, "wb")
  545.             for i = 1, #fileContent do myFile.write(fileContent[i]) end
  546.             myFile.close()
  547.  
  548.             snooze()
  549.         end
  550.  
  551.         args[3] = args[3] or ""
  552.         if args[3] ~= "" and #incoming == 1 then
  553.             print(incoming[1][1] .. " => "..args[3])
  554.             writeFile(args[3], decompressInternal(fromBase64Internal(incoming[1][2])))
  555.         else
  556.             for i = 1, #incoming do
  557.                 print(incoming[i][1])
  558.                 if type(incoming[i][2]) == "string" then
  559.                     writeFile(fs.combine(args[3], incoming[i][1]), decompressInternal(fromBase64Internal(incoming[i][2])))
  560.                 else fs.makeDir(fs.combine(shell.resolve("."), fs.combine(args[3], incoming[i][1]))) end
  561.                 incoming[i] = nil
  562.             end
  563.         end
  564.  
  565.     ---------------------------------------------
  566.     ------------     Compress FS     ------------
  567.     ---------------------------------------------
  568.  
  569.     elseif args[1] == "compress" then
  570.         bbpack.fileSys(true)
  571.         print("Filesystem compression enabled.")
  572.  
  573.     ---------------------------------------------
  574.     ------------    Decompress FS    ------------
  575.     ---------------------------------------------
  576.  
  577.     elseif args[1] == "decompress" then
  578.         print(bbpack.fileSys(false) and "Filesystem compression disabled." or "Filesystem compression disabled, but space is insufficient to decompress all files.")
  579.  
  580.     ---------------------------------------------
  581.     ------------        Mount        ------------
  582.     ---------------------------------------------
  583.  
  584.     elseif args[1] == "mount" then
  585.         bbpack.fileSys(args[2], args[3] and shell.resolve(args[3]))
  586.         print("Successfully mounted.")
  587.  
  588.     ---------------------------------------------
  589.     ------------       Cluster       ------------
  590.     ---------------------------------------------
  591.  
  592.     elseif args[1] == "cluster" then
  593.         local cluster, protocol = args[2], rednet.host and args[2]
  594.  
  595.         rfs.makeDir(cluster)
  596.  
  597.         for _, side in pairs(rs.getSides()) do if peripheral.getType(side) == "modem" then rednet.open(side) end end
  598.  
  599.         print("Running as part of cluster \"" .. cluster .. "\"...")
  600.  
  601.         local function locFile(path)
  602.             local matches = rfs.find(path .. "*")
  603.  
  604.             for i = 1, #matches do
  605.                 local thisMatch = matches[i]
  606.                 if #thisMatch == #path + 3 and thisMatch:sub(1, #path) == path then return thisMatch end
  607.             end
  608.  
  609.             return nil
  610.         end
  611.        
  612.         return (function() while true do
  613.             local sender, msg = rednet.receive(protocol)
  614.  
  615.             if type(msg) == "table" and msg.cluster == cluster then
  616.                 local command, par1, par2 = unpack(msg)
  617.  
  618.                 if command == "rollcall" then
  619.                     rednet.send(sender, {["cluster"] = cluster, ["uuid"] = msg.uuid, "rollcallResponse"}, protocol)
  620.                 elseif command == "isDir" then
  621.                     rednet.send(sender, {["cluster"] = cluster, ["uuid"] = msg.uuid, rfs.isDir(par1)}, protocol)
  622.                 elseif command == "makeDir" then
  623.                     rfs.makeDir(par1)
  624.                 elseif command == "exists" then
  625.                     rednet.send(sender, {["cluster"] = cluster, ["uuid"] = msg.uuid, type(locFile(par1)) == "string"}, protocol)
  626.                 elseif command == "getFreeSpace" then
  627.                     rednet.send(sender, {["cluster"] = cluster, ["uuid"] = msg.uuid, {rfs.getFreeSpace("") - 10000, os.getComputerID()}}, protocol)
  628.                 elseif command == "getSize" then
  629.                     local path = locFile(par1)
  630.                     rednet.send(sender, {["cluster"] = cluster, ["uuid"] = msg.uuid, path and rfs.getSize(path) or 0}, protocol)
  631.                 elseif command == "delete" then
  632.                     local path = locFile(par1)
  633.                     if path then rfs.delete(path) end
  634.                 elseif command == "list" then
  635.                     local list = rfs.list(par1)
  636.  
  637.                     for i = 1, #list do
  638.                         local entry = list[i]
  639.                         if not fs.isDir(fs.combine(par1, entry)) then list[i] = entry:sub(1, -4) end
  640.                     end
  641.  
  642.                     rednet.send(sender, {["cluster"] = cluster, ["uuid"] = msg.uuid, list}, protocol)
  643.                 elseif command == "get" then
  644.                     local path = locFile(par1)
  645.  
  646.                     if path then
  647.                         local file, content = rfs.open(path, "rb")
  648.  
  649.                         if file.readAll then
  650.                             content = file.readAll()
  651.                         else
  652.                             content = {}
  653.                             local counter = 1
  654.                             for byte in file.read do
  655.                                 content[counter] = byte
  656.                                 counter = counter + 1
  657.                             end
  658.                             content = string.char(unpack(content))
  659.                         end
  660.  
  661.                         file.close()
  662.  
  663.                         rednet.send(sender, {["cluster"] = cluster, ["uuid"] = msg.uuid, {tonumber(path:sub(-3)), content}}, protocol)
  664.                     end
  665.                    
  666.                     rednet.send(sender, {["cluster"] = cluster, ["uuid"] = msg.uuid, false}, protocol)
  667.                 elseif command == "put" then
  668.                     local file = rfs.open(par1, "wb")
  669.  
  670.                     if term.setPaletteColour then
  671.                         file.write(par2)
  672.                     else
  673.                         par2 = {par2:byte(1, #par2)}
  674.                         for i = 1, #par2 do file.write(par2[i]) end
  675.                     end
  676.  
  677.                     file.close()
  678.                 elseif command == "update" then
  679.                     local file = rfs.open("bbpack", "w")
  680.                     file.write(downloadPasteInternal("cUYTGbpb"))
  681.                     file.close()
  682.  
  683.                     os.reboot()
  684.                 end
  685.             end
  686.         end end)()
  687.  
  688.     ---------------------------------------------
  689.     ------------        Update       ------------
  690.     ---------------------------------------------
  691.  
  692.     elseif args[1] == "update" then
  693.         bbpack.update()
  694.     end
  695. else
  696.     ---------------------------------------------
  697.     ------------     Load As API     ------------
  698.     ---------------------------------------------
  699.  
  700.     compress =      compressInternal
  701.     decompress =    decompressInternal
  702.  
  703.     toBase64 =      toBase64Internal
  704.     fromBase64 =    fromBase64Internal
  705.  
  706.     uploadPaste =   uploadPasteInternal
  707.     downloadPaste = downloadPasteInternal
  708.  
  709.     function open(file, mode, valRange)
  710.         if (type(file) ~= "table" and type(file) ~= "string") or type(mode) ~= "string" then error("bbpack.open: Expected: file (string or handle), mode (string). Got: " .. type(file) .. ", " .. type(mode) .. ".", 2) end
  711.  
  712.         mode = mode:lower()
  713.         local binary, append, read, write, newhandle = mode:find("b") ~= nil, mode:find("a") ~= nil, mode:find("r") ~= nil, mode:find("w") ~= nil, {}
  714.  
  715.         if not valRange then valRange = 256 end
  716.         if type(valRange) ~= "number" or valRange < 2 or valRange > 256 then error("bbpack.decompress: Value range must be a number between 2 - 256.", 2) end
  717.  
  718.         if not (append or write or read) then error("bbpack.open: Invalid file mode: " .. mode, 2) end
  719.  
  720.         if type(file) == "string" then
  721.             if append and rfs.exists(file) then
  722.                 local oldfile = open(file, binary and "rb" or "r", valRange)
  723.                 if not oldfile then return nil end
  724.                 local olddata = oldfile.readAll()
  725.                 oldfile.close()
  726.  
  727.                 newhandle = open(file, binary and "wb" or "w", valRange)
  728.                 newhandle.write(olddata)
  729.                 return newhandle
  730.             end
  731.  
  732.             file = rfs.open(file, (read and "r" or "w") .. "b")
  733.             if not file then return nil end
  734.         else
  735.             if (write and (file.writeLine or not file.write)) or (read and not file.read) then error("bbpack.open: Handle / mode mismatch.", 2) end
  736.  
  737.             local tempfile, keys = {}, {}
  738.  
  739.             for key, _ in pairs(file) do keys[#keys + 1] = key end
  740.             for i = 1, #keys do
  741.                 tempfile[keys[i]] = file[keys[i]]
  742.                 file[keys[i]] = nil
  743.             end
  744.  
  745.             file = tempfile
  746.         end
  747.  
  748.         if read then
  749.             local data = {}
  750.             if file.readAll then
  751.                 local len = 1
  752.  
  753.                 while true do
  754.                     local amount = file.read()
  755.                     data[len] = string.char(amount)
  756.                     len = len + 1
  757.  
  758.                     if amount == 0 then break end
  759.  
  760.                     data[len] = file.read(amount)
  761.                     len = len + 1
  762.                 end
  763.  
  764.                 data = table.concat(data)
  765.                 data = {data:byte(1, #data)}
  766.             else
  767.                 local len = 1
  768.  
  769.                 while true do
  770.                     local amount = file.read()
  771.                     data[len] = amount
  772.                     len = len + 1
  773.  
  774.                     if amount == 0 then break end
  775.  
  776.                     for i = 1, amount do
  777.                         data[len] = file.read()
  778.                         len = len + 1
  779.                     end
  780.  
  781.                     snooze()
  782.                 end
  783.             end
  784.  
  785.             local decompressIt, outputlist = decompressIterator(valRange, data), ""
  786.  
  787.             if binary then
  788.                 function newhandle.read(amount)
  789.                     if not outputlist then return nil end
  790.  
  791.                     if type(amount) ~= "number" then
  792.                         if #outputlist == 0 then
  793.                             outputlist = decompressIt()
  794.                             if not outputlist then return nil end
  795.                         end
  796.  
  797.                         local result = outputlist:byte(1)
  798.                         outputlist = outputlist:sub(2)
  799.                         return result
  800.                     else
  801.                         while #outputlist < amount do
  802.                             local new = decompressIt()
  803.  
  804.                             if not new then
  805.                                 new = outputlist
  806.                                 outputlist = nil
  807.                                 if #new > 0 then return new else return end
  808.                             end
  809.  
  810.                             outputlist = outputlist .. new
  811.                         end
  812.  
  813.                         local result = outputlist:sub(1, amount)
  814.                         outputlist = outputlist:sub(amount + 1)
  815.                         return result
  816.                     end
  817.                 end
  818.  
  819.                 function newhandle.readAll()
  820.                     if not outputlist then return nil end
  821.  
  822.                     local result, len = {outputlist}, 2
  823.                     for data in decompressIt do
  824.                         result[len] = data
  825.                         len = len + 1
  826.                     end
  827.  
  828.                     outputlist = nil
  829.  
  830.                     return table.concat(result)
  831.                 end
  832.             else
  833.                 function newhandle.readLine()
  834.                     if not outputlist then return nil end
  835.  
  836.                     while not outputlist:find("\n") do
  837.                         local new = decompressIt()
  838.  
  839.                         if not new then
  840.                             new = outputlist
  841.                             outputlist = nil
  842.                             if #new > 0 then return new else return end
  843.                         end
  844.  
  845.                         outputlist = outputlist .. new
  846.                     end
  847.  
  848.                     local result = outputlist:sub(1, outputlist:find("\n") - 1)
  849.                     outputlist = outputlist:sub(outputlist:find("\n") + 1)
  850.  
  851.                     if outputlist:byte(1) == 13 then outputlist = outputlist:sub(2) end
  852.  
  853.                     return result
  854.                 end
  855.  
  856.                 function newhandle.readAll()
  857.                     if not outputlist then return nil end
  858.  
  859.                     local result, len = {outputlist}, 2
  860.                     for data in decompressIt do
  861.                         result[len] = data
  862.                         len = len + 1
  863.                     end
  864.  
  865.                     outputlist = nil
  866.  
  867.                     return table.concat(result)
  868.                 end
  869.             end
  870.  
  871.             function newhandle.extractHandle()
  872.                 local keys = {}
  873.                 for key, _ in pairs(newhandle) do keys[#keys + 1] = key end
  874.                 for i = 1, #keys do newhandle[keys[i]] = nil end
  875.                 return file
  876.             end
  877.         else
  878.             local compressIt = compressIterator(valRange)
  879.  
  880.             if binary then
  881.                 function newhandle.write(data)
  882.                     if type(data) == "number" then
  883.                         compressIt(data)
  884.                     elseif type(data) == "string" then
  885.                         data = {data:byte(1, #data)}
  886.                         for i = 1, #data do compressIt(data[i]) end
  887.                     else error("bbpackHandle.write: bad argument #1 (string or number expected, got " .. type(data) .. ")", 2) end
  888.                 end
  889.             else
  890.                 function newhandle.write(text)
  891.                     text = tostring(text)
  892.                     text = {text:byte(1, #text)}
  893.                     for i = 1, #text do compressIt(text[i]) end
  894.                 end
  895.  
  896.                 function newhandle.writeLine(text)
  897.                     text = tostring(text)
  898.                     text = {text:byte(1, #text)}
  899.                     for i = 1, #text do compressIt(text[i]) end
  900.                     compressIt(10)
  901.                 end
  902.             end
  903.  
  904.             newhandle.flush = file.flush
  905.  
  906.             function newhandle.extractHandle()
  907.                 local output, fWrite = compressIt(false), file.write
  908.                 for j = 1, #output do fWrite(output[j]) end
  909.                 local keys = {}
  910.                 for key, _ in pairs(newhandle) do keys[#keys + 1] = key end
  911.                 for i = 1, #keys do newhandle[keys[i]] = nil end
  912.                 return file
  913.             end
  914.         end
  915.  
  916.         function newhandle.close()
  917.             newhandle.extractHandle().close()
  918.         end
  919.  
  920.         return newhandle
  921.     end
  922.  
  923.     function lines(file)
  924.         if type(file) == "string" then
  925.             file = open(file, "r")
  926.         elseif type(file) ~= "table" or not file.readLine then
  927.             error("bbpack.lines: Expected: file (string or \"r\"-mode handle).", 2)
  928.         end
  929.  
  930.         return function()
  931.             if not file.readLine then return nil end
  932.  
  933.             local line = file.readLine()
  934.             if line then
  935.                 return line
  936.             else
  937.                 file.close()
  938.                 return nil
  939.             end
  940.         end
  941.     end
  942.    
  943.     local function dividePath(path)
  944.         local result = {}
  945.         for element in path:gmatch("[^/]+") do result[#result + 1] = element end
  946.         return result
  947.     end
  948.    
  949.     local function getGithubRepo(repo)
  950.         local elements = dividePath(repo)
  951.         for i = 1, #elements do if table.remove(elements, 1) == "github.com" then break end end
  952.         if #elements < 2 or elements[3] == "raw" then return end
  953.         repo = elements[1] .. "/" .. elements[2]
  954.         local branch = (elements[3] == "tree") and elements[4] or "master"
  955.        
  956.         local webHandle = http.get("https://api.github.com/repos/" .. repo .. "/git/trees/" .. branch .. "?recursive=1")
  957.         if not webHandle then return end
  958.         local json = textutils.unserialize(webHandle.readAll():gsub("\10", ""):gsub(" ", ""):gsub("%[", "{"):gsub("]", "}"):gsub("{\"", "{[\""):gsub(",\"", ",[\""):gsub("\":", "\"]="))
  959.         webHandle.close()
  960.         if json.message == "Not Found" then return end
  961.        
  962.         local tree, results = json.tree, {}
  963.        
  964.         for i = 1, #tree do if tree[i].type == "blob" then
  965.             local path, cur = tree[i].path, results
  966.             local elements = dividePath(path)
  967.            
  968.             for i = 1, #elements - 1 do
  969.                 local element = elements[i]
  970.                 if not cur[element] then cur[element] = {} end
  971.                 cur = cur[element]
  972.             end
  973.            
  974.             cur[elements[#elements]] = "https://raw.githubusercontent.com/" .. repo .. "/" .. branch .. "/" .. path
  975.         end end
  976.        
  977.         if #elements > 4 then for i = 5, #elements do results = results[elements[i]] end end
  978.        
  979.         return (type(results) == "table") and results
  980.     end
  981.    
  982.     local configTable = {["webMounts"] = {}, ["githubRepos"] = {}, ["clusters"] = {}, ["compressedFS"] = false}
  983.  
  984.     if fs.exists(".bbpack.cfg") then
  985.         local file = rfs and rfs.open(".bbpack.cfg", "r") or fs.open(".bbpack.cfg", "r")
  986.         local input = textutils.unserialize(file.readAll())
  987.         file.close()
  988.        
  989.         if type(input) == "table" then
  990.             if type(input.webMounts) == "table" then configTable.webMounts = input.webMounts end
  991.             if type(input.githubRepos) == "table" then configTable.githubRepos = input.githubRepos end
  992.             if type(input.clusters) == "table" then configTable.clusters = input.clusters end
  993.             if type(input.compressedFS) == "boolean" then configTable.compressedFS = input.compressedFS end
  994.         end
  995.     end
  996.    
  997.     local webMountList, clusterList, repoList = configTable.webMounts, configTable.clusters, {}
  998.     for path, url in pairs(configTable.githubRepos) do repoList[path] = getGithubRepo(url) end
  999.     if next(clusterList) then for _, side in pairs(rs.getSides()) do if peripheral.getType(side) == "modem" then rednet.open(side) end end end
  1000.     local blacklist = {"bbpack", "bbpack.lua", "startup", "startup.lua", ".settings", ".gif", ".zip", ".bbpack.cfg"}
  1001.  
  1002.     if not _G.rfs then
  1003.         local rfs, ramdisk = {}, {}
  1004.  
  1005.         for key, value in pairs(fs) do rfs[key] = value end
  1006.        
  1007.         local function clusterTalk(cluster, answer, ...)
  1008.             local target, uuid, result, sender, msg = clusterList[cluster], math.random(1, 0x7FFFFFFF), {}
  1009.  
  1010.             for i = 1, #target do rednet.send(target[i], {["cluster"] = cluster, ["uuid"] = uuid, unpack(arg)}, rednet.host and cluster) end
  1011.  
  1012.             if answer then
  1013.                 for i = 1, #target do
  1014.                     repeat sender, msg = rednet.receive(rednet.host and cluster) until type(msg) == "table" and msg.cluster == cluster and msg.uuid == uuid
  1015.                     result[i] = msg[1]
  1016.                 end
  1017.  
  1018.                 return result
  1019.             end
  1020.         end
  1021.  
  1022.         _G.fs.list = function(path)
  1023.             if type(path) ~= "string" then error("bad argument #1 (expected string, got " .. type(path) .. ")", 2 ) end
  1024.            
  1025.             path = fs.combine(path, "")
  1026.             local elements = dividePath(path)
  1027.  
  1028.             if not fs.isDir(path) then error("Not a directory", 2) end
  1029.  
  1030.             if fs.getDrive(path) == "hdd" then
  1031.                 local results = rfs.list(path)
  1032.  
  1033.                 for i = 1, #results do
  1034.                     local thisResult = results[i]
  1035.                     if thisResult:sub(-4) == ".bbp" then results[i] = thisResult:sub(1, -5) end
  1036.                 end
  1037.  
  1038.                 for mount in pairs(webMountList) do
  1039.                     local mountElements = dividePath(mount)
  1040.  
  1041.                     if #elements == #mountElements - 1 then
  1042.                         local match = true
  1043.  
  1044.                         for i = 1, #elements do if elements[i] ~= mountElements[i] then
  1045.                             match = false
  1046.                             break
  1047.                         end end
  1048.  
  1049.                         if match then results[#results + 1] = mountElements[#mountElements] end
  1050.                     end
  1051.                 end
  1052.  
  1053.                 if path == "" then
  1054.                     results[#results + 1] = "ram"
  1055.                     for cluster in pairs(clusterList) do results[#results + 1] = cluster end
  1056.                     for repo in pairs(repoList) do results[#results + 1] = repo end
  1057.                 end
  1058.  
  1059.                 table.sort(results)
  1060.  
  1061.                 return results
  1062.             elseif clusterList[elements[1]] then
  1063.                 local results = {}
  1064.  
  1065.                 local lists = clusterTalk(elements[1], true, "list", path)
  1066.                 for i = 1, #clusterList[elements[1]] do
  1067.                     local subList = lists[i]
  1068.  
  1069.                     for i = 1, #subList do
  1070.                         local found, thisSub = false, subList[i]
  1071.  
  1072.                         for j = 1, #results do if results[j] == thisSub then
  1073.                             found = true
  1074.                             break
  1075.                         end end
  1076.  
  1077.                         if not found then results[#results + 1] = thisSub end
  1078.                     end
  1079.                 end
  1080.  
  1081.                 table.sort(results)
  1082.  
  1083.                 return results
  1084.             elseif elements[1] == "ram" or repoList[elements[1]] then
  1085.                 local cur, results = (elements[1] == "ram") and ramdisk or repoList[elements[1]], {}
  1086.  
  1087.                 for i = 2, #elements do cur = cur[elements[i]] end
  1088.  
  1089.                 for entry in pairs(cur) do results[#results + 1] = entry end
  1090.  
  1091.                 table.sort(results)
  1092.  
  1093.                 return results
  1094.             else
  1095.                 return rfs.list(path)
  1096.             end
  1097.         end
  1098.  
  1099.         _G.fs.exists = function(path)
  1100.             if type(path) ~= "string" then error("bad argument #1 (expected string, got " .. type(path) .. ")", 2 ) end
  1101.             path = fs.combine(path, "")
  1102.             local elements = dividePath(path)
  1103.  
  1104.             if webMountList[path] then
  1105.                 return true
  1106.             elseif clusterList[elements[1]] then
  1107.                 if #elements == 1 then return true end
  1108.                 local list = clusterTalk(elements[1], true, "exists", path)
  1109.                 for i = 1, #list do if list[i] then return true end end
  1110.                 return false
  1111.             elseif elements[1] == "ram" or repoList[elements[1]] then
  1112.                 local cur = (elements[1] == "ram") and ramdisk or repoList[elements[1]]
  1113.  
  1114.                 for i = 2, #elements do
  1115.                     cur = cur[elements[i]]
  1116.                     if not cur then return false end
  1117.                 end
  1118.  
  1119.                 return true
  1120.             else
  1121.                 return rfs.exists(path..".bbp") or rfs.exists(path)
  1122.             end
  1123.         end
  1124.  
  1125.         _G.fs.isDir = function(path)
  1126.             if type(path) ~= "string" then error("bad argument #1 (expected string, got " .. type(path) .. ")", 2 ) end
  1127.             if not fs.exists(path) then return false end
  1128.             path = fs.combine(path, "")
  1129.             local elements = dividePath(path)
  1130.  
  1131.             if clusterList[elements[1]] then
  1132.                 if #elements == 1 then return true end
  1133.                 local list = clusterTalk(elements[1], true, "isDir", path)
  1134.                 return list[1]
  1135.             elseif elements[1] == "ram" or repoList[elements[1]] then
  1136.                 local cur = (elements[1] == "ram") and ramdisk or repoList[elements[1]]
  1137.  
  1138.                 for i = 2, #elements do
  1139.                     cur = cur[elements[i]]
  1140.                 end
  1141.  
  1142.                 return type(cur) == "table"
  1143.             else
  1144.                 return rfs.isDir(path)
  1145.             end
  1146.         end
  1147.  
  1148.         _G.fs.isReadOnly = function(path)
  1149.             if type(path) ~= "string" then error("bad argument #1 (expected string, got " .. type(path) .. ")", 2 ) end
  1150.             path = fs.combine(path, "")
  1151.             local elements = dividePath(path)
  1152.  
  1153.             if webMountList[path] or repoList[elements[1]] then
  1154.                 return true
  1155.             elseif clusterList[elements[1]] or elements[1] == "ram" then
  1156.                 return false
  1157.             else
  1158.                 return rfs.isReadOnly(rfs.exists(path..".bbp") and (path..".bbp") or path)
  1159.             end
  1160.         end
  1161.  
  1162.         _G.fs.getDrive = function(path)
  1163.             if type(path) ~= "string" then error("bad argument #1 (expected string, got " .. type(path) .. ")", 2 ) end
  1164.             path = fs.combine(path, "")
  1165.             local elements = dividePath(path)
  1166.  
  1167.             if clusterList[elements[1]] or elements[1] == "ram" or repoList[elements[1]] then
  1168.                 return fs.exists(path) and elements[1]
  1169.             else
  1170.                 return rfs.getDrive(rfs.exists(path..".bbp") and (path..".bbp") or path)
  1171.             end
  1172.         end
  1173.  
  1174.         _G.fs.getSize = function(path)
  1175.             if type(path) ~= "string" then error("bad argument #1 (expected string, got " .. type(path) .. ")", 2 ) end
  1176.             path = fs.combine(path, "")
  1177.             local elements = dividePath(path)
  1178.  
  1179.             if webMountList[path] or repoList[elements[1]] then
  1180.                 return 0
  1181.             elseif clusterList[elements[1]] then
  1182.                 if #elements == 1 then return 0 end
  1183.                 if not fs.exists(path) then error("No such file", 2) end
  1184.  
  1185.                 local size, list = 0, clusterTalk(elements[1], true, "getSize", path)
  1186.                 for i = 1, #clusterList[elements[1]] do size = size + list[i] end
  1187.                 return size
  1188.             elseif elements[1] == "ram" then
  1189.                 local cur = ramdisk
  1190.  
  1191.                 for i = 2, #elements do
  1192.                     cur = cur[elements[i]]
  1193.                     if not cur then error("No such file", 2) end
  1194.                 end
  1195.  
  1196.                 return type(cur) == "string" and #cur or 0
  1197.             else
  1198.                 return rfs.getSize(rfs.exists(path..".bbp") and (path..".bbp") or path)
  1199.             end
  1200.         end
  1201.  
  1202.         _G.fs.getFreeSpace = function(path)
  1203.             if type(path) ~= "string" then error("bad argument #1 (expected string, got " .. type(path) .. ")", 2 ) end
  1204.             path = fs.combine(path, "")
  1205.             local elements = dividePath(path)
  1206.  
  1207.             if clusterList[elements[1]] then
  1208.                 local size, list = 0, clusterTalk(elements[1], true, "getFreeSpace")
  1209.                 for i = 1, #clusterList[elements[1]] do size = size + list[i][1] end
  1210.                 return size
  1211.             elseif elements[1] == "ram" then
  1212.                 return math.huge
  1213.             elseif repoList[elements[1]] then
  1214.                 return 0
  1215.             else
  1216.                 return rfs.getFreeSpace(path)
  1217.             end
  1218.         end
  1219.  
  1220.         _G.fs.makeDir = function(path)
  1221.             if type(path) ~= "string" then error("bad argument #1 (expected string, got " .. type(path) .. ")", 2 ) end
  1222.             path = fs.combine(path, "")
  1223.             local elements = dividePath(path)
  1224.  
  1225.             if fs.exists(path) then
  1226.                 if fs.isDir(path) then
  1227.                     return
  1228.                 else
  1229.                     error("File exists", 2)
  1230.                 end
  1231.             end
  1232.  
  1233.             if clusterList[elements[1]] then
  1234.                 clusterTalk(elements[1], false, "makeDir", path)
  1235.             elseif elements[1] == "ram" then
  1236.                 local cur = ramdisk
  1237.  
  1238.                 for i = 2, #elements do
  1239.                     local next = cur[elements[i]]
  1240.  
  1241.                     if next then
  1242.                         cur = next
  1243.                     else
  1244.                         cur[elements[i]] = {}
  1245.                         cur = cur[elements[i]]
  1246.                     end
  1247.                 end
  1248.             elseif repoList[elements[1]] then
  1249.                 error("Access denied", 2)
  1250.             else
  1251.                 return rfs.makeDir(path)
  1252.             end
  1253.         end
  1254.  
  1255.         _G.fs.move = function(path1, path2)
  1256.             if type(path1) ~= "string" then error("bad argument #1 (expected string, got " .. type(path1) .. ")", 2 ) end
  1257.             if type(path2) ~= "string" then error("bad argument #2 (expected string, got " .. type(path2) .. ")", 2 ) end
  1258.             path1, path2 = fs.combine(path1, ""), fs.combine(path2, "")
  1259.  
  1260.             if not fs.exists(path1) then error("No such file", 2) end
  1261.             if fs.exists(path2) then error("File exists", 2) end
  1262.             if fs.isReadOnly(path1) or fs.isReadOnly(path2) or (#dividePath(path1) == 1 and fs.getDrive(path1) ~= "hdd") then error("Access denied", 2) end
  1263.             if #dividePath(path1) < #dividePath(path2) and path2:sub(#path1) == path1 then error("Can't copy a directory inside itself", 2) end
  1264.             -- ... and if we run out of space we'll just let things fall over at the writing level.
  1265.  
  1266.             if fs.isDir(path1) then
  1267.                 fs.makeDir(path2)
  1268.                 local list = fs.list(path1)
  1269.                 for i = 1, #list do fs.move(fs.combine(path1, list[i]), fs.combine(path2, list[i])) end
  1270.             else
  1271.                 local input, output = fs.open(path1, "rb"), fs.open(path2, "wb")
  1272.  
  1273.                 if input.readAll then
  1274.                     output.write(input.readAll())
  1275.                 else
  1276.                     for byte in input.read do output.write(byte) end
  1277.                 end
  1278.  
  1279.                 input.close()
  1280.                 output.close()
  1281.             end
  1282.  
  1283.             fs.delete(path1)
  1284.         end
  1285.  
  1286.         _G.fs.copy = function(path1, path2)
  1287.             if type(path1) ~= "string" then error("bad argument #1 (expected string, got " .. type(path1) .. ")", 2 ) end
  1288.             if type(path2) ~= "string" then error("bad argument #2 (expected string, got " .. type(path2) .. ")", 2 ) end
  1289.             path1, path2 = fs.combine(path1, ""), fs.combine(path2, "")
  1290.  
  1291.             if not fs.exists(path1) then error("No such file", 2) end
  1292.             if fs.exists(path2) then error("File exists", 2) end
  1293.             if fs.isReadOnly(path2) then error("Access denied", 2) end
  1294.             if #dividePath(path1) < #dividePath(path2) and path2:sub(#path1) == path1 then error("Can't copy a directory inside itself", 2) end
  1295.             -- ... and if we run out of space we'll just let things fall over at the writing level.
  1296.  
  1297.             if fs.isDir(path1) then
  1298.                 fs.makeDir(path2)
  1299.                 local list = fs.list(path1)
  1300.                 for i = 1, #list do fs.copy(fs.combine(path1, list[i]), fs.combine(path2, list[i])) end
  1301.             else
  1302.                 local input, output = fs.open(path1, "rb"), fs.open(path2, "wb")
  1303.  
  1304.                 if input.readAll then
  1305.                     output.write(input.readAll())
  1306.                 else
  1307.                     for byte in input.read do output.write(byte) end
  1308.                 end
  1309.  
  1310.                 input.close()
  1311.                 output.close()
  1312.             end
  1313.         end
  1314.  
  1315.         _G.fs.delete = function(path)
  1316.             if type(path) ~= "string" then error("bad argument #1 (expected string, got " .. type(path) .. ")", 2 ) end
  1317.             path = fs.combine(path, "")
  1318.             local elements = dividePath(path)
  1319.  
  1320.             if not fs.exists(path) then return end
  1321.  
  1322.             if webMountList[path] then
  1323.                 webMountList[path] = nil
  1324.                 local file = rfs.open(".bbpack.cfg", "w")
  1325.                 file.write(textutils.serialize(configTable))
  1326.                 file.close()
  1327.             elseif clusterList[elements[1]] then
  1328.                 if #elements == 1 then
  1329.                     clusterList[elements[1]] = nil
  1330.                     local file = rfs.open(".bbpack.cfg", "w")
  1331.                     file.write(textutils.serialize(configTable))
  1332.                     file.close()
  1333.                 else
  1334.                     clusterTalk(elements[1], false, "delete", path)
  1335.                 end
  1336.             elseif repoList[elements[1]] then
  1337.                 if #elements == 1 then
  1338.                     repoList[elements[1]], configTable.githubRepos[elements[1]] = nil, nil
  1339.                     local file = rfs.open(".bbpack.cfg", "w")
  1340.                     file.write(textutils.serialize(configTable))
  1341.                     file.close()
  1342.                 else
  1343.                     error("Access denied", 2)
  1344.                 end
  1345.             elseif elements[1] == "ram" then
  1346.                 if #elements == 1 then error("Access denied", 2) end
  1347.  
  1348.                 local cur = ramdisk
  1349.  
  1350.                 for i = 2, #elements - 1 do
  1351.                     cur = cur[elements[i]]
  1352.                     if not cur then return end
  1353.                 end
  1354.  
  1355.                 cur[elements[#elements]] = nil
  1356.             else
  1357.                 if fs.isDir(path) then
  1358.                     local list = fs.list(path)
  1359.                    
  1360.                     for i = 1, #list do
  1361.                         local ok, err = pcall(fs.delete, fs.combine(path, list[i]))
  1362.                         if not ok then error(err:gsub("pcall: ", ""), 2) end
  1363.                     end
  1364.                    
  1365.                     return rfs.delete(path)
  1366.                 else
  1367.                     return rfs.delete(rfs.exists(path..".bbp") and (path..".bbp") or path)
  1368.                 end
  1369.             end
  1370.         end
  1371.  
  1372.         _G.fs.open = function(path, mode)
  1373.             if type(path) ~= "string" then error("bad argument #1 (expected string, got " .. type(path) .. ")", 2 ) end
  1374.             if type(mode) ~= "string" then error("bad argument #2 (expected string, got " .. type(mode) .. ")", 2 ) end
  1375.             path = fs.combine(path, "")
  1376.             local elements = dividePath(path)
  1377.  
  1378.             mode = mode:lower()
  1379.             local binary, append, read, write = mode:find("b") ~= nil, mode:find("a") ~= nil, mode:find("r") ~= nil, mode:find("w") ~= nil
  1380.  
  1381.             if webMountList[path] or clusterList[elements[1]] or elements[1] == "ram" or repoList[elements[1]] then
  1382.                 if not (append or write or read) then error("Invalid file mode: " .. mode, 2) end
  1383.  
  1384.                 if read then
  1385.                     if not fs.exists(path) or fs.isDir(path) then return nil, "No such file" end
  1386.  
  1387.                     local data
  1388.                     local handle = {["close"] = function() data = nil end}
  1389.  
  1390.                     if webMountList[path] then
  1391.                         local webHandle = http.get(webMountList[path], nil, term.setPaletteColour and true)
  1392.                         data = webHandle.readAll()
  1393.                         webHandle.close()
  1394.                     elseif clusterList[elements[1]] then
  1395.                         data = {}
  1396.                         local list = clusterTalk(elements[1], true, "get", path)
  1397.                         for i = 1, #clusterList[elements[1]] do if list[i] then data[list[i][1]] = list[i][2] end end
  1398.                         data = table.concat(data)
  1399.                         data = decompressInternal({data:byte(1, #data)}, true)
  1400.                     elseif repoList[elements[1]] then
  1401.                         data = repoList[elements[1]]
  1402.                         for i = 2, #elements do data = data[elements[i]] end
  1403.                         local webHandle = http.get(data, nil, term.setPaletteColour and true)
  1404.                         data = webHandle.readAll()
  1405.                         webHandle.close()
  1406.                     else
  1407.                         data = ramdisk
  1408.                         for i = 2, #elements do data = data[elements[i]] end
  1409.                     end
  1410.  
  1411.                     if #data == 0 then data = nil end
  1412.  
  1413.                     if binary then
  1414.                         handle.read = function(amount)
  1415.                             if not data then return nil end
  1416.  
  1417.                             local result
  1418.                             if type(amount) ~= "number" then
  1419.                                 result = data:byte(1)
  1420.                                 data = data:sub(2)
  1421.                             else
  1422.                                 result = data:sub(1, amount)
  1423.                                 data = data:sub(amount + 1)
  1424.                             end
  1425.  
  1426.                             if #data == 0 then data = nil end
  1427.                             return result
  1428.                         end
  1429.  
  1430.                         handle.readAll = function()
  1431.                             if not data then return nil end
  1432.  
  1433.                             local result = data
  1434.                             data = nil
  1435.                             return result
  1436.                         end
  1437.                     else
  1438.                         handle.readLine = function()
  1439.                             if not data then return nil end
  1440.  
  1441.                             if data:find("\n") then
  1442.                                 local result = data:sub(1, data:find("\n") - 1)
  1443.                                 data = data:sub(data:find("\n") + 1)
  1444.                                 if data:byte(1) == 13 then data = data:sub(2) end
  1445.                                 return result
  1446.                             else
  1447.                                 local result = data
  1448.                                 data = nil
  1449.                                 return data
  1450.                             end
  1451.                         end
  1452.  
  1453.                         handle.readAll = function()
  1454.                             if not data then return nil end
  1455.  
  1456.                             local result = data
  1457.                             data = nil
  1458.                             return result
  1459.                         end
  1460.                     end
  1461.  
  1462.                     return handle
  1463.                 elseif write or append then
  1464.                     if webMountList[path] or repoList[elements[1]] then return nil, "Access denied" end
  1465.                     if fs.isDir(path) then return nil, "Cannot write to directory" end
  1466.                     fs.makeDir(fs.getDir(path))
  1467.  
  1468.                     local handle, output, counter = {}, {}, 1
  1469.  
  1470.                     if binary then
  1471.                         handle.write = function(data)
  1472.                             if type(data) ~= "string" and type(data) ~= "number" then error("bad argument #1 (string or number expected, got " .. type(data) .. ")", 2) end
  1473.                             output[counter] = type(data) == "number" and string.char(data) or data
  1474.                             counter = counter + 1
  1475.                         end
  1476.                     else
  1477.                         handle.write = function(data)
  1478.                             output[counter] = tostring(data)
  1479.                             counter = counter + 1
  1480.                         end
  1481.  
  1482.                         handle.writeLine = function(data)
  1483.                             output[counter] = tostring(data)
  1484.                             output[counter + 1] = "\n"
  1485.                             counter = counter + 2
  1486.                         end
  1487.                     end
  1488.  
  1489.                     local ramLink, ramIndex
  1490.                     if clusterList[elements[1]] and append and fs.exists(path) then
  1491.                         local data, list = {}, clusterTalk(elements[1], true, "get", path)
  1492.                         for i = 1, #list do if list[i] then data[list[i][1]] = list[i][2] end end
  1493.                         data = table.concat(data)
  1494.                         output[1], counter = decompressInternal({data:byte(1, #data)}, true), 2
  1495.                     elseif elements[1] == "ram" then
  1496.                         ramLink = ramdisk
  1497.                         for i = 2, #elements - 1 do ramLink = ramLink[elements[i]] end
  1498.                         ramIndex = elements[#elements]
  1499.                         if (append and not ramLink[ramIndex]) or not append then ramLink[ramIndex] = "" end
  1500.                     end
  1501.  
  1502.                     handle.flush = function()
  1503.                         if clusterList[elements[1]] then
  1504.                             output, counter = {table.concat(output)}, 1
  1505.                             local data, segs, pos, totalSpace, thisCluster = string.char(unpack(compressInternal(output[1]))), 1, 1, fs.getFreeSpace(elements[1]), clusterList[elements[1]]
  1506.                             if fs.exists(path) then totalSpace = totalSpace + fs.getSize(path) end
  1507.                             if totalSpace < #data then error("Out of space", 2) end
  1508.  
  1509.                             fs.delete(path)
  1510.                            
  1511.                             local spaceList = clusterTalk(elements[1], true, "getFreeSpace")
  1512.                            
  1513.                             for i = 1, #thisCluster do
  1514.                                 local thisSpace = spaceList[i][1]
  1515.                                
  1516.                                 if thisSpace > 0 then
  1517.                                     local segString = tostring(segs)
  1518.                                     rednet.send(spaceList[i][2], {["cluster"] = elements[1], "put", path .. string.rep("0", 3 - #segString) .. segString, data:sub(pos, pos + thisSpace - 1)}, rednet.host and elements[1])
  1519.                                     pos, segs = pos + thisSpace, segs + 1
  1520.                                 end
  1521.  
  1522.                                 if pos > #data then break end
  1523.                             end
  1524.                         else
  1525.                             output = table.concat(output)
  1526.                             if append then output = ramLink[ramIndex] .. output end
  1527.                             ramLink[ramIndex] = output
  1528.                             output, counter, append = {}, 1, true
  1529.                         end
  1530.                     end
  1531.  
  1532.                     handle.close = function()
  1533.                         handle.flush()
  1534.                         for key in pairs(handle) do handle[key] = function() end end
  1535.                     end
  1536.  
  1537.                     return handle
  1538.                 end
  1539.             else
  1540.                 if (write or append) and rfs.isReadOnly(path) then return nil, "Access denied" end
  1541.  
  1542.                 for i = 1, #blacklist do
  1543.                     local check = blacklist[i]
  1544.                     if path:sub(-#check):lower() == check then return rfs.open(path, mode) end
  1545.                 end
  1546.  
  1547.                 if read then
  1548.                     return rfs.exists(path .. ".bbp") and open(path .. ".bbp", mode) or rfs.open(path, mode)
  1549.                 elseif configTable.compressedFS then
  1550.                     if rfs.getDrive(elements[1]) and rfs.getDrive(elements[1]) ~= "hdd" then
  1551.                         return rfs.open(path, mode)
  1552.                     elseif append then
  1553.                         if rfs.exists(path) then
  1554.                             local file, content = rfs.open(path, binary and "rb" or "r")
  1555.  
  1556.                             if file.readAll then
  1557.                                 content = file.readAll()
  1558.                             else
  1559.                                 content = {}
  1560.                                 for byte in file.read do content[#content + 1] = byte end
  1561.                                 content = string.char(unpack(content))
  1562.                             end
  1563.  
  1564.                             file.close()
  1565.  
  1566.                             rfs.delete(path)
  1567.  
  1568.                             file = open(path .. ".bbp", binary and "wb" or "w")
  1569.                             file.write(content)
  1570.                             return file
  1571.                         else return open(path .. ".bbp", mode) end
  1572.                     elseif write then
  1573.                         if rfs.exists(path) then rfs.delete(path) end
  1574.                         return open(path .. ".bbp", mode)
  1575.                     end
  1576.                 else
  1577.                     if append then
  1578.                         if rfs.exists(path .. ".bbp") then
  1579.                             local file = open(path .. ".bbp", binary and "rb" or "r")
  1580.                             local content = file.readAll()
  1581.                             file.close()
  1582.  
  1583.                             rfs.delete(path .. ".bbp")
  1584.  
  1585.                             file = rfs.open(path, binary and "wb" or "w")
  1586.  
  1587.                             if file.writeLine or term.setPaletteColour then
  1588.                                 file.write(content)
  1589.                             else
  1590.                                 content = {string.byte(1, #content)}
  1591.                                 for i = 1, #content do file.write(content[i]) end
  1592.                             end
  1593.  
  1594.                             return file
  1595.                         else return rfs.open(path, mode) end
  1596.                     elseif write then
  1597.                         if rfs.exists(path .. ".bbp") then rfs.delete(path .. ".bbp") end
  1598.                         return rfs.open(path, mode)
  1599.                     end
  1600.                 end
  1601.  
  1602.                 error("Unsupported mode", 2)
  1603.             end
  1604.         end
  1605.  
  1606.         _G.fs.find = function(path)
  1607.             if type(path) ~= "string" then error("bad argument #1 (expected string, got " .. type(path) .. ")", 2 ) end
  1608.             local pathParts, results, curfolder = {}, {}, "/"
  1609.             for part in path:gmatch("[^/]+") do pathParts[#pathParts + 1] = part:gsub("*", "[^/]*") end
  1610.             if #pathParts == 0 then return {} end
  1611.  
  1612.             local prospects = fs.list(curfolder)
  1613.             for i = 1, #prospects do prospects[i] = {["parent"] = curfolder, ["depth"] = 1, ["name"] = prospects[i]} end
  1614.  
  1615.             while #prospects > 0 do
  1616.                 local thisProspect = table.remove(prospects, 1)
  1617.                 local fullPath = fs.combine(thisProspect.parent, thisProspect.name)
  1618.  
  1619.                 if thisProspect.name == thisProspect.name:match(pathParts[thisProspect.depth]) then
  1620.                     if thisProspect.depth == #pathParts then
  1621.                         results[#results + 1] = fullPath
  1622.                     elseif fs.isDir(fullPath) and thisProspect.depth < #pathParts then
  1623.                         local newList = fs.list(fullPath)
  1624.                         for i = 1, #newList do prospects[#prospects + 1] = {["parent"] = fullPath, ["depth"] = thisProspect.depth + 1, ["name"] = newList[i]} end
  1625.                     end
  1626.                 end
  1627.             end
  1628.  
  1629.             return results
  1630.         end
  1631.  
  1632.         _G.rfs = rfs
  1633.     end
  1634.  
  1635.     update = bbpack and bbpack.update or function()
  1636.         for cluster, ids in pairs(clusterList) do for i = 1, #ids do
  1637.             rednet.send(ids[i], {["cluster"] = cluster, "update"}, rednet.host and cluster)
  1638.         end end
  1639.  
  1640.         local file = rfs.open("bbpack", "w")
  1641.         file.write(downloadPasteInternal("cUYTGbpb"))
  1642.         file.close()
  1643.  
  1644.         os.reboot()
  1645.     end
  1646.  
  1647.     fileSys = bbpack and bbpack.fileSys or function(par1, par2)
  1648.         if type(par1) == "boolean" or (type(par1) == "string" and type(par2) == "boolean") then
  1649.             -- Compress / decompress hdd contents.
  1650.             local list
  1651.             if type(par1) == "boolean" then
  1652.                 list = rfs.list("")
  1653.                 configTable.compressedFS = par1
  1654.             else
  1655.                 list = {par1}
  1656.                 par1 = par2
  1657.             end
  1658.  
  1659.             while #list > 0 do
  1660.                 local entry = list[#list]
  1661.                 list[#list] = nil
  1662.  
  1663.                 if rfs.getDrive(entry) == "hdd" and not webMountList[entry] then
  1664.                     if rfs.isDir(entry) then
  1665.                         local newList, curLen = rfs.list(entry), #list
  1666.                         for i = 1, #newList do list[curLen + i] = fs.combine(entry, newList[i]) end
  1667.                     else
  1668.                         local blacklisted = false
  1669.  
  1670.                         for i = 1, #blacklist do
  1671.                             local check = blacklist[i]
  1672.                             if entry:sub(-#check):lower() == check then
  1673.                                 blacklisted = true
  1674.                                 break
  1675.                             end
  1676.                         end
  1677.  
  1678.                         if not blacklisted then
  1679.                             if par1 and entry:sub(-4) ~= ".bbp" then
  1680.                                 -- Compress this file.
  1681.                                 local file, content = rfs.open(entry, "rb")
  1682.                                 if file.readAll then
  1683.                                     content = file.readAll()
  1684.                                 else
  1685.                                     content = {}
  1686.                                     local counter = 1
  1687.                                     for byte in file.read do
  1688.                                         content[counter] = byte
  1689.                                         counter = counter + 1
  1690.                                     end
  1691.                                     content = string.char(unpack(content))
  1692.                                 end
  1693.                                 file.close()
  1694.                                
  1695.                                 content = compressInternal(content)
  1696.                                 if rfs.getFreeSpace(entry) + rfs.getSize(entry) < #content then return false end
  1697.                                 rfs.delete(entry)
  1698.                                 snooze()
  1699.  
  1700.                                 file = rfs.open(entry .. ".bbp", "wb")
  1701.  
  1702.                                 if term.setPaletteColor then
  1703.                                     file.write(string.char(unpack(content)))
  1704.                                 else
  1705.                                     for i = 1, #content do file.write(content[i]) end
  1706.                                 end
  1707.  
  1708.                                 file.close()
  1709.  
  1710.                                 snooze()
  1711.                             elseif not par1 and entry:sub(-4) == ".bbp" then
  1712.                                 -- Decompress this file.
  1713.                                 local file = open(entry, "rb")
  1714.                                 local content = file.readAll()
  1715.                                 file.close()
  1716.  
  1717.                                 if rfs.getFreeSpace(entry) + rfs.getSize(entry) < #content then return false end
  1718.                                 rfs.delete(entry)
  1719.                                 snooze()
  1720.  
  1721.                                 file = rfs.open(entry:sub(1, -5), "wb")
  1722.  
  1723.                                 if term.setPaletteColor then
  1724.                                     file.write(content)
  1725.                                 else
  1726.                                     content = {content:byte(1, #content)}
  1727.                                     for i = 1, #content do file.write(content[i]) end
  1728.                                 end
  1729.  
  1730.                                 file.close()
  1731.  
  1732.                                 snooze()
  1733.                             end
  1734.                         end
  1735.                     end
  1736.                 end
  1737.             end
  1738.         elseif type(par1) == "string" and type(par2) == "string" then
  1739.             -- New web mount.
  1740.             local url, path = par1, fs.combine(par2, "")
  1741.             local elements = dividePath(path)
  1742.            
  1743.             local repo = getGithubRepo(url)
  1744.             if repo then
  1745.                 if #elements > 1 then error("bbpack.mount: Github repos must be mounted at the root of your file system", 2) end
  1746.                 repoList[path] = repo
  1747.                 configTable.githubRepos[path] = url
  1748.             else
  1749.                 if fs.getDrive(elements[1]) and fs.getDrive(elements[1]) ~= "hdd" then error("bbpack.mount: web mounts must be located on the main hdd", 2) end
  1750.  
  1751.                 local get = http.get(url)
  1752.                 if not get then error("bbpack.mount: Can't connect to URL: "..url, 2) end
  1753.                 get.close()
  1754.  
  1755.                 webMountList[path] = url
  1756.             end
  1757.         elseif type(par1) == "string" then
  1758.             -- New cluster mount.
  1759.             local cluster, uuid = par1, math.random(1, 0x7FFFFFFF)
  1760.             for _, side in pairs(rs.getSides()) do if peripheral.getType(side) == "modem" then rednet.open(side) end end
  1761.             rednet.broadcast({["cluster"] = cluster, ["uuid"] = uuid, "rollcall"}, rednet.host and cluster)
  1762.             clusterList[cluster] = nil
  1763.             local myTimer, map = os.startTimer(5), {}
  1764.  
  1765.             while true do
  1766.                 local event, par1, par2 = os.pullEvent()
  1767.  
  1768.                 if event == "timer" and par1 == myTimer then
  1769.                     break
  1770.                 elseif event == "rednet_message" and type(par2) == "table" and par2.cluster == cluster and par2.uuid == uuid and par2[1] == "rollcallResponse" then
  1771.                     map[#map + 1] = par1
  1772.                 end
  1773.             end
  1774.  
  1775.             if #map == 0 then error("bbpack.mount: Can't connect to cluster: " .. cluster, 2) end
  1776.             clusterList[cluster] = map
  1777.         end
  1778.  
  1779.         local file = rfs.open(".bbpack.cfg", "w")
  1780.         file.write(textutils.serialize(configTable))
  1781.         file.close()
  1782.  
  1783.         return true
  1784.     end
  1785. end
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top