ComputerCraft Archive

APMT - Almost Persistent Mining Turtle

computer mining unknown forum

Description

+---------------------+------------+---------------------+

Installation

Copy one of these commands into your ComputerCraft terminal:

Pastebin:pastebin get cUYTGbpb apmt_-_almost_persistent_mining_turtle
wget:wget https://pastebin.com/raw/cUYTGbpb apmt_-_almost_persistent_mining_turtle
Archive:wget https://cc.shobie.xyz/cc/get/pb-cUYTGbpb apmt_-_almost_persistent_mining_turtle
Quick Install: wget https://cc.shobie.xyz/cc/get/pb-cUYTGbpb APMT - Almost Persistent Mining Turtle

Usage

Run the program after downloading

Tags

miningforumturtle-programs

Source

View Original Source

Code Preview

-- +---------------------+------------+---------------------+
-- |                     |            |                     |
-- |                     |   BBPack   |                     |
-- |                     |            |                     |
-- +---------------------+------------+---------------------+

local version = "Version 1.6.1"

-- Pastebin uploader/downloader for ComputerCraft, by Jeffrey Alexander (aka Bomb Bloke).
-- Handles multiple files in a single paste, as well as non-ASCII symbols within files.
-- Used to be called "package".
-- http://www.computercraft.info/forums2/index.php?/topic/21801-
-- pastebin get cUYTGbpb bbpack

---------------------------------------------
------------Variable Declarations------------
---------------------------------------------

local band, brshift, blshift = bit.band, bit.brshift, bit.blshift

local b64 = {}
for i = 1, 64 do
	b64[i - 1] = ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"):byte(i)
	b64[("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"):sub(i, i)] = i - 1
end

---------------------------------------------
------------Function Declarations------------
---------------------------------------------

local unpack = unpack or table.unpack

local function snooze()
	local myEvent = tostring({})
	os.queueEvent(myEvent)
	os.pullEvent(myEvent)
end

local function toBase64Internal(inputlist)
	if type(inputlist) ~= "table" then error("bbpack.toBase64: Expected: table or file handle", 2) end

	if inputlist.read then
		local templist, len = {}, 1

		for byte in inputlist.read do
			templist[len] = byte
			len = len + 1
		end

		inputlist.close()
		inputlist = templist
	elseif inputlist.readLine then
		inputlist.close()
		error("bbpack.toBase64: Use a binary-mode file handle", 2)
	end

	if #inputlist == 0 then return "" end

	local curbit, curbyte, outputlist, len = 32, 0, {}, 1

	for i = 1, #inputlist do
		local inByte, mask = inputlist[i], 128

		for j = 1, 8 do
			if band(inByte, mask) == mask then curbyte = curbyte + curbit end
			curbit, mask = curbit / 2, mask / 2

			if curbit < 1 then
				outputlist[len] = b64[curbyte]
				curbit, curbyte, len = 32, 0, len + 1
			end
		end
	end

	if curbit > 1 then outputlist[len] = b64[curbyte] end

	return string.char(unpack(outputlist))
end

local function fromBase64Internal(inData)
	if type(inData) ~= "string" and type(inData) ~= "table" then error("bbpack.fromBase64: Expected: string or file handle", 2) end

	if type(inData) == "table" then
		if inData.readLine then
			local temp = inData.readAll()
			inData.close()
			inData = temp
		else
			if inData.close then inData.close() end
			error("bbpack.fromBase64: Use text-mode file handles", 2)
		end
	end

	if #inData == 0 then return {} end

	local curbyte, curbit, outputlist, len = 0, 128, {}, 1

	for i = 1, #inData do
		local mask, curchar = 32, b64[inData:sub(i, i)]

		for j = 1, 6 do
			if band(curchar, mask) == mask then curbyte = curbyte + curbit end
			curbit, mask = curbit / 2, mask / 2

			if curbit < 1 then
				outputlist[len] = curbyte
				curbit, curbyte, len = 128, 0, len + 1
			end
		end
	end

	if curbit > 1 and curbyte > 0 then outputlist[len] = curbyte end

	return outputlist
end

local function compressIterator(ClearCode)
	local startCodeSize = 1
	while math.pow(2, startCodeSize) < ClearCode do startCodeSize = startCodeSize + 1 end

	local EOI, ClearCode = math.pow(2, startCodeSize) + 1, math.pow(2, startCodeSize)
	startCodeSize = startCodeSize + 1

	local curstring, len, curbit, curbyte, outputlist, codes, CodeSize, MaxCode, nextcode, curcode = "", 2, 1, 0, {0}, {}, startCodeSize, math.pow(2, startCodeSize) - 1, EOI + 1

	local function packByte(num)
		local mask = 1

		for i = 1, CodeSize do
			if band(num, mask) == mask then curbyte = curbyte + curbit end
			curbit, mask = curbit * 2, mask * 2

			if curbit > 128 or (i == CodeSize and num == EOI) then
				local counter = blshift(brshift(#outputlist - 1, 8), 8) + 1
				outputlist[counter] = outputlist[counter] + 1

				if outputlist[counter] > 255 then
					outputlist[counter], outputlist[counter + 256], len = 255, 1, len + 1
					snooze()
				end

				outputlist[len] = curbyte
				curbit, curbyte, len = 1, 0, len + 1
			end
		end
	end

	packByte(ClearCode)

	return function(incode)
		if not incode then
			if curcode then packByte(curcode) end
			packByte(EOI)
			outputlist[#outputlist + 1] = 0
			return outputlist
		end

		if not curcode then
			curcode = incode
			return
		end

		curstring = curstring .. string.char(incode)
		local thisCode = codes[curstring]

		if thisCode then
			curcode = thisCode
		else
			codes[curstring] = nextcode
			nextcode = nextcode + 1

			packByte(curcode)

			if nextcode == MaxCode + 2 then
				CodeSize = CodeSize + 1
				MaxCode = math.pow(2, CodeSize) - 1
			end

			if nextcode == 4095 then
				packByte(ClearCode)
				CodeSize, MaxCode, nextcode, codes = startCodeSize, math.pow(2, startCodeSize) - 1, EOI + 1, {}
			end

			curcode, curstring = incode, string.char(incode)
		end
	end
end

local function compressInternal(inputlist, valRange)
	if type(inputlist) ~= "table" and type(inputlist) ~= "string" then error("bbpack.compress: Expected: table, string or file handle", 2) end

	if not valRange then valRange = 256 end
	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

	if type(inputlist) == "table" and inputlist.close then
		local templist
		if inputlist.readAll then
			templist = inputlist.readAll()
		else
			local len = 1
			templist = {}
			for thisByte in inputlist.read do
				templist[len] = thisByte
				len = len + 1
			end
		end
		inputlist.close()
		inputlist = templist
	end

	if type(inputlist) == "string" then inputlist = {inputlist:byte(1, #inputlist)} end

	if #inputlist == 0 then return {} end

	local compressIt = compressIterator(valRange)

	local sleepCounter = 0
	for i = 1, #inputlist do
		compressIt(inputlist[i])

		sleepCounter = sleepCounter + 1
		if sleepCounter > 1023 then
			sleepCounter = 0
			snooze()
		end
	end

	return compressIt(false)
end

local function decompressIterator(ClearCode, codelist)
	local startCodeSize = 1
	while math.pow(2, startCodeSize) < ClearCode do startCodeSize = startCodeSize + 1 end

	local EOI, ClearCode = math.pow(2, startCodeSize) + 1, math.pow(2, startCodeSize)
	startCodeSize = startCodeSize + 1

	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
	for i = 0, ClearCode - 1 do codes[i] = string.char(i) end

	return function()
		while true do
			local curcode, curbit = 0, 1

			for i = 1, CodeSize do
				if band(curbyte, maskbit) == maskbit then curcode = curcode + curbit end
				curbit, maskbit = curbit * 2, maskbit * 2

				if maskbit > 128 and not (i == CodeSize and curcode == EOI) then
					maskbit, curbyte, gotbytes = 1, codelist[spot], gotbytes + 1
					spot = spot + 1

					if gotbytes > lastcounter then
						if curbyte == 0 then break end
						lastcounter, gotbytes = curbyte, 1
						curbyte = codelist[spot]
						spot = spot + 1
						snooze()
					end
				end
			end

			if curcode == ClearCode then
				CodeSize, MaxCode, nextcode, codes = startCodeSize, math.pow(2, startCodeSize) - 1, EOI + 1, {}
				for i = 0, ClearCode - 1 do codes[i] = string.char(i) end
			elseif curcode ~= EOI then
				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

				if nextcode < 4096 then
					codes[nextcode] = codes[curcode]
					nextcode = nextcode + 1
				end

				if nextcode - 2 == MaxCode then
					CodeSize = CodeSize + 1
					MaxCode = math.pow(2, CodeSize) - 1
				end

				return codes[curcode]
			else return end
		end
	end
end

local function decompressInternal(codelist, outputText, valRange)
	if type(codelist) ~= "table" then error("bbpack.decompress: Expected: table or file handle", 2) end

	if not valRange then valRange = 256 end
	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

	if codelist.readLine then
		codelist.close()
		error("bbpack.decompress: Use binary-mode file handles", 2)
	elseif codelist.readAll then
		codelist = codelist.readAll()
		codelist = {codelist:byte(1, #codelist)}
	elseif codelist.read then
		local data, len = {}, 1
		while true do
			local amount = codelist.read()
			data[len] = amount
			len = len + 1

			if amount == 0 then break end

			for i = 1, amount do
				data[len] = codelist.read()
				len = len + 1
			end

			snooze()
		end
		codelist = data
	elseif #codelist == 0 then return outputText and "" or {} end

	local outputlist, decompressIt, len = {}, decompressIterator(valRange, codelist), 1

	local sleepCounter = 0
	while true do
		local output = decompressIt()

		if output then
			outputlist[len] = output
			len = len + 1
		else break end
	end

	outputlist = table.concat(outputlist)

	return outputText and outputlist or {outputlist:byte(1, #outputlist)}
end

local function uploadPasteInternal(name, content)
	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

	if type(content) == "table" then
		if content.readLine then
			local temp = content.readAll()
			content.close()
			content = temp
		else
			if content.close then content.close() end
			error("bbpack.uploadPaste: Use text-mode file handles", 2)
		end
	end

	local webHandle = http.post(
		"https://pastebin.com/api/api_post.php", 
		"api_option=paste&" ..
		"api_dev_key=147764e5c6ac900a3015d77811334df1&" ..
		"api_paste_format=lua&" ..
		"api_paste_name=" .. textutils.urlEncode(name) .. "&" ..
		"api_paste_code=" .. textutils.urlEncode(content))

	if webHandle then
		local response = webHandle.readAll()
		webHandle.close()
		return string.match(response, "[^/]+{{code}}quot;)
	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
end

local function downloadPasteInternal(pasteID)
	if type(pasteID) ~= "string" then error("bbpack.downloadPaste: Expected: (string) paste ID", 2) end

	local webHandle = http.get("https://pastebin.com/raw/" .. textutils.urlEncode(pasteID))

	if webHandle then
		local incoming = webHandle.readAll()
		webHandle.close()
		return incoming
	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
end

if shell then
	---------------------------------------------
	------------     Main Program    ------------
	---------------------------------------------

	if not bbpack then os.loadAPI("bbpack") end

	local args = {...}
	if #args > 0 then args[1] = args[1]:lower() end

	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
		textutils.pagedPrint("Usage:\n")

		textutils.pagedPrint("Uploads specified file or directory:")
		textutils.pagedPrint("bbpack put [file/directory name]\n")

		textutils.pagedPrint("Dumps paste into specified file or directory:")
		textutils.pagedPrint("bbpack get <pasteID> [file/directory name]\n")

		textutils.pagedPrint("Writes specified file or directory to archive file:")
		textutils.pagedPrint("bbpack fileput [file/directory name] <target file>\n")

		textutils.pagedPrint("Unpacks archive file to specified file or directory:")
		textutils.pagedPrint("bbpack fileget <source file> [file/directory name]\n")

		print("For the above options, if [file/directory name] is omitted the root of the drive will be used instead.\n")

		textutils.pagedPrint("Enables automatic compression on hdd and compresses all existing files:")
		textutils.pagedPrint("bbpack compress\n")

		textutils.pagedPrint("Disables automatic compression on hdd and decompresses all existing files:")
		textutils.pagedPrint("bbpack decompress\n")

		textutils.pagedPrint("Mounts a given URL as the specified local file:")
		textutils.pagedPrint("bbpack mount <URL> <fileName>\n")

		textutils.pagedPrint("Turns the system into a server for the specified cluster:")
		textutils.pagedPrint("bbpack cluster <clusterName>\n")

		textutils.pagedPrint("Mounts the specified cluster as a drive:")
		textutils.pagedPrint("bbpack mount <clusterName>\n")

		textutils.pagedPrint("Updates package, reboots, and instructs all mounted cluster servers to do the same:")
		textutils.pagedPrint("bbpack update\n")

		return
	end

	if (args[1] == "put" or args[1] == "get") and not http then
		print("BBPack's pastebin functionality requires that the http API be enabled.")
		print("Shut down MineCraft game/server, set http_enable to true in ComputerCraft.cfg, then restart.")
		error()
	end

	---------------------------------------------
	------------      Uploading      ------------
	---------------------------------------------

	if args[1] == "put" or args[1] == "fileput" then
		local toFile
		if args[1] == "fileput" then toFile = table.remove(args, #args) end

		local uploadName, parent

		if not args[2] then
			print("Full system upload - are you sure? (y/n)")
			if read():sub(1, 1):lower() ~= "y" then
				print("Aborted.")
				error()
			end

			uploadName = os.getComputerLabel()
			if not uploadName then
				print("Enter paste title:")
				uploadName = read()
			end

			args[2] = ""
		end

		local target, output = shell.resolve(args[2]), {}
		uploadName = uploadName or target

		if not fs.exists(target) then
			print("Invalid target.")
			error()
		end

		if fs.isDir(target) then
			local fileList = fs.list(target)
			parent = target
			while #fileList > 0 do
				if fs.isDir(shell.resolve(fs.combine(parent, fileList[#fileList]))) then
					local thisDir = table.remove(fileList, #fileList)
					local newList = fs.list(shell.resolve(fs.combine(parent, thisDir)))
					for i = 1, #newList do fileList[#fileList + 1] = fs.combine(thisDir, newList[i]) end
					if #newList == 0 then output[#output + 1] = thisDir end
				else output[#output + 1] = table.remove(fileList, #fileList) end
			end
			target = output
			output = {}
		else parent, target = "", {target} end

		snooze()

		for i = #target, 1, -1 do
			if fs.combine(parent, target[i]) ~= shell.getRunningProgram() and not (parent == "" and fs.getDrive(target[i]) ~= "hdd") then
				if fs.isDir(fs.combine(parent, target[i])) then
					print(target[i])
					output[#output + 1] = {target[i], true}
				else
					print(target[i])
					output[#output + 1] = {target[i], toBase64Internal(compressInternal(fs.open(fs.combine(parent, target[i]), "rb")))}
					snooze()
				end
			end
		end

		if toFile then
			output = textutils.serialize(output)
			if fs.getFreeSpace(shell.dir()) < #output then error("Output "..#output.." bytes, disk space available "..fs.getFreeSpace(shell.dir()).." bytes: file not written.") end

			write("Writing to file \"" .. toFile .. "\"... ")

			toFile = fs.open(shell.resolve(toFile), "w")
			toFile.write(output)
			toFile.close()
			print("Success.")
		else
			write("Connecting to pastebin.com... ")
			local response = uploadPasteInternal(uploadName, textutils.serialize(output))
			print("Success.")

			print("Uploaded to paste ID: " .. response)
			print("Run \"bbpack get " .. response .. (parent == "" and "" or " " .. parent) .. "\" to download.")
		end

	---------------------------------------------
	------------     Downloading     ------------
	---------------------------------------------

	elseif args[1] == "get" or args[1] == "fileget" then
		local incoming
		if args[1] == "fileget" then
			write("Attempting to read from archive... ")
			if not fs.exists(shell.resolve(args[2])) then error("Can't find \"" .. shell.resolve(args[2]) .. "\".") end
			local inFile = fs.open(shell.resolve(args[2]), "r")
			incoming = textutils.unserialize(inFile.readAll())
			inFile.close()
			print("Success.")
		else
			write("Connecting to pastebin.com... ")
			incoming = textutils.unserialize(downloadPasteInternal(args[2]))
			print("Downloaded.")
		end

		local function getParent(path)
			local pos = #path
			if path:sub(pos,pos) == "/" then pos = pos - 1 end
			while pos > 0 and path:sub(pos,pos) ~= "/" do pos = pos - 1 end
			return pos > 0 and path:sub(1, pos - 1) or ""
		end

		local function writeFile(filePath, fileContent)
			local path = fs.combine(shell.resolve("."), filePath)
			if not fs.exists(getParent(path)) then fs.makeDir(getParent(path)) end
			if fs.getFreeSpace(shell.dir()) < #fileContent then error(path.." "..#fileContent.." bytes, disk space available "..fs.getFreeSpace(shell.dir()).." bytes: file not written.") end

			snooze()

			local myFile = fs.open(path, "wb")
			for i = 1, #fileContent do myFile.write(fileContent[i]) end
			myFile.close()

			snooze()
		end

		args[3] = args[3] or ""
		if args[3] ~= "" and #incoming == 1 then
			print(incoming[1][1] .. " => "..args[3])
			writeFile(args[3], decompressInternal(fromBase64Internal(incoming[1][2])))
		else
			for i = 1, #incoming do
				print(incoming[i][1])
				if type(incoming[i][2]) == "string" then
					writeFile(fs.combine(args[3], incoming[i][1]), decompressInternal(fromBase64Internal(incoming[i][2])))
				else fs.makeDir(fs.combine(shell.resolve("."), fs.combine(args[3], incoming[i][1]))) end
				incoming[i] = nil
			end	
		end

	---------------------------------------------
	------------     Compress FS     ------------
	---------------------------------------------

	elseif args[1] == "compress" then
		bbpack.fileSys(true)
		print("Filesystem compression enabled.")

	---------------------------------------------
	------------    Decompress FS    ------------
	---------------------------------------------

	elseif args[1] == "decompress" then
		print(bbpack.fileSys(false) and "Filesystem compression disabled." or "Filesystem compression disabled, but space is insufficient to decompress all files.")

	---------------------------------------------
	------------        Mount        ------------
	---------------------------------------------

	elseif args[1] == "mount" then
		bbpack.fileSys(args[2], args[3] and shell.resolve(args[3]))
		print("Successfully mounted.")

	---------------------------------------------
	------------       Cluster       ------------
	---------------------------------------------

	elseif args[1] == "cluster" then
		local cluster, protocol = args[2], rednet.host and args[2]

		rfs.makeDir(cluster)

		for _, side in pairs(rs.getSides()) do if peripheral.getType(side) == "modem" then rednet.open(side) end end

		print("Running as part of cluster \"" .. cluster .. "\"...")

		local function locFile(path)
			local matches = rfs.find(path .. "*")

			for i = 1, #matches do
				local thisMatch = matches[i]
				if #thisMatch == #path + 3 and thisMatch:sub(1, #path) == path then return thisMatch end
			end

			return nil
		end
		
		return (function() while true do
			local sender, msg = rednet.receive(protocol)

			if type(msg) == "table" and msg.cluster == cluster then
				local command, par1, par2 = unpack(msg)

				if command == "rollcall" then
					rednet.send(sender, {["cluster"] = cluster, ["uuid"] = msg.uuid, "rollcallResponse"}, protocol)
				elseif command == "isDir" then
					rednet.send(sender, {["cluster"] = cluster, ["uuid"] = msg.uuid, rfs.isDir(par1)}, protocol)
				elseif command == "makeDir" then
					rfs.makeDir(par1)
				elseif command == "exists" then
					rednet.send(sender, {["cluster"] = cluster, ["uuid"] = msg.uuid, type(locFile(par1)) == "string"}, protocol)
				elseif command == "getFreeSpace" then
					rednet.send(sender, {["cluster"] = cluster, ["uuid"] = msg.uuid, {rfs.getFreeSpace("") - 10000, os.getComputerID()}}, protocol)
				elseif command == "getSize" then
					local path = locFile(par1)
					rednet.send(sender, {["cluster"] = cluster, ["uuid"] = msg.uuid, path and rfs.getSize(path) or 0}, protocol)
				elseif command == "delete" then
					local path = locFile(par1)
					if path then rfs.delete(path) end
				elseif command == "list" then
					local list = rfs.list(par1)

					for i = 1, #list do
						local entry = list[i]
						if not fs.isDir(fs.combine(par1, entry)) then list[i] = entry:sub(1, -4) end
					end

					rednet.send(sender, {["cluster"] = cluster, ["uuid"] = msg.uuid, list}, protocol)
				elseif command == "get" then
					local path = locFile(par1)

					if path then
						local file, content = rfs.open(path, "rb")

						if file.readAll then
							content = file.readAll()
						else
							content = {}
							local counter = 1
							for byte in file.read do
								content[counter] = byte
								counter = counter + 1
							end
							content = string.char(unpack(content))
						end

						file.close()

						rednet.send(sender, {["cluster"] = cluster, ["uuid"] = msg.uuid, {tonumber(path:sub(-3)), content}}, protocol)
					end
					
					rednet.send(sender, {["cluster"] = cluster, ["uuid"] = msg.uuid, false}, protocol)
				elseif command == "put" then
					local file = rfs.open(par1, "wb")

					if term.setPaletteColour then
						file.write(par2)
					else
						par2 = {par2:byte(1, #par2)}
						for i = 1, #par2 do file.write(par2[i]) end
					end

					file.close()
				elseif command == "update" then
					local file = rfs.open("bbpack", "w")
					file.write(downloadPasteInternal("cUYTGbpb"))
					file.close()

					os.reboot()
				end
			end
		end end)()

	---------------------------------------------
	------------        Update       ------------
	---------------------------------------------

	elseif args[1] == "update" then
		bbpack.update()
	end
else
	---------------------------------------------
	------------     Load As API     ------------
	---------------------------------------------

	compress =      compressInternal
	decompress =    decompressInternal

	toBase64 =      toBase64Internal
	fromBase64 =    fromBase64Internal

	uploadPaste =   uploadPasteInternal
	downloadPaste = downloadPasteInternal

	function open(file, mode, valRange)
		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

		mode = mode:lower()
		local binary, append, read, write, newhandle = mode:find("b") ~= nil, mode:find("a") ~= nil, mode:find("r") ~= nil, mode:find("w") ~= nil, {}

		if not valRange then valRange = 256 end
		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

		if not (append or write or read) then error("bbpack.open: Invalid file mode: " .. mode, 2) end

		if type(file) == "string" then
			if append and rfs.exists(file) then
				local oldfile = open(file, binary and "rb" or "r", valRange)
				if not oldfile then return nil end
				local olddata = oldfile.readAll()
				oldfile.close()

				newhandle = open(file, binary and "wb" or "w", valRange)
				newhandle.write(olddata)
				return newhandle
			end

			file = rfs.open(file, (read and "r" or "w") .. "b")
			if not file then return nil end
		else
			if (write and (file.writeLine or not file.write)) or (read and not file.read) then error("bbpack.open: Handle / mode mismatch.", 2) end

			local tempfile, keys = {}, {}

			for key, _ in pairs(file) do keys[#keys + 1] = key end
			for i = 1, #keys do
				tempfile[keys[i]] = file[keys[i]]
				file[keys[i]] = nil
			end

			file = tempfile
		end

		if read then
			local data = {}
			if file.readAll then
				local len = 1

				while true do
					local amount = file.read()
					data[len] = string.char(amount)
					len = len + 1

					if amount == 0 then break end

					data[len] = file.read(amount)
					len = len + 1
				end

				data = table.concat(data)
				data = {data:byte(1, #data)}
			else
				local len = 1

				while true do
					local amount = file.read()
					data[len] = amount
					len = len + 1

					if amount == 0 then break end

					for i = 1, amount do
						data[len] = file.read()
						len = len + 1
					end

					snooze()
				end
			end

			local decompressIt, outputlist = decompressIterator(valRange, data), ""

			if binary then
				function newhandle.read(amount)
					if not outputlist then return nil end

					if type(amount) ~= "number" then
						if #outputlist == 0 then
							outputlist = decompressIt()
							if not outputlist then return nil end
						end

						local result = outputlist:byte(1)
						outputlist = outputlist:sub(2)
						return result
					else
						while #outputlist < amount do
							local new = decompressIt()

							if not new then
								new = outputlist
								outputlist = nil
								if #new > 0 then return new else return end
							end

							outputlist = outputlist .. new
						end

						local result = outputlist:sub(1, amount)
						outputlist = outputlist:sub(amount + 1)
						return result
					end
				end

				function newhandle.readAll()
					if not outputlist then return nil end

					local result, len = {outputlist}, 2
					for data in decompressIt do
						result[len] = data
						len = len + 1
					end

					outputlist = nil

					return table.concat(result)
				end
			else
				function newhandle.readLine()
					if not outputlist then return nil end

					while not outputlist:find("\n") do
						local new = decompressIt()

						if not new then
							new = outputlist
							outputlist = nil
							if #new > 0 then return new else return end
						end

						outputlist = outputlist .. new
					end

					local result = outputlist:sub(1, outputlist:find("\n") - 1)
					outputlist = outputlist:sub(outputlist:find("\n") + 1)

					if outputlist:byte(1) == 13 then outputlist = outputlist:sub(2) end

					return result
				end

				function newhandle.readAll()
					if not outputlist then return nil end

					local result, len = {outputlist}, 2
					for data in decompressIt do
						result[len] = data
						len = len + 1
					end

					outputlist = nil

					return table.concat(result)
				end
			end

			function newhandle.extractHandle()
				local keys = {}
				for key, _ in pairs(newhandle) do keys[#keys + 1] = key end
				for i = 1, #keys do newhandle[keys[i]] = nil end
				return file
			end
		else
			local compressIt = compressIterator(valRange)

			if binary then
				function newhandle.write(data)
					if type(data) == "number" then
						compressIt(data)
					elseif type(data) == "string" then
						data = {data:byte(1, #data)}
						for i = 1, #data do compressIt(data[i]) end
					else error("bbpackHandle.write: bad argument #1 (string or number expected, got " .. type(data) .. ")", 2) end
				end
			else
				function newhandle.write(text)
					text = tostring(text)
					text = {text:byte(1, #text)}
					for i = 1, #text do compressIt(text[i]) end
				end

				function newhandle.writeLine(text)
					text = tostring(text)
					text = {text:byte(1, #text)}
					for i = 1, #text do compressIt(text[i]) end
					compressIt(10)
				end
			end

			newhandle.flush = file.flush

			function newhandle.extractHandle()
				local output, fWrite = compressIt(false), file.write
				for j = 1, #output do fWrite(output[j]) end
				local keys = {}
				for key, _ in pairs(newhandle) do keys[#keys + 1] = key end
				for i = 1, #keys do newhandle[keys[i]] = nil end
				return file
			end
		end

		function newhandle.close()
			newhandle.extractHandle().close()
		end

		return newhandle
	end

	function lines(file)
		if type(file) == "string" then
			file = open(file, "r")
		elseif type(file) ~= "table" or not file.readLine then
			error("bbpack.lines: Expected: file (string or \"r\"-mode handle).", 2)
		end

		return function()
			if not file.readLine then return nil end

			local line = file.readLine()
			if line then
				return line
			else
				file.close()
				return nil
			end
		end
	end
	
	local function dividePath(path)
		local result = {}
		for element in path:gmatch("[^/]+") do result[#result + 1] = element end
		return result
	end
	
	local function getGithubRepo(repo)
		local elements = dividePath(repo)
		for i = 1, #elements do if table.remove(elements, 1) == "github.com" then break end end
		if #elements < 2 or elements[3] == "raw" then return end
		repo = elements[1] .. "/" .. elements[2]
		local branch = (elements[3] == "tree") and elements[4] or "master"
		
		local webHandle = http.get("https://api.github.com/repos/" .. repo .. "/git/trees/" .. branch .. "?recursive=1")
		if not webHandle then return end
		local json = textutils.unserialize(webHandle.readAll():gsub("\10", ""):gsub(" ", ""):gsub("%[", "{"):gsub("]", "}"):gsub("{\"", "{[\""):gsub(",\"", ",[\""):gsub("\":", "\"]="))
		webHandle.close()
		if json.message == "Not Found" then return end
		
		local tree, results = json.tree, {}
		
		for i = 1, #tree do if tree[i].type == "blob" then
			local path, cur = tree[i].path, results
			local elements = dividePath(path)
			
			for i = 1, #elements - 1 do
				local element = elements[i]
				if not cur[element] then cur[element] = {} end
				cur = cur[element]
			end
			
			cur[elements[#elements]] = "https://raw.githubusercontent.com/" .. repo .. "/" .. branch .. "/" .. path
		end end
		
		if #elements > 4 then for i = 5, #elements do results = results[elements[i]] end end
		
		return (type(results) == "table") and results
	end
	
	local configTable = {["webMounts"] = {}, ["githubRepos"] = {}, ["clusters"] = {}, ["compressedFS"] = false}

	if fs.exists(".bbpack.cfg") then
		local file = rfs and rfs.open(".bbpack.cfg", "r") or fs.open(".bbpack.cfg", "r")
		local input = textutils.unserialize(file.readAll())
		file.close()
		
		if type(input) == "table" then
			if type(input.webMounts) == "table" then configTable.webMounts = input.webMounts end
			if type(input.githubRepos) == "table" then configTable.githubRepos = input.githubRepos end
			if type(input.clusters) == "table" then configTable.clusters = input.clusters end
			if type(input.compressedFS) == "boolean" then configTable.compressedFS = input.compressedFS end
		end
	end
	
	local webMountList, clusterList, repoList = configTable.webMounts, configTable.clusters, {}
	for path, url in pairs(configTable.githubRepos) do repoList[path] = getGithubRepo(url) end
	if next(clusterList) then for _, side in pairs(rs.getSides()) do if peripheral.getType(side) == "modem" then rednet.open(side) end end end
	local blacklist = {"bbpack", "bbpack.lua", "startup", "startup.lua", ".settings", ".gif", ".zip", ".bbpack.cfg"}

	if not _G.rfs then
		local rfs, ramdisk = {}, {}

		for key, value in pairs(fs) do rfs[key] = value end
		
		local function clusterTalk(cluster, answer, ...)
			local target, uuid, result, sender, msg = clusterList[cluster], math.random(1, 0x7FFFFFFF), {}

			for i = 1, #target do rednet.send(target[i], {["cluster"] = cluster, ["uuid"] = uuid, unpack(arg)}, rednet.host and cluster) end

			if answer then
				for i = 1, #target do
					repeat sender, msg = rednet.receive(rednet.host and cluster) until type(msg) == "table" and msg.cluster == cluster and msg.uuid == uuid
					result[i] = msg[1]
				end

				return result
			end
		end

		_G.fs.list = function(path)
			if type(path) ~= "string" then error("bad argument #1 (expected string, got " .. type(path) .. ")", 2 ) end
			
			path = fs.combine(path, "")
			local elements = dividePath(path)

			if not fs.isDir(path) then error("Not a directory", 2) end

			if fs.getDrive(path) == "hdd" then
				local results = rfs.list(path)

				for i = 1, #results do
					local thisResult = results[i]
					if thisResult:sub(-4) == ".bbp" then results[i] = thisResult:sub(1, -5) end
				end

				for mount in pairs(webMountList) do
					local mountElements = dividePath(mount)

					if #elements == #mountElements - 1 then
						local match = true

						for i = 1, #elements do if elements[i] ~= mountElements[i] then
							match = false
							break
						end end

						if match then results[#results + 1] = mountElements[#mountElements] end
					end
				end

				if path == "" then
					results[#results + 1] = "ram"
					for cluster in pairs(clusterList) do results[#results + 1] = cluster end
					for repo in pairs(repoList) do results[#results + 1] = repo end
				end

				table.sort(results)

				return results
			elseif clusterList[elements[1]] then
				local results = {}

				local lists = clusterTalk(elements[1], true, "list", path)
				for i = 1, #clusterList[elements[1]] do
					local subList = lists[i]

					for i = 1, #subList do
						local found, thisSub = false, subList[i]

						for j = 1, #results do if results[j] == thisSub then
							found = true
							break
						end end

						if not found then results[#results + 1] = thisSub end
					end
				end

				table.sort(results)

				return results
			elseif elements[1] == "ram" or repoList[elements[1]] then
				local cur, results = (elements[1] == "ram") and ramdisk or repoList[elements[1]], {}

				for i = 2, #elements do cur = cur[elements[i]] end

				for entry in pairs(cur) do results[#results + 1] = entry end

				table.sort(results)

				return results
			else
				return rfs.list(path)
			end
		end

		_G.fs.exists = function(path)
			if type(path) ~= "string" then error("bad argument #1 (expected string, got " .. type(path) .. ")", 2 ) end
			path = fs.combine(path, "")
			local elements = dividePath(path)

			if webMountList[path] then
				return true
			elseif clusterList[elements[1]] then
				if #elements == 1 then return true end
				local list = clusterTalk(elements[1], true, "exists", path)
				for i = 1, #list do if list[i] then return true end end
				return false
			elseif elements[1] == "ram" or repoList[elements[1]] then
				local cur = (elements[1] == "ram") and ramdisk or repoList[elements[1]]

				for i = 2, #elements do
					cur = cur[elements[i]]
					if not cur then return false end
				end

				return true
			else
				return rfs.exists(path..".bbp") or rfs.exists(path)
			end
		end

		_G.fs.isDir = function(path)
			if type(path) ~= "string" then error("bad argument #1 (expected string, got " .. type(path) .. ")", 2 ) end
			if not fs.exists(path) then return false end
			path = fs.combine(path, "")
			local elements = dividePath(path)

			if clusterList[elements[1]] then
				if #elements == 1 then return true end
				local list = clusterTalk(elements[1], true, "isDir", path)
				return list[1]
			elseif elements[1] == "ram" or repoList[elements[1]] then
				local cur = (elements[1] == "ram") and ramdisk or repoList[elements[1]]

				for i = 2, #elements do
					cur = cur[elements[i]]
				end

				return type(cur) == "table"
			else
				return rfs.isDir(path)
			end
		end

		_G.fs.isReadOnly = function(path)
			if type(path) ~= "string" then error("bad argument #1 (expected string, got " .. type(path) .. ")", 2 ) end
			path = fs.combine(path, "")
			local elements = dividePath(path)

			if webMountList[path] or repoList[elements[1]] then
				return true
			elseif clusterList[elements[1]] or elements[1] == "ram" then
				return false
			else
				return rfs.isReadOnly(rfs.exists(path..".bbp") and (path..".bbp") or path)
			end
		end

		_G.fs.getDrive = function(path)
			if type(path) ~= "string" then error("bad argument #1 (expected string, got " .. type(path) .. ")", 2 ) end
			path = fs.combine(path, "")
			local elements = dividePath(path)

			if clusterList[elements[1]] or elements[1] == "ram" or repoList[elements[1]] then
				return fs.exists(path) and elements[1]
			else
				return rfs.getDrive(rfs.exists(path..".bbp") and (path..".bbp") or path)
			end
		end

		_G.fs.getSize = function(path)
			if type(path) ~= "string" then error("bad argument #1 (expected string, got " .. type(path) .. ")", 2 ) end
			path = fs.combine(path, "")
			local elements = dividePath(path)

			if webMountList[path] or repoList[elements[1]] then
				return 0
			elseif clusterList[elements[1]] then
				if #elements == 1 then return 0 end
				if not fs.exists(path) then error("No such file", 2) end

				local size, list = 0, clusterTalk(elements[1], true, "getSize", path)
				for i = 1, #clusterList[elements[1]] do size = size + list[i] end
				return size
			elseif elements[1] == "ram" then
				local cur = ramdisk

				for i = 2, #elements do
					cur = cur[elements[i]]
					if not cur then error("No such file", 2) end
				end

				return type(cur) == "string" and #cur or 0
			else
				return rfs.getSize(rfs.exists(path..".bbp") and (path..".bbp") or path)
			end
		end

		_G.fs.getFreeSpace = function(path)
			if type(path) ~= "string" then error("bad argument #1 (expected string, got " .. type(path) .. ")", 2 ) end
			path = fs.combine(path, "")
			local elements = dividePath(path)

			if clusterList[elements[1]] then
				local size, list = 0, clusterTalk(elements[1], true, "getFreeSpace")
				for i = 1, #clusterList[elements[1]] do size = size + list[i][1] end
				return size
			elseif elements[1] == "ram" then
				return math.huge
			elseif repoList[elements[1]] then
				return 0
			else
				return rfs.getFreeSpace(path)
			end
		end

		_G.fs.makeDir = function(path)
			if type(path) ~= "string" then error("bad argument #1 (expected string, got " .. type(path) .. ")", 2 ) end
			path = fs.combine(path, "")
			local elements = dividePath(path)

			if fs.exists(path) then
				if fs.isDir(path) then
					return
				else
					error("File exists", 2)
				end
			end

			if clusterList[elements[1]] then
				clusterTalk(elements[1], false, "makeDir", path)
			elseif elements[1] == "ram" then
				local cur = ramdisk

				for i = 2, #elements do
					local next = cur[elements[i]]

					if next then
						cur = next
					else
						cur[elements[i]] = {}
						cur = cur[elements[i]]
					end
				end
			elseif repoList[elements[1]] then
				error("Access denied", 2)
			else
				return rfs.makeDir(path)
			end
		end

		_G.fs.move = function(path1, path2)
			if type(path1) ~= "string" then error("bad argument #1 (expected string, got " .. type(path1) .. ")", 2 ) end
			if type(path2) ~= "string" then error("bad argument #2 (expected string, got " .. type(path2) .. ")", 2 ) end
			path1, path2 = fs.combine(path1, ""), fs.combine(path2, "")

			if not fs.exists(path1) then error("No such file", 2) end
			if fs.exists(path2) then error("File exists", 2) end
			if fs.isReadOnly(path1) or fs.isReadOnly(path2) or (#dividePath(path1) == 1 and fs.getDrive(path1) ~= "hdd") then error("Access denied", 2) end
			if #dividePath(path1) < #dividePath(path2) and path2:sub(#path1) == path1 then error("Can't copy a directory inside itself", 2) end
			-- ... and if we run out of space we'll just let things fall over at the writing level.

			if fs.isDir(path1) then
				fs.makeDir(path2)
				local list = fs.list(path1)
				for i = 1, #list do fs.move(fs.combine(path1, list[i]), fs.combine(path2, list[i])) end
			else
				local input, output = fs.open(path1, "rb"), fs.open(path2, "wb")

				if input.readAll then
					output.write(input.readAll())
				else
					for byte in input.read do output.write(byte) end
				end

				input.close()
				output.close()
			end

			fs.delete(path1)
		end

		_G.fs.copy = function(path1, path2)
			if type(path1) ~= "string" then error("bad argument #1 (expected string, got " .. type(path1) .. ")", 2 ) end
			if type(path2) ~= "string" then error("bad argument #2 (expected string, got " .. type(path2) .. ")", 2 ) end
			path1, path2 = fs.combine(path1, ""), fs.combine(path2, "")

			if not fs.exists(path1) then error("No such file", 2) end
			if fs.exists(path2) then error("File exists", 2) end
			if fs.isReadOnly(path2) then error("Access denied", 2) end
			if #dividePath(path1) < #dividePath(path2) and path2:sub(#path1) == path1 then error("Can't copy a directory inside itself", 2) end
			-- ... and if we run out of space we'll just let things fall over at the writing level.

			if fs.isDir(path1) then
				fs.makeDir(path2)
				local list = fs.list(path1)
				for i = 1, #list do fs.copy(fs.combine(path1, list[i]), fs.combine(path2, list[i])) end
			else
				local input, output = fs.open(path1, "rb"), fs.open(path2, "wb")

				if input.readAll then
					output.write(input.readAll())
				else
					for byte in input.read do output.write(byte) end
				end

				input.close()
				output.close()
			end
		end

		_G.fs.delete = function(path)
			if type(path) ~= "string" then error("bad argument #1 (expected string, got " .. type(path) .. ")", 2 ) end
			path = fs.combine(path, "")
			local elements = dividePath(path)

			if not fs.exists(path) then return end

			if webMountList[path] then
				webMountList[path] = nil
				local file = rfs.open(".bbpack.cfg", "w")
				file.write(textutils.serialize(configTable))
				file.close()
			elseif clusterList[elements[1]] then
				if #elements == 1 then
					clusterList[elements[1]] = nil
					local file = rfs.open(".bbpack.cfg", "w")
					file.write(textutils.serialize(configTable))
					file.close()
				else
					clusterTalk(elements[1], false, "delete", path)
				end
			elseif repoList[elements[1]] then
				if #elements == 1 then
					repoList[elements[1]], configTable.githubRepos[elements[1]] = nil, nil
					local file = rfs.open(".bbpack.cfg", "w")
					file.write(textutils.serialize(configTable))
					file.close()
				else
					error("Access denied", 2)
				end
			elseif elements[1] == "ram" then
				if #elements == 1 then error("Access denied", 2) end

				local cur = ramdisk

				for i = 2, #elements - 1 do
					cur = cur[elements[i]]
					if not cur then return end
				end

				cur[elements[#elements]] = nil
			else
				if fs.isDir(path) then
					local list = fs.list(path)
					
					for i = 1, #list do
						local ok, err = pcall(fs.delete, fs.combine(path, list[i]))
						if not ok then error(err:gsub("pcall: ", ""), 2) end
					end
					
					return rfs.delete(path)
				else
					return rfs.delete(rfs.exists(path..".bbp") and (path..".bbp") or path)
				end
			end
		end

		_G.fs.open = function(path, mode)
			if type(path) ~= "string" then error("bad argument #1 (expected string, got " .. type(path) .. ")", 2 ) end
			if type(mode) ~= "string" then error("bad argument #2 (expected string, got " .. type(mode) .. ")", 2 ) end
			path = fs.combine(path, "")
			local elements = dividePath(path)

			mode = mode:lower()
			local binary, append, read, write = mode:find("b") ~= nil, mode:find("a") ~= nil, mode:find("r") ~= nil, mode:find("w") ~= nil

			if webMountList[path] or clusterList[elements[1]] or elements[1] == "ram" or repoList[elements[1]] then
				if not (append or write or read) then error("Invalid file mode: " .. mode, 2) end

				if read then
					if not fs.exists(path) or fs.isDir(path) then return nil, "No such file" end

					local data
					local handle = {["close"] = function() data = nil end}

					if webMountList[path] then
						local webHandle = http.get(webMountList[path], nil, term.setPaletteColour and true)
						data = webHandle.readAll()
						webHandle.close()
					elseif clusterList[elements[1]] then
						data = {}
						local list = clusterTalk(elements[1], true, "get", path)
						for i = 1, #clusterList[elements[1]] do if list[i] then data[list[i][1]] = list[i][2] end end
						data = table.concat(data)
						data = decompressInternal({data:byte(1, #data)}, true)
					elseif repoList[elements[1]] then
						data = repoList[elements[1]]
						for i = 2, #elements do data = data[elements[i]] end
						local webHandle = http.get(data, nil, term.setPaletteColour and true)
						data = webHandle.readAll()
						webHandle.close()
					else
						data = ramdisk
						for i = 2, #elements do data = data[elements[i]] end
					end

					if #data == 0 then data = nil end

					if binary then
						handle.read = function(amount)
							if not data then return nil end

							local result
							if type(amount) ~= "number" then
								result = data:byte(1)
								data = data:sub(2)
							else
								result = data:sub(1, amount)
								data = data:sub(amount + 1)
							end

							if #data == 0 then data = nil end
							return result
						end

						handle.readAll = function()
							if not data then return nil end

							local result = data
							data = nil
							return result
						end
					else
						handle.readLine = function()
							if not data then return nil end

							if data:find("\n") then
								local result = data:sub(1, data:find("\n") - 1)
								data = data:sub(data:find("\n") + 1)
								if data:byte(1) == 13 then data = data:sub(2) end
								return result
							else
								local result = data
								data = nil
								return data
							end
						end

						handle.readAll = function()
							if not data then return nil end

							local result = data
							data = nil
							return result
						end
					end

					return handle
				elseif write or append then
					if webMountList[path] or repoList[elements[1]] then return nil, "Access denied" end
					if fs.isDir(path) then return nil, "Cannot write to directory" end
					fs.makeDir(fs.getDir(path))

					local handle, output, counter = {}, {}, 1

					if binary then
						handle.write = function(data)
							if type(data) ~= "string" and type(data) ~= "number" then error("bad argument #1 (string or number expected, got " .. type(data) .. ")", 2) end
							output[counter] = type(data) == "number" and string.char(data) or data
							counter = counter + 1
						end
					else
						handle.write = function(data)
							output[counter] = tostring(data)
							counter = counter + 1
						end

						handle.writeLine = function(data)
							output[counter] = tostring(data)
							output[counter + 1] = "\n"
							counter = counter + 2
						end
					end

					local ramLink, ramIndex
					if clusterList[elements[1]] and append and fs.exists(path) then
						local data, list = {}, clusterTalk(elements[1], true, "get", path)
						for i = 1, #list do if list[i] then data[list[i][1]] = list[i][2] end end
						data = table.concat(data)
						output[1], counter = decompressInternal({data:byte(1, #data)}, true), 2
					elseif elements[1] == "ram" then
						ramLink = ramdisk
						for i = 2, #elements - 1 do ramLink = ramLink[elements[i]] end
						ramIndex = elements[#elements]
						if (append and not ramLink[ramIndex]) or not append then ramLink[ramIndex] = "" end
					end

					handle.flush = function()
						if clusterList[elements[1]] then
							output, counter = {table.concat(output)}, 1
							local data, segs, pos, totalSpace, thisCluster = string.char(unpack(compressInternal(output[1]))), 1, 1, fs.getFreeSpace(elements[1]), clusterList[elements[1]]
							if fs.exists(path) then totalSpace = totalSpace + fs.getSize(path) end
							if totalSpace < #data then error("Out of space", 2) end

							fs.delete(path)
							
							local spaceList = clusterTalk(elements[1], true, "getFreeSpace")
							
							for i = 1, #thisCluster do
								local thisSpace = spaceList[i][1]
								
								if thisSpace > 0 then
									local segString = tostring(segs)
									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])
									pos, segs = pos + thisSpace, segs + 1
								end

								if pos > #data then break end
							end
						else
							output = table.concat(output)
							if append then output = ramLink[ramIndex] .. output end
							ramLink[ramIndex] = output
							output, counter, append = {}, 1, true
						end
					end

					handle.close = function()
						handle.flush()
						for key in pairs(handle) do handle[key] = function() end end
					end

					return handle
				end
			else
				if (write or append) and rfs.isReadOnly(path) then return nil, "Access denied" end

				for i = 1, #blacklist do
					local check = blacklist[i]
					if path:sub(-#check):lower() == check then return rfs.open(path, mode) end
				end

				if read then
					return rfs.exists(path .. ".bbp") and open(path .. ".bbp", mode) or rfs.open(path, mode)
				elseif configTable.compressedFS then
					if rfs.getDrive(elements[1]) and rfs.getDrive(elements[1]) ~= "hdd" then
						return rfs.open(path, mode)
					elseif append then
						if rfs.exists(path) then
							local file, content = rfs.open(path, binary and "rb" or "r")

							if file.readAll then
								content = file.readAll()
							else
								content = {}
								for byte in file.read do content[#content + 1] = byte end
								content = string.char(unpack(content))
							end

							file.close()

							rfs.delete(path)

							file = open(path .. ".bbp", binary and "wb" or "w")
							file.write(content)
							return file
						else return open(path .. ".bbp", mode) end
					elseif write then
						if rfs.exists(path) then rfs.delete(path) end
						return open(path .. ".bbp", mode)
					end
				else
					if append then
						if rfs.exists(path .. ".bbp") then
							local file = open(path .. ".bbp", binary and "rb" or "r")
							local content = file.readAll()
							file.close()

							rfs.delete(path .. ".bbp")

							file = rfs.open(path, binary and "wb" or "w")

							if file.writeLine or term.setPaletteColour then
								file.write(content)
							else
								content = {string.byte(1, #content)}
								for i = 1, #content do file.write(content[i]) end
							end

							return file
						else return rfs.open(path, mode) end
					elseif write then
						if rfs.exists(path .. ".bbp") then rfs.delete(path .. ".bbp") end
						return rfs.open(path, mode)
					end
				end

				error("Unsupported mode", 2)
			end
		end

		_G.fs.find = function(path)
			if type(path) ~= "string" then error("bad argument #1 (expected string, got " .. type(path) .. ")", 2 ) end
			local pathParts, results, curfolder = {}, {}, "/"
			for part in path:gmatch("[^/]+") do pathParts[#pathParts + 1] = part:gsub("*", "[^/]*") end
			if #pathParts == 0 then return {} end

			local prospects = fs.list(curfolder)
			for i = 1, #prospects do prospects[i] = {["parent"] = curfolder, ["depth"] = 1, ["name"] = prospects[i]} end

			while #prospects > 0 do
				local thisProspect = table.remove(prospects, 1)
				local fullPath = fs.combine(thisProspect.parent, thisProspect.name)

				if thisProspect.name == thisProspect.name:match(pathParts[thisProspect.depth]) then
					if thisProspect.depth == #pathParts then
						results[#results + 1] = fullPath
					elseif fs.isDir(fullPath) and thisProspect.depth < #pathParts then
						local newList = fs.list(fullPath)
						for i = 1, #newList do prospects[#prospects + 1] = {["parent"] = fullPath, ["depth"] = thisProspect.depth + 1, ["name"] = newList[i]} end
					end
				end
			end

			return results
		end

		_G.rfs = rfs
	end

	update = bbpack and bbpack.update or function()
		for cluster, ids in pairs(clusterList) do for i = 1, #ids do
			rednet.send(ids[i], {["cluster"] = cluster, "update"}, rednet.host and cluster)
		end end

		local file = rfs.open("bbpack", "w")
		file.write(downloadPasteInternal("cUYTGbpb"))
		file.close()

		os.reboot()
	end

	fileSys = bbpack and bbpack.fileSys or function(par1, par2)
		if type(par1) == "boolean" or (type(par1) == "string" and type(par2) == "boolean") then
			-- Compress / decompress hdd contents.
			local list
			if type(par1) == "boolean" then
				list = rfs.list("")
				configTable.compressedFS = par1
			else
				list = {par1}
				par1 = par2
			end

			while #list > 0 do
				local entry = list[#list]
				list[#list] = nil

				if rfs.getDrive(entry) == "hdd" and not webMountList[entry] then
					if rfs.isDir(entry) then
						local newList, curLen = rfs.list(entry), #list
						for i = 1, #newList do list[curLen + i] = fs.combine(entry, newList[i]) end
					else
						local blacklisted = false

						for i = 1, #blacklist do
							local check = blacklist[i]
							if entry:sub(-#check):lower() == check then
								blacklisted = true
								break
							end
						end

						if not blacklisted then
							if par1 and entry:sub(-4) ~= ".bbp" then
								-- Compress this file.
								local file, content = rfs.open(entry, "rb")
								if file.readAll then
									content = file.readAll()
								else
									content = {}
									local counter = 1
									for byte in file.read do
										content[counter] = byte
										counter = counter + 1
									end
									content = string.char(unpack(content))
								end
								file.close()
								
								content = compressInternal(content)
								if rfs.getFreeSpace(entry) + rfs.getSize(entry) < #content then return false end
								rfs.delete(entry)
								snooze()

								file = rfs.open(entry .. ".bbp", "wb")

								if term.setPaletteColor then
									file.write(string.char(unpack(content)))
								else
									for i = 1, #content do file.write(content[i]) end
								end

								file.close()

								snooze()
							elseif not par1 and entry:sub(-4) == ".bbp" then
								-- Decompress this file.
								local file = open(entry, "rb")
								local content = file.readAll()
								file.close()

								if rfs.getFreeSpace(entry) + rfs.getSize(entry) < #content then return false end
								rfs.delete(entry)
								snooze()

								file = rfs.open(entry:sub(1, -5), "wb")

								if term.setPaletteColor then
									file.write(content)
								else
									content = {content:byte(1, #content)}
									for i = 1, #content do file.write(content[i]) end
								end

								file.close()

								snooze()
							end
						end
					end
				end
			end
		elseif type(par1) == "string" and type(par2) == "string" then
			-- New web mount.
			local url, path = par1, fs.combine(par2, "")
			local elements = dividePath(path)
			
			local repo = getGithubRepo(url)
			if repo then
				if #elements > 1 then error("bbpack.mount: Github repos must be mounted at the root of your file system", 2) end
				repoList[path] = repo
				configTable.githubRepos[path] = url
			else
				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

				local get = http.get(url)
				if not get then error("bbpack.mount: Can't connect to URL: "..url, 2) end
				get.close()

				webMountList[path] = url
			end
		elseif type(par1) == "string" then
			-- New cluster mount.
			local cluster, uuid = par1, math.random(1, 0x7FFFFFFF)
			for _, side in pairs(rs.getSides()) do if peripheral.getType(side) == "modem" then rednet.open(side) end end
			rednet.broadcast({["cluster"] = cluster, ["uuid"] = uuid, "rollcall"}, rednet.host and cluster)
			clusterList[cluster] = nil
			local myTimer, map = os.startTimer(5), {}

			while true do
				local event, par1, par2 = os.pullEvent()

				if event == "timer" and par1 == myTimer then
					break
				elseif event == "rednet_message" and type(par2) == "table" and par2.cluster == cluster and par2.uuid == uuid and par2[1] == "rollcallResponse" then
					map[#map + 1] = par1
				end
			end

			if #map == 0 then error("bbpack.mount: Can't connect to cluster: " .. cluster, 2) end
			clusterList[cluster] = map
		end

		local file = rfs.open(".bbpack.cfg", "w")
		file.write(textutils.serialize(configTable))
		file.close()

		return true
	end
end