HydrantHunter

ccDHD 2.0

Jul 13th, 2015
3,245
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 199.44 KB | None | 0 0
  1. --[[  LanteaCraft      ]]--
  2. --[[      and SGCraft  ]]--
  3. --[[     c c D H D     ]]--
  4. --[[      by Dog       ]]--
  5. --[[ aka HydrantHunter ]]--
  6. --[[  with help from   ]]--
  7. --[[    Bomb Bloke     ]]--
  8. --[[ pastebin 39UhE8Nz ]]--
  9. local ccDHDVer = "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. local tArgs = { ... }
  25. --# Default Settings
  26. local settingsData = "/data/DHDconfig"
  27. local gateData = "/data/DHDgates"
  28. local gateHistory = "/data/DHDhistory"
  29. local lastGate = "/data/DHDlastCall"
  30. local validStates = { }
  31. local dhdSettings = {
  32.   gate = 99999999;       --# gateLiaison
  33.   detailedMarquee = true; --# show gate details on the marquee monitor
  34.   password = "password"; --# lockdown password
  35.   irisPassword = "password"; --# remote iris control password
  36.   hashedPWs = false;     --# Whether passwords have been hashed or not
  37.   newHash = false;       --# Whether passwords are hashed with the old or new hash
  38.   bio = { lock = false, func = "none", auth = 4, fAuth = 2 }; --# BioLock (Startup locked, Standard function, Startup unlock auth level, Standard function activation auth level)
  39.   sync = false;          --# Sync (true, false, "Dial ONLY")
  40.   ecIris = "none";       --# 'endCall Iris' - open/close iris upon disconnect (or do nothing)
  41.   incomingIris = false;  --# incoming call auto-iris-close
  42.   logs = true;           --# keep a record of calls
  43. }
  44. local updateStatus, netSend, sendToAllSyncClients, drawElement, drawCLI, drawLogScreen, drawSettingsScreen, drawRemoteIrisStatus, displayMarquee, displayStatus, displayStatusDetail, displayConnectionTime, displayAddressBook, displayGateMonAddrBook, displayNotes, displayGate, displayChevrons, repositionCursor, saveData
  45. local stdPullEvent = os.pullEvent
  46. --# Peripherals
  47. local termX, termY = term.getSize()    --# standard 51x19 / tab 51x18
  48. local hardware = { wifiSide = "none", modemSide = "none", bio = 0, listMon = 0, marquee = 0, gateMon = 0, timerMon = 0 } --# Modem side(s), Biolock count, and Monitor count
  49. local listMon, listMonSides = { }, { }   --# List monitor(s)     (Address Book)   1x1 array
  50. local marquee, marqueeSides = { }, { }   --# Marquee monitor(s)  (Marquee / Info) 3x1 array
  51. local gateMon, gateMonSides = { }, { }   --# Gate monitor(s)     (Gate + Chevrons / Address Book) 3x3 array
  52. local timerMon, timerMonSides = { }, { } --# Connection Timer monitors(s) 2x1 array
  53. local thisCC, thisGate, ccLabel, gateLiaison = tostring(os.getComputerID())
  54. --# Status Info
  55. local gateStatus, irisStatus, secureStatus, displayState, runState, currentState, tempState, callDirection, dialAddress, remoteIrisStatus, rsSide = "QRY", "QRY", "QRY", "list", "init", "init", "none", "none", "none", "unk", "none"
  56. local menuState, kernelState, irisState, lcGate, gateMonList, dialFromDHD, outgoingAlarm = false, false, false, false, false, false, false
  57. local configChange, gateChange, waitingForIris, gettingInput, fullRedraw, initMarquee = false, false, false, false, false, false
  58. local currentEdit, selectedGate, incomingAddress, sgRemoteAddrLen, connectionTimer, pingTimer, fuelPercent, curX, curY, txtCol, bgCol, word, passChange
  59. local chevronNumber, irisPercent, clientCount, connectionTime, connectionClock = 0, 0, 0, 0, "00:00:0"
  60. local clients = { } --# Sync clients
  61. local dialStates = {
  62.   Dial = colors.blue;
  63.   remotePass = colors.blue;
  64.   goPage = colors.blue;
  65.   importExport = colors.blue;
  66.   exodus = colors.blue;
  67. }
  68. --# Address Book
  69. local addressBook = { { name = "NEW GATE", addr = "ADDRESS", rating = "U", iris = "none", callDrop = false, note = "short note", loc = { x = 0, y = 0, z = 0, dim = "Unspecified" } } }
  70. local gatePage, gatePages, listPage, listPages, gateMonPage, gateMonPages, abCount = 1, 1, 1, 1, 1, 1, 1
  71. local pNum = tostring(gatePage) .. " of " .. tostring(gatePages)
  72. local classifications = {
  73.   B = { order = 1, color = colors.blue, label = "Base/Outpost/Hub" };
  74.   H = { order = 2, color = colors.lightBlue, label = "Home/Camp" };
  75.   V = { order = 3, color = colors.brown, label = "Village" };
  76.   M = { order = 4, color = colors.purple, label = "Misc/Special" };
  77.   S = { order = 5, color = colors.green, label = "Safe/Secured" };
  78.   C = { order = 6, color = colors.orange, label = "Caution" };
  79.   D = { order = 7, color = colors.red, label = "Danger" };
  80.   U = { order = 8, color = colors.lightGray, label = "Unk/Unclassified", long = "Unknown/Unclassified" };
  81. }
  82. --# Call History
  83. local logPage, logPages = 1, 1
  84. local callHistory = { }
  85. local lastCall
  86. --# Color Definitions
  87. local white = colors.white
  88. local silver = colors.lightGray
  89. local gray = colors.gray
  90. local black = colors.black
  91. local brown = colors.brown
  92. local yellow = colors.yellow
  93. local orange = colors.orange
  94. local red = colors.red
  95. --local magenta = colors.magenta
  96. --local purple = colors.purple
  97. local blue = colors.blue
  98. local sky = colors.lightBlue
  99. local cyan = colors.cyan
  100. local lime = colors.lime
  101. local green = colors.green
  102.  
  103. -- Lua 5.1+ base64 v3.0 (c) 2009 by Alex Kloss <alexthkloss@web.de>
  104. -- licensed under the terms of the LGPL2
  105. -- http://lua-users.org/wiki/BaseSixtyFour
  106. -- character table string
  107. local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
  108. -- encoding
  109. local function encode(data)
  110.   return ((data:gsub('.', function(x)
  111.     local r,b='',x:byte()
  112.     for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end
  113.     return r;
  114.   end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
  115.     if (#x < 6) then return '' end
  116.     local c=0
  117.     for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end
  118.     return b:sub(c+1,c+1)
  119.   end)..({ '', '==', '=' })[#data%3+1])
  120. end
  121. -- decoding
  122. local function decode(data)
  123.   data = string.gsub(data, '[^'..b..'=]', '')
  124.   return (data:gsub('.', function(x)
  125.     if (x == '=') then return '' end
  126.     local r,f='',(b:find(x)-1)
  127.     for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end
  128.     return r;
  129.   end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
  130.     if (#x ~= 8) then return '' end
  131.     local c=0
  132.     for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end
  133.     return string.char(c)
  134.   end))
  135. end
  136.  
  137. -- AES Lua implementation by SquidDev
  138. -- https://gist.github.com/SquidDev/86925e07cbabd70773e53d781bd8b2fe
  139. local encrypt, decrypt
  140. do
  141.   local function _W(f) local e=setmetatable({}, {__index = _ENV or getfenv()}) if setfenv then setfenv(f, e) end return f(e) or e end
  142.   local bit=_W(function(_ENV, ...)
  143.   --[[
  144.     This bit API is designed to cope with unsigned integers instead of normal integers
  145.     To do this we add checks for overflows: (x > 2^31 ? x - 2 ^ 32 : x)
  146.     These are written in long form because no constant folding.
  147.   ]]
  148.   local floor = math.floor
  149.   local lshift, rshift
  150.  
  151.   rshift = function(a,disp)
  152.     return floor(a % 4294967296 / 2^disp)
  153.   end
  154.  
  155.   lshift = function(a,disp)
  156.     return (a * 2^disp) % 4294967296
  157.   end
  158.  
  159.   return {
  160.     -- bit operations
  161.     bnot = bit32 and bit32.bnot or bit.bnot,
  162.     band = bit32 and bit32.band or bit.band,
  163.     bor  = bit32 and bit32.bor or bit.bor,
  164.     bxor = bit32 and bit32.bxor or bit.bxor,
  165.     rshift = rshift,
  166.     lshift = lshift,
  167.   }
  168.   end)
  169.  
  170.   local gf=_W(function(_ENV, ...)
  171.   -- finite field with base 2 and modulo irreducible polynom x^8+x^4+x^3+x+1 = 0x11d
  172.   local bxor = bit32 and bit32.bxor or bit.bxor
  173.   local lshift = bit.lshift
  174.   -- private data of gf
  175.   local n = 0x100
  176.   local ord = 0xff
  177.   local irrPolynom = 0x11b
  178.   local exp, log = {}, {}
  179.   --
  180.   -- add two polynoms (its simply xor)
  181.   --
  182.   local function add(operand1, operand2)
  183.     return bxor(operand1,operand2)
  184.   end
  185.   --
  186.   -- subtract two polynoms (same as addition)
  187.   --
  188.   local function sub(operand1, operand2)
  189.     return bxor(operand1,operand2)
  190.   end
  191.   --
  192.   -- inverts element
  193.   -- a^(-1) = g^(order - log(a))
  194.   --
  195.   local function invert(operand)
  196.     -- special case for 1 or normal invert
  197.     return operand == 1 and 1 or exp[ord - log[operand]]
  198.   end
  199.   --
  200.   -- multiply two elements using a logarithm table
  201.   -- a*b = g^(log(a)+log(b))
  202.   --
  203.   local function mul(operand1, operand2)
  204.     if (operand1 == 0 or operand2 == 0) then
  205.       return 0
  206.     end
  207.     local exponent = log[operand1] + log[operand2]
  208.     if (exponent >= ord) then
  209.       exponent = exponent - ord
  210.     end
  211.     return exp[exponent]
  212.   end
  213.   --
  214.   -- divide two elements
  215.   -- a/b = g^(log(a)-log(b))
  216.   --
  217.   local function div(operand1, operand2)
  218.     if (operand1 == 0)  then
  219.       return 0
  220.     end
  221.     -- TODO: exception if operand2 == 0
  222.     local exponent = log[operand1] - log[operand2]
  223.     if (exponent < 0) then
  224.       exponent = exponent + ord
  225.     end
  226.     return exp[exponent]
  227.   end
  228.   --
  229.   -- print logarithmic table
  230.   --
  231.   local function printLog()
  232.     for i = 1, n do
  233.       print("log(", i-1, ")=", log[i-1])
  234.     end
  235.   end
  236.   --
  237.   -- print exponentiation table
  238.   --
  239.   local function printExp()
  240.     for i = 1, n do
  241.       print("exp(", i-1, ")=", exp[i-1])
  242.     end
  243.   end
  244.   --
  245.   -- calculate logarithmic and exponentiation table
  246.   --
  247.   local function initMulTable()
  248.     local a = 1
  249.     for i = 0,ord-1 do
  250.       exp[i] = a
  251.       log[a] = i
  252.       -- multiply with generator x+1 -> left shift + 1
  253.       a = bxor(lshift(a, 1), a)
  254.       -- if a gets larger than order, reduce modulo irreducible polynom
  255.       if a > ord then
  256.         a = sub(a, irrPolynom)
  257.       end
  258.     end
  259.   end
  260.  
  261.   initMulTable()
  262.  
  263.   return {
  264.     add = add,
  265.     sub = sub,
  266.     invert = invert,
  267.     mul = mul,
  268.     div = div,
  269.     printLog = printLog,
  270.     printExp = printExp,
  271.   }
  272.   end)
  273.  
  274.   util=_W(function(_ENV, ...)
  275.   -- Cache some bit operators
  276.   local bxor = bit.bxor
  277.   local rshift = bit.rshift
  278.   local band = bit.band
  279.   local lshift = bit.lshift
  280.   local sleepCheckIn
  281.   --
  282.   -- calculate the parity of one byte
  283.   --
  284.   local function byteParity(byte)
  285.     byte = bxor(byte, rshift(byte, 4))
  286.     byte = bxor(byte, rshift(byte, 2))
  287.     byte = bxor(byte, rshift(byte, 1))
  288.     return band(byte, 1)
  289.   end
  290.   --
  291.   -- get byte at position index
  292.   --
  293.   local function getByte(number, index)
  294.     return index == 0 and band(number,0xff) or band(rshift(number, index*8),0xff)
  295.   end
  296.   --
  297.   -- put number into int at position index
  298.   --
  299.   local function putByte(number, index)
  300.     return index == 0 and band(number,0xff) or lshift(band(number,0xff),index*8)
  301.   end
  302.   --
  303.   -- convert byte array to int array
  304.   --
  305.   local function bytesToInts(bytes, start, n)
  306.     local ints = {}
  307.     for i = 0, n - 1 do
  308.       ints[i + 1] =
  309.           putByte(bytes[start + (i*4)], 3) +
  310.           putByte(bytes[start + (i*4) + 1], 2) +
  311.           putByte(bytes[start + (i*4) + 2], 1) +
  312.           putByte(bytes[start + (i*4) + 3], 0)
  313.       if n % 10000 == 0 then sleepCheckIn() end
  314.     end
  315.     return ints
  316.   end
  317.   --
  318.   -- convert int array to byte array
  319.   --
  320.   local function intsToBytes(ints, output, outputOffset, n)
  321.     n = n or #ints
  322.     for i = 0, n - 1 do
  323.       for j = 0,3 do
  324.         output[outputOffset + i*4 + (3 - j)] = getByte(ints[i + 1], j)
  325.       end
  326.       if n % 10000 == 0 then sleepCheckIn() end
  327.     end
  328.     return output
  329.   end
  330.   --
  331.   -- convert bytes to hexString
  332.   --
  333.   local function bytesToHex(bytes)
  334.     local hexBytes = ""
  335.     for i,byte in ipairs(bytes) do
  336.       hexBytes = hexBytes .. string.format("%02x ", byte)
  337.     end
  338.     return hexBytes
  339.   end
  340.  
  341.   local function hexToBytes(bytes)
  342.     local out = {}
  343.     for i = 1, #bytes, 2 do
  344.       out[#out + 1] = tonumber(bytes:sub(i, i + 1), 16)
  345.     end
  346.     return out
  347.   end
  348.   --
  349.   -- convert data to hex string
  350.   --
  351.   local function toHexString(data)
  352.     local type = type(data)
  353.     if (type == "number") then
  354.       return string.format("%08x",data)
  355.     elseif (type == "table") then
  356.       return bytesToHex(data)
  357.     elseif (type == "string") then
  358.       local bytes = {string.byte(data, 1, #data)}
  359.       return bytesToHex(bytes)
  360.     else
  361.       return data
  362.     end
  363.   end
  364.  
  365.   local function padByteString(data)
  366.     local dataLength = #data
  367.     local random1 = math.random(0,255)
  368.     local random2 = math.random(0,255)
  369.     local prefix = string.char(random1,
  370.       random2,
  371.       random1,
  372.       random2,
  373.       getByte(dataLength, 3),
  374.       getByte(dataLength, 2),
  375.       getByte(dataLength, 1),
  376.       getByte(dataLength, 0)
  377.     )
  378.     data = prefix .. data
  379.     local padding, paddingLength = "", math.ceil(#data/16)*16 - #data
  380.     for i=1,paddingLength do
  381.       padding = padding .. string.char(math.random(0,255))
  382.     end
  383.     return data .. padding
  384.   end
  385.  
  386.   local function properlyDecrypted(data)
  387.     local random = {string.byte(data,1,4)}
  388.     if (random[1] == random[3] and random[2] == random[4]) then
  389.       return true
  390.     end
  391.     return false
  392.   end
  393.  
  394.   local function unpadByteString(data)
  395.     if (not properlyDecrypted(data)) then
  396.       return nil
  397.     end
  398.     local dataLength = putByte(string.byte(data,5), 3)
  399.              + putByte(string.byte(data,6), 2)
  400.              + putByte(string.byte(data,7), 1)
  401.              + putByte(string.byte(data,8), 0)
  402.     return string.sub(data,9,8+dataLength)
  403.   end
  404.  
  405.   local function xorIV(data, iv)
  406.     for i = 1,16 do
  407.       data[i] = bxor(data[i], iv[i])
  408.     end
  409.   end
  410.  
  411.   local function increment(data)
  412.     local i = 16
  413.     while true do
  414.       local value = data[i] + 1
  415.       if value >= 256 then
  416.         data[i] = value - 256
  417.         i = (i - 2) % 16 + 1
  418.       else
  419.         data[i] = value
  420.         break
  421.       end
  422.     end
  423.   end
  424.  
  425.   -- Called every encryption cycle
  426.   local push, pull, time = os.queueEvent, coroutine.yield, os.time
  427.   local oldTime = time()
  428.   local function sleepCheckIn()
  429.     local newTime = time()
  430.     if newTime - oldTime >= 0.03 then -- (0.020 * 1.5)
  431.       oldTime = newTime
  432.       push("sleep")
  433.       pull("sleep")
  434.     end
  435.   end
  436.  
  437.   local function getRandomData(bytes)
  438.     local char, random, sleep, insert = string.char, math.random, sleepCheckIn, table.insert
  439.     local result = {}
  440.     for i=1,bytes do
  441.       insert(result, random(0,255))
  442.       if i % 10240 == 0 then sleep() end
  443.     end
  444.     return result
  445.   end
  446.  
  447.   local function getRandomString(bytes)
  448.     local char, random, sleep, insert = string.char, math.random, sleepCheckIn, table.insert
  449.     local result = {}
  450.     for i=1,bytes do
  451.       insert(result, char(random(0,255)))
  452.       if i % 10240 == 0 then sleep() end
  453.     end
  454.     return table.concat(result)
  455.   end
  456.  
  457.   return {
  458.     byteParity = byteParity,
  459.     getByte = getByte,
  460.     putByte = putByte,
  461.     bytesToInts = bytesToInts,
  462.     intsToBytes = intsToBytes,
  463.     bytesToHex = bytesToHex,
  464.     hexToBytes = hexToBytes,
  465.     toHexString = toHexString,
  466.     padByteString = padByteString,
  467.     properlyDecrypted = properlyDecrypted,
  468.     unpadByteString = unpadByteString,
  469.     xorIV = xorIV,
  470.     increment = increment,
  471.     sleepCheckIn = sleepCheckIn,
  472.     getRandomData = getRandomData,
  473.     getRandomString = getRandomString,
  474.   }
  475.   end)
  476.  
  477.   aes=_W(function(_ENV, ...)
  478.   -- Implementation of AES with nearly pure lua
  479.   -- AES with lua is slow, really slow :-)
  480.   local putByte = util.putByte
  481.   local getByte = util.getByte
  482.   -- some constants
  483.   local ROUNDS = 'rounds'
  484.   local KEY_TYPE = "type"
  485.   local ENCRYPTION_KEY=1
  486.   local DECRYPTION_KEY=2
  487.   -- aes SBOX
  488.   local SBox = {}
  489.   local iSBox = {}
  490.   -- aes tables
  491.   local table0 = {}
  492.   local table1 = {}
  493.   local table2 = {}
  494.   local table3 = {}
  495.   local tableInv0 = {}
  496.   local tableInv1 = {}
  497.   local tableInv2 = {}
  498.   local tableInv3 = {}
  499.   -- round constants
  500.   local rCon = {
  501.     0x01000000,
  502.     0x02000000,
  503.     0x04000000,
  504.     0x08000000,
  505.     0x10000000,
  506.     0x20000000,
  507.     0x40000000,
  508.     0x80000000,
  509.     0x1b000000,
  510.     0x36000000,
  511.     0x6c000000,
  512.     0xd8000000,
  513.     0xab000000,
  514.     0x4d000000,
  515.     0x9a000000,
  516.     0x2f000000,
  517.   }
  518.   --
  519.   -- affine transformation for calculating the S-Box of AES
  520.   --
  521.   local function affinMap(byte)
  522.     mask = 0xf8
  523.     result = 0
  524.     for i = 1,8 do
  525.       result = bit.lshift(result,1)
  526.       parity = util.byteParity(bit.band(byte,mask))
  527.       result = result + parity
  528.       -- simulate roll
  529.       lastbit = bit.band(mask, 1)
  530.       mask = bit.band(bit.rshift(mask, 1),0xff)
  531.       mask = lastbit ~= 0 and bit.bor(mask, 0x80) or bit.band(mask, 0x7f)
  532.     end
  533.     return bit.bxor(result, 0x63)
  534.   end
  535.   --
  536.   -- calculate S-Box and inverse S-Box of AES
  537.   -- apply affine transformation to inverse in finite field 2^8
  538.   --
  539.   local function calcSBox()
  540.     for i = 0, 255 do
  541.       inverse = i ~= 0 and gf.invert(i) or 0
  542.       mapped = affinMap(inverse)
  543.       SBox[i] = mapped
  544.       iSBox[mapped] = i
  545.     end
  546.   end
  547.   --
  548.   -- Calculate round tables
  549.   -- round tables are used to calculate shiftRow, MixColumn and SubBytes
  550.   -- with 4 table lookups and 4 xor operations.
  551.   --
  552.   local function calcRoundTables()
  553.     for x = 0,255 do
  554.       byte = SBox[x]
  555.       table0[x] = putByte(gf.mul(0x03, byte), 0)
  556.                 + putByte(             byte , 1)
  557.                 + putByte(             byte , 2)
  558.                 + putByte(gf.mul(0x02, byte), 3)
  559.       table1[x] = putByte(             byte , 0)
  560.                 + putByte(             byte , 1)
  561.                 + putByte(gf.mul(0x02, byte), 2)
  562.                 + putByte(gf.mul(0x03, byte), 3)
  563.       table2[x] = putByte(             byte , 0)
  564.                 + putByte(gf.mul(0x02, byte), 1)
  565.                 + putByte(gf.mul(0x03, byte), 2)
  566.                 + putByte(             byte , 3)
  567.       table3[x] = putByte(gf.mul(0x02, byte), 0)
  568.                 + putByte(gf.mul(0x03, byte), 1)
  569.                 + putByte(             byte , 2)
  570.                 + putByte(             byte , 3)
  571.     end
  572.   end
  573.   --
  574.   -- Calculate inverse round tables
  575.   -- does the inverse of the normal roundtables for the equivalent
  576.   -- decryption algorithm.
  577.   --
  578.   local function calcInvRoundTables()
  579.     for x = 0,255 do
  580.       byte = iSBox[x]
  581.       tableInv0[x] = putByte(gf.mul(0x0b, byte), 0)
  582.                  + putByte(gf.mul(0x0d, byte), 1)
  583.                  + putByte(gf.mul(0x09, byte), 2)
  584.                  + putByte(gf.mul(0x0e, byte), 3)
  585.       tableInv1[x] = putByte(gf.mul(0x0d, byte), 0)
  586.                  + putByte(gf.mul(0x09, byte), 1)
  587.                  + putByte(gf.mul(0x0e, byte), 2)
  588.                  + putByte(gf.mul(0x0b, byte), 3)
  589.       tableInv2[x] = putByte(gf.mul(0x09, byte), 0)
  590.                  + putByte(gf.mul(0x0e, byte), 1)
  591.                  + putByte(gf.mul(0x0b, byte), 2)
  592.                  + putByte(gf.mul(0x0d, byte), 3)
  593.       tableInv3[x] = putByte(gf.mul(0x0e, byte), 0)
  594.                  + putByte(gf.mul(0x0b, byte), 1)
  595.                  + putByte(gf.mul(0x0d, byte), 2)
  596.                  + putByte(gf.mul(0x09, byte), 3)
  597.     end
  598.   end
  599.   --
  600.   -- rotate word: 0xaabbccdd gets 0xbbccddaa
  601.   -- used for key schedule
  602.   --
  603.   local function rotWord(word)
  604.     local tmp = bit.band(word,0xff000000)
  605.     return (bit.lshift(word,8) + bit.rshift(tmp,24))
  606.   end
  607.   --
  608.   -- replace all bytes in a word with the SBox.
  609.   -- used for key schedule
  610.   --
  611.   local function subWord(word)
  612.     return putByte(SBox[getByte(word,0)],0)
  613.       + putByte(SBox[getByte(word,1)],1)
  614.       + putByte(SBox[getByte(word,2)],2)
  615.       + putByte(SBox[getByte(word,3)],3)
  616.   end
  617.   --
  618.   -- generate key schedule for aes encryption
  619.   --
  620.   -- returns table with all round keys and
  621.   -- the necessary number of rounds saved in [ROUNDS]
  622.   --
  623.   local function expandEncryptionKey(key)
  624.     local keySchedule = {}
  625.     local keyWords = math.floor(#key / 4)
  626.     if ((keyWords ~= 4 and keyWords ~= 6 and keyWords ~= 8) or (keyWords * 4 ~= #key)) then
  627.       error("Invalid key size: " .. tostring(keyWords))
  628.       return nil
  629.     end
  630.     keySchedule[ROUNDS] = keyWords + 6
  631.     keySchedule[KEY_TYPE] = ENCRYPTION_KEY
  632.     for i = 0,keyWords - 1 do
  633.       keySchedule[i] = putByte(key[i*4+1], 3)
  634.                + putByte(key[i*4+2], 2)
  635.                + putByte(key[i*4+3], 1)
  636.                + putByte(key[i*4+4], 0)
  637.     end
  638.     for i = keyWords, (keySchedule[ROUNDS] + 1)*4 - 1 do
  639.       local tmp = keySchedule[i-1]
  640.       if ( i % keyWords == 0) then
  641.         tmp = rotWord(tmp)
  642.         tmp = subWord(tmp)
  643.         local index = math.floor(i/keyWords)
  644.         tmp = bit.bxor(tmp,rCon[index])
  645.       elseif (keyWords > 6 and i % keyWords == 4) then
  646.         tmp = subWord(tmp)
  647.       end
  648.       keySchedule[i] = bit.bxor(keySchedule[(i-keyWords)],tmp)
  649.     end
  650.     return keySchedule
  651.   end
  652.   --
  653.   -- Inverse mix column
  654.   -- used for key schedule of decryption key
  655.   --
  656.   local function invMixColumnOld(word)
  657.     local b0 = getByte(word,3)
  658.     local b1 = getByte(word,2)
  659.     local b2 = getByte(word,1)
  660.     local b3 = getByte(word,0)
  661.     return putByte(gf.add(gf.add(gf.add(gf.mul(0x0b, b1),
  662.                          gf.mul(0x0d, b2)),
  663.                          gf.mul(0x09, b3)),
  664.                          gf.mul(0x0e, b0)),3)
  665.        + putByte(gf.add(gf.add(gf.add(gf.mul(0x0b, b2),
  666.                          gf.mul(0x0d, b3)),
  667.                          gf.mul(0x09, b0)),
  668.                          gf.mul(0x0e, b1)),2)
  669.        + putByte(gf.add(gf.add(gf.add(gf.mul(0x0b, b3),
  670.                          gf.mul(0x0d, b0)),
  671.                          gf.mul(0x09, b1)),
  672.                          gf.mul(0x0e, b2)),1)
  673.        + putByte(gf.add(gf.add(gf.add(gf.mul(0x0b, b0),
  674.                          gf.mul(0x0d, b1)),
  675.                          gf.mul(0x09, b2)),
  676.                          gf.mul(0x0e, b3)),0)
  677.   end
  678.   --
  679.   -- Optimized inverse mix column
  680.   -- look at http://fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.311.pdf
  681.   -- TODO: make it work
  682.   --
  683.   local function invMixColumn(word)
  684.     local b0 = getByte(word,3)
  685.     local b1 = getByte(word,2)
  686.     local b2 = getByte(word,1)
  687.     local b3 = getByte(word,0)
  688.     local t = bit.bxor(b3,b2)
  689.     local u = bit.bxor(b1,b0)
  690.     local v = bit.bxor(t,u)
  691.     v = bit.bxor(v,gf.mul(0x08,v))
  692.     w = bit.bxor(v,gf.mul(0x04, bit.bxor(b2,b0)))
  693.     v = bit.bxor(v,gf.mul(0x04, bit.bxor(b3,b1)))
  694.     return putByte( bit.bxor(bit.bxor(b3,v), gf.mul(0x02, bit.bxor(b0,b3))), 0)
  695.        + putByte( bit.bxor(bit.bxor(b2,w), gf.mul(0x02, t              )), 1)
  696.        + putByte( bit.bxor(bit.bxor(b1,v), gf.mul(0x02, bit.bxor(b0,b3))), 2)
  697.        + putByte( bit.bxor(bit.bxor(b0,w), gf.mul(0x02, u              )), 3)
  698.   end
  699.   --
  700.   -- generate key schedule for aes decryption
  701.   --
  702.   -- uses key schedule for aes encryption and transforms each
  703.   -- key by inverse mix column.
  704.   --
  705.   local function expandDecryptionKey(key)
  706.     local keySchedule = expandEncryptionKey(key)
  707.     if (keySchedule == nil) then
  708.       return nil
  709.     end
  710.     keySchedule[KEY_TYPE] = DECRYPTION_KEY
  711.     for i = 4, (keySchedule[ROUNDS] + 1)*4 - 5 do
  712.       keySchedule[i] = invMixColumnOld(keySchedule[i])
  713.     end
  714.     return keySchedule
  715.   end
  716.   --
  717.   -- xor round key to state
  718.   --
  719.   local function addRoundKey(state, key, round)
  720.     for i = 0, 3 do
  721.       state[i + 1] = bit.bxor(state[i + 1], key[round*4+i])
  722.     end
  723.   end
  724.   --
  725.   -- do encryption round (ShiftRow, SubBytes, MixColumn together)
  726.   --
  727.   local function doRound(origState, dstState)
  728.     dstState[1] =  bit.bxor(bit.bxor(bit.bxor(
  729.           table0[getByte(origState[1],3)],
  730.           table1[getByte(origState[2],2)]),
  731.           table2[getByte(origState[3],1)]),
  732.           table3[getByte(origState[4],0)])
  733.     dstState[2] =  bit.bxor(bit.bxor(bit.bxor(
  734.           table0[getByte(origState[2],3)],
  735.           table1[getByte(origState[3],2)]),
  736.           table2[getByte(origState[4],1)]),
  737.           table3[getByte(origState[1],0)])
  738.     dstState[3] =  bit.bxor(bit.bxor(bit.bxor(
  739.           table0[getByte(origState[3],3)],
  740.           table1[getByte(origState[4],2)]),
  741.           table2[getByte(origState[1],1)]),
  742.           table3[getByte(origState[2],0)])
  743.     dstState[4] =  bit.bxor(bit.bxor(bit.bxor(
  744.           table0[getByte(origState[4],3)],
  745.           table1[getByte(origState[1],2)]),
  746.           table2[getByte(origState[2],1)]),
  747.           table3[getByte(origState[3],0)])
  748.   end
  749.   --
  750.   -- do last encryption round (ShiftRow and SubBytes)
  751.   --
  752.   local function doLastRound(origState, dstState)
  753.     dstState[1] = putByte(SBox[getByte(origState[1],3)], 3)
  754.           + putByte(SBox[getByte(origState[2],2)], 2)
  755.           + putByte(SBox[getByte(origState[3],1)], 1)
  756.           + putByte(SBox[getByte(origState[4],0)], 0)
  757.     dstState[2] = putByte(SBox[getByte(origState[2],3)], 3)
  758.           + putByte(SBox[getByte(origState[3],2)], 2)
  759.           + putByte(SBox[getByte(origState[4],1)], 1)
  760.           + putByte(SBox[getByte(origState[1],0)], 0)
  761.     dstState[3] = putByte(SBox[getByte(origState[3],3)], 3)
  762.           + putByte(SBox[getByte(origState[4],2)], 2)
  763.           + putByte(SBox[getByte(origState[1],1)], 1)
  764.           + putByte(SBox[getByte(origState[2],0)], 0)
  765.     dstState[4] = putByte(SBox[getByte(origState[4],3)], 3)
  766.           + putByte(SBox[getByte(origState[1],2)], 2)
  767.           + putByte(SBox[getByte(origState[2],1)], 1)
  768.           + putByte(SBox[getByte(origState[3],0)], 0)
  769.   end
  770.   --
  771.   -- do decryption round
  772.   --
  773.   local function doInvRound(origState, dstState)
  774.     dstState[1] =  bit.bxor(bit.bxor(bit.bxor(
  775.           tableInv0[getByte(origState[1],3)],
  776.           tableInv1[getByte(origState[4],2)]),
  777.           tableInv2[getByte(origState[3],1)]),
  778.           tableInv3[getByte(origState[2],0)])
  779.     dstState[2] =  bit.bxor(bit.bxor(bit.bxor(
  780.           tableInv0[getByte(origState[2],3)],
  781.           tableInv1[getByte(origState[1],2)]),
  782.           tableInv2[getByte(origState[4],1)]),
  783.           tableInv3[getByte(origState[3],0)])
  784.     dstState[3] =  bit.bxor(bit.bxor(bit.bxor(
  785.           tableInv0[getByte(origState[3],3)],
  786.           tableInv1[getByte(origState[2],2)]),
  787.           tableInv2[getByte(origState[1],1)]),
  788.           tableInv3[getByte(origState[4],0)])
  789.     dstState[4] =  bit.bxor(bit.bxor(bit.bxor(
  790.           tableInv0[getByte(origState[4],3)],
  791.           tableInv1[getByte(origState[3],2)]),
  792.           tableInv2[getByte(origState[2],1)]),
  793.           tableInv3[getByte(origState[1],0)])
  794.   end
  795.   --
  796.   -- do last decryption round
  797.   --
  798.   local function doInvLastRound(origState, dstState)
  799.     dstState[1] = putByte(iSBox[getByte(origState[1],3)], 3)
  800.           + putByte(iSBox[getByte(origState[4],2)], 2)
  801.           + putByte(iSBox[getByte(origState[3],1)], 1)
  802.           + putByte(iSBox[getByte(origState[2],0)], 0)
  803.     dstState[2] = putByte(iSBox[getByte(origState[2],3)], 3)
  804.           + putByte(iSBox[getByte(origState[1],2)], 2)
  805.           + putByte(iSBox[getByte(origState[4],1)], 1)
  806.           + putByte(iSBox[getByte(origState[3],0)], 0)
  807.     dstState[3] = putByte(iSBox[getByte(origState[3],3)], 3)
  808.           + putByte(iSBox[getByte(origState[2],2)], 2)
  809.           + putByte(iSBox[getByte(origState[1],1)], 1)
  810.           + putByte(iSBox[getByte(origState[4],0)], 0)
  811.     dstState[4] = putByte(iSBox[getByte(origState[4],3)], 3)
  812.           + putByte(iSBox[getByte(origState[3],2)], 2)
  813.           + putByte(iSBox[getByte(origState[2],1)], 1)
  814.           + putByte(iSBox[getByte(origState[1],0)], 0)
  815.   end
  816.   --
  817.   -- encrypts 16 Bytes
  818.   -- key           encryption key schedule
  819.   -- input         array with input data
  820.   -- inputOffset   start index for input
  821.   -- output        array for encrypted data
  822.   -- outputOffset  start index for output
  823.   --
  824.   local function encrypt(key, input, inputOffset, output, outputOffset)
  825.     --default parameters
  826.     inputOffset = inputOffset or 1
  827.     output = output or {}
  828.     outputOffset = outputOffset or 1
  829.     local state, tmpState = {}, {}
  830.     if (key[KEY_TYPE] ~= ENCRYPTION_KEY) then
  831.       error("No encryption key: " .. tostring(key[KEY_TYPE]) .. ", expected " .. ENCRYPTION_KEY)
  832.       return
  833.     end
  834.     state = util.bytesToInts(input, inputOffset, 4)
  835.     addRoundKey(state, key, 0)
  836.     local round = 1
  837.     while (round < key[ROUNDS] - 1) do
  838.       -- do a double round to save temporary assignments
  839.       doRound(state, tmpState)
  840.       addRoundKey(tmpState, key, round)
  841.       round = round + 1
  842.       doRound(tmpState, state)
  843.       addRoundKey(state, key, round)
  844.       round = round + 1
  845.     end
  846.     doRound(state, tmpState)
  847.     addRoundKey(tmpState, key, round)
  848.     round = round +1
  849.     doLastRound(tmpState, state)
  850.     addRoundKey(state, key, round)
  851.     util.sleepCheckIn()
  852.     return util.intsToBytes(state, output, outputOffset)
  853.   end
  854.   --
  855.   -- decrypt 16 bytes
  856.   -- key           decryption key schedule
  857.   -- input         array with input data
  858.   -- inputOffset   start index for input
  859.   -- output        array for decrypted data
  860.   -- outputOffset  start index for output
  861.   ---
  862.   local function decrypt(key, input, inputOffset, output, outputOffset)
  863.     -- default arguments
  864.     inputOffset = inputOffset or 1
  865.     output = output or {}
  866.     outputOffset = outputOffset or 1
  867.     local state, tmpState = {}, {}
  868.     if (key[KEY_TYPE] ~= DECRYPTION_KEY) then
  869.       error("No decryption key: " .. tostring(key[KEY_TYPE]))
  870.       return
  871.     end
  872.     state = util.bytesToInts(input, inputOffset, 4)
  873.     addRoundKey(state, key, key[ROUNDS])
  874.     local round = key[ROUNDS] - 1
  875.     while (round > 2) do
  876.       -- do a double round to save temporary assignments
  877.       doInvRound(state, tmpState)
  878.       addRoundKey(tmpState, key, round)
  879.       round = round - 1
  880.       doInvRound(tmpState, state)
  881.       addRoundKey(state, key, round)
  882.       round = round - 1
  883.     end
  884.     doInvRound(state, tmpState)
  885.     addRoundKey(tmpState, key, round)
  886.     round = round - 1
  887.     doInvLastRound(tmpState, state)
  888.     addRoundKey(state, key, round)
  889.     util.sleepCheckIn()
  890.     return util.intsToBytes(state, output, outputOffset)
  891.   end
  892.  
  893.   -- calculate all tables when loading this file
  894.   calcSBox()
  895.   calcRoundTables()
  896.   calcInvRoundTables()
  897.  
  898.   return {
  899.     ROUNDS = ROUNDS,
  900.     KEY_TYPE = KEY_TYPE,
  901.     ENCRYPTION_KEY = ENCRYPTION_KEY,
  902.     DECRYPTION_KEY = DECRYPTION_KEY,
  903.     expandEncryptionKey = expandEncryptionKey,
  904.     expandDecryptionKey = expandDecryptionKey,
  905.     encrypt = encrypt,
  906.     decrypt = decrypt,
  907.   }
  908.   end)
  909.  
  910.   local buffer=_W(function(_ENV, ...)
  911.   local function new()
  912.     return {}
  913.   end
  914.  
  915.   local function addString(stack, s)
  916.     table.insert(stack, s)
  917.   end
  918.  
  919.   local function toString(stack)
  920.     return table.concat(stack)
  921.   end
  922.  
  923.   return {
  924.     new = new,
  925.     addString = addString,
  926.     toString = toString,
  927.   }
  928.   end)
  929.  
  930.   ciphermode=_W(function(_ENV, ...)
  931.   local public = {}
  932.   --
  933.   -- Encrypt strings
  934.   -- key - byte array with key
  935.   -- string - string to encrypt
  936.   -- modefunction - function for cipher mode to use
  937.   --
  938.   local random, unpack = math.random, unpack or table.unpack
  939.   function public.encryptString(key, data, modeFunction, iv)
  940.     if iv then
  941.       local ivCopy = {}
  942.       for i = 1, 16 do ivCopy[i] = iv[i] end
  943.       iv = ivCopy
  944.     else
  945.       iv = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
  946.     end
  947.     local keySched = aes.expandEncryptionKey(key)
  948.     local encryptedData = buffer.new()
  949.     for i = 1, #data/16 do
  950.       local offset = (i-1)*16 + 1
  951.       local byteData = {string.byte(data,offset,offset +15)}
  952.       iv = modeFunction(keySched, byteData, iv)
  953.       buffer.addString(encryptedData, string.char(unpack(byteData)))
  954.     end
  955.     return buffer.toString(encryptedData)
  956.   end
  957.   --
  958.   -- the following 4 functions can be used as
  959.   -- modefunction for encryptString
  960.   --
  961.   -- Electronic code book mode encrypt function
  962.   function public.encryptECB(keySched, byteData, iv)
  963.     aes.encrypt(keySched, byteData, 1, byteData, 1)
  964.   end
  965.  
  966.   -- Cipher block chaining mode encrypt function
  967.   function public.encryptCBC(keySched, byteData, iv)
  968.     util.xorIV(byteData, iv)
  969.     aes.encrypt(keySched, byteData, 1, byteData, 1)
  970.     return byteData
  971.   end
  972.  
  973.   -- Output feedback mode encrypt function
  974.   function public.encryptOFB(keySched, byteData, iv)
  975.     aes.encrypt(keySched, iv, 1, iv, 1)
  976.     util.xorIV(byteData, iv)
  977.     return iv
  978.   end
  979.  
  980.   -- Cipher feedback mode encrypt function
  981.   function public.encryptCFB(keySched, byteData, iv)
  982.     aes.encrypt(keySched, iv, 1, iv, 1)
  983.     util.xorIV(byteData, iv)
  984.     return byteData
  985.   end
  986.  
  987.   function public.encryptCTR(keySched, byteData, iv)
  988.     local nextIV = {}
  989.     for j = 1, 16 do nextIV[j] = iv[j] end
  990.     aes.encrypt(keySched, iv, 1, iv, 1)
  991.     util.xorIV(byteData, iv)
  992.     util.increment(nextIV)
  993.     return nextIV
  994.   end
  995.   --
  996.   -- Decrypt strings
  997.   -- key - byte array with key
  998.   -- string - string to decrypt
  999.   -- modefunction - function for cipher mode to use
  1000.   --
  1001.   function public.decryptString(key, data, modeFunction, iv)
  1002.     if iv then
  1003.       local ivCopy = {}
  1004.       for i = 1, 16 do ivCopy[i] = iv[i] end
  1005.       iv = ivCopy
  1006.     else
  1007.       iv = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
  1008.     end
  1009.     local keySched
  1010.     if modeFunction == public.decryptOFB or modeFunction == public.decryptCFB or modeFunction == public.decryptCTR then
  1011.       keySched = aes.expandEncryptionKey(key)
  1012.     else
  1013.       keySched = aes.expandDecryptionKey(key)
  1014.     end
  1015.     local decryptedData = buffer.new()
  1016.     for i = 1, #data/16 do
  1017.       local offset = (i-1)*16 + 1
  1018.       local byteData = {string.byte(data,offset,offset +15)}
  1019.       iv = modeFunction(keySched, byteData, iv)
  1020.       buffer.addString(decryptedData, string.char(unpack(byteData)))
  1021.     end
  1022.     return buffer.toString(decryptedData)
  1023.   end
  1024.   --
  1025.   -- the following 4 functions can be used as
  1026.   -- modefunction for decryptString
  1027.   --
  1028.   -- Electronic code book mode decrypt function
  1029.   function public.decryptECB(keySched, byteData, iv)
  1030.     aes.decrypt(keySched, byteData, 1, byteData, 1)
  1031.     return iv
  1032.   end
  1033.  
  1034.   -- Cipher block chaining mode decrypt function
  1035.   function public.decryptCBC(keySched, byteData, iv)
  1036.     local nextIV = {}
  1037.     for j = 1, 16 do nextIV[j] = byteData[j] end
  1038.     aes.decrypt(keySched, byteData, 1, byteData, 1)
  1039.     util.xorIV(byteData, iv)
  1040.     return nextIV
  1041.   end
  1042.  
  1043.   -- Output feedback mode decrypt function
  1044.   function public.decryptOFB(keySched, byteData, iv)
  1045.     aes.encrypt(keySched, iv, 1, iv, 1)
  1046.     util.xorIV(byteData, iv)
  1047.     return iv
  1048.   end
  1049.  
  1050.   -- Cipher feedback mode decrypt function
  1051.   function public.decryptCFB(keySched, byteData, iv)
  1052.     local nextIV = {}
  1053.     for j = 1, 16 do nextIV[j] = byteData[j] end
  1054.     aes.encrypt(keySched, iv, 1, iv, 1)
  1055.     util.xorIV(byteData, iv)
  1056.     return nextIV
  1057.   end
  1058.  
  1059.   public.decryptCTR = public.encryptCTR
  1060.   return public
  1061.   end)
  1062.  
  1063.   -- Simple API for encrypting strings.
  1064.   --
  1065.   AES128 = 16
  1066.   AES192 = 24
  1067.   AES256 = 32
  1068.   ECBMODE = 1
  1069.   CBCMODE = 2
  1070.   OFBMODE = 3
  1071.   CFBMODE = 4
  1072.   CTRMODE = 4
  1073.  
  1074.   local function pwToKey(password, keyLength, iv)
  1075.     local padLength = keyLength
  1076.     if (keyLength == AES192) then
  1077.       padLength = 32
  1078.     end
  1079.     if (padLength > #password) then
  1080.       local postfix = ""
  1081.       for i = 1,padLength - #password do
  1082.         postfix = postfix .. string.char(0)
  1083.       end
  1084.       password = password .. postfix
  1085.     else
  1086.       password = string.sub(password, 1, padLength)
  1087.     end
  1088.     local pwBytes = {string.byte(password,1,#password)}
  1089.     password = ciphermode.encryptString(pwBytes, password, ciphermode.encryptCBC, iv)
  1090.     password = string.sub(password, 1, keyLength)
  1091.     return {string.byte(password,1,#password)}
  1092.   end
  1093.   --
  1094.   -- Encrypts string data with password password.
  1095.   -- password  - the encryption key is generated from this string
  1096.   -- data      - string to encrypt (must not be too large)
  1097.   -- keyLength - length of aes key: 128(default), 192 or 256 Bit
  1098.   -- mode      - mode of encryption: ecb, cbc(default), ofb, cfb
  1099.   --
  1100.   -- mode and keyLength must be the same for encryption and decryption.
  1101.   --
  1102.   function encrypt(password, data, keyLength, mode, iv)
  1103.     assert(password ~= nil, "Empty password.")
  1104.     assert(data ~= nil, "Empty data.")
  1105.     local mode = mode or CBCMODE
  1106.     local keyLength = keyLength or AES128
  1107.     local key = pwToKey(password, keyLength, iv)
  1108.     local paddedData = util.padByteString(data)
  1109.     if mode == ECBMODE then
  1110.       return ciphermode.encryptString(key, paddedData, ciphermode.encryptECB, iv)
  1111.     elseif mode == CBCMODE then
  1112.       return ciphermode.encryptString(key, paddedData, ciphermode.encryptCBC, iv)
  1113.     elseif mode == OFBMODE then
  1114.       return ciphermode.encryptString(key, paddedData, ciphermode.encryptOFB, iv)
  1115.     elseif mode == CFBMODE then
  1116.       return ciphermode.encryptString(key, paddedData, ciphermode.encryptCFB, iv)
  1117.     elseif mode == CTRMODE then
  1118.       return ciphermode.encryptString(key, paddedData, ciphermode.encryptCTR, iv)
  1119.     else
  1120.       error("Unknown mode", 2)
  1121.     end
  1122.   end
  1123.   --
  1124.   -- Decrypts string data with password password.
  1125.   -- password  - the decryption key is generated from this string
  1126.   -- data      - string to encrypt
  1127.   -- keyLength - length of aes key: 128(default), 192 or 256 Bit
  1128.   -- mode      - mode of decryption: ecb, cbc(default), ofb, cfb
  1129.   --
  1130.   -- mode and keyLength must be the same for encryption and decryption.
  1131.   --
  1132.   function decrypt(password, data, keyLength, mode, iv)
  1133.     local mode = mode or CBCMODE
  1134.     local keyLength = keyLength or AES128
  1135.     local key = pwToKey(password, keyLength, iv)
  1136.     local plain
  1137.     if mode == ECBMODE then
  1138.       plain = ciphermode.decryptString(key, data, ciphermode.decryptECB, iv)
  1139.     elseif mode == CBCMODE then
  1140.       plain = ciphermode.decryptString(key, data, ciphermode.decryptCBC, iv)
  1141.     elseif mode == OFBMODE then
  1142.       plain = ciphermode.decryptString(key, data, ciphermode.decryptOFB, iv)
  1143.     elseif mode == CFBMODE then
  1144.       plain = ciphermode.decryptString(key, data, ciphermode.decryptCFB, iv)
  1145.     elseif mode == CTRMODE then
  1146.       plain = ciphermode.decryptString(key, data, ciphermode.decryptCTR, iv)
  1147.     else
  1148.       error("Unknown mode", 2)
  1149.     end
  1150.     result = util.unpadByteString(plain)
  1151.     if (result == nil) then
  1152.       return nil
  1153.     end
  1154.     return result
  1155.   end
  1156. end
  1157.  
  1158. -- SHA-256, HMAC and PBKDF2 functions in ComputerCraft
  1159. -- By Anavrins
  1160. -- For help and details, you can PM me on the CC forums
  1161. -- http://www.computercraft.info/forums2/index.php?/user/12870-anavrins
  1162. -- You may use this code in your projects without asking me, as long as credit is given and this header is kept intact
  1163. -- Pastebin: http://pastebin.com/6UV4qfNF
  1164. local digest, hmac, pbkdf2
  1165. do
  1166.   local mod32 = 2^32
  1167.   local sha_hashlen = 32
  1168.   local sha_blocksize = 64
  1169.   local band    = bit32 and bit32.band or bit.band
  1170.   local bnot    = bit32 and bit32.bnot or bit.bnot
  1171.   local bxor    = bit32 and bit32.bxor or bit.bxor
  1172.   local blshift = bit32 and bit32.lshift or bit.blshift
  1173.   local upack   = unpack
  1174.  
  1175.   local function rrotate(n, b)
  1176.     local s = n/(2^b)
  1177.     local f = s%1
  1178.     return (s-f) + f*mod32
  1179.   end
  1180.  
  1181.   local function brshift(int, by) -- Thanks bit32 for bad rshift
  1182.     local s = int / (2^by)
  1183.     return s - s%1
  1184.   end
  1185.  
  1186.   local H = {
  1187.     0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
  1188.     0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
  1189.   }
  1190.  
  1191.   local K = {
  1192.     0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
  1193.     0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
  1194.     0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
  1195.     0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
  1196.     0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
  1197.     0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
  1198.     0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
  1199.     0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
  1200.   }
  1201.  
  1202.   local function counter(incr)
  1203.     local t1, t2 = 0, 0
  1204.     if 0xFFFFFFFF - t1 < incr then
  1205.       t2 = t2 + 1
  1206.       t1 = incr - (0xFFFFFFFF - t1) - 1    
  1207.     else t1 = t1 + incr
  1208.     end
  1209.     return t2, t1
  1210.   end
  1211.  
  1212.   local function BE_toInt(bs, i)
  1213.     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)
  1214.   end
  1215.  
  1216.   local function preprocess(data)
  1217.     local len = #data
  1218.     local proc = {}
  1219.     data[#data+1] = 0x80
  1220.     while #data%64~=56 do data[#data+1] = 0 end
  1221.     local blocks = math.ceil(#data/64)
  1222.     for i = 1, blocks do
  1223.       proc[i] = {}
  1224.       for j = 1, 16 do
  1225.         proc[i][j] = BE_toInt(data, 1+((i-1)*64)+((j-1)*4))
  1226.       end
  1227.     end
  1228.     proc[blocks][15], proc[blocks][16] = counter(len*8)
  1229.     return proc
  1230.   end
  1231.  
  1232.   local function digestblock(w, C)
  1233.     for j = 17, 64 do
  1234.       local v = w[j-15]
  1235.       local s0 = bxor(bxor(rrotate(w[j-15], 7), rrotate(w[j-15], 18)), brshift(w[j-15], 3))
  1236.       local s1 = bxor(bxor(rrotate(w[j-2], 17), rrotate(w[j-2], 19)), brshift(w[j-2], 10))
  1237.       w[j] = (w[j-16] + s0 + w[j-7] + s1)%mod32
  1238.     end
  1239.     local a, b, c, d, e, f, g, h = upack(C)
  1240.     for j = 1, 64 do
  1241.       local S1 = bxor(bxor(rrotate(e, 6), rrotate(e, 11)), rrotate(e, 25))
  1242.       local ch = bxor(band(e, f), band(bnot(e), g))
  1243.       local temp1 = (h + S1 + ch + K[j] + w[j])%mod32
  1244.       local S0 = bxor(bxor(rrotate(a, 2), rrotate(a, 13)), rrotate(a, 22))
  1245.       local maj = bxor(bxor(band(a, b), band(a, c)), band(b, c))
  1246.       local temp2 = (S0 + maj)%mod32
  1247.       h, g, f, e, d, c, b, a = g, f, e, (d+temp1)%mod32, c, b, a, (temp1+temp2)%mod32
  1248.     end
  1249.     C[1] = (C[1] + a)%mod32
  1250.     C[2] = (C[2] + b)%mod32
  1251.     C[3] = (C[3] + c)%mod32
  1252.     C[4] = (C[4] + d)%mod32
  1253.     C[5] = (C[5] + e)%mod32
  1254.     C[6] = (C[6] + f)%mod32
  1255.     C[7] = (C[7] + g)%mod32
  1256.     C[8] = (C[8] + h)%mod32
  1257.     return C
  1258.   end
  1259.  
  1260.   local mt = {
  1261.     __tostring = function(a) return string.char(unpack(a)) end,
  1262.     __index = {
  1263.       toHex = function(self, s) return ("%02x"):rep(#self):format(unpack(self)) end,
  1264.       isEqual = function(self, t)
  1265.         if type(t) ~= "table" then return false end
  1266.         if #self ~= #t then return false end
  1267.         local ret = 0
  1268.         for i = 1, #self do
  1269.           ret = bit32.bor(ret, bxor(self[i], t[i]))
  1270.         end
  1271.         return ret == 0
  1272.       end
  1273.     }
  1274.   }
  1275.  
  1276.   local function toBytes(t, n)
  1277.     local b = {}
  1278.     for i = 1, n do
  1279.       b[(i-1)*4+1] = band(brshift(band(t[i], 0xFF000000), 24), 0xFF)
  1280.       b[(i-1)*4+2] = band(brshift(band(t[i], 0xFF0000), 16), 0xFF)
  1281.       b[(i-1)*4+3] = band(brshift(band(t[i], 0xFF00), 8), 0xFF)
  1282.       b[(i-1)*4+4] = band(t[i], 0xFF)
  1283.     end
  1284.     return setmetatable(b, mt)
  1285.   end
  1286.  
  1287.   digest = function(data)
  1288.     data = data or ""
  1289.     data = type(data) == "string" and {data:byte(1,-1)} or data
  1290.     data = preprocess(data)
  1291.     local C = {upack(H)}
  1292.     for i = 1, #data do C = digestblock(data[i], C) end
  1293.     return toBytes(C, 8)
  1294.   end
  1295.  
  1296.   hmac = function(data, key)
  1297.     local data = type(data) == "table" and {upack(data)} or {tostring(data):byte(1,-1)}
  1298.     local key = type(key) == "table" and {upack(key)} or {tostring(key):byte(1,-1)}
  1299.     local blocksize = sha_blocksize
  1300.     key = #key > blocksize and digest(key) or key
  1301.     local ipad = {}
  1302.     local opad = {}
  1303.     local padded_key = {}
  1304.     for i = 1, blocksize do
  1305.       ipad[i] = bxor(0x36, key[i] or 0)
  1306.       opad[i] = bxor(0x5C, key[i] or 0)
  1307.     end
  1308.     for i = 1, #data do
  1309.       ipad[blocksize+i] = data[i]
  1310.     end
  1311.     ipad = digest(ipad)
  1312.     for i = 1, blocksize do
  1313.       padded_key[i] = opad[i]
  1314.       padded_key[blocksize+i] = ipad[i]
  1315.     end
  1316.     return digest(padded_key)
  1317.   end
  1318.  
  1319.   pbkdf2 = function(pass, salt, iter, dklen)
  1320.     local out = {}
  1321.     local hashlen = sha_hashlen
  1322.     local block = 1
  1323.     dklen = dklen or 32
  1324.     while dklen > 0 do
  1325.       local ikey = {}
  1326.       local isalt = type(salt) == "table" and {upack(salt)} or {tostring(salt):byte(1,-1)}
  1327.       local clen = dklen > hashlen and hashlen or dklen
  1328.       local iCount = #isalt
  1329.       isalt[iCount+1] = band(brshift(band(block, 0xFF000000), 24), 0xFF)
  1330.       isalt[iCount+2] = band(brshift(band(block, 0xFF0000), 16), 0xFF)
  1331.       isalt[iCount+3] = band(brshift(band(block, 0xFF00), 8), 0xFF)
  1332.       isalt[iCount+4] = band(block, 0xFF)
  1333.       for j = 1, iter do
  1334.         isalt = hmac(isalt, pass)
  1335.         for k = 1, clen do ikey[k] = bxor(isalt[k], ikey[k] or 0) end
  1336.         if j % 200 == 0 then os.queueEvent("PBKDF2", j) coroutine.yield("PBKDF2") end
  1337.       end
  1338.       dklen = dklen - clen
  1339.       block = block+1
  1340.       for k = 1, clen do out[#out+1] = ikey[k] end
  1341.     end
  1342.     return setmetatable(out, mt)
  1343.   end
  1344. end
  1345.  
  1346. local function clearMonitors()
  1347.   if hardware.listMon > 0 then
  1348.     for i = 1, hardware.listMon do
  1349.       listMon[i].setBackgroundColor(black)
  1350.       listMon[i].clear()
  1351.     end
  1352.   end
  1353.   if hardware.marquee > 0 then
  1354.     for i = 1, hardware.marquee do
  1355.       marquee[i].setBackgroundColor(black)
  1356.       marquee[i].clear()
  1357.     end
  1358.   end
  1359.   if hardware.gateMon > 0 then
  1360.     for i = 1, hardware.gateMon do
  1361.       gateMon[i].setBackgroundColor(black)
  1362.       gateMon[i].clear()
  1363.     end
  1364.   end
  1365.   if hardware.timerMon > 0 then
  1366.     for i = 1, hardware.timerMon do
  1367.       timerMon[i].setBackgroundColor(black)
  1368.       timerMon[i].clear()
  1369.     end
  1370.   end
  1371. end
  1372.  
  1373. local function clearScreen(bgColor)
  1374.   term.setBackgroundColor(bgColor or black)
  1375.   term.clear()
  1376. end
  1377.  
  1378. local function shutDown()
  1379.   sendToAllSyncClients("Offline")
  1380.   kernelState = false
  1381.   if dhdSettings.sync then rednet.unhost("ccDialerWiFi", thisGate) end
  1382.   if hardware.modemSide ~= "none" and rednet.isOpen(hardware.modemSide) then rednet.close(hardware.modemSide) end
  1383.   if hardware.wifiSide ~= "none" and rednet.isOpen(hardware.wifiSide) then rednet.close(hardware.wifiSide) end
  1384.   clearMonitors()
  1385.   clearScreen()
  1386.   os.pullEvent = stdPullEvent
  1387.   term.setTextColor(white)
  1388.   term.setCursorPos(1, 1)
  1389. end
  1390.  
  1391. local function clearLockdown()
  1392.   secureStatus = "allclear"
  1393.   term.setCursorBlink(false)
  1394.   drawElement((termX / 2) - 7, 9, 14, 1, nil, black)
  1395.   drawElement((termX / 2) - 10, 15, 22, 1)
  1396.   gettingInput = false
  1397.   validStates[runState][2]()
  1398.   if dhdSettings.detailedMarquee then
  1399.     initMarquee = true
  1400.     displayStatusDetail()
  1401.   else
  1402.     displayStatus()
  1403.   end
  1404.   if gateStatus ~= "Idle" then displayAddressBook() end
  1405. end
  1406.  
  1407. local function setupLockdown()
  1408.   secureStatus, displayState = "lockdown", "list"
  1409.   menuState, fullRedraw, initMarquee = false, false, true
  1410.   runState = validStates[runState][1]
  1411.   sendToAllSyncClients("Offline")
  1412.   if hardware.listMon > 0 then
  1413.     for i = 1, hardware.listMon do
  1414.       listMon[i].setBackgroundColor(black)
  1415.       listMon[i].setTextColor(red)
  1416.       listMon[i].clear()
  1417.       listMon[i].setCursorPos(1, 5)
  1418.       listMon[i].write("!! LOCKDOWN !!")
  1419.     end
  1420.   end
  1421.   if gateMonList then
  1422.     gateMonList = false
  1423.     for i = 1, hardware.gateMon do
  1424.       gateMon[i].setBackgroundColor(black)
  1425.       gateMon[i].clear()
  1426.     end
  1427.     displayGate()
  1428.   end
  1429.   drawElement(1, 5, termX, termY - 4, nil, black) --# clear lower screen
  1430. end
  1431.  
  1432. local function recordSessionData()                        --# Human readable log files (last gate & history)
  1433.   local logAddress = incomingAddress or dialAddress       --# set logAddress
  1434.   logAddress = logAddress == "Wormhole" and "N/A" or logAddress
  1435.   local callTime = textutils.formatTime(os.time(), false) --# format the time
  1436.   if #callTime == 7 then callTime = " " .. callTime end   --# move single digit hour times to the right one space
  1437.   local dataLine = tostring(os.day()) .. " @ " .. callTime .. " <" .. callDirection .. "> " .. logAddress --# build the data line to be recorded
  1438.   local previousCall = fs.open(lastGate, "w")             --# record last call
  1439.   previousCall.writeLine(dataLine)
  1440.   previousCall.close()
  1441.   if dhdSettings.logs then                                --# record call in log file if logging is turned on
  1442.     local callArchive = fs.open(gateHistory, fs.exists(gateHistory) and "a" or "w")
  1443.     callArchive.writeLine(dataLine)
  1444.     callArchive.close()
  1445.   end
  1446. end
  1447.  
  1448. local function paginateAddressBook()
  1449.   gatePages = math.ceil(abCount / 18)
  1450.   listPages = math.ceil(abCount / 8)
  1451.   gateMonPages = math.ceil(abCount / 17)
  1452. end
  1453.  
  1454. local function mergeAddressBooks(newGates)
  1455.   local matchFound, abName, abNote, abDim, ngName, ngNote, ngDim = false --# matchFound indicates if a matching gate was found or not
  1456.   for i = 1, #newGates do                             --# start cycling through the list of 'new' gates
  1457.     for j = 1, abCount do                             --# search the address book for a matching address
  1458.       if newGates[i].addr == addressBook[j].addr then --# if the gate is already in the address book...
  1459.         matchFound = true                             --# ...set matchFound to true and...
  1460.         abName, ngName = addressBook[j].name, newGates[i].name
  1461.         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
  1462.           addressBook[j].name = ngName
  1463.           gateChange = true
  1464.         end
  1465.         abNote, ngNote = addressBook[j].note, newGates[i].note
  1466.         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
  1467.           addressBook[j].note = ngNote
  1468.           gateChange = true
  1469.         end
  1470.         if addressBook[j].rating == "U" and newGates[i].rating ~= "U" then
  1471.           addressBook[j].rating = newGates[i].rating
  1472.           gateChange = true
  1473.         end
  1474.         abDim, ngDim = addressBook[j].loc.dim, newGates[i].loc.dim
  1475.         if (abDim == "Overworld" and ngDim ~= "Overworld" and ngDim ~= "Unspecified") or (abDim == "Unspecified" and ngDim ~= "Unspecified") then
  1476.           addressBook[j].loc.dim = ngDim
  1477.           gateChange = true
  1478.         end
  1479.         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
  1480.           addressBook[j].loc.x, addressBook[j].loc.y, addressBook[j].loc.z = newGates[i].loc.x, newGates[i].loc.y, newGates[i].loc.z
  1481.           gateChange = true
  1482.         end
  1483.         break                                         --# stop the address book search loop and move on...
  1484.       end
  1485.     end
  1486.     if matchFound then                                --# if a match was found...
  1487.       matchFound = false                              --# reset the variable for the next iteration of the loop
  1488.     else                                              --# if a match wasn't found...
  1489.       addressBook[abCount + 1], abCount = { }, abCount + 1 --# initialize a new address book entry
  1490.       for k, v in pairs(newGates[i]) do               --# loop through the gate entries...
  1491.         addressBook[abCount][k] = v                   --# ...and add them to the new address book entry
  1492.       end
  1493.       addressBook[abCount].iris = "none"              --# set iris control to 'none'
  1494.       addressBook[abCount].callDrop = false           --# set callDrop to false
  1495.       gateChange = true                               --# indicate that address book data has changed
  1496.     end
  1497.   end
  1498.   paginateAddressBook()                               --# repaginate AddressBook
  1499. end
  1500.  
  1501. sendToAllSyncClients = function(data)
  1502.   if clientCount < 1 then return end
  1503.   for id in pairs(clients) do
  1504.     netSend(id, { program = "ccDialer", data = data }, "ccDialerWiFi")
  1505.     if data == "Offline" then
  1506.       clients[id] = nil
  1507.       clientCount = math.max(0, clientCount - 1)
  1508.     end
  1509.   end
  1510. end
  1511.  
  1512. netSend = function(target, dataPack, protocol)
  1513.   if hardware.modemSide ~= "none" and not rednet.isOpen(hardware.modemSide) then rednet.open(hardware.modemSide) end
  1514.   if target == dhdSettings.gate then
  1515.     if hardware.modemSide ~= "none" and hardware.wifiSide ~= "none" then
  1516.       if rednet.isOpen(hardware.wifiSide) then rednet.close(hardware.wifiSide) end
  1517.     elseif hardware.modemSide == "none" and hardware.wifiSide ~= "none" then
  1518.       if not rednet.isOpen(hardware.wifiSide) then rednet.open(hardware.wifiSide) end
  1519.     end
  1520.   else
  1521.     if hardware.wifiSide ~= "none" and not rednet.isOpen(hardware.wifiSide) then rednet.open(hardware.wifiSide) end
  1522.   end
  1523.   local encKey = tostring(target) .. "ccDHD!General_Comms*Key" .. thisCC
  1524.   local encryptedDataPack = encode(encrypt(encKey, textutils.serialize(dataPack)))
  1525.   rednet.send(target, encryptedDataPack, protocol or "ccDHD")
  1526.   if target == dhdSettings.gate and hardware.wifiSide ~= "none" then
  1527.     if not rednet.isOpen(hardware.wifiSide) then rednet.open(hardware.wifiSide) end
  1528.   end
  1529. end
  1530.  
  1531. local function netReceive()
  1532.   local id, message, encKey, encryptedMessage, decryptedMessage, encodedMessage, success, iColor, mData, iPass, lockPass
  1533.   while true do
  1534.     if hardware.modemSide ~= "none" and not rednet.isOpen(hardware.modemSide) then rednet.open(hardware.modemSide) end
  1535.     if hardware.wifiSide ~= "none" and not rednet.isOpen(hardware.wifiSide) then rednet.open(hardware.wifiSide) end
  1536.     id, encodedMessage = rednet.receive("ccDHD")
  1537.     if id == dhdSettings.gate and type(encodedMessage) == "string" then
  1538.       success, encryptedMessage = pcall(decode, encodedMessage)
  1539.       if success then
  1540.         encKey = thisCC .. "ccDHD!General_Comms*Key" .. tostring(id)
  1541.         success, decryptedMessage = pcall(decrypt, encKey, encryptedMessage)
  1542.         if success then
  1543.           success, message = pcall(textutils.unserialize, decryptedMessage)
  1544.           if success and type(message) == "table" and message.program then
  1545.             if message.program == "ccDHD" then
  1546.               updateStatus(message)
  1547.               if message.gateStatus == "Disconnecting" and type(dhdSettings.ecIris) == "boolean" then
  1548.                 if (irisState and dhdSettings.ecIris and secureStatus == "allclear") or (not irisState and not dhdSettings.ecIris) then
  1549.                   netSend(dhdSettings.gate, { program = "ccDHD", command = dhdSettings.ecIris and "iOPEN" or "iCLOSE" })
  1550.                 end
  1551.               end
  1552.             elseif message.program == "ccDialer" and message.data then
  1553.               mData, iPass, lockPass = message.data, dhdSettings.irisPassword, dhdSettings.password
  1554.               if mData == iPass or mData == lockPass then
  1555.                 if secureStatus == "lockdown" and mData == lockPass then
  1556.                   netSend(dhdSettings.gate, { program = "ccDHD", command = "allclear" })
  1557.                   return clearLockdown()
  1558.                 elseif secureStatus == "allclear" and mData == lockPass then
  1559.                   netSend(dhdSettings.gate, { program = "ccDHD", command = "lockdown" })
  1560.                   return setupLockdown()
  1561.                 elseif secureStatus == "allclear" and mData == iPass then
  1562.                   netSend(dhdSettings.gate, { program = "ccDHD", command = irisState and "iOPEN" or "iCLOSE" })
  1563.                 end
  1564.               elseif mData == "open" or mData == "closed" or mData == "unk" then
  1565.                 sendToAllSyncClients(mData)
  1566.                 remoteIrisStatus = mData
  1567.                 drawRemoteIrisStatus()
  1568.               elseif mData == "Idle" or mData == "Dialing" or mData == "Paused" or mData == "Connected" then
  1569.                 sendToAllSyncClients(mData)
  1570.               end
  1571.             end
  1572.           end
  1573.         end
  1574.       end
  1575.     end
  1576.   end
  1577. end
  1578.  
  1579. local function syncReceive()
  1580.   local id, encKey, message, encryptedMessage, decryptedMessage, encodedMessage, mLength, mCommand, mGate, success
  1581.   while true do
  1582.     if dhdSettings.sync and hardware.wifiSide ~= "none" and not rednet.isOpen(hardware.wifiSide) then rednet.open(hardware.wifiSide) end
  1583.     id, encodedMessage = rednet.receive("ccDialerWiFi")
  1584.     if dhdSettings.sync and type(encodedMessage) == "string" then
  1585.       success, encryptedMessage = pcall(decode, encodedMessage)
  1586.       if success then
  1587.         encKey = thisCC .. "ccDHD!General_Comms*Key" .. tostring(id)
  1588.         success, decryptedMessage = pcall(decrypt, encKey, encryptedMessage)
  1589.         if success then
  1590.           success, message = pcall(textutils.unserialize, decryptedMessage)
  1591.           if success and type(message) == "table" and message.program and message.program == "ccDialer" then
  1592.             if message.command and type(message.command) == "string" then
  1593.               mCommand, mGate = message.command, message.gate
  1594.               mLength = #tostring(mCommand)
  1595.               if mCommand == "QRY" and mGate == "NO HOST" then
  1596.                 if secureStatus == "allclear" then
  1597.                   if not clients[id] then clientCount = clientCount + 1 end
  1598.                   clients[id] = 0
  1599.                 end
  1600.                 netSend(id, { program = "ccDialer", data = secureStatus == "allclear" and thisGate or "lockdown", ccDHD = dhdSettings.sync == true, gStatus = gateStatus, iris = remoteIrisStatus }, "ccDialerWiFi")
  1601.               elseif mCommand == "ping" and thisGate == mGate and clients[id] then
  1602.                 netSend(id, { program = "ccDialer", data = "pong" }, "ccDialerWiFi")
  1603.               elseif mCommand == "pong" and thisGate == mGate and clients[id] then
  1604.                 clients[id] = 0
  1605.               elseif mCommand == "logout" and thisGate == mGate and clients[id] then
  1606.                 clients[id] = nil
  1607.                 clientCount = math.max(0, clientCount - 1)
  1608.               elseif mCommand == "pull" and thisGate == mGate and clients[id] and type(dhdSettings.sync) ~= "string" and secureStatus == "allclear" then
  1609.                 netSend(id, { program = "ccDialer", command = "push", data = addressBook }, "ccDialerWiFi")
  1610.               elseif mCommand == "push" and thisGate == mGate and clients[id] and type(dhdSettings.sync) ~= "string" and secureStatus == "allclear" and message.data and type(message.data) == "table" then
  1611.                 mergeAddressBooks(message.data)
  1612.                 if gateChange then
  1613.                   saveData(gateData, "gate")
  1614.                   if dialStates[runState] then fullRedraw = menuState drawCLI(true) end
  1615.                   if gettingInput then repositionCursor() end
  1616.                 end
  1617.               elseif mCommand ~= thisGate and thisGate == mGate and clients[id] and (mLength == 7 or mLength == 9) then
  1618.                 if mCommand == "endCall" and gateStatus ~= "Disconnecting" and gateStatus ~= "Idle" then
  1619.                   netSend(dhdSettings.gate, { program = "ccDHD", command = "endCall" })
  1620.                 elseif mCommand ~= "endCall" and gateStatus == "Idle" and secureStatus == "allclear" then
  1621.                   dialAddress = mCommand
  1622.                   netSend(dhdSettings.gate, { program = "ccDHD", command = dialAddress })
  1623.                 elseif lcGate and mCommand == dialAddress and (gateStatus == "Dialing" or gateStatus == "Paused") and secureStatus == "allclear" then
  1624.                   netSend(dhdSettings.gate, { program = "ccDHD", command = "dialPause" })
  1625.                 end
  1626.               end
  1627.             elseif message.password and thisGate == message.gate and clients[id] and secureStatus == "allclear" then
  1628.               netSend(dhdSettings.gate, { program = "ccDialer", password = message.password })
  1629.             end
  1630.           end
  1631.         end
  1632.       end
  1633.     end
  1634.   end
  1635. end
  1636.  
  1637. local function updateDisplays(init, backSpace)
  1638.   if displayState == "list" then
  1639.     if dhdSettings.detailedMarquee then
  1640.       initMarquee = init == true
  1641.       displayStatusDetail()
  1642.     else
  1643.       displayStatus()
  1644.     end
  1645.     if gateStatus ~= "Idle" then displayAddressBook() end
  1646.   else
  1647.     displayNotes()
  1648.   end
  1649.   if gateMonList then
  1650.     displayGateMonAddrBook()
  1651.   else
  1652.     displayChevrons(init, backSpace)
  1653.   end
  1654. end
  1655.  
  1656. updateStatus = function(newInfo)
  1657.   local monUpdate, recordInfo, backSpace, addr = initMarquee, false, false
  1658.   sgRemoteAddrLen = newInfo.sgRemoteAddrLen
  1659.   if fuelPercent ~= newInfo.fuelPercent or waitingForIris ~= newInfo.waitingForIris or irisState ~= newInfo.irisState or irisStatus ~= newInfo.irisStatus or irisPercent ~= newInfo.irisPercent or gateStatus ~= newInfo.gateStatus or dialAddress ~= newInfo.dialAddress or incomingAddress ~= newInfo.incomingAddress or chevronNumber ~= newInfo.chevronNumber then
  1660.     monUpdate = true
  1661.   end
  1662.   if menuState and ((newInfo.gateStatus == "Idle" and gateStatus ~= newInfo.gateStatus) or waitingForIris ~= newInfo.waitingForIris or irisState ~= newInfo.irisState) then
  1663.     fullRedraw = true --# trigger a full redraw later
  1664.   end
  1665.   waitingForIris = newInfo.waitingForIris
  1666.   irisState = newInfo.irisState
  1667.   irisStatus = newInfo.irisStatus
  1668.   irisPercent = newInfo.irisPercent
  1669.   fuelPercent = newInfo.fuelPercent
  1670.   dialAddress = newInfo.dialAddress
  1671.   incomingAddress = newInfo.incomingAddress
  1672.   callDirection = newInfo.callDirection
  1673.   if chevronNumber > newInfo.chevronNumber then backSpace = true end
  1674.   chevronNumber = newInfo.chevronNumber
  1675.   if currentState == "DHD" then
  1676.     if newInfo.gateStatus ~= gateStatus then
  1677.       if newInfo.gateStatus == "Connected" then
  1678.         connectionTimer = os.startTimer(0.1) --# not done during init due to delay caused by Sync later during init/startup phase
  1679.         local iLength = #tostring(incomingAddress)
  1680.         if dialAddress ~= "none" or incomingAddress == "Wormhole" or iLength == 7 or iLength == 9 then
  1681.           recordInfo = true
  1682.         end
  1683.       elseif newInfo.gateStatus == "Idle" or newInfo.gateStatus == "Offline" then
  1684.         if connectionTimer then os.cancelTimer(connectionTimer) connectionTimer = nil end
  1685.         connectionTime, connectionClock = 0, "00:00:0"
  1686.         displayConnectionTime()
  1687.       end
  1688.     end
  1689.   else
  1690.     lcGate = newInfo.lcGate
  1691.     thisGate = newInfo.thisGate
  1692.     secureStatus = newInfo.secureStatus --# why did I move this to init?  Shouldn't this be set every time, or were there problems?
  1693.     currentState = "DHD" --# one of the last steps of initialization - don't record connections that are already established upon startup
  1694.   end
  1695.   if newInfo.gateStatus ~= gateStatus then
  1696.     if newInfo.gateStatus == "Connected" then
  1697.       if rsSide ~= "none" then rs.setOutput(rsSide, false) end --# Dialing alarm
  1698.     elseif newInfo.gateStatus == "Dialing" and callDirection == "Outgoing" and dialAddress:find("?") then
  1699.       dialFromDHD = true
  1700.     elseif (newInfo.gateStatus == "Dialing" or newInfo.gateStatus == "Connected") and incomingAddress and incomingAddress ~= "Wormhole" then --# Blacklist
  1701.       for i = 1, abCount do
  1702.         addr = addressBook[i].addr
  1703.         if addressBook[i].callDrop and (addr == incomingAddress or addr == incomingAddress:sub(1, 7) or addr:sub(1, 7) == incomingAddress) then
  1704.           netSend(dhdSettings.gate, { program = "ccDHD", command = "endCall" })
  1705.           break
  1706.         end
  1707.       end
  1708.     elseif newInfo.gateStatus == "Idle" or newInfo.gateStatus == "Offline" then
  1709.       dialFromDHD = false
  1710.       if rsSide ~= "none" then rs.setOutput(rsSide, false) end --# Dialing alarm
  1711.     end
  1712.     if newInfo.gateStatus == "Dialing" and (callDirection == "Incoming" or (callDirection == "Outgoing" and outgoingAlarm)) then
  1713.       if rsSide ~= "none" then rs.setOutput(rsSide, true) end --# Dailing alarm
  1714.     end
  1715.     gateStatus = newInfo.gateStatus
  1716.   end
  1717.   local irisClose, irisManage = false, false
  1718.   if (recordInfo or runState == "init") and incomingAddress and secureStatus == "allclear" then --# Iris management
  1719.     if dhdSettings.incomingIris then irisClose = true end
  1720.     irisManage = true
  1721.   end
  1722.   if irisManage then
  1723.     for i = 1, abCount do
  1724.       addr = addressBook[i].addr
  1725.       if addr == incomingAddress or addr == incomingAddress:sub(1, 7) or addr:sub(1, 7) == incomingAddress then
  1726.         if type(addressBook[i].iris) == "boolean" then --# addressBook entry automatic iris control is set to 'open' (true) or 'close' (false)
  1727.           if addressBook[i].iris then
  1728.             if irisState then
  1729.               netSend(dhdSettings.gate, { program = "ccDHD", command = "iOPEN" })
  1730.             end
  1731.             irisClose = false
  1732.           else
  1733.             irisClose = true
  1734.           end
  1735.         end
  1736.         break
  1737.       end
  1738.     end
  1739.   end
  1740.   if irisClose and not irisState then
  1741.     netSend(dhdSettings.gate, { program = "ccDHD", command = "iCLOSE" })
  1742.   end
  1743.   if recordInfo then recordSessionData() end
  1744.   if validStates[runState] then
  1745.     if validStates[runState][5] then
  1746.       drawCLI(monUpdate, nil, backSpace)
  1747.     elseif monUpdate then
  1748.       updateDisplays(nil, backSpace)
  1749.     end
  1750.   end
  1751. end
  1752.  
  1753. saveData = function(fileName, fileType)
  1754.   local dhdData = fs.open(fileName, "w")
  1755.   if fileType == "cfg" then
  1756.     dhdData.write(textutils.serialize(dhdSettings))
  1757.     configChange = false
  1758.   elseif fileType == "gate" then
  1759.     dhdData.write(textutils.serialize(addressBook))
  1760.     if fileName == gateData then gateChange = false end
  1761.   end
  1762.   dhdData.close()
  1763. end
  1764.  
  1765. local function ingestData(fileName, fileType)
  1766.   if fileName == "logs" then       --# Logs
  1767.     if fs.exists(gateHistory) then
  1768.       for logEntry in io.lines(gateHistory) do
  1769.         table.insert(callHistory, 1, logEntry)
  1770.       end
  1771.       logPages = math.max(1, math.ceil(#callHistory / 11)) --# paginate call logs
  1772.     end
  1773.     if fs.exists(lastGate) then
  1774.       local dhdLast = fs.open(lastGate, "r")
  1775.       lastCall = dhdLast.readLine()
  1776.       dhdLast.close()
  1777.     end
  1778.   else
  1779.     local dhdData = fs.open(fileName, "r")
  1780.     local dhdInfo = dhdData.readAll()
  1781.     dhdData.close()
  1782.     if fileType == "cfg" then      --# Config
  1783.       dhdSettings = textutils.unserialize(dhdInfo)
  1784.       if dhdSettings.startIris ~= nil or not dhdSettings.irisPassword or not dhdSettings.hashedPWs or dhdSettings.pSync ~= nil or dhdSettings.highlight ~= nil then
  1785.         --# Upgrade path from 1.5 to 2.0 for SGCraft users
  1786.         if dhdSettings.startIris ~= nil then
  1787.           dhdSettings.incomingIris = dhdSettings.startIris
  1788.           dhdSettings.startIris = nil
  1789.         end
  1790.         if not dhdSettings.irisPassword then
  1791.           dhdSettings.irisPassword = "password"
  1792.           if dhdSettings.hashedPWs then
  1793.             if dhdSettings.newHash then
  1794.               dhdSettings.irisPassword = table.concat(pbkdf2(dhdSettings.irisPassword, "ccDHD!Pass.Hash", 15))
  1795.             else
  1796.               dhdSettings.irisPassword = table.concat(pbkdf2(dhdSettings.irisPassword, "ccDHD!pSync", 15))
  1797.             end
  1798.           end
  1799.         end
  1800.         if not dhdSettings.hashedPWs then
  1801.           dhdSettings.hashedPWs, dhdSettings.newHash = true, true
  1802.           dhdSettings.password = table.concat(pbkdf2(dhdSettings.password, "ccDHD!Pass.Hash", 15))
  1803.           dhdSettings.irisPassword = table.concat(pbkdf2(dhdSettings.irisPassword, "ccDHD!Pass.Hash", 15))
  1804.         end
  1805.         if dhdSettings.pSync ~= nil then
  1806.           dhdSettings.sync = dhdSettings.pSync
  1807.           dhdSettings.pSync = nil
  1808.         end
  1809.         if dhdSettings.highlight ~= nil then
  1810.           dhdSettings.detailedMarquee = true
  1811.           dhdSettings.highlight = nil
  1812.         end
  1813.         --# End upgrade path
  1814.         saveData(settingsData, "cfg")
  1815.       end
  1816.     elseif fileType == "gate" then --# Address book (main or backup)
  1817.       for i = abCount, 1, -1 do
  1818.         addressBook[i] = nil
  1819.       end
  1820.       addressBook = textutils.unserialize(dhdInfo)
  1821.       abCount = #addressBook
  1822.       --# Upgrade path from 1.5 to 2.0 for SGCraft users
  1823.       local saveIt = false
  1824.       if addressBook[1].callDrop == nil then
  1825.         for i = 1, abCount do
  1826.           addressBook[i].callDrop = false
  1827.         end
  1828.         saveIt = true
  1829.       end
  1830.       for i = 1, abCount do
  1831.         if addressBook[i].iris == "open" then
  1832.           addressBook[i].iris, saveIt = true, true
  1833.         elseif addressBook[i].iris == "close" then
  1834.           addressBook[i].iris, saveIt = false, true
  1835.         end
  1836.       end
  1837.       if saveIt and fileName == gateData then saveData(gateData, "gate") end
  1838.       --# End upgrade path
  1839.       gatePage, listPage, gateMonPage = 1, 1, 1
  1840.       gateChange = false
  1841.       paginateAddressBook()
  1842.     end
  1843.   end
  1844. end
  1845.  
  1846. local function assignColor(gateNumber)
  1847.   return classifications[addressBook[gateNumber].rating].color or silver
  1848. end
  1849.  
  1850. local function assignRating(gateNumber)
  1851.   return classifications[addressBook[gateNumber].rating].long or (classifications[addressBook[gateNumber].rating].label or "Unknown/Unclassified")
  1852. end
  1853.  
  1854. repositionCursor = function() --# account for screen writes repositioning the cursor
  1855.   term.setBackgroundColor(bgCol)
  1856.   term.setTextColor(txtCol)
  1857.   term.setCursorPos((runState == "goPage" or runState == "remotePass") and curX or curX + #word, curY)
  1858.   if (runState == "goPage" or runState == "remotePass") and word ~= "" then term.write(runState == "goPage" and word or string.rep("*", #word)) end
  1859. end
  1860.  
  1861. displayGateMonAddrBook = function()
  1862.   if hardware.gateMon == 0 or not gateMonList then return end
  1863.   local yPos, firstGate, spacer, gm, addr = 0, ((gateMonPage - 1) * 17) + 1, string.rep(" ", 29)
  1864.   for i = 1, hardware.gateMon do
  1865.     yPos, gm = 0, gateMon[i]
  1866.     for j = firstGate, firstGate + 16 do
  1867.       yPos = yPos + 1
  1868.       gm.setCursorPos(1, yPos)
  1869.       if j > abCount then
  1870.         gm.setBackgroundColor(black)
  1871.         gm.write(spacer)
  1872.       else
  1873.         addr = addressBook[j].addr
  1874.         if (addr == thisGate or addr:sub(1, 7) == thisGate or addr == thisGate:sub(1, 7)) and dialAddress == "none" and not incomingAddress then
  1875.           gm.setBackgroundColor(gray)
  1876.         else
  1877.           gm.setBackgroundColor((dialAddress == "none" and not incomingAddress) and green or red)
  1878.         end
  1879.         gm.setTextColor(((addr == thisGate or addr:sub(1, 7) == thisGate or addr == thisGate:sub(1, 7)) and dialAddress == "none" and not incomingAddress) and silver or white)
  1880.         gm.write((dialAddress == "none" and not incomingAddress) and " Dial " or "Hangup")
  1881.         gm.setCursorPos(8, yPos)
  1882.         gm.setBackgroundColor(black)
  1883.         if addr == thisGate then
  1884.           gm.setTextColor(gray)
  1885.         else
  1886.           gm.setTextColor(((dialAddress == "none" and not incomingAddress) or (dialAddress == addr or dialAddress:sub(1, 7) == addr or dialAddress == addr:sub(1, 7) or (incomingAddress and (incomingAddress == addr or incomingAddress:sub(1, 7) == addr or incomingAddress == addr:sub(1, 7))))) and assignColor(j) or gray)
  1887.         end
  1888.         gm.write(addressBook[j].name .. string.rep(" ", 12 - #addressBook[j].name))
  1889.         gm.setCursorPos(21, yPos)
  1890.         if addr == thisGate then
  1891.           gm.setTextColor(gray)
  1892.         else
  1893.           gm.setTextColor(((dialAddress == "none" and not incomingAddress) or (dialAddress == addr or dialAddress:sub(1, 7) == addr or dialAddress == addr:sub(1, 7) or (incomingAddress and (incomingAddress == addr or incomingAddress:sub(1, 7) == addr or incomingAddress == addr:sub(1, 7))))) and yellow or gray)
  1894.         end
  1895.         gm.write(addr .. "  ")
  1896.       end
  1897.     end
  1898.     gm.setCursorPos(1, 19)
  1899.     gm.setBackgroundColor(cyan)
  1900.     gm.setTextColor(white)
  1901.     gm.write("    /\\                 \\/    ")
  1902.     gm.setCursorPos(12, 19)
  1903.     gm.setBackgroundColor(gray)
  1904.     gm.write(" Close ")
  1905.   end
  1906. end
  1907.  
  1908. displayAddressBook = function()
  1909.   if hardware.listMon == 0 then return end
  1910.   local firstGate, yPos, lm, addr = ((listPage - 1) * 8) + 1, 0
  1911.   for i = 1, hardware.listMon do
  1912.     yPos, lm = 0, listMon[i]
  1913.     lm.setBackgroundColor(black)
  1914.     lm.clear()
  1915.     for j = firstGate, math.min(firstGate + 7, abCount) do
  1916.       yPos, addr = yPos + 1, addressBook[j].addr
  1917.       lm.setTextColor((addr == thisGate or addr:sub(1, 7) == thisGate or addr == thisGate:sub(1, 7)) and gray or assignColor(j))
  1918.       lm.setCursorPos(2, yPos)
  1919.       lm.write(addressBook[j].name)
  1920.     end
  1921.     lm.setBackgroundColor(cyan)
  1922.     lm.setTextColor(white)
  1923.     lm.setCursorPos(1, 10)
  1924.     lm.write("   /\\     \\/   ")
  1925.   end
  1926. end
  1927.  
  1928. displayNotes = function()
  1929.   if hardware.listMon > 0 then
  1930.     local gateColor, name, address, lm = assignColor(selectedGate), addressBook[selectedGate].name, addressBook[selectedGate].addr
  1931.     for i = 1, hardware.listMon do
  1932.       lm = listMon[i]
  1933.       lm.setBackgroundColor(black)
  1934.       lm.clear()
  1935.       lm.setBackgroundColor(gateColor)
  1936.       for j = 1, 10, 9 do
  1937.         lm.setCursorPos(1, j)
  1938.         lm.write("               ")
  1939.       end
  1940.       lm.setBackgroundColor(black)
  1941.       lm.setCursorPos(2, 3)
  1942.       lm.setTextColor(cyan)
  1943.       lm.write(name)
  1944.       lm.setCursorPos(2, 5)
  1945.       lm.setTextColor(yellow)
  1946.       lm.write(address)
  1947.       if address == thisGate then
  1948.         lm.setTextColor(gray)
  1949.         lm.setCursorPos(2, 7)
  1950.         lm.write("THIS GATE")
  1951.       end
  1952.     end
  1953.   end
  1954.   if hardware.marquee > 0 then
  1955.     local dimension, note = addressBook[selectedGate].loc.dim, addressBook[selectedGate].note
  1956.     local noteLength, noteA, noteB, m = #note
  1957.     if noteLength > 29 then                                                          --# Note
  1958.       local breakPoint = note:sub(25, 30):find(" ") + 23
  1959.       noteA = note:sub(1, breakPoint)
  1960.       noteB = note:sub(breakPoint + 2)
  1961.     end
  1962.     for i = 1, hardware.marquee do
  1963.       m = marquee[i]
  1964.       m.setTextScale(1)
  1965.       m.setBackgroundColor(black)
  1966.       m.clear()
  1967.       m.setCursorPos(1, 2)
  1968.       m.setTextColor(brown)
  1969.       m.write(dimension)
  1970.       m.setCursorPos(1, 4)
  1971.       m.setTextColor(white)
  1972.       if noteLength > 29 then
  1973.         m.write(noteA)
  1974.         m.setCursorPos(2, 5)
  1975.         m.write(noteB)
  1976.       else
  1977.         m.write(note)
  1978.       end
  1979.     end
  1980.   end
  1981. end
  1982.  
  1983. do
  1984.   local chevrons7LC = {
  1985.     [1] = { x = 22, y = 5 };  --# 1
  1986.     [2] = { x = 25, y = 8 };  --# 2
  1987.     [3] = { x = 25, y = 13 }; --# 3
  1988.     [4] = { x = 5, y = 13 };  --# 6
  1989.     [5] = { x = 5, y = 8 };   --# 7
  1990.     [6] = { x = 8, y = 5 };   --# 8
  1991.     [7] = { x = 15, y = 3 };  --# 0
  1992.   }
  1993.  
  1994.   local chevrons9LC = {
  1995.     [1] = { x = 22, y = 5 };  --# 1
  1996.     [2] = { x = 25, y = 8 };  --# 2
  1997.     [3] = { x = 25, y = 13 }; --# 3
  1998.     [4] = { x = 5, y = 13 };  --# 6
  1999.     [5] = { x = 5, y = 8 };   --# 7
  2000.     [6] = { x = 8, y = 5 };   --# 8
  2001.     [7] = { x = 22, y = 16 }; --# 4
  2002.     [8] = { x = 8, y = 16 };  --# 5
  2003.     [9] = { x = 15, y = 3 };  --# 0
  2004.   }
  2005.  
  2006.   local chevrons7SG = {
  2007.     [1] = { x = 22, y = 5 };  --# 1
  2008.     [2] = { x = 25, y = 8 };  --# 2
  2009.     [3] = { x = 25, y = 13 }; --# 3
  2010.     [4] = { x = 5, y = 13 };  --# 6
  2011.     [5] = { x = 5, y = 8 };   --# 7
  2012.     [6] = { x = 8, y = 5 };   --# 8
  2013.     [7] = { x = 15, y = 3 };  --# 0
  2014.   }
  2015.  
  2016.   local chevrons9SG = {
  2017.     [1] = { x = 22, y = 5 };  --# 1
  2018.     [2] = { x = 25, y = 8 };  --# 2
  2019.     [3] = { x = 25, y = 13 }; --# 3
  2020.     [4] = { x = 5, y = 13 };  --# 6
  2021.     [5] = { x = 5, y = 8 };   --# 7
  2022.     [6] = { x = 8, y = 5 };   --# 8
  2023.     [7] = { x = 22, y = 16 }; --# 4
  2024.     [8] = { x = 8, y = 16 };  --# 5
  2025.     [9] = { x = 15, y = 3 };  --# 0
  2026.   }
  2027.  
  2028.   local wormhole = {
  2029.     [1] = { x = 11, y = 5, line = "         " };
  2030.     [2] = { x = 10, y = 6, line = "           " };
  2031.     [3] = { x = 9, y = 7, line = "             " };
  2032.     [4] = { x = 8, y = 8, line = "               " };
  2033.     [5] = { x = 7, y = 9, line = "                 " };
  2034.     [6] = { x = 6, y = 10, line = "                   " };
  2035.     [7] = { x = 6, y = 11, line = "                   " };
  2036.     [8] = { x = 7, y = 12, line = "                 " };
  2037.     [9] = { x = 8, y = 13, line = "               " };
  2038.     [10] = { x = 9, y = 14, line = "             " };
  2039.     [11] = { x = 10, y = 15, line = "           " };
  2040.     [12] = { x = 11, y = 16, line = "         " };
  2041.   }
  2042.  
  2043.   local function displayWormhole()
  2044.     local bgColor, gm, w = gateStatus == "Connected" and blue or black
  2045.     for i = 1, hardware.gateMon do
  2046.       gm = gateMon[i]
  2047.       gm.setBackgroundColor(bgColor)
  2048.       for j = 1, 12 do
  2049.         w = wormhole[j]
  2050.         gm.setCursorPos(w.x, w.y)
  2051.         gm.write(w.line)
  2052.       end
  2053.     end
  2054.   end
  2055.  
  2056.   local function displayGlyph(i, chevron, addrLen, backSpace)
  2057.     if addrLen == 7 or addrLen == 9 or backSpace then
  2058.       local gm = gateMon[i]
  2059.       if lcGate then
  2060.         gm.setCursorPos(addrLen == 7 and chevrons7LC[chevron].x or chevrons9LC[chevron].x, addrLen == 7 and chevrons7LC[chevron].y or chevrons9LC[chevron].y)
  2061.       else
  2062.         if chevron > sgRemoteAddrLen then sgRemoteAddrLen = 9 end
  2063.         gm.setCursorPos((addrLen == 7 or sgRemoteAddrLen == 7) and chevrons7SG[chevron].x or chevrons9SG[chevron].x, (addrLen == 7 or sgRemoteAddrLen == 7) and chevrons7SG[chevron].y or chevrons9SG[chevron].y)
  2064.       end
  2065.       if backSpace then
  2066.         gm.write(" ")
  2067.       else
  2068.         gm.write(incomingAddress and incomingAddress:sub(chevron, chevron) or dialAddress:sub(chevron, chevron))
  2069.       end
  2070.     end
  2071.   end
  2072.  
  2073.   displayChevrons = function(init, backSpace)
  2074.     if hardware.gateMon == 0 or gateMonList then return end
  2075.     local gm
  2076.     if gateStatus == "Idle" then
  2077.       if not init then
  2078.         for i = 1, hardware.gateMon do
  2079.           gm = gateMon[i]
  2080.           gm.setBackgroundColor(silver)
  2081.           for j = 1, 9 do
  2082.             gm.setCursorPos(chevrons9LC[j].x, chevrons9LC[j].y)
  2083.             gm.write(" ")
  2084.           end
  2085.         end
  2086.         displayWormhole()
  2087.       end
  2088.     elseif gateStatus == "Connected" then
  2089.       local addrLen = incomingAddress and #incomingAddress or #dialAddress
  2090.       if init then
  2091.         if addrLen == 7 or addrLen == 9 then
  2092.           for i = 1, hardware.gateMon do
  2093.             gateMon[i].setBackgroundColor(orange)
  2094.             gateMon[i].setTextColor(black)
  2095.             for j = 1, sgRemoteAddrLen == 0 and addrLen or sgRemoteAddrLen do
  2096.               displayGlyph(i, j, addrLen)
  2097.             end
  2098.           end
  2099.         end
  2100.       else
  2101.         if addrLen == 7 and dialFromDHD then
  2102.           for i = 1, hardware.gateMon do
  2103.             gm = gateMon[i]
  2104.             gm.setBackgroundColor(silver)
  2105.             for j = 1, 9 do
  2106.               gm.setCursorPos(chevrons9LC[j].x, chevrons9LC[j].y)
  2107.               gm.write(" ")
  2108.             end
  2109.             gm.setBackgroundColor(orange)
  2110.             gm.setTextColor(black)
  2111.             for j = 1, sgRemoteAddrLen == 0 and addrLen or sgRemoteAddrLen do
  2112.               displayGlyph(i, j, addrLen)
  2113.             end
  2114.           end
  2115.         end
  2116.       end
  2117.       displayWormhole()
  2118.     elseif gateStatus == "Dialing" and backSpace then
  2119.       for i = 1, hardware.gateMon do
  2120.         gateMon[i].setBackgroundColor(silver)
  2121.         displayGlyph(i, chevronNumber + 1, 9, true)
  2122.       end
  2123.     elseif gateStatus ~= "Disconnecting" then
  2124.       if chevronNumber > 0 then
  2125.         local addrLen = incomingAddress and #incomingAddress or #dialAddress
  2126.         for i = 1, hardware.gateMon do
  2127.           gateMon[i].setBackgroundColor(orange)
  2128.           gateMon[i].setTextColor(black)
  2129.           if init then
  2130.             for j = 1, chevronNumber do
  2131.               displayGlyph(i, j, addrLen)
  2132.             end
  2133.           else
  2134.             displayGlyph(i, chevronNumber, addrLen)
  2135.           end
  2136.         end
  2137.       end
  2138.     end
  2139.   end
  2140. end
  2141.  
  2142. do
  2143.   local gateImage = {
  2144.     {},
  2145.     { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 128, 128, 128, 128, 128, },
  2146.     { 0, 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 128, 128, 128, 256, 128, 128, 128, 128, 128, 128, 128, },
  2147.     { 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, },
  2148.     { 0, 0, 0, 0, 0, 128, 128, 256, 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 128, 256, 128, 128, },
  2149.     { 0, 0, 0, 0, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 128, },
  2150.     { 0, 0, 0, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 128, },
  2151.     { 0, 0, 128, 128, 256, 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 128, 256, 128, 128, },
  2152.     { 0, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 128, },
  2153.     { 0, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, },
  2154.     { 0, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, },
  2155.     { 0, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 128, },
  2156.     { 0, 0, 128, 128, 256, 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 128, 256, 128, 128, },
  2157.     { 0, 0, 0, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 128, },
  2158.     { 0, 0, 0, 0, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 128, },
  2159.     { 0, 0, 0, 0, 0, 128, 128, 256, 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 128, 256, 128, 128, },
  2160.     { 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, },
  2161.     { 0, 0, 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, },
  2162.   }
  2163.  
  2164.   displayGate = function()
  2165.     if hardware.gateMon == 0 then return end
  2166.     local oldTerm = term.redirect(gateMon[1])
  2167.     paintutils.drawImage(gateImage, 1, 1)
  2168.     if hardware.gateMon > 1 then
  2169.       for i = 2, hardware.gateMon do
  2170.         term.redirect(gateMon[i])
  2171.         paintutils.drawImage(gateImage, 1, 1)
  2172.       end
  2173.     end
  2174.     term.redirect(oldTerm)
  2175.   end
  2176. end
  2177.  
  2178. displayConnectionTime = function()
  2179.   if validStates[runState][5] or secureStatus == "lockdown" then
  2180.     drawElement(42, 1, 9, 1, connectionTime > 0 and white or gray, black, connectionClock)
  2181.     if gettingInput then repositionCursor() end
  2182.   end
  2183.   if not timerMon[1] then return end
  2184.   for i = 1, hardware.timerMon do
  2185.     if connectionTime < 2 then
  2186.       timerMon[i].setCursorPos(1, 1)
  2187.       timerMon[i].setTextColor(connectionTime > 0 and green or gray)
  2188.       timerMon[i].write("Connect")
  2189.     end
  2190.     timerMon[i].setCursorPos(1, 2)
  2191.     timerMon[i].setTextColor(connectionTime > 0 and white or gray)
  2192.     timerMon[i].write(connectionClock)
  2193.   end
  2194. end
  2195.  
  2196. do
  2197.   local gateStatusColors = {
  2198.     Idle = { green, green };
  2199.     Dialing = { orange, orange };
  2200.     Connected = { sky, red };
  2201.     Disconnecting = { sky, orange };
  2202.   }
  2203.  
  2204.   displayStatusDetail = function()
  2205.     if gateStatus == "Idle" and secureStatus == "allclear" then
  2206.       if displayState == "list" then
  2207.         displayAddressBook()
  2208.       else
  2209.         displayNotes()
  2210.         return
  2211.       end
  2212.     end
  2213.     if hardware.marquee == 0 then return end
  2214.     local irisStateString, irisStateColor, irisPercentColor, irisPercentString = irisState and "Closed" or "Open", irisState and green or orange, irisPercent > 49 and green or (irisPercent < 25 and red or orange), tostring(irisPercent)
  2215.     local fuelPercentString, fuelColor, gName, line, gIndex, m = tostring(fuelPercent), fuelPercent > 24 and green or (fuelPercent < 6 and red or orange), thisGate, string.rep(" ", 57)
  2216.     if initMarquee then
  2217.       for i = 1, abCount do
  2218.         if addressBook[i].addr == thisGate then
  2219.           gName, gIndex = addressBook[i].name, i
  2220.           break
  2221.         end
  2222.       end
  2223.       local textX, outlineColor = math.floor((57 - #gName) / 2), secureStatus == "allclear" and (gIndex and assignColor(gIndex) or blue) or red
  2224.       for i = 1, hardware.marquee do
  2225.         m = marquee[i]
  2226.         m.setTextScale(0.5) --# 3 x 1 = 57 x 10
  2227.         m.setBackgroundColor(black)
  2228.         m.clear()
  2229.         m.setBackgroundColor(outlineColor)
  2230.         m.setTextColor(white)
  2231.         m.setCursorPos(1, 1)
  2232.         m.write(line)
  2233.         m.setCursorPos(1, 10)
  2234.         m.write(line)
  2235.         m.setCursorPos(textX, 1)
  2236.         m.write(gName)
  2237.         m.setBackgroundColor(secureStatus == "allclear" and green or red)
  2238.         m.setCursorPos(24, 9)
  2239.         m.write(secureStatus == "allclear" and " All Clear " or " LOCKDOWN ")
  2240.         m.setBackgroundColor(black)
  2241.         m.setTextColor(gray)
  2242.         m.setCursorPos(2, 3)
  2243.         m.write("This Gate                  Target")
  2244.         m.setCursorPos(2, 5)
  2245.         m.write("Iris Status                Gate Status")
  2246.         m.setCursorPos(2, 7)
  2247.         m.write("Fuel                       Direction")
  2248.         m.setTextColor(secureStatus == "allclear" and yellow or red)
  2249.         m.setCursorPos(15, 3)
  2250.         m.write(secureStatus == "allclear" and thisGate or "LOCKDOWN") --# thisGate
  2251.       end
  2252.       initMarquee = false
  2253.     end
  2254.     for i = 1, hardware.marquee do
  2255.       m = marquee[i]
  2256.       --# Column 1
  2257.       m.setBackgroundColor(black)
  2258.       m.setTextColor(irisStateColor)
  2259.       m.setCursorPos(15, 5)
  2260.       m.write(irisStateString .. "  ") --# iris status
  2261.       if lcGate then
  2262.         m.setTextColor(gray)
  2263.         m.write("/ ")
  2264.         m.setTextColor(irisPercentColor)
  2265.         m.write(irisPercentString) --# iris percent
  2266.         m.setTextColor(gray)
  2267.         m.write("%  ")
  2268.       end
  2269.       m.setTextColor(fuelColor)
  2270.       m.setCursorPos(15, 7)
  2271.       m.write(fuelPercentString) --# fuel percent
  2272.       m.setTextColor(gray)
  2273.       m.write("%  ")
  2274.       --# Column 2
  2275.       m.setTextColor(gateStatus == "Idle" and silver or gray)
  2276.       m.setCursorPos(42, 3)
  2277.       m.write((incomingAddress or dialAddress) .. "     ") --# target address
  2278.       if gateStatus == "Dialing" or gateStatus == "Connected" then
  2279.         m.setTextColor(secureStatus == "allclear" and yellow or orange)
  2280.         m.setCursorPos(42, 3)
  2281.         if incomingAddress then
  2282.           if lcGate then      --# LanteaCraft
  2283.             m.write(incomingAddress)
  2284.           else                --# SGCraft
  2285.             if gateStatus == "Connected" and chevronNumber == 0 then chevronNumber = #incomingAddress end
  2286.             if chevronNumber > 0 then marquee[i].write(incomingAddress:sub(1, chevronNumber)) end
  2287.             m.setTextColor(gray)
  2288.             m.write(incomingAddress:sub(chevronNumber + 1))
  2289.           end
  2290.         else
  2291.           if gateStatus == "Connected" and chevronNumber == 0 then chevronNumber = #dialAddress end
  2292.           if chevronNumber > 0 then marquee[i].write(dialAddress:sub(1, chevronNumber)) end
  2293.           m.setTextColor(gray)
  2294.           m.write(dialAddress:sub(chevronNumber + 1))
  2295.         end
  2296.       end
  2297.       m.setTextColor(secureStatus == "allclear" and (gateStatusColors[gateStatus][1] or red) or (gateStatusColors[gateStatus][2] or red))
  2298.       m.setCursorPos(42, 5)
  2299.       m.write(gateStatus .. "         ") --# gateStatus
  2300.       m.setTextColor(callDirection == "none" and silver or orange) --(secureStatus == "allclear" and orange or red))
  2301.       m.setCursorPos(42, 7)
  2302.       m.write(callDirection .. "    ") --# call direction      
  2303.     end
  2304.   end
  2305. end
  2306.  
  2307. do
  2308.   local function makeLongName(name) --# This spaces out a 7 character address
  2309.     return #name ~= 7 and name or table.concat({ name:sub(1, 1), " ", name:sub(2, 2), " ", name:sub(3, 3), " ", name:sub(4, 4), " ", name:sub(5, 5), " ", name:sub(6, 6), " ", name:sub(7) })
  2310.   end
  2311.  
  2312.   displayStatus = function()
  2313.     if gateStatus == "Idle" and secureStatus == "allclear" then
  2314.       if displayState == "list" then --# Display the local address when there is no other information to display
  2315.         if hardware.marquee > 0 then
  2316.           local longAddress, xPos, m = makeLongName(thisGate), #thisGate - 5
  2317.           for i = 1, hardware.marquee do
  2318.             m = marquee[i]
  2319.             m.setTextScale(2)
  2320.             m.setBackgroundColor(black)
  2321.             m.clear()
  2322.             m.setTextColor(cyan)
  2323.             m.setCursorPos(1, 1)
  2324.             m.write("Stargate")
  2325.             m.setTextColor(yellow)
  2326.             m.setCursorPos(xPos, 2)
  2327.             m.write(longAddress)
  2328.           end
  2329.         end
  2330.         displayAddressBook()
  2331.       else
  2332.         displayNotes()
  2333.       end
  2334.       return
  2335.     end
  2336.     if hardware.marquee > 0 then
  2337.       local longAddress = incomingAddress and makeLongName(incomingAddress) or makeLongName(dialAddress)
  2338.       local addrLen = incomingAddress and #incomingAddress or #dialAddress
  2339.       local tX, sBuffer, m = addrLen - 5, string.rep(" ", 9 - addrLen)
  2340.       for i = 1, hardware.marquee do
  2341.         m = marquee[i]
  2342.         m.setTextScale(2)
  2343.         m.setBackgroundColor(black)
  2344.         m.setCursorPos(1, 1)
  2345.       end
  2346.       if gateStatus == "Dialing" or gateStatus == "Paused" then
  2347.         if incomingAddress then --# incoming call
  2348.           for i = 1, hardware.marquee do
  2349.             m = marquee[i]
  2350.             m.setTextColor(secureStatus == "allclear" and sky or red)
  2351.             m.write(incomingAddress == "Wormhole" and "Incoming     " or "Incoming from ")
  2352.             m.setTextColor(secureStatus == "allclear" and yellow or orange)
  2353.             if lcGate then      --# LanteaCraft
  2354.               m.setCursorPos(4, 2)
  2355.               m.write(incomingAddress .. sBuffer)
  2356.             else                --# SGCraft
  2357.               m.setCursorPos(tX, 2)
  2358.               m.write(addrLen == 7 and longAddress:sub(1, chevronNumber * 2) or incomingAddress:sub(1, chevronNumber))
  2359.               m.setTextColor(gray)
  2360.               m.write(addrLen == 7 and longAddress:sub((chevronNumber * 2) + 1) or incomingAddress:sub(chevronNumber + 1))
  2361.             end
  2362.           end
  2363.         else                    --# outgoing call
  2364.           for i = 1, hardware.marquee do
  2365.             m = marquee[i]
  2366.             m.setTextColor(sky)
  2367.             m.write(gateStatus .. "  ")
  2368.             if dialAddress ~= "none" then
  2369.               m.setCursorPos(tX, 2)
  2370.               m.setTextColor(yellow)
  2371.               m.write(addrLen == 7 and longAddress:sub(1, chevronNumber * 2) or dialAddress:sub(1, chevronNumber))
  2372.               m.setTextColor(gray)
  2373.               m.write(addrLen == 7 and longAddress:sub((chevronNumber * 2) + 1) or dialAddress:sub(chevronNumber + 1))
  2374.             end
  2375.           end
  2376.         end
  2377.       elseif gateStatus == "Connected" then
  2378.         if incomingAddress then --# incoming connection
  2379.           for i = 1, hardware.marquee do
  2380.             m = marquee[i]
  2381.             m.setTextColor(secureStatus == "allclear" and sky or red)
  2382.             m.write(incomingAddress == "Wormhole" and "Incoming      " or "Incoming from ")
  2383.             m.setTextColor(secureStatus == "allclear" and yellow or orange)
  2384.             m.setCursorPos(tX, 2)
  2385.             m.write(longAddress)
  2386.           end
  2387.         else                    --# outgoing connection
  2388.           for i = 1, hardware.marquee do
  2389.             m = marquee[i]
  2390.             m.setTextColor(sky)
  2391.             if dialAddress == "none" then
  2392.               m.write("Connected   ")
  2393.             else
  2394.               m.write("Connected to")
  2395.               m.setTextColor(yellow)
  2396.               m.setCursorPos(tX, 2)
  2397.               m.write(longAddress)
  2398.             end
  2399.           end
  2400.         end
  2401.       elseif gateStatus == "Disconnecting" then
  2402.         for i = 1, hardware.marquee do
  2403.           m = marquee[i]
  2404.           m.clear()
  2405.           m.setTextColor(sky)
  2406.           m.write("Disconnecting")
  2407.         end
  2408.       end
  2409.     end
  2410.   end
  2411. end
  2412.  
  2413. do
  2414.   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; }
  2415.  
  2416.   drawElement = function(x, y, w, h, txtColor, bgColor, text)
  2417.     if type(x) == "number" and type(y) == "number" then
  2418.       if type(w) == "string" then           --# Switch (Neutral)
  2419.         x = math.floor(math.max(1, math.min(x, termX - 2))) --# Validate x coord
  2420.         y = math.floor(math.max(1, math.min(y, termY)))     --# Validate y coord
  2421.         term.setCursorPos(x, y)
  2422.         term.setBackgroundColor(green)
  2423.         term.write(" ")
  2424.         term.setBackgroundColor(gray)
  2425.         term.write("  ")
  2426.         term.setBackgroundColor(red)
  2427.         term.write(" ")
  2428.       elseif type(w) == "boolean" then      --# Switch (ON/OFF)
  2429.         x = math.floor(math.max(1, math.min(x, termX - 2))) --# Validate x coord
  2430.         y = math.floor(math.max(1, math.min(y, termY)))     --# Validate y coord
  2431.         term.setCursorPos(x, y)
  2432.         term.setBackgroundColor(w and green or gray)
  2433.         term.write("  ")
  2434.         term.setBackgroundColor(w and gray or red)
  2435.         term.write("  ")
  2436.       elseif type(w) == "number" and type(h) == "number" then --# Text and/or box/rectangle
  2437.         local sText = text and tostring(text) or "" --# Ensure text is a string
  2438.         w = math.floor(math.min(math.max(1, math.max(#sText, w)), termX)) --# Validate width
  2439.         h = math.floor(math.max(1, math.min(h, termY)))                   --# valid height
  2440.         x = math.floor(math.max(1, math.min(x, termX - w + 1)))           --# validate x coord
  2441.         y = math.floor(math.max(1, math.min(y, termY - h + 1)))           --# validate y coord
  2442.         local spacer = (w - #sText) / 2
  2443.         local txtLine = string.rep(" ", math.floor(spacer)) .. sText .. string.rep(" ", math.ceil(spacer))
  2444.         if type(txtColor) == "number" and validColors[txtColor] then term.setTextColor(txtColor) end    --# validate the text color
  2445.         if type(bgColor) == "number" and validColors[bgColor] then term.setBackgroundColor(bgColor) end --# validate the background color
  2446.         if h == 1 then                   --# if the height is 1 then...
  2447.           term.setCursorPos(x, y)        --# Position cursor
  2448.           term.write(txtLine)            --# Draw the single line of the 'element'
  2449.         else                             --# otherwise...
  2450.           local line, textRow = 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
  2451.           for i = y, y + h - 1 do        --# Loop through the height of the 'element' line by line (top to bottom)
  2452.             term.setCursorPos(x, i)      --# Position cursor
  2453.             term.write(i == textRow and txtLine or line) --# Draw 1 of h lines of the 'element' (box/rectangle/line-seg)
  2454.           end
  2455.         end
  2456.       end
  2457.     end
  2458.   end
  2459. end
  2460.  
  2461. drawRemoteIrisStatus = function()
  2462.   if validStates[runState][5] and gateStatus == "Connected" then
  2463.     local iColor = remoteIrisStatus == "open" and green or silver
  2464.     iColor = remoteIrisStatus == "closed" and red or iColor
  2465.     drawElement(termX - 2, 3, 1, 1, silver, gray, "[ ]")
  2466.     drawElement(termX - 1, 3, 1, 1, iColor, nil, "I")
  2467.     if gettingInput then repositionCursor() end
  2468.   end
  2469. end
  2470.  
  2471. local function drawSubheader()
  2472.   drawElement(1, 2, termX, 3, nil, gray) --# 'Status' area
  2473.   drawElement(3, 3, 1, 1, silver, nil, lcGate and "F/I:            Gate:" or "Fuel:           Gate:")
  2474.   --# Fuel, Iris, & Gate Status
  2475.   local fuelColor = fuelPercent > 24 and lime or orange
  2476.   fuelColor = fuelPercent < 6 and red or fuelColor
  2477.   drawElement(lcGate and 8 or 9, 3, 1, 1, fuelColor, gray, tostring(fuelPercent)) --# Fuel level
  2478.   if lcGate then
  2479.     local irisColor = irisPercent > 49 and lime or orange
  2480.     irisColor = irisPercent < 11 and red or irisColor
  2481.     term.setTextColor(silver)
  2482.     term.write("|")
  2483.     term.setTextColor(irisColor)
  2484.     term.write(tostring(irisPercent))
  2485.   end
  2486.   term.setTextColor(silver)
  2487.   term.write("%  ")
  2488.   local allStatusColors = { ["Idle"] = lime, ["Disconnecting"] = sky }
  2489.   local clearStatusColors = { ["Dialing"] = cyan, ["Paused"] = orange, ["Connected"] = orange }
  2490.   local txtColor = allStatusColors[gateStatus] or red
  2491.   if secureStatus == "allclear" and clearStatusColors[gateStatus] then
  2492.     txtColor = clearStatusColors[gateStatus]
  2493.   end
  2494.   term.setTextColor(txtColor)
  2495.   term.setCursorPos(25, 3)
  2496.   if (gateStatus == "Connected" or gateStatus == "Dialing") and dialAddress == "none" then --# incoming calls
  2497.     if incomingAddress then
  2498.       term.write("Incoming from ")
  2499.       term.setTextColor(secureStatus == "allclear" and yellow or orange)
  2500.       if lcGate or gateStatus == "Connected" then
  2501.         term.write(incomingAddress)
  2502.       else
  2503.         term.write(incomingAddress:sub(1, chevronNumber))
  2504.         term.setTextColor(silver)
  2505.         term.write(incomingAddress:sub(chevronNumber + 1))
  2506.       end
  2507.     else
  2508.       term.write(gateStatus)
  2509.     end
  2510.   else
  2511.     if gateStatus == "Connected" and dialAddress ~= "none" then --# outgoing calls and connection data
  2512.       term.write("Connected to ")
  2513.       term.setTextColor(yellow)
  2514.       term.write(dialAddress)
  2515.     else
  2516.       term.write(gateStatus)
  2517.     end
  2518.     if gateStatus == "Dialing" or gateStatus == "Paused" then --# dialing data
  2519.       term.setCursorPos(33, 3)
  2520.       term.setTextColor(yellow)
  2521.       term.write(dialAddress:sub(1, chevronNumber))
  2522.       term.setTextColor(silver)
  2523.       term.write(dialAddress:sub(chevronNumber + 1))
  2524.     end
  2525.   end
  2526.   drawRemoteIrisStatus()
  2527. end
  2528.  
  2529. local function drawHeader()
  2530.   local selected = false
  2531.   local bgColor = secureStatus == "allclear" and (dialStates[runState] or assignColor(currentEdit)) or red
  2532.   local headerText = currentEdit and (addressBook[currentEdit].name .. " (" .. addressBook[currentEdit].addr .. ")") or thisGate
  2533.   if dialStates[runState] then
  2534.     for i = 1, abCount do
  2535.       if addressBook[i].addr == thisGate then
  2536.         headerText = secureStatus == "allclear" and (addressBook[i].name .. " (" .. thisGate .. ")") or addressBook[i].name
  2537.         bgColor = secureStatus == "allclear" and assignColor(i) or red
  2538.         selected = true
  2539.         break
  2540.       end
  2541.     end
  2542.   end
  2543.   if not selected and secureStatus == "lockdown" then headerText = "LOCKDOWN" end
  2544.   drawElement(1, 1, termX, 1, white, bgColor, headerText)     --# Title
  2545.   if validStates[runState][5] or secureStatus == "lockdown" then
  2546.     drawElement(42, 1, 9, 1, connectionTime > 0 and white or gray, black, connectionClock) --# connection timer
  2547.   end
  2548.   if dialStates[runState] and secureStatus == "allclear" then --# Dial mode gets the -DHD- menu
  2549.     drawElement(2, 1, 5, 1, white, nil, "-DHD-")              --# Drop down menu button
  2550.   end
  2551. end
  2552.  
  2553. local function drawFullHeader(drawSub)
  2554.   drawHeader()
  2555.   if drawSub then drawSubheader() end
  2556. end
  2557.  
  2558. local function drawLogHeader()
  2559.   drawElement(1, 1, termX, 1, nil, yellow) --# yellow header                                                      
  2560.   drawElement(1, 2, termX, 3, nil, gray)   --# sub-header body
  2561.   local title = thisGate
  2562.   for i = 1, abCount do
  2563.     if addressBook[i].addr == thisGate then
  2564.       title = addressBook[i].name
  2565.       break
  2566.     end
  2567.   end
  2568.   drawElement(((termX - #title) / 2) - 3, 1, 1, 1, yellow, nil, " " .. title .. " Logs ") --# Header text
  2569.   drawElement(2, 2, 1, 1, silver, nil, "Last Call:") --# Last call label
  2570.   drawElement(2, 4, 25, 1, nil, nil, "Day   Time      Vector    Address") --# Column labels
  2571.   drawElement(termX - 6, 2, 7, 1, red, silver, " Close ") --# Close button
  2572.   drawElement(termX - 6, 4, 7, 1, callHistory[1] and orange or gray, nil, " Clear ") --# Clear logs button
  2573.   drawElement(1, 3, 37, 1, nil, black)               --# lastCall bg
  2574.   if lastCall then                                   --# Last call
  2575.     local callDir = lastCall:sub(lastCall:find("<") + 1, lastCall:find(">") - 1)
  2576.     drawElement(2, 3, 1, 1, (callDir == "Inbound" or callDir == "Incoming") and silver or ((callDir == "Outgoing" or callDir == "Outbound") and gray or red), nil, lastCall:sub(1, lastCall:find("@") - 2)) --# Last call day
  2577.     drawElement(8, 3, 1, 1, nil, nil, lastCall:sub(lastCall:find("@") + 2, lastCall:find("M"))) --# Last call time
  2578.     drawElement(18, 3, 1, 1, nil, nil, callDir)      --# Last call direciton
  2579.     drawElement(28, 3, 1, 1, (callDir == "Inbound" or callDir == "Incoming") and sky or ((callDir == "Outgoing" or callDir == "Outbound") and cyan or orange), nil, lastCall:sub(lastCall:find(">") + 2)) --# Last call address
  2580.   else                                               --# No last call
  2581.     drawElement(2, 3, 1, 1, gray, nil, "none  none      none      none")
  2582.   end
  2583.   drawLogScreen()
  2584. end
  2585.  
  2586. local function drawSecureUI(mon, backSpace)
  2587.   if hardware.marquee > 0 then
  2588.     if dhdSettings.detailedMarquee then
  2589.       if mon then initMarquee = true end
  2590.       displayStatusDetail()
  2591.     else
  2592.       if incomingAddress then
  2593.         displayStatus()
  2594.       else
  2595.         for i = 1, hardware.marquee do
  2596.           marquee[i].setBackgroundColor(black)
  2597.           marquee[i].setTextColor(red)
  2598.           marquee[i].setTextScale(2)
  2599.           marquee[i].clear()
  2600.           marquee[i].setCursorPos(1, 1)
  2601.           marquee[i].write("!! LOCKDOWN !!")
  2602.         end
  2603.       end
  2604.     end
  2605.   end
  2606.   if mon then
  2607.     displayChevrons(nil, backSpace)
  2608.     if not backSpace then
  2609.       if hardware.listMon > 0 then
  2610.         for i = 1, hardware.listMon do
  2611.           listMon[i].setBackgroundColor(black)
  2612.           listMon[i].setTextColor(red)
  2613.           listMon[i].clear()
  2614.           listMon[i].setCursorPos(1, 5)
  2615.           listMon[i].write("!! LOCKDOWN !!")
  2616.         end
  2617.       end
  2618.     end
  2619.   end
  2620.   drawFullHeader(true)
  2621.   drawElement((termX / 2) - 7, 9, 1, 1, red, black, "!! LOCKDOWN !!")
  2622.   drawElement((termX / 2) - 10, 15, 1, 1, gray, nil, "password:")
  2623. end
  2624.  
  2625. local function drawAddrBookButton()
  2626.   drawElement(43, 13, 1, 1, gateChange and lime or white, blue, "AddrBook ")
  2627. end
  2628.  
  2629. local function drawControlUI()
  2630.   local iColor = irisStatus == "Offline" and silver or white
  2631.   if waitingForIris then
  2632.     iColor = irisStatus == "Opening" and orange or green
  2633.   end
  2634.   drawElement(42, 6, 10, 11, nil, gray)                                      --# Control UI menu body
  2635.   drawElement(43, 7, 1, 1, iColor, sky, "Iris   ")                           --# Iris
  2636.   drawElement(termX - 1, 7, 2, 1, nil, irisState and green or orange)        --# Iris pip
  2637.   drawElement(43, 9, 1, 1, black, orange, "END Call ")                       --# endCall
  2638.   drawElement(43, 11, 1, 1, orange, red, "LOCKDOWN ")                        --# Lockdown
  2639.   drawElement(43, 13, 1, 1, gateChange and lime or white, blue, "AddrBook ") --# Address Book
  2640.   drawElement(43, 15, 1, 1, white, green, "New Gate ")                       --# New Gate
  2641. end
  2642.  
  2643. local function drawNaviUI()
  2644.   pNum = tostring(gatePage) .. " of " .. tostring(gatePages)
  2645.   drawElement((termX / 2) - 15, termY, 1, 1, gray, black, gatePage > 1 and "<< <" or "    ")
  2646.   drawElement((termX / 2) + 3, termY, 1, 1, nil, nil, gatePage < gatePages and "> >>" or "    ")
  2647.   drawElement(((termX / 2) - 3) - (#pNum / 2) - 2, termY, 1, 1, runState == "goPage" and gray or silver, nil, " " .. pNum .. " ")
  2648. end
  2649.  
  2650. local function drawAddressBook() --# Gate Address Book
  2651.   local xPos, yPos, magicNumber = 2, 6, ((gatePage - 1) * 17) + gatePage
  2652.   local txtColor, bgColor, addr, highlight
  2653.   for i = magicNumber, math.min(abCount, magicNumber + 17) do
  2654.     addr = addressBook[i].addr
  2655.     highlight = (addr == dialAddress or addr:sub(1, 7) == dialAddress:sub(1, 7) or addr == incomingAddress or addr:sub(1, 7) == tostring(incomingAddress):sub(1, 7))
  2656.     txtColor = (thisGate == addr or thisGate:sub(1, 7) == addr or thisGate == addr:sub(1, 7)) and silver or (highlight and black or white)
  2657.     bgColor = thisGate == addr and gray or assignColor(i)
  2658.     drawElement(xPos, yPos, 12, 1, txtColor, bgColor, addressBook[i].name) --# Button
  2659.     yPos = yPos + 2
  2660.     if yPos > 16 then yPos = 6 xPos = xPos + 13 end
  2661.   end
  2662. end
  2663.  
  2664. local function drawHelpScreen()
  2665.   clearScreen(white)                                                --# Body
  2666.   drawElement(1, 1, termX, 1, nil, cyan)                            --# Header
  2667.   drawElement((termX / 2) - 4, 1, 1, 1, cyan, gray, " ccDHD Help ") --# Header text
  2668.   drawElement(1, termY, termX, 1)                                   --# Footer
  2669.   drawElement(2, termY, 1, 1, silver, nil, "ccDHD ver. " .. ccDHDVer .. "     cc# " .. thisCC) --# Footer text
  2670.   drawElement(termX - 1 - #ccLabel, termY, 1, 1, nil, nil, ccLabel)
  2671.   drawElement(1, 2, termX, 1)                                       --# Header/Separator
  2672.   drawElement(termX - 6, 2, 7, 1, red, silver, " Close ")           --# Close button
  2673.   drawElement(1, 3, termX, 1, nil, black)
  2674.   drawElement(1, termY - 1, termX, 1)
  2675.   drawElement(1, 4, 14, termY - 5, nil, gray)                       --# draw help left pane
  2676.   drawElement(2, 5, 1, 1, white, nil, "Main Screen ")
  2677.   drawElement(2, 10, 1, 1, nil, nil, "AddrBook Btn")
  2678.   drawElement(2, 14, 1, 1, nil, nil, "Log Screen  ")
  2679.   drawElement(2, 6, 1, 1, silver, nil, "LEFT click")
  2680.   drawElement(2, 7, 1, 1, nil, nil, "RIGHT click")
  2681.   drawElement(2, 8, 1, 1, nil, nil, "MIDDLE click")
  2682.   drawElement(2, 11, 1, 1, nil, nil, "LEFT click")
  2683.   drawElement(2, 12, 1, 1, nil, nil, "RIGHT click")
  2684.   drawElement(2, 15, 1, 1, nil, nil, "LEFT click")
  2685.   drawElement(2, 16, 1, 1, nil, nil, "RIGHT click")
  2686.   drawElement(16, 6, 1, 1, nil, white, lcGate and "an address to dial / pause" or "an address to dial")
  2687.   drawElement(16, 7, 1, 1, nil, nil, "an address to view or edit details")
  2688.   drawElement(16, 8, 1, 1, nil, nil, "an address to delete")
  2689.   drawElement(16, 11, 1, 1, nil, nil, "to import/export/save address book")
  2690.   drawElement(16, 12, 1, 1, nil, nil, "to quick-save address book")
  2691.   drawElement(16, 15, 1, 1, nil, nil, "an address to dial")
  2692.   drawElement(16, 16, 1, 1, nil, nil, "an address to add to address book")
  2693. end
  2694.  
  2695. do
  2696.   local labels = {
  2697.     "Change pass:";
  2698.     { "Biolock Login", "This Computer..." };
  2699.     { "Bio:", "CC Label:" };
  2700.     { "Bio Login Lvl", "CC#/Gate:" };
  2701.     { "Bio Func. Lvl", "Space:" };
  2702.     "Sync:";
  2703.     "Detailed Marquee";
  2704.     "Call Logging";
  2705.     "Incoming Iris";
  2706.     "END Call Iris";
  2707.   }
  2708.  
  2709.   local function drawSettingsData()
  2710.     --# Settings 1st column
  2711.     if hardware.bio > 0 then
  2712.       drawElement(22, 10, dhdSettings.bio.lock)   --# Fistprint Authentication (Bioscanner) - LOGIN
  2713.       if dhdSettings.bio.func == "none" then      --# Bioscanner function (Iris / Lockdown)
  2714.         drawElement(7, 12, 1, 1, silver, black, "No Function")
  2715.         drawElement(22, 12, "MID")
  2716.       elseif dhdSettings.bio.func == "iris" then
  2717.         drawElement(7, 12, 1, 1, green, black, "Iris       ")
  2718.         drawElement(22, 12, true)
  2719.       elseif dhdSettings.bio.func == "lock" then
  2720.         drawElement(7, 12, 1, 1, red, black, "Lockdown   ")
  2721.         drawElement(22, 12, false)
  2722.       end
  2723.       drawElement(19 + dhdSettings.bio.auth, 14, 1, 1, black, silver, tostring(dhdSettings.bio.auth)) --# Login authorization level
  2724.       drawElement(19 + dhdSettings.bio.fAuth, 16, 1, 1, nil, nil, tostring(dhdSettings.bio.fAuth))    --# Bio-Function authorization level
  2725.     else
  2726.       local gateLiaisonString = tostring(dhdSettings.gate)
  2727.       drawElement(12, 12, 1, 1, white, black, ccLabel)
  2728.       drawElement(12, 14, 1, 1, silver, nil, thisCC .. "/" .. gateLiaisonString .. " (     )")
  2729.       local wifi = (hardware.modemSide == "none" and hardware.wifiSide ~= "none")
  2730.       drawElement(15 + #thisCC + #gateLiaisonString, 14, 1, 1, wifi and orange or green, nil, wifi and "Wi-Fi" or "Wired")
  2731.       local drivespace, diskspace = tostring(fs.getFreeSpace("/")), fs.exists("/disk") and tostring(fs.getFreeSpace("/disk")) or 0
  2732.       local spaceline = drivespace .. "/" .. diskspace
  2733.       drawElement(12, 16, 1, 1, silver, nil, #spaceline < 16 and spaceline or drivespace)
  2734.     end
  2735.     --# Settings 2nd column
  2736.     drawElement(34, 6, 1, 1, type(dhdSettings.sync) == "string" and orange or (dhdSettings.sync and green or red), black, type(dhdSettings.sync) == "string" and "Dial ONLY" or (dhdSettings.sync and "Dial & Sync" or "OFF")) --# Sync setting
  2737.     drawElement(46, 6, dhdSettings.sync)             --# Sync
  2738.     drawElement(46, 10, dhdSettings.detailedMarquee) --# Highlight gate
  2739.     drawElement(46, 12, dhdSettings.logs)            --# Call logging
  2740.     drawElement(46, 14, dhdSettings.incomingIris)    --# Incoming Iris auto-close
  2741.     drawElement(46, 16, dhdSettings.ecIris)          --# endCall Iris auto-open
  2742.   end
  2743.  
  2744.   drawSettingsScreen = function()
  2745.     local gName
  2746.     for i = 1, abCount do
  2747.       if addressBook[i].addr == thisGate then gName = addressBook[i].name break end
  2748.     end
  2749.     drawElement(1, 1, termX, 1, nil, sky)                              --# Header
  2750.     drawElement(1, 2, termX, 3, nil, gray)                             --# Sub-Header
  2751.     drawElement(1, 8, termX, 1)                                        --# Separator
  2752.     drawElement(gName and (((termX - #gName) / 2) - 5) or (((termX - #thisGate) / 2) - 5), 1, 1, 1, sky, nil, " " .. (gName or thisGate) .. " Settings ") --# Title
  2753.     drawElement(1, termY, termX, 1, silver, nil, "ccDHD " .. ccDHDVer) --# Footer / Lowline
  2754.     drawElement(gName and (((termX - #gName) / 2) - (1 + (#thisGate / 2))) or (((termX - 5) / 2) - (2 + (#thisGate / 2))), 3, 1, 1, gName and cyan or silver, nil, gName or "This Gate:") --# Header (This Gate:)
  2755.     term.setTextColor(cyan)
  2756.     term.write(gName and "  (" .. thisGate .. ")" or " " .. thisGate)  --# Header (thisGate)
  2757.     drawElement(45, 2, 7, 1, red, silver, " Close ")                   --# Close button
  2758.     drawElement(45, 4, 7, 1, configChange and lime or gray, nil, " Save  ") --# Save button
  2759.     local xOffset, yOffset = 2, 6
  2760.     term.setTextColor(gray)
  2761.     term.setBackgroundColor(black)
  2762.     for i = 1, #labels do
  2763.       drawElement(xOffset, yOffset, 1, 1, nil, nil, (xOffset == 2 and yOffset > 9 and yOffset < 17) and (hardware.bio > 0 and labels[i][1] or labels[i][2]) or labels[i])
  2764.       yOffset = yOffset + 2
  2765.       if yOffset == 8 then yOffset = 10 end
  2766.       if yOffset == 18 and xOffset == 2 then
  2767.         xOffset, yOffset = 28, 6
  2768.       end
  2769.     end
  2770.     if hardware.bio > 0 then
  2771.       drawElement(19, 14, 1, 1, nil, nil, "|-----|")   --# Biolock login level
  2772.       drawElement(19, 16, 1, 1, nil, nil, "|-----|")   --# Biolock iris/lockdown level
  2773.     end
  2774.     drawElement(16, 6, 1, 1, orange, nil, "*********") --# password
  2775.     drawSettingsData()
  2776.   end
  2777. end
  2778.  
  2779. local function drawPopUp()
  2780.   if runState == "clearLogs" then
  2781.     drawElement((termX / 2) - 5, (termY / 2) - 2, 13, 1, black, yellow, "Clear Logs?")
  2782.     drawElement((termX / 2) - 5, (termY / 2) - 1, 13, 3, nil, gray)
  2783.     drawElement((termX / 2) - 4, termY / 2, 5, 1, nil, green, "YES")
  2784.     drawElement((termX / 2) + 2, termY / 2, 5, 1, nil, red, "N O")
  2785.   elseif runState == "goPage" or runState == "goLogPage" then
  2786.     local a = runState == "goLogPage" and 4 or 8
  2787.     local b = runState == "goLogPage" and 3 or 7
  2788.     drawElement((termX / 2) - a, termY - 3, 8, 1, white, gray, " :Page: ")
  2789.     drawElement((termX / 2) - a, termY - 2, 8, 2)
  2790.     drawElement((termX / 2) - b, termY - 2, 6, 1, nil, black) --# input area bg
  2791.   elseif runState == "importExport" then
  2792.     drawElement((termX / 2) - 13, (termY / 2) - 1, 19, 1, white, blue, "Address Book Mgt.")
  2793.     drawElement((termX / 2) - 13, termY / 2, 19, 7, nil, gray)
  2794.     if fs.exists("/disk/data/DHDgates") then
  2795.       drawElement((termX / 2) - 12, (termY / 2) + 1, 8, 1, black, yellow, "Import")
  2796.     else
  2797.       drawElement((termX / 2) - 12, (termY / 2) + 1, 8, 1, gray, silver, "Import")
  2798.     end
  2799.     if fs.exists("/disk") then
  2800.       drawElement((termX / 2) - 3, (termY / 2) + 1, 8, 1, black, orange, "Export")
  2801.     else
  2802.       drawElement((termX / 2) - 3, (termY / 2) + 1, 8, 1, gray, silver, "Export")
  2803.     end
  2804.     if gateChange then
  2805.       drawElement((termX / 2) - 12, (termY / 2) + 3, 8, 1, white, sky, "Load")
  2806.       drawElement((termX / 2) - 3, (termY / 2) + 3, 8, 1, white, green, "Save")
  2807.     else
  2808.       drawElement((termX / 2) - 12, (termY / 2) + 3, 8, 1, gray, silver, "Load")
  2809.       drawElement((termX / 2) - 3, (termY / 2) + 3, 8, 1, gray, silver, "Save")
  2810.     end
  2811.     drawElement((termX / 2) - 7, (termY / 2) + 5, 7, 1, white, red, "CLOSE")
  2812.   elseif runState == "newPassword" then
  2813.     drawElement((termX / 2) - 7, 6, 1, 1, white, blue, "  Change  Pass  ")
  2814.     drawElement((termX / 2) - 7, 7, 1, 1, black, silver, "  Lockdown P/W  ")
  2815.     drawElement((termX / 2) - 7, 8, 16, 4)               --# body
  2816.     drawElement((termX / 2) - 7, 9, 1, 1, nil, nil, "  I r i s  P/W  ")
  2817.     drawElement((termX / 2) - 6, 8, 14, 1, yellow, gray, "Select") --# Lockdown PW
  2818.     drawElement((termX / 2) - 6, 10, 14, 1, nil, nil, "Select")    --# Iris PW
  2819.     drawElement((termX / 2) - 3, 11, 1, 1, white, orange, " Cancel ")
  2820.   elseif runState == "newLockPass" then
  2821.     drawElement((termX / 2) - 7, 6, 1, 1, white, blue, "  Lockdown P/W  ")
  2822.     drawElement((termX / 2) - 7, 7, 1, 1, black, silver, "  OLD Password  ")
  2823.     drawElement((termX / 2) - 7, 9, 1, 1, nil, nil, "  NEW Password  ")
  2824.     drawElement((termX / 2) - 6, 8, 14, 1, nil, black)   --# oldPass input area bg
  2825.     drawElement((termX / 2) - 6, 10, 14, 1)              --# newPass input area bg
  2826.     drawElement((termX / 2) - 3, 11, 1, 1, white, orange, " Cancel ")
  2827.   elseif runState == "newIrisPass" then
  2828.     drawElement((termX / 2) - 7, 6, 1, 1, white, blue, "  I r i s  P/W  ")
  2829.     drawElement((termX / 2) - 7, 7, 1, 1, black, silver, "  OLD Password  ")
  2830.     drawElement((termX / 2) - 7, 9, 1, 1, nil, nil, "  NEW Password  ")
  2831.     drawElement((termX / 2) - 6, 8, 14, 1, nil, black)   --# oldPass input area bg
  2832.     drawElement((termX / 2) - 6, 10, 14, 1)              --# newPass input area bg
  2833.     drawElement((termX / 2) - 3, 11, 1, 1, white, orange, " Cancel ")
  2834.   elseif runState == "remotePass" then
  2835.     drawElement((termX / 2) - 8, 10, 16, 1, silver, gray, "Remote  Pass")
  2836.     drawElement((termX / 2) - 8, 11, 16, 2, nil, nil)    --# body
  2837.     drawElement((termX / 2) - 7, 11, 14, 1, nil, silver) --# input area
  2838.   elseif runState == "exodus" then
  2839.     if gateChange and not configChange then
  2840.       drawElement((termX / 2) - 13, (termY / 2) - 1, 19, 1, white, blue, "Save Addr Book?")
  2841.     elseif configChange and not gateChange then
  2842.       drawElement((termX / 2) - 13, (termY / 2) - 1, 19, 1, white, blue, "Save Settings?")
  2843.     else
  2844.       drawElement((termX / 2) - 13, (termY / 2) - 1, 19, 1, white, blue, "Save Gates & Cfg?")
  2845.     end
  2846.     drawElement((termX / 2) - 13, termY / 2, 19, 3, nil, gray)
  2847.     drawElement((termX / 2) - 12, (termY / 2) + 1, 1, 1, nil, green, "  Save  ")
  2848.     drawElement((termX / 2) - 3, (termY / 2) + 1, 1, 1, nil, orange, "  Quit  ")
  2849.   end
  2850. end
  2851.  
  2852. local function drawRatingList(rating)
  2853.   local txtColor
  2854.   drawElement(18, 6, 1, 1, white, assignColor(currentEdit), "   Classification   ")
  2855.   drawElement(18, 7, 20, 10, nil, gray)                   --# menu body
  2856.   for k, v in pairs(classifications) do
  2857.     if rating == k then
  2858.       txtColor = rating == "U" and white or v.color
  2859.       drawElement(37, v.order + 7, 1, 1, nil, v.color) --# selected rating color pip
  2860.     else
  2861.       txtColor = silver
  2862.     end
  2863.     drawElement(18, v.order + 7, 1, 1, nil, v.color)   --# color pip
  2864.     drawElement(20, v.order + 7, 1, 1, txtColor, gray, v.label)
  2865.   end
  2866. end
  2867.  
  2868. local function drawMenu()
  2869.   menuState = true
  2870.   drawElement(2, 1, 5, 1, white, black, "_DHD_")
  2871.   drawElement(2, 2, 1, 9, nil, gray)   --# dark gray line along left side of menu
  2872.   drawElement(2, 3, 1, 1, nil, sky)    --# Settings pip
  2873.   drawElement(2, 5, 1, 1, nil, yellow) --# Logs pip
  2874.   drawElement(2, 7, 1, 1, nil, cyan)   --# Help pip
  2875.   drawElement(2, 9, 1, 1, nil, red)    --# Exit pip
  2876.   drawElement(3, 2, 9, 9, nil, silver) --# menu body
  2877.   drawElement(3, 3, 1, 1, gray, nil, "Settings")
  2878.   drawElement(3, 5, 1, 1, nil, nil, "View Logs")
  2879.   drawElement(3, 7, 1, 1, nil, nil, "DHD Help")
  2880.   drawElement(3, 9, 1, 1, nil, nil, "  EXIT")
  2881. end
  2882.  
  2883. local function clearMenu()
  2884.   menuState = false
  2885.   drawElement(2, 5, 11, 6, nil, black) --# clear menu
  2886.   drawElement(2, 1, 5, 1, white, nil, "-DHD-") --# Drop down menu button
  2887.   drawSubheader()
  2888.   drawAddressBook()
  2889. end
  2890.  
  2891. drawCLI = function(mon, init, backSpace)
  2892.   --# Terminal output
  2893.   if secureStatus == "lockdown" then
  2894.     drawSecureUI(mon, backSpace)
  2895.     if gettingInput then repositionCursor() end
  2896.   else
  2897.     if menuState then
  2898.       if fullRedraw then
  2899.         validStates[runState][3]()
  2900.         fullRedraw = false
  2901.       elseif incomingAddress or dialAddress ~= "none" then
  2902.         drawSubheader()
  2903.         if chevronNumber == 0 then drawAddressBook() end
  2904.       end
  2905.       drawMenu()
  2906.     else
  2907.       validStates[runState][3]()
  2908.       if gettingInput then repositionCursor() end
  2909.     end
  2910.     --# Monitor output
  2911.     if mon then updateDisplays(init, backSpace) end
  2912.   end
  2913. end
  2914.  
  2915. drawLogScreen = function()
  2916.   if logPage == logPages and logPages > 1 then drawElement(1, 5, termX, termY - 4, nil, black) end --# clear lower screen
  2917.   drawElement(1, termY, termX, 1, nil, gray)      --# Footer
  2918.   if callHistory[1] then                          --# Logs to display
  2919.     --# populate footer
  2920.     drawElement(11, termY, 1, 1, silver, nil, "<< <                    > >>")
  2921.     local lPage, lPages = tostring(logPage), tostring(logPages)
  2922.     drawElement((termX / 2) - (((#lPage + #lPages) / 2) + 2), termY, 1, 1, nil, nil, lPage .. " of " .. lPages .. "  ")
  2923.     --# end of footer
  2924.     term.setBackgroundColor(black)
  2925.     local currentEntry = ((logPage - 1) * 11) + 1 --# Set the first entry to show (based on page number)
  2926.     local callDay, callTime, callDir, callAddr, caLen, abMatch, addr
  2927.     for i = currentEntry, math.min(currentEntry + 10, #callHistory) do --# Display logs
  2928.       callDay = callHistory[i]:sub(1, callHistory[i]:find("@") - 2)
  2929.       callTime = callHistory[i]:sub(callHistory[i]:find("@") + 2, callHistory[i]:find("M"))
  2930.       callDir = callHistory[i]:sub(callHistory[i]:find("<") + 1, callHistory[i]:find(">") - 1)
  2931.       callAddr = callHistory[i]:sub(callHistory[i]:find(">") + 2)
  2932.       caLen = #callAddr
  2933.       drawElement(2, i - currentEntry + 6, 1, 1, (caLen == 7 or caLen == 9) and ((callDir == "Inbound" or callDir == "Incoming") and silver or ((callDir == "Outgoing" or callDir == "Outbound") and gray or red)) or red, nil, callDay .. "   ")
  2934.       drawElement(8, i - currentEntry + 6, 1, 1, nil, nil, callTime .. " ")
  2935.       drawElement(18, i - currentEntry + 6, 1, 1, nil, nil, callDir .. " ")
  2936.       drawElement(28, i - currentEntry + 6, 1, 1, (caLen == 7 or caLen == 9) and ((callDir == "Inbound" or callDir == "Incoming") and sky or ((callDir == "Outgoing" or callDir == "Outbound") and cyan or orange)) or orange, nil, callAddr .. string.rep(" ", 9 - #callAddr))
  2937.       abMatch = false
  2938.       for j = 1, abCount do
  2939.         addr = addressBook[j].addr
  2940.         if addr == callAddr or addr:sub(1, 7) == callAddr or addr == callAddr:sub(1, 7) then
  2941.           drawElement(39, i - currentEntry + 6, 1, 1, (callDir == "Inbound" or callDir == "Incoming") and silver or ((callDir == "Outgoing" or callDir == "Outbound") and gray or red), nil, addressBook[j].name .. string.rep(" ", 12 - #addressBook[j].name))
  2942.           abMatch = true
  2943.           break
  2944.         end
  2945.       end
  2946.       if not abMatch then drawElement(39, i - currentEntry + 6, 12, 1) end
  2947.     end
  2948.   else                                            --# No logs to display
  2949.     drawElement(2, 6, 1, 1, gray, black, "No Logs")
  2950.   end
  2951. end
  2952.  
  2953. local function drawGateData()
  2954.   drawHeader()
  2955.   local name, addr, note, dim, rating = addressBook[currentEdit].name, addressBook[currentEdit].addr, addressBook[currentEdit].note, addressBook[currentEdit].loc.dim, assignRating(currentEdit)
  2956.   if addr == thisGate or addr:sub(1, 7) == thisGate or addr == thisGate:sub(1, 7) then
  2957.     drawElement(termX - 14, 8, 1, 1, gray, black, "< This Gate >")
  2958.   elseif addr == dialAddress or addr:sub(1, 7) == dialAddress or addr == dialAddress:sub(1, 7) then
  2959.     drawElement(termX - 14, 8, 1, 1, gray, black, "< Target >   ")
  2960.   elseif incomingAddress and (addr == incomingAddress or addr:sub(1, 7) == incomingAddress or addr == incomingAddress:sub(1, 7)) then
  2961.     drawElement(termX - 14, 8, 1, 1, gray, black, "< Incoming > ")
  2962.   else
  2963.     drawElement(termX - 14, 8, 13, 1, nil, black)
  2964.   end
  2965.   local xStr, yStr, zStr, ceStr = tostring(addressBook[currentEdit].loc.x), tostring(addressBook[currentEdit].loc.y), tostring(addressBook[currentEdit].loc.z), tostring(currentEdit)
  2966.   drawElement(11, 6, 1, 1, cyan, nil, name .. string.rep(" ", 12 - #name))   --# Name
  2967.   drawElement(11, 8, 1, 1, yellow, nil, addr .. "  ")                        --# Address
  2968.   drawElement(termX - 6, 6, 1, 1, white, nil, ceStr .. string.rep(" ", 5 - #ceStr)) --# Position in address book
  2969.   drawElement(8, 10, 1, 1, nil, nil, note .. string.rep(" ", 43 - #note))    --# Note(s)
  2970.   drawElement(18, 12, 1, 1, assignColor(currentEdit), nil, rating .. string.rep(" ", 20 - #rating)) --# Classification
  2971.   drawElement(13, 14, 1, 1, brown, nil, dim .. string.rep(" ", 19 - #dim))   --# Dimension
  2972.   drawElement(15, 16, 1, 1, silver, nil, xStr .. string.rep(" ", 9 - #xStr)) --# X
  2973.   drawElement(28, 16, 1, 1, nil, nil, yStr .. string.rep(" ", 9 - #yStr))    --# Y
  2974.   drawElement(41, 16, 1, 1, nil, nil, zStr .. string.rep(" ", 9 - #zStr))    --# Z
  2975.   if not lcGate then
  2976.     drawElement(31, 6, addressBook[currentEdit].iris)
  2977.     drawElement(31, 8, addressBook[currentEdit].callDrop)
  2978.   end
  2979. end
  2980.  
  2981. local function drawGateLabels()
  2982.   drawElement(1, 5, termX, termY - 4, nil, black) --# clear lower screen
  2983.   drawElement(2, 6, 1, 1, gray, nil, lcGate and "Name:                                    #" or "Name:                  Iris:             #")
  2984.   drawElement(2, 8, 1, 1, nil, nil, lcGate and "Address:" or "Address:               Drop:")
  2985.   drawElement(2, 10, 1, 1, nil, nil, "Note:")
  2986.   drawElement(2, 12, 1, 1, nil, nil, "Classification:")
  2987.   drawElement(2, 14, 10, 1, nil, nil, "Dimension:")
  2988.   drawElement(2, 16, 1, 1, nil, nil, "Coords:   x:           y:           z:")
  2989.   drawElement((termX / 2) - 8, termY - 1, 6, 1, gateChange and green or silver, gray, " Save ")
  2990.   drawElement((termX / 2) + 1, termY - 1, 7, 1, red, nil, " Close ")
  2991.   drawGateData()
  2992. end
  2993.  
  2994. local function addNewAddress(newAddress, fast)
  2995.   abCount = abCount + 1
  2996.   addressBook[abCount] = { name = newAddress or "NEW GATE", addr = newAddress or "ADDRESS", rating = "U", iris = "none", callDrop = false, note = newAddress and "Added from logs" or "short note", loc = { x = 0, y = 0, z = 0, dim = "Unspecified" } } --# ...create new entry
  2997.   paginateAddressBook()                            --# paginate the address book
  2998.   gateChange = true                                --# indicate the address book has changed
  2999.   if fast then                                     --# if it's a quick-add...
  3000.     if gatePage == gatePages then drawAddressBook() end --# ...redraw the address book if we're on the last page
  3001.     drawNaviUI()                                   --# update the page navigation UI (page number)
  3002.   else                                             --# if it's not a quick-add...
  3003.     currentEdit = abCount                          --# ...set the entry number being edited
  3004.     drawSubheader()
  3005.     drawGateLabels()                               --# draw the gate information
  3006.   end
  3007.   if displayState == "list" and listPage == listPages then displayAddressBook() end
  3008.   if gateMonList and gateMonPage == gateMonPages then displayGateMonAddrBook() end
  3009. end
  3010.  
  3011. local function deleteGate(gateNum) --# Delete gate
  3012.   table.remove(addressBook, gateNum)
  3013.   gateChange, abCount = true, abCount - 1
  3014.   paginateAddressBook()
  3015.   gatePage = math.min(gatePage, gatePages)
  3016.   listPage = math.min(listPage, listPages)
  3017.   gateMonPage = math.min(gateMonPage, gateMonPages)
  3018.   if displayState == "info" then
  3019.     if selectedGate == gateNum then
  3020.       displayState = "list"
  3021.       if dhdSettings.detailedMarquee then
  3022.         initMarquee = true
  3023.         displayStatusDetail()
  3024.       else
  3025.         displayStatus()
  3026.       end
  3027.       if gateStatus ~= "Idle" then displayAddressBook() end
  3028.     elseif selectedGate > gateNum then
  3029.       selectedGate = selectedGate - 1
  3030.       displayNotes()
  3031.     end
  3032.   else
  3033.     displayAddressBook()
  3034.   end
  3035.   if gateMonList then displayGateMonAddrBook() end
  3036. end
  3037.  
  3038. local function gateDataChange()
  3039.   gateChange = true
  3040.   drawElement((termX / 2) - 8, termY - 1, 1, 1, green, gray, " Save ")
  3041. end
  3042.  
  3043. local function settingsChange()
  3044.   configChange = true
  3045.   drawElement(45, 4, 1, 1, lime, silver, " Save  ")
  3046. end
  3047.  
  3048. local function clearCallHistory()
  3049.   for i = #callHistory, 1, -1 do
  3050.     callHistory[i] = nil
  3051.   end
  3052. end
  3053.  
  3054. local function addAddressFromLogs(address)
  3055.   clearCallHistory()
  3056.   lastCall = nil
  3057.   runState = "GateEdit"
  3058.   for i = 1, abCount do
  3059.     if addressBook[i].addr == address then
  3060.       currentEdit = i
  3061.       drawSubheader()
  3062.       drawGateLabels()
  3063.       return
  3064.     end
  3065.   end
  3066.   addNewAddress(address)
  3067. end
  3068.  
  3069. local function dialAddressFromLogs(address)
  3070.   runState = "Dial"
  3071.   lastCall = nil
  3072.   clearCallHistory()
  3073.   netSend(dhdSettings.gate, { program = "ccDHD", command = address })
  3074.   drawElement(1, 5, termX, termY - 4, nil, black) --# clear lower screen
  3075.   drawCLI(true)
  3076. end
  3077.  
  3078. local function flashDial(name, x, y, gateNum)
  3079.   local gColor = assignColor(gateNum)
  3080.   drawElement(x, y, 12, 1, gColor, black, name)
  3081.   sleep(0.1)
  3082.   drawElement(x, y, 12, 1, black, gColor, name)
  3083. end
  3084.  
  3085. local function flashChoice(x, y, tColor, bColor, text)
  3086.   drawElement(x, y, 1, 1, tColor, bColor, text)
  3087.   sleep(0.1)
  3088. end
  3089.  
  3090. --# custom read function courtesy of theoriginalbit (modified by Dog)
  3091. local function read(_mask, _history, _limit, _noTerminate, _noMouse, _gField)
  3092.   if _mask and type(_mask) ~= "string" then
  3093.     error("Invalid parameter #1: Expected string, got " .. type(_mask), 2)
  3094.   end
  3095.   if _history and type(_history) ~= "table" then
  3096.     error("Invalid parameter #2: Expected table, got " .. type(_history), 2)
  3097.   end
  3098.   if _limit and type(_limit) ~= "number" then
  3099.     error("Invalid parameter #3: Expected number, got " .. type(_limit), 2)
  3100.   end
  3101.   if _noTerminate and type(_noTerminate) ~= "boolean" then
  3102.     error("Invalid argument #4: Expected boolean, got " .. nativeType(_noTerminate), 2)
  3103.   end
  3104.   if _noMouse and type(_noMouse) ~= "boolean" then
  3105.     error("Invalid argument #5: Expected boolean, got " .. nativeType(_noMouse), 2)
  3106.   end
  3107.   if _gField and type(_gField) ~= "boolean" then
  3108.     error("Invalid argument #6: Expected boolean, got " .. nativeType(_gField), 2)
  3109.   end
  3110.   term.setCursorBlink(true)
  3111.   gettingInput = true
  3112.   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 }
  3113.   local passSymbols = { "!", "@", "#", "$", "%", "^", "&", "*", "?", "=", "+", "-", "_", "x", "X" }
  3114.   local mouseLimit = _limit or 0
  3115.   local input, secWord, lineCleared = "", "", false
  3116.   word = ""
  3117.   local pos = 0
  3118.   local historyPos = nil
  3119.   local pullEvent = _noTerminate and os.pullEventRaw or os.pullEvent
  3120.   local sw, sh = term.getSize()
  3121.   local sx, sy = term.getCursorPos()
  3122.   curX, curY = sx, sy
  3123.   local function redraw(_special)
  3124.     local scroll = (sx + pos >= sw and (sx + pos) - sw or 0)
  3125.     local replace = _special or _mask
  3126.     local output = replace and (string.rep(replace, math.ceil(#input / #replace) - scroll)):sub(1, #input) or input:sub(scroll + 1)
  3127.     term.setCursorPos(sx, sy)
  3128.     term.write(output)
  3129.     term.setCursorPos(sx + pos - scroll, sy)
  3130.   end
  3131.   local nativeScroll = term.scroll
  3132.   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
  3133.   local function historyHandler(value)
  3134.     redraw(' ')
  3135.     if value == -1 or value == keys.up then
  3136.       if not historyPos then
  3137.         historyPos = #_history
  3138.       elseif historyPos > 1 then
  3139.         historyPos = historyPos - 1
  3140.       end
  3141.     else
  3142.       if historyPos ~= nil and historyPos < #_history then
  3143.         historyPos = historyPos + 1
  3144.       elseif historyPos == #_history then
  3145.         historyPos = nil
  3146.       end
  3147.     end
  3148.     if historyPos and #_history > 0 then
  3149.       input = string.sub(_history[historyPos], 1, _limit) or ""
  3150.       pos = #input
  3151.       word = input
  3152.       if not _limit then mouseLimit = pos end
  3153.     else
  3154.       input = ""
  3155.       pos = 0
  3156.       word = input
  3157.       if not _limit then mouseLimit = 0 end
  3158.     end
  3159.   end
  3160.   local goodData, event, code, x, y, secSymbols, newWord, glyph = false
  3161.   while true do
  3162.     event, code, x, y = pullEvent()
  3163.     if event == "char" and (not _limit or #input < _limit) then
  3164.       if not lineCleared and pos == 0 then term.write(string.rep(" ", _limit)) lineCleared = true end
  3165.       goodData = false
  3166.       if secureStatus == "lockdown" then
  3167.         secWord = secWord:sub(1, pos) .. passSymbols[math.random(1, #passSymbols)] .. secWord:sub(pos + 1)
  3168.         goodData = true
  3169.       elseif _gField then
  3170.         if symbols[code:upper()] then
  3171.           code = code:upper()
  3172.           goodData = true
  3173.         end
  3174.       else
  3175.         goodData = true
  3176.       end
  3177.       if goodData then
  3178.         input = input:sub(1, pos) .. code .. input:sub(pos + 1)
  3179.         pos = pos + 1
  3180.         word = secureStatus == "allclear" and input or secWord
  3181.         if not _limit then mouseLimit = math.min(mouseLimit + 1, sw - (sw - sx)) end
  3182.       end
  3183.     elseif event == "paste" and (not _limit or #input < _limit) then
  3184.       if _limit and #input + #code > _limit then
  3185.         code = code:sub(1, _limit - #input)
  3186.       end
  3187.       if not lineCleared and pos == 0 then term.write(string.rep(" ", _limit)) lineCleared = true end
  3188.       if secureStatus == "lockdown" then
  3189.         if secSymbols[1] then for i = #secSymbols, 1, -1 do secSymbols[i] = nil end end
  3190.         for i = 1, #input do
  3191.           secSymbols[i] = passSymbols[math.random(1, #passSymbols)]
  3192.         end
  3193.         secWord = secWord:sub(1, pos) .. table.concat(secSymbols) .. secWord:sub(pos + 1)
  3194.       elseif _gField then
  3195.         glyph = ""
  3196.         if newWord[1] then for i = #newWord, 1, -1 do newWord[i] = nil end end
  3197.         for i = 1, #code do
  3198.           glyph = string.upper(code:sub(i, i))
  3199.           newWord[i] = symbols[glyph] and glyph or "?"
  3200.         end
  3201.         code = table.concat(newWord)
  3202.       end
  3203.       input = input:sub(1, pos) .. code .. input:sub(pos + 1)
  3204.       pos = pos + #code
  3205.       word = secureStatus == "allclear" and input or secWord
  3206.       if not _limit then mouseLimit = math.min(mouseLimit + #code, sw - (sw - sx)) end
  3207.     elseif event == "key" then
  3208.       if code == keys.enter or code == keys.numPadEnter then
  3209.         break
  3210.       elseif code == keys.backspace and pos > 0 then
  3211.         redraw(' ')
  3212.         input = input:sub(1, math.max(pos - 1, 0)) .. input:sub(pos + 1)
  3213.         pos = math.max(pos - 1, 0)
  3214.         if secureStatus == "lockdown" then
  3215.           secWord = #secWord > 0 and secWord:sub(1, #secWord - 1) or ""
  3216.         end
  3217.         word = secureStatus == "allclear" and input or secWord
  3218.         if not _limit then mouseLimit = math.max(mouseLimit - 1, 0) end
  3219.       elseif code == keys.delete and pos < #input then
  3220.         redraw(' ')
  3221.         input = input:sub(1, pos) .. input:sub(pos + 2)
  3222.         if secureStatus == "lockdown" then
  3223.           secWord = secWord:sub(1, pos) .. secWord:sub(pos + 2)
  3224.         end
  3225.         word = secureStatus == "allclear" and input or secWord
  3226.         if not _limit then mouseLimit = math.max(mouseLimit - 1, 0) end
  3227.       elseif code == keys.home then
  3228.         pos = 0
  3229.       elseif code == keys["end"] then
  3230.         pos = #input
  3231.       elseif code == keys.left and pos > 0 then
  3232.         pos = math.max(pos - 1, 0)
  3233.       elseif code == keys.right and pos < #input then
  3234.         pos = math.min(pos + 1, #input)
  3235.       elseif _history and (code == keys.up or code == keys.down) then
  3236.         historyHandler(code)
  3237.       elseif code == keys.tab and (runState == "newLockPass" or runState == "newIrisPass") then
  3238.         break
  3239.       end
  3240.     elseif event == "mouse_click" and not _noMouse and ((x < sx or x >= sx + mouseLimit) or (y ~= sy)) and code == 1 then
  3241.       if runState == "newLockPass" or runState == "newIrisPass" then
  3242.         if y == 11 and x > math.floor(termX / 2) - 4 and x < math.floor(termX / 2) + 5 then
  3243.           term.scroll = nativeScroll
  3244.           term.setCursorBlink(false)
  3245.           gettingInput = false
  3246.           word = ""
  3247.           flashChoice((termX / 2) - 3, 11, orange, white, " Cancel ")
  3248.           runState = "DHDsettings"
  3249.           drawElement((termX / 2) - 7, 6, 16, 6, nil, black) --# clear pop-up
  3250.           return drawSettingsScreen()
  3251.         elseif x > math.floor(termX / 2) - 6 and x < math.floor(termX / 2) + 6 and ((y == 8 and curY == 10) or (y == 10 and curY == 8)) then
  3252.           break
  3253.         end
  3254.       else
  3255.         break
  3256.       end
  3257.     elseif event == "mouse_scroll" and _history then
  3258.       historyHandler(code)
  3259.     end
  3260.     redraw(secureStatus == "lockdown" and secWord or _mask)
  3261.   end
  3262.   term.scroll = nativeScroll
  3263.   term.setCursorBlink(false)
  3264.   if sy + 1 > sh then
  3265.     term.scroll(sy + 1 - sh)
  3266.     term.setCursorPos(1, sy)
  3267.   else
  3268.     term.setCursorPos(1, sy + 1)
  3269.   end
  3270.   word = ""
  3271.   gettingInput = false
  3272.   return input
  3273. end
  3274.  
  3275. local function inputClearLogsPopUp()
  3276.   local _, button, x, y
  3277.   while true do
  3278.     _, button, x, y = os.pullEvent("mouse_click")
  3279.     if y == math.floor(termY / 2) and button == 1 then
  3280.       if x > math.floor(termX / 2) - 5 and x < math.floor(termX / 2) + 1 then
  3281.         flashChoice((termX / 2) - 4, termY / 2, green, white, " YES ")
  3282.         local clearLog = fs.open(gateHistory, "w")
  3283.         clearLog.close()
  3284.         clearCallHistory()
  3285.         logPage, logPages = 1, 1
  3286.         runState = "logs"
  3287.         drawElement(1, 5, termX, termY - 4, nil, black) --# clear lower screen
  3288.         drawElement(termX - 6, 4, 7, 1, gray, silver, " Clear ") --# 'clear logs' button
  3289.         return drawLogScreen()
  3290.       elseif x > math.floor(termX / 2) + 1 and x < math.floor(termX / 2) + 7 then
  3291.         flashChoice((termX / 2) + 2, termY / 2, red, white, " N O ")
  3292.         drawElement((termX / 2) - 5, (termY / 2) - 2, 13, 4, nil, black) --# clear dialogue box
  3293.         runState = "logs"
  3294.         return drawLogScreen()
  3295.       end
  3296.     end
  3297.   end
  3298. end
  3299.  
  3300. local function inputSaveAndQuitPopUp()
  3301.   local _, button, x, y
  3302.   while true do
  3303.     _, button, x, y = os.pullEvent("mouse_click")
  3304.     if x > math.floor(termX / 2) - 14 and x < math.floor(termX / 2) + 6 and y > math.floor(termY / 2) - 2 and y < math.floor(termY / 2) + 3 then
  3305.       if x > math.floor(termX / 2) - 13 and x < math.floor(termX / 2) - 4 and y == math.floor(termY / 2) + 1 and button == 1 then
  3306.         flashChoice((termX / 2) - 12, (termY / 2) + 1, green, white, "  Save  ")
  3307.         if gateChange then saveData(gateData, "gate") end
  3308.         if configChange then saveData(settingsData, "cfg") end
  3309.         return shutDown()
  3310.       elseif x > math.floor(termX / 2) - 4 and x < math.floor(termX / 2) + 5 and y == math.floor(termY / 2) + 1 and button == 1 then
  3311.         flashChoice((termX / 2) - 3, (termY / 2) + 1, orange, white, "  Quit  ")
  3312.         return shutDown()
  3313.       end
  3314.     else
  3315.       runState = "Dial"
  3316.       drawElement((termX / 2) - 13, (termY / 2) - 1, 19, 4, nil, black) --#clear pop-up
  3317.       return drawAddressBook()
  3318.     end
  3319.   end
  3320. end
  3321.  
  3322. local function inputImportExportPopUp()
  3323.   local _, button, x, y
  3324.   while true do
  3325.     _, button, x, y = os.pullEvent("mouse_click")
  3326.     if x > math.floor(termX / 2) - 14 and x < math.floor(termX / 2) + 5 and y < math.floor(termY / 2) + 7 and y > math.floor(termY / 2) - 1 and button == 1 then --# Import (Merge)
  3327.       if x > math.floor(termX / 2) - 13 and x < math.floor(termX / 2) - 4 and y == math.floor(termY / 2) + 1 then
  3328.         if fs.exists("/disk/data/DHDgates") then
  3329.           flashChoice((termX / 2) - 12, (termY / 2) + 1, yellow, black, " Import ")
  3330.           local diskData = fs.open("/disk/data/DHDgates", "r")
  3331.           local dhdData = textutils.unserialize(diskData.readAll())
  3332.           diskData.close()
  3333.           mergeAddressBooks(dhdData)
  3334.           if gateChange then
  3335.             drawHeader() --# redraw header in case thisGate has been imported or updated
  3336.             drawAddressBook()
  3337.             drawNaviUI()
  3338.             drawAddrBookButton()
  3339.             if displayState == "list" then displayAddressBook() end
  3340.             if gateMonList then displayGateMonAddrBook() end
  3341.           end
  3342.           drawPopUp()
  3343.         end
  3344.       elseif x > math.floor(termX / 2) - 4 and x < math.floor(termX / 2) + 5 and y == math.floor(termY / 2) + 1 then --# Export
  3345.         if fs.exists("/disk") then
  3346.           flashChoice((termX / 2) - 3, (termY / 2) + 1, orange, black, " Export ")
  3347.           if not fs.exists("/disk/data") then fs.makeDir("/disk/data") end
  3348.           saveData("/disk/data/DHDgates", "gate")
  3349.           drawPopUp()
  3350.         end
  3351.       elseif x > math.floor(termX / 2) - 13 and x < math.floor(termX / 2) - 4 and y == math.floor(termY / 2) + 3 and gateChange then --# Load Address Book
  3352.         flashChoice((termX / 2) - 12, (termY / 2) + 3, sky, white, "  Load  ")
  3353.         ingestData(gateData, "gate")
  3354.         drawElement(2, 6, 39, 11, nil, black) --# clear data area in case loaded address book has fewer entries that address book in memory
  3355.         gateChange = false
  3356.         drawHeader()
  3357.         drawAddressBook()
  3358.         drawNaviUI()
  3359.         drawAddrBookButton()
  3360.         drawPopUp()
  3361.         if displayState == "info" then
  3362.           displayState = "list"
  3363.           if dhdSettings.detailedMarquee then
  3364.             initMarquee = true
  3365.             displayStatusDetail()
  3366.           else
  3367.             displayStatus()
  3368.           end
  3369.           if gateStatus ~= "Idle" then displayAddressBook() end
  3370.         else
  3371.           displayAddressBook()
  3372.         end
  3373.         if gateMonList then displayGateMonAddrBook() end
  3374.       elseif x > math.floor(termX / 2) - 4 and x < math.floor(termX / 2) + 5 and y == math.floor(termY / 2) + 3 and gateChange then --# Save Address Book
  3375.         flashChoice((termX / 2) - 3, (termY / 2) + 3, green, white, "  Save  ")
  3376.         saveData(gateData, "gate")
  3377.         drawAddrBookButton()
  3378.         drawPopUp()
  3379.       elseif x > math.floor(termX / 2) - 8 and x < math.floor(termX / 2) and y == math.floor(termY / 2) + 5 then --# Exit popup
  3380.         runState = "Dial"
  3381.         flashChoice((termX / 2) - 7, (termY / 2) + 5, red, white, " CLOSE ")
  3382.         drawElement((termX / 2) - 13, (termY / 2) - 1, 19, 8, nil, black) -- clear pop-up
  3383.         return drawAddressBook()
  3384.       end
  3385.     end
  3386.   end
  3387. end
  3388.  
  3389. local function inputGoToLogPage()
  3390.   bgCol, txtCol = black, white
  3391.   term.setBackgroundColor(black)
  3392.   term.setTextColor(white)
  3393.   term.setCursorPos(math.floor(termX / 2) - 2, termY - 2)
  3394.   local newPage = tonumber(read(nil, nil, 4))
  3395.   drawElement((termX / 2) - 4, termY - 3, 8, 3) --# clear pop-up
  3396.   logPage = newPage or logPage
  3397.   logPage = math.floor(math.max(1, math.min(logPage, logPages)))
  3398.   runState = "logs"
  3399.   drawLogScreen()
  3400. end
  3401.  
  3402. local function inputGoToPage()
  3403.   bgCol, txtCol = black, white
  3404.   term.setBackgroundColor(black)
  3405.   term.setTextColor(white)
  3406.   term.setCursorPos(math.floor(termX / 2) - 6, termY - 2)
  3407.   local newPage = tonumber(read(nil, nil, 4))
  3408.   drawElement((termX / 2) - 8, termY - 3, 8, 3) --# clear pop-up
  3409.   gatePage = newPage or gatePage
  3410.   gatePage = math.floor(math.max(1, math.min(gatePage, gatePages)))
  3411.   if gatePage == gatePages and gatePages > 1 then drawElement(2, 6, 39, 11) end --# clear data area
  3412.   runState = "Dial"
  3413.   drawAddressBook()
  3414.   drawNaviUI()
  3415. end
  3416.  
  3417. local function inputRemotePassword()
  3418.   bgCol, txtCol = silver, black
  3419.   term.setBackgroundColor(silver)
  3420.   term.setTextColor(black)
  3421.   term.setCursorPos(math.floor(termX / 2) - 6, 11)
  3422.   local remotePass = read("*", nil, 12)
  3423.   if remotePass ~= "" then
  3424.     local pass = table.concat(pbkdf2(remotePass, "ccDHD!Pass.Hash", 15))
  3425.     netSend(dhdSettings.gate, { program = "ccDialer", password = pass })
  3426.   end
  3427.   drawElement((termX / 2) - 8, 10, 16, 3, nil, black) --# clear pop-up
  3428.   runState = tempState
  3429.   if runState == "GateEdit" then drawGateData() else drawAddressBook() end
  3430. end
  3431.  
  3432. local function inputChangeLockdownPassword()
  3433.   local oldPass, newPass, newHashed, oldHashed, goodChange = "", "", "", "", false
  3434.   bgCol, txtCol = black, yellow
  3435.   term.setTextColor(yellow)
  3436.   while true do
  3437.     drawElement((termX / 2) - 6, 8, 14, 1, yellow, black) --# old password entry area
  3438.     term.setCursorPos(math.floor(termX / 2) - 5, 8)
  3439.     oldPass = read("*", nil, 12)
  3440.     if runState == "DHDsettings" then return end --# Cancel has been clicked
  3441.     if oldPass ~= "" then
  3442.       drawElement((termX / 2) - 5, 8, 1, 1, gray, nil, string.rep("*", #oldPass))
  3443.       oldHashed = table.concat(pbkdf2(oldPass, "ccDHD!Pass.Hash", 15))
  3444.     else
  3445.       oldHashed = ""
  3446.     end
  3447.     if newPass ~= "" and newHashed ~= "" and newHashed ~= dhdSettings.password and oldHashed == dhdSettings.password then
  3448.       goodChange = true
  3449.       break
  3450.     end
  3451.     drawElement((termX / 2) - 6, 10, 14, 1, yellow) --# new password entry area
  3452.     term.setCursorPos(math.floor(termX / 2) - 5, 10)
  3453.     newPass = read("*", nil, 12)
  3454.     if runState == "DHDsettings" then return end --# Cancel has been clicked
  3455.     if newPass ~= "" then
  3456.       drawElement((termX / 2) - 5, 10, 1, 1, gray, nil, string.rep("*", #newPass))
  3457.       newHashed = table.concat(pbkdf2(newPass, "ccDHD!Pass.Hash", 15))
  3458.     else
  3459.       newHashed = ""
  3460.     end
  3461.     if newPass ~= "" and newHashed ~= "" and newHashed ~= dhdSettings.password and oldHashed == dhdSettings.password then
  3462.       goodChange = true
  3463.       break
  3464.     end
  3465.   end
  3466.   if goodChange then
  3467.     configChange = true
  3468.     dhdSettings.password = newHashed
  3469.     runState = "DHDsettings"
  3470.     drawElement((termX / 2) - 7, 6, 16, 6) --# clear password change box
  3471.     drawSettingsScreen()
  3472.   end
  3473. end
  3474.  
  3475. local function inputChangeIrisPassword()
  3476.   local oldPass, newPass, oldHashed, newHashed, goodChange = "", "", "", "", false
  3477.   bgCol, txtCol = black, yellow
  3478.   term.setTextColor(yellow)
  3479.   while true do
  3480.     drawElement((termX / 2) - 6, 8, 14, 1, yellow, black) --# old password entry area
  3481.     term.setCursorPos(math.floor(termX / 2) - 5, 8)
  3482.     oldPass = read("*", nil, 12)
  3483.     if runState == "DHDsettings" then return end --# Cancel has been clicked
  3484.     if oldPass ~= "" then
  3485.       drawElement((termX / 2) - 5, 8, 1, 1, gray, nil, string.rep("*", #oldPass))
  3486.       oldHashed = table.concat(pbkdf2(oldPass, "ccDHD!Pass.Hash", 15))
  3487.     else
  3488.       oldHashed = ""
  3489.     end
  3490.     if newPass ~= "" and newHashed ~= "" and newHashed ~= dhdSettings.irisPassword and oldHashed == dhdSettings.irisPassword then
  3491.       goodChange = true
  3492.       break
  3493.     end
  3494.     drawElement((termX / 2) - 6, 10, 14, 1, yellow) --# new password entry area
  3495.     term.setCursorPos(math.floor(termX / 2) - 5, 10)
  3496.     newPass = read("*", nil, 12)
  3497.     if runState == "DHDsettings" then return end --# Cancel has been clicked
  3498.     if newPass ~= "" then
  3499.       drawElement((termX / 2) - 5, 10, 1, 1, gray, nil, string.rep("*", #newPass))
  3500.       newHashed = table.concat(pbkdf2(newPass, "ccDHD!Pass.Hash", 15))
  3501.     else
  3502.       newHashed = ""
  3503.     end
  3504.     if newPass ~= "" and newHashed ~= "" and newHashed ~= dhdSettings.irisPassword and oldHashed == dhdSettings.irisPassword then
  3505.       goodChange = true
  3506.       break
  3507.     end
  3508.   end
  3509.   if goodChange then
  3510.     configChange = true
  3511.     dhdSettings.irisPassword = newHashed
  3512.     runState = "DHDsettings"
  3513.     drawElement((termX / 2) - 7, 6, 16, 6) --# clear password change box
  3514.     drawSettingsScreen()
  3515.   end
  3516. end
  3517.  
  3518. local function inputSelectPassword()
  3519.   local _, button, x, y
  3520.   while true do
  3521.     _, button, x, y = os.pullEvent("mouse_click")
  3522.     if y > 7 and y < 12 and x > math.floor(termX / 2) - 7 and x < math.floor(termX / 2) + 8 and button == 1 then
  3523.       if y == 8 and x > math.floor(termX / 2) - 7 and x < math.floor(termX / 2) + 8 then --# lockdown
  3524.         runState = "newLockPass"
  3525.         return drawPopUp()
  3526.       elseif y == 10 and x > math.floor(termX / 2) - 7 and x < math.floor(termX / 2) + 8 then --# iris
  3527.         runState = "newIrisPass"
  3528.         return drawPopUp()
  3529.       elseif y == 11 and x > math.floor(termX / 2) - 4 and x < math.floor(termX / 2) + 5 then --# cancel
  3530.         flashChoice((termX / 2) - 3, 11, orange, white, " Cancel ")
  3531.         runState = "DHDsettings"
  3532.         drawElement((termX / 2) - 7, 6, 16, 6, nil, black) --# clear password change box
  3533.         return drawSettingsScreen()
  3534.       end
  3535.     end
  3536.   end
  3537. end
  3538.  
  3539. local function inputEditGateEntry()
  3540.   local event, data, x, y, addr
  3541.   while true do
  3542.     event, data, x, y = os.pullEvent()
  3543.     if event == "mouse_click" then
  3544.       addr = addressBook[currentEdit].addr
  3545.       if y == 3 and x > termX - 3 and data == 1 and gateStatus == "Connected" and secureStatus == "allclear" then --# Remote Iris
  3546.         tempState = runState
  3547.         runState = "remotePass"
  3548.         return drawPopUp()
  3549.       elseif y == 6 and data == 1 then
  3550.         if x > 10 and x < 23 then --# Gate Name
  3551.           drawElement(11, 6, 1, 1, gray, black, addressBook[currentEdit].name)
  3552.           bgCol, txtCol = black, cyan
  3553.           term.setTextColor(cyan)
  3554.           term.setCursorPos(11, 6)
  3555.           local newGateName = read(nil, { addr, addressBook[currentEdit].name }, 12)
  3556.           if newGateName ~= "" and newGateName ~= addressBook[currentEdit].name then
  3557.             addressBook[currentEdit].name = newGateName
  3558.             if displayState == "list" then
  3559.               if addr == thisGate or addr:sub(1, 7) == thisGate or addr == thisGate:sub(1, 7) then
  3560.                 if dhdSettings.detailedMarquee then
  3561.                   initMarquee = true --# in case entry being edited is this gate
  3562.                   displayStatusDetail()
  3563.                 else
  3564.                   displayStatus()
  3565.                 end
  3566.               end
  3567.               displayAddressBook()
  3568.             elseif displayState == "info" and currentEdit == selectedGate then
  3569.               displayNotes()
  3570.             end
  3571.             if gateMonList then displayGateMonAddrBook() end
  3572.             gateDataChange()
  3573.             drawHeader()
  3574.           end
  3575.           drawElement(11, 6, 1, 1, cyan, black, addressBook[currentEdit].name .. string.rep(" ", 12 - #addressBook[currentEdit].name))
  3576.         elseif x > 30 and x < 35 and not lcGate then --# Iris control
  3577.           if addressBook[currentEdit].iris == "none" then
  3578.             addressBook[currentEdit].iris = true
  3579.           elseif addressBook[currentEdit].iris then
  3580.             addressBook[currentEdit].iris = false
  3581.           else
  3582.             addressBook[currentEdit].iris = "none"
  3583.           end
  3584.           drawElement(31, 6, addressBook[currentEdit].iris)
  3585.           gateDataChange()
  3586.         elseif x > termX - 7 and x < termX - 6 + #tostring(currentEdit) then --# Address Book entry position (for reordering)
  3587.           drawElement(termX - 6, 6, 1, 1, gray, black, tostring(currentEdit))
  3588.           bgCol, txtCol = black, white
  3589.           term.setTextColor(white)
  3590.           term.setCursorPos(termX - 6, 6)
  3591.           local newPos = tonumber(read(nil, { tostring(currentEdit) }, 5))
  3592.           if newPos then newPos = math.floor(newPos) end
  3593.           if newPos and newPos ~= currentEdit and newPos > 0 and newPos <= abCount then
  3594.             local tempGateData = { }
  3595.             for k, v in pairs(addressBook[currentEdit]) do
  3596.               tempGateData[k] = v
  3597.             end
  3598.             table.remove(addressBook, currentEdit)
  3599.             table.insert(addressBook, newPos, tempGateData)
  3600.             if displayState == "info" then
  3601.               if currentEdit == selectedGate then
  3602.                 selectedGate = newPos
  3603.               elseif currentEdit > selectedGate and newPos < selectedGate then
  3604.                 selectedGate = selectedGate + 1
  3605.               elseif currentEdit < selectedGate and newPos > selectedGate then
  3606.                 selectedGate = selectedGate - 1
  3607.               end
  3608.               displayNotes()
  3609.             else
  3610.               displayAddressBook()
  3611.             end
  3612.             if gateMonList then displayGateMonAddrBook() end
  3613.             currentEdit = newPos
  3614.             gateDataChange()
  3615.           end
  3616.           drawElement(termX - 6, 6, 1, 1, white, black, tostring(currentEdit) .. string.rep(" ", 5 - #tostring(currentEdit)))
  3617.         end
  3618.       elseif y == 8 then
  3619.         if x > 10 and x < 20 then --# Gate Address
  3620.           if data == 1 then
  3621.             drawElement(11, 8, 1, 1, gray, black, addr)
  3622.             bgCol, txtCol = black, yellow
  3623.             term.setTextColor(yellow)
  3624.             term.setCursorPos(11, 8)
  3625.             local newGateAddress = read(nil, { addr }, 9, nil, nil, true)
  3626.             local ngLen, name = #newGateAddress, addressBook[currentEdit].name
  3627.             if newGateAddress ~= "" and (ngLen == 7 or ngLen == 9) and newGateAddress ~= addr and not newGateAddress:find("?") then
  3628.               addressBook[currentEdit].addr = newGateAddress
  3629.               if name == "NEW GATE" or name == "Name" or name == "NO GATES" then
  3630.                 addressBook[currentEdit].name = newGateAddress
  3631.                 drawElement(11, 6, 1, 1, sky, nil, addressBook[currentEdit].name .. string.rep(" ", 12 - #addressBook[currentEdit].name))
  3632.               end
  3633.               if displayState == "list" then
  3634.                 if addr == thisGate or addr:sub(1, 7) == thisGate or addr == thisGate:sub(1, 7) then
  3635.                   if dhdSettings.detailedMarquee then
  3636.                     initMarquee = true --# in case entry being edited is this gate
  3637.                     displayStatusDetail()
  3638.                   else
  3639.                     displayStatus()
  3640.                   end
  3641.                 end
  3642.                 displayAddressBook()
  3643.               elseif displayState == "info" and currentEdit == selectedGate then
  3644.                 displayNotes()
  3645.               end
  3646.               if gateMonList then displayGateMonAddrBook() end
  3647.               gateDataChange()
  3648.               drawHeader()
  3649.             end
  3650.             drawElement(11, 8, 1, 1, yellow, black, addressBook[currentEdit].addr .. "  ")
  3651.           elseif data == 2 and addr ~= thisGate then
  3652.             runState = "Dial"
  3653.             dialAddress = addr
  3654.             netSend(dhdSettings.gate, { program = "ccDHD", command = dialAddress })
  3655.             return drawElement(1, 5, termX, termY - 4, nil, black) --# clear lower screen
  3656.           end
  3657.         elseif x > 30 and x < 35 and data == 1 and not lcGate then --# callDrop control
  3658.           addressBook[currentEdit].callDrop = not addressBook[currentEdit].callDrop
  3659.           drawElement(31, 8, addressBook[currentEdit].callDrop)
  3660.           gateDataChange()
  3661.         end
  3662.       elseif y == 10 and data == 1 then --# Edit Gate Note
  3663.         if x > 7 and x < 8 + #addressBook[currentEdit].note:sub(1, 43) then
  3664.           drawElement(8, 10, 1, 1, gray, black, addressBook[currentEdit].note)
  3665.           bgCol, txtCol = black, white
  3666.           term.setTextColor(white)
  3667.           term.setCursorPos(8, 10)
  3668.           local newNote = read(nil, { addressBook[currentEdit].note }, 43)
  3669.           if newNote ~= "" and newNote ~= addressBook[currentEdit].note then
  3670.             addressBook[currentEdit].note = newNote
  3671.             if displayState == "info" and currentEdit == selectedGate then
  3672.               displayNotes()
  3673.             end
  3674.             gateDataChange()
  3675.           end
  3676.           drawElement(8, 10, 1, 1, white, black, addressBook[currentEdit].note .. string.rep(" ", 43 - #addressBook[currentEdit].note))
  3677.         end
  3678.       elseif y == 12 and data == 1 then --# Change Gate Rating/Classification
  3679.         if x > 17 and x < 18 + #assignRating(currentEdit) then
  3680.           drawRatingList(addressBook[currentEdit].rating)
  3681.           local selected, _, mButton, mX, mY = false
  3682.           while true do
  3683.             _, mButton, mX, mY = os.pullEvent("mouse_click")
  3684.             if mX > 17 and mX < 38 and mY > 5 and mY < 17 then --# Bounds of pop-up, so clicking on the popup won't close it
  3685.               if mButton == 1 and mY > 7 and mY < 16 then      --# actual selection bounds
  3686.                 for k, v in pairs(classifications) do
  3687.                   if mY == v.order + 7 then
  3688.                     if addressBook[currentEdit].rating ~= k then
  3689.                       addressBook[currentEdit].rating = k
  3690.                       drawHeader()
  3691.                       if displayState == "list" then
  3692.                         if addr == thisGate or addr:sub(1, 7) == thisGate or addr == thisGate:sub(1, 7) then
  3693.                           if dhdSettings.detailedMarquee then
  3694.                             initMarquee = true --# in case entry being edited is this gate
  3695.                             displayStatusDetail()
  3696.                           else
  3697.                             displayStatus()
  3698.                           end
  3699.                         end
  3700.                         displayAddressBook()
  3701.                       elseif displayState == "info" and currentEdit == selectedGate then
  3702.                         displayNotes()
  3703.                       end
  3704.                       if gateMonList then displayGateMonAddrBook() end
  3705.                       gateDataChange()
  3706.                     end
  3707.                     selected = true
  3708.                     break
  3709.                   end
  3710.                 end
  3711.                 if selected then break end
  3712.               end
  3713.             else
  3714.               break
  3715.             end
  3716.           end
  3717.           drawElement(18, 6, 20, 11, nil, black) --# clear rating popup
  3718.           drawElement(11, 6, 1, 1, cyan, nil, addressBook[currentEdit].name) --# clean up after the rating pop-up
  3719.           drawElement(11, 8, 1, 1, yellow, nil, addr)
  3720.           drawElement(18, 12, 1, 1, assignColor(currentEdit), nil, assignRating(currentEdit))
  3721.           drawElement(25, 16, 2, 1, gray, nil, "y:")
  3722.           if addr == thisGate or addr:sub(1, 7) == thisGate or addr == thisGate:sub(1, 7) or addr == dialAddress or addr:sub(1, 7) == dialAddress or addr == dialAddress:sub(1, 7) or (incomingAddress and (addr == incomingAddress or addr:sub(1, 7) == incomingAddress or addr == incomingAddress:sub(1, 7))) then
  3723.             drawElement(termX - 14, 8, 1, 1, nil, nil, "<")
  3724.           end
  3725.           drawElement(8, 10, 1, 1, white, nil, addressBook[currentEdit].note)
  3726.           drawElement(13, 14, 1, 1, brown, nil, addressBook[currentEdit].loc.dim)
  3727.           drawElement(15, 16, 1, 1, silver, nil, tostring(addressBook[currentEdit].loc.x))
  3728.           drawElement(28, 16, 1, 1, nil, nil, tostring(addressBook[currentEdit].loc.y))
  3729.           if not lcGate then
  3730.             drawElement(25, 6, 1, 1, gray, nil, "Iris:")
  3731.             drawElement(25, 8, 1, 1, nil, nil, "Drop:")
  3732.             drawElement(31, 6, addressBook[currentEdit].iris)
  3733.             drawElement(31, 8, addressBook[currentEdit].callDrop)
  3734.           end
  3735.         end
  3736.       elseif y == 14 and data == 1 then      --# Gate Dimension
  3737.         if x > 12 and x < 13 + #addressBook[currentEdit].loc.dim then
  3738.           drawElement(13, 14, 1, 1, gray, black, addressBook[currentEdit].loc.dim)
  3739.           bgCol, txtCol = black, brown
  3740.           term.setTextColor(brown)
  3741.           term.setCursorPos(13, 14)
  3742.           local newDim = read(nil, { "Overworld", "Nether", "The End", addressBook[currentEdit].loc.dim }, 19)
  3743.           if newDim ~= "" and newDim ~= addressBook[currentEdit].loc.dim then
  3744.             addressBook[currentEdit].loc.dim = newDim
  3745.             if displayState == "info" and currentEdit == selectedGate then
  3746.               displayNotes()
  3747.             end
  3748.             gateDataChange()
  3749.           end
  3750.           drawElement(13, 14, 1, 1, brown, black, addressBook[currentEdit].loc.dim .. string.rep(" ", 19 - #addressBook[currentEdit].loc.dim))
  3751.         end
  3752.       elseif y == 16 and data == 1 then      --# Gate X/Y/Z Coordinates
  3753.         local xStr, yStr, zStr = tostring(addressBook[currentEdit].loc.x), tostring(addressBook[currentEdit].loc.y), tostring(addressBook[currentEdit].loc.z)
  3754.         if x > 14 and x < 15 + #xStr then     --# X coordinate
  3755.           drawElement(15, 16, 1, 1, gray, black, xStr)
  3756.           bgCol, txtCol = black, silver
  3757.           term.setTextColor(silver)
  3758.           term.setCursorPos(15, 16)
  3759.           local newX = tonumber(read(nil, { xStr }, 9))
  3760.           if newX and newX ~= addressBook[currentEdit].loc.x then
  3761.             addressBook[currentEdit].loc.x = newX
  3762.             xStr = tostring(newX)
  3763.             gateDataChange()
  3764.           end
  3765.           drawElement(15, 16, 1, 1, silver, black, xStr .. string.rep(" ", 9 - #xStr))
  3766.         elseif x > 27 and x < 28 + #yStr then --# Y coordinate
  3767.           drawElement(28, 16, 1, 1, gray, black, yStr)
  3768.           bgCol, txtCol = black, silver
  3769.           term.setTextColor(silver)
  3770.           term.setCursorPos(28, 16)
  3771.           local newY = tonumber(read(nil, { yStr }, 9))
  3772.           if newY and newY ~= addressBook[currentEdit].loc.y then
  3773.             addressBook[currentEdit].loc.y = newY
  3774.             yStr = tostring(newY)
  3775.             gateDataChange()
  3776.           end
  3777.           drawElement(28, 16, 1, 1, silver, black, yStr .. string.rep(" ", 9 - #yStr))
  3778.         elseif x > 40 and x < 41 + #zStr then --# Z coordinate
  3779.           drawElement(41, 16, 1, 1, gray, black, zStr)
  3780.           bgCol, txtCol = black, silver
  3781.           term.setTextColor(silver)
  3782.           term.setCursorPos(41, 16)
  3783.           local newZ = tonumber(read(nil, { zStr }, 9))
  3784.           if newZ and newZ ~= addressBook[currentEdit].loc.z then
  3785.             addressBook[currentEdit].loc.z = newZ
  3786.             zStr = tostring(newZ)
  3787.             gateDataChange()
  3788.           end
  3789.           drawElement(41, 16, 1, 1, silver, black, zStr .. string.rep(" ", 9 - #zStr))
  3790.         end
  3791.       elseif y == termY - 1 and data == 1 then
  3792.         if x > math.floor(termX / 2) - 9 and x < math.floor(termX / 2) - 2 and gateChange then --# Save Address Book
  3793.           flashChoice((termX / 2) - 8, termY - 1, white, green, " Save ")
  3794.           drawElement((termX / 2) - 8, termY - 1, 1, 1, silver, gray, " Save ")
  3795.           saveData(gateData, "gate")
  3796.         elseif x > math.floor(termX / 2) and x < math.floor(termX / 2) + 8 then --# Exit Gate View/Edit Screen
  3797.           runState = "Dial"
  3798.           currentEdit = nil
  3799.           flashChoice((termX / 2) + 1, termY - 1, white, red, " Close ")
  3800.           drawElement(1, 5, termX, termY - 4, nil, black) --# clear lower screen
  3801.           return drawCLI()
  3802.         end
  3803.       end
  3804.     elseif event == "mouse_scroll" and ((data == 1 and currentEdit < abCount) or (data == -1 and currentEdit > 1)) then
  3805.       currentEdit = currentEdit + data
  3806.       if gettingInput then
  3807.         term.setCursorBlink(false)
  3808.         gettingInput = false
  3809.       end
  3810.       drawGateData()
  3811.     end
  3812.   end
  3813. end
  3814.  
  3815. local function inputSettings()
  3816.   local _, button, x, y
  3817.   while true do
  3818.     _, button, x, y = os.pullEvent("mouse_click")
  3819.     --# Save / Close buttons
  3820.     if y == 2 and x > 44 and button == 1 then --# Close Settings (no save)
  3821.       flashChoice(termX - 6, 2, white, red, " Close ")
  3822.       drawElement(1, 5, termX, termY - 4, nil, black) --# clear lower screen
  3823.       runState = "Dial"
  3824.       return drawCLI()
  3825.     elseif y == 4 and x > 44 and button == 1 and configChange then --# Save Settings
  3826.       flashChoice(45, 4, white, green, " Save  ")
  3827.       drawElement(45, 4, 1, 1, gray, silver, " Save  ")
  3828.       saveData(settingsData, "cfg")
  3829.       configChange = false
  3830.     end
  3831.     --# Column 1
  3832.     if y == 6 and x > 15 and x < 25 and button == 1 then --# Change LOCKDOWN or remote Iris Password
  3833.       runState = "newPassword"
  3834.       return drawPopUp()
  3835.     end
  3836.     if hardware.bio > 0 then
  3837.       if y == 10 and x > 21 and x < 26 and button == 1 then --# Biolock ON/OFF
  3838.         dhdSettings.bio.lock = not dhdSettings.bio.lock
  3839.         drawElement(22, 10, dhdSettings.bio.lock)
  3840.         settingsChange()
  3841.       elseif y == 12 and x > 21 and x < 26 and button == 1 then --# Bioscanner function (iris/lockdown)
  3842.         if dhdSettings.bio.func == "none" then
  3843.           dhdSettings.bio.func = "iris"
  3844.           drawElement(7, 12, 1, 1, green, black, "Iris       ")
  3845.           drawElement(22, 12, true)
  3846.         elseif dhdSettings.bio.func == "iris" then
  3847.           dhdSettings.bio.func = "lock"
  3848.           drawElement(7, 12, 1, 1, red, black, "Lockdown   ")
  3849.           drawElement(22, 12, false)
  3850.         elseif dhdSettings.bio.func == "lock" then
  3851.           dhdSettings.bio.func = "none"
  3852.           drawElement(7, 12, 1, 1, silver, black, "No Function")
  3853.           drawElement(22, 12, "MID")
  3854.         end
  3855.         settingsChange()
  3856.       elseif y == 14 and x > 19 and x < 25 and button == 1 then --# Set Biolock login authorization level
  3857.         if dhdSettings.bio.auth ~= x - 19 then
  3858.           drawElement(19 + dhdSettings.bio.auth, 14, 1, 1, gray, black, "-")
  3859.           dhdSettings.bio.auth = x - 19
  3860.           drawElement(19 + dhdSettings.bio.auth, 14, 1, 1, black, silver, tostring(dhdSettings.bio.auth))
  3861.           settingsChange()
  3862.         end
  3863.       elseif y == 16 and x > 19 and x < 25 and button == 1 then --# Set Biolock iris/lockdown authorization level
  3864.         if dhdSettings.bio.fAuth ~= x - 19 then
  3865.           drawElement(19 + dhdSettings.bio.fAuth, 16, 1, 1, gray, black, "-")
  3866.           dhdSettings.bio.fAuth = x - 19
  3867.           drawElement(19 + dhdSettings.bio.fAuth, 16, 1, 1, black, silver, tostring(dhdSettings.bio.fAuth))
  3868.           settingsChange()
  3869.         end
  3870.       end
  3871.     else
  3872.       if y == 12 and x > 11 and x < 12 + #ccLabel and button == 1 then --# Change computer label
  3873.         drawElement(12, 12, 1, 1, gray, black, ccLabel)
  3874.         txtCol, bgCol = white, black
  3875.         term.setTextColor(white)
  3876.         term.setCursorPos(12, 12)
  3877.         local newLabel = read(nil, { ccLabel }, 15)
  3878.         drawElement(12, 12, 15, 1)
  3879.         if newLabel ~= "" and newLabel ~= ccLabel then ccLabel = newLabel os.setComputerLabel(newLabel) end
  3880.         drawElement(12, 12, 1, 1, white, black, ccLabel)
  3881.       end
  3882.     end
  3883.     --# Column 2
  3884.     if x > 45 and x < 50 and y > 5 and y < 17 and button == 1 then
  3885.       if y == 6 and hardware.wifiSide ~= "none" then --# Sync for ccDialer
  3886.         if type(dhdSettings.sync) == "string" then --# if Sync is already set to 'Dial ONLY' then set it to 'Dial & Sync'...
  3887.           dhdSettings.sync = true
  3888.           drawElement(34, 6, 1, 1, green, black, "Dial & Sync")
  3889.           drawElement(46, 6, true)
  3890.           settingsChange()
  3891.         elseif dhdSettings.sync then --# otherwise if Sync is set to 'Dial & Sync' then turn it off...
  3892.           sendToAllSyncClients("Offline")
  3893.           dhdSettings.sync = false
  3894.           rednet.unhost("ccDialerWiFi", thisGate)
  3895.           if hardware.modemSide ~= "none" and rednet.isOpen(hardware.wifiSide) then rednet.close(hardware.wifiSide) end
  3896.           if clientCount > 0 then
  3897.             for id in pairs(clients) do
  3898.               clients[id] = nil
  3899.               clientCount = math.max(0, clientCount - 1)
  3900.             end
  3901.           end
  3902.           drawElement(34, 6, 1, 1, red, black, "OFF        ")
  3903.           drawElement(46, 6, false)
  3904.           settingsChange()
  3905.         else --# otherwise if Sync is off then set it to 'Dial ONLY'...
  3906.           dhdSettings.sync, configChange = "Dial ONLY", true
  3907.           drawElement(46, 6, 4, 1, nil, orange) --# 'Sync initializing' switch position (orange)
  3908.           drawElement(47, 6, 2, 1, nil, gray)
  3909.           drawElement((termX / 2) - 7, (termY / 2) - 2, 20, 1, white, blue, "Sync")
  3910.           drawElement((termX / 2) - 7, (termY / 2) - 1, 20, 3, nil, silver, "Initializing...")
  3911.           if not rednet.isOpen(hardware.wifiSide) then rednet.open(hardware.wifiSide) end
  3912.           rednet.host("ccDialerWiFi", thisGate)
  3913.           drawElement((termX / 2) - 7, (termY / 2) - 2, 20, 4, nil, black) --# clear pop-up
  3914.           drawSettingsScreen()
  3915.         end
  3916.       elseif y == 10 then --# Show detailed gate info on the marquee monitor
  3917.         dhdSettings.detailedMarquee = not dhdSettings.detailedMarquee
  3918.         drawElement(46, 10, dhdSettings.detailedMarquee)
  3919.         if displayState == "list" then
  3920.           if dhdSettings.detailedMarquee then
  3921.             initMarquee = true
  3922.             displayStatusDetail()
  3923.           else
  3924.             displayStatus()
  3925.           end
  3926.         end
  3927.         settingsChange()
  3928.       elseif y == 12 then --# Call Logging (ON/OFF)
  3929.         dhdSettings.logs = not dhdSettings.logs
  3930.         drawElement(46, 12, dhdSettings.logs)
  3931.         settingsChange()
  3932.       elseif y == 14 then --# Incoming Call Auto-Iris (CLOSE/DO NOTHING)
  3933.         dhdSettings.incomingIris = not dhdSettings.incomingIris
  3934.         drawElement(46, 14, dhdSettings.incomingIris)
  3935.         settingsChange()
  3936.       elseif y == 16 then --# End Call Iris (OPEN/CLOSE/DO NOTHING)
  3937.         if type(dhdSettings.ecIris) == "boolean" then
  3938.           if dhdSettings.ecIris then
  3939.             dhdSettings.ecIris = false
  3940.           else
  3941.             dhdSettings.ecIris = "none"
  3942.           end
  3943.         elseif type(dhdSettings.ecIris) == "string" then
  3944.           dhdSettings.ecIris = true
  3945.         end
  3946.         drawElement(46, 16, dhdSettings.ecIris)
  3947.         settingsChange()
  3948.       end
  3949.     end
  3950.   end
  3951. end
  3952.  
  3953. local function inputLogs()
  3954.   local event, data, x, y
  3955.   while true do
  3956.     event, data, x, y = os.pullEvent()
  3957.     if event == "mouse_click" then
  3958.       if y == 2 and x > 44 and data == 1 then
  3959.         flashChoice(termX - 6, 2, white, red, " Close ")
  3960.         runState = "Dial"
  3961.         lastCall = nil
  3962.         clearCallHistory()
  3963.         drawElement(1, 5, termX, termY - 4, nil, black) --# clear lower screen
  3964.         return drawCLI()
  3965.       elseif y == 4 and x > 44 and data == 1 and callHistory[1] then
  3966.         flashChoice(termX - 6, 4, white, orange, " Clear ")
  3967.         drawElement(termX - 6, 4, 1, 1, orange, silver, " Clear ")
  3968.         runState = "clearLogs"
  3969.         return drawPopUp()
  3970.       elseif y == termY and data == 1 then
  3971.         if (x > 10 and x < 13 and logPage > 1) or (x > 36 and x < 39 and logPage < logPages) then --# Home / End
  3972.           logPage = x < 13 and 1 or logPages
  3973.           drawLogScreen()
  3974.         elseif (x > 13 and x < 15 and logPage > 1) or (x > 34 and x < 36 and logPage < logPages) then --# PageBack / PageForward
  3975.           logPage = x < 15 and logPage - 1 or logPage + 1
  3976.           drawLogScreen()
  3977.         elseif x > 18 and x < 33 and logPages > 1 then --# Page Entry
  3978.           local lPage, lPages = tostring(logPage), tostring(logPages)
  3979.           flashChoice((termX / 2) - 3 - ((#lPage + #lPages) / 2), termY, gray, silver, " " .. lPage .. " of " .. lPages .. " ")
  3980.           drawElement((termX / 2) - 3 - ((#lPage + #lPages) / 2), termY, 1, 1, silver, gray, " " .. lPage .. " of " .. lPages .. " ")
  3981.           runState = "goLogPage"
  3982.           return drawPopUp()
  3983.         end
  3984.       elseif y == 3 and x > 27 and x < 37 and lastCall then --# last call
  3985.         local callAddress = lastCall:sub(lastCall:find(">") + 2)
  3986.         local caLen = #callAddress
  3987.         if data == 1 and (caLen == 7 or caLen == 9) then
  3988.           return dialAddressFromLogs(callAddress)
  3989.         elseif data == 2 and (caLen == 7 or caLen == 9) then
  3990.           return addAddressFromLogs(callAddress)
  3991.         end
  3992.       elseif y > 5 and y < termY - 2 and x > 27 and x < 37 and callHistory[1] then --# call log
  3993.         local currentEntry = ((logPage - 1) * 11) + 1    --# Set the first entry (based on page number)
  3994.         if callHistory[currentEntry + y - 6] then
  3995.           local callAddress = callHistory[currentEntry + y - 6]:sub(callHistory[currentEntry + y - 6]:find(">") + 2)
  3996.           local caLen = #callAddress
  3997.           if data == 1 and (caLen == 7 or caLen == 9) then
  3998.             return dialAddressFromLogs(callAddress)
  3999.           elseif data == 2 and (caLen == 7 or caLen == 9) then
  4000.             return addAddressFromLogs(callAddress)
  4001.           end
  4002.         end
  4003.       end
  4004.     elseif event == "mouse_scroll" and ((data == 1 and logPage < logPages) or (data == -1 and logPage > 1)) then
  4005.       logPage = logPage + data
  4006.       drawLogScreen()
  4007.     elseif event == "key" and logPages > 1 then
  4008.       if (data == keys.home and logPage > 1) or (data == keys["end"] and logPage < logPages) then
  4009.         logPage = data == keys.home and 1 or logPages
  4010.         drawLogScreen()
  4011.       elseif (data == keys.pageUp and logPage > 1) or (data == keys.pageDown and logPage < logPages) then
  4012.         logPage = data == keys.pageUp and logPage - 1 or logPage + 1
  4013.         drawLogScreen()
  4014.       end
  4015.     end
  4016.   end
  4017. end
  4018.  
  4019. local function inputDial()
  4020.   local event, data, x, y
  4021.   while true do
  4022.     event, data, x, y = os.pullEvent()
  4023.     if event == "mouse_click" then
  4024.       --# Menu & Menu Selections
  4025.       if menuState then
  4026.         if x > 1 and x < 12 and y > 1 and y < 11 then
  4027.           if y == 3 and data == 1 then
  4028.             flashChoice(3, 3, white, sky, "Settings ")
  4029.             runState = "DHDsettings"
  4030.             menuState = false
  4031.             drawElement(1, 5, termX, termY - 4, nil, black) --# clear lower screen
  4032.             return drawSettingsScreen()
  4033.           elseif y == 5 and data == 1 then
  4034.             flashChoice(3, 5, black, yellow, "View Logs")
  4035.             runState = "logs"
  4036.             menuState = false
  4037.             ingestData("logs")
  4038.             drawElement(1, 5, termX, termY - 4, nil, black) --# clear lower screen
  4039.             return drawLogHeader()
  4040.           elseif y == 7 and data == 1 then
  4041.             flashChoice(3, 7, white, cyan, "DHD Help ")
  4042.             runState = "DHDhelp"
  4043.             menuState = false
  4044.             return drawHelpScreen()
  4045.           elseif y == 9 and data == 1 then
  4046.             flashChoice(3, 9, white, red, "  EXIT   ")
  4047.             if gateChange or configChange then
  4048.               clearMenu()
  4049.               runState = "exodus"
  4050.               return drawPopUp()
  4051.             else
  4052.               return shutDown()
  4053.             end
  4054.           end
  4055.         else
  4056.           clearMenu()
  4057.         end
  4058.       else
  4059.         if y == 1 and x > 1 and x < 7 and data == 1 then --# Open menu
  4060.           drawMenu()
  4061.         end
  4062.         --# Remote Iris
  4063.         if gateStatus == "Connected" and x > termX - 3 and y == 3 and data == 1 then
  4064.           tempState = runState
  4065.           runState = "remotePass"
  4066.           return drawPopUp()
  4067.         end
  4068.         --# Command Buttons
  4069.         if x > 42 and y > 6 and y < 16 and secureStatus == "allclear" then
  4070.           if y == 7 and data == 1 then      --# Iris
  4071.             if irisStatus == "Open" or irisStatus == "Opening" then
  4072.               netSend(dhdSettings.gate, { program = "ccDHD", command = "iCLOSE" })
  4073.             elseif (irisStatus == "Closed" or irisStatus == "Closing") and secureStatus == "allclear" then
  4074.               netSend(dhdSettings.gate, { program = "ccDHD", command = "iOPEN" })
  4075.             end
  4076.           elseif y == 9 and data == 1 then  --# END Call
  4077.             if gateStatus ~= "Disconnecting" and gateStatus ~= "Idle" then
  4078.               flashChoice(43, 9, orange, black, "END Call ")
  4079.               drawElement(43, 9, 1, 1, black, orange, "END Call ")
  4080.               netSend(dhdSettings.gate, { program = "ccDHD", command = "endCall" })
  4081.             end
  4082.           elseif y == 11 and data == 1 then --# LOCKDOWN
  4083.             flashChoice(43, 11, red, orange, "LOCKDOWN ")
  4084.             drawElement(43, 11, 1, 1, orange, red, "LOCKDOWN ")
  4085.             netSend(dhdSettings.gate, { program = "ccDHD", command = "lockdown" })
  4086.             return setupLockdown()
  4087.           elseif y == 13 then               --# AddrBook
  4088.             if data == 1 or (data == 2 and gateChange) then
  4089.               flashChoice(43, 13, blue, white, "AddrBook ")
  4090.               if data == 1 then
  4091.                 drawAddrBookButton()
  4092.                 runState = "importExport"
  4093.                 return drawPopUp()
  4094.               else
  4095.                 saveData(gateData, "gate")
  4096.                 drawAddrBookButton()
  4097.               end
  4098.             end
  4099.           elseif y == 15 then               --# New Gate
  4100.             if data == 1 or data == 3 then
  4101.               flashChoice(43, 15, green, white, "New Gate ")
  4102.               if data == 1 then
  4103.                 runState = "GateEdit"
  4104.                 return addNewAddress()
  4105.               else
  4106.                 drawElement(43, 15, 1, 1, white, green, "New Gate ")
  4107.                 addNewAddress(nil, true)
  4108.                 drawAddrBookButton()
  4109.               end
  4110.             end
  4111.           end
  4112.         end
  4113.         --# Page Navigation via click
  4114.         if y == termY and x > 9 and x < 32 and data == 1 and secureStatus == "allclear" then
  4115.           if (x > 9 and x < 12 and gatePage > 1) or (x > 29 and x < 32 and gatePage < gatePages) then --# Home / End
  4116.             gatePage = x < 12 and 1 or gatePages
  4117.             if gatePage == gatePages then drawElement(2, 6, 39, 11, nil, black) end --# clear data area
  4118.             drawAddressBook()
  4119.             drawNaviUI()
  4120.           elseif (x > 12 and x < 14 and gatePage > 1) or (x > 27 and x < 29 and gatePage < gatePages) then --# Back / Forward
  4121.             gatePage = x < 14 and gatePage - 1 or gatePage + 1
  4122.             if gatePage == gatePages then drawElement(2, 6, 39, 11, nil, black) end --# clear data area
  4123.             drawAddressBook()
  4124.             drawNaviUI()
  4125.           elseif x > 16 and x < 25 and gatePages > 1 then --# Page Numbers (Go To Page dialogue)
  4126.             flashChoice(((termX / 2) - 3) - (#pNum / 2) - 2, termY, black, gray, " " .. pNum .. " ")
  4127.             drawElement(((termX / 2) - 3) - (#pNum / 2) - 2, termY, 1, 1, gray, black, " " .. pNum .. " ")
  4128.             runState = "goPage"
  4129.             return drawPopUp()
  4130.           end
  4131.         end
  4132.         --# Dial a listed address, view it's info, or delete
  4133.         if x > 1 and x < 41 and y > 5 and y < 17 then
  4134.           local magicNumber = ((gatePage - 1) * 17) + gatePage
  4135.           local xPos, yPos = 2, 6
  4136.           for i = magicNumber, math.min(abCount, gatePage * 18) do
  4137.             if x >= xPos and x <= xPos + 11 and y == yPos then
  4138.               if data == 1 and gateStatus == "Idle" then --# Dial entry
  4139.                 if addressBook[i].addr == thisGate then break end
  4140.                 flashDial(addressBook[i].name, xPos, yPos, i)
  4141.                 dialAddress = addressBook[i].addr
  4142.                 netSend(dhdSettings.gate, { program = "ccDHD", command = dialAddress })
  4143.                 break
  4144.               elseif data == 1 and lcGate and (gateStatus == "Dialing" or gateStatus == "Paused") and dialAddress == addressBook[i].addr then
  4145.                 flashDial(addressBook[i].name, xPos, yPos, i)
  4146.                 netSend(dhdSettings.gate, { program = "ccDHD", command = "dialPause" })
  4147.                 break
  4148.               elseif data == 2 then --# View/Edit entry
  4149.                 flashDial(addressBook[i].name, xPos, yPos, i)
  4150.                 runState = "GateEdit"
  4151.                 currentEdit = i
  4152.                 return drawGateLabels()
  4153.               elseif data == 3 and abCount > 1 then --# Delete entry if there is more than one entry in the addressBook
  4154.                 local gMatch = addressBook[i].addr == thisGate
  4155.                 deleteGate(i)
  4156.                 if gatePage == gatePages then drawElement(2, 6, 39, 11, nil, black) end --# clear data area
  4157.                 if gMatch then drawFullHeader(false) end
  4158.                 drawAddressBook()
  4159.                 drawAddrBookButton()
  4160.                 drawNaviUI()
  4161.                 break
  4162.               end
  4163.             end
  4164.             yPos = yPos + 2
  4165.             if yPos > 16 then yPos = 6 xPos = xPos + 13 end
  4166.           end
  4167.         end
  4168.       end
  4169.     elseif event == "mouse_scroll" and ((data == 1 and gatePage < gatePages) or (data == -1 and gatePage > 1)) then
  4170.       gatePage = gatePage + data
  4171.       if menuState then
  4172.         menuState = false
  4173.         drawElement(2, 2, 10, 11, nil, black) --# clear menu
  4174.         drawFullHeader(true)
  4175.       end
  4176.       if gatePage == gatePages then drawElement(2, 6, 39, 11, nil, black) end --# clear data area
  4177.       drawAddressBook()
  4178.       drawNaviUI()
  4179.     elseif event == "key" then
  4180.       if data == keys.f1 then
  4181.         runState = "DHDhelp"
  4182.         return drawHelpScreen()
  4183.       elseif (data == keys.home and gatePage > 1) or (data == keys["end"] and gatePage < gatePages) then
  4184.         gatePage = data == keys.home and 1 or gatePages
  4185.         if gatePage == gatePages then drawElement(2, 6, 39, 11, nil, black) end --# clear data area
  4186.         drawAddressBook()
  4187.         drawNaviUI()
  4188.       elseif (data == keys.pageUp and gatePage > 1) or (data == keys.pageDown and gatePage < gatePages) then
  4189.         gatePage = data == keys.pageUp and gatePage - 1 or gatePage + 1
  4190.         if gatePage == gatePages then drawElement(2, 6, 39, 11, nil, black) end --# clear data area
  4191.         drawAddressBook()
  4192.         drawNaviUI()
  4193.       end
  4194.     end
  4195.   end
  4196. end
  4197.  
  4198. local function inputHelp()
  4199.   local event, data, x, y
  4200.   while true do
  4201.     event, data, x, y = os.pullEvent()
  4202.     if (event == "mouse_click" and x > 44 and y == 2 and data == 1) or (event == "key" and data == keys.f1) then --# DHD Help Screen (close button)
  4203.       flashChoice(termX - 6, 2, white, red, " Close ")
  4204.       drawElement(1, 5, termX, termY - 4, nil, black) --# clear lower screen
  4205.       runState = "Dial"
  4206.       return drawCLI()
  4207.     end
  4208.   end
  4209. end
  4210.  
  4211. local function inputLockdown()
  4212.   bgCol, txtCol = black, yellow
  4213.   term.setBackgroundColor(black)
  4214.   term.setTextColor(yellow)
  4215.   repeat
  4216.     term.setCursorPos(math.floor(termX / 2), 15)
  4217.     local passwordInput = table.concat(pbkdf2(read(nil, nil, 12, true, true), "ccDHD!Pass.Hash", 15))
  4218.     if passwordInput == dhdSettings.password then
  4219.       netSend(dhdSettings.gate, { program = "ccDHD", command = "allclear" })
  4220.       clearLockdown()
  4221.     end
  4222.     term.setCursorPos(math.floor(termX / 2), 15)
  4223.     term.write(string.rep(" ", 12))
  4224.   until secureStatus == "allclear"
  4225. end
  4226.  
  4227. local function inputMonTouch()
  4228.   local _, monSide, touchX, touchY, touchRegistered
  4229.   while true do
  4230.     _, monSide, touchX, touchY = os.pullEvent("monitor_touch")
  4231.     touchRegistered = false
  4232.     if secureStatus == "allclear" and hardware.listMon > 0 then
  4233.       for i = 1, hardware.listMon do
  4234.         if monSide == listMonSides[i] then touchRegistered = true break end
  4235.       end
  4236.       if touchRegistered and displayState == "info" then
  4237.         displayState = "list"
  4238.         if dhdSettings.detailedMarquee then
  4239.           initMarquee = true
  4240.           displayStatusDetail()
  4241.         else
  4242.           displayStatus()
  4243.         end
  4244.         if gateStatus ~= "Idle" then displayAddressBook() end
  4245.       elseif touchRegistered and displayState == "list" then
  4246.         if touchY < 9 then
  4247.           selectedGate = (listPage - 1) * 8 + touchY
  4248.           if selectedGate <= abCount then
  4249.             displayState = "info"
  4250.             displayNotes()
  4251.           end
  4252.         elseif touchY > 8 and ((touchX < 10 and listPage > 1) or (touchX > 9 and listPage < listPages)) then
  4253.           listPage = touchX < 10 and math.max(1, listPage - 1) or math.min(listPage + 1, listPages)
  4254.           displayAddressBook()
  4255.         end
  4256.       end
  4257.     end
  4258.     if not touchRegistered and hardware.marquee > 0 then
  4259.       for i = 1, hardware.marquee do
  4260.         if monSide == marqueeSides[i] then touchRegistered = true break end
  4261.       end
  4262.       if touchRegistered and displayState == "info" and gateStatus == "Idle" and secureStatus == "allclear" then
  4263.         if addressBook[selectedGate].addr ~= thisGate then
  4264.           dialAddress = addressBook[selectedGate].addr
  4265.           netSend(dhdSettings.gate, { program = "ccDHD", command = dialAddress })
  4266.         end
  4267.       elseif touchRegistered and displayState == "info" and lcGate and (gateStatus == "Dialing" or gateStatus == "Paused") and addressBook[selectedGate].addr == dialAddress and secureStatus == "allclear" then
  4268.         netSend(dhdSettings.gate, { program = "ccDHD", command = "dialPause" })
  4269.       elseif touchRegistered and (gateStatus == "Connected" or gateStatus == "Dialing" or gateStatus == "Paused") then --# not sure about (lcGate with gateStatus == "Dialing")
  4270.         netSend(dhdSettings.gate, { program = "ccDHD", command = "endCall" })
  4271.       end
  4272.     end
  4273.     if not touchRegistered and hardware.gateMon > 0 then
  4274.       for i = 1, hardware.gateMon do
  4275.         if monSide == gateMonSides[i] then touchRegistered = true break end
  4276.       end
  4277.       if touchRegistered and gateMonList then
  4278.         if touchY < 18 and touchX < 7 then
  4279.           local magicNumber = (gateMonPage - 1) * 17 + touchY
  4280.           if magicNumber <= abCount then
  4281.             if dialAddress ~= "none" or incomingAddress then
  4282.               netSend(dhdSettings.gate, { program = "ccDHD", command = "endCall" })
  4283.             else
  4284.               if addressBook[magicNumber].addr ~= thisGate and secureStatus == "allclear" then
  4285.                 dialAddress = addressBook[magicNumber].addr
  4286.                 netSend(dhdSettings.gate, { program = "ccDHD", command = dialAddress })
  4287.               end
  4288.             end
  4289.           end
  4290.         elseif touchY > 18 then
  4291.           if (touchX < math.floor(29 / 2) - 2 and gateMonPage > 1) or (touchX > math.ceil(29 / 2) + 3 and gateMonPage < gateMonPages) then
  4292.             gateMonPage = touchX < math.floor(29 / 2) - 2 and math.max(gateMonPage - 1, 1) or math.min(gateMonPage + 1, gateMonPages)
  4293.             displayGateMonAddrBook()
  4294.           elseif touchX > math.floor(29 / 2) - 3 and touchX < math.ceil(29 / 2) + 4 then
  4295.             gateMonList = false
  4296.             for i = 1, hardware.gateMon do
  4297.               gateMon[i].setBackgroundColor(black)
  4298.               gateMon[i].clear()
  4299.             end
  4300.             displayGate()
  4301.           end
  4302.         end
  4303.       elseif touchRegistered and not gateMonList and secureStatus == "allclear" then
  4304.         for i = 1, hardware.gateMon do
  4305.           gateMon[i].setBackgroundColor(black)
  4306.           gateMon[i].clear()
  4307.         end
  4308.         gateMonList = true
  4309.         displayGateMonAddrBook()
  4310.       end
  4311.     end
  4312.   end
  4313. end
  4314.  
  4315. local function inputBioScan() --# "biolock", userIDprint, scannerAttachName, userName, userAccessLevel
  4316.   while true do
  4317.     local fistPrint = { os.pullEvent("biolock") }
  4318.     if fistPrint[1] == "biolock" then --# this is here because ^T otherwise causes an error 2/4 lines down during lockdown
  4319.       if runState == "init" then
  4320.         if fistPrint[5] >= dhdSettings.bio.auth then return end
  4321.       else
  4322.         if fistPrint[5] >= dhdSettings.bio.fAuth then
  4323.           if dhdSettings.bio.func == "iris" then
  4324.             if irisStatus == "Open" or irisStatus == "Opening" then
  4325.               netSend(dhdSettings.gate, { program = "ccDHD", command = "iCLOSE" })
  4326.             elseif (irisStatus == "Closed" or irisStatus == "Closing") and secureStatus == "allclear" then
  4327.               netSend(dhdSettings.gate, { program = "ccDHD", command = "iOPEN" })
  4328.             end
  4329.           elseif dhdSettings.bio.func == "lock" then
  4330.             if secureStatus == "allclear" then
  4331.               netSend(dhdSettings.gate, { program = "ccDHD", command = "lockdown" })
  4332.               return setupLockdown()
  4333.             else
  4334.               netSend(dhdSettings.gate, { program = "ccDHD", command = "allclear" })
  4335.               return clearLockdown()
  4336.             end
  4337.           end
  4338.         end
  4339.       end
  4340.     end
  4341.   end
  4342. end
  4343.  
  4344. local function userInput()
  4345.   repeat
  4346.     if secureStatus == "lockdown" then
  4347.       inputLockdown()
  4348.     elseif validStates[runState] then
  4349.       validStates[runState][4]()
  4350.     end
  4351.   until not kernelState
  4352. end
  4353.  
  4354. local function dataPoller()
  4355.   local _, timer, hours, minutes, seconds, sMinutes, sSeconds, sTenths
  4356.   while true do
  4357.     _, timer = os.pullEvent("timer")
  4358.     if timer == connectionTimer and gateStatus == "Connected" then
  4359.       connectionTime = connectionTime + 1
  4360.       connectionTimer = os.startTimer(0.1)
  4361.       hours = connectionTime > 35999 and math.floor(connectionTime / 36000) or 0
  4362.       minutes = connectionTime > 599 and math.floor((connectionTime - (hours * 36000)) / 600) or 0
  4363.       sMinutes = minutes > 9 and tostring(minutes) or "0" .. tostring(minutes)
  4364.       seconds = connectionTime > 9 and math.floor((connectionTime - ((hours * 36000) + (minutes * 600))) / 10) or 0
  4365.       sSeconds = seconds > 9 and tostring(seconds) or "0" .. tostring(seconds)
  4366.       sTenths = tostring(math.floor(connectionTime - ((hours * 36000) + (minutes * 600) + (seconds * 10))))
  4367.       connectionClock = sMinutes .. ":" .. sSeconds .. ":" .. sTenths
  4368.       displayConnectionTime()
  4369.     elseif timer == pingTimer then
  4370.       if clientCount > 0 then
  4371.         for id, timeout in pairs(clients) do
  4372.           clients[id] = timeout + 1
  4373.           if clients[id] > 2 then
  4374.             clients[id] = nil
  4375.             clientCount = math.max(0, clientCount - 1)
  4376.           else
  4377.             netSend(id, { program = "ccDialer", data = "ping" }, "ccDialerWiFi")
  4378.           end
  4379.         end
  4380.       end
  4381.       pingTimer = os.startTimer(5)
  4382.     end
  4383.   end
  4384. end
  4385.  
  4386. local function startupError(id)
  4387.   clearScreen()
  4388.   if id == "firstRunModem" then
  4389.     drawElement(2, 2, 1, 1, red, black, "gateLiaison or modem not detected!")
  4390.     drawElement(2, 4, 1, 1, gray, nil, "A modem and gateLiaison are REQUIRED.")
  4391.   elseif id == "modem" then
  4392.     drawElement(2, 2, 1, 1, red, black, "No modem detected!")
  4393.     drawElement(2, 4, 1, 1, gray, nil, "A modem is REQUIRED.")
  4394.   elseif id == "hardware" then
  4395.     local isColor = term.isColor()
  4396.     drawElement(2, 2, 1, 1, isColor and red or white, black, "Incorrect hardware detected!")
  4397.     drawElement(2, 4, 1, 1, isColor and gray or white, nil, "Advanced computer REQUIRED.")
  4398.   elseif id == "resolution" then
  4399.     drawElement(2, 2, 1, 1, red, black, "Screen size is wrong!")
  4400.     drawElement(2, 4, 1, 1, gray, nil, "51 x 18 or 51 x 19 REQUIRED.")
  4401.   end
  4402.   term.setCursorPos(1, 7)
  4403. end
  4404.  
  4405. local function addMonitor(attachName)
  4406.   peripheral.call(attachName, "setTextScale", 1)
  4407.   local monX, monY = peripheral.call(attachName, "getSize")
  4408.   if monX == 7 and monY == 5 then       --# 1x1 monitor
  4409.     hardware.listMon = hardware.listMon + 1
  4410.     listMon[hardware.listMon] = peripheral.wrap(attachName)
  4411.     listMonSides[hardware.listMon] = attachName
  4412.   elseif monX == 18 and monY == 5 then  --# 2x1 monitor array
  4413.     hardware.timerMon = hardware.timerMon + 1
  4414.     timerMon[hardware.timerMon] = peripheral.wrap(attachName)
  4415.     timerMonSides[hardware.timerMon] = attachName
  4416.     timerMon[hardware.timerMon].setTextScale(2.5)
  4417.   elseif monX == 29 and monY == 5 then  --# 3x1 monitor array
  4418.     hardware.marquee = hardware.marquee + 1
  4419.     marquee[hardware.marquee] = peripheral.wrap(attachName)
  4420.     marqueeSides[hardware.marquee] = attachName
  4421.   elseif monX == 29 and monY == 19 then --# 3x3 monitor array
  4422.     hardware.gateMon = hardware.gateMon + 1
  4423.     gateMon[hardware.gateMon] = peripheral.wrap(attachName)
  4424.     gateMonSides[hardware.gateMon] = attachName
  4425.   end
  4426. end
  4427.  
  4428. local function detectHardware(hwType, firstRunNow)
  4429.   if hwType == "bio" then
  4430.     local tempBio = { peripheral.find("biolock") }
  4431.     hardware.bio = #tempBio
  4432.     drawElement(16, 8, 1, 1, hardware.bio > 0 and green or red, gray, hardware.bio > 0 and "O" or "0")
  4433.   elseif hwType == "modem" then
  4434.     local missed, id
  4435.     for _, side in pairs(rs.getSides()) do
  4436.       if peripheral.isPresent(side) and peripheral.getType(side) == "modem" then
  4437.         if peripheral.call(side, "isWireless") then
  4438.           if hardware.wifiSide == "none" then
  4439.             hardware.wifiSide = side
  4440.             rednet.open(side)
  4441.           end
  4442.         elseif hardware.modemSide == "none" then
  4443.           for _, name in pairs(peripheral.call(side, "getNamesRemote")) do
  4444.             if peripheral.getType(name) == "computer" and peripheral.call(side, "isPresentRemote", name) then
  4445.               id = peripheral.call(side, "callRemote", name, "getID")
  4446.               rednet.open(side)
  4447.               if firstRunNow then
  4448.                 missed = 0
  4449.                 repeat
  4450.                   gateLiaison = rednet.lookup("ccDHDSetup")
  4451.                   if not gateLiaison then missed = missed + 1 sleep(1) end
  4452.                 until (gateLiaison or missed > 9)
  4453.               end
  4454.               if (firstRunNow and id == gateLiaison) or id == dhdSettings.gate then
  4455.                 hardware.modemSide = side
  4456.                 break
  4457.               else
  4458.                 rednet.close(side)
  4459.               end
  4460.             end
  4461.           end
  4462.         end
  4463.         if hardware.modemSide ~= "none" and hardware.wifiSide ~= "none" then break end
  4464.       end
  4465.     end
  4466.   elseif hwType == "mon" then
  4467.     for _, side in pairs(rs.getSides()) do
  4468.       if peripheral.isPresent(side) then
  4469.         if peripheral.getType(side) == "monitor" and peripheral.call(side, "isColor") then
  4470.           addMonitor(side)
  4471.         elseif peripheral.getType(side) == "modem" and not peripheral.call(side, "isWireless") then
  4472.           for _, name in pairs(peripheral.call(side, "getNamesRemote")) do
  4473.             if peripheral.getType(name) == "monitor" and peripheral.call(name, "isColor") then
  4474.               addMonitor(name)
  4475.             end
  4476.           end
  4477.         end
  4478.       end
  4479.     end
  4480.   end
  4481. end
  4482.  
  4483. local function waitAnimation() --# intentionally not using drawElement - less expense
  4484.   local xA, xB, y, bgColor
  4485.   if currentState == "init" then
  4486.     xA, xB, y, bgColor = 9, 25, 8, cyan --# gateLiaison QRY
  4487.   else
  4488.     xA, xB, y, bgColor = 2, 20, 4, blue --# Biolock
  4489.   end
  4490.   while true do
  4491.     for i = xA, xB do
  4492.       term.setCursorPos(i, y)
  4493.       term.setBackgroundColor(bgColor)
  4494.       term.write(" ")
  4495.       term.setBackgroundColor(black)
  4496.       term.setCursorPos(i - 1, y)
  4497.       term.write(" ")
  4498.       sleep(0.04)
  4499.     end
  4500.     for i = xB, xA, -1 do
  4501.       term.setCursorPos(i, y)
  4502.       term.setBackgroundColor(bgColor)
  4503.       term.write(" ")
  4504.       term.setBackgroundColor(black)
  4505.       term.setCursorPos(i + 1, y)
  4506.       term.write(" ")
  4507.       sleep(0.04)
  4508.     end
  4509.   end
  4510. end
  4511.  
  4512. local function waitingNet()
  4513.   local id, message, encKey, encryptedMessage, decryptedMessage, encodedMessage, success
  4514.   while true do
  4515.     if not gateLiaison then gateLiaison = rednet.lookup("ccDHDSetup") end
  4516.     if gateLiaison then
  4517.       dhdSettings.gate = gateLiaison
  4518.       while true do
  4519.         netSend(dhdSettings.gate, { program = "ccDHD", command = "1stRun" }, "ccDHDSetup")
  4520.         id, encodedMessage = rednet.receive("ccDHDSetup", 3)
  4521.         if id == dhdSettings.gate and type(encodedMessage) == "string" then
  4522.           success, encryptedMessage = pcall(decode, encodedMessage)
  4523.           if success then
  4524.             encKey = thisCC .. "ccDHD!General_Comms*Key" .. tostring(id)
  4525.             success, decryptedMessage = pcall(decrypt, encKey, encryptedMessage)
  4526.             if success then
  4527.               success, message = pcall(textutils.unserialize, decryptedMessage)
  4528.               if success and type(message) == "table" and message.program and message.program == "ccDHD" and message.thisGate then
  4529.                 thisGate = message.thisGate
  4530.                 return
  4531.               end
  4532.             end
  4533.           end
  4534.         end
  4535.       end
  4536.     else
  4537.       sleep(3)
  4538.     end
  4539.   end
  4540. end
  4541.  
  4542. if not term.isColor() or pocket or turtle then return startupError("hardware") end
  4543. if termX ~= 51 or termY < 18 or termY > 19 then return startupError("resolution") end
  4544. clearScreen()
  4545. local firstRunNow = false
  4546. if not fs.exists(settingsData) then --# First run
  4547.   if not fs.exists("/data") then fs.makeDir("/data") end
  4548.   if not os.getComputerLabel() then os.setComputerLabel("ccDHD.cc#" .. thisCC) end --# Check and, if necessary, set computer label
  4549.   firstRunNow = true
  4550.   term.setCursorPos(1, 1)
  4551.   term.write("ccDHD . . . First Run . . . Initializing . . .")
  4552.   detectHardware("modem", true)
  4553.   if hardware.wifiSide == "none" and hardware.modemSide == "none" then return startupError("firstRunModem") end
  4554.   term.clear()
  4555.   drawElement(2, 2, 1, 1, nil, nil, "Please start gateLiaison to complete setup.")
  4556.   drawElement(2, 4, 1, 1, nil, nil, "ccDHD will automatically start")
  4557.   drawElement(2, 5, 1, 1, nil, nil, "after gateLiaison is initialized.")
  4558.   drawElement(2, 7, 1, 1, gray, nil, "... waiting for gateLiaison ...")
  4559.   parallel.waitForAny(waitingNet, waitAnimation)
  4560.   dhdSettings.password = table.concat(pbkdf2(dhdSettings.password, "ccDHD!Pass.Hash", 15))
  4561.   dhdSettings.irisPassword = table.concat(pbkdf2(dhdSettings.irisPassword, "ccDHD!Pass.Hash", 15))
  4562.   dhdSettings.hashedPWs, dhdSettings.newHash = true, true
  4563.   saveData(settingsData, "cfg")
  4564.   clearScreen()
  4565. else
  4566.   ingestData(settingsData, "cfg")
  4567. end
  4568. if not dhdSettings.newHash then
  4569.   local lockPW, irisPW
  4570.   drawElement(2, 2, 1, 1, red, nil, "ccDHD PASSWORD UPGRADE")
  4571.   drawElement(2, 4, 1, 1, white, nil, "Please enter the LOCKDOWN password:")
  4572.   while true do
  4573.     term.setCursorPos(38, 4)
  4574.     lockPW = read("*", nil, 12, nil, true)
  4575.     if table.concat(pbkdf2(lockPW, "ccDHD!pSync", 15)) == dhdSettings.password then
  4576.       dhdSettings.password = table.concat(pbkdf2(lockPW, "ccDHD!Pass.Hash", 15))
  4577.       break
  4578.     end
  4579.   end
  4580.   drawElement(2, 6, 1, 1, nil, nil, "Please enter the IRIS password:")
  4581.   while true do
  4582.     term.setCursorPos(34, 6)
  4583.     irisPW = read("*", nil, 12, nil, true)
  4584.     if table.concat(pbkdf2(irisPW, "ccDHD!pSync", 15)) == dhdSettings.irisPassword then
  4585.       dhdSettings.irisPassword = table.concat(pbkdf2(irisPW, "ccDHD!Pass.Hash", 15))
  4586.       break
  4587.     end
  4588.   end
  4589.   dhdSettings.newHash = true
  4590.   saveData(settingsData, "cfg")
  4591.   clearScreen()
  4592. end
  4593. ccLabel = os.getComputerLabel():sub(1, 15)
  4594. drawElement(15, 4, 22, 1, white, blue, "ccDHD Initializing")
  4595. drawElement(15, 5, 22, 10, nil, gray)
  4596. local initSteps = {
  4597.   "Hardware Discovery";
  4598.   "Monitors";
  4599.   "Biometric Scanner"; --# Make this config data and move to top once bio is removed?
  4600.   "Modem/Network";
  4601.   "Query gateLiaison";
  4602.   "Await Response";
  4603.   "Host Sync";
  4604.   "Load address book";
  4605. }
  4606. for i = 6, 13 do
  4607.   drawElement(16, i, 1, 1, red, nil, "0")
  4608.   drawElement(18, i, 1, 1, silver, nil, initSteps[i - 5])
  4609. end
  4610. if tArgs[1] then
  4611.   local validSides = { left = true, right = true, top = true, bottom = true, front = true, back = true }
  4612.   for i = 1, #tArgs do
  4613.     if tArgs[i] == "outgoing" then
  4614.       outgoingAlarm = true
  4615.     elseif validSides[tArgs[i]] then
  4616.       rsSide = tArgs[i]
  4617.     end
  4618.   end
  4619. end
  4620. drawElement(18, 6, 1, 1, white, nil, "Hardware Discovery")
  4621. drawElement(18, 7, 1, 1, nil, nil, "Monitors")
  4622. drawElement(16, 6, 1, 1, orange, nil, "0")  --# Hardware Discovery
  4623. drawElement(16, 7, 1, 1, nil, nil, "0")     --# Monitors
  4624. detectHardware("mon")
  4625. if hardware.listMon > 0 or hardware.marquee > 0 or hardware.gateMon > 0 then
  4626.   drawElement(16, 7, 1, 1, green, nil, "O") --# Monitors
  4627. else
  4628.   drawElement(16, 7, 1, 1, red, nil, "0")
  4629. end
  4630. if hardware.listMon > 0 then
  4631.   for i = 1, hardware.listMon do
  4632.     listMon[i].setBackgroundColor(white)
  4633.     listMon[i].setTextColor(black)
  4634.     listMon[i].clear()
  4635.     listMon[i].setTextScale(1)
  4636.     listMon[i].setCursorPos(1, 3)
  4637.     listMon[i].write("Init...")
  4638.   end
  4639. end
  4640. if hardware.timerMon > 0 then
  4641.   for i = 1, hardware.timerMon do
  4642.     timerMon[i].setBackgroundColor(white)
  4643.     timerMon[i].setTextColor(black)
  4644.     timerMon[i].clear()
  4645.     timerMon[i].setCursorPos(2, 1)
  4646.     timerMon[i].write("ccDHD")
  4647.     timerMon[i].setCursorPos(1, 2)
  4648.     timerMon[i].write("Init...")
  4649.   end
  4650. end
  4651. if hardware.marquee > 0 then
  4652.   for i = 1, hardware.marquee do
  4653.     marquee[i].setBackgroundColor(white)
  4654.     marquee[i].setTextColor(black)
  4655.     marquee[i].clear()
  4656.     marquee[i].setTextScale(2)
  4657.     marquee[i].setCursorPos(5, 1)
  4658.     marquee[i].write("ccDHD")
  4659.     marquee[i].setCursorPos(2, 2)
  4660.     marquee[i].write("Initializing")
  4661.   end
  4662. end
  4663. if hardware.gateMon > 0 then
  4664.   for i = 1, hardware.gateMon do
  4665.     gateMon[i].setBackgroundColor(white)
  4666.     gateMon[i].setTextColor(black)
  4667.     gateMon[i].clear()
  4668.     gateMon[i].setTextScale(2)
  4669.     gateMon[i].setCursorPos(5, 1)
  4670.     gateMon[i].write("ccDHD")
  4671.     gateMon[i].setCursorPos(2, 2)
  4672.     gateMon[i].write("Initializing")
  4673.   end
  4674. end
  4675. drawElement(16, 8, 1, 1, orange, nil, "0")  --# Biolocks
  4676. drawElement(18, 8, 1, 1, white, nil, "Biometric Scanner")
  4677. detectHardware("bio")
  4678. drawElement(18, 9, 1, 1, white, nil, "Modem/Network")
  4679. if firstRunNow then
  4680.   drawElement(16, 6, 1, 1, green, nil, "O") --# Hardware Discovery complete
  4681.   drawElement(16, 9, 1, 1, nil, nil, "O")   --# Modem/Network
  4682. else
  4683.   detectHardware("modem")
  4684.   if hardware.wifiSide == "none" and hardware.modemSide == "none" then
  4685.     clearMonitors()
  4686.     return startupError("modem")
  4687.   else
  4688.     drawElement(16, 6, 1, 1, green, gray, "O") --# Hardware Discovery complete
  4689.     drawElement(16, 9, 1, 1, nil, nil, "O")    --# Modem/Network
  4690.   end
  4691. end
  4692. if hardware.wifiSide == "none" then dhdSettings.sync = false end
  4693. drawElement(16, 10, 1, 1, orange, nil, "0") --# Query gateLiaison
  4694. drawElement(18, 10, 1, 1, white, nil, "Query gateLiaison")
  4695. netSend(dhdSettings.gate, { program = "ccDHD", command = "QRY" })
  4696. drawElement(16, 10, 1, 1, green, nil, "O")  --# Query gateLiaison
  4697. drawElement(16, 11, 1, 1, orange, nil, "0") --# Await response
  4698. drawElement(18, 11, 1, 1, white, nil, "Await response")
  4699. local missed, id, message, encryptedMessage, decryptedMessage, encodedMessage, encKey, success = 0
  4700. while true do
  4701.   id, encodedMessage = rednet.receive("ccDHD", 1)
  4702.   if id == dhdSettings.gate and type(encodedMessage) == "string" then
  4703.     success, encryptedMessage = pcall(decode, encodedMessage)
  4704.     if success then
  4705.       encKey = thisCC .. "ccDHD!General_Comms*Key" .. tostring(id)
  4706.       success, decryptedMessage = pcall(decrypt, encKey, encryptedMessage)
  4707.       if success then
  4708.         success, message = pcall(textutils.unserialize, decryptedMessage)
  4709.         if success and type(message) == "table" then
  4710.           updateStatus(message)
  4711.           drawElement(16, 11, 1, 1, green, nil, "O") --# Await response
  4712.           break
  4713.         end
  4714.       end
  4715.     end
  4716.   end
  4717.   missed = missed + 1
  4718.   if missed > 9 then
  4719.     clearMonitors()
  4720.     clearScreen()
  4721.     if hardware.modemSide ~= "none" and rednet.isOpen(hardware.modemSide) then rednet.close(hardware.modemSide) end
  4722.     if hardware.wifiSide ~= "none" and rednet.isOpen(hardware.wifiSide) then rednet.close(hardware.wifiSide) end
  4723.     drawElement(2, 2, 1, 1, red, nil, "Unable to syncronize with gateLiaison")
  4724.     term.setCursorPos(1, 4)
  4725.     return
  4726.   end
  4727.   netSend(dhdSettings.gate, { program = "ccDHD", command = "QRY" })
  4728. end
  4729. drawElement(18, 12, 1, 1, white, nil, "Host Sync")
  4730. if dhdSettings.sync then
  4731.   drawElement(16, 12, 1, 1, orange, nil, "0") --# Host Sync
  4732.   rednet.host("ccDialerWiFi", thisGate)
  4733.   drawElement(16, 12, 1, 1, green, nil, "O")  --# Host Sync
  4734. else
  4735.   drawElement(16, 12, 1, 1, red, nil, "0")    --# Host Sync
  4736. end
  4737. drawElement(16, 13, 1, 1, orange, nil, "0")   --# Load address book
  4738. drawElement(18, 13, 1, 1, white, nil, "Load address book")
  4739. if fs.exists(gateData) then
  4740.   ingestData(gateData, "gate")
  4741. else
  4742.   addressBook[1].name, addressBook[1].addr = thisGate, thisGate
  4743.   saveData(gateData, "gate")
  4744. end
  4745. drawElement(16, 13, 1, 1, green, nil, "O")    --# Load address book
  4746. os.pullEvent = os.pullEventRaw
  4747. clearMonitors()
  4748. if hardware.bio > 0 and dhdSettings.bio.lock then --# Bioscanner login
  4749.   clearScreen()
  4750.   drawElement(2, 2, 1, 1, orange, nil, "Waiting for Bioscan")
  4751.   if hardware.listMon > 0 then
  4752.     for i = 1, hardware.listMon do
  4753.       listMon[i].setTextColor(blue)
  4754.       listMon[i].setCursorPos(1, 2)
  4755.       listMon[i].write("Waiting")
  4756.       listMon[i].setCursorPos(3, 3)
  4757.       listMon[i].write("for")
  4758.       listMon[i].setCursorPos(1, 4)
  4759.       listMon[i].write("Bioscan")
  4760.     end
  4761.   end
  4762.   parallel.waitForAny(inputBioScan, waitAnimation)
  4763. end
  4764. validStates = { --# [1] = lockdown, [2] = clear lockdown, [3] = drawCLI, [4] = userInput, [5] = update screen during gate/iris activity
  4765.   Dial = { "Dial", function() end, function() drawFullHeader(true) drawAddressBook() drawControlUI() drawNaviUI() end, function() inputDial() end, true };
  4766.   remotePass = { "Dial", function() end, function() drawSubheader() drawAddressBook() drawControlUI() if gateChange then drawNaviUI() end drawPopUp() end, function() inputRemotePassword() end, true };
  4767.   goPage = { "Dial", function() end, function() drawSubheader() drawAddressBook() drawControlUI() if gateChange then drawNaviUI() end drawPopUp() end, function() inputGoToPage() end, true };
  4768.   importExport = { "Dial", function() end, function() drawSubheader() drawAddressBook() drawControlUI() if gateChange then drawNaviUI() end drawPopUp() end, function () inputImportExportPopUp() end, true };
  4769.   exodus = { "Dial", function() end, function() drawSubheader() drawAddressBook() drawControlUI() if gateChange then drawNaviUI() end drawPopUp() end, function() inputSaveAndQuitPopUp() end, true };
  4770.   GateEdit = { "GateEdit", function() drawGateLabels() end, function() drawSubheader() end, function() inputEditGateEntry() end, true }; --# draw subheader for incoming/outgoing calls
  4771.   DHDhelp = { "DHDhelp", function() drawHelpScreen() end, function() end, function() inputHelp() end, false };
  4772.   DHDsettings = { "DHDsettings", function() drawSettingsScreen() end, function() end, function () inputSettings() end, false };
  4773.   newPassword = { "DHDsettings", function() end, function() end, function () inputSelectPassword() end, false };
  4774.   newLockPass = { "DHDsettings", function() end, function() end, function () inputChangeLockdownPassword() end, false };
  4775.   newIrisPass = { "DHDsettings", function() end, function() end, function () inputChangeIrisPassword() end, false };
  4776.   logs = { "logs", function() drawLogHeader() end, function() end, function() inputLogs() end, false };
  4777.   clearLogs = { "logs", function() end, function() end, function() inputClearLogsPopUp() end, false };
  4778.   goLogPage = { "logs", function() end, function() end, function() inputGoToLogPage() end, false };
  4779. }
  4780. if hardware.listMon > 0 then
  4781.   for i = 1, hardware.listMon do
  4782.     listMon[i].setTextScale(0.5)
  4783.   end
  4784. end
  4785. if hardware.gateMon > 0 then
  4786.   for i = 1, hardware.gateMon do
  4787.     gateMon[i].setTextScale(1)
  4788.   end
  4789. end
  4790. kernelState = true
  4791. runState = "Dial"
  4792. displayConnectionTime()
  4793. displayGate()
  4794. clearScreen()
  4795. drawCLI(true, true)
  4796. pingTimer = os.startTimer(5)
  4797. if gateStatus == "Connected" then connectionTimer = os.startTimer(0.1) end
  4798. repeat
  4799.   parallel.waitForAny(userInput, inputMonTouch, inputBioScan, netReceive, syncReceive, dataPoller)
  4800. until not kernelState
Add Comment
Please, Sign In to add comment