HydrantHunter

ccDialer 2.0 for Plethora Neural Interface

Feb 8th, 2019
371
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 103.45 KB | None | 0 0
  1. --[[  LanteaCraft      ]]--
  2. --[[      and SGCraft  ]]--
  3. --[[     ccDialer      ]]--
  4. --[[      by Dog       ]]--
  5. --[[ aka HydrantHunter ]]--
  6. --[[   for Plethora    ]]--
  7. --[[   by  SquidDev    ]]--
  8. --[[ pastebin VgUMbZyF ]]--
  9. local ccDialVer = "2.0.00"
  10. --[[
  11. Tested with/requires:
  12.   - Minecraft 1.7.10+ AND ComputerCraft 1.75+ || LanteaCraft LC2-16+ | OR | SGCraft1.11.x-mc1.7.10+
  13.   For setup and usage instructions, please visit http://tinyurl.com/jf3rjr7
  14.  
  15. Special thanks to: theoriginalbit (custom read function)
  16.                    Anavrins       (pbkdf2/sha256 hashing)
  17.                    SquidDev       (AES encryption/decryption)
  18.                    Alex Kloss     (base64 encoder/decoder)
  19.  
  20. IMPORTANT NOTE:
  21.   - all of the following variables are set by the program as necessary
  22.   - editing any of them below will most likely cause unexpected results
  23. ]]--
  24. --# AUTOMATIC/STATIC CONFIGURATION
  25. --# Peripherals
  26. local termX, termY = term.getSize() --# 39 x 13 (39 x 12 tab)
  27. local modemSide, thisCC = "none", tostring(os.getComputerID())
  28. --# Status Info
  29. local runState, dialAddress, remoteIrisState = "init", "none", "unk"
  30. local drawCLI, drawAddressBook, drawElement, pNum, currentEdit, word, curX, curY, bgCol, txtCol, pingTimer
  31. local missedPings = 0
  32. local hosts, host = { }, { id = nil, name = "NO HOST", addr = "NO HOST", ccDHD = false, status = nil }
  33. local gettingInput = false
  34. --# Address Book
  35. local addressBook = { { name = "NO GATES", addr = "ADDRESS", rating = "U", iris = "none", callDrop = false, note = "Short note", loc = { x = 0, y = 0, z = 0, dim = "Unspecified", }, }, }
  36. local gateChange = false
  37. local gatePages, gatePage, syncPages, syncPage, abCount = 1, 1, 1, 1, 1
  38. local classifications = {
  39.   B = { order = 1, color = colors.blue, label = "Base/Outpost/Hub" };
  40.   H = { order = 2, color = colors.lightBlue, label = "Home/Camp" };
  41.   V = { order = 3, color = colors.brown, label = "Village" };
  42.   M = { order = 4, color = colors.purple, label = "Misc/Special" };
  43.   S = { order = 5, color = colors.green, label = "Safe/Secured" };
  44.   C = { order = 6, color = colors.orange, label = "Caution" };
  45.   D = { order = 7, color = colors.red, label = "Danger" };
  46.   U = { order = 8, color = colors.lightGray, label = "Unk/Unclassified", long = "Unknown/Unclassified" };
  47. }
  48. --# Color Definitions
  49. local white = colors.white
  50. local silver = colors.lightGray
  51. local gray = colors.gray
  52. local black = colors.black
  53. local brown = colors.brown
  54. local yellow = colors.yellow
  55. local orange = colors.orange
  56. local red = colors.red
  57. --local magenta = colors.magenta
  58. --local purple = colors.purple
  59. local blue = colors.blue
  60. local sky = colors.lightBlue
  61. local cyan = colors.cyan
  62. --local lime = colors.lime
  63. local green = colors.green
  64. --# END AUTOMATIC/STATIC CONFIGURATION
  65.  
  66. -- Lua 5.1+ base64 v3.0 (c) 2009 by Alex Kloss <alexthkloss@web.de>
  67. -- licensed under the terms of the LGPL2
  68. -- http://lua-users.org/wiki/BaseSixtyFour
  69. -- character table string
  70. local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
  71. -- encoding
  72. local function encode(data)
  73.   return ((data:gsub('.', function(x)
  74.     local r,b='',x:byte()
  75.     for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end
  76.     return r;
  77.   end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
  78.     if (#x < 6) then return '' end
  79.     local c=0
  80.     for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end
  81.     return b:sub(c+1,c+1)
  82.   end)..({ '', '==', '=' })[#data%3+1])
  83. end
  84. -- decoding
  85. local function decode(data)
  86.   data = string.gsub(data, '[^'..b..'=]', '')
  87.   return (data:gsub('.', function(x)
  88.     if (x == '=') then return '' end
  89.     local r,f='',(b:find(x)-1)
  90.     for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end
  91.     return r;
  92.   end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
  93.     if (#x ~= 8) then return '' end
  94.     local c=0
  95.     for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end
  96.     return string.char(c)
  97.   end))
  98. end
  99.  
  100. -- AES Lua implementation by SquidDev
  101. -- https://gist.github.com/SquidDev/86925e07cbabd70773e53d781bd8b2fe
  102. local encrypt, decrypt
  103. do
  104.   local function _W(f) local e=setmetatable({}, {__index = _ENV or getfenv()}) if setfenv then setfenv(f, e) end return f(e) or e end
  105.   local bit=_W(function(_ENV, ...)
  106.   --[[
  107.     This bit API is designed to cope with unsigned integers instead of normal integers
  108.     To do this we add checks for overflows: (x > 2^31 ? x - 2 ^ 32 : x)
  109.     These are written in long form because no constant folding.
  110.   ]]
  111.   local floor = math.floor
  112.   local lshift, rshift
  113.  
  114.   rshift = function(a,disp)
  115.     return floor(a % 4294967296 / 2^disp)
  116.   end
  117.  
  118.   lshift = function(a,disp)
  119.     return (a * 2^disp) % 4294967296
  120.   end
  121.  
  122.   return {
  123.     -- bit operations
  124.     bnot = bit32 and bit32.bnot or bit.bnot,
  125.     band = bit32 and bit32.band or bit.band,
  126.     bor  = bit32 and bit32.bor or bit.bor,
  127.     bxor = bit32 and bit32.bxor or bit.bxor,
  128.     rshift = rshift,
  129.     lshift = lshift,
  130.   }
  131.   end)
  132.  
  133.   local gf=_W(function(_ENV, ...)
  134.   -- finite field with base 2 and modulo irreducible polynom x^8+x^4+x^3+x+1 = 0x11d
  135.   local bxor = bit32 and bit32.bxor or bit.bxor
  136.   local lshift = bit.lshift
  137.   -- private data of gf
  138.   local n = 0x100
  139.   local ord = 0xff
  140.   local irrPolynom = 0x11b
  141.   local exp, log = {}, {}
  142.   --
  143.   -- add two polynoms (its simply xor)
  144.   --
  145.   local function add(operand1, operand2)
  146.     return bxor(operand1,operand2)
  147.   end
  148.   --
  149.   -- subtract two polynoms (same as addition)
  150.   --
  151.   local function sub(operand1, operand2)
  152.     return bxor(operand1,operand2)
  153.   end
  154.   --
  155.   -- inverts element
  156.   -- a^(-1) = g^(order - log(a))
  157.   --
  158.   local function invert(operand)
  159.     -- special case for 1 or normal invert
  160.     return operand == 1 and 1 or exp[ord - log[operand]]
  161.   end
  162.   --
  163.   -- multiply two elements using a logarithm table
  164.   -- a*b = g^(log(a)+log(b))
  165.   --
  166.   local function mul(operand1, operand2)
  167.     if (operand1 == 0 or operand2 == 0) then
  168.       return 0
  169.     end
  170.     local exponent = log[operand1] + log[operand2]
  171.     if (exponent >= ord) then
  172.       exponent = exponent - ord
  173.     end
  174.     return exp[exponent]
  175.   end
  176.   --
  177.   -- divide two elements
  178.   -- a/b = g^(log(a)-log(b))
  179.   --
  180.   local function div(operand1, operand2)
  181.     if (operand1 == 0)  then
  182.       return 0
  183.     end
  184.     -- TODO: exception if operand2 == 0
  185.     local exponent = log[operand1] - log[operand2]
  186.     if (exponent < 0) then
  187.       exponent = exponent + ord
  188.     end
  189.     return exp[exponent]
  190.   end
  191.   --
  192.   -- print logarithmic table
  193.   --
  194.   local function printLog()
  195.     for i = 1, n do
  196.       print("log(", i-1, ")=", log[i-1])
  197.     end
  198.   end
  199.   --
  200.   -- print exponentiation table
  201.   --
  202.   local function printExp()
  203.     for i = 1, n do
  204.       print("exp(", i-1, ")=", exp[i-1])
  205.     end
  206.   end
  207.   --
  208.   -- calculate logarithmic and exponentiation table
  209.   --
  210.   local function initMulTable()
  211.     local a = 1
  212.     for i = 0,ord-1 do
  213.       exp[i] = a
  214.       log[a] = i
  215.       -- multiply with generator x+1 -> left shift + 1
  216.       a = bxor(lshift(a, 1), a)
  217.       -- if a gets larger than order, reduce modulo irreducible polynom
  218.       if a > ord then
  219.         a = sub(a, irrPolynom)
  220.       end
  221.     end
  222.   end
  223.  
  224.   initMulTable()
  225.  
  226.   return {
  227.     add = add,
  228.     sub = sub,
  229.     invert = invert,
  230.     mul = mul,
  231.     div = div,
  232.     printLog = printLog,
  233.     printExp = printExp,
  234.   }
  235.   end)
  236.  
  237.   util=_W(function(_ENV, ...)
  238.   -- Cache some bit operators
  239.   local bxor = bit.bxor
  240.   local rshift = bit.rshift
  241.   local band = bit.band
  242.   local lshift = bit.lshift
  243.   local sleepCheckIn
  244.   --
  245.   -- calculate the parity of one byte
  246.   --
  247.   local function byteParity(byte)
  248.     byte = bxor(byte, rshift(byte, 4))
  249.     byte = bxor(byte, rshift(byte, 2))
  250.     byte = bxor(byte, rshift(byte, 1))
  251.     return band(byte, 1)
  252.   end
  253.   --
  254.   -- get byte at position index
  255.   --
  256.   local function getByte(number, index)
  257.     return index == 0 and band(number,0xff) or band(rshift(number, index*8),0xff)
  258.   end
  259.   --
  260.   -- put number into int at position index
  261.   --
  262.   local function putByte(number, index)
  263.     return index == 0 and band(number,0xff) or lshift(band(number,0xff),index*8)
  264.   end
  265.   --
  266.   -- convert byte array to int array
  267.   --
  268.   local function bytesToInts(bytes, start, n)
  269.     local ints = {}
  270.     for i = 0, n - 1 do
  271.       ints[i + 1] =
  272.           putByte(bytes[start + (i*4)], 3) +
  273.           putByte(bytes[start + (i*4) + 1], 2) +
  274.           putByte(bytes[start + (i*4) + 2], 1) +
  275.           putByte(bytes[start + (i*4) + 3], 0)
  276.       if n % 10000 == 0 then sleepCheckIn() end
  277.     end
  278.     return ints
  279.   end
  280.   --
  281.   -- convert int array to byte array
  282.   --
  283.   local function intsToBytes(ints, output, outputOffset, n)
  284.     n = n or #ints
  285.     for i = 0, n - 1 do
  286.       for j = 0,3 do
  287.         output[outputOffset + i*4 + (3 - j)] = getByte(ints[i + 1], j)
  288.       end
  289.       if n % 10000 == 0 then sleepCheckIn() end
  290.     end
  291.     return output
  292.   end
  293.   --
  294.   -- convert bytes to hexString
  295.   --
  296.   local function bytesToHex(bytes)
  297.     local hexBytes = ""
  298.     for i,byte in ipairs(bytes) do
  299.       hexBytes = hexBytes .. string.format("%02x ", byte)
  300.     end
  301.     return hexBytes
  302.   end
  303.  
  304.   local function hexToBytes(bytes)
  305.     local out = {}
  306.     for i = 1, #bytes, 2 do
  307.       out[#out + 1] = tonumber(bytes:sub(i, i + 1), 16)
  308.     end
  309.     return out
  310.   end
  311.   --
  312.   -- convert data to hex string
  313.   --
  314.   local function toHexString(data)
  315.     local type = type(data)
  316.     if (type == "number") then
  317.       return string.format("%08x",data)
  318.     elseif (type == "table") then
  319.       return bytesToHex(data)
  320.     elseif (type == "string") then
  321.       local bytes = {string.byte(data, 1, #data)}
  322.       return bytesToHex(bytes)
  323.     else
  324.       return data
  325.     end
  326.   end
  327.  
  328.   local function padByteString(data)
  329.     local dataLength = #data
  330.     local random1 = math.random(0,255)
  331.     local random2 = math.random(0,255)
  332.     local prefix = string.char(random1,
  333.       random2,
  334.       random1,
  335.       random2,
  336.       getByte(dataLength, 3),
  337.       getByte(dataLength, 2),
  338.       getByte(dataLength, 1),
  339.       getByte(dataLength, 0)
  340.     )
  341.     data = prefix .. data
  342.     local padding, paddingLength = "", math.ceil(#data/16)*16 - #data
  343.     for i=1,paddingLength do
  344.       padding = padding .. string.char(math.random(0,255))
  345.     end
  346.     return data .. padding
  347.   end
  348.  
  349.   local function properlyDecrypted(data)
  350.     local random = {string.byte(data,1,4)}
  351.     if (random[1] == random[3] and random[2] == random[4]) then
  352.       return true
  353.     end
  354.     return false
  355.   end
  356.  
  357.   local function unpadByteString(data)
  358.     if (not properlyDecrypted(data)) then
  359.       return nil
  360.     end
  361.     local dataLength = putByte(string.byte(data,5), 3)
  362.              + putByte(string.byte(data,6), 2)
  363.              + putByte(string.byte(data,7), 1)
  364.              + putByte(string.byte(data,8), 0)
  365.     return string.sub(data,9,8+dataLength)
  366.   end
  367.  
  368.   local function xorIV(data, iv)
  369.     for i = 1,16 do
  370.       data[i] = bxor(data[i], iv[i])
  371.     end
  372.   end
  373.  
  374.   local function increment(data)
  375.     local i = 16
  376.     while true do
  377.       local value = data[i] + 1
  378.       if value >= 256 then
  379.         data[i] = value - 256
  380.         i = (i - 2) % 16 + 1
  381.       else
  382.         data[i] = value
  383.         break
  384.       end
  385.     end
  386.   end
  387.  
  388.   -- Called every encryption cycle
  389.   local push, pull, time = os.queueEvent, coroutine.yield, os.time
  390.   local oldTime = time()
  391.   local function sleepCheckIn()
  392.     local newTime = time()
  393.     if newTime - oldTime >= 0.03 then -- (0.020 * 1.5)
  394.       oldTime = newTime
  395.       push("sleep")
  396.       pull("sleep")
  397.     end
  398.   end
  399.  
  400.   local function getRandomData(bytes)
  401.     local char, random, sleep, insert = string.char, math.random, sleepCheckIn, table.insert
  402.     local result = {}
  403.     for i=1,bytes do
  404.       insert(result, random(0,255))
  405.       if i % 10240 == 0 then sleep() end
  406.     end
  407.     return result
  408.   end
  409.  
  410.   local function getRandomString(bytes)
  411.     local char, random, sleep, insert = string.char, math.random, sleepCheckIn, table.insert
  412.     local result = {}
  413.     for i=1,bytes do
  414.       insert(result, char(random(0,255)))
  415.       if i % 10240 == 0 then sleep() end
  416.     end
  417.     return table.concat(result)
  418.   end
  419.  
  420.   return {
  421.     byteParity = byteParity,
  422.     getByte = getByte,
  423.     putByte = putByte,
  424.     bytesToInts = bytesToInts,
  425.     intsToBytes = intsToBytes,
  426.     bytesToHex = bytesToHex,
  427.     hexToBytes = hexToBytes,
  428.     toHexString = toHexString,
  429.     padByteString = padByteString,
  430.     properlyDecrypted = properlyDecrypted,
  431.     unpadByteString = unpadByteString,
  432.     xorIV = xorIV,
  433.     increment = increment,
  434.     sleepCheckIn = sleepCheckIn,
  435.     getRandomData = getRandomData,
  436.     getRandomString = getRandomString,
  437.   }
  438.   end)
  439.  
  440.   aes=_W(function(_ENV, ...)
  441.   -- Implementation of AES with nearly pure lua
  442.   -- AES with lua is slow, really slow :-)
  443.   local putByte = util.putByte
  444.   local getByte = util.getByte
  445.   -- some constants
  446.   local ROUNDS = 'rounds'
  447.   local KEY_TYPE = "type"
  448.   local ENCRYPTION_KEY=1
  449.   local DECRYPTION_KEY=2
  450.   -- aes SBOX
  451.   local SBox = {}
  452.   local iSBox = {}
  453.   -- aes tables
  454.   local table0 = {}
  455.   local table1 = {}
  456.   local table2 = {}
  457.   local table3 = {}
  458.   local tableInv0 = {}
  459.   local tableInv1 = {}
  460.   local tableInv2 = {}
  461.   local tableInv3 = {}
  462.   -- round constants
  463.   local rCon = {
  464.     0x01000000,
  465.     0x02000000,
  466.     0x04000000,
  467.     0x08000000,
  468.     0x10000000,
  469.     0x20000000,
  470.     0x40000000,
  471.     0x80000000,
  472.     0x1b000000,
  473.     0x36000000,
  474.     0x6c000000,
  475.     0xd8000000,
  476.     0xab000000,
  477.     0x4d000000,
  478.     0x9a000000,
  479.     0x2f000000,
  480.   }
  481.   --
  482.   -- affine transformation for calculating the S-Box of AES
  483.   --
  484.   local function affinMap(byte)
  485.     mask = 0xf8
  486.     result = 0
  487.     for i = 1,8 do
  488.       result = bit.lshift(result,1)
  489.       parity = util.byteParity(bit.band(byte,mask))
  490.       result = result + parity
  491.       -- simulate roll
  492.       lastbit = bit.band(mask, 1)
  493.       mask = bit.band(bit.rshift(mask, 1),0xff)
  494.       mask = lastbit ~= 0 and bit.bor(mask, 0x80) or bit.band(mask, 0x7f)
  495.     end
  496.     return bit.bxor(result, 0x63)
  497.   end
  498.   --
  499.   -- calculate S-Box and inverse S-Box of AES
  500.   -- apply affine transformation to inverse in finite field 2^8
  501.   --
  502.   local function calcSBox()
  503.     for i = 0, 255 do
  504.       inverse = i ~= 0 and gf.invert(i) or 0
  505.       mapped = affinMap(inverse)
  506.       SBox[i] = mapped
  507.       iSBox[mapped] = i
  508.     end
  509.   end
  510.   --
  511.   -- Calculate round tables
  512.   -- round tables are used to calculate shiftRow, MixColumn and SubBytes
  513.   -- with 4 table lookups and 4 xor operations.
  514.   --
  515.   local function calcRoundTables()
  516.     for x = 0,255 do
  517.       byte = SBox[x]
  518.       table0[x] = putByte(gf.mul(0x03, byte), 0)
  519.                 + putByte(             byte , 1)
  520.                 + putByte(             byte , 2)
  521.                 + putByte(gf.mul(0x02, byte), 3)
  522.       table1[x] = putByte(             byte , 0)
  523.                 + putByte(             byte , 1)
  524.                 + putByte(gf.mul(0x02, byte), 2)
  525.                 + putByte(gf.mul(0x03, byte), 3)
  526.       table2[x] = putByte(             byte , 0)
  527.                 + putByte(gf.mul(0x02, byte), 1)
  528.                 + putByte(gf.mul(0x03, byte), 2)
  529.                 + putByte(             byte , 3)
  530.       table3[x] = putByte(gf.mul(0x02, byte), 0)
  531.                 + putByte(gf.mul(0x03, byte), 1)
  532.                 + putByte(             byte , 2)
  533.                 + putByte(             byte , 3)
  534.     end
  535.   end
  536.   --
  537.   -- Calculate inverse round tables
  538.   -- does the inverse of the normal roundtables for the equivalent
  539.   -- decryption algorithm.
  540.   --
  541.   local function calcInvRoundTables()
  542.     for x = 0,255 do
  543.       byte = iSBox[x]
  544.       tableInv0[x] = putByte(gf.mul(0x0b, byte), 0)
  545.                  + putByte(gf.mul(0x0d, byte), 1)
  546.                  + putByte(gf.mul(0x09, byte), 2)
  547.                  + putByte(gf.mul(0x0e, byte), 3)
  548.       tableInv1[x] = putByte(gf.mul(0x0d, byte), 0)
  549.                  + putByte(gf.mul(0x09, byte), 1)
  550.                  + putByte(gf.mul(0x0e, byte), 2)
  551.                  + putByte(gf.mul(0x0b, byte), 3)
  552.       tableInv2[x] = putByte(gf.mul(0x09, byte), 0)
  553.                  + putByte(gf.mul(0x0e, byte), 1)
  554.                  + putByte(gf.mul(0x0b, byte), 2)
  555.                  + putByte(gf.mul(0x0d, byte), 3)
  556.       tableInv3[x] = putByte(gf.mul(0x0e, byte), 0)
  557.                  + putByte(gf.mul(0x0b, byte), 1)
  558.                  + putByte(gf.mul(0x0d, byte), 2)
  559.                  + putByte(gf.mul(0x09, byte), 3)
  560.     end
  561.   end
  562.   --
  563.   -- rotate word: 0xaabbccdd gets 0xbbccddaa
  564.   -- used for key schedule
  565.   --
  566.   local function rotWord(word)
  567.     local tmp = bit.band(word,0xff000000)
  568.     return (bit.lshift(word,8) + bit.rshift(tmp,24))
  569.   end
  570.   --
  571.   -- replace all bytes in a word with the SBox.
  572.   -- used for key schedule
  573.   --
  574.   local function subWord(word)
  575.     return putByte(SBox[getByte(word,0)],0)
  576.       + putByte(SBox[getByte(word,1)],1)
  577.       + putByte(SBox[getByte(word,2)],2)
  578.       + putByte(SBox[getByte(word,3)],3)
  579.   end
  580.   --
  581.   -- generate key schedule for aes encryption
  582.   --
  583.   -- returns table with all round keys and
  584.   -- the necessary number of rounds saved in [ROUNDS]
  585.   --
  586.   local function expandEncryptionKey(key)
  587.     local keySchedule = {}
  588.     local keyWords = math.floor(#key / 4)
  589.     if ((keyWords ~= 4 and keyWords ~= 6 and keyWords ~= 8) or (keyWords * 4 ~= #key)) then
  590.       error("Invalid key size: " .. tostring(keyWords))
  591.       return nil
  592.     end
  593.     keySchedule[ROUNDS] = keyWords + 6
  594.     keySchedule[KEY_TYPE] = ENCRYPTION_KEY
  595.     for i = 0,keyWords - 1 do
  596.       keySchedule[i] = putByte(key[i*4+1], 3)
  597.                + putByte(key[i*4+2], 2)
  598.                + putByte(key[i*4+3], 1)
  599.                + putByte(key[i*4+4], 0)
  600.     end
  601.     for i = keyWords, (keySchedule[ROUNDS] + 1)*4 - 1 do
  602.       local tmp = keySchedule[i-1]
  603.       if ( i % keyWords == 0) then
  604.         tmp = rotWord(tmp)
  605.         tmp = subWord(tmp)
  606.         local index = math.floor(i/keyWords)
  607.         tmp = bit.bxor(tmp,rCon[index])
  608.       elseif (keyWords > 6 and i % keyWords == 4) then
  609.         tmp = subWord(tmp)
  610.       end
  611.       keySchedule[i] = bit.bxor(keySchedule[(i-keyWords)],tmp)
  612.     end
  613.     return keySchedule
  614.   end
  615.   --
  616.   -- Inverse mix column
  617.   -- used for key schedule of decryption key
  618.   --
  619.   local function invMixColumnOld(word)
  620.     local b0 = getByte(word,3)
  621.     local b1 = getByte(word,2)
  622.     local b2 = getByte(word,1)
  623.     local b3 = getByte(word,0)
  624.     return putByte(gf.add(gf.add(gf.add(gf.mul(0x0b, b1),
  625.                          gf.mul(0x0d, b2)),
  626.                          gf.mul(0x09, b3)),
  627.                          gf.mul(0x0e, b0)),3)
  628.        + putByte(gf.add(gf.add(gf.add(gf.mul(0x0b, b2),
  629.                          gf.mul(0x0d, b3)),
  630.                          gf.mul(0x09, b0)),
  631.                          gf.mul(0x0e, b1)),2)
  632.        + putByte(gf.add(gf.add(gf.add(gf.mul(0x0b, b3),
  633.                          gf.mul(0x0d, b0)),
  634.                          gf.mul(0x09, b1)),
  635.                          gf.mul(0x0e, b2)),1)
  636.        + putByte(gf.add(gf.add(gf.add(gf.mul(0x0b, b0),
  637.                          gf.mul(0x0d, b1)),
  638.                          gf.mul(0x09, b2)),
  639.                          gf.mul(0x0e, b3)),0)
  640.   end
  641.   --
  642.   -- Optimized inverse mix column
  643.   -- look at http://fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.311.pdf
  644.   -- TODO: make it work
  645.   --
  646.   local function invMixColumn(word)
  647.     local b0 = getByte(word,3)
  648.     local b1 = getByte(word,2)
  649.     local b2 = getByte(word,1)
  650.     local b3 = getByte(word,0)
  651.     local t = bit.bxor(b3,b2)
  652.     local u = bit.bxor(b1,b0)
  653.     local v = bit.bxor(t,u)
  654.     v = bit.bxor(v,gf.mul(0x08,v))
  655.     w = bit.bxor(v,gf.mul(0x04, bit.bxor(b2,b0)))
  656.     v = bit.bxor(v,gf.mul(0x04, bit.bxor(b3,b1)))
  657.     return putByte( bit.bxor(bit.bxor(b3,v), gf.mul(0x02, bit.bxor(b0,b3))), 0)
  658.        + putByte( bit.bxor(bit.bxor(b2,w), gf.mul(0x02, t              )), 1)
  659.        + putByte( bit.bxor(bit.bxor(b1,v), gf.mul(0x02, bit.bxor(b0,b3))), 2)
  660.        + putByte( bit.bxor(bit.bxor(b0,w), gf.mul(0x02, u              )), 3)
  661.   end
  662.   --
  663.   -- generate key schedule for aes decryption
  664.   --
  665.   -- uses key schedule for aes encryption and transforms each
  666.   -- key by inverse mix column.
  667.   --
  668.   local function expandDecryptionKey(key)
  669.     local keySchedule = expandEncryptionKey(key)
  670.     if (keySchedule == nil) then
  671.       return nil
  672.     end
  673.     keySchedule[KEY_TYPE] = DECRYPTION_KEY
  674.     for i = 4, (keySchedule[ROUNDS] + 1)*4 - 5 do
  675.       keySchedule[i] = invMixColumnOld(keySchedule[i])
  676.     end
  677.     return keySchedule
  678.   end
  679.   --
  680.   -- xor round key to state
  681.   --
  682.   local function addRoundKey(state, key, round)
  683.     for i = 0, 3 do
  684.       state[i + 1] = bit.bxor(state[i + 1], key[round*4+i])
  685.     end
  686.   end
  687.   --
  688.   -- do encryption round (ShiftRow, SubBytes, MixColumn together)
  689.   --
  690.   local function doRound(origState, dstState)
  691.     dstState[1] =  bit.bxor(bit.bxor(bit.bxor(
  692.           table0[getByte(origState[1],3)],
  693.           table1[getByte(origState[2],2)]),
  694.           table2[getByte(origState[3],1)]),
  695.           table3[getByte(origState[4],0)])
  696.     dstState[2] =  bit.bxor(bit.bxor(bit.bxor(
  697.           table0[getByte(origState[2],3)],
  698.           table1[getByte(origState[3],2)]),
  699.           table2[getByte(origState[4],1)]),
  700.           table3[getByte(origState[1],0)])
  701.     dstState[3] =  bit.bxor(bit.bxor(bit.bxor(
  702.           table0[getByte(origState[3],3)],
  703.           table1[getByte(origState[4],2)]),
  704.           table2[getByte(origState[1],1)]),
  705.           table3[getByte(origState[2],0)])
  706.     dstState[4] =  bit.bxor(bit.bxor(bit.bxor(
  707.           table0[getByte(origState[4],3)],
  708.           table1[getByte(origState[1],2)]),
  709.           table2[getByte(origState[2],1)]),
  710.           table3[getByte(origState[3],0)])
  711.   end
  712.   --
  713.   -- do last encryption round (ShiftRow and SubBytes)
  714.   --
  715.   local function doLastRound(origState, dstState)
  716.     dstState[1] = putByte(SBox[getByte(origState[1],3)], 3)
  717.           + putByte(SBox[getByte(origState[2],2)], 2)
  718.           + putByte(SBox[getByte(origState[3],1)], 1)
  719.           + putByte(SBox[getByte(origState[4],0)], 0)
  720.     dstState[2] = putByte(SBox[getByte(origState[2],3)], 3)
  721.           + putByte(SBox[getByte(origState[3],2)], 2)
  722.           + putByte(SBox[getByte(origState[4],1)], 1)
  723.           + putByte(SBox[getByte(origState[1],0)], 0)
  724.     dstState[3] = putByte(SBox[getByte(origState[3],3)], 3)
  725.           + putByte(SBox[getByte(origState[4],2)], 2)
  726.           + putByte(SBox[getByte(origState[1],1)], 1)
  727.           + putByte(SBox[getByte(origState[2],0)], 0)
  728.     dstState[4] = putByte(SBox[getByte(origState[4],3)], 3)
  729.           + putByte(SBox[getByte(origState[1],2)], 2)
  730.           + putByte(SBox[getByte(origState[2],1)], 1)
  731.           + putByte(SBox[getByte(origState[3],0)], 0)
  732.   end
  733.   --
  734.   -- do decryption round
  735.   --
  736.   local function doInvRound(origState, dstState)
  737.     dstState[1] =  bit.bxor(bit.bxor(bit.bxor(
  738.           tableInv0[getByte(origState[1],3)],
  739.           tableInv1[getByte(origState[4],2)]),
  740.           tableInv2[getByte(origState[3],1)]),
  741.           tableInv3[getByte(origState[2],0)])
  742.     dstState[2] =  bit.bxor(bit.bxor(bit.bxor(
  743.           tableInv0[getByte(origState[2],3)],
  744.           tableInv1[getByte(origState[1],2)]),
  745.           tableInv2[getByte(origState[4],1)]),
  746.           tableInv3[getByte(origState[3],0)])
  747.     dstState[3] =  bit.bxor(bit.bxor(bit.bxor(
  748.           tableInv0[getByte(origState[3],3)],
  749.           tableInv1[getByte(origState[2],2)]),
  750.           tableInv2[getByte(origState[1],1)]),
  751.           tableInv3[getByte(origState[4],0)])
  752.     dstState[4] =  bit.bxor(bit.bxor(bit.bxor(
  753.           tableInv0[getByte(origState[4],3)],
  754.           tableInv1[getByte(origState[3],2)]),
  755.           tableInv2[getByte(origState[2],1)]),
  756.           tableInv3[getByte(origState[1],0)])
  757.   end
  758.   --
  759.   -- do last decryption round
  760.   --
  761.   local function doInvLastRound(origState, dstState)
  762.     dstState[1] = putByte(iSBox[getByte(origState[1],3)], 3)
  763.           + putByte(iSBox[getByte(origState[4],2)], 2)
  764.           + putByte(iSBox[getByte(origState[3],1)], 1)
  765.           + putByte(iSBox[getByte(origState[2],0)], 0)
  766.     dstState[2] = putByte(iSBox[getByte(origState[2],3)], 3)
  767.           + putByte(iSBox[getByte(origState[1],2)], 2)
  768.           + putByte(iSBox[getByte(origState[4],1)], 1)
  769.           + putByte(iSBox[getByte(origState[3],0)], 0)
  770.     dstState[3] = putByte(iSBox[getByte(origState[3],3)], 3)
  771.           + putByte(iSBox[getByte(origState[2],2)], 2)
  772.           + putByte(iSBox[getByte(origState[1],1)], 1)
  773.           + putByte(iSBox[getByte(origState[4],0)], 0)
  774.     dstState[4] = putByte(iSBox[getByte(origState[4],3)], 3)
  775.           + putByte(iSBox[getByte(origState[3],2)], 2)
  776.           + putByte(iSBox[getByte(origState[2],1)], 1)
  777.           + putByte(iSBox[getByte(origState[1],0)], 0)
  778.   end
  779.   --
  780.   -- encrypts 16 Bytes
  781.   -- key           encryption key schedule
  782.   -- input         array with input data
  783.   -- inputOffset   start index for input
  784.   -- output        array for encrypted data
  785.   -- outputOffset  start index for output
  786.   --
  787.   local function encrypt(key, input, inputOffset, output, outputOffset)
  788.     --default parameters
  789.     inputOffset = inputOffset or 1
  790.     output = output or {}
  791.     outputOffset = outputOffset or 1
  792.     local state, tmpState = {}, {}
  793.     if (key[KEY_TYPE] ~= ENCRYPTION_KEY) then
  794.       error("No encryption key: " .. tostring(key[KEY_TYPE]) .. ", expected " .. ENCRYPTION_KEY)
  795.       return
  796.     end
  797.     state = util.bytesToInts(input, inputOffset, 4)
  798.     addRoundKey(state, key, 0)
  799.     local round = 1
  800.     while (round < key[ROUNDS] - 1) do
  801.       -- do a double round to save temporary assignments
  802.       doRound(state, tmpState)
  803.       addRoundKey(tmpState, key, round)
  804.       round = round + 1
  805.       doRound(tmpState, state)
  806.       addRoundKey(state, key, round)
  807.       round = round + 1
  808.     end
  809.     doRound(state, tmpState)
  810.     addRoundKey(tmpState, key, round)
  811.     round = round +1
  812.     doLastRound(tmpState, state)
  813.     addRoundKey(state, key, round)
  814.     util.sleepCheckIn()
  815.     return util.intsToBytes(state, output, outputOffset)
  816.   end
  817.   --
  818.   -- decrypt 16 bytes
  819.   -- key           decryption key schedule
  820.   -- input         array with input data
  821.   -- inputOffset   start index for input
  822.   -- output        array for decrypted data
  823.   -- outputOffset  start index for output
  824.   ---
  825.   local function decrypt(key, input, inputOffset, output, outputOffset)
  826.     -- default arguments
  827.     inputOffset = inputOffset or 1
  828.     output = output or {}
  829.     outputOffset = outputOffset or 1
  830.     local state, tmpState = {}, {}
  831.     if (key[KEY_TYPE] ~= DECRYPTION_KEY) then
  832.       error("No decryption key: " .. tostring(key[KEY_TYPE]))
  833.       return
  834.     end
  835.     state = util.bytesToInts(input, inputOffset, 4)
  836.     addRoundKey(state, key, key[ROUNDS])
  837.     local round = key[ROUNDS] - 1
  838.     while (round > 2) do
  839.       -- do a double round to save temporary assignments
  840.       doInvRound(state, tmpState)
  841.       addRoundKey(tmpState, key, round)
  842.       round = round - 1
  843.       doInvRound(tmpState, state)
  844.       addRoundKey(state, key, round)
  845.       round = round - 1
  846.     end
  847.     doInvRound(state, tmpState)
  848.     addRoundKey(tmpState, key, round)
  849.     round = round - 1
  850.     doInvLastRound(tmpState, state)
  851.     addRoundKey(state, key, round)
  852.     util.sleepCheckIn()
  853.     return util.intsToBytes(state, output, outputOffset)
  854.   end
  855.  
  856.   -- calculate all tables when loading this file
  857.   calcSBox()
  858.   calcRoundTables()
  859.   calcInvRoundTables()
  860.  
  861.   return {
  862.     ROUNDS = ROUNDS,
  863.     KEY_TYPE = KEY_TYPE,
  864.     ENCRYPTION_KEY = ENCRYPTION_KEY,
  865.     DECRYPTION_KEY = DECRYPTION_KEY,
  866.     expandEncryptionKey = expandEncryptionKey,
  867.     expandDecryptionKey = expandDecryptionKey,
  868.     encrypt = encrypt,
  869.     decrypt = decrypt,
  870.   }
  871.   end)
  872.  
  873.   local buffer=_W(function(_ENV, ...)
  874.   local function new()
  875.     return {}
  876.   end
  877.  
  878.   local function addString(stack, s)
  879.     table.insert(stack, s)
  880.   end
  881.  
  882.   local function toString(stack)
  883.     return table.concat(stack)
  884.   end
  885.  
  886.   return {
  887.     new = new,
  888.     addString = addString,
  889.     toString = toString,
  890.   }
  891.   end)
  892.  
  893.   ciphermode=_W(function(_ENV, ...)
  894.   local public = {}
  895.   --
  896.   -- Encrypt strings
  897.   -- key - byte array with key
  898.   -- string - string to encrypt
  899.   -- modefunction - function for cipher mode to use
  900.   --
  901.   local random, unpack = math.random, unpack or table.unpack
  902.   function public.encryptString(key, data, modeFunction, iv)
  903.     if iv then
  904.       local ivCopy = {}
  905.       for i = 1, 16 do ivCopy[i] = iv[i] end
  906.       iv = ivCopy
  907.     else
  908.       iv = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
  909.     end
  910.     local keySched = aes.expandEncryptionKey(key)
  911.     local encryptedData = buffer.new()
  912.     for i = 1, #data/16 do
  913.       local offset = (i-1)*16 + 1
  914.       local byteData = {string.byte(data,offset,offset +15)}
  915.       iv = modeFunction(keySched, byteData, iv)
  916.       buffer.addString(encryptedData, string.char(unpack(byteData)))
  917.     end
  918.     return buffer.toString(encryptedData)
  919.   end
  920.   --
  921.   -- the following 4 functions can be used as
  922.   -- modefunction for encryptString
  923.   --
  924.   -- Electronic code book mode encrypt function
  925.   function public.encryptECB(keySched, byteData, iv)
  926.     aes.encrypt(keySched, byteData, 1, byteData, 1)
  927.   end
  928.  
  929.   -- Cipher block chaining mode encrypt function
  930.   function public.encryptCBC(keySched, byteData, iv)
  931.     util.xorIV(byteData, iv)
  932.     aes.encrypt(keySched, byteData, 1, byteData, 1)
  933.     return byteData
  934.   end
  935.  
  936.   -- Output feedback mode encrypt function
  937.   function public.encryptOFB(keySched, byteData, iv)
  938.     aes.encrypt(keySched, iv, 1, iv, 1)
  939.     util.xorIV(byteData, iv)
  940.     return iv
  941.   end
  942.  
  943.   -- Cipher feedback mode encrypt function
  944.   function public.encryptCFB(keySched, byteData, iv)
  945.     aes.encrypt(keySched, iv, 1, iv, 1)
  946.     util.xorIV(byteData, iv)
  947.     return byteData
  948.   end
  949.  
  950.   function public.encryptCTR(keySched, byteData, iv)
  951.     local nextIV = {}
  952.     for j = 1, 16 do nextIV[j] = iv[j] end
  953.     aes.encrypt(keySched, iv, 1, iv, 1)
  954.     util.xorIV(byteData, iv)
  955.     util.increment(nextIV)
  956.     return nextIV
  957.   end
  958.   --
  959.   -- Decrypt strings
  960.   -- key - byte array with key
  961.   -- string - string to decrypt
  962.   -- modefunction - function for cipher mode to use
  963.   --
  964.   function public.decryptString(key, data, modeFunction, iv)
  965.     if iv then
  966.       local ivCopy = {}
  967.       for i = 1, 16 do ivCopy[i] = iv[i] end
  968.       iv = ivCopy
  969.     else
  970.       iv = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
  971.     end
  972.     local keySched
  973.     if modeFunction == public.decryptOFB or modeFunction == public.decryptCFB or modeFunction == public.decryptCTR then
  974.       keySched = aes.expandEncryptionKey(key)
  975.     else
  976.       keySched = aes.expandDecryptionKey(key)
  977.     end
  978.     local decryptedData = buffer.new()
  979.     for i = 1, #data/16 do
  980.       local offset = (i-1)*16 + 1
  981.       local byteData = {string.byte(data,offset,offset +15)}
  982.       iv = modeFunction(keySched, byteData, iv)
  983.       buffer.addString(decryptedData, string.char(unpack(byteData)))
  984.     end
  985.     return buffer.toString(decryptedData)
  986.   end
  987.   --
  988.   -- the following 4 functions can be used as
  989.   -- modefunction for decryptString
  990.   --
  991.   -- Electronic code book mode decrypt function
  992.   function public.decryptECB(keySched, byteData, iv)
  993.     aes.decrypt(keySched, byteData, 1, byteData, 1)
  994.     return iv
  995.   end
  996.  
  997.   -- Cipher block chaining mode decrypt function
  998.   function public.decryptCBC(keySched, byteData, iv)
  999.     local nextIV = {}
  1000.     for j = 1, 16 do nextIV[j] = byteData[j] end
  1001.     aes.decrypt(keySched, byteData, 1, byteData, 1)
  1002.     util.xorIV(byteData, iv)
  1003.     return nextIV
  1004.   end
  1005.  
  1006.   -- Output feedback mode decrypt function
  1007.   function public.decryptOFB(keySched, byteData, iv)
  1008.     aes.encrypt(keySched, iv, 1, iv, 1)
  1009.     util.xorIV(byteData, iv)
  1010.     return iv
  1011.   end
  1012.  
  1013.   -- Cipher feedback mode decrypt function
  1014.   function public.decryptCFB(keySched, byteData, iv)
  1015.     local nextIV = {}
  1016.     for j = 1, 16 do nextIV[j] = byteData[j] end
  1017.     aes.encrypt(keySched, iv, 1, iv, 1)
  1018.     util.xorIV(byteData, iv)
  1019.     return nextIV
  1020.   end
  1021.  
  1022.   public.decryptCTR = public.encryptCTR
  1023.   return public
  1024.   end)
  1025.  
  1026.   -- Simple API for encrypting strings.
  1027.   --
  1028.   AES128 = 16
  1029.   AES192 = 24
  1030.   AES256 = 32
  1031.   ECBMODE = 1
  1032.   CBCMODE = 2
  1033.   OFBMODE = 3
  1034.   CFBMODE = 4
  1035.   CTRMODE = 4
  1036.  
  1037.   local function pwToKey(password, keyLength, iv)
  1038.     local padLength = keyLength
  1039.     if (keyLength == AES192) then
  1040.       padLength = 32
  1041.     end
  1042.     if (padLength > #password) then
  1043.       local postfix = ""
  1044.       for i = 1,padLength - #password do
  1045.         postfix = postfix .. string.char(0)
  1046.       end
  1047.       password = password .. postfix
  1048.     else
  1049.       password = string.sub(password, 1, padLength)
  1050.     end
  1051.     local pwBytes = {string.byte(password,1,#password)}
  1052.     password = ciphermode.encryptString(pwBytes, password, ciphermode.encryptCBC, iv)
  1053.     password = string.sub(password, 1, keyLength)
  1054.     return {string.byte(password,1,#password)}
  1055.   end
  1056.   --
  1057.   -- Encrypts string data with password password.
  1058.   -- password  - the encryption key is generated from this string
  1059.   -- data      - string to encrypt (must not be too large)
  1060.   -- keyLength - length of aes key: 128(default), 192 or 256 Bit
  1061.   -- mode      - mode of encryption: ecb, cbc(default), ofb, cfb
  1062.   --
  1063.   -- mode and keyLength must be the same for encryption and decryption.
  1064.   --
  1065.   function encrypt(password, data, keyLength, mode, iv)
  1066.     assert(password ~= nil, "Empty password.")
  1067.     assert(data ~= nil, "Empty data.")
  1068.     local mode = mode or CBCMODE
  1069.     local keyLength = keyLength or AES128
  1070.     local key = pwToKey(password, keyLength, iv)
  1071.     local paddedData = util.padByteString(data)
  1072.     if mode == ECBMODE then
  1073.       return ciphermode.encryptString(key, paddedData, ciphermode.encryptECB, iv)
  1074.     elseif mode == CBCMODE then
  1075.       return ciphermode.encryptString(key, paddedData, ciphermode.encryptCBC, iv)
  1076.     elseif mode == OFBMODE then
  1077.       return ciphermode.encryptString(key, paddedData, ciphermode.encryptOFB, iv)
  1078.     elseif mode == CFBMODE then
  1079.       return ciphermode.encryptString(key, paddedData, ciphermode.encryptCFB, iv)
  1080.     elseif mode == CTRMODE then
  1081.       return ciphermode.encryptString(key, paddedData, ciphermode.encryptCTR, iv)
  1082.     else
  1083.       error("Unknown mode", 2)
  1084.     end
  1085.   end
  1086.   --
  1087.   -- Decrypts string data with password password.
  1088.   -- password  - the decryption key is generated from this string
  1089.   -- data      - string to encrypt
  1090.   -- keyLength - length of aes key: 128(default), 192 or 256 Bit
  1091.   -- mode      - mode of decryption: ecb, cbc(default), ofb, cfb
  1092.   --
  1093.   -- mode and keyLength must be the same for encryption and decryption.
  1094.   --
  1095.   function decrypt(password, data, keyLength, mode, iv)
  1096.     local mode = mode or CBCMODE
  1097.     local keyLength = keyLength or AES128
  1098.     local key = pwToKey(password, keyLength, iv)
  1099.     local plain
  1100.     if mode == ECBMODE then
  1101.       plain = ciphermode.decryptString(key, data, ciphermode.decryptECB, iv)
  1102.     elseif mode == CBCMODE then
  1103.       plain = ciphermode.decryptString(key, data, ciphermode.decryptCBC, iv)
  1104.     elseif mode == OFBMODE then
  1105.       plain = ciphermode.decryptString(key, data, ciphermode.decryptOFB, iv)
  1106.     elseif mode == CFBMODE then
  1107.       plain = ciphermode.decryptString(key, data, ciphermode.decryptCFB, iv)
  1108.     elseif mode == CTRMODE then
  1109.       plain = ciphermode.decryptString(key, data, ciphermode.decryptCTR, iv)
  1110.     else
  1111.       error("Unknown mode", 2)
  1112.     end
  1113.     result = util.unpadByteString(plain)
  1114.     if (result == nil) then
  1115.       return nil
  1116.     end
  1117.     return result
  1118.   end
  1119. end
  1120.  
  1121. -- SHA-256, HMAC and PBKDF2 functions in ComputerCraft
  1122. -- By Anavrins
  1123. -- For help and details, you can PM me on the CC forums
  1124. -- http://www.computercraft.info/forums2/index.php?/user/12870-anavrins
  1125. -- You may use this code in your projects without asking me, as long as credit is given and this header is kept intact
  1126. -- Pastebin: http://pastebin.com/6UV4qfNF
  1127. local digest, hmac, pbkdf2
  1128. do
  1129.   local mod32 = 2^32
  1130.   local sha_hashlen = 32
  1131.   local sha_blocksize = 64
  1132.   local band    = bit32 and bit32.band or bit.band
  1133.   local bnot    = bit32 and bit32.bnot or bit.bnot
  1134.   local bxor    = bit32 and bit32.bxor or bit.bxor
  1135.   local blshift = bit32 and bit32.lshift or bit.blshift
  1136.   local upack   = unpack
  1137.  
  1138.   local function rrotate(n, b)
  1139.     local s = n/(2^b)
  1140.     local f = s%1
  1141.     return (s-f) + f*mod32
  1142.   end
  1143.  
  1144.   local function brshift(int, by) -- Thanks bit32 for bad rshift
  1145.     local s = int / (2^by)
  1146.     return s - s%1
  1147.   end
  1148.  
  1149.   local H = {
  1150.     0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
  1151.     0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
  1152.   }
  1153.  
  1154.   local K = {
  1155.     0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
  1156.     0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
  1157.     0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
  1158.     0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
  1159.     0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
  1160.     0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
  1161.     0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
  1162.     0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
  1163.   }
  1164.  
  1165.   local function counter(incr)
  1166.     local t1, t2 = 0, 0
  1167.     if 0xFFFFFFFF - t1 < incr then
  1168.       t2 = t2 + 1
  1169.       t1 = incr - (0xFFFFFFFF - t1) - 1    
  1170.     else t1 = t1 + incr
  1171.     end
  1172.     return t2, t1
  1173.   end
  1174.  
  1175.   local function BE_toInt(bs, i)
  1176.     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)
  1177.   end
  1178.  
  1179.   local function preprocess(data)
  1180.     local len = #data
  1181.     local proc = {}
  1182.     data[#data+1] = 0x80
  1183.     while #data%64~=56 do data[#data+1] = 0 end
  1184.     local blocks = math.ceil(#data/64)
  1185.     for i = 1, blocks do
  1186.       proc[i] = {}
  1187.       for j = 1, 16 do
  1188.         proc[i][j] = BE_toInt(data, 1+((i-1)*64)+((j-1)*4))
  1189.       end
  1190.     end
  1191.     proc[blocks][15], proc[blocks][16] = counter(len*8)
  1192.     return proc
  1193.   end
  1194.  
  1195.   local function digestblock(w, C)
  1196.     for j = 17, 64 do
  1197.       local v = w[j-15]
  1198.       local s0 = bxor(bxor(rrotate(w[j-15], 7), rrotate(w[j-15], 18)), brshift(w[j-15], 3))
  1199.       local s1 = bxor(bxor(rrotate(w[j-2], 17), rrotate(w[j-2], 19)), brshift(w[j-2], 10))
  1200.       w[j] = (w[j-16] + s0 + w[j-7] + s1)%mod32
  1201.     end
  1202.     local a, b, c, d, e, f, g, h = upack(C)
  1203.     for j = 1, 64 do
  1204.       local S1 = bxor(bxor(rrotate(e, 6), rrotate(e, 11)), rrotate(e, 25))
  1205.       local ch = bxor(band(e, f), band(bnot(e), g))
  1206.       local temp1 = (h + S1 + ch + K[j] + w[j])%mod32
  1207.       local S0 = bxor(bxor(rrotate(a, 2), rrotate(a, 13)), rrotate(a, 22))
  1208.       local maj = bxor(bxor(band(a, b), band(a, c)), band(b, c))
  1209.       local temp2 = (S0 + maj)%mod32
  1210.       h, g, f, e, d, c, b, a = g, f, e, (d+temp1)%mod32, c, b, a, (temp1+temp2)%mod32
  1211.     end
  1212.     C[1] = (C[1] + a)%mod32
  1213.     C[2] = (C[2] + b)%mod32
  1214.     C[3] = (C[3] + c)%mod32
  1215.     C[4] = (C[4] + d)%mod32
  1216.     C[5] = (C[5] + e)%mod32
  1217.     C[6] = (C[6] + f)%mod32
  1218.     C[7] = (C[7] + g)%mod32
  1219.     C[8] = (C[8] + h)%mod32
  1220.     return C
  1221.   end
  1222.  
  1223.   local mt = {
  1224.     __tostring = function(a) return string.char(unpack(a)) end,
  1225.     __index = {
  1226.       toHex = function(self, s) return ("%02x"):rep(#self):format(unpack(self)) end,
  1227.       isEqual = function(self, t)
  1228.         if type(t) ~= "table" then return false end
  1229.         if #self ~= #t then return false end
  1230.         local ret = 0
  1231.         for i = 1, #self do
  1232.           ret = bit32.bor(ret, bxor(self[i], t[i]))
  1233.         end
  1234.         return ret == 0
  1235.       end
  1236.     }
  1237.   }
  1238.  
  1239.   local function toBytes(t, n)
  1240.     local b = {}
  1241.     for i = 1, n do
  1242.       b[(i-1)*4+1] = band(brshift(band(t[i], 0xFF000000), 24), 0xFF)
  1243.       b[(i-1)*4+2] = band(brshift(band(t[i], 0xFF0000), 16), 0xFF)
  1244.       b[(i-1)*4+3] = band(brshift(band(t[i], 0xFF00), 8), 0xFF)
  1245.       b[(i-1)*4+4] = band(t[i], 0xFF)
  1246.     end
  1247.     return setmetatable(b, mt)
  1248.   end
  1249.  
  1250.   digest = function(data)
  1251.     data = data or ""
  1252.     data = type(data) == "string" and {data:byte(1,-1)} or data
  1253.     data = preprocess(data)
  1254.     local C = {upack(H)}
  1255.     for i = 1, #data do C = digestblock(data[i], C) end
  1256.     return toBytes(C, 8)
  1257.   end
  1258.  
  1259.   hmac = function(data, key)
  1260.     local data = type(data) == "table" and {upack(data)} or {tostring(data):byte(1,-1)}
  1261.     local key = type(key) == "table" and {upack(key)} or {tostring(key):byte(1,-1)}
  1262.     local blocksize = sha_blocksize
  1263.     key = #key > blocksize and digest(key) or key
  1264.     local ipad = {}
  1265.     local opad = {}
  1266.     local padded_key = {}
  1267.     for i = 1, blocksize do
  1268.       ipad[i] = bxor(0x36, key[i] or 0)
  1269.       opad[i] = bxor(0x5C, key[i] or 0)
  1270.     end
  1271.     for i = 1, #data do
  1272.       ipad[blocksize+i] = data[i]
  1273.     end
  1274.     ipad = digest(ipad)
  1275.     for i = 1, blocksize do
  1276.       padded_key[i] = opad[i]
  1277.       padded_key[blocksize+i] = ipad[i]
  1278.     end
  1279.     return digest(padded_key)
  1280.   end
  1281.  
  1282.   pbkdf2 = function(pass, salt, iter, dklen)
  1283.     local out = {}
  1284.     local hashlen = sha_hashlen
  1285.     local block = 1
  1286.     dklen = dklen or 32
  1287.     while dklen > 0 do
  1288.       local ikey = {}
  1289.       local isalt = type(salt) == "table" and {upack(salt)} or {tostring(salt):byte(1,-1)}
  1290.       local clen = dklen > hashlen and hashlen or dklen
  1291.       local iCount = #isalt
  1292.       isalt[iCount+1] = band(brshift(band(block, 0xFF000000), 24), 0xFF)
  1293.       isalt[iCount+2] = band(brshift(band(block, 0xFF0000), 16), 0xFF)
  1294.       isalt[iCount+3] = band(brshift(band(block, 0xFF00), 8), 0xFF)
  1295.       isalt[iCount+4] = band(block, 0xFF)
  1296.       for j = 1, iter do
  1297.         isalt = hmac(isalt, pass)
  1298.         for k = 1, clen do ikey[k] = bxor(isalt[k], ikey[k] or 0) end
  1299.         if j % 200 == 0 then os.queueEvent("PBKDF2", j) coroutine.yield("PBKDF2") end
  1300.       end
  1301.       dklen = dklen - clen
  1302.       block = block+1
  1303.       for k = 1, clen do out[#out+1] = ikey[k] end
  1304.     end
  1305.     return setmetatable(out, mt)
  1306.   end
  1307. end
  1308.  
  1309. local function mergeAddressBooks(newGates)
  1310.   local matchFound, abName, abNote, abDim, ngName, ngNote, ngDim = false --# use matchFound to indicate if a matching gate was found or not
  1311.   for i = 1, #newGates do                             --# start cycling through the list of 'new' gates
  1312.     for j = 1, abCount do                             --# search the address book for a matching address
  1313.       if newGates[i].addr == addressBook[j].addr then --# if the gate is already in the address book...
  1314.         matchFound = true                             --# ...set matchFound to true and...
  1315.         abName, ngName = addressBook[j].name, newGates[i].name
  1316.         if (abName == "Name" or abName == "NO GATES" or abName == "NEW GATE" or abName == addressBook[j].addr) and ngName ~= newGates[i].addr and ngName ~= "NEW GATE" and ngName ~= "NO GATES" and ngName ~= "Name" then
  1317.           addressBook[j].name = ngName
  1318.           gateChange = true
  1319.         end
  1320.         abNote, ngNote = addressBook[j].note, newGates[i].note
  1321.         if (abNote == "short note" or abNote == "Discovered gate" or abNote == "Added from logs") and ngNote ~= "short note" and ngNote ~= "Discovered gate" and ngNote ~= "Added from logs" then
  1322.           addressBook[j].note = ngNote
  1323.           gateChange = true
  1324.         end
  1325.         if addressBook[j].rating == "U" and newGates[i].rating ~= "U" then
  1326.           addressBook[j].rating = newGates[i].rating
  1327.           gateChange = true
  1328.         end
  1329.         abDim, ngDim = addressBook[j].loc.dim, newGates[i].loc.dim
  1330.         if (abDim == "Overworld" and ngDim ~= "Overworld" and ngDim ~= "Unspecified") or (abDim == "Unspecified" and ngDim ~= "Unspecified") then
  1331.           addressBook[j].loc.dim = ngDim
  1332.           gateChange = true
  1333.         end
  1334.         if addressBook[j].loc.x == 0 and addressBook[j].loc.y == 0 and addressBook[j].loc.z == 0 and (newGates[i].loc.x ~= 0 or newGates[i].loc.y ~= 0 or newGates[i].loc.z ~= 0) then
  1335.           addressBook[j].loc.x, addressBook[j].loc.y, addressBook[j].loc.z = newGates[i].loc.x, newGates[i].loc.y, newGates[i].loc.z
  1336.           gateChange = true
  1337.         end
  1338.         break                                         --# stop the address book search loop and move on to the next gate in the 'new' list
  1339.       end
  1340.     end
  1341.     if matchFound then                                --# if a match was found...
  1342.       matchFound = false                              --# ...reset the variable for the next iteration of the loop
  1343.     else                                              --# otherwise...
  1344.       addressBook[abCount + 1], abCount = { }, abCount + 1 --# initialize a new address book entry
  1345.       for k, v in pairs(newGates[i]) do               --# loop through the gate entries...
  1346.         addressBook[abCount][k] = v                   --# ...and add them to the new address book entry
  1347.       end
  1348.       addressBook[abCount].iris = "none"
  1349.       addressBook[abCount].callDrop = false
  1350.       gateChange = true                               --# indicate that address book data has changed
  1351.     end
  1352.   end
  1353.   if abCount > 1 and (addressBook[1].name == "NO GATES" or addressBook[1].name == "Name") and addressBook[1].addr == "ADDRESS" then table.remove(addressBook, 1) abCount = abCount - 1 end
  1354.   gatePages = math.ceil(abCount / 12)                 --# repaginate AddressBook
  1355. end
  1356.  
  1357. local function saveData()
  1358.   local gateData = fs.open("/data/DHDgates", "w")
  1359.   gateData.write(textutils.serialize(addressBook))
  1360.   gateData.close()
  1361.   gateChange = false
  1362. end
  1363.  
  1364. local function repositionCursor()
  1365.   term.setTextColor(txtCol)
  1366.   term.setBackgroundColor(bgCol)
  1367.   term.setCursorPos(curX, curY)
  1368.   term.write(word)
  1369. end
  1370.  
  1371. local function netSend(dataPack)
  1372.   if not rednet.isOpen(modemSide) then rednet.open(modemSide) end
  1373.   local encKey = tostring(host.id) .. "ccDHD!General_Comms*Key" .. thisCC
  1374.   local encryptedDataPack = encode(encrypt(encKey, textutils.serialize(dataPack)))
  1375.   rednet.send(host.id, encryptedDataPack, "ccDialerWiFi")
  1376. end
  1377.  
  1378. local function netReceive()
  1379.   local id, message, encKey, encryptedMessage, decryptedMessage, encodedMessage, success, iColor, mData
  1380.   while true do
  1381.     if not rednet.isOpen(modemSide) then rednet.open(modemSide) end
  1382.     id, encodedMessage = rednet.receive("ccDialerWiFi")
  1383.     if id == host.id and type(encodedMessage) == "string" then
  1384.       success, encryptedMessage = pcall(decode, encodedMessage)
  1385.       if success then
  1386.         encKey = thisCC .. "ccDHD!General_Comms*Key" .. tostring(id)
  1387.         success, decryptedMessage = pcall(decrypt, encKey, encryptedMessage)
  1388.         if success then
  1389.           success, message = pcall(textutils.unserialize, decryptedMessage)
  1390.           if success and type(message) == "table" and message.program and message.program == "ccDialer" and message.data then
  1391.             mData = message.data
  1392.             if type(mData) == "string" then
  1393.               if mData == "pong" then
  1394.                 missedPings = 0
  1395.               elseif mData == "ping" then
  1396.                 netSend({ program = "ccDialer", gate = host.addr, command = "pong" })
  1397.               elseif mData == "open" or mData == "closed" or mData == "unk" then
  1398.                 remoteIrisState = mData
  1399.                 if runState ~= "Help" and runState ~= "viewing" and runState ~= "selectHost" then
  1400.                   iColor = remoteIrisState == "open" and green or red
  1401.                   iColor = remoteIrisState == "unk" and gray or iColor
  1402.                   drawElement(6, 1, 1, 1, iColor, blue, "I") --# 'I' for Iris control
  1403.                   if gettingInput then repositionCursor() end
  1404.                 end
  1405.               elseif mData == "Idle" or mData == "Dialing" or mData == "Paused" or mData == "Connected" or mData == "Offline" then
  1406.                 if mData == "Offline" or mData == "Idle" then
  1407.                   remoteIrisState, dialAddress = "unk", "none"
  1408.                   if mData == "Offline" then
  1409.                     host = { id = nil, name = "NO HOST", addr = "NO HOST", ccDHD = false, status = nil }
  1410.                   else
  1411.                     host.status = mData
  1412.                   end
  1413.                 end
  1414.                 if runState == "Dial" or runState == "goPage" or runState == "Sync" then
  1415.                   if mData == "Offline" or mData == "Idle" then
  1416.                     drawElement(6, 1, 1, 1, gray, blue, "I") --# 'I' for Iris control
  1417.                     if mData == "Offline" and runState == "Sync" then
  1418.                       runState = "Dial"
  1419.                       drawElement(1, 5, termX - 1, termY - 5, nil, black) --# clear address book area
  1420.                     end
  1421.                     mData = mData == "Idle" and "Idle     " or "         "
  1422.                   elseif mData == "Dialing" then
  1423.                     mData = "Dialing  "
  1424.                   end
  1425.                   if runState == "Dial" or runState == "goPage" then
  1426.                     if mData == "Dialing  " then
  1427.                       drawElement(22, 2, 1, 1, silver, gray, mData) --# gate status
  1428.                     else
  1429.                       drawCLI() --# redraw the address book, et. al.
  1430.                     end
  1431.                   end
  1432.                   if gettingInput then repositionCursor() end
  1433.                 end
  1434.               end
  1435.             elseif type(mData) == "table" and message.command and message.command == "push" then
  1436.               mergeAddressBooks(mData)
  1437.               if gateChange then
  1438.                 saveData()
  1439.                 if host.addr == host.name and host.addr ~= "NO HOST" then
  1440.                   for i = 1, #mData do
  1441.                     if host.addr == mData[i].addr then
  1442.                       host.name = mData[i].name
  1443.                       break
  1444.                     end
  1445.                   end
  1446.                 end
  1447.                 if runState == "Dial" or runState == "goPage" then drawCLI() end
  1448.                 if gettingInput then repositionCursor() end
  1449.               end
  1450.             end
  1451.           end
  1452.         end
  1453.       end
  1454.     end
  1455.   end
  1456. end
  1457.  
  1458. local function assignColor(gateNumber)
  1459.   return classifications[addressBook[gateNumber].rating].color or silver
  1460. end
  1461.  
  1462. local function assignRating(gateNumber)
  1463.   return classifications[addressBook[gateNumber].rating].long or (classifications[addressBook[gateNumber].rating].label or "Unknown/Unclassified")
  1464. end
  1465.  
  1466. local function clearScreen(bgColor)
  1467.   term.setBackgroundColor(bgColor or black)
  1468.   term.clear()
  1469. end
  1470.  
  1471. local function shutDown()
  1472.   if host.id then netSend({ program = "ccDialer", gate = host.addr, command = "logout" }) end
  1473.   if rednet.isOpen(modemSide) then rednet.close(modemSide) end
  1474.   clearScreen()
  1475.   term.setTextColor(white)
  1476.   term.setCursorPos(1, 1)
  1477. end
  1478.  
  1479. do
  1480.   local validColors = { [1] = true; [2] = true; [4] = true; [8] = true; [16] = true; [32] = true; [64] = true; [128] = true; [256] = true; [512] = true; [1024] = true; [2048] = true; [4096] = true; [8192] = true; [16384] = true, [32768] = true; }
  1481.  
  1482.   drawElement = function(x, y, w, h, txtColor, bgColor, text)
  1483.     if type(x) == "number" and type(y) == "number" and type(w) == "number" and type(h) == "number" then
  1484.       local sText = text and tostring(text) or "" --# Ensure text is a string
  1485.       w = math.floor(math.min(math.max(1, math.max(#sText, w)), termX)) --# validate width
  1486.       h = math.floor(math.max(1, math.min(h, termY)))                   --# validate height
  1487.       x = math.floor(math.max(1, math.min(x, termX - w + 1)))           --# validate x coord
  1488.       y = math.floor(math.max(1, math.min(y, termY - h + 1)))           --# validate y coord
  1489.       local spacer = (w - #sText) / 2
  1490.       local txtLine = string.rep(" ", math.floor(spacer)) .. sText .. string.rep(" ", math.ceil(spacer))
  1491.       if type(txtColor) == "number" and validColors[txtColor] then term.setTextColor(txtColor) end    --# validate the text color
  1492.       if type(bgColor) == "number" and validColors[bgColor] then term.setBackgroundColor(bgColor) end --# validate the background color
  1493.       if h == 1 then              --# if the height is 1 then...
  1494.         term.setCursorPos(x, y)   --# Position cursor
  1495.         term.write(txtLine)       --# Draw the single line of the 'element'
  1496.       else                        --# otherwise...
  1497.         local line, txtRow = string.rep(" ", w), math.floor(h / 2) + y --# Define one line of the 'element' (box/rectangle/line-seg) and the row the text will be drawn on
  1498.         for i = y, y + h - 1 do   --# Loop through the height of the 'element' line by line (top to bottom)
  1499.           term.setCursorPos(x, i) --# Position cursor
  1500.           term.write(i == txtRow and txtLine or line) --# Draw 1 of h lines of the 'element' (box/rectangle)
  1501.         end
  1502.       end
  1503.     end
  1504.   end
  1505. end
  1506.  
  1507. local function drawRatingList(rating)
  1508.   local txtColor
  1509.   drawElement(7, 4, 19, 8, nil, gray)                  --# menu body
  1510.   for k, v in pairs(classifications) do
  1511.     if rating == k then
  1512.       txtColor = rating == "U" and white or v.color
  1513.       drawElement(25, v.order + 3, 1, 1, nil, v.color) --# selected rating color pip
  1514.     else
  1515.       txtColor = silver
  1516.     end
  1517.     drawElement(6, v.order + 3, 1, 1, nil, v.color)    --# color pip
  1518.     drawElement(8, v.order + 3, 1, 1, txtColor, gray, v.label)
  1519.   end
  1520. end
  1521.  
  1522. local function drawHeader()
  1523.   local spacer, title = termX / 2, ""
  1524.   if runState == "viewing" then
  1525.     local titleText = addressBook[currentEdit].name
  1526.     spacer = (termX - #titleText) / 2
  1527.     title = string.rep(" ", math.floor(spacer)) .. titleText .. string.rep(" ", math.ceil(spacer) - 3) .. "[ ]"
  1528.   elseif runState == "Help" then
  1529.     title = string.rep(" ", math.floor(spacer) - 4) .. "ccDialer" .. string.rep(" ", math.ceil(spacer) - 7) .. "[ ]"
  1530.   else
  1531.     title = "[ ] [ ]" .. string.rep(" ", math.floor(spacer) - 11) .. "ccDialer" .. string.rep(" ", math.ceil(spacer) - 7) .. "[ ]"
  1532.   end
  1533.   drawElement(1, 1, termX, 1, white, runState == "viewing" and assignColor(currentEdit) or blue, title) --# title bar
  1534.   if runState ~= "viewing" and runState ~= "Help" then
  1535.     drawElement(2, 1, 1, 1, host.id and green or gray, nil, "H") --# 'H' for Sync Hosts
  1536.     local iColor = remoteIrisState == "open" and green or red
  1537.     iColor = remoteIrisState == "unk" and gray or iColor
  1538.     drawElement(6, 1, 1, 1, iColor, nil, "I")                    --# 'I' for Iris control
  1539.   end
  1540.   local xColor = red
  1541.   if runState == "viewing" and addressBook[currentEdit].rating == "D" then xColor = white end
  1542.   drawElement(termX - 1, 1, 1, 1, xColor, nil, "X")              --# 'X' for Exit
  1543.   if runState == "Dial" then
  1544.     drawElement(1, 2, termX, 1, nil, gray)
  1545.     drawElement(20, 2, 1, 1, nil, blue, " ")
  1546.     drawElement(2, 2, 1, 1, silver, gray, host.name)
  1547.     drawElement(22, 2, 1, 1, nil, nil, host.status or "          ")
  1548.     drawElement(1, 3, (termX / 2) - 1, 1, host.ccDHD and white or gray, host.ccDHD and cyan or silver, "Sync")
  1549.     term.setBackgroundColor(green)
  1550.     term.setTextColor(gateChange and orange or white)
  1551.     term.write(" + ")
  1552.     term.setBackgroundColor(red)
  1553.     if gateChange then term.setTextColor(white) end --# change the text color back to white if it was changed to orange
  1554.     term.write("     END Call     ")
  1555.   elseif runState == "Sync" then
  1556.     drawElement(1, 2, termX, 1, nil, gray)
  1557.     drawElement(20, 2, 1, 1, nil, blue, " ")
  1558.     drawElement(2, 2, 1, 1, silver, gray, host.name)
  1559.     drawElement(22, 2, 1, 1, nil, nil, host.status or "          ")
  1560.   elseif runState == "Help" then
  1561.     drawElement(1, 2, termX, 1, silver, gray, "+ button")
  1562.   elseif runState == "viewing" then
  1563.     drawElement(1, 2, termX, 1, silver, gray, "Gate Information")
  1564.   end
  1565. end
  1566.  
  1567. local function drawHostsUI()
  1568.   local yPos, hostCount, magicNumber = 1, #hosts, ((syncPage - 1) * 5) + 1
  1569.   drawElement(11, 1, 16, 1, white, blue, tostring(hostCount) .. " Hosts") --# Header
  1570.   drawElement(11, 2, 16, 11, nil, gray) --# Body
  1571.   for i = magicNumber, math.min(hostCount, magicNumber + 4) do
  1572.     yPos = yPos + 2
  1573.     drawElement(12, yPos, 14, 1, nil, hosts[i].ccDHD and green or red) --# Indicate ccDHD host on left & right side of hostName
  1574.     drawElement(13, yPos, 12, 1, nil, silver, hosts[i].name) --# hostName (host name or address)
  1575.   end
  1576.   drawElement(11, 13, 16, 1, silver, blue, "Page " .. tostring(syncPage) .. " of " .. tostring(syncPages)) --# footer
  1577. end
  1578.  
  1579. local function drawPasswordUI()
  1580.   drawElement(2, 5, termX - 2, 1, gray, black, "Remote  password") --# header
  1581.   drawElement(12, 6, 16, 3, nil, silver)                           --# body
  1582.   drawElement(13, 7, 14, 1, nil, gray)                             --# input area
  1583. end
  1584.  
  1585. local function drawGoPagePopUp()
  1586.   drawElement((termX / 2) - 3, termY - 3, 1, 1, white, gray, " :Page: ") --# header
  1587.   drawElement((termX / 2) - 3, termY - 2, 8, 2)                          --# body
  1588.   drawElement((termX / 2) - 2, termY - 2, 6, 1, nil, black)              --# input area bg
  1589. end
  1590.  
  1591. local function drawNaviUI()
  1592.   pNum = tostring(gatePage) .. " of " .. tostring(gatePages)
  1593.   drawElement((termX / 2) - 10, termY, 1, 1, gray, black, gatePages > 1 and "<< <              > >>" or "                      ") --# fwd/back buttons
  1594.   drawElement(((termX - #pNum) / 2) + 1, termY, 1, 1, silver, nil, pNum) --# page number
  1595. end
  1596.  
  1597. local function drawHelpScreen()
  1598.   drawElement(1, 5, termX, 1, silver, gray, "Address Book")
  1599.   drawElement(1, 9, termX, 1, nil, nil, "Header buttons")
  1600.   --drawElement(1, 13, termX, 1, nil, nil, "Sync button")
  1601.   if termY > 12 then drawElement(1, termY, termX, 1, nil, nil, "version " .. ccDialVer) end
  1602.   drawElement(2, 3, 1, 1, black, white, "(L) click: Add address")
  1603.   drawElement(2, 4, 1, 1, nil, nil, "(R) click: Save addr book")
  1604.   drawElement(2, 6, 1, 1, nil, nil, "(L) click: dial address")
  1605.   drawElement(2, 7, 1, 1, nil, nil, "(R) click: view or edit")
  1606.   drawElement(2, 8, 1, 1, nil, nil, "(M) click: del. address")
  1607.   drawElement(2, 10, 1, 1, nil, nil, "[H] = locate Sync host")
  1608.   drawElement(2, 11, 1, 1, nil, nil, "[I] = open remote iris")
  1609.   drawElement(2, 12, 1, 1, nil, nil, "[X] = Exit/Quit")
  1610.   --drawElement(2, 14, 1, 1, nil, nil, "(L) click: Send/Receive addr book")
  1611. end
  1612.  
  1613. local function drawSyncScreen()
  1614.   drawElement(2, 4, termX - 2, 3, white, green, "GET Address Book")
  1615.   drawElement(2, 7, termX - 2, 3, nil, orange, "SEND Address Book")
  1616.   drawElement(2, 10, termX - 2, 3, nil, red, "C A N C E L")
  1617. end
  1618.  
  1619. local function drawGateData()
  1620.   local addr, note, breakPoint = addressBook[currentEdit].addr, addressBook[currentEdit].note, 37
  1621.   drawElement(2, 4, 1, 1, cyan, black, addressBook[currentEdit].name .. string.rep(" ", 12 - #addressBook[currentEdit].name)) --# Name
  1622.   if host.addr == addr or host.addr:sub(1, 7) == addr or host.addr == addr:sub(1, 7) then
  1623.     drawElement(15, 6, 1, 1, gray, nil, "Sync Host")                          --# Sync Host
  1624.   elseif addr == dialAddress or addr:sub(1, 7) == dialAddress or addr == dialAddress:sub(1, 7) then
  1625.     drawElement(15, 6, 1, 1, gray, nil, "Target   ")                          --# Dial target
  1626.   else
  1627.     drawElement(15, 6, 9, 1)
  1628.   end
  1629.   drawElement(2, 6, 1, 1, yellow, nil, addr .. "  ")                          --# Address
  1630.   local ratingWord = assignRating(currentEdit)
  1631.   drawElement(2, 8, 1, 1, silver, nil, "[" .. ratingWord .. "]" .. string.rep(" ", termX - #ratingWord - 3))
  1632.   drawElement(3, 8, 1, 1, assignColor(currentEdit), nil, ratingWord)          --# Classification
  1633.   drawElement(20, 4, 1, 1, white, nil, tostring(currentEdit) .. string.rep(" ", 5 - #tostring(currentEdit))) --# Address book entry number ('#')
  1634.   if #note > 37 then                                                          --# Note
  1635.     breakPoint = note:sub(32, 38):find(" ") + 30
  1636.   else
  1637.     breakPoint = #note
  1638.   end
  1639.   drawElement(2, 10, 1, 1, nil, nil, note:sub(1, breakPoint) .. string.rep(" ", 37 - breakPoint))
  1640.   if #note > 37 then
  1641.     drawElement(2, 11, 1, 1, nil, nil, note:sub(breakPoint + 2) .. string.rep(" ", 37 - #note:sub(breakPoint + 2)))
  1642.   else
  1643.     drawElement(2, 11, 37, 1) --# clear line
  1644.   end
  1645.   local xStr, yStr, zStr = tostring(addressBook[currentEdit].loc.x), tostring(addressBook[currentEdit].loc.y), tostring(addressBook[currentEdit].loc.z)
  1646.   drawElement(26, 4, 1, 1, brown, nil, addressBook[currentEdit].loc.dim:sub(1, 13) .. string.rep(" ", 13 - #addressBook[currentEdit].loc.dim:sub(1, 13))) --# Dimension
  1647.   drawElement(29, 5, 1, 1, silver, nil, xStr .. string.rep(" ", 9 - #xStr))   --# X
  1648.   drawElement(29, 6, 1, 1, nil, nil, yStr .. string.rep(" ", 9 - #yStr))      --# Y
  1649.   drawElement(29, 7, 1, 1, nil, nil, zStr .. string.rep(" ", 9 - #zStr))      --# Z
  1650.   drawElement((termX / 2) + 2, termY, 7, 1, red, gray, "Close")
  1651.   drawElement((termX / 2) - 8, termY, 6, 1, gateChange and green or silver, nil, "Save")
  1652. end
  1653.  
  1654. local function drawGateLabels(id)
  1655.   runState, currentEdit = "viewing", id
  1656.   clearScreen()
  1657.   drawHeader()
  1658.   drawElement(18, 4, 1, 1, gray, black, "#")
  1659.   --drawElement(2, 13, 4, 1, nil, nil, "Dim:")
  1660.   drawElement(26, 5, 2, 1, nil, nil, "x:")
  1661.   drawElement(26, 6, 2, 1, nil, nil, "y:")
  1662.   drawElement(26, 7, 2, 1, nil, nil, "z:")
  1663.   drawGateData()
  1664. end
  1665.  
  1666. drawAddressBook = function() --# Gate Address Book
  1667.   local xPos, yPos, magicNumber, hostAddr = 1, 5, ((gatePage - 1) * 12) + 1, host.addr
  1668.   local txtColor, bgColor, addr
  1669.   for i = magicNumber, math.min(abCount, magicNumber + 11) do
  1670.     addr = addressBook[i].addr
  1671.     txtColor = hostAddr == addr and silver or (dialAddress == addr and black or white)
  1672.     bgColor = hostAddr == addr and gray or assignColor(i)
  1673.     drawElement(xPos, yPos, 12, 1, txtColor, bgColor, addressBook[i].name)
  1674.     yPos = yPos + 2
  1675.     if yPos > 11 then xPos = xPos + 13 yPos = 5 end
  1676.   end
  1677. end
  1678.  
  1679. do
  1680.   local stateTable = {
  1681.     Dial = function() drawAddressBook() drawNaviUI() end;
  1682.     Sync = function() drawSyncScreen() end;
  1683.     Help = function() drawHelpScreen() end;
  1684.     goPage = function()
  1685.       drawAddressBook()
  1686.       drawNaviUI()
  1687.       drawGoPagePopUp()
  1688.     end;
  1689.   }
  1690.  
  1691.   drawCLI = function()
  1692.     if runState ~= "selectHost" then drawHeader() end
  1693.     stateTable[runState]()
  1694.   end
  1695. end
  1696.  
  1697. local function flashDial(gateName, x, y, gateNum)
  1698.   local gColor = assignColor(gateNum)
  1699.   drawElement(x, y, 12, 1, gColor, black, gateName)
  1700.   sleep(0.1)
  1701.   drawElement(x, y, 12, 1, dialAddress == addressBook[gateNum].addr and black or white, gColor, gateName)
  1702. end
  1703.  
  1704. local function flashChoice(x, y, w, h, txtClr, bgClr, label)
  1705.   drawElement(x, y, w, h, txtClr, bgClr, label)
  1706.   sleep(0.1)
  1707. end
  1708.  
  1709. local function gateDataChange()
  1710.   gateChange = true
  1711.   drawElement((termX / 2) - 8, termY, 6, 1, green, gray, "Save")
  1712. end
  1713.  
  1714. local function addNewAddress(fast)
  1715.   if abCount == 1 and addressBook[1].name == "NO GATES" and addressBook[1].addr == "ADDRESS" then
  1716.     addressBook[1].name = "Name"
  1717.   else
  1718.     abCount = abCount + 1
  1719.     addressBook[abCount] = { name = "Name", addr = "ADDRESS", rating = "U", iris = "none", callDrop = false, note = "short note", loc = { x = 0, y = 0, z = 0, dim = "Unspecified", }, }
  1720.   end
  1721.   gateChange = true
  1722.   gatePages = math.ceil(abCount / 12)                   --# Re-paginate AddressBook
  1723.   if fast then                                          --# if the new gate is a quick-add....
  1724.     if gatePage == gatePages then drawAddressBook() end --# ...and we're on the last page, redraw the address list
  1725.     drawNaviUI()
  1726.   else
  1727.     drawGateLabels(abCount)
  1728.   end
  1729. end
  1730.  
  1731. local function selectHost() --# host selection during initialization
  1732.   local hostCount, event, data, x, y, magicNumber, yPos = #hosts
  1733.   while hosts[1] do
  1734.     event, data, x, y = os.pullEvent()
  1735.     if event == "mouse_click" and x > 11 and x < 26 and y > 2 and y < 12 and data == 1 then
  1736.       yPos, magicNumber = 1, ((syncPage - 1) * 5) + 1
  1737.       for i = magicNumber, math.min(hostCount, magicNumber + 4) do
  1738.         yPos = yPos + 2
  1739.         if y == yPos then
  1740.           flashChoice(12, yPos, 14, 1, black, white, hosts[i].name)
  1741.           host.id, host.addr, host.ccDHD, host.name, host.status, remoteIrisState = hosts[i].id, hosts[i].addr, hosts[i].ccDHD, hosts[i].name, hosts[i].gStatus, hosts[i].remoteIris
  1742.           pingTimer = os.startTimer(5)
  1743.           return
  1744.         end
  1745.       end
  1746.     elseif event == "mouse_scroll" and ((data == 1 and syncPage < syncPages) or (data == -1 and syncPage > 1)) then
  1747.       syncPage = syncPage + data
  1748.       drawHostsUI()
  1749.     elseif event == "key" and syncPages > 1 then
  1750.       if (data == keys.pageUp and syncPage > 1) or (data == keys.pageDown and syncPage < syncPages) then
  1751.         syncPage = data == keys.pageUp and syncPage - 1 or syncPage + 1
  1752.         drawHostsUI()
  1753.       elseif (data == keys.home and syncPage > 1) or (data == keys["end"] and syncPage < syncPages) then
  1754.         syncPage = data == keys.home and 1 or syncPages
  1755.         drawHostsUI()
  1756.       end
  1757.     end
  1758.   end
  1759. end
  1760.  
  1761. local function findHosts()
  1762.   if runState ~= "init" then
  1763.     drawElement((termX / 2) - 9, 5, 20, 1, white, blue, "Sync")             --# Sync search header
  1764.     drawElement((termX / 2) - 9, 6, 20, 3, nil, gray, "Locating hosts ...") --# Sync search body
  1765.   end
  1766.   for i = #hosts, 1, -1 do hosts[i] = nil end       --# clear hosts table
  1767.   host.id, host.name, host.addr, host.status, remoteIrisState, syncPage, syncPages = nil, "NO HOST", "NO HOST", nil, "unk", 1, 1 --# reset host and remoteIrisState
  1768.   local hostIDs = { rednet.lookup("ccDialerWiFi") } --# populate hostIDs table
  1769.   local hostCount = #hostIDs
  1770.   if hostCount > 0 then
  1771.     local id, message, encryptedMessage, decryptedMessage, encodedMessage, encKey, success, msgData, msgLen, gateMatch, hostAddr, tempState, _, button, x, y
  1772.     for i = 1, hostCount do                         --# process host list
  1773.       hosts[i] = { }
  1774.       hosts[i].id, host.id, host.addr, host.ccDHD, host.status = hostIDs[i], hostIDs[i], "NO HOST", false, nil --# set the host and reset the gate information
  1775.       netSend({ program = "ccDialer", gate = "NO HOST", command = "QRY" }) --# query the host
  1776.       id, encodedMessage = rednet.receive("ccDialerWiFi", 1)
  1777.       if id == host.id and type(encodedMessage) == "string" then
  1778.         success, encryptedMessage = pcall(decode, encodedMessage)
  1779.         if success then
  1780.           encKey = thisCC .. "ccDHD!General_Comms*Key" .. tostring(id)
  1781.           success, decryptedMessage = pcall(decrypt, encKey, encryptedMessage)
  1782.           if success then
  1783.             success, message = pcall(textutils.unserialize, decryptedMessage)
  1784.             if success and type(message) == "table" and message.program and message.program == "ccDialer" and message.data and type(message.data) == "string" and type(message.ccDHD) == "boolean" then
  1785.               msgData = message.data
  1786.               msgLen = #msgData
  1787.               if msgLen == 7 or msgLen == 9 or msgData == "lockdown" then
  1788.                 host.addr, host.ccDHD, host.status, remoteIrisState = msgData, message.ccDHD, message.gStatus, message.iris
  1789.                 if host.addr ~= "lockdown" then --and host.addr ~= "NO HOST" then
  1790.                   gateMatch, hostAddr, hosts[i].name = false, host.addr, host.addr
  1791.                   for j = 1, abCount do --# Cycle through addressBook (for an entry matching host's address)
  1792.                     if hostAddr == addressBook[j].addr then --# if a match is found...
  1793.                       hosts[i].name = addressBook[j].name   --# ...set the hostName to the name of the addressBook entry...
  1794.                       gateMatch = true                      --# ...indicate a match has been found and...
  1795.                       break                                 --# ...stop the addressBook loop and continue with the next hostAddress
  1796.                     end
  1797.                   end
  1798.                   if not gateMatch then                                         --# Found a new gate
  1799.                     clearScreen()
  1800.                     if runState ~= "init" then drawHeader() end
  1801.                     tempState = runState
  1802.                     runState = "newGate"
  1803.                     drawElement(11, 4, 19, 1, white, blue, "New Gate Detected") --# New gate header
  1804.                     drawElement(11, 5, 19, 5, nil, gray)                        --# New gate body
  1805.                     drawElement(#hostAddr == 9 and 16 or 17, 6, 1, 1, silver, nil, hostAddr)
  1806.                     drawElement(14, 8, 5, 1, white, green, "Add")
  1807.                     drawElement(21, 8, 6, 1, nil, orange, "Skip")
  1808.                     while true do
  1809.                       _, button, x, y = os.pullEvent("mouse_click")
  1810.                       if x > 13 and x < 19 and y == 8 and button == 1 then      --# Add Gate
  1811.                         if abCount == 1 and (addressBook[1].name == "NO GATES" or addressBook[1].name == "Name") and addressBook[1].addr == "ADDRESS" then
  1812.                           addressBook[1].name, addressBook[1].addr, addressBook[1].note = hostAddr, hostAddr, "Discovered gate"
  1813.                         else
  1814.                           abCount = abCount + 1
  1815.                           addressBook[abCount] = { name = hostAddr, addr = hostAddr, rating = "U", iris = "none", callDrop = false, note = "Discovered gate", loc = { x = 0, y = 0, z = 0, dim = "Unspecified", }, }
  1816.                           gatePages = math.ceil(abCount / 12)                   --# Re-paginate AddressBook
  1817.                         end
  1818.                         saveData()
  1819.                         break
  1820.                       elseif x > 20 and x < 27 and y == 8 and button == 1 then  --# Skip
  1821.                         break
  1822.                       end
  1823.                     end
  1824.                     runState = tempState
  1825.                     drawElement(11, 4, 19, 6, nil, black)                       --# Clear pop-up
  1826.                   end
  1827.                 end
  1828.               end
  1829.             end
  1830.           end
  1831.         end
  1832.       end
  1833.       hosts[i].addr, hosts[i].ccDHD, hosts[i].gStatus, hosts[i].remoteIris = host.addr, host.ccDHD, host.status, remoteIrisState --# store gate address and ccDHD status
  1834.     end
  1835.     host.id, host.addr, host.status, remoteIrisState = nil, "NO HOST", nil, "unk" --# clear host info
  1836.     for i = hostCount, 1, -1 do                        --# process and sanitize host list
  1837.       if hosts[i].addr == "lockdown" or hosts[i].addr == "NO HOST" then --# if gate is in lockdown or hasn't replied...
  1838.         table.remove(hosts, i)                         --# ...remove entry from hosts table
  1839.       end
  1840.     end
  1841.     hostCount = #hosts
  1842.     syncPages = math.ceil(hostCount / 5)
  1843.   end
  1844.   if hostCount > 1 then                           --# more than 1 host responds
  1845.     if runState == "init" then
  1846.       drawElement(2, 2, termX - 1, termY - 1, nil, black) --# clear most of screen
  1847.       drawHostsUI()
  1848.       selectHost()
  1849.     else
  1850.       drawElement(1, 1, termX, termY, nil, black) --# clear screen
  1851.       drawHostsUI()
  1852.     end
  1853.   elseif hostCount == 1 then                      --# only one host responds
  1854.     host.id, host.name, host.addr, host.ccDHD, host.status, remoteIrisState = hosts[1].id, hosts[1].name, hosts[1].addr, hosts[1].ccDHD, hosts[1].gStatus, hosts[1].remoteIris --# set host ID, gate address, and ccDHD status
  1855.     if runState == "init" then
  1856.        drawElement(5, 12, 1, 1, green, gray, "O")
  1857.     else
  1858.       runState = "Dial"
  1859.       drawElement(10, 5, 20, 4, nil, black)       --# clear Sync pop-up
  1860.       drawCLI()
  1861.     end
  1862.   else                                            --# no hosts respond
  1863.     host.ccDHD, host.addr, host.name, host.id, host.status, remoteIrisState = false, "NO HOST", "NO HOST", nil, nil, "unk"
  1864.     if runState == "init" then
  1865.       drawElement(5, 12, 1, 1, red, gray, "0")
  1866.     else
  1867.       runState = "Dial"
  1868.       drawElement(10, 5, 20, 4, nil, black)       --# clear Sync pop-up
  1869.       drawCLI()
  1870.     end
  1871.   end
  1872. end
  1873.  
  1874. --# custom read function courtesy of theoriginalbit (modified by Dog)
  1875. local function read(_mask, _history, _limit, _noTerminate, _noMouse, _gField)
  1876.   if _mask and type(_mask) ~= "string" then
  1877.     error("Invalid parameter #1: Expected string, got " .. type(_mask), 2)
  1878.   end
  1879.   if _history and type(_history) ~= "table" then
  1880.     error("Invalid parameter #2: Expected table, got " .. type(_history), 2)
  1881.   end
  1882.   if _limit and type(_limit) ~= "number" then
  1883.     error("Invalid parameter #3: Expected number, got " .. type(_limit), 2)
  1884.   end
  1885.   if _noTerminate and type(_noTerminate) ~= "boolean" then
  1886.     error("Invalid argument #4: Expected boolean, got " .. nativeType(_noTerminate), 2)
  1887.   end
  1888.   if _noMouse and type(_noMouse) ~= "boolean" then
  1889.     error("Invalid argument #5: Expected boolean, got " .. nativeType(_noMouse), 2)
  1890.   end
  1891.   if _gField and type(_gField) ~= "boolean" then
  1892.     error("Invalid argument #6: Expected boolean, got " .. nativeType(_gField), 2)
  1893.   end
  1894.   term.setCursorBlink(true)
  1895.   gettingInput = true
  1896.   local symbols = { ["A"] = true, ["B"] = true, ["C"] = true, ["D"] = true, ["E"] = true , ["F"] = true, ["G"] = true, ["H"] = true, ["I"] = true, ["J"] = true, ["K"] = true, ["L"] = true, ["M"] = true, ["N"] = true, ["O"] = true, ["P"] = true, ["Q"] = true, ["R"] = true, ["S"] = true, ["T"] = true, ["U"] = true, ["V"] = true, ["W"] = true, ["X"] = true, ["Y"] = true, ["Z"] = true, ["0"] = true, ["1"] = true, ["2"] = true, ["3"] = true, ["4"] = true, ["5"] = true, ["6"] = true, ["7"] = true, ["8"] = true, ["9"] = true, ["-"] = true, ["+"] = true }
  1897.   local mouseLimit = _limit or 0
  1898.   local input, lineCleared = "", false
  1899.   word = ""
  1900.   local pos = 0
  1901.   local historyPos = nil
  1902.   local pullEvent = _noTerminate and os.pullEventRaw or os.pullEvent
  1903.   local sw, sh = term.getSize()
  1904.   local sx, sy = term.getCursorPos()
  1905.   curX, curY = sx, sy
  1906.   local function redraw(_special)
  1907.     local scroll = (sx + pos >= sw and (sx + pos) - sw or 0)
  1908.     local replace = _special or _mask
  1909.     local output = replace and (string.rep(replace, math.ceil(#input / #replace) - scroll)):sub(1, #input) or input:sub(scroll + 1)
  1910.     term.setCursorPos(sx, sy)
  1911.     term.write(output)
  1912.     term.setCursorPos(sx + pos - scroll, sy)
  1913.   end
  1914.   local nativeScroll = term.scroll
  1915.   term.scroll = function(_n) local ok, err = pcall(function() return nativeScroll(_n) end) if ok then sy = sy - _n return err end error(err, 2) end
  1916.   local function historyHandler(value)
  1917.     redraw(' ')
  1918.     if value == -1 or value == keys.up then
  1919.       if not historyPos then
  1920.         historyPos = #_history
  1921.       elseif historyPos > 1 then
  1922.         historyPos = historyPos - 1
  1923.       end
  1924.     else
  1925.       if historyPos ~= nil and historyPos < #_history then
  1926.         historyPos = historyPos + 1
  1927.       elseif historyPos == #_history then
  1928.         historyPos = nil
  1929.       end
  1930.     end
  1931.     if historyPos and #_history > 0 then
  1932.       input = string.sub(_history[historyPos], 1, _limit) or ""
  1933.       pos = #input
  1934.       word = input
  1935.       if not _limit then mouseLimit = pos end
  1936.     else
  1937.       input = ""
  1938.       pos = 0
  1939.       word = input
  1940.       if not _limit then mouseLimit = 0 end
  1941.     end
  1942.   end
  1943.   while true do
  1944.     local event, code, x, y = pullEvent()
  1945.     if event == "char" and (not _limit or #input < _limit) then
  1946.       if not lineCleared and pos == 0 then term.write(string.rep(" ", _limit)) lineCleared = true end
  1947.       local goodData = false
  1948.       if _gField then
  1949.         if symbols[code:upper()] then
  1950.           code = code:upper()
  1951.           goodData = true
  1952.         end
  1953.       else
  1954.         goodData = true
  1955.       end
  1956.       if goodData then
  1957.         input = input:sub(1, pos) .. code .. input:sub(pos + 1)
  1958.         pos = pos + 1
  1959.         word = input
  1960.         if not _limit then mouseLimit = math.min(mouseLimit + 1, sw - (sw - sx)) end
  1961.       end
  1962.     elseif event == "paste" and (not _limit or #input < _limit) then
  1963.       if not lineCleared and pos == 0 then term.write(string.rep(" ", _limit)) lineCleared = true end
  1964.       if _limit and #input + #code > _limit then
  1965.         code = code:sub(1, _limit - #input)
  1966.       end
  1967.       if _gField then
  1968.         local newWord, glyph = { }, ""
  1969.         for i = 1, #code do
  1970.           glyph = string.upper(code:sub(i, i))
  1971.           newWord[i] = symbols[glyph] and glyph or "?"
  1972.         end
  1973.         code = table.concat(newWord)
  1974.       end
  1975.       input = input:sub(1, pos) .. code .. input:sub(pos + 1)
  1976.       pos = pos + #code
  1977.       word = input
  1978.       if not _limit then mouseLimit = math.min(mouseLimit + #code, sw - (sw - sx)) end
  1979.     elseif event == "key" then
  1980.       if code == keys.enter or code == keys.numPadEnter then
  1981.         break
  1982.       elseif code == keys.backspace and pos > 0 then
  1983.         redraw(' ')
  1984.         input = input:sub(1, math.max(pos - 1, 0)) .. input:sub(pos + 1)
  1985.         pos = math.max(pos - 1, 0)
  1986.         word = input
  1987.         if not _limit then mouseLimit = math.max(mouseLimit - 1, 0) end
  1988.       elseif code == keys.delete and pos < #input then
  1989.         redraw(' ')
  1990.         input = input:sub(1, pos)..input:sub(pos + 2)
  1991.         word = input
  1992.         if not _limit then mouseLimit = math.max(mouseLimit - 1, 0) end
  1993.       elseif code == keys.home then
  1994.         pos = 0
  1995.       elseif code == keys["end"] then
  1996.         pos = #input
  1997.       elseif code == keys.left and pos > 0 then
  1998.         pos = math.max(pos - 1, 0)
  1999.       elseif code == keys.right and pos < #input then
  2000.         pos = math.min(pos + 1, #input)
  2001.       elseif _history and code == keys.up or code == keys.down then
  2002.         historyHandler(code)
  2003.       end
  2004.     elseif event == "mouse_click" and not _noMouse and ((x < sx or x >= sx + mouseLimit) or (y ~= sy)) then
  2005.       break
  2006.     elseif event == "mouse_scroll" and _history then
  2007.       historyHandler(code)
  2008.     end
  2009.     redraw(_mask)
  2010.   end
  2011.   term.scroll = nativeScroll
  2012.   term.setCursorBlink(false)
  2013.   if sy + 1 > sh then
  2014.     term.scroll(sy + 1 - sh)
  2015.     term.setCursorPos(1, sy)
  2016.   else
  2017.     term.setCursorPos(1, sy + 1)
  2018.   end
  2019.   word = ""
  2020.   gettingInput = false
  2021.   return input
  2022. end
  2023.  
  2024. local function remotePassword()
  2025.   drawElement(1, 4, termX, termY - 3, nil, black) --# clear most of screen
  2026.   drawPasswordUI()
  2027.   runState, bgCol, txtCol = "RemotePass", gray, white
  2028.   term.setBackgroundColor(gray)
  2029.   term.setTextColor(white)
  2030.   term.setCursorPos(14, 7)
  2031.   local remotePass = read("*", nil, 12)
  2032.   if remotePass ~= "" then
  2033.     local pass = table.concat(pbkdf2(remotePass, "ccDHD!Pass.Hash", 15))
  2034.     netSend({ program = "ccDialer", gate = host.addr, password = pass })
  2035.   end
  2036.   drawElement(2, 5, termX - 2, 5, nil, black) --# clear iris pop-up
  2037.   runState = "Dial"
  2038.   drawAddressBook()
  2039.   drawNaviUI()
  2040. end
  2041.  
  2042. local function saveAndQuit()
  2043.   local tX, tY = math.floor(termX / 2), math.floor(termY / 2)
  2044.   drawElement(tX - 9, tY - 1, 1, 1, white, blue, "  Save Addr Book ?  ")
  2045.   drawElement(tX - 9, tY, 20, 3, nil, gray)
  2046.   drawElement(tX - 8, tY + 1, 1, 1, nil, green, "  Save  ")
  2047.   drawElement(tX + 2, tY + 1, 1, 1, nil, orange, "  Quit  ")
  2048.   while true do
  2049.     local _, button, x, y = os.pullEvent("mouse_click")
  2050.     if x > tX - 10 and x < tX + 11 and y > tY - 2 and y < tY + 3 then
  2051.       if x > tX - 9 and x < tX and y == tY + 1 and button == 1 then
  2052.         flashChoice(tX - 8, tY + 1, 8, 1, green, white, "Save")
  2053.         saveData()
  2054.         return true
  2055.       elseif x > tX + 1 and x < tX + 10 and y == tY + 1 and button == 1 then
  2056.         flashChoice(tX + 2, tY + 1, 8, 1, orange, white, "Quit")
  2057.         return true
  2058.       end
  2059.     else
  2060.       drawElement(tX - 9, tY - 1, 20, 4, nil, black) --# clear save & quit pop-up
  2061.       drawAddressBook()
  2062.       return false
  2063.     end
  2064.   end
  2065. end
  2066.  
  2067. local function userInput()
  2068.   local event, data, x, y
  2069.   while true do
  2070.     event, data, x, y = os.pullEvent()
  2071.     if event == "mouse_click" then
  2072.       if y == 1 and runState ~= "selectHost" then
  2073.         if x > termX - 3 and data == 1 then               --# 'X' (exit button)
  2074.           flashChoice(termX - 1, 1, 1, 1, white, runState == "viewing" and assignColor(currentEdit) or blue, "X")
  2075.           drawElement(termX - 1, 1, 1, 1, red, nil, "X")
  2076.           if runState == "Dial" then
  2077.             if gateChange then
  2078.               if saveAndQuit() then shutDown() break end
  2079.             else
  2080.               shutDown()
  2081.               break
  2082.             end
  2083.           else
  2084.             runState = "Dial"
  2085.             clearScreen()
  2086.             drawCLI()
  2087.           end
  2088.         elseif x < 4 and runState == "Dial" and data == 1 then --# 'H' (hosts button)
  2089.           drawElement(2, 1, 1, 1, gray, blue, "H")
  2090.           drawElement(6, 1, 1, 1, nil, nil, "I")
  2091.           drawElement(2, 2, 1, 1, silver, gray, "NO HOST     ")
  2092.           drawElement(22, 2, 10, 1)
  2093.           drawElement(1, 3, termX, termY - 2, nil, black) --# clear most of screen
  2094.           if host.id then netSend({ program = "ccDialer", gate = host.addr, command = "logout" }) end
  2095.           runState, remoteIrisState = "selectHost", "unk"
  2096.           findHosts()
  2097.         elseif x > 4 and x < 8 and runState == "Dial" then --# 'I' (iris button)
  2098.           if data == 1 and host.status == "Connected" then --# host.addr ~= "NO HOST"
  2099.             remotePassword()
  2100.           elseif data == 3 then
  2101.             remoteIrisState = "unk"
  2102.             drawElement(6, 1, 1, 1, gray, blue, "I")      --# 'I' for Iris control
  2103.           end
  2104.         end
  2105.       end
  2106.       if runState == "viewing" then                       --# Gate Viewing / Editing
  2107.         if y == 4 and data == 1 then
  2108.           if x > 1 and x < 11 then                        --# Name
  2109.             drawElement(2, 4, 1, 1, gray, black, addressBook[currentEdit].name)
  2110.             bgCol, txtCol = black, cyan
  2111.             term.setTextColor(cyan)
  2112.             term.setCursorPos(2, 4)
  2113.             local newGateName = read(nil, { addressBook[currentEdit].addr, addressBook[currentEdit].name }, 12)
  2114.             if newGateName ~= addressBook[currentEdit].name and newGateName ~= "" then
  2115.               addressBook[currentEdit].name = newGateName
  2116.               gateDataChange()
  2117.             end
  2118.             drawElement(2, 4, 1, 1, cyan, black, addressBook[currentEdit].name .. string.rep(" ", 12 - #addressBook[currentEdit].name))
  2119.           elseif x > 19 and x < 20 + #tostring(currentEdit) then --# Position / reorder
  2120.             drawElement(20, 4, 1, 1, gray, black, currentEdit)
  2121.             bgCol, txtCol = black, white
  2122.             term.setTextColor(white)
  2123.             term.setCursorPos(20, 4)
  2124.             local newPos = tonumber(read(nil, nil, 5))
  2125.             if newPos and newPos > 0 and newPos <= abCount and newPos ~= currentEdit then
  2126.               local tempGateData = { }
  2127.               for k, v in pairs(addressBook[currentEdit]) do
  2128.                 tempGateData[k] = v
  2129.               end
  2130.               table.remove(addressBook, currentEdit)
  2131.               table.insert(addressBook, newPos, tempGateData)
  2132.               currentEdit = newPos
  2133.               gateDataChange()
  2134.             end
  2135.             drawElement(20, 4, 1, 1, white, black, tostring(currentEdit) .. string.rep(" ", 5 - #tostring(currentEdit)))
  2136.           elseif x > 25 and x < termX then                --# Dimension
  2137.             drawElement(26, 4, 1, 1, gray, black, addressBook[currentEdit].loc.dim:sub(1, 13))
  2138.             bgCol, txtCol = black, brown
  2139.             term.setTextColor(brown)
  2140.             term.setCursorPos(26, 4)
  2141.             local newDim = read(nil, { "Overworld", "Nether", "The End", addressBook[currentEdit].loc.dim }, 19)
  2142.             if newDim ~= "" and newDim ~= addressBook[currentEdit].loc.dim then
  2143.               addressBook[currentEdit].loc.dim = newDim
  2144.               if #newDim > 13 then drawElement(26, 4, 13, 1) end
  2145.               gateDataChange()
  2146.             end
  2147.             drawElement(26, 4, 1, 1, brown, black, addressBook[currentEdit].loc.dim:sub(1, 13) .. string.rep(" ", 13 - #addressBook[currentEdit].loc.dim:sub(1, 13)))
  2148.           end
  2149.         elseif y == 5 and x > 28 and x < termX - 1 and data == 1 then --# X
  2150.           local xStr = tostring(addressBook[currentEdit].loc.x)
  2151.           drawElement(29, 5, 1, 1, gray, black, xStr)
  2152.           bgCol, txtCol = black, silver
  2153.           term.setTextColor(silver)
  2154.           term.setCursorPos(29, 5)
  2155.           local newX = tonumber(read(nil, { xStr }, 9))
  2156.           if newX and newX ~= addressBook[currentEdit].loc.x then
  2157.             addressBook[currentEdit].loc.x = newX
  2158.             gateDataChange()
  2159.           end
  2160.           drawElement(29, 5, 1, 1, silver, black, tostring(addressBook[currentEdit].loc.x) .. string.rep(" ", 9 - #tostring(addressBook[currentEdit].loc.x)))
  2161.         elseif y == 6 then
  2162.           if x > 1 and x < 11 then                        --# Address
  2163.             if data == 1 then
  2164.               drawElement(2, 6, 1, 1, gray, black, addressBook[currentEdit].addr)
  2165.               bgCol, txtCol = black, yellow
  2166.               term.setTextColor(yellow)
  2167.               term.setCursorPos(2, 6)
  2168.               local newGateAddress = read(nil, { addressBook[currentEdit].addr }, 9, nil, nil, true)
  2169.               local ngaLen, name = #newGateAddress, addressBook[currentEdit].name
  2170.               if (ngaLen == 7 or ngaLen == 9) and newGateAddress ~= addressBook[currentEdit].addr and not newGateAddress:find("?") then
  2171.                 addressBook[currentEdit].addr = newGateAddress
  2172.                 if name == "Name" or name == "NEW GATE" or name == "NO GATES" then
  2173.                   addressBook[currentEdit].name = newGateAddress
  2174.                   drawElement(2, 4, 1, 1, sky, nil, addressBook[currentEdit].name .. string.rep(" ", 12 - #addressBook[currentEdit].name))
  2175.                 end
  2176.                 gateDataChange()
  2177.               end
  2178.               drawElement(2, 6, 1, 1, yellow, black, addressBook[currentEdit].addr .. "  ")
  2179.             elseif data == 2 and host.id and addressBook[currentEdit].addr ~= host.addr then
  2180.               netSend({ program = "ccDialer", gate = host.addr, command = addressBook[currentEdit].addr })
  2181.               runState = "Dial"
  2182.               clearScreen()
  2183.               drawCLI()
  2184.             end
  2185.           elseif x > 28 and x < termX - 1 and data == 1 then --# Y
  2186.             local yStr = tostring(addressBook[currentEdit].loc.y)
  2187.             drawElement(29, 6, 1, 1, gray, black, yStr)
  2188.             bgCol, txtCol = black, silver
  2189.             term.setTextColor(silver)
  2190.             term.setCursorPos(29, 6)
  2191.             local newY = tonumber(read(nil, { yStr }, 9))
  2192.             if newY and newY ~= addressBook[currentEdit].loc.y then
  2193.               addressBook[currentEdit].loc.y = newY
  2194.               gateDataChange()
  2195.             end
  2196.             drawElement(29, 6, 1, 1, silver, black, tostring(addressBook[currentEdit].loc.y) .. string.rep(" ", 9 - #tostring(addressBook[currentEdit].loc.y)))
  2197.           end
  2198.         elseif y == 7 and x > 28 and x < termX - 1 and data == 1 then --# Z
  2199.           local zStr = tostring(addressBook[currentEdit].loc.z)
  2200.           drawElement(29, 7, 1, 1, gray, black, zStr)
  2201.           bgCol, txtCol = black, silver
  2202.           term.setTextColor(silver)
  2203.           term.setCursorPos(29, 7)
  2204.           local newZ = tonumber(read(nil, { zStr }, 9))
  2205.           if newZ and newZ ~= addressBook[currentEdit].loc.z then
  2206.             addressBook[currentEdit].loc.z = newZ
  2207.             gateDataChange()
  2208.           end
  2209.           drawElement(29, 7, 1, 1, silver, black, tostring(addressBook[currentEdit].loc.z) .. string.rep(" ", 9 - #tostring(addressBook[currentEdit].loc.z)))
  2210.         elseif y == 8 and x > 1 and x < 4 + #assignRating(currentEdit) and data == 1 then --# Classification
  2211.           drawRatingList(addressBook[currentEdit].rating)
  2212.           local selected = false
  2213.           while true do
  2214.             local _, button, mX, mY = os.pullEvent("mouse_click")
  2215.             if mX > 5 and mX < 26 and mY > 3 and mY < 12 and button == 1 then
  2216.               for k, v in pairs(classifications) do
  2217.                 if mY == v.order + 3 then
  2218.                   if addressBook[currentEdit].rating ~= k then
  2219.                     addressBook[currentEdit].rating = k
  2220.                     gateDataChange()
  2221.                   end
  2222.                   selected = true
  2223.                   break
  2224.                 end
  2225.               end
  2226.               if selected then break end
  2227.             else
  2228.               break
  2229.             end
  2230.           end
  2231.           drawElement(6, 4, 20, 11, nil, black)           --# clear menu body
  2232.           drawElement(19, 4, 1, 1, gray, black, "#")
  2233.           if gateChange and selected then drawHeader() end
  2234.           drawGateData()
  2235.         elseif ((y == 10 and x > 1 and x < 2 + #addressBook[currentEdit].note:sub(1, 37)) or (y == 11 and #addressBook[currentEdit].note > 24 and x > 1 and x < 2 + #addressBook[currentEdit].note:sub(25, 43))) and data == 1 then --# Note
  2236.           drawElement(2, 10, 1, 1, gray, black, addressBook[currentEdit].note:sub(1, 37))
  2237.           if #addressBook[currentEdit].note > 37 then
  2238.             drawElement(2, 11, 1, 1, nil, nil, addressBook[currentEdit].note:sub(38, 43))
  2239.           end
  2240.           bgCol, txtCol = black, white
  2241.           term.setTextColor(white)
  2242.           term.setCursorPos(2, 10)
  2243.           local newNote = read(nil, { addressBook[currentEdit].note }, 43)
  2244.           if newNote ~= "" and newNote ~= addressBook[currentEdit].note then
  2245.             addressBook[currentEdit].note = newNote
  2246.             gateDataChange()
  2247.           end
  2248.           drawElement(2, 10, 1, 1, white, black, addressBook[currentEdit].note:sub(1, 37) .. string.rep(" ", 38 - #addressBook[currentEdit].note:sub(1, 37)))
  2249.           if #addressBook[currentEdit].note > 24 then
  2250.             drawElement(2, 11, 1, 1, nil, nil, addressBook[currentEdit].note:sub(38, 43) .. string.rep(" ", 6 - #addressBook[currentEdit].note:sub(38, 43)))
  2251.           else
  2252.             drawElement(2, 11, 19, 1)                     --# clear line
  2253.           end
  2254.         elseif y == termY and data == 1 then              --# Save / Close
  2255.           if x > math.floor(termX / 2) - 9 and x < math.floor(termX / 2) - 1 and gateChange then
  2256.             flashChoice((termX / 2) - 8, termY, 6, 1, white, green, "Save")
  2257.             drawElement((termX / 2) - 8, termY, 6, 1, silver, gray, "Save")
  2258.             saveData()
  2259.           elseif x > math.floor(termX / 2) + 1 and x < math.floor(termX / 2) + 9 then
  2260.             flashChoice((termX / 2) + 2, termY, 7, 1, white, red, "Close")
  2261.             runState = "Dial"
  2262.             drawElement(1, 4, termX - 1, termY - 3, nil, black) --# clear most of screen
  2263.             drawCLI()
  2264.           end
  2265.         end
  2266.       elseif runState == "Dial" then                      --# Dial (main screen)
  2267.         if y == 3 then                                    --# Sub-header buttons (Sync, + , End Call)
  2268.           if x < math.floor(termX / 2) and host.ccDHD and data == 1 then --# Sync
  2269.             flashChoice(1, 3, (termX / 2) - 1, 1, cyan, white, "Sync")
  2270.             drawElement(1, 4, termX, termY - 2, nil, black) --# clear most of screen
  2271.             runState = "Sync"
  2272.             drawCLI()
  2273.           elseif x > math.floor(termX / 2) - 1 and x < math.floor(termX / 2) + 3 then --# '+' Add Gate / Quick-add Gate / Save Address Book
  2274.             if data == 1 then                             --# Add gate
  2275.               flashChoice(termX / 2, 3, 3, 1, green, white, " + ")
  2276.               addNewAddress()
  2277.             elseif data == 2 and gateChange then          --# Save Address Book
  2278.               flashChoice(termX / 2, 3, 3, 1, green, white, " + ")
  2279.               drawElement(termX / 2, 3, 3, 1, white, green, " + ")
  2280.               saveData()
  2281.             elseif data == 3 then                         --# Quick-Add gate
  2282.               flashChoice(termX / 2, 3, 3, 1, green, white, " + ")
  2283.               drawElement(termX / 2, 3, 3, 1, orange, green, " + ")
  2284.               addNewAddress(true)
  2285.             end
  2286.           elseif x > math.floor(termX / 2) + 2 and host.id and (host.status == "Dialing" or host.status == "Connected" or dialAddress ~= "none") and data == 1 then --# End Call
  2287.             flashChoice((termX / 2) + 3, 3, 18, 1, red, white, "END Call")
  2288.             drawElement((termX / 2) + 3, 3, 18, 1, white, red, "END Call")
  2289.             remoteIrisState, dialAddress = "unk", "none"
  2290.             drawElement(6, 1, 1, 1, gray, blue, "I")      --# 'I' for Iris control
  2291.             drawElement(22, 2, 9, 1, nil, gray)           --# clear local gate status area
  2292.             drawAddressBook()
  2293.             netSend({ program = "ccDialer", gate = host.addr, command = "endCall" })
  2294.           end
  2295.         elseif y > 4 and y < 12 then                      --# Dial a listed address, view it's info, or delete it
  2296.           local xPos, yPos, magicNumber = 1, 5, ((gatePage - 1) * 12) + 1
  2297.           for i = magicNumber, math.min(abCount, gatePage * 12) do
  2298.             if x >= xPos and x <= xPos + 11 and y == yPos then
  2299.               if data == 1 and host.id and host.addr ~= addressBook[i].addr and host.status ~= "Connected" then --# Dial / Pause
  2300.                 dialAddress = addressBook[i].addr
  2301.                 flashDial(addressBook[i].name, xPos, yPos, i)
  2302.                 netSend({ program = "ccDialer", gate = host.addr, command = dialAddress })
  2303.                 drawAddressBook()
  2304.               elseif data == 2 then                       --# View/Edit
  2305.                 flashDial(addressBook[i].name, xPos, yPos, i)
  2306.                 drawGateLabels(i)
  2307.               elseif data == 3 and abCount > 1 then  --# Delete entry if there is more than one entry in the address book
  2308.                 flashDial(addressBook[i].name, xPos, yPos, i)
  2309.                 if addressBook[i].addr == host.addr then host.name = host.addr drawHeader() end
  2310.                 table.remove(addressBook, i)
  2311.                 gateChange, abCount = true, abCount - 1
  2312.                 gatePages = math.ceil(abCount / 12)  --# Re-paginate AddressBook
  2313.                 gatePage = math.min(gatePage, gatePages)
  2314.                 if gatePage == gatePages then drawElement(2, 5, termX - 2, termY - 5, nil, black) end --# Clear address book area of screen
  2315.                 drawElement((termX / 2) + 1, 3, 1, 1, orange, green, "+")
  2316.                 drawAddressBook()
  2317.                 drawNaviUI()
  2318.               end
  2319.               break
  2320.             else
  2321.               yPos = yPos + 2
  2322.               if yPos > 11 then xPos = xPos + 13 yPos = 5 end
  2323.             end
  2324.           end
  2325.         elseif y == termY and gatePages > 1 and data == 1 then --# Page Navigation via click
  2326.           if (x > 8 and x < 11 and gatePage > 1) or (x > 28 and x < 31 and gatePage < gatePages) then --# Home / End
  2327.             gatePage = x < 11 and 1 or gatePages
  2328.             if gatePage == gatePages then drawElement(1, 4, termX - 1, termY - 4, nil, black) end --# clear most of screen
  2329.             drawAddressBook()
  2330.             drawNaviUI()
  2331.           elseif (x > 11 and x < 13 and gatePage > 1) or (x > 26 and x < 28 and gatePage < gatePages) then --# Page Back / Page Forward
  2332.             gatePage = x < 13 and gatePage - 1 or gatePage + 1
  2333.             if gatePage == gatePages then drawElement(1, 4, termX - 1, termY - 4, nil, black) end --# clear most of screen
  2334.             drawAddressBook()
  2335.             drawNaviUI()
  2336.           elseif x > math.floor((termX - #pNum) / 2) and x < math.floor((termX + #pNum) / 2) + 1 and gatePages > 1 then --# Page Numbers (Go To Page dialogue)
  2337.             local pNumW = #pNum
  2338.             flashChoice(((termX - pNumW) / 2) + 1, termY, pNumW, 1, black, gray, pNum)
  2339.             drawElement(((termX - pNumW) / 2) + 1, termY, pNumW, 1, gray, black, pNum)
  2340.             drawGoPagePopUp()
  2341.             runState, bgCol, txtCol = "goPage", black, white
  2342.             term.setCursorPos(math.floor(termX / 2) - 1, termY - 2)
  2343.             local newPage = tonumber(read(nil, nil, 4))                            --# user input
  2344.             drawElement((termX / 2) - 3, termY - 3, 8, 3)                          --# clear 'Page' dialogue
  2345.             gatePage = newPage or gatePage                                         --# set gate page
  2346.             gatePage = math.floor(math.max(1, math.min(gatePage, gatePages)))
  2347.             if gatePage == gatePages and gatePages > 1 then drawElement(1, 4, termX - 1, termY - 4) end --# ...clear the data area if on the last page and there is more than one page
  2348.             runState = "Dial"                                                      --# restore runState
  2349.             drawAddressBook()
  2350.             drawNaviUI()
  2351.           end
  2352.         end
  2353.       elseif runState == "Sync" then                      --# Sync
  2354.         if x > 1 and x < termX and y > 3 and y < 13 and data == 1 then
  2355.           if y > 3 and y < 7 then                         --# Import
  2356.             flashChoice(2, 4, termX - 2, 3, green, white, "GET Address Book")
  2357.             netSend({ program = "ccDialer", gate = host.addr, command = "pull" })
  2358.             runState = "Dial"
  2359.           elseif y > 6 and y < 10 then                    --# Export
  2360.             flashChoice(2, 7, termX - 2, 3, orange, white, "SEND Address Book")
  2361.             netSend({ program = "ccDialer", gate = host.addr, command = "push", data = addressBook })
  2362.             runState = "Dial"
  2363.           elseif y > 9 and y < 13 then                    --# Cancel
  2364.             flashChoice(2, 10, termX - 2, 3, red, white, "C A N C E L")
  2365.             runState = "Dial"
  2366.           end
  2367.           if runState == "Dial" then
  2368.             drawElement(2, 4, termX - 1, termY - 3, nil, black) --# clear most of screen
  2369.             drawCLI()
  2370.           end
  2371.         end
  2372.       elseif runState == "selectHost" then                --# Hosts list
  2373.         if x > 11 and x < 26 and y > 2 and y < 12 and data == 1 then
  2374.           local yPos, hostCount, magicNumber = 1, #hosts, ((syncPage - 1) * 5) + 1
  2375.           for i = magicNumber, math.min(hostCount, magicNumber + 4) do
  2376.             yPos = yPos + 2
  2377.             if y == yPos then
  2378.               flashChoice(12, yPos, 14, 1, black, white, hosts[i].name)
  2379.               runState, host.id, host.addr, host.ccDHD, host.name, host.status, remoteIrisState = "Dial", hosts[i].id, hosts[i].addr, hosts[i].ccDHD, hosts[i].name, hosts[i].gStatus, hosts[i].remoteIris
  2380.               pingTimer = os.startTimer(5)
  2381.               missedPings = 0
  2382.               drawElement(11, 2, 16, 11, nil, black)      --# Clear hosts UI
  2383.               drawCLI()
  2384.               break
  2385.             end
  2386.           end
  2387.         end
  2388.       end
  2389.     elseif event == "mouse_scroll" then
  2390.       if runState == "Dial" and ((data == 1 and gatePage < gatePages) or (data == -1 and gatePage > 1)) then --# Address book
  2391.         gatePage = gatePage + data
  2392.         if gatePage == gatePages then drawElement(1, 5, termX - 1, termY - 5, nil, black) end --# clear address book area
  2393.         drawAddressBook()
  2394.         drawNaviUI()
  2395.       elseif runState == "viewing" and ((data == 1 and currentEdit < abCount) or (data == -1 and currentEdit > 1)) then --# View/Edit address book entry
  2396.         currentEdit = currentEdit + data
  2397.         drawHeader()
  2398.         drawGateData()
  2399.       elseif runState == "selectHost" and ((data == 1 and syncPage < syncPages) or (data == -1 and syncPage > 1)) then --# Hosts list
  2400.         syncPage = syncPage + data
  2401.         drawHostsUI()
  2402.       end
  2403.     elseif event == "key" then
  2404.       if data == keys.f1 and (runState == "Dial" or runState == "Help") then
  2405.         runState = runState == "Dial" and "Help" or "Dial"
  2406.         clearScreen(runState == "Dial" and black or white)
  2407.         drawCLI()
  2408.       elseif data == keys.pageUp or data == keys.pageDown then
  2409.         if runState == "Dial" and ((data == keys.pageUp and gatePage > 1) or (data == keys.pageDown and gatePage < gatePages)) then --# Address book
  2410.           gatePage = data == keys.pageUp and gatePage - 1 or gatePage + 1
  2411.           if gatePage == gatePages then drawElement(1, 4, termX - 1, termY - 4, nil, black) end --# clear data area of screen
  2412.           drawAddressBook()
  2413.           drawNaviUI()
  2414.         elseif runState == "selectHost" and ((data == keys.pageUp and syncPage > 1) or (data == keys.pageDown and syncPage < syncPages)) then --# Hosts list
  2415.           syncPage = data == keys.pageUp and syncPage - 1 or syncPage + 1
  2416.           drawHostsUI()
  2417.         end
  2418.       elseif (data == keys.home or data == keys["end"]) then
  2419.         if runState == "Dial" and ((data == keys.home and gatePage > 1) or (data == keys["end"] and gatePage < gatePages)) then --# Address book
  2420.           gatePage = data == keys.home and 1 or gatePages
  2421.           if gatePage == gatePages then drawElement(1, 4, termX - 1, termY - 4, nil, black) end --# clear data area of screen
  2422.           drawAddressBook()
  2423.           drawNaviUI()
  2424.         elseif runState == "selectHost" and ((data == keys.home and syncPage > 1) or (data == keys["end"] and syncPage < syncPages)) then --# Hosts list
  2425.           syncPage = data == keys.home and 1 or syncPages
  2426.           drawHostsUI()
  2427.         end
  2428.       end
  2429.     end
  2430.   end
  2431. end
  2432.  
  2433. local function dataPoller()
  2434.   local _, timer
  2435.   while true do
  2436.     _, timer = os.pullEvent("timer")
  2437.     if timer == pingTimer then
  2438.       missedPings = missedPings + 1
  2439.       if missedPings > 2 then
  2440.         if dialAddress ~= "none" then
  2441.           remoteIrisState, dialAddress = "unk", "none"
  2442.           if runState == "Sync" then
  2443.             runState = "Dial"
  2444.             drawElement(1, 5, termX - 1, termY - 5, nil, black) --# clear address book area
  2445.           end
  2446.         end
  2447.         host = { id = nil, name = "NO HOST", addr = "NO HOST", ccDHD = false, status = nil }
  2448.         pingTimer = nil
  2449.         missedPings = 0
  2450.         if runState == "Dial" or runState == "goPage" then
  2451.           drawHeader()
  2452.           drawCLI()
  2453.           if gettingInput then repositionCursor() end
  2454.         end
  2455.       elseif host.id then
  2456.         netSend({ program = "ccDialer", gate = host.addr, command = "ping" })
  2457.         pingTimer = os.startTimer(5)
  2458.       end
  2459.     end
  2460.   end
  2461. end
  2462.  
  2463. local function startupError(id)
  2464.   clearScreen()
  2465.   if id == "neural" then
  2466.     drawElement(2, 2, 1, 1, term.isColor() and red or white, black, "No Plethora neural")
  2467.     drawElement(2, 3, 1, 1, nil, nil, "interface detected!")
  2468.     drawElement(2, 5, 1, 1, nil, nil, "ccDialer REQUIRES a")
  2469.     drawElement(2, 6, 1, 1, nil, nil, "Plethora neural interface.")
  2470.   elseif id == "modem" then
  2471.     drawElement(2, 2, 1, 1, red, black, "No wireless")
  2472.     drawElement(2, 3, 1, 1, nil, nil, "modem detected!")
  2473.     drawElement(2, 5, 1, 1, nil, nil, "ccDialer REQUIRES")
  2474.     drawElement(2, 6, 1, 1, nil, nil, "a wireless modem.")
  2475.   elseif id == "resolution" then
  2476.     drawElement(2, 2, 1, 1, red, black, "Screen size")
  2477.     drawElement(2, 3, 1, 1, nil, nil, "is wrong!")
  2478.     drawElement(2, 5, 1, 1, nil, nil, "ccDialer REQUIRES")
  2479.     drawElement(2, 6, 1, 1, nil, nil, "39x12 or 39x13.")
  2480.   end
  2481.   term.setTextColor(white)
  2482.   term.setCursorPos(1, 8)
  2483. end
  2484.  
  2485. if not term.isColor() or pocket or turtle then return startupError("neural") end
  2486. if termX ~= 39 or termY < 12 or termY > 13 then return startupError("resolution") end
  2487. clearScreen()
  2488. drawElement(9, 2, 22, 1, white, blue, "ccDialer Init...")
  2489. drawElement(9, 3, 22, 9, nil, gray)
  2490. local initSteps = {
  2491.   "Check CC label";
  2492.   "Hardware Discovery";
  2493.   "Ingest gate data";
  2494.   "Locate Sync host";
  2495. }
  2496. for i = 4, 10, 2 do
  2497.   drawElement(10, i, 1, 1, red, nil, "0")
  2498.   drawElement(12, i, 1, 1, silver, nil, initSteps[(i / 2) - 1])
  2499. end
  2500. drawElement(10, 4, 1, 1, orange, nil, "0")
  2501. drawElement(12, 4, 1, 1, white, nil, "Check CC label")
  2502. if not os.getComputerLabel() then os.setComputerLabel("Neural.cc#" .. thisCC) end
  2503. drawElement(10, 4, 1, 1, green, nil, "O")
  2504. drawElement(10, 6, 1, 1, orange, nil, "0")
  2505. drawElement(12, 6, 1, 1, white, nil, "Hardware Discovery")
  2506. for _, side in pairs(rs.getSides()) do
  2507.   if peripheral.isPresent(side) and peripheral.getType(side) == "modem" and peripheral.call(side, "isWireless") then
  2508.     rednet.open(side)
  2509.     modemSide = side
  2510.     break
  2511.   end
  2512. end
  2513. if modemSide == "none" then return startupError("modem") end
  2514. drawElement(10, 6, 1, 1, green, nil, "O")
  2515. drawElement(10, 8, 1, 1, orange, nil, "0")
  2516. drawElement(12, 8, 1, 1, white, nil, "Ingest gate data")
  2517. if not fs.exists("/data") then fs.makeDir("/data") end
  2518. if fs.exists("/data/DHDgates") then
  2519.   addressBook[1] = nil
  2520.   local gateData = fs.open("/data/DHDgates", "r")
  2521.   addressBook = textutils.unserialize(gateData.readAll())
  2522.   gateData.close()
  2523.   abCount = #addressBook
  2524.   if addressBook[1].callDrop == nil then
  2525.     for i = 1, abCount do
  2526.       addressBook[i].callDrop = false
  2527.     end
  2528.     saveData()
  2529.   end
  2530. end
  2531. gatePages = math.max(1, math.ceil(abCount / 12))
  2532. drawElement(10, 8, 1, 1, green, nil, "O")
  2533. drawElement(10, 10, 1, 1, orange, nil, "0")
  2534. drawElement(12, 10, 1, 1, white, nil, "Locate Sync host")
  2535. findHosts()
  2536. runState = "Dial"
  2537. clearScreen()
  2538. drawCLI()
  2539. parallel.waitForAny(netReceive, userInput, dataPoller)
Add Comment
Please, Sign In to add comment