Bmorr

temporary to get it to singleplayer

Dec 30th, 2020
1,045
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 32.86 KB | None | 0 0
  1. package.preload["argparse"] = function(...)
  2.   local function errorf(msg, ...)
  3.     error(msg:format(...), 0)
  4.   end
  5.   local function setter(arg, result, value)
  6.     result[arg.name] = value or true
  7.   end
  8.   local parser = { __name = "ArgParser" }
  9.   parser.__index = parser
  10.   function parser:add(names, arg)
  11.     if type(names) == "string" then names = { names } end
  12.     arg.names = names
  13.     for i = 1, #names do
  14.       local name = names[i]
  15.       if name:sub(1, 2) == "--" then self.options[name:sub(3)] = arg
  16.       elseif name:sub(1, 1) == "-" then self.flags[name:sub(2)] = arg
  17.       else self.arguments[#self.arguments + 1] = arg; arg.argument = true end
  18.     end
  19.     table.insert(self.list, #self.list, arg)
  20.     if arg.action == nil then arg.action = setter end
  21.     if arg.required == nil then arg.required = names[1]:sub(1, 1) ~= "-" end
  22.     if arg.name == nil then arg.name = names[1]:gsub("^-+", "") end
  23.     if arg.mvar == nil then arg.mvar = arg.name:upper() end
  24.   end
  25.   function parser:parse(...)
  26.     local args = table.pack(...)
  27.     local i, n = 1, #args
  28.     local arg_idx = 1
  29.     local result = {}
  30.     while i <= n do
  31.       local arg = args[i]
  32.       i = i + 1
  33.       if arg:find("^%-%-([^=]+)=(.+)$") then
  34.         local name, value = arg:match("^%-%-([^=]+)=(.+)$")
  35.         local arg = self.options[name]
  36.         if not arg then errorf("Unknown argument %q", name) end
  37.         if not arg.many and result[arg.name] ~= nil then errorf("%s has already been set", name) end
  38.         if not arg.value then errorf("%s does not accept a value", name) end
  39.         arg:action(result, value)
  40.       elseif arg:find("^%-%-(.*)$") then
  41.         local name = arg:match("^%-%-(.*)$")
  42.         local arg = self.options[name]
  43.         if not arg then errorf("Unknown argument %q", name) end
  44.         if not arg.many and result[arg.name] ~= nil then errorf("%s has already been set", name) end
  45.         if arg.value then
  46.           local value = args[i]
  47.           i = i + 1
  48.           if not value then errorf("%s needs a value", name) end
  49.           arg:action(result, value)
  50.         else
  51.           arg:action(result)
  52.         end
  53.       elseif arg:find("^%-(.+)$") then
  54.         local flags = arg:match("^%-(.+)$")
  55.         for j = 1, #flags do
  56.           local name = flags:sub(j, j)
  57.           local arg = self.flags[name]
  58.           if not arg then errorf("Unknown argument %q", name) end
  59.           if not arg.many and result[arg.name] ~= nil then errorf("%s has already been set", name) end
  60.           if arg.value then
  61.             local value
  62.             if j == #flags then
  63.               value = args[i]
  64.               i = i + 1
  65.             else
  66.               value = flags:sub(j + 1)
  67.             end
  68.             if not value then errorf("%s expects a value", name) end
  69.             arg:action(result, value)
  70.             break
  71.           else
  72.             arg:action(result)
  73.           end
  74.         end
  75.       else
  76.         local argument = self.arguments[arg_idx]
  77.         if argument then
  78.           argument:action(result, arg)
  79.           arg_idx = arg_idx + 1
  80.         else
  81.           errorf("Unexpected argument %q", arg)
  82.         end
  83.       end
  84.     end
  85.     for i = 1, #self.list do
  86.       local arg = self.list[i]
  87.       if arg and arg.required and result[arg.name] == nil then
  88.         errorf("%s is required (use -h to see usage)", arg.name)
  89.       end
  90.     end
  91.     return result
  92.   end
  93.   local function get_usage(arg)
  94.     local name
  95.     if arg.argument then name = arg.mvar
  96.     elseif arg.value then name = arg.names[1] .. "=" .. arg.mvar
  97.     else name = arg.names[1]
  98.     end
  99.     if #arg.names > 1 then name = name .. "," .. table.concat(arg.names, ",", 2) end
  100.     return name
  101.   end
  102.   local function create(prefix)
  103.     local parser = setmetatable({
  104.       options = {},
  105.       flags = {},
  106.       arguments = {},
  107.       list = {},
  108.     }, parser)
  109.     parser:add({ "-h", "--help", "-?" }, {
  110.       value = false, required = false,
  111.       doc = "Show this help message",
  112.       action = function()
  113.         if prefix then print(prefix) print() end
  114.         print("USAGE")
  115.         local max = 0
  116.         for i = 1, #parser.list do max = math.max(max, #get_usage(parser.list[i])) end
  117.         local format = " %-" .. max .. "s %s"
  118.         for i = 1, #parser.list do
  119.           local arg = parser.list[i]
  120.           print(format:format(get_usage(arg), arg.doc or ""))
  121.         end
  122.         error("", 0)
  123.       end,
  124.     })
  125.     return parser
  126.   end
  127.   local function is_help(cmd)
  128.     return cmd == "help" or cmd == "--help" or cmd == "-h" or cmd == "-?"
  129.   end
  130.   return { create = create, is_help = is_help }
  131. end
  132. package.preload["framebuffer"] = function(...)
  133.   local stringify = require("json").stringify
  134.   local colour_lookup = {}
  135.   for i = 0, 15 do
  136.     colour_lookup[2 ^ i] = string.format("%x", i)
  137.   end
  138.   local void = function() end
  139.   local function empty(colour, width, height)
  140.     local function is_colour() return colour end
  141.     return {
  142.       write = void, blit = void, clear = void, clearLine = void,
  143.       setCursorPos = void, setCursorBlink = void,
  144.       setPaletteColour = void, setPaletteColor = void,
  145.       setTextColour = void, setTextColor = void, setBackgroundColour = void, setBackgroundColor = void,
  146.       getTextColour = void, getTextColor = void, getBackgroundColour = void, getBackgroundColor = void,
  147.       scroll = void,
  148.       isColour = is_colour, isColor = is_colour,
  149.       getSize = function() return width, height end,
  150.       getPaletteColour = term.native().getPaletteColour, getPaletteColor = term.native().getPaletteColor,
  151.     }
  152.   end
  153.   local function buffer(original)
  154.     local text = {}
  155.     local text_colour = {}
  156.     local back_colour = {}
  157.     local palette = {}
  158.     local palette_24 = {}
  159.     local cursor_x, cursor_y = 1, 1
  160.     local cursor_blink = false
  161.     local cur_text_colour = "0"
  162.     local cur_back_colour = "f"
  163.     local sizeX, sizeY = original.getSize()
  164.     local color = original.isColor()
  165.     local dirty = false
  166.     local redirect = {}
  167.     if original.getPaletteColour then
  168.       for i = 0, 15 do
  169.         local c = 2 ^ i
  170.         palette[c] = { original.getPaletteColour( c ) }
  171.         palette_24[colour_lookup[c]] = colours.rgb8(original.getPaletteColour( c ))
  172.       end
  173.     end
  174.     function redirect.write(writeText)
  175.       writeText = tostring(writeText)
  176.       original.write(writeText)
  177.       dirty = true
  178.       if cursor_y > sizeY or cursor_y < 1 or cursor_x + #writeText <= 1 or cursor_x > sizeX then
  179.         cursor_x = cursor_x + #writeText
  180.         return
  181.       end
  182.       if cursor_x < 1 then
  183.         writeText = writeText:sub(-cursor_x + 2)
  184.         cursor_x = 1
  185.       elseif cursor_x + #writeText > sizeX then
  186.         writeText = writeText:sub(1, sizeX - cursor_x + 1)
  187.       end
  188.       local lineText = text[cursor_y]
  189.       local lineColor = text_colour[cursor_y]
  190.       local lineBack = back_colour[cursor_y]
  191.       local preStop = cursor_x - 1
  192.       local preStart = math.min(1, preStop)
  193.       local postStart = cursor_x + #writeText
  194.       local postStop = sizeX
  195.       local sub, rep = string.sub, string.rep
  196.       text[cursor_y] = sub(lineText, preStart, preStop)..writeText..sub(lineText, postStart, postStop)
  197.       text_colour[cursor_y] = sub(lineColor, preStart, preStop)..rep(cur_text_colour, #writeText)..sub(lineColor, postStart, postStop)
  198.       back_colour[cursor_y] = sub(lineBack, preStart, preStop)..rep(cur_back_colour, #writeText)..sub(lineBack, postStart, postStop)
  199.       cursor_x = cursor_x + #writeText
  200.     end
  201.     function redirect.blit(writeText, writeFore, writeBack)
  202.       original.blit(writeText, writeFore, writeBack)
  203.       dirty = true
  204.       if cursor_y > sizeY or cursor_y < 1 or cursor_x + #writeText <= 1 or cursor_x > sizeX then
  205.         cursor_x = cursor_x + #writeText
  206.         return
  207.       end
  208.       if cursor_x < 1 then
  209.         writeText = writeText:sub(-cursor_x + 2)
  210.         writeFore = writeFore:sub(-cursor_x + 2)
  211.         writeBack = writeBack:sub(-cursor_x + 2)
  212.         cursor_x = 1
  213.       elseif cursor_x + #writeText > sizeX then
  214.         writeText = writeText:sub(1, sizeX - cursor_x + 1)
  215.         writeFore = writeFore:sub(1, sizeX - cursor_x + 1)
  216.         writeBack = writeBack:sub(1, sizeX - cursor_x + 1)
  217.       end
  218.       local lineText = text[cursor_y]
  219.       local lineColor = text_colour[cursor_y]
  220.       local lineBack = back_colour[cursor_y]
  221.       local preStop = cursor_x - 1
  222.       local preStart = math.min(1, preStop)
  223.       local postStart = cursor_x + #writeText
  224.       local postStop = sizeX
  225.       local sub = string.sub
  226.       text[cursor_y] = sub(lineText, preStart, preStop)..writeText..sub(lineText, postStart, postStop)
  227.       text_colour[cursor_y] = sub(lineColor, preStart, preStop)..writeFore..sub(lineColor, postStart, postStop)
  228.       back_colour[cursor_y] = sub(lineBack, preStart, preStop)..writeBack..sub(lineBack, postStart, postStop)
  229.       cursor_x = cursor_x + #writeText
  230.     end
  231.     function redirect.clear()
  232.       for i = 1, sizeY do
  233.         text[i] = string.rep(" ", sizeX)
  234.         text_colour[i] = string.rep(cur_text_colour, sizeX)
  235.         back_colour[i] = string.rep(cur_back_colour, sizeX)
  236.       end
  237.       dirty = true
  238.       return original.clear()
  239.     end
  240.     function redirect.clearLine()
  241.       if cursor_y > sizeY or cursor_y < 1 then
  242.         return
  243.       end
  244.       text[cursor_y] = string.rep(" ", sizeX)
  245.       text_colour[cursor_y] = string.rep(cur_text_colour, sizeX)
  246.       back_colour[cursor_y] = string.rep(cur_back_colour, sizeX)
  247.       dirty = true
  248.       return original.clearLine()
  249.     end
  250.     function redirect.getCursorPos()
  251.       return cursor_x, cursor_y
  252.     end
  253.     function redirect.setCursorPos(x, y)
  254.       if type(x) ~= "number" then error("bad argument #1 (expected number, got " .. type(x) .. ")", 2) end
  255.       if type(y) ~= "number" then error("bad argument #2 (expected number, got " .. type(y) .. ")", 2) end
  256.       if x ~= cursor_x or y ~= cursor_y then
  257.         cursor_x = math.floor(x)
  258.         cursor_y = math.floor(y)
  259.         dirty = true
  260.       end
  261.       return original.setCursorPos(x, y)
  262.     end
  263.     function redirect.setCursorBlink(b)
  264.       if type(b) ~= "boolean" then error("bad argument #1 (expected boolean, got " .. type(b) .. ")", 2) end
  265.       if cursor_blink ~= b then
  266.         cursor_blink = b
  267.         dirty = true
  268.       end
  269.       return original.setCursorBlink(b)
  270.     end
  271.     function redirect.getSize()
  272.       return sizeX, sizeY
  273.     end
  274.     function redirect.scroll(n)
  275.       if type(n) ~= "number" then error("bad argument #1 (expected number, got " .. type(n) .. ")", 2) end
  276.       local empty_text = string.rep(" ", sizeX)
  277.       local empty_text_colour = string.rep(cur_text_colour, sizeX)
  278.       local empty_back_colour = string.rep(cur_back_colour, sizeX)
  279.       if n > 0 then
  280.         for i = 1, sizeY do
  281.           text[i] = text[i + n] or empty_text
  282.           text_colour[i] = text_colour[i + n] or empty_text_colour
  283.           back_colour[i] = back_colour[i + n] or empty_back_colour
  284.         end
  285.       elseif n < 0 then
  286.         for i = sizeY, 1, -1 do
  287.           text[i] = text[i + n] or empty_text
  288.           text_colour[i] = text_colour[i + n] or empty_text_colour
  289.           back_colour[i] = back_colour[i + n] or empty_back_colour
  290.         end
  291.       end
  292.       dirty = true
  293.       return original.scroll(n)
  294.     end
  295.     function redirect.setTextColour(clr)
  296.       if type(clr) ~= "number" then error("bad argument #1 (expected number, got " .. type(clr) .. ")", 2) end
  297.       local new_colour = colour_lookup[clr] or error("Invalid colour (got " .. clr .. ")" , 2)
  298.       if new_colour ~= cur_text_colour then
  299.         dirty = true
  300.         cur_text_colour = new_colour
  301.       end
  302.       return original.setTextColour(clr)
  303.     end
  304.     redirect.setTextColor = redirect.setTextColour
  305.     function redirect.setBackgroundColour(clr)
  306.       if type(clr) ~= "number" then error("bad argument #1 (expected number, got " .. type(clr) .. ")", 2) end
  307.       local new_colour = colour_lookup[clr] or error("Invalid colour (got " .. clr .. ")" , 2)
  308.       if new_colour ~= cur_back_colour then
  309.         dirty = true
  310.         cur_back_colour = new_colour
  311.       end
  312.       return original.setBackgroundColour(clr)
  313.     end
  314.     redirect.setBackgroundColor = redirect.setBackgroundColour
  315.     function redirect.isColour()
  316.       return color == true
  317.     end
  318.     redirect.isColor = redirect.isColour
  319.     function redirect.getTextColour()
  320.       return 2 ^ tonumber(cur_text_colour, 16)
  321.     end
  322.     redirect.getTextColor = redirect.getTextColour
  323.     function redirect.getBackgroundColour()
  324.       return 2 ^ tonumber(cur_back_colour, 16)
  325.     end
  326.     redirect.getBackgroundColor = redirect.getBackgroundColour
  327.     if original.getPaletteColour then
  328.       function redirect.setPaletteColour(colour, r, g, b)
  329.         local palcol = palette[colour]
  330.         if not palcol then error("Invalid colour (got " .. tostring(colour) .. ")", 2) end
  331.         if type(r) == "number" and g == nil and b == nil then
  332.             palcol[1], palcol[2], palcol[3] = colours.rgb8(r)
  333.             palette_24[colour] = r
  334.         else
  335.             if type(r) ~= "number" then error("bad argument #2 (expected number, got " .. type(r) .. ")", 2) end
  336.             if type(g) ~= "number" then error("bad argument #3 (expected number, got " .. type(g) .. ")", 2) end
  337.             if type(b) ~= "number" then error("bad argument #4 (expected number, got " .. type(b ) .. ")", 2 ) end
  338.             palcol[1], palcol[2], palcol[3] = r, g, b
  339.             palette_24[colour_lookup[colour]] = colours.rgb8(r, g, b)
  340.         end
  341.         dirty = true
  342.         return original.setPaletteColour(colour, r, g, b)
  343.       end
  344.       redirect.setPaletteColor = redirect.setPaletteColour
  345.       function redirect.getPaletteColour(colour)
  346.         local palcol = palette[colour]
  347.         if not palcol then error("Invalid colour (got " .. tostring(colour) .. ")", 2) end
  348.         return palcol[1], palcol[2], palcol[3]
  349.       end
  350.       redirect.getPaletteColor = redirect.getPaletteColour
  351.     end
  352.     function redirect.is_dirty() return dirty end
  353.     function redirect.clear_dirty() dirty = false end
  354.     function redirect.serialise()
  355.       return stringify {
  356.         packet = 0x10,
  357.         width = sizeX, height = sizeY,
  358.         cursorX = cursor_x, cursorY = cursor_y, cursorBlink = cursor_blink,
  359.         curFore = cur_text_colour, curBack = cur_back_colour,
  360.         palette = palette_24,
  361.         text = text, fore = text_colour, back = back_colour
  362.       }
  363.     end
  364.     redirect.setCursorPos(1, 1)
  365.     redirect.setBackgroundColor(colours.black)
  366.     redirect.setTextColor(colours.white)
  367.     redirect.clear()
  368.     return redirect
  369.   end
  370.   return { buffer = buffer, empty = empty }
  371. end
  372. package.preload["encode"] = function(...)
  373.   local function fletcher_32(str)
  374.     local s1, s2, byte = 0, 0, string.byte
  375.     if #str % 2 ~= 0 then str = str .. "\0" end
  376.     for i = 1, #str, 2 do
  377.       local c1, c2 = byte(str, i, i + 1)
  378.       s1 = (s1 + c1 + (c2 * 0x100)) % 0xFFFF
  379.       s2 = (s2 + s1) % 0xFFFF
  380.     end
  381.     return s2 * 0x10000 + s1
  382.   end
  383.   return {
  384.     fletcher_32 = fletcher_32
  385.   }
  386. end
  387. package.preload["json"] = function(...)
  388.   local tonumber = tonumber
  389.   local function skip_delim(str, pos, delim, err_if_missing)
  390.     pos = pos + #str:match('^%s*', pos)
  391.     if str:sub(pos, pos) ~= delim then
  392.       if err_if_missing then error('Expected ' .. delim) end
  393.       return pos, false
  394.     end
  395.     return pos + 1, true
  396.   end
  397.   local esc_map = { b = '\b', f = '\f', n = '\n', r = '\r', t = '\t' }
  398.   local function parse_str_val(str, pos)
  399.     local out, n = {}, 0
  400.     if pos > #str then error("Malformed JSON (in string)") end
  401.     while true do
  402.       local c = str:sub(pos, pos)
  403.       if c == '"' then return table.concat(out, "", 1, n), pos + 1 end
  404.       n = n + 1
  405.       if c == '\\' then
  406.         local nextc = str:sub(pos + 1, pos + 1)
  407.         if not nextc then error("Malformed JSON (in string)") end
  408.         if nextc == "u" then
  409.           local num = tonumber(str:sub(pos + 2, pos + 5), 16)
  410.           if not num then error("Malformed JSON (in unicode string) ") end
  411.           if num <= 255 then
  412.             pos, out[n] = pos + 6, string.char(num)
  413.           else
  414.             pos, out[n] = pos + 6, "?"
  415.           end
  416.         else
  417.           pos, out[n] = pos + 2, esc_map[nextc] or nextc
  418.         end
  419.       else
  420.         pos, out[n] = pos + 1, c
  421.       end
  422.     end
  423.   end
  424.   local function parse_num_val(str, pos)
  425.     local num_str = str:match('^-?%d+%.?%d*[eE]?[+-]?%d*', pos)
  426.     local val = tonumber(num_str)
  427.     if not val then error('Error parsing number at position ' .. pos .. '.') end
  428.     return val, pos + #num_str
  429.   end
  430.   local null = {}
  431.   local literals = {['true'] = true, ['false'] = false, ['null'] = null }
  432.   local escapes = {}
  433.   for i = 0, 255 do
  434.     local c = string.char(i)
  435.     if i >= 32 and i <= 126
  436.     then escapes[c] = c
  437.     else escapes[c] = ("\\u00%02x"):format(i)
  438.     end
  439.   end
  440.   escapes["\t"], escapes["\n"], escapes["\r"], escapes["\""], escapes["\\"] = "\\t", "\\n", "\\r", "\\\"", "\\\\"
  441.   local function parse(str, pos, end_delim)
  442.     pos = pos or 1
  443.     if pos > #str then error('Reached unexpected end of input.') end
  444.     local pos = pos + #str:match('^%s*', pos)
  445.     local first = str:sub(pos, pos)
  446.     if first == '{' then
  447.       local obj, key, delim_found = {}, true, true
  448.       pos = pos + 1
  449.       while true do
  450.         key, pos = parse(str, pos, '}')
  451.         if key == nil then return obj, pos end
  452.         if not delim_found then error('Comma missing between object items.') end
  453.         pos = skip_delim(str, pos, ':', true)
  454.         obj[key], pos = parse(str, pos)
  455.         pos, delim_found = skip_delim(str, pos, ',')
  456.       end
  457.     elseif first == '[' then
  458.       local arr, val, delim_found = {}, true, true
  459.       pos = pos + 1
  460.       while true do
  461.         val, pos = parse(str, pos, ']')
  462.         if val == nil then return arr, pos end
  463.         if not delim_found then error('Comma missing between array items.') end
  464.         arr[#arr + 1] = val
  465.         pos, delim_found = skip_delim(str, pos, ',')
  466.       end
  467.     elseif first == '"' then
  468.       return parse_str_val(str, pos + 1)
  469.     elseif first == '-' or first:match('%d') then
  470.       return parse_num_val(str, pos)
  471.     elseif first == end_delim then
  472.       return nil, pos + 1
  473.     else
  474.       for lit_str, lit_val in pairs(literals) do
  475.         local lit_end = pos + #lit_str - 1
  476.         if str:sub(pos, lit_end) == lit_str then return lit_val, lit_end + 1 end
  477.       end
  478.       local pos_info_str = 'position ' .. pos .. ': ' .. str:sub(pos, pos + 10)
  479.       error('Invalid json syntax starting at ' .. pos_info_str)
  480.     end
  481.   end
  482.   local format, gsub, tostring, pairs, next, type, concat
  483.       = string.format, string.gsub, tostring, pairs, next, type, table.concat
  484.   local function stringify_impl(t, out, n)
  485.     local ty = type(t)
  486.     if ty == "table" then
  487.       local first_ty = type(next(t))
  488.       if first_ty == "nil" then
  489.           out[n], n = "{}", n + 1
  490.           return n
  491.       elseif first_ty == "string" then
  492.         out[n], n = "{", n + 1
  493.         local first = true
  494.         for k, v in pairs(t) do
  495.           if first then first = false else out[n], n = ",", n + 1 end
  496.           out[n] = format("\"%s\":", k)
  497.           n = stringify_impl(v, out, n + 1)
  498.         end
  499.         out[n], n = "}", n + 1
  500.         return n
  501.       elseif first_ty == "number" then
  502.         out[n], n = "[", n + 1
  503.         for i = 1, #t do
  504.           if i > 1 then out[n], n = ",", n + 1 end
  505.           n = stringify_impl(t[i], out, n)
  506.         end
  507.         out[n], n = "]", n + 1
  508.         return n
  509.       else
  510.         error("Cannot serialize key " .. first_ty)
  511.       end
  512.     elseif ty == "string" then
  513.       if t:match("^[ -~]*$") then
  514.         out[n], n = gsub(format("%q", t), "\n", "n"), n + 1
  515.       else
  516.         out[n], n = "\"" .. gsub(t, ".", escapes) .. "\"", n + 1
  517.       end
  518.       return n
  519.     elseif ty == "number" or ty == "boolean" then
  520.       out[n],n  = tostring(t), n + 1
  521.       return n
  522.     else error("Cannot serialize type " .. ty)
  523.     end
  524.   end
  525.   local function stringify(object)
  526.     local buffer = {}
  527.     local n = stringify_impl(object, buffer, 1)
  528.     return concat(buffer, "", 1, n - 1)
  529.   end
  530.   local function try_parse(msg)
  531.     local ok, res = pcall(parse, msg)
  532.     if ok then return res else return nil, res end
  533.   end
  534.   return {
  535.     stringify = stringify,
  536.     try_parse = try_parse,
  537.     parse = parse,
  538.     null = null
  539.   }
  540. end
  541. local tonumber, type, keys = tonumber, type, keys
  542. local argparse = require "argparse"
  543. local framebuffer = require "framebuffer"
  544. local encode = require "encode"
  545. local json = require "json"
  546. if _G.cloud_catcher then
  547.   local usage = ([[
  548.   cloud: <subcommand> [args]
  549.   Communicate with the cloud-catcher session.
  550.   Subcommands:
  551.     edit <file> Open a file on the remote server.
  552.     token       Display the token for this
  553.                 connection.
  554.   ]]):gsub("^%s+", ""):gsub("%s+$", ""):gsub("\n  ", "\n")
  555.   local subcommand = ...
  556.   if subcommand == "edit" or subcommand == "e" then
  557.     local arguments = argparse.create("cloud edit: Edit a file in the remote viewer")
  558.     arguments:add({ "file" }, { doc = "The file to upload", required = true })
  559.     local result = arguments:parse(select(2, ...))
  560.     local file = result.file
  561.     local resolved = shell.resolve(file)
  562.     if not fs.exists(resolved) and not resolved:find("%.") then
  563.       local extension = settings.get("edit.default_extension", "")
  564.       if extension ~= "" and type(extension) == "string" then
  565.           resolved = resolved .. "." .. extension
  566.       end
  567.     end
  568.     if fs.isDir(resolved) then error(("%q is a directory"):format(file), 0) end
  569.     if fs.isReadOnly(resolved) then
  570.       if fs.exists(resolved) then
  571.         print(("%q is read only, will not be able to modify"):format(file))
  572.       else
  573.         error(("%q does not exist"):format(file), 0)
  574.       end
  575.     end
  576.     local ok, err = _G.cloud_catcher.edit(resolved)
  577.     if not ok then error(err, 0) end
  578.   elseif subcommand == "token" or subcommand == "t" then
  579.     print(_G.cloud_catcher.token())
  580.   elseif argparse.is_help(subcommand) then
  581.     print(usage)
  582.   elseif subcommand == nil then
  583.     printError(usage)
  584.     error()
  585.   else
  586.     error(("%q is not a cloud catcher subcommand, run with --h for more info"):format(subcommand), 0)
  587.   end
  588.   return
  589. end
  590. local current_path = shell.getRunningProgram()
  591. local current_name = fs.getName(current_path)
  592. local arguments = argparse.create(current_name .. ": Interact with this computer remotely")
  593. arguments:add({ "token" }, { doc = "The token to use when connecting" })
  594. arguments:add({ "--term", "-t" }, { value = true, doc = "Terminal dimensions or none to hide" })
  595. arguments:add({ "--dir",  "-d" }, { value = true, doc = "The directory to sync to. Defaults to the current one." })
  596. arguments:add({ "--http", "-H" }, { value = false, doc = "Use HTTP instead of HTTPs" })
  597. local args = arguments:parse(...)
  598. local token = args.token
  599. if #token ~= 32 or token:find("[^%a%d]") then
  600.   error("Invalid token (must be 32 alpha-numeric characters)", 0)
  601. end
  602. local capabilities = {}
  603. local term_opts = args.term
  604. local previous_term, parent_term = term.current()
  605. if term_opts == nil then
  606.   parent_term = previous_term
  607. else if term_opts == "none" then
  608.   parent_term = nil
  609. elseif term_opts == "hide" then
  610.   parent_term = framebuffer.empty(true, term.getSize())
  611. elseif term_opts:find("^(%d+)x(%d+)$") then
  612.   local w, h = term_opts:match("^(%d+)x(%d+)$")
  613.   if w == 0 or h == 0 then error("Terminal cannot have 0 size", 0) end
  614.   parent_term = framebuffer.empty(true, tonumber(w), tonumber(h))
  615. else
  616.     error("Unknown format for term: expected \"none\", \"hide\" or \"wxh\"", 0)
  617.   end
  618. end
  619. if parent_term then
  620.   table.insert(capabilities, "terminal:host")
  621.   local w, h = parent_term.getSize()
  622.   if w * h > 5000 then error("Terminal is too large to handle", 0) end
  623. end
  624. local sync_dir = shell.resolve(args.dir or "./")
  625. if not fs.isDir(sync_dir) then error(("%q is not a directory"):format(sync_dir), 0) end
  626. table.insert(capabilities, "file:host")
  627. local url = ("%s://cloud-catcher.squiddev.cc/connect?id=%s&capabilities=%s"):format(
  628.   args.http and "ws" or "wss", token, table.concat(capabilities, ","))
  629. local remote, err = http.websocket(url)
  630. if not remote then error("Cannot connect to cloud-catcher server: " .. err, 0) end
  631. local server_term, server_file_edit, server_file_host = false, false, false
  632. do
  633.   local max_packet_size = 16384
  634.   _G.cloud_catcher = {
  635.     token = function() return token end,
  636.     edit = function(file, force)
  637.       if not server_file_edit then
  638.         return false, "There are no editors connected"
  639.       end
  640.       local contents, exists
  641.       local handle = fs.open(file, "rb")
  642.       if handle then
  643.         contents = handle.readAll()
  644.         handle.close()
  645.         exists = true
  646.       else
  647.         contents = ""
  648.         exists = false
  649.       end
  650.       if #file + #contents + 5 > max_packet_size then
  651.         return false, "This file is too large to be edited remotely"
  652.       end
  653.       local check = encode.fletcher_32(contents)
  654.       local flag = 0x04
  655.       if fs.isReadOnly(file) then flag = flag + 0x01 end
  656.       if not exists then flag = flag + 0x08 end
  657.       remote.send(json.stringify {
  658.         packet = 0x22,
  659.         id = 0,
  660.         actions = {
  661.           { file = file, checksum = check, flags = flag, action = 0, contents = contents }
  662.         }
  663.       })
  664.       return true
  665.     end
  666.   }
  667.   shell.setAlias("cloud", "/" .. current_path)
  668.   local function complete_multi(text, options)
  669.     local results = {}
  670.     for i = 1, #options do
  671.         local option, add_spaces = options[i][1], options[i][2]
  672.         if #option + (add_spaces and 1 or 0) > #text and option:sub(1, #text) == text then
  673.             local result = option:sub(#text + 1)
  674.             if add_spaces then table.insert( results, result .. " " )
  675.             else table.insert( results, result )
  676.             end
  677.         end
  678.     end
  679.     return results
  680.   end
  681.   local subcommands = { { "edit", true }, { "token", false } }
  682.   shell.setCompletionFunction(current_path, function(shell, index, text, previous_text)
  683.     if _G.cloud_catcher == nil then return end
  684.     if index == 1 then
  685.       return complete_multi(text, subcommands)
  686.     elseif index == 2 and previous_text[2] == "edit" then
  687.         return fs.complete(text, shell.dir(), true, false)
  688.     end
  689.   end)
  690. end
  691. local co, buffer
  692. if parent_term ~= nil then
  693.   buffer = framebuffer.buffer(parent_term)
  694.   co = coroutine.create(shell.run)
  695.   term.redirect(buffer)
  696. end
  697. local info_dirty, last_label, get_label = true, nil, os.getComputerLabel
  698. local function send_info()
  699.   last_label = get_label()
  700.   info_dirty = false
  701.   remote.send(json.stringify {
  702.     packet = 0x12,
  703.     id = os.getComputerID(),
  704.     label = last_label,
  705.   })
  706. end
  707. local ok, res = true
  708. if co then ok, res = coroutine.resume(co, "shell") end
  709. local last_change, last_timer = os.clock(), nil
  710. local pending_events, pending_n = {}, 0
  711. while ok and (not co or coroutine.status(co) ~= "dead") do
  712.   if not info_dirty and last_label ~= get_label() then info_dirty = true end
  713.   if server_term and last_timer == nil and (buffer.is_dirty() or info_dirty) then
  714.     local now = os.clock()
  715.     if now - last_change < 0.04 then
  716.       last_timer = os.startTimer(0)
  717.     else
  718.       last_change = os.clock()
  719.       if buffer.is_dirty() then
  720.         remote.send(buffer.serialise())
  721.         buffer.clear_dirty()
  722.       end
  723.       if info_dirty then send_info() end
  724.     end
  725.   end
  726.   local event
  727.   if pending_n >= 1 then
  728.     event = table.remove(pending_events, 1)
  729.     pending_n = pending_n - 1
  730.   else
  731.     event = table.pack(coroutine.yield())
  732.   end
  733.   if event[1] == "timer" and event[2] == last_timer then
  734.     last_timer = nil
  735.     if server_term then
  736.       last_change = os.clock()
  737.       if buffer.is_dirty() then remote.send(buffer.serialise()) buffer.clear_dirty() end
  738.       if info_dirty then send_info() end
  739.     end
  740.   elseif event[1] == "websocket_closed" and event[2] == url then
  741.     ok, res = false, "Connection lost"
  742.     remote = nil
  743.   elseif event[1] == "websocket_message" and event[2] == url then
  744.     local packet = json.try_parse(event[3])
  745.     local code = packet and packet.packet
  746.     if type(code) ~= "number" then code = - 1 end
  747.     if code >= 0x00 and code < 0x10 then
  748.       if code == 0x00 then -- ConnectionUpdate
  749.         server_term, server_file_edit, server_file_host = false, false, false
  750.         for _, cap in ipairs(packet.capabilities) do
  751.           if cap == "terminal:view" and buffer ~= nil then
  752.             server_term = true
  753.             remote.send(buffer.serialise()) buffer.clear_dirty()
  754.             send_info()
  755.             last_change = os.clock()
  756.           elseif cap == "file:host" then
  757.             server_file_host = true
  758.           elseif cap == "file:edit" then
  759.             server_file_edit = true
  760.           end
  761.         end
  762.       elseif code == 0x02 then -- ConnectionPing
  763.         remote.send([[{"packet":2}]])
  764.       end
  765.     elseif server_term and code >= 0x10 and code < 0x20 then
  766.       if code == 0x11 then -- TerminalEvents
  767.         for _, event in ipairs(packet.events) do
  768.           pending_n = pending_n + 1
  769.           if event.name == "cloud_catcher_key" then
  770.             local key = keys[event.args[1]]
  771.             if type(key) == "number" then pending_events[pending_n] = { n = 3, "key", key, event.args[2] } end
  772.           elseif event.name == "cloud_catcher_key_up" then
  773.               local key = keys[event.args[1]]
  774.               if type(key) == "number" then pending_events[pending_n] = { n = 2, "key_up", key } end
  775.           else
  776.             pending_events[pending_n] = table.pack(event.name, table.unpack(event.args))
  777.           end
  778.         end
  779.       end
  780.     elseif code >= 0x20 and code < 0x30 then
  781.       if code == 0x22 then -- FileAction
  782.         local result = {}
  783.         for i, action in pairs(packet.actions) do
  784.           local ok = bit32.band(action.flags, 0x1) == 1
  785.           local expected_checksum = 0
  786.           local handle = fs.open(action.file, "rb")
  787.           if handle then
  788.             local contents = handle.readAll()
  789.             handle.close()
  790.             expected_checksum = encode.fletcher_32(contents)
  791.           end
  792.           if not ok then
  793.             ok = expected_checksum == 0 or action.checksum == expected_checksum
  794.           end
  795.           if not ok then
  796.             result[i] = { file = action.file, checksum = expected_checksum, result = 2 }
  797.           elseif action.action == 0x0 then -- Replace
  798.             handle = fs.open(action.file, "wb")
  799.             if handle then
  800.               handle.write(action.contents)
  801.               handle.close()
  802.               result[i] = { file = action.file, checksum = encode.fletcher_32(action.contents), result = 1 }
  803.             else
  804.               result[i] = { file = action.file, checksum = expected_checksum, result = 3 }
  805.             end
  806.           elseif action.action == 0x1 then -- Patch
  807.             handle = fs.open(action.file, "rb")
  808.             if handle then
  809.               local out, n = {}, 0
  810.               for _, delta in pairs(action.delta) do
  811.                 if delta.kind == 0 then -- Same
  812.                   n = n + 1
  813.                   out[n] = handle.read(delta.length)
  814.                 elseif delta.kind == 1 then -- Added
  815.                   n = n + 1
  816.                   out[n] = delta.contents
  817.                 elseif delta.kind == 2 then -- Removed
  818.                   handle.read(delta.length)
  819.                 end
  820.               end
  821.               handle.close()
  822.               handle = fs.open(action.file, "wb")
  823.               if handle then
  824.                 local contents = table.concat(out)
  825.                 handle.write(contents)
  826.                 handle.close()
  827.                 result[i] = { file = action.file, checksum = encode.fletcher_32(contents), result = 1 }
  828.               else
  829.                 result[i] = { file = action.file, checksum = expected_checksum, result = 3 }
  830.               end
  831.             else
  832.               result[i] = { file = action.file, checksum = expected_checksum, result = 2 }
  833.             end
  834.           elseif action.action == 0x02 then -- Delete
  835.             local ok = fs.delete(action.file)
  836.             result[i] = { file = action.file, checksum = action.checksum, result = ok and 1 or 3 }
  837.           end
  838.         end
  839.         remote.send(json.stringify {
  840.           packet = 0x23,
  841.           id = packet.id,
  842.           files = result,
  843.         })
  844.       end
  845.     end
  846.   elseif res == nil or event[1] == res or event[1] == "terminate" then
  847.     if co then
  848.       ok, res = coroutine.resume(co, table.unpack(event, 1, event.n))
  849.     elseif event[1] == "terminate" then
  850.       ok, res = false, "Terminated"
  851.     end
  852.   end
  853. end
  854. term.redirect(previous_term)
  855. if previous_term == parent_term then
  856.   term.clear()
  857.   term.setCursorPos(1, 1)
  858.   if previous_term.endPrivateMode then previous_term.endPrivateMode() end
  859. end
  860. _G.cloud_catcher = nil
  861. shell.clearAlias("cloud")
  862. shell.getCompletionInfo()[current_path] = nil
  863. if remote ~= nil then remote.close() end
  864. if not ok then error(res, 0) end
  865.  
Advertisement
Add Comment
Please, Sign In to add comment