ComputerCraft Archive

== gtRemote == (GUI Turtle Remote)

pocket networking unknown forum

Description

Here's my take on a turtle remote for ComputerCraft 1.63+ advanced wireless pocket computers. - Turtles running the host program will show up in the remote list automatically when in range and drop of...

Installation

Copy one of these commands into your ComputerCraft terminal:

Pastebin:pastebin get XQQsRyLX ==_gtremote_==_(gui_turtle_remote)
wget:wget https://pastebin.com/raw/XQQsRyLX ==_gtremote_==_(gui_turtle_remote)
Archive:wget https://cc.shobie.xyz/cc/get/pb-XQQsRyLX ==_gtremote_==_(gui_turtle_remote)
Quick Install: wget https://cc.shobie.xyz/cc/get/pb-XQQsRyLX == gtRemote == (GUI Turtle Remote)

Usage

Run the program after downloading

Tags

networkingforumpocket-programs

Source

View Original Source

Code Preview

--[[     gtRemote      ]]--
--[[      by Dog       ]]--
--[[ aka HydrantHunter ]]--
--[[  with assistance  ]]--
--[[   from CometWolf  ]]--
--[[ pastebin XQQsRyLX ]]--
local gtRver = "2.0.05"
--[[
Tested with/requires:
  - Minecraft 1.6.4+
  - ComputerCraft 1.63+
    - 1 Advanced Wireless Pocket Computer
    - gtHost running on one or more Wireless Turtles (standard or advanced)

Special thanks to: Anavrins       (pbkdf2/sha256 hashing)
                   SquidDev       (AES encryption/decryption)
                   Alex Kloss     (base64 encoder/decoder)
]]--
--# AUTOMATIC/STATIC CONFIGURATION
--# Default Settings
local termX, termY = term.getSize()
local netSide, runState, tDirection, thisCC = "none", "init", "Forward", tostring(os.getComputerID())
local kernelState, timerDelay, inputting = true, false, false
local netReceive, drawCLI, drawHeader, turtleInventory, turtleControlScreen, userInput, pollTimer, tempState
--# Turtle List
local allTurtles, filteredTurtles = { }, { }
local numPages, pageNum = 1, 1
local fuelFilter, showFuel = false, false
local gtHost, thisTurtle
local colorBurst = {
  G = { order = 1, text = "Green", color = colors.green };
  S = { order = 2, text = "Sky", color = colors.lightBlue };
  B = { order = 3, text = "Blue", color = colors.blue };
  P = { order = 4, text = "Purple", color = colors.purple };
  R = { order = 5, text = "Red", color = colors.red };
  O = { order = 6, text = "Orange", color = colors.orange };
  N = { order = 7, text = "Brown", color = colors.brown };
  I = { order = 8, text = "Silver", color = colors.lightGray };
}
--# Color Definitions
local white = colors.white
local black = colors.black
local silver = colors.lightGray
local gray = colors.gray
local brown = colors.brown
local yellow = colors.yellow
local orange = colors.orange
local red = colors.red
local magenta = colors.magenta
local purple = colors.purple
local blue = colors.blue
local sky = colors.lightBlue
local cyan = colors.cyan
local lime = colors.lime
local green = colors.green
--# END AUTOMATIC/STATIC CONFIGURATION

-- Lua 5.1+ base64 v3.0 (c) 2009 by Alex Kloss <[email protected]>
-- licensed under the terms of the LGPL2
-- http://lua-users.org/wiki/BaseSixtyFour
-- character table string
local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
-- encoding
function encode(data)
  return ((data:gsub('.', function(x) 
    local r,b='',x:byte()
    for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end
    return r;
  end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
    if (#x < 6) then return '' end
    local c=0
    for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end
    return b:sub(c+1,c+1)
  end)..({ '', '==', '=' })[#data%3+1])
end
-- decoding
function decode(data)
  data = string.gsub(data, '[^'..b..'=]', '')
  return (data:gsub('.', function(x)
    if (x == '=') then return '' end
    local r,f='',(b:find(x)-1)
    for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end
    return r;
  end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
    if (#x ~= 8) then return '' end
    local c=0
    for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end
    return string.char(c)
  end))
end

-- AES Lua implementation by SquidDev
-- https://gist.github.com/SquidDev/86925e07cbabd70773e53d781bd8b2fe
local encrypt, decrypt
do
  local function _W(f) local e=setmetatable({}, {__index = _ENV or getfenv()}) if setfenv then setfenv(f, e) end return f(e) or e end
  local bit=_W(function(_ENV, ...)
  --[[
    This bit API is designed to cope with unsigned integers instead of normal integers
    To do this we add checks for overflows: (x > 2^31 ? x - 2 ^ 32 : x)
    These are written in long form because no constant folding.
  ]]
  local floor = math.floor
  local lshift, rshift

  rshift = function(a,disp)
    return floor(a % 4294967296 / 2^disp)
  end

  lshift = function(a,disp)
    return (a * 2^disp) % 4294967296
  end

  return {
    -- bit operations
    bnot = bit32 and bit32.bnot or bit.bnot,
    band = bit32 and bit32.band or bit.band,
    bor  = bit32 and bit32.bor or bit.bor,
    bxor = bit32 and bit32.bxor or bit.bxor,
    rshift = rshift,
    lshift = lshift,
  }
  end)

  local gf=_W(function(_ENV, ...)
  -- finite field with base 2 and modulo irreducible polynom x^8+x^4+x^3+x+1 = 0x11d
  local bxor = bit32 and bit32.bxor or bit.bxor
  local lshift = bit.lshift
  -- private data of gf
  local n = 0x100
  local ord = 0xff
  local irrPolynom = 0x11b
  local exp = {}
  local log = {}
  --
  -- add two polynoms (its simply xor)
  --
  local function add(operand1, operand2)
    return bxor(operand1,operand2)
  end
  --
  -- subtract two polynoms (same as addition)
  --
  local function sub(operand1, operand2)
    return bxor(operand1,operand2)
  end
  --
  -- inverts element
  -- a^(-1) = g^(order - log(a))
  --
  local function invert(operand)
    -- special case for 1
    if (operand == 1) then
      return 1
    end
    -- normal invert
    local exponent = ord - log[operand]
    return exp[exponent]
  end
  --
  -- multiply two elements using a logarithm table
  -- a*b = g^(log(a)+log(b))
  --
  local function mul(operand1, operand2)
    if (operand1 == 0 or operand2 == 0) then
      return 0
    end
    local exponent = log[operand1] + log[operand2]
    if (exponent >= ord) then
      exponent = exponent - ord
    end
    return exp[exponent]
  end
  --
  -- divide two elements
  -- a/b = g^(log(a)-log(b))
  --
  local function div(operand1, operand2)
    if (operand1 == 0)  then
      return 0
    end
    -- TODO: exception if operand2 == 0
    local exponent = log[operand1] - log[operand2]
    if (exponent < 0) then
      exponent = exponent + ord
    end
    return exp[exponent]
  end
  --
  -- print logarithmic table
  --
  local function printLog()
    for i = 1, n do
      print("log(", i-1, ")=", log[i-1])
    end
  end
  --
  -- print exponentiation table
  --
  local function printExp()
    for i = 1, n do
      print("exp(", i-1, ")=", exp[i-1])
    end
  end
  --
  -- calculate logarithmic and exponentiation table
  --
  local function initMulTable()
    local a = 1
    for i = 0,ord-1 do
      exp[i] = a
      log[a] = i
      -- multiply with generator x+1 -> left shift + 1
      a = bxor(lshift(a, 1), a)
      -- if a gets larger than order, reduce modulo irreducible polynom
      if a > ord then
        a = sub(a, irrPolynom)
      end
    end
  end

  initMulTable()

  return {
    add = add,
    sub = sub,
    invert = invert,
    mul = mul,
    div = div,
    printLog = printLog,
    printExp = printExp,
  }
  end)

  util=_W(function(_ENV, ...)
  -- Cache some bit operators
  local bxor = bit.bxor
  local rshift = bit.rshift
  local band = bit.band
  local lshift = bit.lshift
  local sleepCheckIn
  --
  -- calculate the parity of one byte
  --
  local function byteParity(byte)
    byte = bxor(byte, rshift(byte, 4))
    byte = bxor(byte, rshift(byte, 2))
    byte = bxor(byte, rshift(byte, 1))
    return band(byte, 1)
  end
  --
  -- get byte at position index
  --
  local function getByte(number, index)
    return index == 0 and band(number,0xff) or band(rshift(number, index*8),0xff)
  end
  --
  -- put number into int at position index
  --
  local function putByte(number, index)
    return index == 0 and band(number,0xff) or lshift(band(number,0xff),index*8)
  end
  --
  -- convert byte array to int array
  --
  local function bytesToInts(bytes, start, n)
    local ints = {}
    for i = 0, n - 1 do
      ints[i + 1] =
          putByte(bytes[start + (i*4)], 3) +
          putByte(bytes[start + (i*4) + 1], 2) +
          putByte(bytes[start + (i*4) + 2], 1) +
          putByte(bytes[start + (i*4) + 3], 0)
      if n % 10000 == 0 then sleepCheckIn() end
    end
    return ints
  end
  --
  -- convert int array to byte array
  --
  local function intsToBytes(ints, output, outputOffset, n)
    n = n or #ints
    for i = 0, n - 1 do
      for j = 0,3 do
        output[outputOffset + i*4 + (3 - j)] = getByte(ints[i + 1], j)
      end
      if n % 10000 == 0 then sleepCheckIn() end
    end
    return output
  end
  --
  -- convert bytes to hexString
  --
  local function bytesToHex(bytes)
    local hexBytes = ""
    for i,byte in ipairs(bytes) do
      hexBytes = hexBytes .. string.format("%02x ", byte)
    end
    return hexBytes
  end

  local function hexToBytes(bytes)
    local out = {}
    for i = 1, #bytes, 2 do
      out[#out + 1] = tonumber(bytes:sub(i, i + 1), 16)
    end
    return out
  end
  --
  -- convert data to hex string
  --
  local function toHexString(data)
    local type = type(data)
    if (type == "number") then
      return string.format("%08x",data)
    elseif (type == "table") then
      return bytesToHex(data)
    elseif (type == "string") then
      local bytes = {string.byte(data, 1, #data)}
      return bytesToHex(bytes)
    else
      return data
    end
  end

  local function padByteString(data)
    local dataLength = #data
    local random1 = math.random(0,255)
    local random2 = math.random(0,255)
    local prefix = string.char(random1,
      random2,
      random1,
      random2,
      getByte(dataLength, 3),
      getByte(dataLength, 2),
      getByte(dataLength, 1),
      getByte(dataLength, 0)
    )
    data = prefix .. data
    local padding, paddingLength = "", math.ceil(#data/16)*16 - #data
    for i=1,paddingLength do
      padding = padding .. string.char(math.random(0,255))
    end
    return data .. padding
  end

  local function properlyDecrypted(data)
    local random = {string.byte(data,1,4)}

    if (random[1] == random[3] and random[2] == random[4]) then
      return true
    end

    return false
  end

  local function unpadByteString(data)
    if (not properlyDecrypted(data)) then
      return nil
    end
    local dataLength = putByte(string.byte(data,5), 3)
             + putByte(string.byte(data,6), 2)
             + putByte(string.byte(data,7), 1)
             + putByte(string.byte(data,8), 0)
    return string.sub(data,9,8+dataLength)
  end

  local function xorIV(data, iv)
    for i = 1,16 do
      data[i] = bxor(data[i], iv[i])
    end
  end

  local function increment(data)
    local i = 16
    while true do
      local value = data[i] + 1
      if value >= 256 then
        data[i] = value - 256
        i = (i - 2) % 16 + 1
      else
        data[i] = value
        break
      end
    end
  end

  -- Called every encryption cycle
  local push, pull, time = os.queueEvent, coroutine.yield, os.time
  local oldTime = time()
  local function sleepCheckIn()
    local newTime = time()
    if newTime - oldTime >= 0.03 then -- (0.020 * 1.5)
      oldTime = newTime
      push("sleep")
      pull("sleep")
    end
  end

  local function getRandomData(bytes)
    local char, random, sleep, insert = string.char, math.random, sleepCheckIn, table.insert
    local result = {}
    for i=1,bytes do
      insert(result, random(0,255))
      if i % 10240 == 0 then sleep() end
    end
    return result
  end

  local function getRandomString(bytes)
    local char, random, sleep, insert = string.char, math.random, sleepCheckIn, table.insert
    local result = {}
    for i=1,bytes do
      insert(result, char(random(0,255)))
      if i % 10240 == 0 then sleep() end
    end
    return table.concat(result)
  end

  return {
    byteParity = byteParity,
    getByte = getByte,
    putByte = putByte,
    bytesToInts = bytesToInts,
    intsToBytes = intsToBytes,
    bytesToHex = bytesToHex,
    hexToBytes = hexToBytes,
    toHexString = toHexString,
    padByteString = padByteString,
    properlyDecrypted = properlyDecrypted,
    unpadByteString = unpadByteString,
    xorIV = xorIV,
    increment = increment,
    sleepCheckIn = sleepCheckIn,
    getRandomData = getRandomData,
    getRandomString = getRandomString,
  }
  end)

  aes=_W(function(_ENV, ...)
  -- Implementation of AES with nearly pure lua
  -- AES with lua is slow, really slow :-)
  local putByte = util.putByte
  local getByte = util.getByte
  -- some constants
  local ROUNDS = 'rounds'
  local KEY_TYPE = "type"
  local ENCRYPTION_KEY=1
  local DECRYPTION_KEY=2
  -- aes SBOX
  local SBox = {}
  local iSBox = {}
  -- aes tables
  local table0 = {}
  local table1 = {}
  local table2 = {}
  local table3 = {}
  local tableInv0 = {}
  local tableInv1 = {}
  local tableInv2 = {}
  local tableInv3 = {}
  -- round constants
  local rCon = {
    0x01000000,
    0x02000000,
    0x04000000,
    0x08000000,
    0x10000000,
    0x20000000,
    0x40000000,
    0x80000000,
    0x1b000000,
    0x36000000,
    0x6c000000,
    0xd8000000,
    0xab000000,
    0x4d000000,
    0x9a000000,
    0x2f000000,
  }
  --
  -- affine transformation for calculating the S-Box of AES
  --
  local function affinMap(byte)
    mask = 0xf8
    result = 0
    for i = 1,8 do
      result = bit.lshift(result,1)
      parity = util.byteParity(bit.band(byte,mask))
      result = result + parity
      -- simulate roll
      lastbit = bit.band(mask, 1)
      mask = bit.band(bit.rshift(mask, 1),0xff)
      mask = lastbit ~= 0 and bit.bor(mask, 0x80) or bit.band(mask, 0x7f) 
    end
    return bit.bxor(result, 0x63)
  end
  --
  -- calculate S-Box and inverse S-Box of AES
  -- apply affine transformation to inverse in finite field 2^8
  --
  local function calcSBox()
    for i = 0, 255 do
      inverse = i ~= 0 and gf.invert(i) or i
      mapped = affinMap(inverse)
      SBox[i] = mapped
      iSBox[mapped] = i
    end
  end
  --
  -- Calculate round tables
  -- round tables are used to calculate shiftRow, MixColumn and SubBytes
  -- with 4 table lookups and 4 xor operations.
  --
  local function calcRoundTables()
    for x = 0,255 do
      byte = SBox[x]
      table0[x] = putByte(gf.mul(0x03, byte), 0)
                + putByte(             byte , 1)
                + putByte(             byte , 2)
                + putByte(gf.mul(0x02, byte), 3)
      table1[x] = putByte(             byte , 0)
                + putByte(             byte , 1)
                + putByte(gf.mul(0x02, byte), 2)
                + putByte(gf.mul(0x03, byte), 3)
      table2[x] = putByte(             byte , 0)
                + putByte(gf.mul(0x02, byte), 1)
                + putByte(gf.mul(0x03, byte), 2)
                + putByte(             byte , 3)
      table3[x] = putByte(gf.mul(0x02, byte), 0)
                + putByte(gf.mul(0x03, byte), 1)
                + putByte(             byte , 2)
                + putByte(             byte , 3)
    end
  end
  --
  -- Calculate inverse round tables
  -- does the inverse of the normal roundtables for the equivalent
  -- decryption algorithm.
  --
  local function calcInvRoundTables()
    for x = 0,255 do
      byte = iSBox[x]
      tableInv0[x] = putByte(gf.mul(0x0b, byte), 0)
                 + putByte(gf.mul(0x0d, byte), 1)
                 + putByte(gf.mul(0x09, byte), 2)
                 + putByte(gf.mul(0x0e, byte), 3)
      tableInv1[x] = putByte(gf.mul(0x0d, byte), 0)
                 + putByte(gf.mul(0x09, byte), 1)
                 + putByte(gf.mul(0x0e, byte), 2)
                 + putByte(gf.mul(0x0b, byte), 3)
      tableInv2[x] = putByte(gf.mul(0x09, byte), 0)
                 + putByte(gf.mul(0x0e, byte), 1)
                 + putByte(gf.mul(0x0b, byte), 2)
                 + putByte(gf.mul(0x0d, byte), 3)
      tableInv3[x] = putByte(gf.mul(0x0e, byte), 0)
                 + putByte(gf.mul(0x0b, byte), 1)
                 + putByte(gf.mul(0x0d, byte), 2)
                 + putByte(gf.mul(0x09, byte), 3)
    end
  end
  --
  -- rotate word: 0xaabbccdd gets 0xbbccddaa
  -- used for key schedule
  --
  local function rotWord(word)
    local tmp = bit.band(word,0xff000000)
    return (bit.lshift(word,8) + bit.rshift(tmp,24))
  end
  --
  -- replace all bytes in a word with the SBox.
  -- used for key schedule
  --
  local function subWord(word)
    return putByte(SBox[getByte(word,0)],0)
      + putByte(SBox[getByte(word,1)],1)
      + putByte(SBox[getByte(word,2)],2)
      + putByte(SBox[getByte(word,3)],3)
  end
  --
  -- generate key schedule for aes encryption
  --
  -- returns table with all round keys and
  -- the necessary number of rounds saved in [ROUNDS]
  --
  local function expandEncryptionKey(key)
    local keySchedule = {}
    local keyWords = math.floor(#key / 4)
    if ((keyWords ~= 4 and keyWords ~= 6 and keyWords ~= 8) or (keyWords * 4 ~= #key)) then
      error("Invalid key size: " .. tostring(keyWords))
      return nil
    end
    keySchedule[ROUNDS] = keyWords + 6
    keySchedule[KEY_TYPE] = ENCRYPTION_KEY
    for i = 0,keyWords - 1 do
      keySchedule[i] = putByte(key[i*4+1], 3)
               + putByte(key[i*4+2], 2)
               + putByte(key[i*4+3], 1)
               + putByte(key[i*4+4], 0)
    end
    for i = keyWords, (keySchedule[ROUNDS] + 1)*4 - 1 do
      local tmp = keySchedule[i-1]
      if ( i % keyWords == 0) then
        tmp = rotWord(tmp)
        tmp = subWord(tmp)
        local index = math.floor(i/keyWords)
        tmp = bit.bxor(tmp,rCon[index])
      elseif (keyWords > 6 and i % keyWords == 4) then
        tmp = subWord(tmp)
      end
      keySchedule[i] = bit.bxor(keySchedule[(i-keyWords)],tmp)
    end
    return keySchedule
  end
  --
  -- Inverse mix column
  -- used for key schedule of decryption key
  --
  local function invMixColumnOld(word)
    local b0 = getByte(word,3)
    local b1 = getByte(word,2)
    local b2 = getByte(word,1)
    local b3 = getByte(word,0)
    return putByte(gf.add(gf.add(gf.add(gf.mul(0x0b, b1),
                         gf.mul(0x0d, b2)),
                         gf.mul(0x09, b3)),
                         gf.mul(0x0e, b0)),3)
       + putByte(gf.add(gf.add(gf.add(gf.mul(0x0b, b2),
                         gf.mul(0x0d, b3)),
                         gf.mul(0x09, b0)),
                         gf.mul(0x0e, b1)),2)
       + putByte(gf.add(gf.add(gf.add(gf.mul(0x0b, b3),
                         gf.mul(0x0d, b0)),
                         gf.mul(0x09, b1)),
                         gf.mul(0x0e, b2)),1)
       + putByte(gf.add(gf.add(gf.add(gf.mul(0x0b, b0),
                         gf.mul(0x0d, b1)),
                         gf.mul(0x09, b2)),
                         gf.mul(0x0e, b3)),0)
  end
  --
  -- Optimized inverse mix column
  -- look at http://fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.311.pdf
  -- TODO: make it work
  --
  local function invMixColumn(word)
    local b0 = getByte(word,3)
    local b1 = getByte(word,2)
    local b2 = getByte(word,1)
    local b3 = getByte(word,0)
    local t = bit.bxor(b3,b2)
    local u = bit.bxor(b1,b0)
    local v = bit.bxor(t,u)
    v = bit.bxor(v,gf.mul(0x08,v))
    w = bit.bxor(v,gf.mul(0x04, bit.bxor(b2,b0)))
    v = bit.bxor(v,gf.mul(0x04, bit.bxor(b3,b1)))
    return putByte( bit.bxor(bit.bxor(b3,v), gf.mul(0x02, bit.bxor(b0,b3))), 0)
       + putByte( bit.bxor(bit.bxor(b2,w), gf.mul(0x02, t              )), 1)
       + putByte( bit.bxor(bit.bxor(b1,v), gf.mul(0x02, bit.bxor(b0,b3))), 2)
       + putByte( bit.bxor(bit.bxor(b0,w), gf.mul(0x02, u              )), 3)
  end
  --
  -- generate key schedule for aes decryption
  --
  -- uses key schedule for aes encryption and transforms each
  -- key by inverse mix column.
  --
  local function expandDecryptionKey(key)
    local keySchedule = expandEncryptionKey(key)
    if (keySchedule == nil) then
      return nil
    end
    keySchedule[KEY_TYPE] = DECRYPTION_KEY
    for i = 4, (keySchedule[ROUNDS] + 1)*4 - 5 do
      keySchedule[i] = invMixColumnOld(keySchedule[i])
    end
    return keySchedule
  end
  --
  -- xor round key to state
  --
  local function addRoundKey(state, key, round)
    for i = 0, 3 do
      state[i + 1] = bit.bxor(state[i + 1], key[round*4+i])
    end
  end
  --
  -- do encryption round (ShiftRow, SubBytes, MixColumn together)
  --
  local function doRound(origState, dstState)
    dstState[1] =  bit.bxor(bit.bxor(bit.bxor(
          table0[getByte(origState[1],3)],
          table1[getByte(origState[2],2)]),
          table2[getByte(origState[3],1)]),
          table3[getByte(origState[4],0)])
    dstState[2] =  bit.bxor(bit.bxor(bit.bxor(
          table0[getByte(origState[2],3)],
          table1[getByte(origState[3],2)]),
          table2[getByte(origState[4],1)]),
          table3[getByte(origState[1],0)])
    dstState[3] =  bit.bxor(bit.bxor(bit.bxor(
          table0[getByte(origState[3],3)],
          table1[getByte(origState[4],2)]),
          table2[getByte(origState[1],1)]),
          table3[getByte(origState[2],0)])
    dstState[4] =  bit.bxor(bit.bxor(bit.bxor(
          table0[getByte(origState[4],3)],
          table1[getByte(origState[1],2)]),
          table2[getByte(origState[2],1)]),
          table3[getByte(origState[3],0)])
  end
  --
  -- do last encryption round (ShiftRow and SubBytes)
  --
  local function doLastRound(origState, dstState)
    dstState[1] = putByte(SBox[getByte(origState[1],3)], 3)
          + putByte(SBox[getByte(origState[2],2)], 2)
          + putByte(SBox[getByte(origState[3],1)], 1)
          + putByte(SBox[getByte(origState[4],0)], 0)
    dstState[2] = putByte(SBox[getByte(origState[2],3)], 3)
          + putByte(SBox[getByte(origState[3],2)], 2)
          + putByte(SBox[getByte(origState[4],1)], 1)
          + putByte(SBox[getByte(origState[1],0)], 0)
    dstState[3] = putByte(SBox[getByte(origState[3],3)], 3)
          + putByte(SBox[getByte(origState[4],2)], 2)
          + putByte(SBox[getByte(origState[1],1)], 1)
          + putByte(SBox[getByte(origState[2],0)], 0)
    dstState[4] = putByte(SBox[getByte(origState[4],3)], 3)
          + putByte(SBox[getByte(origState[1],2)], 2)
          + putByte(SBox[getByte(origState[2],1)], 1)
          + putByte(SBox[getByte(origState[3],0)], 0)
  end
  --
  -- do decryption round
  --
  local function doInvRound(origState, dstState)
    dstState[1] =  bit.bxor(bit.bxor(bit.bxor(
          tableInv0[getByte(origState[1],3)],
          tableInv1[getByte(origState[4],2)]),
          tableInv2[getByte(origState[3],1)]),
          tableInv3[getByte(origState[2],0)])
    dstState[2] =  bit.bxor(bit.bxor(bit.bxor(
          tableInv0[getByte(origState[2],3)],
          tableInv1[getByte(origState[1],2)]),
          tableInv2[getByte(origState[4],1)]),
          tableInv3[getByte(origState[3],0)])
    dstState[3] =  bit.bxor(bit.bxor(bit.bxor(
          tableInv0[getByte(origState[3],3)],
          tableInv1[getByte(origState[2],2)]),
          tableInv2[getByte(origState[1],1)]),
          tableInv3[getByte(origState[4],0)])
    dstState[4] =  bit.bxor(bit.bxor(bit.bxor(
          tableInv0[getByte(origState[4],3)],
          tableInv1[getByte(origState[3],2)]),
          tableInv2[getByte(origState[2],1)]),
          tableInv3[getByte(origState[1],0)])
  end
  --
  -- do last decryption round
  --
  local function doInvLastRound(origState, dstState)
    dstState[1] = putByte(iSBox[getByte(origState[1],3)], 3)
          + putByte(iSBox[getByte(origState[4],2)], 2)
          + putByte(iSBox[getByte(origState[3],1)], 1)
          + putByte(iSBox[getByte(origState[2],0)], 0)
    dstState[2] = putByte(iSBox[getByte(origState[2],3)], 3)
          + putByte(iSBox[getByte(origState[1],2)], 2)
          + putByte(iSBox[getByte(origState[4],1)], 1)
          + putByte(iSBox[getByte(origState[3],0)], 0)
    dstState[3] = putByte(iSBox[getByte(origState[3],3)], 3)
          + putByte(iSBox[getByte(origState[2],2)], 2)
          + putByte(iSBox[getByte(origState[1],1)], 1)
          + putByte(iSBox[getByte(origState[4],0)], 0)
    dstState[4] = putByte(iSBox[getByte(origState[4],3)], 3)
          + putByte(iSBox[getByte(origState[3],2)], 2)
          + putByte(iSBox[getByte(origState[2],1)], 1)
          + putByte(iSBox[getByte(origState[1],0)], 0)
  end
  --
  -- encrypts 16 Bytes
  -- key           encryption key schedule
  -- input         array with input data
  -- inputOffset   start index for input
  -- output        array for encrypted data
  -- outputOffset  start index for output
  --
  local function encrypt(key, input, inputOffset, output, outputOffset)
    --default parameters
    inputOffset = inputOffset or 1
    output = output or {}
    outputOffset = outputOffset or 1
    local state = {}
    local tmpState = {}
    if (key[KEY_TYPE] ~= ENCRYPTION_KEY) then
      error("No encryption key: " .. tostring(key[KEY_TYPE]) .. ", expected " .. ENCRYPTION_KEY)
      return
    end
    state = util.bytesToInts(input, inputOffset, 4)
    addRoundKey(state, key, 0)
    local round = 1
    while (round < key[ROUNDS] - 1) do
      -- do a double round to save temporary assignments
      doRound(state, tmpState)
      addRoundKey(tmpState, key, round)
      round = round + 1
      doRound(tmpState, state)
      addRoundKey(state, key, round)
      round = round + 1
    end
    doRound(state, tmpState)
    addRoundKey(tmpState, key, round)
    round = round +1
    doLastRound(tmpState, state)
    addRoundKey(state, key, round)
    util.sleepCheckIn()
    return util.intsToBytes(state, output, outputOffset)
  end
  --
  -- decrypt 16 bytes
  -- key           decryption key schedule
  -- input         array with input data
  -- inputOffset   start index for input
  -- output        array for decrypted data
  -- outputOffset  start index for output
  ---
  local function decrypt(key, input, inputOffset, output, outputOffset)
    -- default arguments
    inputOffset = inputOffset or 1
    output = output or {}
    outputOffset = outputOffset or 1
    local state = {}
    local tmpState = {}
    if (key[KEY_TYPE] ~= DECRYPTION_KEY) then
      error("No decryption key: " .. tostring(key[KEY_TYPE]))
      return
    end
    state = util.bytesToInts(input, inputOffset, 4)
    addRoundKey(state, key, key[ROUNDS])
    local round = key[ROUNDS] - 1
    while (round > 2) do
      -- do a double round to save temporary assignments
      doInvRound(state, tmpState)
      addRoundKey(tmpState, key, round)
      round = round - 1
      doInvRound(tmpState, state)
      addRoundKey(state, key, round)
      round = round - 1
    end
    doInvRound(state, tmpState)
    addRoundKey(tmpState, key, round)
    round = round - 1
    doInvLastRound(tmpState, state)
    addRoundKey(state, key, round)
    util.sleepCheckIn()
    return util.intsToBytes(state, output, outputOffset)
  end

  -- calculate all tables when loading this file
  calcSBox()
  calcRoundTables()
  calcInvRoundTables()

  return {
    ROUNDS = ROUNDS,
    KEY_TYPE = KEY_TYPE,
    ENCRYPTION_KEY = ENCRYPTION_KEY,
    DECRYPTION_KEY = DECRYPTION_KEY,
    expandEncryptionKey = expandEncryptionKey,
    expandDecryptionKey = expandDecryptionKey,
    encrypt = encrypt,
    decrypt = decrypt,
  }
  end)

  local buffer=_W(function(_ENV, ...)
  local function new ()
    return {}
  end

  local function addString (stack, s)
    table.insert(stack, s)
  end

  local function toString (stack)
    return table.concat(stack)
  end

  return {
    new = new,
    addString = addString,
    toString = toString,
  }
  end)

  ciphermode=_W(function(_ENV, ...)
  local public = {}
  --
  -- Encrypt strings
  -- key - byte array with key
  -- string - string to encrypt
  -- modefunction - function for cipher mode to use
  --
  local random, unpack = math.random, unpack or table.unpack
  function public.encryptString(key, data, modeFunction, iv)
    if iv then
      local ivCopy = {}
      for i = 1, 16 do ivCopy[i] = iv[i] end
      iv = ivCopy
    else
      iv = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
    end
    local keySched = aes.expandEncryptionKey(key)
    local encryptedData = buffer.new()
    for i = 1, #data/16 do
      local offset = (i-1)*16 + 1
      local byteData = {string.byte(data,offset,offset +15)}
      iv = modeFunction(keySched, byteData, iv)
      buffer.addString(encryptedData, string.char(unpack(byteData)))
    end
    return buffer.toString(encryptedData)
  end
  --
  -- the following 4 functions can be used as
  -- modefunction for encryptString
  --
  -- Electronic code book mode encrypt function
  function public.encryptECB(keySched, byteData, iv)
    aes.encrypt(keySched, byteData, 1, byteData, 1)
  end

  -- Cipher block chaining mode encrypt function
  function public.encryptCBC(keySched, byteData, iv)
    util.xorIV(byteData, iv)
    aes.encrypt(keySched, byteData, 1, byteData, 1)
    return byteData
  end

  -- Output feedback mode encrypt function
  function public.encryptOFB(keySched, byteData, iv)
    aes.encrypt(keySched, iv, 1, iv, 1)
    util.xorIV(byteData, iv)
    return iv
  end

  -- Cipher feedback mode encrypt function
  function public.encryptCFB(keySched, byteData, iv)
    aes.encrypt(keySched, iv, 1, iv, 1)
    util.xorIV(byteData, iv)
    return byteData
  end

  function public.encryptCTR(keySched, byteData, iv)
    local nextIV = {}
    for j = 1, 16 do nextIV[j] = iv[j] end
    aes.encrypt(keySched, iv, 1, iv, 1)
    util.xorIV(byteData, iv)
    util.increment(nextIV)
    return nextIV
  end
  --
  -- Decrypt strings
  -- key - byte array with key
  -- string - string to decrypt
  -- modefunction - function for cipher mode to use
  --
  function public.decryptString(key, data, modeFunction, iv)
    if iv then
      local ivCopy = {}
      for i = 1, 16 do ivCopy[i] = iv[i] end
      iv = ivCopy
    else
      iv = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
    end
    local keySched
    if modeFunction == public.decryptOFB or modeFunction == public.decryptCFB or modeFunction == public.decryptCTR then
      keySched = aes.expandEncryptionKey(key)
    else
      keySched = aes.expandDecryptionKey(key)
    end
    local decryptedData = buffer.new()
    for i = 1, #data/16 do
      local offset = (i-1)*16 + 1
      local byteData = {string.byte(data,offset,offset +15)}
      iv = modeFunction(keySched, byteData, iv)
      buffer.addString(decryptedData, string.char(unpack(byteData)))
    end
    return buffer.toString(decryptedData)
  end
  --
  -- the following 4 functions can be used as
  -- modefunction for decryptString
  --
  -- Electronic code book mode decrypt function
  function public.decryptECB(keySched, byteData, iv)
    aes.decrypt(keySched, byteData, 1, byteData, 1)
    return iv
  end

  -- Cipher block chaining mode decrypt function
  function public.decryptCBC(keySched, byteData, iv)
    local nextIV = {}
    for j = 1, 16 do nextIV[j] = byteData[j] end
    aes.decrypt(keySched, byteData, 1, byteData, 1)
    util.xorIV(byteData, iv)
    return nextIV
  end

  -- Output feedback mode decrypt function
  function public.decryptOFB(keySched, byteData, iv)
    aes.encrypt(keySched, iv, 1, iv, 1)
    util.xorIV(byteData, iv)
    return iv
  end

  -- Cipher feedback mode decrypt function
  function public.decryptCFB(keySched, byteData, iv)
    local nextIV = {}
    for j = 1, 16 do nextIV[j] = byteData[j] end
    aes.encrypt(keySched, iv, 1, iv, 1)
    util.xorIV(byteData, iv)
    return nextIV
  end

  public.decryptCTR = public.encryptCTR
  return public
  end)

  -- Simple API for encrypting strings.
  --
  AES128 = 16
  AES192 = 24
  AES256 = 32
  ECBMODE = 1
  CBCMODE = 2
  OFBMODE = 3
  CFBMODE = 4
  CTRMODE = 4

  local function pwToKey(password, keyLength, iv)
    local padLength = keyLength
    if (keyLength == AES192) then
      padLength = 32
    end
    if (padLength > #password) then
      local postfix = ""
      for i = 1,padLength - #password do
        postfix = postfix .. string.char(0)
      end
      password = password .. postfix
    else
      password = string.sub(password, 1, padLength)
    end
    local pwBytes = {string.byte(password,1,#password)}
    password = ciphermode.encryptString(pwBytes, password, ciphermode.encryptCBC, iv)
    password = string.sub(password, 1, keyLength)
    return {string.byte(password,1,#password)}
  end
  --
  -- Encrypts string data with password password.
  -- password  - the encryption key is generated from this string
  -- data      - string to encrypt (must not be too large)
  -- keyLength - length of aes key: 128(default), 192 or 256 Bit
  -- mode      - mode of encryption: ecb, cbc(default), ofb, cfb
  --
  -- mode and keyLength must be the same for encryption and decryption.
  --
  function encrypt(password, data, keyLength, mode, iv)
    assert(password ~= nil, "Empty password.")
    assert(data ~= nil, "Empty data.")
    local mode = mode or CBCMODE
    local keyLength = keyLength or AES128
    local key = pwToKey(password, keyLength, iv)
    local paddedData = util.padByteString(data)
    if mode == ECBMODE then
      return ciphermode.encryptString(key, paddedData, ciphermode.encryptECB, iv)
    elseif mode == CBCMODE then
      return ciphermode.encryptString(key, paddedData, ciphermode.encryptCBC, iv)
    elseif mode == OFBMODE then
      return ciphermode.encryptString(key, paddedData, ciphermode.encryptOFB, iv)
    elseif mode == CFBMODE then
      return ciphermode.encryptString(key, paddedData, ciphermode.encryptCFB, iv)
    elseif mode == CTRMODE then
      return ciphermode.encryptString(key, paddedData, ciphermode.encryptCTR, iv)
    else
      error("Unknown mode", 2)
    end
  end
  --
  -- Decrypts string data with password password.
  -- password  - the decryption key is generated from this string
  -- data      - string to encrypt
  -- keyLength - length of aes key: 128(default), 192 or 256 Bit
  -- mode      - mode of decryption: ecb, cbc(default), ofb, cfb
  --
  -- mode and keyLength must be the same for encryption and decryption.
  --
  function decrypt(password, data, keyLength, mode, iv)
    local mode = mode or CBCMODE
    local keyLength = keyLength or AES128
    local key = pwToKey(password, keyLength, iv)
    local plain
    if mode == ECBMODE then
      plain = ciphermode.decryptString(key, data, ciphermode.decryptECB, iv)
    elseif mode == CBCMODE then
      plain = ciphermode.decryptString(key, data, ciphermode.decryptCBC, iv)
    elseif mode == OFBMODE then
      plain = ciphermode.decryptString(key, data, ciphermode.decryptOFB, iv)
    elseif mode == CFBMODE then
      plain = ciphermode.decryptString(key, data, ciphermode.decryptCFB, iv)
    elseif mode == CTRMODE then
      plain = ciphermode.decryptString(key, data, ciphermode.decryptCTR, iv)
    else
      error("Unknown mode", 2)
    end
    result = util.unpadByteString(plain)
    if (result == nil) then
      return nil
    end
    return result
  end
end

-- SHA-256, HMAC and PBKDF2 functions in ComputerCraft
-- By Anavrins
-- For help and details, you can PM me on the CC forums
-- http://www.computercraft.info/forums2/index.php?/user/12870-anavrins
-- Pastebin: http://pastebin.com/6UV4qfNF
-- You may use this code in your projects without asking me, as long as credit is given and this header is kept intact
local digest, hmac, pbkdf2
do
  local mod32 = 2^32
  local sha_hashlen = 32
  local sha_blocksize = 64
  local band    = bit32 and bit32.band or bit.band
  local bnot    = bit32 and bit32.bnot or bit.bnot
  local bxor    = bit32 and bit32.bxor or bit.bxor
  local blshift = bit32 and bit32.lshift or bit.blshift
  local upack   = unpack

  local function rrotate(n, b)
    local s = n/(2^b)
    local f = s%1
    return (s-f) + f*mod32
  end

  local function brshift(int, by) -- Thanks bit32 for bad rshift
    local s = int / (2^by)
    return s - s%1
  end

  local H = {
    0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
    0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
  }

  local K = {
    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
  }

  local function counter(incr)
    local t1, t2 = 0, 0
    if 0xFFFFFFFF - t1 < incr then
      t2 = t2 + 1
      t1 = incr - (0xFFFFFFFF - t1) - 1		
    else t1 = t1 + incr
    end
    return t2, t1
  end

  local function BE_toInt(bs, i)
    return blshift((bs[i] or 0), 24) + blshift((bs[i+1] or 0), 16) + blshift((bs[i+2] or 0), 8) + (bs[i+3] or 0)
  end

  local function preprocess(data)
    local len = #data
    local proc = {}
    data[#data+1] = 0x80
    while #data%64~=56 do data[#data+1] = 0 end
    local blocks = math.ceil(#data/64)
    for i = 1, blocks do
      proc[i] = {}
      for j = 1, 16 do
        proc[i][j] = BE_toInt(data, 1+((i-1)*64)+((j-1)*4))
      end
    end
    proc[blocks][15], proc[blocks][16] = counter(len*8)
    return proc
  end

  local function digestblock(w, C)
    for j = 17, 64 do
      local v = w[j-15]
      local s0 = bxor(bxor(rrotate(w[j-15], 7), rrotate(w[j-15], 18)), brshift(w[j-15], 3))
      local s1 = bxor(bxor(rrotate(w[j-2], 17), rrotate(w[j-2], 19)), brshift(w[j-2], 10))
      w[j] = (w[j-16] + s0 + w[j-7] + s1)%mod32
    end
    local a, b, c, d, e, f, g, h = upack(C)
    for j = 1, 64 do
      local S1 = bxor(bxor(rrotate(e, 6), rrotate(e, 11)), rrotate(e, 25))
      local ch = bxor(band(e, f), band(bnot(e), g))
      local temp1 = (h + S1 + ch + K[j] + w[j])%mod32
      local S0 = bxor(bxor(rrotate(a, 2), rrotate(a, 13)), rrotate(a, 22))
      local maj = bxor(bxor(band(a, b), band(a, c)), band(b, c))
      local temp2 = (S0 + maj)%mod32
      h, g, f, e, d, c, b, a = g, f, e, (d+temp1)%mod32, c, b, a, (temp1+temp2)%mod32
    end
    C[1] = (C[1] + a)%mod32
    C[2] = (C[2] + b)%mod32
    C[3] = (C[3] + c)%mod32
    C[4] = (C[4] + d)%mod32
    C[5] = (C[5] + e)%mod32
    C[6] = (C[6] + f)%mod32
    C[7] = (C[7] + g)%mod32
    C[8] = (C[8] + h)%mod32
    return C
  end

  local mt = {
    __tostring = function(a) return string.char(unpack(a)) end,
    __index = {
      toHex = function(self, s) return ("%02x"):rep(#self):format(unpack(self)) end,
      isEqual = function(self, t)
        if type(t) ~= "table" then return false end
        if #self ~= #t then return false end
        local ret = 0
        for i = 1, #self do
          ret = bit32.bor(ret, bxor(self[i], t[i]))
        end
        return ret == 0
      end
    }
  }

  local function toBytes(t, n)
    local b = {}
    for i = 1, n do
      b[(i-1)*4+1] = band(brshift(band(t[i], 0xFF000000), 24), 0xFF)
      b[(i-1)*4+2] = band(brshift(band(t[i], 0xFF0000), 16), 0xFF)
      b[(i-1)*4+3] = band(brshift(band(t[i], 0xFF00), 8), 0xFF)
      b[(i-1)*4+4] = band(t[i], 0xFF)
    end
    return setmetatable(b, mt)
  end

  digest = function(data)
    data = data or ""
    data = type(data) == "string" and {data:byte(1,-1)} or data
    data = preprocess(data)
    local C = {upack(H)}
    for i = 1, #data do C = digestblock(data[i], C) end
    return toBytes(C, 8)
  end

  hmac = function(data, key)
    local data = type(data) == "table" and {upack(data)} or {tostring(data):byte(1,-1)}
    local key = type(key) == "table" and {upack(key)} or {tostring(key):byte(1,-1)}
    local blocksize = sha_blocksize
    key = #key > blocksize and digest(key) or key
    local ipad = {}
    local opad = {}
    local padded_key = {}
    for i = 1, blocksize do
      ipad[i] = bxor(0x36, key[i] or 0)
      opad[i] = bxor(0x5C, key[i] or 0)
    end
    for i = 1, #data do
      ipad[blocksize+i] = data[i]
    end
    ipad = digest(ipad)
    for i = 1, blocksize do
      padded_key[i] = opad[i]
      padded_key[blocksize+i] = ipad[i]
    end
    return digest(padded_key)
  end

  pbkdf2 = function(pass, salt, iter, dklen)
    local out = {}
    local hashlen = sha_hashlen
    local block = 1
    dklen = dklen or 32
    while dklen > 0 do
      local ikey = {}
      local isalt = type(salt) == "table" and {upack(salt)} or {tostring(salt):byte(1,-1)}
      local clen = dklen > hashlen and hashlen or dklen
      local iCount = #isalt
      isalt[iCount+1] = band(brshift(band(block, 0xFF000000), 24), 0xFF)
      isalt[iCount+2] = band(brshift(band(block, 0xFF0000), 16), 0xFF)
      isalt[iCount+3] = band(brshift(band(block, 0xFF00), 8), 0xFF)
      isalt[iCount+4] = band(block, 0xFF)
      for j = 1, iter do
        isalt = hmac(isalt, pass)
        for k = 1, clen do ikey[k] = bxor(isalt[k], ikey[k] or 0) end
        if j % 200 == 0 then os.queueEvent("PBKDF2", j) coroutine.yield("PBKDF2") end
      end
      dklen = dklen - clen
      block = block+1
      for k = 1, clen do out[#out+1] = ikey[k] end
    end
    return setmetatable(out, mt)
  end
end

local function filterTurtleList()
  for i = #filteredTurtles, 1, -1 do
    filteredTurtles[i] = nil
  end
  local turtleCount = 0
  for i = 1, #allTurtles do
    if (type(fuelFilter) == "boolean" and fuelFilter and allTurtles[i].fuelState) or (not fuelFilter) or (type(fuelFilter) == "string" and not allTurtles[i].fuelState) then
      turtleCount = turtleCount + 1
      filteredTurtles[turtleCount] = { }
      for k, v in pairs(allTurtles[i]) do
        filteredTurtles[turtleCount][k] = v
      end
      filteredTurtles[turtleCount].atList = i
    end
  end
  numPages = math.max(1, math.ceil(#filteredTurtles / 24))
  pageNum = math.min(pageNum, numPages)
end

do
  local function updateTurtleList(newSettings)
    local turtleCount = #allTurtles
    if turtleCount == 0 then                --# the list is empty
      allTurtles[1] = { }
      for k, v in pairs(newSettings) do     --# add this turtle
        allTurtles[1][k] = v
      end
    else
      local nsName, filterCount, done = newSettings.name, #filteredTurtles, false
      for i = 1, turtleCount do
        if gtHost == allTurtles[i].cc then    --# we're already on the list
          for k, v in pairs(newSettings) do   --# update entry with current state of host
            allTurtles[i][k] = v
          end
          for j = 1, filterCount do
            if filteredTurtles[j].atList == i then
              for k, v in pairs(newSettings) do
                filteredTurtles[j][k] = v
              end
              done = true
              break
            end
            if done then break end
          end
          return
        end
      end
      for i = 1, turtleCount do
        if nsName < allTurtles[i].name then --# alphabetize
          table.insert(allTurtles, i, newSettings) --# insert
          break
        end
        if i == turtleCount then            --# we've reached the end of the list
          turtleCount = turtleCount + 1
          allTurtles[turtleCount] = { }     --# tack it on
          for k, v in pairs(newSettings) do
            allTurtles[turtleCount][k] = v
          end
          break
        end
      end
    end
    filterTurtleList()
  end

  netReceive = function()
    local id, success, encKey, message, encryptedMessage, decryptedMessage, decodedMessage
    while true do
      if not rednet.isOpen(netSide) then rednet.open(netSide) end
      id, encryptedMessage = rednet.receive("gtRemote")
      if type(encryptedMessage) == "string" then
        success, decodedMessage = pcall(decode, encryptedMessage)
        if success and type(decodedMessage) == "string" then
          encKey = thisCC .. "gt!Remote" .. tostring(id)
          success, decryptedMessage = pcall(decrypt, encKey, decodedMessage)
          if success and type(decryptedMessage) == "string" then
            success, message = pcall(textutils.unserialize, decryptedMessage)
            if success and type(message) == "table" and message.program and message.program == "gtRemoteHost" then
              gtHost = id
              updateTurtleList(message)
              if (((runState == "Inventory" or runState == "Remote") and gtHost == allTurtles[thisTurtle].cc) or runState == "List") and not inputting then
                if runState == "Inventory" then
                  drawHeader()
                  turtleInventory()
                elseif runState == "Remote" then
                  turtleControlScreen()
                else
                  drawCLI()
                end
              end
            end
          end
        end
      end
    end
  end
end

local function netSend(targetHost, cmd1, act1)
  local dataPack = textutils.serialize({ program = "gtRemote", cc = tonumber(thisCC), cmd = cmd1, action = act1 })
  if not rednet.isOpen(netSide) then rednet.open(netSide) end
  if targetHost == "ALL" then
    local encKey = thisCC .. "gt!Remote_Broadcast" .. thisCC
    local encryptedData = encode(encrypt(encKey, dataPack))
    rednet.broadcast(encryptedData, "gtRemote")
  else
    local encKey = tostring(targetHost) .. "gt!Remote" .. thisCC
    local encryptedData = encode(encrypt(encKey, dataPack))
    rednet.send(targetHost, encryptedData, "gtRemote")
  end
end

local function assignColor(assignment)
  return colorBurst[assignment].color or silver
end

local function drawElement(x, y, w, h, txtColor, bgColor, text)
  if type(w) == "boolean" then
    term.setCursorPos(x, y)
    term.setBackgroundColor(w and green or gray)
    term.write("  ")
    term.setBackgroundColor(w and gray or red)
    term.write("  ")
  else
    text = text and tostring(text) or ""
    local txtLen = #text
    w = math.max(w, txtLen)
    local spacer = (w - txtLen) / 2
    local txtLine = string.rep(" ", math.floor(spacer)) .. text .. string.rep(" ", math.ceil(spacer))
    if txtColor then term.setTextColor(txtColor) end
    if bgColor then term.setBackgroundColor(bgColor) end
    if h == 1 then
      term.setCursorPos(x, y)
      term.write(txtLine)
    else
      local line, textRow = string.rep(" ", w), y + math.floor(h / 2)
      for i = y, y + h - 1 do
        term.setCursorPos(x, i)         
        term.write(i == textRow and txtLine or line) --# Draw one line of the 'element' (box/rectangle/line-seg)
      end
    end
  end
end

drawHeader = function()
  local hColor = (runState == "Remote" or runState == "Inventory" or runState == "color") and assignColor(allTurtles[thisTurtle].color) or blue
  local hText = runState == "Help" and "gtRemote  " .. gtRver or ((runState == "Remote" or runState == "Inventory" or runState == "color") and allTurtles[thisTurtle].name .. " (cc# " .. tostring(allTurtles[thisTurtle].cc) .. ")" or "gtRemote")
  drawElement(1, 1, termX, 1, white, hColor, hText) --# title bar
  if runState == "List" or runState == "goPage" then
    drawElement(1, 1, 3, 1, nil, nil, "[ ]")
    local fColor = fuelFilter and green or gray
    drawElement(2, 1, 1, 1, type(fuelFilter) == "boolean" and fColor or orange, nil, "F")
    drawElement(termX - 2, 1, 3, 1, white, nil, "[ ]")
    drawElement(termX - 1, 1, 1, 1, red, nil, "X")
    drawElement(1, 2, 1, 1, green, gray, string.rep(" ", math.floor(termX / 4) - 2) .. "All ON" .. string.rep(" ", math.ceil(termX / 4) - 2))
    drawElement(math.ceil(termX / 2), 2, 1, 1, red, nil, string.rep(" ", math.floor(termX / 4) - 2) .. "All OFF" .. string.rep(" ", math.ceil(termX / 4) - 2))
  elseif runState == "Remote" or runState == "Inventory" or runState == "color" or runState == "Help" then
    drawElement(1, 2, termX, 1, red, gray, "Close")
  end
end

local function drawColorList(rating)
  local color
  drawElement(8, 4, 9, 8, nil, gray)                      --# menu body
  for k, v in pairs(colorBurst) do
    if rating == k then
      color = v.color
      drawElement(16, v.order + 3, 1, 1, nil, color, " ") --# selected color pip
    else
      color = silver
    end
    drawElement(7, v.order + 3, 1, 1, nil, v.color, " ")  --# rating color pips
    if rating == "I" and k == "I" then color = white end
    drawElement(9, v.order + 3, 1, 1, color, gray, v.text)
  end
end

turtleInventory = function()
  local currentSlot, selectedSlot, slotContents, isFuel, sCount, txtColor, bgColor = 0, allTurtles[thisTurtle].slot, allTurtles[thisTurtle].contents
  for y = 7, 13, 2 do       --# y position
    for x = 3, 12, 3 do     --# x position
      currentSlot = currentSlot + 1 --# increment inventory slot
      isFuel = allTurtles[thisTurtle].inv[currentSlot].isFuel
      sCount = allTurtles[thisTurtle].inv[currentSlot].count
      txtColor = currentSlot == selectedSlot and black or (sCount > 0 and sky or silver)
      txtColor = (currentSlot ~= selectedSlot and isFuel) and yellow or txtColor
      bgColor = currentSlot == selectedSlot and (isFuel and yellow or (sCount > 0 and sky or silver)) or black
      drawElement(x, y, 2, 1, txtColor, bgColor, tostring(sCount)) --# write inventory count
    end
  end
  drawElement(8, 16, 1, 1, allTurtles[thisTurtle].inv[selectedSlot].isFuel and yellow or (allTurtles[thisTurtle].inv[selectedSlot].count > 0 and sky or silver), black, slotContents .. string.rep(" ", 18 - #slotContents))
  drawElement(13, 17, 1, 1, silver, nil, tDirection .. "     ")
end

local function staticTurtleInventory()
  drawHeader()
  drawElement(2, 4, termX - 2, 1, silver, black, allTurtles[thisTurtle].name .. "'s Inventory")
  drawElement(2, 6, 13, 9, nil, gray) --# Inventory 'box'
  drawElement(2, 16, 1, 1, gray, black, "Slot:")
  drawElement(2, 17, 1, 1, nil, nil, "Direction:")
  drawElement(16, 7, 9, 1, black, yellow, " Refuel")
  drawElement(16, 9, 9, 1, nil, green, "Place")
  drawElement(16, 11, 9, 1, nil, red, "Break")
  drawElement(16, 13, 9, 1, nil, silver, "Equip")
  drawElement(math.floor(termX / 2) - 9, termY - 1, 1, 1, white, brown, " Refresh Inventory ")
  turtleInventory()
end

turtleControlScreen = function()
  runState = "Remote"
  drawHeader()
  if thisTurtle <= #allTurtles then
    drawElement(2, 4, 1, 1, assignColor(allTurtles[thisTurtle].color), black, allTurtles[thisTurtle].name) --# Name
    drawElement(12, 4, 1, 1, red, black, "Redstone")     --# Redstone Output
    drawElement(22, 4, allTurtles[thisTurtle].redstone)  --# Redstone Output Switch
    drawElement(2, 6, 1, 1, silver, black, allTurtles[thisTurtle].note) --# Short description
    local dCheck, tFwd, tUp, tDown = allTurtles[thisTurtle].dirCheck, allTurtles[thisTurtle].forward, allTurtles[thisTurtle].up, allTurtles[thisTurtle].down
    local bColor = (tFwd and dCheck) and red or black    --# Direction buttons
    drawElement(5, 8, 2, 1, bColor, white, "/\\")   --# Forward
    drawElement(5, 9, 2, 1)                         --#
    drawElement(5, 12, 2, 2, black, nil, "\\/")     --# Back
    drawElement(3, 10, 2, 1, nil, nil, "/ ")        --# Left
    drawElement(3, 11, 2, 1, nil, nil, "\\ ")       --#
    drawElement(7, 10, 2, 1, nil, nil, " \\")       --# Right
    drawElement(7, 11, 2, 1, nil, nil, " /")        --#
    drawElement(5, 10, 2, 1, white, black, "..")    --# Center
    drawElement(5, 11, 2, 1, nil, nil, "..")        --#
    bColor = (tUp and dCheck) and red or black
    drawElement(3, 8, 2, 1, bColor, gray, "/\\")    --# Up
    drawElement(3, 9, 2, 1)                         --#
    bColor = (tDown and dCheck) and red or black
    drawElement(3, 12, 2, 2, bColor, gray, "\\/")   --# Down
    if dCheck then
      if (tDirection == "Up" and tUp) or (tDirection == "Down" and tDown) or (tDirection == "Forward" and tFwd) then
        drawElement(7, 8, 2, 1, black, gray, "PL")  --# Place/Break
        drawElement(7, 9, 2, 1, nil, red, "BR")
      else
        drawElement(7, 8, 2, 1, black, green, "PL") --# Place/Break
        drawElement(7, 9, 2, 1, nil, gray, "BR")
      end
    else
      drawElement(7, 8, 2, 1, black, gray, "PL")    --# Place/Break
      drawElement(7, 9, 2, 1, nil, nil, "BR")
    end
    drawElement(7, 12, 2, 1, black, dCheck and gray or red, "OB")
    drawElement(7, 13, 2, 1, nil, dCheck and green or gray, "CK")
    drawElement(12, 8, 1, 1, gray, black, "Fuel")        --# Fuel Gauge
    local fuelPercent = allTurtles[thisTurtle].fuelPercent
    local fuelColor = fuelPercent > 49 and green or orange
    drawElement(17, 8, 1, 1, fuelPercent < 10 and red or fuelColor, nil, fuelPercent < 101 and tostring(fuelPercent) or "Unlimited")
    if fuelPercent < 101 then
      term.setTextColor(gray)
      term.write("%  ")
      drawElement(12, 9, 1, 1, nil, nil, showFuel and "Units " or "Coal ")
      term.setTextColor(silver)
      term.write(showFuel and (tostring(allTurtles[thisTurtle].fuelAmount) .. "   ") or (tostring(allTurtles[thisTurtle].coal) .. "   "))
    end
    drawElement(12, 11, 1, 1, gray, nil, "PLBR")         --# Place/Break direction
    drawElement(17, 11, 1, 1, silver, nil, tDirection .. "     ")
    drawElement(12, 12, 1, 1, gray, nil, "Slot")         --# Slot selection
    local selectedSlot = allTurtles[thisTurtle].slot
    drawElement(17, 12, 1, 1, silver, nil, tostring(selectedSlot) .. " | " .. tostring(allTurtles[thisTurtle].inv[selectedSlot].count) .. "  ")
    drawElement(12, 13, 14, 1, nil, nil, "<            >")
    drawElement(14, 13, 10, 1, nil, gray)
    drawElement(14, 13, 10, 1, allTurtles[thisTurtle].inv[selectedSlot].isFuel and yellow or (allTurtles[thisTurtle].inv[selectedSlot].count > 0 and sky or silver), nil, allTurtles[thisTurtle].contents:sub(1, 10)) --# Selected slot contents
    drawElement(16, 15, 9, 1, black, nil, "Refresh")     --# Turtle location (Refresh GPS fix button)
    drawElement(16, 16, 9, 1, nil, nil, "GPS Fix")
    if allTurtles[thisTurtle].loc.x ~= "No GPS Fix" then --# pgps export (export to pocketgps button)
      drawElement(16, 17, 9, 1, nil, silver, "Export")
      drawElement(16, 18, 9, 1, nil, nil, "to pgps")
    end
    drawElement(2, 15, 1, 1, silver, black, "Location:") --# Turtle location (x/y/z)
    drawElement(2, 16, 1, 1, nil, nil, "x:")
    drawElement(2, 17, 1, 1, nil, nil, "y:")
    drawElement(2, 18, 1, 1, nil, nil, "z:")
    drawElement(5, 16, 1, 1, sky, nil, tostring(allTurtles[thisTurtle].loc.x))
    drawElement(5, 17, 1, 1, nil, nil, tostring(allTurtles[thisTurtle].loc.y))
    drawElement(5, 18, 1, 1, nil, nil, tostring(allTurtles[thisTurtle].loc.z))
  end
end

local function drawHelpUI()
  drawElement(1, 3, termX, termY - 2, nil, white)
  drawHeader()
  if tempState == "List" then
    drawElement(2, 4, termX - 1, 1, black, white, "Left click a turtle to")
    drawElement(2, 5, termX - 1, 1, nil, nil, "control and manage it")
    drawElement(2, 7, termX - 1, 1, nil, nil, "Middle click a turtle to")
    drawElement(2, 8, termX - 1, 1, nil, nil, "lock it")
    drawElement(2, 11, termX - 1, 1, nil, nil, "'All ON / All OFF'")
    drawElement(2, 12, termX - 1, 1, nil, nil, "controls redstone output")
    drawElement(2, 13, termX - 1, 1, nil, nil, "on turtles in range")
    drawElement(2, 15, termX - 1, 1, nil, nil, "[F] = Fuel Filter")
    drawElement(2, 16, termX - 1, 1, nil, nil, "Filters turtles based")
    drawElement(2, 17, termX - 1, 1, nil, nil, "on fueled status")
  elseif tempState == "Remote" then
    drawElement(2, 4, termX - 1, 1, black, white, "Click arrows to drive")
    drawElement(2, 5, termX - 1, 1, nil, nil, "or use [W, A, S, D, Q, E]")
    drawElement(2, 7, termX - 1, 1, nil, nil, "PL/BR[P] = Place/Break   ")
    drawElement(2, 8, termX - 1, 1, nil, nil, "OB/CK[O] = Obstacle check")
    drawElement(2, 10, termX - 1, 1, nil, nil, "Left click name or note")
    drawElement(2, 11, termX - 1, 1, nil, nil, "to edit")
    drawElement(2, 12, termX - 1, 1, nil, nil, "Right click name to")
    drawElement(2, 13, termX - 1, 1, nil, nil, "change color")
    drawElement(2, 15, termX - 1, 1, nil, nil, "Redstone [R] controls")
    drawElement(2, 16, termX - 1, 1, nil, nil, "RS output on the front")
    drawElement(2, 18, termX - 1, 1, nil, nil, "'Export to pgps' adds")
    drawElement(2, 19, termX - 1, 1, nil, nil, "turtle to pgps .places")
  elseif tempState == "Inventory" then
    drawElement(2, 4, termX - 1, 1, black, white, "Click inventory slots")
    drawElement(2, 5, termX - 1, 1, nil, nil, "to select for action")
    drawElement(2, 7, termX - 1, 1, nil, nil, "Click action button")
    drawElement(2, 8, termX - 1, 1, nil, nil, "to carry out action")
    drawElement(2, 10, termX - 1, 1, nil, nil, "Right click 'Place' to")
    drawElement(2, 11, termX - 1, 1, nil, nil, "engrave a sign")
    drawElement(2, 13, termX - 1, 1, nil, nil, "Right click 'Break' to")
    drawElement(2, 14, termX - 1, 1, nil, nil, "attack")
    drawElement(2, 16, termX - 1, 1, nil, nil, "Left click the displayed")
    drawElement(2, 17, termX - 1, 1, nil, nil, "direction to change the")
    drawElement(2, 18, termX - 1, 1, nil, nil, "direction of action")
  end
end

local function drawNaviUI()
  local pNum = tostring(pageNum)
  if pageNum < 100 then pNum = "0" .. pNum end --# Add a "0" before double digit page numbers
  if pageNum < 10 then pNum = "0" .. pNum end  --# Add another "0" before single digit page numbers
  drawElement(math.floor(termX / 2) - 10, termY, 7, 1, gray, black, pageNum > 1 and "<< BACK" or "       ")
  drawElement(math.floor(termX / 2) + 4, termY, 7, 1, nil, nil, pageNum < numPages and "NEXT >>" or "       ")
  drawElement(math.floor((termX - #pNum) / 2) + 1, termY, 3, 1, silver, nil, pNum) --# Page Number
end

local function drawMainUI() --# Turtle 'Address Book'
  local xPos, yPos, tName, tFuelState, tColor, spacer = 1, 4
  local magicNumber = ((pageNum - 1) * 23) + pageNum
  for i = magicNumber, math.min(#filteredTurtles, pageNum * 24) do
    term.setCursorPos(xPos, yPos)
    tFuelState, tColor = filteredTurtles[i].fuelState, filteredTurtles[i].color
    if not filteredTurtles[i].lockState then
      term.setTextColor(tFuelState and white or assignColor(tColor))
      term.setBackgroundColor(tFuelState and assignColor(tColor) or black)
    else
      term.setTextColor(tFuelState and silver or gray)
      term.setBackgroundColor(tFuelState and gray or black)
    end
    tName = filteredTurtles[i].name
    spacer = (8 - #tName) / 2
    term.write(string.rep(" ", math.floor(spacer)) .. tName .. string.rep(" ", math.ceil(spacer)))
    yPos = yPos + 2
    if yPos > 19 then yPos = 4 xPos = xPos + 9 end
  end
end

do
  local runStates = {
    List = function() drawMainUI() drawNaviUI() end;
    Remote = function() turtleControlScreen() end;
    Inventory = function() staticTurtleInventory() end;
  }

  drawCLI = function() --# Client Interface 'decider'
    if not inputting then
      drawHeader()
      if runStates[runState] then runStates[runState]() end
    end
  end
end

local function readInput(cX, cY, cO, bG, limit, mask) --# cursor X, Y, text color, bg color, character limit, character mask
  local word, pos = "", 0
  local curX, curY = cX, cY
  inputting, timerDelay = true, true
  os.cancelTimer(pollTimer)
  bG = bG or black
  limit = (limit and type(limit) == "number" and limit > 0) and limit or 21
  term.setCursorPos(curX, curY)
  term.setTextColor(cO)
  term.setBackgroundColor(bG)
  term.setCursorBlink(true)
  while true do
    local event, data, mX, mY = os.pullEvent()
    if event == "key" then
      if data == keys.backspace then
        if curX > cX then
          curX = math.max(cX, curX - 1)
          pos = math.max(0, pos - 1)
          word = word:sub(1, #word - 1)
        end
        drawElement(curX, curY, 1, 1, cO, bG, " ")
        term.setCursorPos(curX, curY)
      elseif data == keys.enter or data == keys.numPadEnter then
        break
      end
    elseif event == "char" and pos < limit then
      drawElement(curX, curY, 1, 1, cO, bG, mask or data)
      word = word .. data
      curX = curX + 1
      pos = pos + 1
    elseif event == "paste" and pos < limit then
      if pos + #data > limit then
        data = data:sub(1, limit - pos)
      end
      drawElement(curX, curY, 1, 1, cO, bG, mask and string.rep(mask, #data) or data)
      word = word .. data
      curX = curX + #data
      pos = pos + #data
    elseif event == "mouse_click" then
      if mY ~= curY or mX < cX or mX >= cX + limit then
        break
      end
    end
  end
  term.setCursorBlink(false)
  inputting = false
  return word
end

do
  local function toggleFuelFilter()
    if type(fuelFilter) == "boolean" then
      fuelFilter = fuelFilter and "noFuel" or true
    else
      fuelFilter = false
    end
    filterTurtleList()
    drawElement(1, 3, termX, termY - 2, nil, black)
    drawCLI()
  end

  local function execPLBR(check)
    if check then
      if (tDirection == "Up" and allTurtles[thisTurtle].up) or (tDirection == "Down" and allTurtles[thisTurtle].down) or (tDirection == "Forward" and allTurtles[thisTurtle].forward) then
        drawElement(7, 8, 2, 1, white, red, "PL")   --# Place/Break
        drawElement(7, 9, 2, 1, nil, nil, "BR")
        sleep(0.1)
        drawElement(7, 8, 2, 1, black, gray, "PL")  --# Place/Break
        drawElement(7, 9, 2, 1, nil, red, "BR")
      else
        drawElement(7, 8, 2, 1, white, green, "PL") --# Place/Break
        drawElement(7, 9, 2, 1, nil, nil, "BR")
        sleep(0.1)
        drawElement(7, 8, 2, 1, black, nil, "PL")   --# Place/Break
        drawElement(7, 9, 2, 1, nil, gray, "BR")
      end
    else
      drawElement(7, 8, 2, 1, black, white, "PL")   --# Place/Break
      drawElement(7, 9, 2, 1, nil, nil, "BR")
      sleep(0.1)
      drawElement(7, 8, 2, 1, nil, gray, "PL")      --# Place/Break
      drawElement(7, 9, 2, 1, nil, nil, "BR")
    end
    if not check or ((tDirection == "Up" and allTurtles[thisTurtle].up) or (tDirection == "Down" and allTurtles[thisTurtle].down) or (tDirection == "Forward" and allTurtles[thisTurtle].forward)) then
      netSend(allTurtles[thisTurtle].cc, "dig", tDirection)
    else
      netSend(allTurtles[thisTurtle].cc, "put", tDirection)
    end
  end

  local function inputArrow(direction)
    if direction == "up" then
      drawElement(3, 8, 2, 1, black, white, "/\\")  --# Up
      drawElement(3, 9, 2, 1)
      sleep(0.1)
      drawElement(3, 8, 2, 1, black, gray, "/\\")   --# Up
      drawElement(3, 9, 2, 1)
      netSend(allTurtles[thisTurtle].cc, "move", "up")
    elseif direction == "down" then
      drawElement(3, 12, 2, 2, black, white, "\\/") --# Down
      sleep(0.1)
      drawElement(3, 12, 2, 2, nil, gray, "\\/")    --# Down
      netSend(allTurtles[thisTurtle].cc, "move", "down")
    elseif direction == "forward" then
      drawElement(5, 8, 2, 1, white, gray, "/\\")   --# Forward
      drawElement(5, 9, 2, 1)
      sleep(0.1)
      drawElement(5, 8, 2, 1, black, white, "/\\")  --# Forward
      drawElement(5, 9, 2, 1)
      netSend(allTurtles[thisTurtle].cc, "move", "forward")
    elseif direction == "back" then
      drawElement(5, 12, 2, 2, white, gray, "\\/")  --# Back
      sleep(0.1)
      drawElement(5, 12, 2, 2, black, white, "\\/") --# Back
      netSend(allTurtles[thisTurtle].cc, "move", "back")
    elseif direction == "left" then
      drawElement(3, 10, 2, 1, white, gray, "/ ")   --# Left
      drawElement(3, 11, 2, 1, nil, nil, "\\ ")     --#
      sleep(0.1)
      drawElement(3, 10, 2, 1, black, white, "/ ")  --# Left
      drawElement(3, 11, 2, 1, nil, nil, "\\ ")     --#
      netSend(allTurtles[thisTurtle].cc, "move", "left")
    elseif direction == "right" then
      drawElement(7, 10, 2, 1, white, gray, " \\")  --# Right
      drawElement(7, 11, 2, 1, nil, nil, " /")      --#
      sleep(0.1)
      drawElement(7, 10, 2, 1, black, white, " \\") --# Right
      drawElement(7, 11, 2, 1, nil, nil, " /")      --#
      netSend(allTurtles[thisTurtle].cc, "move", "right")
    end
  end

  local directions = {
    Up = "Down";
    Down = "Forward";
    Forward = "Up";
  }

  local invSelect = {
    [3] = { [7] = 1, [9] = 5, [11] = 9, [13] = 13 }; --# [xCoord] = { [yCoord] = slot, [yCoord] = slot, etc. }
    [4] = { [7] = 1, [9] = 5, [11] = 9, [13] = 13 };
    [6] = { [7] = 2, [9] = 6, [11] = 10, [13] = 14 };
    [7] = { [7] = 2, [9] = 6, [11] = 10, [13] = 14 };
    [9] = { [7] = 3, [9] = 7, [11] = 11, [13] = 15 };
    [10] = { [7] = 3, [9] = 7, [11] = 11, [13] = 15 };
    [12] = { [7] = 4, [9] = 8, [11] = 12, [13] = 16 };
    [13] = { [7] = 4, [9] = 8, [11] = 12, [13] = 16 };
  }

  local keyList = {
    [16] = function() inputArrow("down") end;    --# Q (down)
    [17] = function() inputArrow("forward") end; --# W (forward)
    [18] = function() inputArrow("up") end;      --# E (up)
    [19] = function() netSend(allTurtles[thisTurtle].cc, "rds", not allTurtles[thisTurtle].redstone) end; --# R (redstone)
    [24] = function() netSend(allTurtles[thisTurtle].cc, "dcToggle", not allTurtles[thisTurtle].dirCheck) end; --# O (Obstacle check)
    [25] = function() execPLBR(allTurtles[thisTurtle].dirCheck) end; --# P (Place/Break)
    [30] = function() inputArrow("left") end;    --# A (left)
    [31] = function() inputArrow("back") end;    --# S (back)
    [32] = function() inputArrow("right") end;   --# D (right)
  }

  local function inputMain()
    local event, mButton, mcX, mcY, xPos, yPos, magicNumber
    while true do
      event, mButton, mcX, mcY = os.pullEvent()
      if event == "mouse_click" then
        if runState == "Help" then
          if mcY == 2 then
            drawElement(1, 2, termX, termY - 1, nil, black)
            runState = tempState
            drawCLI()
          end
        elseif runState == "Remote" then
          if mcY == 2 then                      --# Exit Remote/Edit Screen
            drawElement(2, 2, termX, termY, nil, black)
            runState = "List"
            drawCLI()
          elseif mcY == 4 then
            if mcX > 1 and mcX < 10 and mButton ~= 3 then
              if mButton == 1 then              --# Edit Turtle Name
                drawElement(2, 4, 1, 1, gray, black, allTurtles[thisTurtle].name)
                runState = "EditName"
              else                              --# Change color assignment
                drawColorList(allTurtles[thisTurtle].color)
                runState = "color"
              end
              return
            elseif mcX > 21 and mcX < 26 and mButton == 1 then --# Redstone output
              netSend(allTurtles[thisTurtle].cc, "rds", not allTurtles[thisTurtle].redstone)
            end
          elseif mcY == 6 and mButton == 1 then --# Edit Turtle Note
            if mcX > 1 then
              drawElement(2, 6, 1, 1, gray, black, allTurtles[thisTurtle].note)
              term.setBackgroundColor(black)
              runState = "EditNote"
              return
            end
          elseif mcY == 8 and mButton == 1 then --# Movement / Actions
            if mcX > 2 and mcX < 5 then         --# UP
              inputArrow("up")
            elseif mcX > 4 and mcX < 7 then     --# Forward
              inputArrow("forward")
            elseif mcX > 6 and mcX < 9 then     --# Place/Break
              execPLBR(allTurtles[thisTurtle].dirCheck)
            end
          elseif mcY == 9 and mButton == 1 then
            if mcX > 2 and mcX < 5 then         --# UP
              inputArrow("up")
            elseif mcX > 4 and mcX < 7 then     --# Forward
              inputArrow("forward")
            elseif mcX > 6 and mcX < 9 then     --# Tunnel
              execPLBR(allTurtles[thisTurtle].dirCheck)
            elseif mcX > 11 and mcX < termX and allTurtles[thisTurtle].fuelPercent < 101 then --# Fuel/Coal filter
              showFuel = not showFuel
              drawElement(12, 9, 1, 1, gray, black, showFuel and "Units " or "Coal ")
              term.setTextColor(silver)
              term.write(showFuel and (tostring(allTurtles[thisTurtle].fuelAmount)) or (tostring(allTurtles[thisTurtle].coal) .. "     "))
            end
          elseif mcY == 10 and mButton == 1 then
            if mcX > 2 and mcX < 5 then         --# Left
              inputArrow("left")
            elseif mcX > 4 and mcX < 7 then     --# Center
              runState = "shellCmd"
              return
            elseif mcX > 6 and mcX < 9 then     --# Right
              inputArrow("right")
            end
          elseif mcY == 11 and mButton == 1 then
            if mcX > 2 and mcX < 5 then         --# Left
              inputArrow("left")
            elseif mcX > 4 and mcX < 7 then     --# Center
              runState = "shellCmd"
              return
            elseif mcX > 6 and mcX < 9 then     --# Right
              inputArrow("right")
            elseif mcX > 11 and mcX < termX - 1 then --# Change direction of action
              tDirection = directions[tDirection]
              drawElement(17, 11, 1, 1, silver, black, tDirection .. "     ")
              if allTurtles[thisTurtle].dirCheck then
                if (tDirection == "Up" and allTurtles[thisTurtle].up) or (tDirection == "Down" and allTurtles[thisTurtle].down) or (tDirection == "Forward" and allTurtles[thisTurtle].forward) then
                  drawElement(7, 8, 2, 1, black, gray, "PL")  --# Place/Break
                  drawElement(7, 9, 2, 1, nil, red, "BR")
                else
                  drawElement(7, 8, 2, 1, black, green, "PL") --# Place/Break
                  drawElement(7, 9, 2, 1, nil, gray, "BR")
                end
              end
            end
          elseif mcY == 12 and mButton == 1 then
            if mcX > 2 and mcX < 5 then         --# DOWN
              inputArrow("down")
            elseif mcX > 4 and mcX < 7 then     --# Back
              inputArrow("back")
            elseif mcX > 6 and mcX < 9 then     --# Obstacle check
              netSend(allTurtles[thisTurtle].cc, "dcToggle", not allTurtles[thisTurtle].dirCheck)
            end
          elseif mcY == 13 and mButton == 1 then
            if mcX > 2 and mcX < 5 then         --# DOWN
              inputArrow("down")
            elseif mcX > 4 and mcX < 7 then     --# Back
              inputArrow("back")
            elseif mcX > 6 and mcX < 9 then     --# Obstacle check
              netSend(allTurtles[thisTurtle].cc, "dcToggle", not allTurtles[thisTurtle].dirCheck)
            elseif mcX == 12 or mcX == termX - 1 then --# Select inventory slot
              local selectedSlot = allTurtles[thisTurtle].slot
              selectedSlot = mcX == 12 and selectedSlot - 1 or selectedSlot + 1
              if selectedSlot < 1 then selectedSlot = 16 end
              if selectedSlot > 16 then selectedSlot = 1 end
              netSend(allTurtles[thisTurtle].cc, "sel", selectedSlot)
            elseif mcX > 13 and mcX < termX - 2 then --# Open inventory screen
              netSend(allTurtles[thisTurtle].cc, "inv", "inv")
              drawElement(1, 2, termX, termY - 1, nil, black)
              runState = "Inventory"
              drawCLI()
            end
          elseif (mcY == 15 or mcY == 16) and mButton == 1 then --# Refresh GPS fix
            if mcX > 15 and mcX < 25 then
              drawElement(16, 15, 9, 1, white, gray, "Refresh")
              drawElement(16, 16, 9, 1, nil, nil, "GPS Fix")
              sleep(0.1)
              drawElement(16, 15, 9, 1, black, nil, "Refresh")
              drawElement(16, 16, 9, 1, nil, nil, "GPS Fix")
              netSend(allTurtles[thisTurtle].cc, "gpsLoc")
            end
          elseif (mcY == 17 or mcY == 18) and mButton == 1 then
            if mcX > 15 and mcX < 25 and allTurtles[thisTurtle].loc.x ~= "No GPS Fix" then --# Export to Lyqyd's pocketgps
              drawElement(16, 17, 9, 1, white, silver, "Export")
              drawElement(16, 18, 9, 1, nil, nil, "to pgps")
              sleep(0.1)
              drawElement(16, 17, 9, 1, black, nil, "Export")
              drawElement(16, 18, 9, 1, nil, nil, "to pgps")
              local pgpsPlaces = fs.open("/.places", fs.exists("/.places") and "a" or "w")
              pgpsPlaces.writeLine(allTurtles[thisTurtle].loc.x .. ", " .. allTurtles[thisTurtle].loc.y .. ", " .. allTurtles[thisTurtle].loc.z .. ", " .. allTurtles[thisTurtle].name)
              pgpsPlaces.close()
            end
          end
        elseif runState == "Inventory" then
          if mcY == 2 and mButton == 1 then --# Close
            drawElement(1, 2, termX, termY - 1, nil, black)
            runState = "Remote"
            drawCLI()
          elseif mcY == 17 and mButton == 1 then
            if mcX > 12 and mcX < 20 then   --# Choose direction of action
              tDirection = directions[tDirection]
              drawElement(13, 17, 1, 1, silver, black, tDirection .. "     ")
            end
          elseif mcY == termY - 1 and mButton == 1 then --# Refresh inventory
            drawElement(math.floor(termX / 2) - 9, termY - 1, 1, 1, black, silver, " Refresh Inventory ")
            sleep(0.1)
            drawElement(math.floor(termX / 2) - 9, termY - 1, 1, 1, white, brown, " Refresh Inventory ")
            netSend(allTurtles[thisTurtle].cc, "inv", "inv")
          end
          if mcX < 15 and mButton == 1 then --# Inventory
            if invSelect[mcX] and invSelect[mcX][mcY] then
              netSend(allTurtles[thisTurtle].cc, "sel", invSelect[mcX][mcY])
            end
          elseif mcX > 14 then              --# Refuel/Place/Break/Equip
            if mcY == 7 then           --# Refuel
              drawElement(16, 7, 9, 1, white, gray, " Refuel")
              sleep(0.1)
              drawElement(16, 7, 9, 1, black, yellow, " Refuel")
              if mButton == 1 then
                netSend(allTurtles[thisTurtle].cc, "refuel", 1)
              elseif mButton == 2 then
                netSend(allTurtles[thisTurtle].cc, "refuel", 10)
              elseif mButton == 3 then
                netSend(allTurtles[thisTurtle].cc, "refuel", allTurtles[thisTurtle].inv[allTurtles[thisTurtle].slot].count)
              end
            elseif mcY == 9 and mButton ~= 3 then --# Place or engrave
              drawElement(16, 9, 9, 1, white, gray, "Place")
              sleep(0.1)
              drawElement(16, 9, 9, 1, black, green, "Place")
              if mButton == 1 then     --# Place
                netSend(allTurtles[thisTurtle].cc, "put", tDirection)
              elseif mButton == 2 and allTurtles[thisTurtle].contents == "sign" then --# Engrave
                runState = "engrave"
                return
              end
            elseif mcY == 11 and mButton ~= 3 then --# All units, break and attack!
              drawElement(16, 11, 9, 1, white, gray, "Break")
              sleep(0.1)
              drawElement(16, 11, 9, 1, black, red, "Break")
              if mButton == 1 then     --# Break
                netSend(allTurtles[thisTurtle].cc, "dig", tDirection)
              elseif mButton == 2 then --# Attack
                netSend(allTurtles[thisTurtle].cc, "atk", tDirection)
              end
            elseif mcY == 13 and mButton ~= 3 then --# Equip
              drawElement(16, 13, 9, 1, white, gray, "Equip")
              sleep(0.1)
              drawElement(16, 13, 9, 1, black, silver, "Equip")
              if mButton == 1 then     --# Equip left
                netSend(allTurtles[thisTurtle].cc, "eqp", "left")
              elseif mButton == 2 then --# Equip right
                netSend(allTurtles[thisTurtle].cc, "eqp", "right")
              end
            end
          end
        elseif runState == "List" then
          if mcY == 1 and mButton == 1 then
            if mcX > 0 and mcX < 4 then --# Fuel filter
              toggleFuelFilter()
            elseif mcX > termX - 3 and mcX <= termX then --# Quit
              kernelState = false
              if rednet.isOpen(netSide) then rednet.close(netSide) end
              term.setBackgroundColor(black)
              term.setTextColor(white)
              term.clear()
              term.setCursorPos(1, 1)
              return
            end
          elseif mcY == 2 and mButton == 1 then --# ALL ON/OFF
            netSend("ALL", "rds", mcX < termX / 2)
          elseif mcY > 3 and mcY < 19 then --# Open a turtle's properties/control screen
            magicNumber, xPos, yPos = ((pageNum - 1) * 23) + pageNum, 1, 4
            for i = magicNumber, math.min(#filteredTurtles, pageNum * 24) do
              if mcX >= xPos and mcX < xPos + 8 and mcY == yPos then
                thisTurtle = filteredTurtles[i].atList
                if not filteredTurtles[i].lockState then
                  if mButton == 1 or mButton == 2 then
                    netSend(allTurtles[thisTurtle].cc, "inv", "inv")
                    drawElement(1, 2, termX, termY - 1, nil, black)
                    turtleControlScreen()
                  elseif mButton == 3 then
                    netSend(allTurtles[thisTurtle].cc, "lock")
                  end
                  break
                else
                  runState = "password"
                  return
                end
              end
              yPos = yPos + 2
              if yPos > 19 then yPos = 4 xPos = xPos + 9 end
            end
          elseif mcY == termY and numPages > 1 and mButton == 1 then --# Page Navigation via click
            if mcX < math.floor(termX / 2) - 3 or mcX > math.floor(termX / 2) + 3 then --# Page Back / Forward
              pageNum = mcX < math.floor(termX / 2) - 3 and math.max(1, pageNum - 1) or math.min(pageNum + 1, numPages)
              if pageNum == numPages and numPages > 1 then drawElement(2, 4, termX, termY, nil, black) end
              drawMainUI()
              drawNaviUI()
            elseif mcX > math.floor(termX / 2) - 2 and mcX < math.floor(termX / 2) + 2 then --# Page Numbers (Go To Page dialogue)
              runState = "goPage"
              return
            end
          end
        end
      elseif event == "mouse_scroll" and runState == "List" then
        pageNum = mButton == 1 and math.min(pageNum + 1, numPages) or math.max(1, pageNum - 1)
        if pageNum == numPages and numPages > 1 then drawElement(2, 4, termX, termY, nil, black) end
        drawMainUI()
        drawNaviUI()
      elseif event == "key" then
        if mButton == keys.f1 then --# F1 / Help
          if runState ~= "Help" then
            tempState = runState
            runState = "Help"
            drawHelpUI()
          else
            runState = tempState
            drawElement(1, 3, termX, termY - 2, nil, black)
            drawCLI()
          end
        end
        if runState == "List" then
          if mButton == 33 then --# F / Fuel Filter
            toggleFuelFilter()
          end
        elseif runState == "Remote" then
          if keyList[mButton] then keyList[mButton]() end
        end
      end
    end
  end

  local function colorPicker()
    local choice = false
    local _, button, x, y = os.pullEvent("mouse_click")
    if x > 6 and x < 17 and y > 3 and y < 12 and button == 1 then
      for colorChoice = 4, 11 do
        if y == colorChoice then
          for k, v in pairs(colorBurst) do
            if (colorChoice - 3) == v.order then
              netSend(allTurtles[thisTurtle].cc, "color", k)
              choice = true
              break
            end
          end
        end
        if choice then break end
      end
    end
    drawElement(7, 4, 10, 8, nil, black)
    turtleControlScreen()
  end

  local function editEntry()
    if runState == "EditName" then
      drawElement(2, 4, 1, 1, gray, black, allTurtles[thisTurtle].name)
      local newName = readInput(2, 4, yellow, nil, 8)
      if newName ~= "" then
        netSend(allTurtles[thisTurtle].cc, "name", newName)
        drawElement(2, 4, 8, 1)
      else
        drawElement(2, 4, 1, 1, assignColor(allTurtles[thisTurtle].color), nil, allTurtles[thisTurtle].name)
      end
    elseif runState == "EditNote" then
      drawElement(2, 6, 1, 1, gray, black, allTurtles[thisTurtle].note)
      local newNote = readInput(2, 6, white, nil, 21)
      if newNote ~= "" then
        netSend(allTurtles[thisTurtle].cc, "note", newNote)
        drawElement(2, 6, 21, 1)
      else
        drawElement(2, 6, 1, 1, silver, nil, allTurtles[thisTurtle].note)
      end
    end
    runState = "Remote"
  end

  local function enterPassword()
    drawElement(math.floor(termX / 2) - 7, 8, 15, 1, white, gray, "   Password:   ")
    drawElement(math.floor(termX / 2) - 7, 9, 15, 2)
    drawElement(math.floor(termX / 2) - 6, 9, 13, 1, nil, black) --# input area bg
    local pass = readInput(math.floor(termX / 2) - 6, 9, lime, nil, 13, "*")
    if pass ~= "" then
      local pw = table.concat(pbkdf2(pass, "gt!Remote", 15))
      netSend(allTurtles[thisTurtle].cc, "unlock", pw)
    end
    drawElement(math.floor(termX / 2) - 7, 8, 15, 3)
    runState = "List"
    drawMainUI()
  end

  local function goToPage()
    drawElement(math.floor(termX / 2) - 3, termY - 3, 7, 1, white, gray, "Page:")
    drawElement(math.floor(termX / 2) - 3, termY - 2, 7, 2)
    drawElement(math.floor(termX / 2) - 2, termY - 2, 5, 1, nil, black) --# input area bg
    local newPage = tonumber(readInput(math.floor(termX / 2) - 1, termY - 2, lime, nil, 3))
    pageNum = newPage or pageNum
    pageNum = math.max(1, math.min(pageNum, numPages))
    if pageNum == numPages and numPages > 1 then
      drawElement(1, 4, termX, termY - 3)
    else
      drawElement(math.floor(termX / 2) - 3, termY - 3, 7, 3)
    end
    runState = "List"
    drawMainUI()
    drawNaviUI()
  end

  local function getSignText()
    drawElement(math.floor(termX / 2) - 8, math.floor(termY / 2) - 1, 17, 1, black, silver, "Engrave")
    drawElement(math.floor(termX / 2) - 8, math.floor(termY / 2), 17, 2)
    drawElement(math.floor(termX / 2) - 7, math.floor(termY / 2), 15, 1, nil, black) --# input area bg
    local newText = readInput(math.floor(termX / 2) - 7, math.floor(termY / 2), lime)
    if newText ~= "" then
      netSend(allTurtles[thisTurtle].cc, "put", newText)
    end
    drawElement(math.floor(termX / 2) - 8, math.floor(termY / 2) - 1, 17, 3)
    runState = "Inventory"
    staticTurtleInventory()
  end

  local function shellCommand()
    drawElement(2, 8, termX - 2, 1, white, gray, "Command:")
    drawElement(2, 9, termX - 2, 2)
    drawElement(3, 9, termX - 4, 1, nil, black) --# input area bg
    local sCommand = readInput(3, 9, lime, nil, 22)
    if sCommand ~= "" then
      netSend(allTurtles[thisTurtle].cc, "sCmd", sCommand)
    end
    drawElement(2, 8, termX - 2, 3)
    turtleControlScreen()
  end

  local runStates = {
    List = function() inputMain() end;
    Remote = function() inputMain() end;
    Inventory = function() inputMain() end;
    Help = function() inputMain() end;
    EditName = function() editEntry() end;
    EditNote = function() editEntry() end;
    password = function() enterPassword() end;
    engrave = function() getSignText() end;
    shellCmd = function() shellCommand() end;
    goPage = function() goToPage() end;
    color = function() colorPicker() end;
  }

  userInput = function()
    repeat
      if runStates[runState] then runStates[runState]() end
      if kernelState and timerDelay and not inputting then
        timerDelay = false
        pollTimer = os.startTimer(0.1)
      end
    until not kernelState
  end
end

local function dataPoller()
  local removal, _, timer = false
  while true do
    _, timer = os.pullEvent("timer")
    if timer == pollTimer then
      if runState == "List" then
        for i = #allTurtles, 1, -1 do
          if allTurtles[i].quietCount > 1 then
            table.remove(allTurtles, i)
            removal = true
          else
            allTurtles[i].quietCount = allTurtles[i].quietCount + 1
          end
        end
        if removal then
          removal = false
          filterTurtleList()
          drawElement(1, 4, termX, termY - 3, nil, black)
          drawMainUI()
          drawNaviUI()
        end
      end
      if not timerDelay then
        netSend("ALL", "gtrQRY", "gtrQRY")
        pollTimer = os.startTimer(5)
      end
    end
  end
end

term.setBackgroundColor(black)
term.clear()
if not term.isColor() or not pocket then term.setCursorPos(1, 1) error("Advanced Wireless Pocket Computer Required", 0) end
drawElement(2, 2, 1, 1, white, nil, "Initializing . . .")
if not os.getComputerLabel() then os.setComputerLabel("PocketPC.ID#" .. thisCC) end
if peripheral.isPresent("back") and peripheral.getType("back") == "modem" and peripheral.call("back", "isWireless") then
  rednet.open("back")
  netSide = "back"
else
  term.clear()
  drawElement(2, 2, 1, 1, red, nil, "No wireless")
  drawElement(2, 3, 1, 1, nil, nil, "modem detected!")
  drawElement(2, 5, 1, 1, nil, nil, "gtRemote REQUIRES")
  drawElement(2, 6, 1, 1, nil, nil, "a wireless modem.")
  term.setCursorPos(1, 9)
  return
end
runState = "List"
drawElement(2, 4, 1, 1, white, nil, "Searching for hosts . . .")
netSend("ALL", "gtrQRY", "Full")
pollTimer = os.startTimer(5)
term.clear()
drawCLI()
parallel.waitForAny(userInput, netReceive, dataPoller)